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

How to use with no-std #19

Closed
creationix opened this issue Jan 18, 2017 · 30 comments · Fixed by #103
Closed

How to use with no-std #19

creationix opened this issue Jan 18, 2017 · 30 comments · Fixed by #103

Comments

@creationix
Copy link

I'm trying out rust on a cheap microcontroller following this tutorial https://polyfractal.com/post/rustl8710/

The tutorial works great and the sample echo program runs over serial.

I'd love to add in a rust scripting language and I really like the design of Rhai. But when I add the library, the compile fails because of std is missing.

info: using existing install for 'nightly-x86_64-unknown-linux-gnu'
info: override toolchain for '/home/tim/rustl8710/src/rust' set to 'nightly-x86_64-unknown-linux-gnu'

  nightly-x86_64-unknown-linux-gnu unchanged - rustc 1.16.0-nightly (4ce7accaa 2017-01-17)

cd src/rust && xargo build --target thumbv7m-none-eabi
   Compiling rhai v0.4.0
error[E0463]: can't find crate for `std`
  |
  = note: the `thumbv7m-none-eabi` target may not be installed

I'm fairly new to rust still and am not quite clear on what this all means. Is Rhai meant to be used in such embedded use cases? It sure would be neat if it or something like it did work.

@sophiajt
Copy link
Collaborator

(unfortunately this repo is largely unmaintained at this point, sadly... though I'd love to have time to work on it)

I'm not familiar with xargo, but it may be possible to move rhai over to no-std in the future. I know there are some efforts to make this easier in the future. In theory, most of it should be pretty portable.

@bootchk
Copy link

bootchk commented Sep 29, 2017

I'm new too, but my thoughts: xargo should build the std crate for the target, at least some tutorials say it will. I think " the thumbv7m-none-eabi target may not be installed" means that you are missing some component or configuration for llvm or rust? rustc --print target-list will print the list of target specifications (not the list of distributed std libraries?), and your target is in that list on my system. I had this error message also when doing "xargo build --examples hello" but other builds worked.

@luciusmagn
Copy link
Collaborator

In the future, we may provide a no-std feature, though. Interesting idea

@thejpster
Copy link

I'm in need of a #[no_std] scripting language and REPL for https://github.com/thejpster/monotron and porting this looks a lot simpler than writing my own from scratch! I'm going to take a look and see what's involved and report back.

@VictorKoenders
Copy link
Contributor

We also have a project that will run on a microcontroller and has need for a scripting language. For our case we would be okay with a #![no_std] solution that requires alloc. This way we have access to Vec, Box, etc.

@VictorKoenders
Copy link
Contributor

VictorKoenders commented Mar 2, 2020

Started working on this here: https://github.com/VictorKoenders/rhai/tree/no_std

I ran into 2 big issues:

  • hashmap is not available in alloc: Move HashMap to alloc::collections rust-lang/rust#56192
    This can be solved by depending on hashbrown directly, however this introduces additional dependencies.
  • error::Error is not available in core/alloc
    Because of backwards compatibility, std::error::Error cannot be used in core or alloc, because it depends on OS-specific implementations that are now deprecated.
    There is a crate that exposes a trait that can be used in core: core-error, but it is unclear if this is the direction that Rust is going to take.

Furthermore I've disabled everything depending on std::fs::File for obvious reasons.

Edit: added hashbrown and core-error, currently blocked on math not being available in core. Yes, really.

Edit 2: core::intrinsics did have the math functions, so I added those and updated the travis build to also test no_std on nightly. The project compiles but I'll need to test it in a no_std environment to see if it actually runs and how much memory it uses.

@schungx
Copy link
Collaborator

schungx commented Mar 3, 2020

Started working on this here: https://github.com/VictorKoenders/rhai/tree/no_std

@VictorKoenders I have pushed a large amount of changes recently. Can you see if it is easy to for you to update your branch?

@VictorKoenders
Copy link
Contributor

It was easier to re-do my changes. I've updated my branch to the latest master version.

One thing I ran into is that Engine::new sets on_print and on_debug to use println, which is not available on no_std targets (because there's no stdout). Currently in no_std mode, the messages silently get dropped.

I've added an alternative function new_with_callbacks where the developer can configure their own print and debug outputs, however I think it'd be better to use a builder for this in the future. Something like:

let engine = Engine::custom()
    .on_print(|x| println!("{}", x))
    .on_debug(|x| eprintln!("{}", x))
    .map_type("alloc::string::String", "string") // fills `type_names`
    .map_type("alloc::vec::Vec<alloc::boxed::Box<dyn rhai::any::Any>>>", "array")
    .map_type("alloc::boxed::Box<dyn rhai::any::Any>", "dynamic")
    .build();

@schungx
Copy link
Collaborator

schungx commented Mar 3, 2020

I think it'd be better to use a builder for this in the future

The would be nice, especially some API to do function registrations and setup the initial scope with variables.

@schungx
Copy link
Collaborator

schungx commented Mar 3, 2020

One thing I ran into is that Engine::new sets on_print and on_debug to use println, which is not available on no_std targets

Maybe in no-std builds we need to make the default no-op's?

Or make them no-op's by default for all targets? If someone wants to print them, it'd be easy to register a callback with println!...

@schungx
Copy link
Collaborator

schungx commented Mar 4, 2020

I added a new no-std feature that I intend to use to cut off some of this utility functions as well as things like println!. Eventually I intend the no-std feature flag to turn on exact no-std mode, although I have no idea whether it will work.

Go clone from https://github.com/schungx/rhai to check it out.

@VictorKoenders
Copy link
Contributor

I should be able to do a final test tonight. One of the issues I ran into is that the microcontrollers I have only have several KB of storage space (16KB and 64KB), which needs to contain the binary and the .rhai file. Rhai is incredibly small, only about 155KB, but that's still too big for the devices I currently have. I managed to shave off 80KB by disabling the builtin functions, but that still leaves the firmware at 77KB, just over the limit of 64KB I have.

Another thing that I want to do before making a PR is to update the README, as this currently (correctly) states that rhai has no dependencies, but when using no_std we'll have to use some dependencies (or writing our own hashmap, error trait and math functions).

@schungx
Copy link
Collaborator

schungx commented Mar 4, 2020

It is better that you base your work on my working repo so we won't have a huge headache later on trying to merge... :-)

@schungx
Copy link
Collaborator

schungx commented Mar 4, 2020

but that still leaves the firmware at 77KB, just over the limit of 64KB I have.

Did you compile release mode with lto = true? I found that using lto typically shaves 30-40% off the size of the final binary.

If it is still large, I can see what things I can trim out.

@VictorKoenders
Copy link
Contributor

But I've already done all the work in my repo, and it's easy to rebase it.

The project I'm trying to run is on https://github.com/victorkoenders/embedded-rhai. These are my cargo flags:

[profile.release]
codegen-units = 1 # better optimizations
debug = true # symbols are nice and they don't increase the size on Flash
lto = true # better optimizations
opt-level = "z"

The output of cargo bloat is:

File  .text    Size         Crate Name
0.6%  11.6%  8.4KiB          rhai <rhai::parser::TokenIterator as core::iter::traits::iterator::Ite...
0.5%  10.0%  7.2KiB           std core::fmt::float::float_to_decimal_common_shortest
0.5%   9.1%  6.6KiB           std core::fmt::float::float_to_decimal_common_exact
0.2%   4.6%  3.3KiB embedded_rhai embedded_rhai::__cortex_m_rt_main
0.2%   4.4%  3.2KiB          rhai rhai::engine::Engine::eval_expr
0.1%   2.5%  1.8KiB          rhai rhai::parser::parse_binop
0.1%   1.9%  1.4KiB          rhai rhai::engine::Engine::get_dot_val_helper
0.1%   1.9%  1.4KiB          rhai rhai::engine::Engine::eval_stmt
0.1%   1.9%  1.4KiB          rhai rhai::parser::parse_primary
0.1%   1.7%  1.3KiB          rhai rhai::engine::Engine::call_fn_raw
0.1%   1.6%  1.2KiB          rhai <rhai::parser::Token as core::fmt::Debug>::fmt
0.1%   1.4%  1.0KiB          rhai rhai::parser::parse_stmt
0.1%   1.3%   1000B           std __udivmoddi4
0.1%   1.2%    910B           std core::char::methods::<impl char>::escape_debug_ext
0.1%   1.0%    776B           std core::num::bignum::Big32x40::mul_digits
0.1%   1.0%    774B           std __divdf3
0.1%   1.0%    734B          rhai <&T as core::fmt::Debug>::fmt
0.1%   1.0%    732B           std core::fmt::Formatter::pad
0.1%   1.0%    726B           std __muldf3
0.0%   0.9%    680B           std core::fmt::Formatter::write_formatted_parts
2.0%  38.6% 28.0KiB               And 341 smaller methods. Use -n N to show more.
5.2% 100.0% 72.5KiB               .text section size, the file size is 1.4MiB

I'm looking into precompiling the AST and having the engine evaluate the statements on the microcontroller. However I think that's outside of the scope of this issue.

@timfish
Copy link
Collaborator

timfish commented Mar 4, 2020

I was reading somewhere that the recommendation is to add a feature called std that is enabled by default rather than no_std which is a double negative.

https://rust-lang.github.io/api-guidelines/naming.html#feature-names-are-free-of-placeholder-words-c-feature

The problem with using no_std is that's also the name of the crate-level attribute.

@VictorKoenders
Copy link
Contributor

Normally I'd agree, but in this case we actually need to add dependencies when we switch to no_std mode, and I don't know a way to include a dependency when a feature is taken away.

  • HashMap is not in alloc, so we need to add hashbrown
  • error::Error is not in core, so we need to add core-error
  • f64::pow and f64::powi are not in core, and using core::intrinsics gives a link-time error, so we add libm

@schungx
Copy link
Collaborator

schungx commented Mar 5, 2020

But I've already done all the work in my repo, and it's easy to rebase it.

I'm about to do another PR with a large amounts of coding and documentation cleanup. I think it might be easier if you redo you changes based on what I have right now...

Alternatively, I can manually redo your changes to my my repo, but then I'll have no way to test that it works and I haven't made a mistake.

@schungx
Copy link
Collaborator

schungx commented Mar 5, 2020

I've created a new PR #104 with the latest clean-up for reference.

@schungx
Copy link
Collaborator

schungx commented Mar 6, 2020

@VictorKoenders I've back ported your no_std changes to the latest version. There is a no_std branch: https://github.com/schungx/rhai/tree/no-std

It almost compiles on my machine, except for core::intrinsics because I'm running stable.

Can you test if it works OK?

@VictorKoenders
Copy link
Contributor

My PR #103 doesn't involve core::intrinsics, so I'm not sure what you're porting.

Feel free to poke me to rebase my changes when you're done with yours. I can reapply these changes trivially and do another test to see if it runs on a microcontroller.

@schungx
Copy link
Collaborator

schungx commented Mar 6, 2020

My PR #103 doesn't involve core::intrinsics,

![cfg_attr(feature = "no_std", feature(core_intrinsics))]

Feel free to poke me to rebase my changes

Great! I'm a noob Git user and have no idea how to rebase something...

You can see in my branch that I've spun out _std into a separate module so I don't have to pollute lib.rs with no_std stuff. I think this is a better way to do it.

Also, builtins.rs has some changes. In particular, a core library with minimal stuff (like arithmetic and logic) is always compiled in and I have used the feature no_stdlib which is opt-out to exclude other built-in features, instead of default_buildins which is opt-in.

@schungx
Copy link
Collaborator

schungx commented Mar 6, 2020

@VictorKoenders I've merged the latest round of changes. You can start your rebasing.

@schungx
Copy link
Collaborator

schungx commented Mar 8, 2020

@VictorKoenders Commit b1b25d3 adds a dependency to num-traits in order to avoid scripts panic-ing the system by doing checked arithmetic.

I understand that the feature libm of num-traits should be turned on when building a no-std build. Please take this into consideration when you rebase your changes.

num-traits is not large so I'm quite sure it should not bloat your code size... in a future commit, I'll enable disabling arithmetic checking with a new feature.

@schungx
Copy link
Collaborator

schungx commented Mar 18, 2020

let engine = Engine::custom()
    .on_print(|x| println!("{}", x))
    .on_debug(|x| eprintln!("{}", x))
    .map_type("alloc::string::String", "string") // fills `type_names`
    .map_type("alloc::vec::Vec<alloc::boxed::Box<dyn rhai::any::Any>>>", "array")
    .map_type("alloc::boxed::Box<dyn rhai::any::Any>", "dynamic")
    .build();

@VictorKoenders I have been thinking about the builder style. It looks nice, but there is a problem in that it forbids changes to the engine after you build it (or you have to build a new one).

If someone uses Full optimizations to optimize their scripts, they most likely need to defer registration of their custom types and functions until after the scripts are parsed. The process will look like this:

// Here we use the default engine to compile the script and optimize it
let mut engine = Engine::custom().
    .set_optimization_level(OptimizationLevel::Full)
        :
    .build();

let ast = engine.compile("... some script ...")?;

// Here we create another Engine
engine = Engine::custom()
    .set_optimization_level(OptimizationLevel::Simple)
    .register_fn("my_func", some_dangerous_func_with_side_effects)
        :
    .build();

// or something like this
engine = engine.reopen()
    .set_optimization_level(OptimizationLevel::Simple)
    .register_fn("my_func", some_dangerous_func_with_side_effects)
    .build();

engine.eval_ast::<i64>(&ast)?;

So, is a builder style good here?

@schungx schungx reopened this Mar 18, 2020
@VictorKoenders
Copy link
Contributor

I think builder style is perfectly fine here. I doubt people will be adding new functions to their scripts on the fly

@schungx
Copy link
Collaborator

schungx commented Apr 8, 2020

@VictorKoenders did you get the chance to test it out?

@VictorKoenders
Copy link
Contributor

I have not, I can't even compile rhai 0.11.0 or master with the latest changes. These changes seem to compile, but I don't have a working microcontroller at the moment I can test it on, and I don't know what effect this has on the std version of this crate.

diff --git a/Cargo.toml b/Cargo.toml
index 063abd0..0e2a168 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ keywords = [ "scripting" ]
 categories = [ "no-std", "embedded", "parser-implementations" ]
 
 [dependencies]
-num-traits = "0.2.11"
+num-traits = { version = "0.2.11", default-features = false }
 
 [features]
 #default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
@@ -47,7 +47,7 @@ version = "*"
 optional = true
 
 [dependencies.core-error]
-version = "*"
+version = "0.0.1-rc4"
 features = ["alloc"]
 optional = true

@schungx
Copy link
Collaborator

schungx commented Apr 12, 2020

Yes, I have a habit (probably not a good one) to omit version numbers in my Cargo.toml. Better put them back in, I guess...

Eliah-Lakhin pushed a commit to Eliah-Lakhin/rhai that referenced this issue Jul 29, 2020
@schungx
Copy link
Collaborator

schungx commented Aug 4, 2020

The repo now has a full dedicated no_std example. Closing this one.

@schungx schungx closed this as completed Aug 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants