Skip to content

ACP: Add a slim_mem_ops flag #907

Open

Description

@kleinesfilmroellchen

Proposal

Problem statement

When compiling the standard library via build_std, despite -C opt-level=s and the optimize_for_size feature, some standard library functionality uses an unreasonable amount of code size. It is not currently possible to prioritize code size above all else in the standard library, even to the detriment of execution speed.

Motivating examples or use cases

Some classes of embedded systems supported by Rust are extremely memory-constrained. Examples from my own experience, which include some widely-used platforms:

  • The CH32V003 is a popular extreme-low-cost microcontroller with 2 KiB of RAM and 16 KiB of flash (=code and constant data storage).
  • The ATMega 328P, possibly one of the most widely-used microcontrollers of all time as the main component of the Arduino UNO, offers 2 KiB RAM and 32 KiB flash. Other AVR microcontrollers, especially ones from the ATTiny family, have even more restricted memory.
  • From my own work, the PebbleOS "Aplite" platform runs on an STM32F205, where userland applications are restricted to 24 KiB of total memory.

These are three different architectures (32-bit RISC-V, 8-bit AVR, 32-bit ARM), roughly from three different decades (2020s, 2000s, 2010s), sharing approximately the same level of memory constraint1. Many other platforms can be listed here too. This goes to show that these strong memory constraints are not just a feature of legacy systems, and that we can expect them to stick around for a long time.

While -C opt-level=s and sometimes -C opt-level=z, as well as multiple unstable Rust features like build_std, the standard library feature optimize_for_size, and -C panic=immediate-abort exist to decrease the code size of Rust programs, they are not always enough. On platforms like the ones listed, especially newer and relatively fast2 microcontrollers, the code size vs. speed tradeoff is often different from larger embedded platforms: Code size will be the limiting factor and must be decreased by all means, possibly at the cost of performance.

Solution sketch

I propose to introduce a new standard library feature slim_mem_ops, similar to the existing optimize_for_size. This feature is unstable as it can only be invoked through build_std, and their stabilization together with build_std is not proposed.

slim_mem_ops enables alternative implementations of standard memory operations, mainly everything in compile_builtins::mem and core::mem, which intentionally optimize the code for size at the detriment of all other properties, especially performance. These features are specifically intended for extremely size-constrained platforms like the ones discussed above. Users should be aware that while the generated code is almost certainly going to be much smaller, it may also perform much worse.3

History: The original intent of the ACP was to introduce the more generic optimize_for_size_aggressive, which has been deemed both too generic and not motivated enough. The new solution focuses only on the proposed changes in compiler_builtins::mem APIs, such as memcpy, memcmp, etc.

In the future, this flag could serve as a starting point for splitting up optimize_for_size into several smaller flags, and thinking more critically about whether optimize_for_size should exist and what its scope should be.

Alternatives

  • The extremely size-optimized memcpy/memset versions I proposed could be put behind the existing optimize_for_size flag. However, as has become clear from the discussion, that would be undesirable for many platforms which still want to use optimize_for_size to remove other large implementations in the standard library, since these particular implementations decrease performance a lot compared to existing optimize_for_size tweaks.
  • A more generic flag, like optimize_for_size_aggressive which was originally proposed by this ACP. This flag has been deemed too generic and not sufficiently motivated by enough users in the standard library.
  • Do this automatically when -Oz (or some other optimization level) is enabled. This first runs into the issue of -Oz frequently being worse than -Os at size optimization. Second, configuring these library options independent of the general compiler optimization level is desirable in many scenarios (e.g. some targets might want -O3 but still optimize_for_size, and in many cases the perfect combination will be -Os with slim_mem_ops).
  • Alternative ways of achieving the memcpy/memset improvements in my PR. This is sometimes possible through a combination of #![no_builtins], externally implementable items (an incomplete early-stages experimental compiler feature), and (finicky) correct use of compiler and linker flags. As has become apparent through discussion and experiments, achieving this is usually difficult for end-users, and requires them (or dependent crates) to supply their own implementations, even though there are only a few useful implementations of these basic functions. Overall, this path is a large, generic, low-level and difficult-to-use hammer for what ultimately amounts to a simple problem.

Links and related work

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Footnotes

  1. Many AVR microcontrollers have even less memory, but the AVR instruction set is also more compact than either RV32IC or ARM T32, so they can be considered equivalent. ↩

  2. The CH32V003, despite its position at the absolute lower end of physical size and capabilities of 32-bit microcontrollers, still runs at up to 48MHz, four times as fast as the ATMega 328P. Many other microcontrollers in this class similarly offer a speed of up to 100MHz at the upper end. Considering that these microcontrollers often run approximately one instruction per clock cycle with basic pipelining, their raw execution speed is comparatively high. ↩

  3. For example: The smallest reasonable memcpy implementation, which would be one of the algorithms enabled by this flag, is about 30 bytes in size on ARM T32, a 90% decrease. However, this implementation is up to 4 times slower than the default. On the aforementioned PebbleOS Aplite platform and assuming a very pessimistic 30 cycles per byte, the entire available memory can be copied or zeroed in 11 milliseconds. ↩

Activity

  1. rustbot commented on Oct 8, 2026

    @rustbot
    Collaborator

    Note

    A #t-libs/api-changes Zulip thread was opened for this ACP. You are free to discuss on GitHub or Zulip.

  2. rustbot commented on Oct 8, 2026

    @rustbot
  3. scottmcm commented on Oct 8, 2026

    @scottmcm
    Member

    Pondering: on tier 1 targets we don't use the compiler-builtins versions of these, IIRC, using a platform implementation instead.

    For extreme versions, in any direction, I wonder if we'd be better off not offering cfgs for all of them but instead making it easier for people to "bring their own" so they can better pick their tradeoffs. Even if people want smaller, I wonder if people might often also want version with a word-at-a-time loop for better performance without probably using that much more code size, for example. (Suppose that was 70 bytes instead of 30 -- would it be worth it? Would it need another cfg? It'd be nice to not have to decide.)

  4. programmerjake commented on Oct 8, 2026

    @programmerjake
    Member

    maybe something like having #[no_builtins]:

    #[no_builtins]
    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 {
        let mut i = 0;
        // don't use for since the iterators might need memcpy
        while i < n {
            unsafe { dest.cast::<MaybeUninit<u8>>().add(i).write(src.cast::<MaybeUninit<u8>>().add(i).read()) }
            i += 1;
        }
        dest
    }
  5. dead-claudia commented on Oct 12, 2026

    @dead-claudia
  6. tgross35 commented on Oct 14, 2026

    @tgross35
    Member

    Why does rustc need to be aware of this?

  7. kleinesfilmroellchen commented on Oct 15, 2026

    @kleinesfilmroellchen
    Author

    #[no_builtins] would require rustc support, but not this flag. I’m not proposing the former, or any more complex "bring your own" mechanism, simply a "build the standard library slightly differently" feature.

  8. dead-claudia commented on Oct 16, 2026

    @dead-claudia
  9. kleinesfilmroellchen commented on Oct 17, 2026

    @kleinesfilmroellchen
    Author

    Is there anything for me to do on this ACP, or is it waiting for a libs-team meeting discussion?

  10. ChrisDenton commented on Oct 17, 2026

    @ChrisDenton
    Member

    This feels a little more impactful than the average ACP so I think individual libs members might be hesitant to unilaterally second it. I'll nominate for a team meeting.

    If anyone has more thoughts or feedback then please do mention it.

  11. added
    I-libs-nominatedIndicates that an issue has been nominated for discussion during a team meeting.
    on Oct 17, 2026
  12. kleinesfilmroellchen commented on Oct 17, 2026

    @kleinesfilmroellchen
    Author

    The primary existing concern (including the Zulip discussion) is that a bring-your-own solution may be better, which @scottmcm elaborated above. Thanks for nominating!

  13. nia-e commented on Nov 3, 2026

    @nia-e
    Member

    Per today's libs meeting, we already optimise for size quite aggressively - but if there's some concrete example(s) that can be found where we'd want this in the library, we could do this. memcpy and the likes can probably be solved other ways (e.g. via EIIs), but if there are other cases that are found that can't use other mechanisms, consider this a +1 from me. Thanks!

  14. removed
    I-libs-nominatedIndicates that an issue has been nominated for discussion during a team meeting.
    on Nov 3, 2026
  15. kleinesfilmroellchen commented on Nov 5, 2026

    @kleinesfilmroellchen
    Author

    So if I understand this correctly:

    • The proposed changes still should not be included under the existing flag
    • This is not currently acceptable due to lack of use cases
    • Externally implementable items is a better design (despite the humongous implementation complexity and it being entirely outside of what I can contribute)

    This basically kills the proposal for now, since I have no other use cases, and no time or skills to implement the more generic feature.

  16. the8472 commented on Nov 5, 2026

    @the8472
    Member

    I wouldn't quite summarize the meeting outcome that way. EIIs have costs (currently can't be inlined) and don't work on all platforms yet.

    So if you run into a case

    • where the core/std are too bloated even with optimize_for_size
    • it can't be handled by bringing your own #[no_builtins] extern "C" fn ..
    • and an EII wouldn't be applicable for one reason or another

    then we'd likely accept another cfg()

  17. kleinesfilmroellchen commented on Nov 5, 2026

    @kleinesfilmroellchen
    Author

    (1) This is currently the case and has been explained above, but the argument has been refuted as not important enough to warrant this change.
    (2) As has been noted in the zulip discussion, #[no_builtins] only applies to crates, though there is no good technical reason why it is limited in that way.
    (3) EIIs fully apply in this case, probably not others (moving away from C-based functions like memset/memcpy), but the whole situation rests on the fact that I don’t currently have any other cases.

  18. clarfonthey commented on Nov 5, 2026

    @clarfonthey
    Contributor

    So, after talking a bit more on Zulip, here's my summary:

    • Right now, build-std is on the horizon, so, we're going to have to start thinking about which cfgs we offer should be included as features.
    • We also have no concept of what features for std means for stability, and should fix that.
    • Because of how cargo features work, maybe we should really make these more granular and just make stuff like optimize_for_size aggregate.
    • Maybe we are overgeneralising a bit too hard.

    So, offering something like slim_mem_ops for compiler-builtins which makes memcpy and co. have slimmer implementations that don't unroll seems like a reasonable compromise.

  19. tgross35 commented on Nov 6, 2026

    @tgross35
    Member

    Pointed this out on the issue but isn't slim_mem_ops just a different name for optimize_for_size_aggressive? If the two work identically then I'd rather call it optimize_for_size_aggressive, which leaves the door open for putting more than just mem things under that umbrella.

    • it can't be handled by bringing your own #[no_builtins] extern "C" fn ..
    • and an EII wouldn't be applicable for one reason or another

    The advantage over these two is that you don't actually need to bring your own library, we can distribute both versions. But I agree that it's a fairly attractive option to just have a crates.io crate with the #[no_mangle] functions, which you could pull in as a dependency if you want whatever flavor mem* it brings.

  20. changed the title [-]ACP: Add an optimize_for_size_aggressive flag[/-] [+]ACP: Add a `slim_mem_ops` flag[/+] on Nov 6, 2026
  21. kleinesfilmroellchen commented on Nov 6, 2026

    @kleinesfilmroellchen
    Author

    Updated the ACP to instead propose the more focused slim_mem_ops which doesn’t require use cases outside of core::mem/cb::mem, and does not imply optimize_for_size.

  22. clarfonthey commented on Nov 6, 2026

    @clarfonthey
    Contributor

    So, I propose to accept the new version of the ACP where:

    1. We implicitly allow adding new, small cargo features to various std crates and keeping these features scoped to similar changes (e.g., one per lookup table removed, one per kind of change applied to one group of functions, etc.)
    2. We make optimize_for_size now just a meta-feature inheriting all those features where it makes sense, with potential changes added later
    3. We consider moving over existing cfgs that are really features into new feature flags. this would require consulting with the existing users of them, and inverting a few of them, e.g. no_rc should be an rc feature enabled by default
    4. By that metric, maybe optimize_for_size is a bad feature name since we generally prefer positive features (e.g. include this LUT) rather than negative ones (don't do this), so, that can be bikeshedded later
    5. Longer-term we need to come up with a strategy for stabilising cargo features and that should probably be brought up with the build-std folks. we don't even know what it means for there to be default features for std and that would be a good place to start

    For the sake of unblocking this change, I say we accept a PR adding the slim_mem_ops feature by itself now, and then keep this open until we have a better handle on how we want the optimize_for_size meta-feature to work. I'll also post some comments on the build-std goal just to ask how much they've thought about std features and how stability for those would work. In the meantime, this is all unstable, so, as long as we don't overtly break anything without telling people, it should be fine.

  23. ChayimFriedman2 commented on Nov 7, 2026

    @ChayimFriedman2

    When build-std is stable, there should probably be some unstable gating for unstable std features.

  24. nia-e commented on Nov 9, 2026

    @nia-e
    Member

    Seconding @clarfonthey in endorsing the revised form of the ACP - I think this is a reasonable change and better-motivated now, so I'm going to just go ahead and approve this

  25. added
    ACP-acceptedAPI Change Proposal is accepted (seconded with no objections)
    on Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ACP-acceptedAPI Change Proposal is accepted (seconded with no objections)T-libsapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions