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

Support Func imports with zero shims #839

Merged
merged 2 commits into from
Feb 4, 2020

Conversation

alexcrichton
Copy link
Member

This commit extends the Func type in the wasmtime crate with static
wrap* constructors. The goal of these constructors is to create a
Func type which has zero shims associated with it, creating as small
of a layer as possible between wasm code and calling imported Rust code.

This is achieved by creating an extern "C" shim function which matches
the ABI of what Cranelift will generate, and then the host function is
passed directly into an InstanceHandle to get called later. This also
enables enough inlining opportunities that LLVM will be able to see all
functions and inline everything to the point where your function is
called immediately from wasm, no questions asked.

There's a few items which I think need some more discussion on this PR before we land it, and I'm also just curious how others think of an API like this for embedding!

  • Figuring out how traps work. Right now Result<T, Trap> is intended to be supported as a return from a Rust closure, but without generating a function shim it's not trivial to generate a trap. Currently the code sort of cops out by manually doing a segfault via write_volatile, but that feels pretty brittle. I'm curious if others have a good idea of how to trigger a trap from Rust code?

  • Dealing with multi-value isn't supported yet. I talked with @fitzgen briefly but it sounds like we may not have a native ABI in Rust that matches up with multi-value returns. I'd ideally like to support Rust closures returning, for example, (i32, i32) to automatically get wired up to a multi-value return import, but the ABI of that Rust function I don't think matches what Cranelift currently does. I suspect that this will require some work in Cranelift to (maybe?) add a new ABI that matches the extern "C" abi for the native platform.

  • I'm also curious to largely just get some more eyes on this. I'm shooting for an extremely low overhead Func wrapper, but naturally there's a lot of possible unsafety here if things don't line up precisely. Close review would definitely be appreciated!

I've wanted to do this for some time but recently I've been thinking that the wasmtime-wasi crate should move over to the wasmtime API, but to preserve the current functional semantics where it has zero shims at runtime I felt was important, so I wanted to try to pursue this style of creating a low-overhead Func.

@pepyakin
Copy link
Collaborator

  1. Why don't ud2 work? It seems a bit better than a volatile write
  2. Traps are handled by signals and signal handling depends on SJLJ. Can we do straight longjmp?

@alexcrichton
Copy link
Member Author

Oh I should clarify in that I didn't actually try ud2, I just didn't know of a stable way to generate it so a segfault was easier.

I was initially hesitant to longjmp since there's some other trap state exposed, but now that I think more about it I think it would be fine to do so, so I'll try to get that implemented, surely makes more sense than a segfault :)

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.

Overall, this looks good.

But it all depends on us compiling exported wasm functions with the system ABI, and I'm not sure that we actually always do that or not, and it is a little scary that safety of this relies on something that is so far away and also isn't commented or documented anywhere as far as I can tell. I think we should do something to improve this, but I'm not sure exactly what yet. Thinking.

@alexcrichton
Copy link
Member Author

That's a good point yeah. Currently it boils down to this line which indicates that the ABI for the function that we're creating has, at least if the function name does what it says, the native calling convention.

There's certainly a lot of dots to connect here, though, and I doubt that this is 100% right just yet. I'd hope though that this is something we can continue to improve over time and fix through bug reports, this is in general a feature I think we want to work towards to enable fast imports by ensuring there are zero wasmtime-inserted shims. Currently there's a lot of indirection as arguments are moved through memory, vectors are allocated, etc.

@alexcrichton alexcrichton force-pushed the func-pointers branch 2 times, most recently from b61a393 to a48865e Compare January 24, 2020 16:39
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 24, 2020
This commit performs a few refactorings and fixes a bug as well. The
changes here are:

* The `thread_local!` in the `wasmtime` crate for trap information is
  removed. The thread local in the `wasmtime_runtime` crate is now
  leveraged to transmit trap information.

* Panics in user-provided functions are now caught explicitly to be
  carried across JIT code manually. Getting Rust panics unwinding
  through JIT code is pretty likely to be super tricky and difficult to
  do, so in the meantime we can get by with catching panics and resuming
  the panic once we've resumed in Rust code.

* Various take/record trap apis have all been removed in favor of
  working directly with `Trap` objects, where the internal trap object
  has been expanded slightly to encompass user-provided errors as well.

This borrows a bit bytecodealliance#839 and otherwise will...

Closes bytecodealliance#848
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 24, 2020
This commit performs a few refactorings and fixes a bug as well. The
changes here are:

* The `thread_local!` in the `wasmtime` crate for trap information is
  removed. The thread local in the `wasmtime_runtime` crate is now
  leveraged to transmit trap information.

* Panics in user-provided functions are now caught explicitly to be
  carried across JIT code manually. Getting Rust panics unwinding
  through JIT code is pretty likely to be super tricky and difficult to
  do, so in the meantime we can get by with catching panics and resuming
  the panic once we've resumed in Rust code.

* Various take/record trap apis have all been removed in favor of
  working directly with `Trap` objects, where the internal trap object
  has been expanded slightly to encompass user-provided errors as well.

This borrows a bit bytecodealliance#839 and otherwise will...

Closes bytecodealliance#848
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 24, 2020
This commit performs a few refactorings and fixes a bug as well. The
changes here are:

* The `thread_local!` in the `wasmtime` crate for trap information is
  removed. The thread local in the `wasmtime_runtime` crate is now
  leveraged to transmit trap information.

* Panics in user-provided functions are now caught explicitly to be
  carried across JIT code manually. Getting Rust panics unwinding
  through JIT code is pretty likely to be super tricky and difficult to
  do, so in the meantime we can get by with catching panics and resuming
  the panic once we've resumed in Rust code.

* Various take/record trap apis have all been removed in favor of
  working directly with `Trap` objects, where the internal trap object
  has been expanded slightly to encompass user-provided errors as well.

This borrows a bit bytecodealliance#839 and otherwise will...

Closes bytecodealliance#848
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 24, 2020
This commit performs a few refactorings and fixes a bug as well. The
changes here are:

* The `thread_local!` in the `wasmtime` crate for trap information is
  removed. The thread local in the `wasmtime_runtime` crate is now
  leveraged to transmit trap information.

* Panics in user-provided functions are now caught explicitly to be
  carried across JIT code manually. Getting Rust panics unwinding
  through JIT code is pretty likely to be super tricky and difficult to
  do, so in the meantime we can get by with catching panics and resuming
  the panic once we've resumed in Rust code.

* Various take/record trap apis have all been removed in favor of
  working directly with `Trap` objects, where the internal trap object
  has been expanded slightly to encompass user-provided errors as well.

This borrows a bit bytecodealliance#839 and otherwise will...

Closes bytecodealliance#848
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 24, 2020
This commit performs a few refactorings and fixes a bug as well. The
changes here are:

* The `thread_local!` in the `wasmtime` crate for trap information is
  removed. The thread local in the `wasmtime_runtime` crate is now
  leveraged to transmit trap information.

* Panics in user-provided functions are now caught explicitly to be
  carried across JIT code manually. Getting Rust panics unwinding
  through JIT code is pretty likely to be super tricky and difficult to
  do, so in the meantime we can get by with catching panics and resuming
  the panic once we've resumed in Rust code.

* Various take/record trap apis have all been removed in favor of
  working directly with `Trap` objects, where the internal trap object
  has been expanded slightly to encompass user-provided errors as well.

This borrows a bit bytecodealliance#839 and otherwise will...

Closes bytecodealliance#848
@alexcrichton
Copy link
Member Author

Ok after talking more with @sunfishcode I think this is ready to go once approved. To answer some of my original questions:

Figuring out how traps work

With @pepyakin's suggestion I feel much better about this. With #857 we're doing the strategy of an "explicit raise" function anyway, so I think this is the way to go.

Dealing with multi-value isn't supported yet

ABI-wise we're good to go today. Cranelift's "default system ABI" does indeed match the default system for arguments, and so we should be covered in that regard. Multi-value is fundamentally not matched to the C ABI today, and would require other fixes. I'll file an issue shortly about this on the cranelift repository.

I'm also curious to largely just get some more eyes on this

That's the review approval part :)

@alexcrichton
Copy link
Member Author

I've opened https://github.com/bytecodealliance/cranelift/issues/1369 about cranelift support for multi-value for this feature.

alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 30, 2020
This commit performs a few refactorings and fixes a bug as well. The
changes here are:

* The `thread_local!` in the `wasmtime` crate for trap information is
  removed. The thread local in the `wasmtime_runtime` crate is now
  leveraged to transmit trap information.

* Panics in user-provided functions are now caught explicitly to be
  carried across JIT code manually. Getting Rust panics unwinding
  through JIT code is pretty likely to be super tricky and difficult to
  do, so in the meantime we can get by with catching panics and resuming
  the panic once we've resumed in Rust code.

* Various take/record trap apis have all been removed in favor of
  working directly with `Trap` objects, where the internal trap object
  has been expanded slightly to encompass user-provided errors as well.

This borrows a bit bytecodealliance#839 and otherwise will...

Closes bytecodealliance#848
alexcrichton added a commit to alexcrichton/wasmtime that referenced this pull request Jan 30, 2020
This commit performs a few refactorings and fixes a bug as well. The
changes here are:

* The `thread_local!` in the `wasmtime` crate for trap information is
  removed. The thread local in the `wasmtime_runtime` crate is now
  leveraged to transmit trap information.

* Panics in user-provided functions are now caught explicitly to be
  carried across JIT code manually. Getting Rust panics unwinding
  through JIT code is pretty likely to be super tricky and difficult to
  do, so in the meantime we can get by with catching panics and resuming
  the panic once we've resumed in Rust code.

* Various take/record trap apis have all been removed in favor of
  working directly with `Trap` objects, where the internal trap object
  has been expanded slightly to encompass user-provided errors as well.

This borrows a bit bytecodealliance#839 and otherwise will...

Closes bytecodealliance#848
alexcrichton added a commit that referenced this pull request Jan 30, 2020
* Improve panics/traps from imported functions

This commit performs a few refactorings and fixes a bug as well. The
changes here are:

* The `thread_local!` in the `wasmtime` crate for trap information is
  removed. The thread local in the `wasmtime_runtime` crate is now
  leveraged to transmit trap information.

* Panics in user-provided functions are now caught explicitly to be
  carried across JIT code manually. Getting Rust panics unwinding
  through JIT code is pretty likely to be super tricky and difficult to
  do, so in the meantime we can get by with catching panics and resuming
  the panic once we've resumed in Rust code.

* Various take/record trap apis have all been removed in favor of
  working directly with `Trap` objects, where the internal trap object
  has been expanded slightly to encompass user-provided errors as well.

This borrows a bit #839 and otherwise will...

Closes #848

* Rename `r#return` to `ret`
Copy link
Collaborator

@pepyakin pepyakin left a comment

Choose a reason for hiding this comment

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

Change from the logic PoV looks good. I had one question though and also a comment regarding the unsafeness would be great!

{
let ret = {
let instance = InstanceHandle::from_vmctx(vmctx);
let func = instance.host_state().downcast_ref::<F>().expect("state");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not to block the merge, but I wonder what would your thoughts be on this. Here and below, if this expect triggers a panic is raised and the unwinding will reach the FFI boundary. On one hand, it feels that this code is somewhat trusted and we certainly never trigger this assert ( : ) ) and not worth the hassle addressing this, but on the other hand "with UB anything is possible"

crates/api/src/trampoline/mod.rs Show resolved Hide resolved
This commit extends the `Func` type in the `wasmtime` crate with static
`wrap*` constructors. The goal of these constructors is to create a
`Func` type which has zero shims associated with it, creating as small
of a layer as possible between wasm code and calling imported Rust code.

This is achieved by creating an `extern "C"` shim function which matches
the ABI of what Cranelift will generate, and then the host function is
passed directly into an `InstanceHandle` to get called later. This also
enables enough inlining opportunities that LLVM will be able to see all
functions and inline everything to the point where your function is
called immediately from wasm, no questions asked.
@alexcrichton
Copy link
Member Author

Sure thing, added a comment!

I'm gonna go ahead and merge this once green and we can continue to iterate on it as necessary.

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

Successfully merging this pull request may close these issues.

None yet

3 participants