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

RFC: Add async support to wasmtime #2

Merged
merged 1 commit into from
Feb 26, 2021

Conversation

alexcrichton
Copy link
Member

This RFC proposes adding native support to the wasmtime crate for
imported functions which are async. Additionally it provides APIs to
call wasm functions as an async function.

rendered

This RFC proposes adding native support to the `wasmtime` crate for
imported functions which are `async`. Additionally it provides APIs to
call wasm functions as an `async` function.
ty: FuncType,
state: T,
func: fn(Caller<'a>, &'a T, &'a [Val], &'a mut [Val]) -> R,
) -> Func
Copy link
Member

Choose a reason for hiding this comment

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

Should this (and wrap_async) be -> Result<Func> so that it can gracefully fail when given a non-async Store?

Copy link
Member

Choose a reason for hiding this comment

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

Erm, should have kept reading to where you talk about panicking. Not sure I like the idea of panicking for the mismatch, but I can see the logic in doing so.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's true yeah that either way is technically possible, but I would personally tend to classify this as a programmer error because applications would be expected to pick one or the other of async or not, and if you get it wrong by accident it seems like you'd want to discover that ASAP

Copy link
Member

Choose a reason for hiding this comment

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

I'm onboard with panicking 👍

Copy link
Member

Choose a reason for hiding this comment

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

I agree with panicking on mismatch, if async is an internal implementation detail of the Store.

Is there any way, though, that we could capture this in the type system, and then produce a type error at compile time?

I can think of two ways to do that. (All names below are drafts, intended as placeholders; we can always change them.)

  • We could make Store take a type parameter for a marker type, where most functions accept Store<A> for any A, but functions that require an async-capable store require Store<AsyncStore>. Most Store functions would be implemented for Store<A> for any A, but Store::new and Store::new_async would return one of the two specific types. Then, if you attempt to call Func::new_async with a Store<SyncStore> you'll get a compile error. This has two advantages: Store is the same type with no duplicate implementation effort, and the async property becomes a compile-time property rather than a run-time boolean. Most code will only ever use one of those two types, so this should not add to code size; any given program should only monomorphize one of those two types. The disadvantage would be having to pass around the generic parameter, but that seems worth getting this checked at compile time. We could also have type aliases for the two types.

  • We could have two types, Store and StoreAsync, and implement a sealed trait AnyStore on them (so that those are the only two instances). Most functions would accept impl AnyStore, and functions like Func::new_async would accept only StoreAsync specifically. This has the advantage of not requiring a generic parameter everywhere. The disadvantage is that we'd need a more complex set of forwarding functions within wasmtime to handle the two distinct types, so while we could share one implementation, we'll have 2-3 sets of function signatures and simple forwarding stubs for many of the same functions.

Personally, I'd favor the former approach: Store<SyncStore>, Store<AsyncStore>, and type aliases for those two so people don't normally have to think about the generic parameter.

Thoughts?

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 think it's possible to move this into the type system but I'd fear that we're kind of shooting ourselves in the foot too much and/or sacrificing ergonomics. We would not only need an async store type-level distinction but we would also need and async function type-level distinction because if you extract a function from an instance then you can only call it asynchronously in an async store. But if we don't want a runtime footgun then we'd also need async instances, and this quickly spirals to creating almost double the api surface area.

Overall it seemed that given the use cases in mind a runtime flag would be fine here and errors would be weeded out very quickly at runtime instead of surprising someone in production by accident.

Copy link
Member

Choose a reason for hiding this comment

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

@alexcrichton Ah, I see. Having to add that parameter to many different types does indeed make this much less appealing.

One other alternative: could we consider a wasmtime feature flag for this, enabling/disabling async support, and then if it's enabled, we automatically use it for everything?

A concrete use case in my mind that's motivating the attempt to make this more than just a runtime flag: suppose you're writing a library crate that works with wasmtime and you provide functions to be called with wasmtime types, but you're not the top-level crate that's instantiating wasmtime. For instance, you want to write a library crate to expose some interesting functions, or to manage some objects. Conversely, the top-level crate may not know if it needs to call new or new_async, because it doesn't need any async itself but its dependencies might. If your crate requires async support, it seems unfortunate if that only lives in your crate's documentation, and you have to tell people "make sure to use new_async or you'll get runtime panics". I'd really love for the crate to be able to express, whether through the type system or through a cargo feature, "I need async and won't work without it".

If that's not possible, I understand. But if there's any way we can let crates surface "I need an async runtime" programmatically and ensure that they get one, rather than just adding it to their README, I think that'd be a usability improvement.

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 definitely agree that the scenario you're outlining is a downside. Unfortunately though I don't know of a fix other than two crates, wasmtime and wasmtime-async. Switching between async/not-async isn't really a good fit for Cargo features which are additive (but this is more mutually exclusive).

The only real way for embedders to support this in libraries would be to either pick one or to also support both async and sync APIs.

Put another way, I think a runtime flag is a local optimum for "don't duplicate the API surface area" as a constraint. Otherwise there are possibilities like different types or different crates I think, although I'm not sure which is best myself.

@peterhuene
Copy link
Member

peterhuene commented Nov 11, 2020

This looks great and I'm really excited for this!

I think what is outlined in this RFC makes sense and I don't have concerns other than how tricky implementing a feature like this will be.

I'm also in the process of writing an RFC for "instance pools" where we can reserve all of an instance's resources upfront (with configurable constraints since limits will need to be enforced) in a module-agnostic manner to allow for extremely fast instance creation in highly-concurrent scenarios à la Lucet. Part of that work will include setting up a region in virtual memory that can be used for the alternative stack for each instance and thus will have some overlap with this proposal.

@alexcrichton
Copy link
Member Author

Heh yeah I figured that the work you're doing would lend itself well to stack allocation, so seems reasonable to defer to that!

@fitzgen
Copy link
Member

fitzgen commented Nov 16, 2020

Because we are never spawning futures ourselves, and because leaf Futures hold a waker handle, we don't need to worry about taking in an executor handle or anything like that right? Basically I just want to confirm that we aren't wedding ourselves to a particular executor, which we shouldn't do.

I guess if Func::new_async et al took a handle to the executor, they could pump the event loop in place rather than switching stacks and "remotely" returning NotReady. Not advocating for this though, as event loop reentry has been a terrible hair mess every time I've ever ran into it...

@alexcrichton
Copy link
Member Author

Right yeah, that's a goal of this integration is that it's all executor-agnostic. Wasmtime won't necessarily be a leaf future but we'll always be executing within the context of a future's poll method, meaning we'll always have a Context to pass to any future we get from calling an async host function.

@alexcrichton
Copy link
Member Author

Ok I've been implementing this and I've run into a snag so far which is somewhat unfortunate. This proposed signature:

impl Func {
    pub fn new_async<'a, T, R>(
        store: &Store,
        ty: FuncType,
        state: T,
        func: fn(Caller<'a>, &'a T, &'a [Val], &'a mut [Val]) -> R,
    ) -> Func
    where
        R: Future<Output = Result<(), Trap>> + 'a,
    {
        // ...
    }
}

does not compile in that it's not what we want. The problem with this signature is that it's letting the caller pick 'a, which instead we want the callee to pick 'a (a higher-rank bound). Otherwise this, which is memory unsafe, would be allowed:

async fn foo(caller: Caller<'static>, state: &'static i32, params: &'static [Val], results: &'static [Val]) -> Result<(), Trap> {
    // ... 
}

Func::new_async(store, ty, 0i32, |a, b, c, d| async { foo(a, b, c, d).await })

Clearly 'static isn't correct in this case!

Unfortunately though when using a HRTB I can't figure out how to return an unboxed future. This means that the final signature I'm working with is:

pub fn new_async<T, R, F>(store: &Store, ty: FuncType, state: T, func: F) -> Func
where                                                                            
    F: for<'a> Fn(                                                               
            Caller<'a>,                                                          
            &'a T,                                                               
            &'a [Val],                                                           
            &'a mut [Val],                                                       
        ) -> Box<dyn Future<Output = Result<(), Trap>> + 'a>                     
        + 'static,                                                               
    T: 'static,                                                          

which... wow is that a mouthful. It expresses what we want though in that as the callee we pick 'a and you as the caller have to work with whatever we pick, meaning that the lifetimes are anonymous to you and you can't persist them beyond your call frame (which is what we want). The major downside to this is that the call-site of this function now looks like:

Func::wrap_async(store, state, |state, param| Box::new(async { state.async_method(param) }))

which... is also a mouthful.

Unfortunately though I can't really figure out how to make this more ergonomic. I basically don't know how to write a function that takes an async function. I don't think that Rust-the-language really has what we want at this time in that this is just a fact of life when working with async, but if others have suggestions on how to improve this they would be most welcome!

@fitzgen
Copy link
Member

fitzgen commented Nov 18, 2020

I think this works for making the future not a boxed trait object:

pub fn new_async<T, R, Fut, F>(store: &Store, ty: FuncType, state: T, func: F) -> Func
where
    F: for<'a> Fn(
            Caller<'a>,
            &'a T,
            &'a [Val],
            &'a mut [Val],
        ) -> Fut
        + 'static,
    T: 'static,
    Fut: Future<Output = ()>
{
    todo!()
}

Or am I missing something? I haven't actually tried calling it with anything so it might be one of those rare signatures that type checks alone, but doesn't actually work at any call sites.

@alexcrichton
Copy link
Member Author

Ah yeah that will indeed compile, but the problem is that the future returned can't borrow any of the input arguments with that signature or the input state itself. That means that it won't be compatible with most async fn definitions which would otherwise just take those parameters as arguments (e.g. would have &self as a method receiver).

This is where I think it's not possible in Rust, what we really want is impl Fn() -> impl Future but that requires higher-rank type parameters, not just higher-rank lifetime parameters.

@fitzgen
Copy link
Member

fitzgen commented Nov 18, 2020

Yeah you would need to put closed-over state into Arcs and clone them into an async move result or something. Whether this is better than boxing the future up into a trait object isn't clear to me.

@alexcrichton
Copy link
Member Author

If you can't close over the arguments, however, I fear that it loses a key bit of expressivity with async functions. One consequence, for example, is that for Func::new you can't ever actually safely fill in the results array. The returned future can't borrow it so it looks like it's only valid for the initial creation of the future, not for the duration of the future (which it actually is). Furthermore having to close over your own state is probably not how libraries are written already, generally using &self as a method receiver, forcing you to otherwise do:

let my_state = the_rc.clone();
let func = Func::wrap_async(&store, move || {
    let futures_state = my_state.clone();
    async move { futures_state.async_method().await }
});

compared with, if you can close over state:

let my_state = the_rc.clone();
let func = Func::wrap_async(&store, my_state, move |state| {
    Box::new(async move { state.async_method().await })
});

Overall I think there's not a great answer here. Nothing feels "obviously right" because the async story in Rust is still getting baked. I think we're going to be forced to pick the lesser of two evils, and I would personally think that we should sacrifice ergonomics first before sacrificing capabilities in the API (e.g. what you can/can't borrow)

@fitzgen
Copy link
Member

fitzgen commented Nov 18, 2020

One consequence, for example, is that for Func::new you can't ever actually safely fill in the results array.

lol good point

yeah seems like a boxed trait object is the way to go until some future time when Rust gets more expressive.

alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Nov 19, 2020
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
@alexcrichton
Copy link
Member Author

Ok I've posted the initial implementation I've got for this. The only API change other than what I already mentioned above is that Func::wrap_async ended up not working out due to what I think is a compiler bug that made the ergonomics of using it unbearable. Instead of a singular Func::wrap_async there's now a huge suite of Func::wrapN_async functions that all force the closure to take a Caller<'_>.

Overall I'm really not all that happy with how the API turn out in terms of ergonomics, but I am pretty happy with where it is in terms of expressivity and what you can do with it.

@joshtriplett
Copy link
Member

joshtriplett commented Nov 20, 2020

I posted a comment above about checking for sync/async stores via the type system at compile time, rather than panicking at runtime.

I'm also wondering: will this be forward-compatible with wasm being able to call host functions with a promise-like interface? Right now, the current proposal still looks like a blocking call to wasm. Eventually, I'd like wasm to be able to ask the host to start a computation and return a promise, and later ask for the value from the promise, while doing other work in the interim (including calling other host functions that return promises, as well).

@alexcrichton
Copy link
Member Author

AFAIK there's not any proposals right now for wasm calling a host function and getting a promise of some kind. In that sense it's hard to say. Otherwise to do what you're thinking today I believe that would need host-layer emulation where you'd return handles back to wasm and wasm could query the status of the handle later on.

@joshtriplett
Copy link
Member

joshtriplett commented Nov 23, 2020 via email

@alexcrichton
Copy link
Member Author

@joshtriplett oh that's right for defining functions but calling functions must match the type of store. As a library you could always define a synchronous host function, but for calling wasm you're always going to need to match the store which is where the mutual exclusivity comes into play.

alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 21, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
@alexcrichton
Copy link
Member Author

alexcrichton commented Jan 26, 2021

Motion to finalize with a disposition to merge

I'm proposing that we merge this RFC.

Early feedback has been received and all requested outstanding changes to the proposal has been made.

An implementation in Wasmtime has already been started.

Stakeholders sign-off

Tagging all employees of BA-affiliated companies who have committed to the Wasmtime repo in the last three months plus anyone who has given feedback on this PR as a stakeholder.

Fastly

IBM

Intel

Mozilla

Copy link
Member

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

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

✔️

Copy link
Contributor

@pchickey pchickey left a comment

Choose a reason for hiding this comment

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

🎉

alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 26, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 29, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Feb 1, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
peterhuene pushed a commit to peterhuene/wasmtime that referenced this pull request Feb 4, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
peterhuene pushed a commit to peterhuene/wasmtime that referenced this pull request Feb 4, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
peterhuene pushed a commit to peterhuene/wasmtime that referenced this pull request Feb 4, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
peterhuene pushed a commit to peterhuene/wasmtime that referenced this pull request Feb 11, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
peterhuene pushed a commit to peterhuene/wasmtime that referenced this pull request Feb 12, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
@tschneidereit
Copy link
Member

@abrown would you be up for giving this a look? It's blocking #5 as well which you r+'d, and we're quite eager to merge both. (It's blocking #5 because of stack handling, and more on the implementation than the RFC side, to be clear.)

@alexcrichton
Copy link
Member Author

Entering Final Call Period

https://github.com/bytecodealliance/rfcs/blob/main/accepted/rfc-process.md#making-a-decision-merge-or-close

Once any stakeholder from a different group has signed off, the RFC will move into a 10 calendar day final comment period (FCP), long enough to ensure that other stakeholders have at least a full business week to respond.

The FCP will end on February 26th.

peterhuene pushed a commit to peterhuene/wasmtime that referenced this pull request Feb 18, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Feb 18, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
peterhuene pushed a commit to peterhuene/wasmtime that referenced this pull request Feb 19, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
@alexcrichton
Copy link
Member Author

The FCP has elapsed without any objections being raised. I'm going to merge this. Thanks everybody!

@alexcrichton alexcrichton merged commit 32c3bd4 into bytecodealliance:main Feb 26, 2021
@alexcrichton alexcrichton deleted the async-wasmtime branch February 26, 2021 15:10
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Feb 26, 2021
This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2
alexcrichton added a commit to bytecodealliance/wasmtime that referenced this pull request Feb 26, 2021
* Implement support for `async` functions in Wasmtime

This is an implementation of [RFC 2] in Wasmtime which is to support
`async`-defined host functions. At a high level support is added by
executing WebAssembly code that might invoke an asynchronous host
function on a separate native stack. When the host function's future is
not ready we switch back to the main native stack to continue execution.

There's a whole bunch of details in this commit, and it's a bit much to
go over them all here in this commit message. The most important changes
here are:

* A new `wasmtime-fiber` crate has been written to manage the low-level
  details of stack-switching. Unixes use `mmap` to allocate a stack and
  Windows uses the native fibers implementation. We'll surely want to
  refactor this to move stack allocation elsewhere in the future. Fibers
  are intended to be relatively general with a lot of type paremters to
  fling values back and forth across suspension points. The whole crate
  is a giant wad of `unsafe` unfortunately and involves handwritten
  assembly with custom dwarf CFI directives to boot. Definitely deserves
  a close eye in review!

* The `Store` type has two new methods -- `block_on` and `on_fiber`
  which bridge between the async and non-async worlds. Lots of unsafe
  fiddly bits here as we're trying to communicate context pointers
  between disparate portions of the code. Extra eyes and care in review
  is greatly appreciated.

* The APIs for binding `async` functions are unfortunately pretty ugly
  in `Func`. This is mostly due to language limitations and compiler
  bugs (I believe) in Rust. Instead of `Func::wrap` we have a
  `Func::wrapN_async` family of methods, and we've also got a whole
  bunch of `Func::getN_async` methods now too. It may be worth
  rethinking the API of `Func` to try to make the documentation page
  actually grok'able.

This isn't super heavily tested but the various test should suffice for
engaging hopefully nearly all the infrastructure in one form or another.
This is just the start though!

[RFC 2]: bytecodealliance/rfcs#2

* Add wasmtime-fiber to publish script

* Save vector/float registers on ARM too.

* Fix a typo

* Update lock file

* Implement periodically yielding with fuel consumption

This commit implements APIs on `Store` to periodically yield execution
of futures through the consumption of fuel. When fuel runs out a
future's execution is yielded back to the caller, and then upon
resumption fuel is re-injected. The goal of this is to allow cooperative
multi-tasking with futures.

* Fix compile without async

* Save/restore the frame pointer in fiber switching

Turns out this is another caller-saved register!

* Simplify x86_64 fiber asm

Take a leaf out of aarch64's playbook and don't have extra memory to
load/store these arguments, instead leverage how `wasmtime_fiber_switch`
already loads a bunch of data into registers which we can then
immediately start using on a fiber's start without any extra memory
accesses.

* Add x86 support to wasmtime-fiber

* Add ARM32 support to fiber crate

* Make fiber build file probing more flexible

* Use CreateFiberEx on Windows

* Remove a stray no-longer-used trait declaration

* Don't reach into `Caller` internals

* Tweak async fuel to eventually run out.

With fuel it's probably best to not provide any way to inject infinite
fuel.

* Fix some typos

* Cleanup asm a bit

* Use a shared header file to deduplicate some directives
* Guarantee hidden visibility for functions
* Enable gc-sections on macOS x86_64
* Add `.type` annotations for ARM

* Update lock file

* Fix compile error

* Review comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants