Description
In this issue I'd like to propose officially adding support for Rust's #![no_std]
mode and crate attribute to the wasmtime
crate and runtime. Notably, I'm proposing that we refute current documentation about "why not no_std
". This would additionally revert earlier work in #554 and #2024.
Before I go into more depth, this is not a new issue to Wasmtime. This has been discussed in a number of places such as #1158, #3451, #3495, #6424, #7700, and I'm sure I'm missing others as well. I'll also point out that I was personally one of the ones previously advocating for specifically not supporting no_std
, and my opinion has changed in the intervening years.
Why support no_std
?
As far as I know the benefits of no_std
haven't really ever been in question. After all, who doesn't want a project to be able to run in as many places as possible? To me I would personally rephrase this question as why to use no_std
to support platform as opposed to alternative strategies. I'd answer this with the fact that no_std
is the most idiomatic and well-supported solution in the Rust ecosystem. Rust developers in the embedded space are already used to no_std
and what it entails. Additionally there are community idioms/expectations around the no_std
feature to follow which set precedent.
Why now?
For a number of years now Wasmtime has had a page in its documentation for "What about #[no_std]
?", so I think a fair question is why would we revert this and reconsider this previous decision at this point in time. The embedded space has become more interested in WebAssembly over time and there are a fair number of users today. The general feedback is that Wasmtime is not suitable in these environments, and one primary reason is that it's difficult to get Wasmtime working in these environments. Work such as #7995 is to obscure for others to productively use as-is without being able to slot more idiomatically into the no_std
ecosystem. More-or-less there's interest to use WebAssembly on platforms Wasmtime cannot easily target, and no_std
support is intended to be a step towards making Wasmtime support on these platforms easier.
Was the previous rationale wrong?
In my opinion, the current documentation Wasmtime has for not supporting no_std
is both right and wrong. Supporting no_std
is not "free", it's something we'll have to maintain over time as a project and explicitly test in CI and consider for all new features. This cost wasn't necessarily justifiable earlier on in Wasmtime's development. Nowadays, though, Wasmtime has many more compile-time features and it's more clear what a no_std
build of Wasmtime might look like. Supporting no_std
is, however, idiomatic and the best way to support non-standard platforms in Rust. While it's theoretically possible to change how the standard library looks in upstream rust-lang/rust that's basically not happening any time soon.
What does no_std
for Wasmtime mean?
With some of the more rationale-facing questions out of the way, I want to dive into some more details as to what no_std
means for Wasmtime. If you read the title of this issue and say "wow finally it's about time Wasmtime had support" you're probably not going to be too interested in this section. This section is intended to outline what I learned from my experience of porting Wasmtime to no_std
. I have two branches which ports the wasmparser
crate to no_std
as well as the runtime
feature of the wasmtime
crate:
- https://github.com/alexcrichton/wasm-tools/tree/nostd
- https://github.com/alexcrichton/wasmtime/tree/no-std
Features of Wasmtime and no_std
Primarily the first part to mention is that I don't think we'll want to support every single feature of Wasmtime in the no_std
configuration. Instead we'll want a few features explicitly listed as "this supports no_std
" but everything else will implicitly enable a dependency on the std
feature and the standard library. An example of this is that the runtime
feature, the ability to execute WebAssembly modules, won't depend on std but the cache
feature, which writes to a filesystem, would depend on the standard library. Notably, for the initial implementation, I'd also expect that cranelift
itself would require the standard library. There's no inherent reason to do so, but that's how I wrote things originally.
Usage of the alloc
crate
I also personally think it's worth clarifying that no_std
for Wasmtime means core
and alloc
will be used. Notably this means that Wasmtime will still assume that all allocations succeed at all times and a failing allocation will cause a process abort and/or unrecoverable error. Wasmtime can't really feasibly be ported to an alloc
-less build at this time, although if that's of interest to folks then it's something that might be worth investigating later on. I'm not sure how we'd support this in Wasmtime, though, so I'd prefer to defer such a question to a future issue. In the meantime the rest here will assume that no_std
means that alloc
can be used.
Technical details of what support looks like to users
To end users and consumers of Wasmtime what I'd propose is that the wasmtime
crate will grow a compile-time Cargo feature called std
. This feature is enabled by default and is additionally enabled by any other feature which depends on standard library support, such as cache
. If all of Wasmtime's features are disabled, however, then a small set of features, such as runtime
and component-model
, can be enabled without enabling the std
feature of Wasmtime. This will prevent use of the std
crate on some platforms.
The wasmtime-runtime
crate will, by default, use the standard library on platforms that are known to have the standard library. For example on Linux the wasmtime-runtime
crate will continue to use std
as necessary. If wasmtime-runtime
is compiled for an unknown platform, however, then the custom platform support added in #7995 will be enabled by default. This means that Wasmtime will not work out-of-the-box since this header file will need to be implemented. This will be mentioned as part of Wasmtime's documentation and will be the escape hatch to enable embedders to implement custom logic to implement Wasmtime's runtime needs.
Put together, the general workflow for embedders using Wasmtime in no_std
mode would look like:
- Usage of
wasmtime
is audited to confirm that onlyno_std
-compatible features of Wasmtime are used (e.g. notcache
) - Wasmtime is compiled for a custom target that's not Linux, for example. This is then integrated as normal into other build systems as required.
- Platform support for Wasmtime's runtime needs, such as mmap, the Rust
GlobalAlloc
trait, etc, must be provided by the embedder. For now this'll be a header file for Wasmtime's runtime needs and otherwise it's up to the embedder to fix compile errors aboutGlobalAlloc
for example.
My intent would be that we'd have at least one or two examples in the repository doing all this, like the current min-platform
example.
Technical details of what support looks like to Wasmtime developers
To those who develop Wasmtime this issue is effectively asking more development cost to be taken on by the project. Despite having community norms and expectations the no_std
development mode in Rust is not the standard development mode and may serve as a road bump to contributions and/or developers. Here I'd like to outline the specific issues I've found when porting Wasmtime to no_std
:
- Imports now come from
core
andalloc
, notstd
. This is mostly a muscle memory thing to get over, but it's a downside in that only some parts of the project will do this. For example inwasmtime-cache
we'll probably still haveuse std::mem;
whereas inwasmtime-runtime
that would be spelleduse core::mem;
. - The prelude is different for
#![no_std]
mode. Notably collections likeVec
andString
are not present. To work around this I've created amod prelude { ... }
in thewasmtime-environ
crate which is reexported at all other crate roots. Most modules in crate then haveuse crate::prelude::*;
instead of individually importing common types likeString
andVec
. - Hash maps are different. The Rust standard library does not provide
HashMap
in thealloc
crate due to seeding and DoS issues. The proof-of-concept branch I have switches everything needed to thehashbrown
crate'sHashMap
with theahash
hasher. I have not investigated how random keys are generated inno_std
mode yet. Note that these changes also apply to the ubiquitous usage ofIndexMap
. TheIndexMap
type inno-std
mode does not have a defaulted third type parameter and we will need to manually configure this parameter ourselves - Error handling is weird. The
std::error::Error
trait is not present incore
. While there are efforts to do this they have not come to fruition yet. This crucially means thatanyhow::Error
no longer has a blanketFrom
impl for types that implementError
(that requires thestd
feature ofanyhow
which won't be enabled by default). For internal development I added extension traits to enable.err2anyhow()
onResult<T, E>
. This means that while?
will work alone instd
mode it'll require an explicit.err2anyhow()
whenstd
is not enabled. It's hoped that one day this will be easier to work with ascore::error::Error
eventually gets stabilized. - Various float intrinsics in
wasmtime-runtime
are implemented with thelibm
crate instead of methods that require the standard library. Whenstd
is enabled, though, the standard library methods are used.
Crates with no-std support will have a prelude that looks like:
#![no_std]
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
use wasmtime_environ::prelude;
and each module will have
use crate::prelude::*;
Why open an issue on this?
This is a big enough topic that I don't want to "just" open a PR for this. Instead I'd like to make sure everyone's on board with the general direction. First I'd like to establish a baseline of "yes we'd like to support this" followed by bikeshedding as necessary over the technical details. My two branches above are a working-enough version of this proposal for what I was working on and my goal in linking them is to having a starting point for design conversations about how best to support no-std.
If accepted my plan would be to incrementally land the linked branch. Notably I'd like to transition a crate-at-a-time as opposed to landing everything all at once. This would need to start in wasm-tools
with the wasmparser
crate for example.
Put another way, I'd like to get others' thoughts on this. I plan on linking this in various locations to raise visibility for interested parties.