-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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 for funcref
s, ref.func
, and table.grow
with funcref
s
#1901
Conversation
Subscribe to Label Actioncc @bnjbvr, @peterhuene
This issue or pull request has been labeled: "cranelift", "cranelift:wasm", "wasmtime:api"
Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, that was smaller than I expected! One thing we'll need to handle is that ValueType::from_wasmtime_type
is incorrect if both externref
and funcref
translate to the same underlying cranelift type.
Other than that, though, I was hoping that we wouldn't have to allocate an ExternRef
for dealing with funcref
. There's no real need to GC them since they have the same lifetime of the Instance
in theory, so it seems like wasted time to GC them and to allocate new data structures for them.
Thinking a bit about this, can we perhaps make the representation of funcref
to be *mut VMCallerCheckedAnyfunc
? That way it would still be word-sized (easy to manage) and we could easily implement calling that and such. Additionally it's very easy to convert that into a wasmtime::Func
, and we'll probably just need to update wasmtime::Func
to internally contain *mut VMCallerCheckedAnyfunc
to make it easy to create a Func
. "morally" at least I'd expect that going from funcref
to wasmtime::Func
should basically be "pair the internal representation with a Store
and you're off to the races"
We statically know all possible indices that can be referenced by ref.func
, so I think what we can do is something like this:
- The function index may be referenced by an element segment (passive/declared/active). In that case we don't currently ever drop those until the end of the
Store
, so assuming that we store all these element segments asVec<VMCallerCheckedAnyfunc>
(as we do today for tables) we could just load a constant offset from an element segment (e.g function N is at element segment A at offset B) - Otherwise we can allocate a
Vec<VMCallerCheckedAnyfunc>
specifically for non-element-segment-referenced indices (those referenced through globals for now) and attach that theVMContext
. I guess not really allocate aVec<T>
but we would just make theVMContext
allocation bigger.
In any case I think it'd be best if we could leverage the current state of things to avoid GC/allocation overhead for funcref
if we can. WDYT?
crates/runtime/src/table.rs
Outdated
#[cfg(target_pointer_width = "32")] | ||
TableElements::ExternRefs(_) => TableElementType::Val(ir::types::R32), | ||
#[cfg(target_pointer_width = "64")] | ||
TableElements::ExternRefs(_) => TableElementType::Val(ir::types::R64), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW this pattern with #[cfg]
might be good to encapsulate somewhere with something like ir::types::rsize()
That definitely sounds like a better design, albeit more implementation effort. How strongly do you feel vs waiting until we actually care about the performance of
I know that this is true at the spec level with the |
I think it's fine to take the shortest path for now regardless of perf to a complete implementation of reference types, but looking forward to things like Currently AFAIK the |
My one concern with your proposed design is just figuring out where to stick the new side tables for |
Perhaps we could temporarily allocate a |
Pushed a wip commit that switches to using |
Subscribe to Label Actioncc @fitzgen
This issue or pull request has been labeled: "fuzzing"
Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
@alexcrichton okay I think this is ready for review again. |
Subscribe to Label Actioncc @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:
To subscribe or unsubscribe from this label, edit the |
0a9f160
to
7c0d77a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great! My main comment below is about the change to Extern
which had a pretty large impact, but I don't think should be necessary.
Otherwise, though, could you add some tests which acquire a Val::FuncRef
and then test whether it's None
or Some
, and if Some
calls the function to make sure it gets the right result?
Additionally I think some tests would be good for passing in various FuncRef
values. For example Some
, None
, something from another store, something from another instance, etc.
crates/wasmtime/src/values.rs
Outdated
} | ||
} | ||
Val::FuncRef(None) => store.null_func_ref(), | ||
Val::FuncRef(Some(f)) => f.caller_checked_anyfunc(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An idle thought, but we could perhaps have something like:
impl Func {
pub(crate) fn into_raw(self, dst: &Store) -> Result<NonNull<VMCallerCheckedAnyfunc>> {
// ...
}
}
to bake in how you can't access the raw value without using the right store. Anyway not necessary to do here, just a thought!
@alexcrichton okay, I've deduped the null func refs into the store, and made Still need to write a couple tests that pass in |
Looks great to me! I've got one final question though before landing, which I just thought of after reading this. Could we be even more clever and make the null funcref a literal null pointer? It looks like you were worried about the cost of With that in place translation of |
The concern wasn't about the path for Originally, we couldn't use a null pointer for a null func ref, but after all the refactorings this PR has been through, that might not be the case anymore. Let me double check. Either way, I think |
Oh I'm not actually worried about the cost of Could you elaborate on the cache misses, though? With this current incantation a |
The extra potential cache miss is from changing table elements from It makes it so that there is an extra pointer chase to get to the |
Ah ok, I remember talking about that yeah. I thought you meant that the extra cache miss was about the representation of the null function in the sense of is it a null pointer or is it a sentinel. |
…thods This serves two purposes: 1. It ensures that we call `get_or_create_table` to ensure that the embedder already had a chance to create the given table (although this is mostly redundant due to validation). 2. It allows the embedder to easily get the `ir::TableData` associated with this table, and more easily emit whatever inline JIT code to translate the table instruction (rather than falling back to VM calls).
* Allow different Cranelift IR types to be used for different Wasm reference types. * Do not assume that all Wasm reference types are always a Cranelift IR reference type. For example, `funcref`s might not need GC in some implementations, and can therefore be represented with a pointer rather than a reference type.
…ncIndex` It was previously taking a raw `u32`. This change makes it more clear what index space that index points into.
phew. okay.
@alexcrichton I think this is ready for another (and hopefully the last!) round of review. Thanks for your patience with this one! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there's some warnings on aarch64 causing CI to fail, but otherwise 👍 from me, thanks for being patient with me too :)
Mind adding a test or two for ref.is_null
while you're at it?
c9cf3ec
to
818a814
Compare
`funcref`s are implemented as `NonNull<VMCallerCheckedAnyfunc>`. This should be more efficient than using a `VMExternRef` that points at a `VMCallerCheckedAnyfunc` because it gets rid of an indirection, dynamic allocation, and some reference counting. Note that the null function reference is *NOT* a null pointer; it is a `VMCallerCheckedAnyfunc` that has a null `func_ptr` member. Part of bytecodealliance#929
No description provided.