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

Implement support for async functions in Wasmtime #2434

Merged
merged 21 commits into from
Feb 26, 2021

Conversation

alexcrichton
Copy link
Member

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!

@github-actions github-actions bot added the wasmtime:api Related to the API of the `wasmtime` crate itself label Nov 19, 2020
@github-actions
Copy link

Subscribe to Label Action

cc @peterhuene

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

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

  • peterhuene: wasmtime:api

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

Learn more.

alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 21, 2021
Instantiation right now uses a recursive `instantiate` function since it
was relatively easy to write that way, but this is unfortunately not
factored in a way friendly to the async implementation in bytecodealliance#2434. This
commit refactors the function to instead use an iterative loop and
refactors code in such a way that it should be easy to rebase bytecodealliance#2434 on
top of this change. The main goal is to make the body of `Instance::new`
as small as possible since it needs to be duplicated with
`Instance::new_async`.
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 21, 2021
Instantiation right now uses a recursive `instantiate` function since it
was relatively easy to write that way, but this is unfortunately not
factored in a way friendly to the async implementation in bytecodealliance#2434. This
commit refactors the function to instead use an iterative loop and
refactors code in such a way that it should be easy to rebase bytecodealliance#2434 on
top of this change. The main goal is to make the body of `Instance::new`
as small as possible since it needs to be duplicated with
`Instance::new_async`.
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 21, 2021
Instantiation right now uses a recursive `instantiate` function since it
was relatively easy to write that way, but this is unfortunately not
factored in a way friendly to the async implementation in bytecodealliance#2434. This
commit refactors the function to instead use an iterative loop and
refactors code in such a way that it should be easy to rebase bytecodealliance#2434 on
top of this change. The main goal is to make the body of `Instance::new`
as small as possible since it needs to be duplicated with
`Instance::new_async`.
alexcrichton added a commit that referenced this pull request Jan 21, 2021
Instantiation right now uses a recursive `instantiate` function since it
was relatively easy to write that way, but this is unfortunately not
factored in a way friendly to the async implementation in #2434. This
commit refactors the function to instead use an iterative loop and
refactors code in such a way that it should be easy to rebase #2434 on
top of this change. The main goal is to make the body of `Instance::new`
as small as possible since it needs to be duplicated with
`Instance::new_async`.
Copy link

@smacleod smacleod left a comment

Choose a reason for hiding this comment

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

(Since I'm commenting anyway, figured I'd say I'm excited for this feature. I've been using your WIP fiber crate to wrap the released Wasmtime and experiment with running Wasm asynchronously. So far it's working great on Mac OS and Linux x86_64)

crates/fiber/src/arch/aarch64.S Outdated Show resolved Hide resolved
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
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.
Turns out this is another caller-saved register!
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.
With fuel it's probably best to not provide any way to inject infinite
fuel.
* 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
.flat_map(|f| f.symbols())
.filter_map(|s| Some(s.name()?.to_string()))
.any(|s| s.contains("look_for_me"))
// TODO: apparently windows unwind routines don't unwind through fibers, so this will always fail. Is there a way we can fix that?
Copy link
Member

Choose a reason for hiding this comment

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

I'll ping some Microsoft debugger folks to see if it's a known limitation of StackWalkEx when the given thread is a fiber. I suspect RtlVirtualUnwind might not have this problem.

Copy link
Member

@peterhuene peterhuene left a comment

Choose a reason for hiding this comment

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

Just those few comments above. This looks fantastic and I'm excited to see what gets built on top of this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wasmtime:api Related to the API of the `wasmtime` crate itself
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants