Conversation
When Emscripten's WASM memory grows (due to -sALLOW_MEMORY_GROWTH), all existing TypedArray views become detached because the underlying ArrayBuffer is replaced. This caused bugs where reading from views after FFI calls returned undefined values if memory had grown. The fix introduces RefreshableTypedArray, a wrapper that lazily recreates the TypedArray view when HEAPU8.buffer changes. This is a simple reference comparison that only triggers view recreation when actually needed. Affected call sites: - runtime.ts: executePendingJobs - reads ctxPtrOut after QTS_ExecutePendingJob - context.ts: newPromise - reads resolve/reject handles after QTS_NewPromiseCapability - context.ts: getLength - reads uint32Out after QTS_GetLength - context.ts: getOwnPropertyNames - reads outPtr and uint32Out after QTS_GetOwnPropertyNames Also fixed: getOwnPropertyNames was using HEAP8.buffer instead of HEAPU8.buffer Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
57ce055 to
48f9af3
Compare
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Update? |
|
Independent confirmation from a downstream user, plus the memory-intensive workload the test plan We hit the exact site your PR lists first ( const rt = QuickJS.newRuntime()
rt.setMemoryLimit(32 * 1024 * 1024)
rt.setMaxStackSize(512 * 1024)
const vm = rt.newContext()
vm.evalCode(`Promise.resolve().then(() => {
const a = []; for (let i = 0; i < 200000; i++) a.push({ i });
})`)
rt.executePendingJobs()
vm.dispose()
rt.dispose()
// Aborted(Assertion failed: list_empty(&rt->gc_obj_list), at: quickjs.c, JS_FreeRuntime)What we measured while the assert fires:
We prototyped the minimal local fix (read the out parameter through a view of the current heap: |
Summary
When Emscripten's WASM memory grows (due to
-sALLOW_MEMORY_GROWTH), all existing TypedArray views become detached because the underlying ArrayBuffer is replaced. This caused bugs where reading from views after FFI calls returned undefined (in the C correctness sense) values if memory had grown during the call.The fix introduces
RefreshableTypedArray, a wrapper that lazily recreates the TypedArray view whenHEAPU8.bufferchanges. This uses a simple reference comparison that only triggers view recreation when actually needed.Affected call sites:
runtime.ts:executePendingJobs- readsctxPtrOutafterQTS_ExecutePendingJobcontext.ts:newPromise- reads resolve/reject handles afterQTS_NewPromiseCapabilitycontext.ts:getLength- readsuint32OutafterQTS_GetLengthcontext.ts:getOwnPropertyNames- readsoutPtranduint32OutafterQTS_GetOwnPropertyNamesAlso fixed:
getOwnPropertyNameswas usingHEAP8.bufferinstead ofHEAPU8.bufferFixes #240
Test plan
🤖 Generated with Claude Code