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

Reactor support. #1565

Merged
merged 18 commits into from
May 26, 2020
Merged

Reactor support. #1565

merged 18 commits into from
May 26, 2020

Conversation

sunfishcode
Copy link
Member

This implements the new WASI ABI described here:

https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md

It adds APIs to Instance and Linker with support for running
WASI programs, and also simplifies the process of instantiating
WASI API modules.

This currently only includes Rust API support.

@sunfishcode sunfishcode added the wasmtime Issues about wasmtime that don't fall into another label label Apr 21, 2020
@github-actions github-actions bot added wasi Issues pertaining to WASI wasmtime:api Related to the API of the `wasmtime` crate itself wasmtime:docs Issues related to Wasmtime's documentation labels Apr 21, 2020
@github-actions
Copy link

Subscribe to Label Action

cc @kubkon, @peterhuene

This issue or pull request has been labeled: "wasi", "wasmtime", "wasmtime:api", "wasmtime:docs"

Thus the following users have been cc'd because of the following labels:

  • kubkon: wasi
  • peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

RELEASES.md Outdated Show resolved Hide resolved
/// the initialized `Instance` is returned.
///
/// [WASI ABI initialization]: https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi
pub fn new_wasi_abi(module: &Module, imports: &[Extern]) -> Result<Option<Instance>, Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this API makes sense given WASI today, but this feels sort of uncomfortable. This means that we're splitting the world between "instantiate this module" and "instantiate this wasi module". These constructor semantics need to be bubbled to all callers and such.

Would it be possible to avoid splitting into two worlds? One option might be to have this instead of a constructor:

impl Instance {
    fn initialize_wasi(self) -> Result<Option<Instance>, Error> {
        // ...
    }
}

That way you'd always create the Instance the same way and it's up to you what to do with it after that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm imagining new_wasi_abi will be a common case. The name is perhaps confusing because this doesn't mean "run with all the WASI APIs available", but just "run the WASI startup functions if present". I actually wonder if we should rename this to new and rename the current new to new_raw or something, to emphasize that it's not automatically running the WASI startup functions (though it does still run the wasm start function).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally still feel that this is not the API we want. Adding a new constructor for instances which "does the wasi thing" is infectious and will continue to force us to duplicate API surface area into the future. I feel remembering to call new_wasi_abi is basically equivalent to remembering to call initialize_wasi, and in terms of composability I feel that an explicit function to initialize is more convenient.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lucet change to disconnect initialization from instantiation also points in this direction.

I experimented with a few different API approaches to this, and came up with the patch that's now pushed here. With this, the Rust API's Instance::new and Linker::instantiate return a NewInstance, which is a wrapper around Instance where the programmer has to initialize it before getting the Instance out. We may need to do something different for the C API though.

src/commands/run.rs Outdated Show resolved Hide resolved
crates/wasi/src/lib.rs Outdated Show resolved Hide resolved
examples/wasi/main.rs Outdated Show resolved Hide resolved
@github-actions github-actions bot added the wasmtime:c-api Issues pertaining to the C API. label May 18, 2020
@github-actions
Copy link

Subscribe to Label Action

cc @peterhuene

This issue or pull request has been labeled: "wasmtime:c-api"

Thus the following users have been cc'd because of the following labels:

  • peterhuene: wasmtime:c-api

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

@alexcrichton
Copy link
Member

I do like this approach better myself, but I feel like it's still a bit too heavyweight because it makes NewInstance almost exclusively WASI-focused. I would expect Instance to be neutral with respect to WASI-or-not and users have to opt-in to WASI-specific hooks.

I realize it can be a small footgun if these methods are added directly to Instance, but I feel that the footgun is worth it in terms of ergonomics. For example if you're reading the documentation of the wasmtime crate all the examples have init_reactor(&[]) which is somewhat bizarre if you've never heard of WASI before.

@sunfishcode
Copy link
Member Author

They aren't really WASI-specific. I expect that between the linking spec and interface types, the definition of commands and reactors will eventually want to move out of WASI and into one of those other specs. Would it help if we moved the definition into tool-conventions for now? We could then avoid calling these "WASI" functions. Would that help avoid confusion?

I'm also not tied to the name init_reactor. Would .initialize(&[]) or something else look less surprising?

@github-actions github-actions bot added the fuzzing Issues related to our fuzzing infrastructure label May 20, 2020
@github-actions
Copy link

Subscribe to Label Action

cc @fitzgen

This issue or pull request has been labeled: "fuzzing"

Thus the following users have been cc'd because of the following labels:

  • fitzgen: fuzzing

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

@alexcrichton
Copy link
Member

Oh sorry, so I mean to say that the current as-is state of NewInstance feels very WASI-specific. The options you have are:

  • start - this is actually somewhat unrelated to the wasm start function and classifies every "normal" wasm module as a "reactor"
  • run_command, init_reactor - WASI-specific

There's not really an easy option for "just give me the Instance I don't care about WASI". Even with init_reactor you have to pass an array of arguments which can be confusing.

This is why I still think it's best to return an work with an Instance. The type-level distinction of a NewInstance I don't think is buying us much here. If we really want we could always probe what an Instance looks like and dynamically guard invocations of functions. We could say, for example, that you can't call WASI functions before you call the wasi initialization function which does the command/reactor thing.

This implements the new WASI ABI described here:

https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md

It adds APIs to `Instance` and `Linker` with support for running
WASI programs, and also simplifies the process of instantiating
WASI API modules.

This currently only includes Rust API support.
This also separates instantiation from initialization in a manner
similar to bytecodealliance/lucet#506.
@sunfishcode
Copy link
Member Author

Ok, I've now gone back to Instance::new not doing any command/reactor stuff. I kept NewInstance, with a start function that now just runs the wasm start function. That also simplifies the C API story, so this patch now has C API updates corresponding to the Rust API updates.

The new approach now adds a new function Linker::module, which handles instantiating the module, automatically handling command/reactor semantics, which ends up being a lot nicer because we don't need APIs that conditionally consume an Instance. Linker::module avoids the user having to deal with Instances altogether.

Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I think the usage of Linker here is pretty slick.

Would it be possible to add some API-level tests for the new Linker methods too?

src/commands/run.rs Outdated Show resolved Hide resolved
crates/wasmtime/src/instance.rs Outdated Show resolved Hide resolved
crates/wasmtime/src/linker.rs Outdated Show resolved Hide resolved
crates/wasmtime/src/linker.rs Outdated Show resolved Hide resolved
crates/wasmtime/src/linker.rs Show resolved Hide resolved
crates/wasmtime/src/linker.rs Show resolved Hide resolved
///
/// An export with an empty string is considered to be a "default export".
/// "_start" is also recognized for compatibility.
pub fn get_default(&self, module: &str) -> Result<Func> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function feels a bit odd to me because the notion of a "default" export doesn't seems super well defined. Is there WASI documentation this could link to?

In isolation it seems like this API should be on an Instance as well, but I understand why it's here instead of instances at this time. Perhaps they could share an implementation of sorts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also since this is WASI-specific would it make sense to do something like get_wasi_default()?

@@ -270,6 +271,117 @@ impl Linker {
Ok(self)
}

/// Define automatic instantiations of a [`Module`] in this linker.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should highlight very clearly that that the major purpose of this method is to transparently handle WASI conventions as well, e.g. "WASI" should be somewhere in this heading.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually trying to de-emphasize "WASI" here. Commands and Reactors are currently defined in a WASI-repository page, but they're not related to WASI APIs, and I expect they'll eventually move out into the linking and/or interface-types specs.

}

fn handle_module(&self, store: &Store, module_registry: &ModuleRegistry) -> Result<()> {
fn load_main_module(&self, linker: &mut Linker) -> Result<()> {
if let Some(timeout) = self.wasm_timeout {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a "preexisting" bug but we should probably at some point spawn this thread before we start instantiating anything to account for start functions and _initialize and such.

(although ideally still discounting compile times of Module)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, not something to handle in this PR, just a thought.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Perhaps, if caching is enabled and a timeout is requested, we can prime the cache with module compilations, and then start the timer before we start the Linker. If caching is disabled, then we just count compilation time toward the timeout, perhaps.

// Use "" as a default module name.
let module = Module::from_file(linker.store(), &self.module)?;
linker
.module("", &module)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While probably not a huge issue it's a bit odd that we have to assign a name to the instance here, since ideally we'd pull out an instance and "do the wasi thing" on it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a name here because I think src/commands/run.rs shouldn't be pulling out the instance and doing the initialization manually. The Linker handles that for it, which is good because then the functionality is available to API users as well as command-line users.

@alexcrichton
Copy link
Member

Ok this all looks great to me, thanks again for this @sunfishcode! I feel like anything further we can continue to iterate on in-tree if necessary, so I'm gonna merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fuzzing Issues related to our fuzzing infrastructure wasi Issues pertaining to WASI wasmtime:api Related to the API of the `wasmtime` crate itself wasmtime:c-api Issues pertaining to the C API. wasmtime:docs Issues related to Wasmtime's documentation wasmtime Issues about wasmtime that don't fall into another label
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants