Closed creationix closed 4 years ago
(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.
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.
In the future, we may provide a no-std feature, though. Interesting idea
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.
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.
Started working on this here: https://github.com/VictorKoenders/rhai/tree/no_std
I ran into 2 big issues:
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.
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?
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();
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.
One thing I ran into is that
Engine::new
setson_print
andon_debug
to useprintln
, 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!
...
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.
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).
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... :-)
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.
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.
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.
The problem with using no_std
is that's also the name of the crate-level attribute.
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.
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.
I've created a new PR https://github.com/jonathandturner/rhai/pull/104 with the latest clean-up for reference.
@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?
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.
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.
@VictorKoenders I've merged the latest round of changes. You can start your rebasing.
@VictorKoenders Commit https://github.com/jonathandturner/rhai/pull/105/commits/b1b25d3043e194ca6e74a40ba6c20aabe864d7a4 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.
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?
I think builder style is perfectly fine here. I doubt people will be adding new functions to their scripts on the fly
@VictorKoenders did you get the chance to test it out?
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
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...
The repo now has a full dedicated no_std
example. Closing this one.
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.
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.