Skip to content

Allow opting out of specific parts of core crate (e.g. core::fmt) #3665

Open

Description

@RealSuperRyn

In embedded systems development, there's usually a small maximum binary size. (e.g. 8KiB).
The size of core::fmt when compiled into the executable is too big for this.

Take a freestanding, x86_64-unknown-none, panic="abort" executable with 3 functions: A panic handler, a function named panik, and the _start function. In both the panic and panik functions, the only expression is loop {unsafe {asm!("hlt");}}. In the _start function, the only expression is either panic!(); or panik();

If you run cargo bloat --release --target=x86_64-unknown-none on the program's source when the only expression in _start is panic!();, the resulting executable size is 665.4KiB, which is 83 times the hypothetical size limit of our executable.

However, if you run the same command when the function in _start used is panik();, the executable size is now only 1.8KiB, which is only 25% of the limit: 332 times smaller.

It would be very nice if we could opt out of using parts of core like core::fmt instead of struggling to avoid compiling it. When opted out of part of core, the compiler will output an error (E0412) upon compiling something that is in or depends on the opted out portion, instead of silently compiling it in. My suggestion for the syntax is unuse core::foo::bar;, though other syntax is fine.

Activity

  1. workingjubilee commented on Jun 24, 2024

    @workingjubilee
    Member

    We have begun exploring allowing size-based optimizations based on cfg in core/std. Given how much of the code bloat is "for making printing numbers of large sizes and precisions very fast", and people still want to print numbers even on embedded, but would like that to not consume several hundred KiB, I think pursuing such optimizations would be more to-the-point than modifying the language, especially when we have barely done any at all, so far.

  2. RealSuperRyn commented on Jun 24, 2024

    @RealSuperRyn
    Author

    We have begun exploring allowing size-based optimizations based on cfg in core/std. Given how much of the code bloat is "for making printing numbers of large sizes and precisions very fast", and people still want to print numbers even on embedded, but would like that to not consume several hundred KiB, I think pursuing such optimizations would be more to-the-point than modifying the language, especially when we have barely done any at all, so far.

    Those optimizations might save a few dozen KiB, but I don't see them shrinking the code size below 8KiB. Adding the ability to opt out of parts of core should be a fairly small change to the language which requires less effort than making optional size optimizations in core.

  3. workingjubilee commented on Jun 24, 2024

    @workingjubilee
    Member

    people still use Arduino ATtiny85s? I figured they had all moved on to, like, Teensys by now.

  4. RealSuperRyn commented on Jun 24, 2024

    @RealSuperRyn
    Author

    people still use Arduino ATtiny85s? I figured they had all moved on to, like, Teensys by now.

    I'm using 8KiB as an example because I saw a thread somewhere of someone having issues with not being able to get their code below that size when their job required it. Also, people can't just say "but there's new hardware" every time someone has issues with resource usage, otherwise code will only become more inefficient.

  5. workingjubilee commented on Jun 24, 2024

    @workingjubilee
    Member

    I suppose the ATmega line and STM32s are "new" on some timescale...

    Talking about "efficiency" is a distraction. Space and time generally trade against each other. More recent processors often compute more efficiently, even more efficiently than microcontrollers, if you factor in how much work they get done, which would take more time for the microcontroller and thus potentially more energy.

    Anyways, the problem is that your example with the smaller binary... exists. It works because it doesn't use code that wants to link against the formatting code. So simply declaring portions "unused" which nonetheless would want to be linked in has a slight problem: how do you handle the name resolution errors?

  6. workingjubilee commented on Jun 24, 2024

    @workingjubilee
    Member

    If the answer is "somehow the rest of libcore would magically adapt to the exclusion" then it seems you have arrived at another, even-more-aggressive cfg option for building core? So I still think that such is a more appropriate avenue to explore.

  7. RealSuperRyn commented on Jun 24, 2024

    @RealSuperRyn
    Author

    how do you handle the name resolution errors?

    Give an error saying that part of libcore depends on the excluded portion.

  8. workingjubilee commented on Jun 25, 2024

    @workingjubilee
    Member

    how do you handle the name resolution errors?

    Give an error saying that part of libcore depends on the excluded portion.

    that demand occurs instantly upon building libcore, however.

  9. DemiMarie commented on Jul 4, 2024

    @DemiMarie

    I suppose the ATmega line and STM32s are "new" on some timescale...

    Talking about "efficiency" is a distraction. Space and time generally trade against each other. More recent processors often compute more efficiently, even more efficiently than microcontrollers, if you factor in how much work they get done, which would take more time for the microcontroller and thus potentially more energy.

    I’m not an embedded developer, but my understanding is that this is often infeasible. There can be multiple reasons:

    1. DRAM consumes power even when it is not in use.
    2. Denser processes have higher static power consumption, which prevents deep sleep states that preserve state (and therefore have near-instant wakeup times).
    3. Batch processing (which seems to be what you are proposing) is not feasible in e.g. a real-time control application.
    4. More powerful processors cost more, which is important at high volumes.
    5. Some applications must use special secure microcontrollers that provide protection against physical tampering and other attacks. These chips are very resource constrained.

    Anyways, the problem is that your example with the smaller binary... exists. It works because it doesn't use code that wants to link against the formatting code. So simply declaring portions "unused" which nonetheless would want to be linked in has a slight problem: how do you handle the name resolution errors?

    By building libcore without the calls to core::fmt. IIUC these are almost all for either panic!() messages or trait implementations. In constrained embedded systems, it makes much more sense to emit logs in binary form and convert to human-readable form on a general purpose system.

  10. workingjubilee commented on Jul 5, 2024

    @workingjubilee
    Member

    Which is not implementable as cfg and requires new language syntax to do it why exactly?

  11. DemiMarie commented on Jul 5, 2024

    @DemiMarie

    @workingjubilee It is implementable as cfg. No new language syntax required.

  12. surechen commented on May 24, 2025

    @surechen

    We have begun exploring allowing size-based optimizations based on cfg in core/std. Given how much of the code bloat is "for making printing numbers of large sizes and precisions very fast", and people still want to print numbers even on embedded, but would like that to not consume several hundred KiB, I think pursuing such optimizations would be more to-the-point than modifying the language, especially when we have barely done any at all, so far.

    Hello, I am very interested in binary size reduction of standard libraries in embedded systems. Where can I get detailed progress information or participate in discussions? Also, does the std have a similar strategy? Thank you very much! @workingjubilee

  13. kennytm commented on May 24, 2025

    @kennytm
    Member

    The cfg-based solution can already be used on nightly with -Zbuild-std-features like

    cargo +nightly build -Zbuild-std=std,panic_abort -Zbuild-std-features=optimize_for_size --target=...
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions