Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
RealSuperRyn opened this issue Jun 24, 2024 · 11 comments
Open

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

RealSuperRyn opened this issue Jun 24, 2024 · 11 comments

Comments

@RealSuperRyn
Copy link

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.

@workingjubilee
Copy link
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.

@RealSuperRyn
Copy link
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.

@workingjubilee
Copy link
Member

workingjubilee commented Jun 24, 2024

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

@RealSuperRyn
Copy link
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.

@workingjubilee
Copy link
Member

workingjubilee commented Jun 24, 2024

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?

@workingjubilee
Copy link
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.

@RealSuperRyn
Copy link
Author

how do you handle the name resolution errors?

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

@workingjubilee
Copy link
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.

@DemiMarie
Copy link

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.

@workingjubilee
Copy link
Member

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

@DemiMarie
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants