peterhuene opened PR #2625 from engine-funcs
to main
:
This PR introduces defining host functions at the
Engine
rather than with
Func
tied to aStore
.The intention here is to enable a host to define all of the functions once at
with anEngine
and then use aLinker
(or directly with
Store::get_engine_func
) to use the functions when instantiating a module.This should help improve the performance of use cases where a
Store
is
short-lived and redefining the functions at every module instantiation a
noticeable performance hit.See bytecodealliance/rfcs#9 regarding these changes.
alexcrichton submitted PR Review.
alexcrichton submitted PR Review.
alexcrichton created PR Review Comment:
FWIW this sort of interning I was hoping would make
Store
faster sinceStore
was short-lived, but sinceEngine
is long-lived it's probably not necessary to do this and we can probably just get away with the simpler-to-readString
everywhere perhaps?
alexcrichton created PR Review Comment:
Similar to above, could this document the
Send
andSync
bounds?
alexcrichton created PR Review Comment:
Do you think there's any need for this to be dynamic for the lifetime of the
Engine
itself? I would naively expect that any typical usage of this would be "let's create everything at the beginning and then that's it". If that's the case I think it's a bummer to put this behind an rwlock.If you agree, perhaps we could move the building portion to
Config
which is basically anEngineBuilder
at this point anyway. That way we wouldn't need to have the synchronization around this. Similarly we could literally have anEngineBuilder
if needed to which is constructed with aConfig
and is then consumed to produce anEngine
.In any case this is mostly a stylistic thing, I just think it'd be nice to avoid the usage of locks if we can.
alexcrichton created PR Review Comment:
Ideally this
Store::upgrade
would only happen once here, although it would make theresume_panic
case slightly trickier where it has to be sure to dropstore
. Since we're already manually dropping things here, though, that may be ok?
alexcrichton created PR Review Comment:
I think this can be a bit unsafe since
EngineFunc
doesn't embed a reference to itsEngine
, so this cloned object isn't guaranteed to not outlive anEngine
. That being said I suspect the usage is all correct, but this comment may need to be updated.
alexcrichton created PR Review Comment:
Due to the trickineses involved here, especially around not having local variables that need to be dropped, could this shim be shared with
into_func
above?
alexcrichton created PR Review Comment:
Could the docs here also be sure to point out the
Send
andSync
bounds and how more relaxed bounds are available onFunc
?
alexcrichton created PR Review Comment:
One thing I'm worried about is reading this value later on since it could get signatures mismatched. Could this perhaps store
u32::max_value()
as a signature index (or w/e the max value is) so if it's accidentally used we weed out that bug sooner rather than later?
alexcrichton created PR Review Comment:
Could this have
+Send+Sync
as well? If it can't cast down could a transmute be used to erase the type bounds?
alexcrichton created PR Review Comment:
To avoid the
pub(crate)
here could this perhaps be defined in thefunc.rs
module?
alexcrichton created PR Review Comment:
This is one of the things I'm most worried about with this PR. I don't think there's any issue with this currently, only that one could be lurking by accident in the future.
My concern here is that the not-threadsafe
InstanceHandle
is being shared across threads. This is ok because we happen to not touch any of the non-threadsafe state, but all ofInstanceHandle
and consumers are currently written without thread-safety in mind. Do you think there's anything we can do to future-proof this? For example I mentioned one elsewhere in this PR where we store an invalid index so if it's ever used we hopefully abort quickly. I think ideally we'd have checks inwasmtime_runtime::Instance
as well, but I'm not sure if they would be too onerous for other usages.Basically my main worry here is that this function is introducing threading in a non-compiler-checked fashion. All authors of
runtime/src/instance.rs
need to be aware of this but that's difficult to enforce because this happens in such an isolated location.
alexcrichton created PR Review Comment:
I think these may not need to be
pub
?
alexcrichton created PR Review Comment:
Like above could this have
+Send+Sync
?
alexcrichton created PR Review Comment:
Since
define_func
above returns aResult<()>
should this as well?
fitzgen submitted PR Review.
fitzgen created PR Review Comment:
I don't know, deduplicating strings is kinda nice (module string will be repeated a bunch, for example)
fitzgen submitted PR Review.
fitzgen created PR Review Comment:
My concern here is that the not-threadsafe
InstanceHandle
is being shared across threads. This is ok because we happen to not touch any of the non-threadsafe state, but all ofInstanceHandle
and consumers are currently written without thread-safety in mind. Do you think there's anything we can do to future-proof this?Would it be feasible to split out a struct of
Sync
members fromInstanceHandle
that we can share, without sharing the whole thing across threads?e.g.
struct InstanceHandleSync { // sync members in here... } struct InstanceHandle { sync: InstanceHandleSync, // non-sync members here... }
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
That's an excellent point. I don't think one would redefine these functions after creating the
Engine
and doing this in a builder pattern would also remove the need to track "redefined" functions to keep them alive.I think
Config
would work for this. Perhapsdefine_host_func
andwrap_host_func
?
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
I'll add that.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
I'll add that.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
I'll add those bounds.
alexcrichton submitted PR Review.
alexcrichton created PR Review Comment:
Eh I don't mind too much about the names, those seem fine to me!
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
VMSharedSignatureIndex::default()
isu32::max_value()
and guaranteed to never match a signature as the registry doesn't hand that value out.Using the default should cause any indirect call to fail if somehow this value gets used, which would clearly be a bug.
This value should never be read anyway as all imports of the engine function should use the store-local
VMCallerCheckedAnyfunc
that has the correct shared signature.
alexcrichton submitted PR Review.
alexcrichton created PR Review Comment:
Unfortunately I don't think so since most JIT code goes from
*mut VMContext
straight to*mut Instance
(the internals of the handle) which has all the non-Sync
pieces. For example we just have a dynamic guarantee that the instance never tries toelem.drop
, for example.One of my bigger worries is leaking the incorrect
VMSharedSignatureIndex
which is configured in theVMContext
. I think storing an out-of-bounds index will help weed things out, but otherwise I don't have any great ideas. That being said I don't believe that it's leaked today at all.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
Will add.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
I'll fix.
alexcrichton submitted PR Review.
alexcrichton created PR Review Comment:
Oh nice! I didn't realize that was the default but that's exactly the behavior I was hoping to have here, so sounds like there's nothing to do!
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
I share your concerns over safety here.
I still think we're getting some mileage out of the compiler for safety as
Store
is!Send+!Sync
and the store to use for an engine function is always retrieved from TLS (and thus should be the same thread that created the store).Perhaps a check in
Caller::store()
that ensures the call is happening on the same thread that created the store, just to be safe?Would it be feasible to split out a struct of Sync members from InstanceHandle that we can share, without sharing the whole thing across threads?
I think splitting
wasmtime_runtime::Instance
like that shouldn't be necessary as the thread-affinity of an instance shouldn't be violated by these changes.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
Func::new
returnsSelf
because of theexpect
call on the result fromgenerate_func_export
.Should we just change
define_func
to return()
similarly to match?
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
Agreed, this comment is misleading as nothing in a runtime instance ties back to the engine itself.
This clone impl will likely not be needed if I remove the
RwLock
in favor of the builder pattern anyway.
alexcrichton submitted PR Review.
alexcrichton created PR Review Comment:
Yeah that seems fine to me! We'll probably want to document the shadowing behavior though since this differs slightly than that of
Linker
where by defaultLinker
returns an error. Returning an error here though feels a bit heavyweight.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
These fields are also accessed from
Store::get_engine_func
, but I'm happy to make these private with accessor impls if you think it's warranted.
alexcrichton submitted PR Review.
alexcrichton created PR Review Comment:
My worry is moreso for the internals of Wasmtime itself rather than callers. You're right though in that we do have a number of consistency checks and internals (I suspect more than a few locations modified in this PR were found via assertion failures rather than knowing what to do off the top of your head). My biggest worry is hidden mutability in
InstanceHandle
, but I think it's not the end of the world for that to be something we remain vigilant for.Perhaps a nice doc-block can go somewhere though explaining the threading involved here?
alexcrichton submitted PR Review.
alexcrichton created PR Review Comment:
Oh true, perhaps though there could be something like
engine_func.to_func(&store)
? (mostly just looking to localize this logic if we can)
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
I don't have a strong opinion on this. I'm fine with removing the de-duplication for clarity's sake.
That said, I think de-duplication is more likely to be exploited at the engine-level where (ideally) WASI is being defined rather than in a
Linker
.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
I'll see what I can do to localize the implementation to
EngineFunc
.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
A non-issue if these get removed from
Engine
in favor of the builder-pattern, though.Re: shadowing, this PR should include a check in
Linker
that ensures failure if an engine function exists and the linker is!allow_shadowing
. I'll fix that.
peterhuene edited PR Review Comment.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
Agreed, this can be done once. I'll fix this.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
The problem is these two shims differ in how the store is accessed (one from capture and the other from TLS); related to that is the associated instance state also differs.
As such, I don't think much could be shared here since I wouldn't want the
resume_panic
call moved to any other frame than the shim function itself.
peterhuene edited PR Review Comment.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
So there's a problem with adding this bound.
We're passing
self
in theIntoFunc
impls, which has typeF
with boundsF: Fn(Caller<'_>, $($args),*) -> R + 'static
and unfortunately that bound is shared withinto_func
which wouldn't want those bounds onF
otherwise it pollutesFunc::wrap
.To properly implement this, we'd need a separate
IntoEngineFunc
impl. Thoughts?
peterhuene edited PR Review Comment.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
Or would we rather add these bounds here and transmute them in for the
IntoFunc
impl with a big comment as to why this is safe (i.e. because the bounds are present in theEngine::wrap_func
)?
peterhuene edited PR Review Comment.
peterhuene edited PR Review Comment.
Last updated: Dec 23 2024 at 12:05 UTC