Repository navigation
Description
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_sizeto 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_aggressivewhich 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-Ozfrequently being worse than-Osat 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-O3but stilloptimize_for_size, and in many cases the perfect combination will be-Oswithslim_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
-
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. ↩
-
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. ↩
-
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
rustbot commentedon Oct 8, 2026
Note
A #t-libs/api-changes Zulip thread was opened for this ACP. You are free to discuss on GitHub or Zulip.
rustbot commentedon Oct 8, 2026
scottmcm commentedon Oct 8, 2026
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.)programmerjake commentedon Oct 8, 2026
maybe something like having
#[no_builtins]:dead-claudia commentedon Oct 12, 2026
tgross35 commentedon Oct 14, 2026
Why does rustc need to be aware of this?
kleinesfilmroellchen commentedon Oct 15, 2026
#[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.
dead-claudia commentedon Oct 16, 2026
kleinesfilmroellchen commentedon Oct 17, 2026
Is there anything for me to do on this ACP, or is it waiting for a libs-team meeting discussion?
ChrisDenton commentedon Oct 17, 2026
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.
kleinesfilmroellchen commentedon Oct 17, 2026
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!
nia-e commentedon Nov 3, 2026
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.
memcpyand 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!kleinesfilmroellchen commentedon Nov 5, 2026
So if I understand this correctly:
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.
the8472 commentedon Nov 5, 2026
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
optimize_for_size#[no_builtins] extern "C" fn ..then we'd likely accept another
cfg()kleinesfilmroellchen commentedon Nov 5, 2026
(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.
clarfonthey commentedon Nov 5, 2026
So, after talking a bit more on Zulip, here's my summary:
cfgs we offer should be included as features.optimize_for_sizeaggregate.So, offering something like
slim_mem_opsforcompiler-builtinswhich makes memcpy and co. have slimmer implementations that don't unroll seems like a reasonable compromise.tgross35 commentedon Nov 6, 2026
Pointed this out on the issue but isn't
slim_mem_opsjust a different name foroptimize_for_size_aggressive? If the two work identically then I'd rather call itoptimize_for_size_aggressive, which leaves the door open for putting more than just mem things under that umbrella.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 flavormem*it brings.[-]ACP: Add an optimize_for_size_aggressive flag[/-][+]ACP: Add a `slim_mem_ops` flag[/+]kleinesfilmroellchen commentedon Nov 6, 2026
Updated the ACP to instead propose the more focused
slim_mem_opswhich doesn’t require use cases outside of core::mem/cb::mem, and does not implyoptimize_for_size.clarfonthey commentedon Nov 6, 2026
So, I propose to accept the new version of the ACP where:
optimize_for_sizenow just a meta-feature inheriting all those features where it makes sense, with potential changes added latercfgs 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_rcshould be anrcfeature enabled by defaultoptimize_for_sizeis 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 laterFor the sake of unblocking this change, I say we accept a PR adding the
slim_mem_opsfeature by itself now, and then keep this open until we have a better handle on how we want theoptimize_for_sizemeta-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.ChayimFriedman2 commentedon Nov 7, 2026
When build-std is stable, there should probably be some unstable gating for unstable std features.
nia-e commentedon Nov 9, 2026
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