threads
threads copied to clipboard
[js-api] Fix `WebAssembly.Memory` buffer accessor
A given agent will only have one WebAssembly.Memory
object that
references a given Data Block
. Even if a Memory
is postMessage
'd
to another worker and back, the agent's memory object cache will contain
a reference to the Memory
object associated with that Data Block
.
So if another agent shares a memory with this agent, and the other agent
grows the memory, the only thing that's different in this agent is the
Data Block
's length. So we check that in the getter, and if it's
different we create a new buffer object and store it on the cached
Memory
object.
@binji, when was this proposed? I'm not sure I agree that this is a good idea. JS does not do this kind of caching cross-postMessage for SharedArrayBuffer.
(Currently in SpiderMonkey, serialization of a WebAssembly.Memory just transmits the data block with enough metadata to construct a WebAssembly.Memory on the receiving side, and always creates a new one upon deserialization.)
In JS, SharedArrayBuffer
is the raw object. However, for Wasm, the WebAssembly.Memory
object is actually a container around the underlying SharedArrayBuffer
, so it makes sense to me that this is where caching comes in. It's also more natural (IMO) in how a web engine would want to implement the .buffer
property. Currently, V8 eagerly materializes all JS objects associated with an instance, including the exported Wasm functions. We want to move to being lazier for these objects and also the memory. It simplifies memory.grow
, since it can now just invalidate the (cached) reference to the [Shared]ArrayBuffer
underneath.
IIUC, this PR adds surprising GC implications:
If I have a shared Memory
object m
and a WeakMap
w
, and I w.set(m, 42)
, then as long as m
's underlying buffer is reachable from any other agent, then I must keep m
alive since in the future that buffer may be postMessage()
d back to w
's agent and, at that point, w.get(futureReceivedM)
must be 42
. This effectively adds a liveness edge from the (cross-thread shared) buffer object to all its object wrappers in all agents. (Technically, only those objects whose identities are persistently visible via WeakMap
, expando property, ...) And this new liveness edge can create intra-agent cycles, so I assume we don't actually want this :)
(To wit, this problem with rematerialization of object wrappers is why Instance
objects need to keep a strong edge to any exported wasm functions they materialize once; I started with a weak map in FF and then found this bug.)
OK, the GC issue makes sense to me.
This PR came out of this discussion. I assumed that we would create a new Memory object (as Lars describes above), so it would be possible to have two separate Memory objects with the same data block. @littledan had already written spec text for postMessage of Memory objects, which reuses the Memory object cache on deserialization. It makes sense, since the object cache is described as being for this purpose:
This mapping is used to ensure that, for a given agent, there exists at most one JavaScript object for a particular WebAssembly address.
But it sounds like we may want to break this guarantee here.
Yeah, sounds like we should rewrite how that cache works (again). The relevant text is in the current JS API spec, not even this repository. I'll think about the phrasing.
A given agent will only have one
WebAssembly.Memory
object that references a givenData Block
.
We have given up on that invariant. Does anything else need to happen in the threads proposal? Do we already have tests?