alexcrichton labeled Issue #960:
From our discussion today on the wasmtime call the topic of GC,
table.set, and https://github.com/bytecodealliance/wasmtime/issues/954 all came up. It was concluded that today with the MVP you can actually create a cyclic dependency between modules, for example:(module $a (table (export "table") 2 2 funcref) (func i32.const 1 call_indirect) (elem (i32.const 0) func 0)) (module $b (import "a" "table" (table 2 2 funcref)) (func i32.const 0 call_indirect) (elem (i32.const 1) func 0))These two modules only use MVP features but they have a shared table which cyclically references the two modules.
Our conclusion from this was that
Storeis actually a "clique" of instances together, and once you instantiate something you can never deallocate anything until all the instances have gone. Internally this would entail ensuring that all types exported inwasmtimehave some sort of handle on their store (basically all of them already do, this isn't hard).Implementation-wise I don't think this will be hard to do, but this does have some critical implications I'd like to sort out first:
We need to acknowledge that an
Instancewill never be fully deallocated until all objects referencing theStorehave gone away. This feels like a pretty bad crutch to lean on (but it's not like we have much choice) and is something we would need to document thoroughly. You would basically never want user code generating instances into aStorebecause it means you could OOM quickly. Instead your instance-creation patterns must be known statically. (this may eventually be different with the whole linking proposal, but this would be the status quo that we would have to document)If we start thinking of
Storeas a "clique of instances" then I don't think the API is quite aligned for that today. For example if you compile aModuleI wouldn't expect that to be tied to a particular store. AModuleshould have its own compiled memory cached, but this can be instantiated into anyStoreobject since it's just adding new references to the compiled memory in multiple stores (possibly). Furthermore I don't know how to rationalize the thread-safety here. For example anInstancecannot be sent across threads (although this is up for debate), but you almost for sure want to share aModuleacross threads. You may even want multiple threads to be part of a singleStore, but if aStorehas to have a handle to all of itsInstanceobjects then this is sort of a backwards relation becauseStoreneeds to be threadsafe, but it can't be threadsafe becauseInstancecan't be threadsafe.I think a bunch of this may just boil down to "Alex needs further clarification of what's already there", but I don't really see how
StoreandEnginemap to concepts of what you would want today. I understand they came from v8, but I'm not sure why we want them in our API as well and how it maps to thread safety and such. This is all stuff we need to sort out, though, because the current way we treat multiple stores and instances referencing each other is not memory safe and can easily segfault.
pepyakin commented on Issue #960:
I think it came from wasm-c-api (which seems to be heavily influenced by v8). It seems that wasm-c-api was inspired by the wasm spec, but in contrast to it, wasm-c-api's
ModulerequiresStorefor some reason. Looking at their impl, their store is thread-safe in the sense that it isSendbut not thread-safe in the sense that multiple threads can use it at the same time.So, by this
You may even want multiple threads to be part of a single
Storeyou also mean
Send?
alexcrichton commented on Issue #960:
Somehow we're going to want to be able to support threaded wasm where instances are connected to each other via a
Memoryand they can all instantiate the sameModuleon multiple threads. That's sort of the bare minimum required, but today we've painted ourselves a bit into a corner whereModulestores aStorewhich is not thread safe inherently becauseInstancefields should never be dropped until aStoreis dropped. Furthermore we also require that all imports come from the sameStore, which would cause more issues if we were to try to make targeted fixes.We're basically in a corner right now which I dont think makes sense. We need to figure out, probably from scratch, what we want our multithreading story to be. For example what structures are supposed to be shared, what's the idioms we are expecting for multithreaded instances, etc.
alexcrichton closed Issue #960:
From our discussion today on the wasmtime call the topic of GC,
table.set, and https://github.com/bytecodealliance/wasmtime/issues/954 all came up. It was concluded that today with the MVP you can actually create a cyclic dependency between modules, for example:(module $a (table (export "table") 2 2 funcref) (func i32.const 1 call_indirect) (elem (i32.const 0) func 0)) (module $b (import "a" "table" (table 2 2 funcref)) (func i32.const 0 call_indirect) (elem (i32.const 1) func 0))These two modules only use MVP features but they have a shared table which cyclically references the two modules.
Our conclusion from this was that
Storeis actually a "clique" of instances together, and once you instantiate something you can never deallocate anything until all the instances have gone. Internally this would entail ensuring that all types exported inwasmtimehave some sort of handle on their store (basically all of them already do, this isn't hard).Implementation-wise I don't think this will be hard to do, but this does have some critical implications I'd like to sort out first:
We need to acknowledge that an
Instancewill never be fully deallocated until all objects referencing theStorehave gone away. This feels like a pretty bad crutch to lean on (but it's not like we have much choice) and is something we would need to document thoroughly. You would basically never want user code generating instances into aStorebecause it means you could OOM quickly. Instead your instance-creation patterns must be known statically. (this may eventually be different with the whole linking proposal, but this would be the status quo that we would have to document)If we start thinking of
Storeas a "clique of instances" then I don't think the API is quite aligned for that today. For example if you compile aModuleI wouldn't expect that to be tied to a particular store. AModuleshould have its own compiled memory cached, but this can be instantiated into anyStoreobject since it's just adding new references to the compiled memory in multiple stores (possibly). Furthermore I don't know how to rationalize the thread-safety here. For example anInstancecannot be sent across threads (although this is up for debate), but you almost for sure want to share aModuleacross threads. You may even want multiple threads to be part of a singleStore, but if aStorehas to have a handle to all of itsInstanceobjects then this is sort of a backwards relation becauseStoreneeds to be threadsafe, but it can't be threadsafe becauseInstancecan't be threadsafe.I think a bunch of this may just boil down to "Alex needs further clarification of what's already there", but I don't really see how
StoreandEnginemap to concepts of what you would want today. I understand they came from v8, but I'm not sure why we want them in our API as well and how it maps to thread safety and such. This is all stuff we need to sort out, though, because the current way we treat multiple stores and instances referencing each other is not memory safe and can easily segfault.
DemiMarie-parity commented on Issue #960:
@alexcrichton what about doing some form of garbage collection pass?
alexcrichton commented on Issue #960:
Indeed that should be able to help clear out memory sooner! That's a pretty major feature though and one we haven't planned on adding any time soon (AFAIK) to wasmtime, so it's aways out if at all.
Last updated: Dec 06 2025 at 06:05 UTC