fix: make instantiate async
This commit is contained in:
1 parent
f5be0ea510
commit
d1ead872b1
1 file changed
+33
-19
@@ -594,24 +594,31 @@ pub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unchecked_into();
|
.unchecked_into();
|
||||||
|
|
||||||
// ── SYNC SECTION — no `.await` past this point ────────────────
|
// ── HOST-STATE-MUTATING SECTION ───────────────────────────────
|
||||||
//
|
//
|
||||||
// Everything below mutates shared JS state (linear memory, the
|
// Below we grow shared linear memory and the indirect function
|
||||||
// indirect function table, the patch's data segment) and the
|
// table, then async-instantiate the patch into them and commit
|
||||||
// jump-table pointer. We must not yield, because:
|
// the new jump table. There IS one `.await` for the instantiate
|
||||||
|
// (we can't avoid it: Chrome disallows synchronous
|
||||||
|
// `new WebAssembly.Instance` on the main thread for modules
|
||||||
|
// larger than 8MB, and patches routinely cross that), but it's
|
||||||
|
// safe — the original race we fixed wasn't about yielding here:
|
||||||
//
|
//
|
||||||
// * `memory.grow` detaches the previous `ArrayBuffer`. The old
|
// * `memory.grow` and `funcs.grow` each return their PRIOR
|
||||||
// code captured `memory.buffer()` BEFORE the awaits and then
|
// length atomically. Concurrent `apply_patch` tasks therefore
|
||||||
// read its `byteLength` to compute `__memory_base`; if any
|
// each get a unique, non-overlapping `memory_base` /
|
||||||
// other task ran a `memory.grow` during the awaits (e.g. an
|
// `table_base`, so two patches can't land on the same region.
|
||||||
// allocator call from a concurrent render), the snapshot was
|
// * Host code can't observe the half-instantiated patch: the
|
||||||
// detached, `byteLength` returned 0, and the patch's data
|
// new memory pages are zero, the new table slots are null,
|
||||||
// segment ended up loaded over the host's stack/rodata.
|
// and the jump table isn't committed until the very end of
|
||||||
// * Two concurrent `apply_patch` tasks must not interleave
|
// this block, so nothing redirects through the new slots.
|
||||||
// between memory/table grow and `commit_patch`. Doing all
|
// * The original bug — using `memory.buffer().byteLength()`
|
||||||
// side-effecting work in one synchronous turn guarantees that.
|
// captured before the awaits, which returned 0 if the buffer
|
||||||
// * If the future is cancelled before this point, host state
|
// had been detached by a concurrent grow — is gone because
|
||||||
// is untouched.
|
// we derive `memory_base` from `memory.grow()`'s return
|
||||||
|
// value instead.
|
||||||
|
// * Cancellation between grow and `commit_patch` leaks memory
|
||||||
|
// pages and table slots, but doesn't corrupt anything.
|
||||||
const PAGE_SIZE: u32 = 64 * 1024;
|
const PAGE_SIZE: u32 = 64 * 1024;
|
||||||
let patch_pages = (dl_bytes.byte_length() as f64 / PAGE_SIZE as f64).ceil() as u32 + 1;
|
let patch_pages = (dl_bytes.byte_length() as f64 / PAGE_SIZE as f64).ceil() as u32 + 1;
|
||||||
|
|
||||||
@@ -646,9 +653,16 @@ pub unsafe fn apply_patch(mut table: JumpTable) -> Result<(), PatchError> {
|
|||||||
let imports = Object::new();
|
let imports = Object::new();
|
||||||
Reflect::set(&imports, &"env".into(), &env).unwrap();
|
Reflect::set(&imports, &"env".into(), &env).unwrap();
|
||||||
|
|
||||||
// Synchronous instantiation of the precompiled module — writes
|
// Async instantiation of the precompiled module. We use the
|
||||||
// the patch's data segment into memory at `memory_base`.
|
// (Module, imports) form of `WebAssembly.instantiate`, which
|
||||||
let instance = Instance::new(&module, &imports).unwrap();
|
// resolves directly to an `Instance` (not `{module, instance}`).
|
||||||
|
// This is the no-size-limit path; the synchronous
|
||||||
|
// `new WebAssembly.Instance` constructor is capped at 8MB on
|
||||||
|
// Chrome's main thread.
|
||||||
|
let instance: Instance = JsFuture::from(WebAssembly::instantiate_module(&module, &imports))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unchecked_into();
|
||||||
let inst_exports: Object = instance.exports();
|
let inst_exports: Object = instance.exports();
|
||||||
|
|
||||||
// Run the patch's relocation thunks and constructors. Order
|
// Run the patch's relocation thunks and constructors. Order
|
||||||
|
|||||||
Reference in new issue
Block a user