Closed alexcrichton closed 4 years ago
One of the things I'm interested in doing is creating a custom sandbox, where I provide some custom functions for a wasm module to import, and some wasi functions (but maybe not all of the wasi functions). I think your wasi_unstable
"bare minimum" function handles this case, by allowing me to pick from the returned HashMap and use whatever ones I want during instantiation. I can definitely see how this would interact with name resolution (I'd like to be able to detect if the module I'm running is importing a wasi function I'm unwilling to provide them)
One of the things that confused me about the current version of Callable
is how you store your results in a &mut [Val]
. Just based off the function signature, one of the things I tried first was:
fn call(&self, _params: &[Val], results: &mut [Val]) -> Result<(), HostRef<Trap>> {
if let Some(Val::I32(v)) = results.get_mut(0) {
*v = 42;
} // else return trap
}
The runtime error you get here is fairly inscrutable. I don't have a concrete proposal here, but I wanted to mention this minor papercut.
As I was hiding HostRef in the Module API in my PR #696 I noticed that once I removed the #[derive(Clone)]
from the ModuleInner, the compiler began to issue a complaint, namely that the store
field wasn't actually ever being used. I've considered maybe adding a #[allow(dead_code)]
to the field for now, but perhaps I should just go ahead and add a stores accessor (as you suggest up there) instead?
I've been looking for a scripting language to embed into some of my Rust game development projects, to facilitate scripting (with hot reloading!) custom logic and events for levels. Along the way, I've tried a bunch of different languages and have developed opinions about their APIs. So without further ado: A review of some Rusty embeddable scripting language APIs.
https://github.com/PistonDevelopers/dyon/blob/master/examples/call.rs
I very much dislike Dyon's scripting API.
In terms of features, most things are there (Dyon can return Rust structs to your Rust host code) but the parts that you'd like to be able to control, like deciding how Dyon objects are turned into Rust structs, are hidden from you using really arcane macros, and the parts that you don't care so much about, like wrapping everything in Arc
and declaring function type signatures by hand using Dyon's enums for that... aren't. It makes exposing a Rust API to Dyon scripts really annoying, and ultimately drove me away from using Dyon ever again.
https://github.com/zesterer/forge/blob/master/examples/callbacks.rs I'm actually very fond of Forge's embedding API. You can directly pass globals and functions to Forge's API using the builder pattern, which is patently Rusty and very ergonomic. I ended up not using Forge simply because the language itself lacks many features, and because the embedding doesn't yet support returning Rust structs from Forge, one of the things I can't do without for my use case.
https://github.com/jonathandturner/rhai
Rhai has very good documentation on their embedding API right in their README.MD.
The API is a little bit messy and could potentially be made cleaner by leveraging Rust's type system better, but the core concepts behind it are similar to Forge's (with a useful distinction between adding functions directly to the interpreting Engine and adding them to particular modules). Their embedding API also supports instantiating and manipulating Rust structs from Rhai. I ended up choosing not to use Rhai, however, because of the lack of features in the language (no for in
loop) which prevented it from being ergonomic to use.
https://pyo3.rs/master/python_from_rust.html I was looking for an easily embeddable pure Rust solution for my scripting needs, but ultimately I found that the pure Rust ecosystem just wasn't quite there yet. PyO3's wrapper around Python's C FFI fulfilled all of my needs and then some, and the macros were very helpful and seemed to work with me instead of against me as was the case with Dyon. I was able to hack together an abuse of Rust's type system which allows me to take a Rust struct that's returned from Python, make sure it's one of the ECS components in my game, and then introduce it into the simulation. So far I've used this to allow Python to override objects in my map files as they're being loaded, so that a Python script can i.e. choose one of several possible enemy formations stored in the map file and choose to load just one. Aside from that, I haven't tried this yet, but all of the machinery is also there for enemy formations and even rooms and such to be generated from scratch (instead of from a map file) in Python. https://github.com/cedric-h/hauntfall/blob/master/serv/src/config/level.rs
The ultimate solution to this problem, of course, is wasmtime, which is why I'm excited to see the Interface Types proposal implemented; that should remove most of the barriers that prevent its use in my (and the rest of the Rust gamedev community's) projects.
Instantiation is a bit wonky where you have to line up imports 1:1 with the expected imports of the module. We should explore ideas where we have a more name resolution based mechanism which leverages the module system. Would perhaps make it much easier to slot in WASI or slot in a module. Pretty tricky API though so we'd have to think elsewhere about this.
I would love to see functions provided from the host/embedder in the style of module exports, with interface types and names, and then have wasmtime handle name resolution.
Once we have linking, what if we have a new type for a "HostModule", with a module name and a set of named exported functions with types?
@eminence good point! I think that the details of Func
and how that's created are definitely not ergonomic right now and it's something we want to improve. I think though it's probably best to separate out discussion from this issue since I think that will largely be a separable concern. I'm hoping we can get a lot of the skeleton of the API improved through this issue, and then we can do deep dives like https://github.com/bytecodealliance/wasmtime/issues/727 into different APIs.
@cedric-h I don't think we'll want to add allow(dead_code)
but I think we largely just need to be careful about our incremental progress. So long as the tests all continue passing I think we're all good :)
@joshtriplett I've started a dedicated discussion for that at https://github.com/bytecodealliance/wasmtime/issues/727 since I think it'll be a big topic, want to take a look at the proposal there and see if it sounds like it'll meet your needs?
With https://github.com/bytecodealliance/wasmtime/pull/814 I believe everything here is either fixed or split out into a separate issue, so I'm going to close!
I've been reviewing the
wasmtime
crate from a Rust API perspective and ended up realizing that there's actually quite a few changes that I would like to make to the crate. I think that these changes are far too large so simply send in a PR, so I wanted to make sure that we had some discussion of this first!In this issue I hope to lay out a vision for an end-state
wasmtime
crate and what the API might look like. I'm assuming that we can incrementally reach this end-goal over time and the exact route through which we get here isn't too too important. In any case, I'm curious if others have thoughts on all this!Some of these items below may warrant their own separate issue as well, but I wanted to make sure that I had this all written down in one location first
High-level changes
wasmtime-*
crates should be private dependenciesStore
should implementDefault
Send
andSync
of all typesHostRef
,HostRef
is hiddenInstance
is no longer reference counted to ensure that it'sSend
meaning globals/tables/functions are no longer reference counted either.Config
wasmtime-*
typesSend
andSync
Engine
Default
(uses defaultConfig
)Send
andSync
Clone
)Store
Default
(uses defaultConfig
andEngine
)Should be(moved to https://github.com/bytecodealliance/wasmtime/issues/777)Send
andSync
Clone
)wasmtime-*
accessorsModule
Should be(covered by https://github.com/bytecodealliance/wasmtime/issues/777)Send
andSync
Clone
)&Store
accessorInstance
Store
(inferred fromModule
)Trap
(handled ifTrap
is anError
)module
andstore
accessorSend
~~ (moved to https://github.com/bytecodealliance/wasmtime/issues/793)find_export_by_name
=>get_export
Global
,Table
,Memory
Memory
should be fine to leave with an internalArc
Everything should be(moved to https://github.com/bytecodealliance/wasmtime/issues/793)Send
, onlyMemory
should beSync
Table::grow
shouldn't needmut
Global::set
shouldn't needmut
type
methods renamed toty
Memory::grow
shouldn't needmut
These types are only accessed via a reference when fetched through an
Instance
.Func
This type I think needs a lot of improvements. I think it's best to separate out these concerns into a separate issue, however. Some high-level unbaked thoughts are:
usize
) and registering it with a type signature. This would skip all trampolines/etc and we'd document the expected ABI. This would be anunsafe
method.MemoryType
,TableType
,GlobalType
Mutability
, otherwise seems good!ImportType
,ExportType
FuncType
Store
, but probably fine for now.Trap
Error
traitClone
,Send
, andSync
Val
AnyRef
will become some custom form ofRc
defined bywasmtime
itselfBox<u128>
for now to avoid blowing up the sizeVal
an entirely opaque struct, perhaps with adecode
method which returns a fullenum
. We may not want to commit to a public byte-by-byte representation of aVal
just yet. This seems like an advanced concern we can punt though.WASI
wasmtime
cratefn wasi_unstable(&WasiConfiguration) -> HashMap<String, Extern>
Instance
somehow?Name Resolution
Instantiation is a bit wonky where you have to line up imports 1:1 with the expected imports of the module. We should explore ideas where we have a more name resolution based mechanism which leverages the module system. Would perhaps make it much easier to slot in WASI or slot in a module. Pretty tricky API though so we'd have to think elsewhere about this.