Skip to content

Add no_std support #103

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

Merged
merged 1 commit into from
Mar 18, 2020
Merged

Add no_std support #103

merged 1 commit into from
Mar 18, 2020

Conversation

VictorKoenders
Copy link
Contributor

This adds no_std support to Rhai. The dependant crates are responsible for making sure the alloc crate is enabled. I've used https://github.com/rust-embedded/alloc-cortex-m for this.

The reason I chose for a feature called no_std, instead of adding a default feature std, is because we need to import several crates when we're not compiling from std. In most cases we have less dependencies.

  • HashBrown. HashMap is not available in alloc, because it uses an OS-based hasher by default: Move HashMap to alloc::collections rust-lang/rust#56192
  • core-error. Rhai uses std::error::Error, which is locked to std because of methods that are now deprecated. Core-error aims to be a replacement crate.
  • libm. core does not have support for floating point mathematics by default, and there are 2 places where f64::pow and f64::powi are called. Libm provides those functions on different platforms.

Two new features are added in this PR:

  • no_std: this enables #![no_std] and switches all imports to use core and alloc.
  • default_buildins: This enables all the methods in src/builtin.rs. This feature is enabled by default. If users of this crate (including myself) are limited for space on their target platform, disabling this feature saves a nice 80KB (out of 155KB).

Closes #19

@VictorKoenders
Copy link
Contributor Author

I got Rhai working on an stm32f103 with 64KB of FLASH. With this change the library was barely too big (77KB), however after temporarily removing some language features (who needs functions, variables and if-statements?) I managed to get Rhai down to 37KB.

https://twitter.com/victorkoenders/status/1235247234356310016

@schungx
Copy link
Collaborator

schungx commented Mar 10, 2020

I got Rhai working on an stm32f103 with 64KB of FLASH. With this change the library was barely too big (77KB), however after temporarily removing some language features (who needs functions, variables and if-statements?) I managed to get Rhai down to 37KB.

@VictorKoenders I have been thinking. Why does cutting off functions, variables and if-statements cut down on code size? AFAIK those features are quite simple to implement, and adds little code to the code base. The significant elephant features are arrays, resolving array indexing, and custom types (with dot getters/setters chain).

I'd imagine that simply by cutting off custom types, you'd probably cut down 1/4 of the code size. Another probably 1/4 if you cut arrays.

However, maybe you need custom types to expose features in your embedded system... but those can easily be flattened into a bunch of function-based API instead. So if it were for me, I'd be dropping custom types but keeping functions, because function definitions mostly use code that are common for evaluation function calls...

@VictorKoenders
Copy link
Contributor Author

Sorry my week's been busy. I'll finish the code before the end of the week.

Why does cutting off functions, variables and if-statements cut down on code size?

The large space saver is that the code ignores more tokens and has less complexity. The embedded version simply panics if it encounters a token it does not recognize. Because in my case all I had was a loop and 2 external fn calls, I didn't need to account for other language tokens.

From what I can tell, a large part of the rhai binary rhai is because of text-to-AST parsing. If that could be offloaded somewhere else, and have the AST send over and executed on the microcontroller, rhai would easily fit on a 64KB chip. In the future I can look into exporting and importing AST so this could be possible.

I'd imagine that simply by cutting off custom types, you'd probably cut down 1/4 of the code size.

This is probably true. Disabling builtins already saved almost 50%. I think in my experiment I removed all variable assignment so rustc was probably able to optimize away most variables and types.

@schungx
Copy link
Collaborator

schungx commented Mar 10, 2020

OK, I did some experiments with the examples. These program sizes are for standard release builds for x64 with standard library:

Build Size Decrease
standard 830K
unchecked 780K 50K
unchecked + no_index 670K 160K
unchecked + no_index + no_float 550K 280K
unchecked + no_index + no_float + no_stdlib 470K 360K

The same minimal build for arm-unknown-linux-musleabi is 440K.

Therefore:

  • checked arithmetic is 50K of code
  • indexing feature is 110K of code
  • floating-point is 120K of code
  • the standard library is 80K of code
  • removing all integer types other than i32 (only_i32) saves 50K of code
  • removing script-defined functions (no_function) yield no meaningful savings

Your code size might be different for an ARM CPU, and for a no-std build.

Notice that, as predicted, turning off functions does not yield any size savings simply because most of the function-call mechanisms remain in-place.

@schungx
Copy link
Collaborator

schungx commented Mar 10, 2020

So the major reduction ones are: arrays/indexing (as predicted), and floating-point support.

Your milage may vary because no-std doesn't have floating point in the first place.

@schungx
Copy link
Collaborator

schungx commented Mar 11, 2020

As expected, removing functions does not make the code size any smaller.

@schungx
Copy link
Collaborator

schungx commented Mar 11, 2020

I realize that by doing lto=fat and not setting opt-level=z, I have been optimizing for speed instead of size.

Rebuilding for size in 32-bits on a Windows system:

Build Size Decrease Feature
standard 492K
unchecked 464K 28K unchecked = 28K
unchecked + no_index 408K 82K no_index = 56K
unchecked + no_index + no_float 323K 169K no_float = 87K
unchecked + no_index + no_float + only_i32 268K 224K only_i32 = 55K
unchecked + no_index + no_float + only_i32 + no_stdlib 238K 254K no_stdlib = 30K
unchecked + no_index + no_float + only_i32 + no_stdlib + no_function 233K 259K no_function = 5K

So, as you can see, the main space savers are:

  • Floating point (87K) + Standard library (30K) - a lot of the floating point code is in the standard library anyway, so they go together
  • Arrays (56K) - this is expected
  • Restrict to 32-bit integers (55K) - removes most of the core library because no need to have code to operate on other integer types
  • Unchecked math (28K) - this is a bit surprising that it saves so much
  • Functions (5K) - not surprisingly, it does not save much

So, the conclusion is: I'd suggest you simply disable arrays, standard library, other integer types and math checking. Just by disabling arrays, you'll probably save enough for you to put back the while/if/let statements etc.

If you don't need floating-point, disable that also, but as long as you disable the standard library, it probably doesn't add much.

@VictorKoenders
Copy link
Contributor Author

@schungx I've re-applied all the changes. I also moved all the imports to stdlib.rs which matches the no_stdlib feature you introduced.

With all the stuff going on in the world I'm not able to test this on a microcontroller any time soon. However seeing as this compiles and with the size optimalizations you've done, I'm certain it works fine. The codebase itself already worked and if people want to reduce the file size they can work it out.

@schungx schungx merged commit 42ecae4 into rhaiscript:master Mar 18, 2020
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

Successfully merging this pull request may close these issues.

How to use with no-std
2 participants