Repository navigation
WebAssembly W5: the Memory, Table and Global constructors, Module.imports/exports/customSections, and function references from tables - #1410
Merged
Conversation
…orts/exports/customSections, and function references from tables new WebAssembly.Memory, Table and Global construct the runtime objects an instance exports, with Node 24's descriptor order, defaults, conversions and messages (V8's EnforceUint32 errors, the v128 and shared-memory errors; a valid shared memory is refused with a TypeError until threads), so either kind can be imported. Imports of memories, tables and globals now check limits, types and mutability with V8's LinkError messages, and exports keep V8's identity (imported objects re-export as themselves, one object per memory and table). The classes called without new throw Node's TypeError. WebAssembly.Module.imports, exports and customSections read the descriptor (ABI v2). Custom sections are carried only by programs that call customSections. Every function a module references reaches JavaScript as a callable function: the translator emits one call thunk per signature, so table.get and funcref globals return the module's unexported functions too, and exported functions can be stored in tables and globals from any instance. Discovery also finds .so WebAssembly shared libraries at resolved paths. Fixes to W3/W4 found on the way: externref Table.grow fills with undefined; Memory.grow/Table index errors and funcref messages match V8; an invalid funcref global import is a LinkError; global.valueOf() on a WebAssembly.Global-typed receiver answers the value; explicit-bounds memories keep the blocks grow replaced, so old ArrayBuffers never dangle. This unblocks quickjs-wasi's extension loader: quickjs-wasi with its url, encoding, headers and structured-clone extensions runs through its own glue identically to Node, and pi codemode's static coverage goes from 83% to 84% with no WebAssembly blocker left. Evidence: differential corpus programs for the API (webassembly-jsapi), a 261-test port of WPT wasm/jsapi (webassembly-wpt-jsapi), quickjs-wasi's dist/extensions.js run unmodified against dylink-shaped modules (webassembly-dylink), and the 2.0 spec suite's modules that import the spectest memory and table (new webassembly-spec-imports group plus the memory and table groups); all match Node in the plain and sanitized lanes. The zero-cost gate leaves every non-WebAssembly program byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
euphemism
pushed a commit
to euphemism/scriptc
that referenced
this pull request
Oct 20, 2026
…e headers The backend can emit scr_wasm_memory_construct, scr_wasm_table_construct, scr_wasm_global_construct, scr_wasm_construct_without_new, scr_wasm_module_imports, scr_wasm_module_exports, scr_wasm_module_custom_sections and scr_wasm_value_of (vercel-labs#1410), and scr_stream_dyn_native, scr_stream_pipe_dyn and scr_stream_unpipe_dyn (vercel-labs#1524). Their prototypes were in scr_wasm.c or nowhere, so the runtime ABI guard failed. They are now in scr_wasm.h and scr_stream.h, and scr_wasm.c no longer repeats the header's declarations. The speculative-hoisting test relied on `v instanceof Num` in a JavaScript function failing, which constructor-function instanceof now compiles (vercel-labs#1512). Its hoisted function reads the prototype through Reflect.getPrototypeOf instead, which still fences.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1395 (WebAssembly W3–W4). This PR targets
main, but its diff includes #1395's commit until that merges; review only the top commit. It implements stage W5 of the WebAssembly design (results/opt-wasm-design/DESIGN.md). The goal is pi's codemode: quickjs-wasi's extension loader (dist/extensions.js), which every program importing quickjs-wasi reaches, needednew WebAssembly.Global,WebAssembly.Module.importsandWebAssembly.Module.customSections, which #1395 still refused with SC2020.quickjs-wasi now loads its native extensions through its own glue, with output identical to Node. The real package was tested, with
url.so,encoding.so,headers.soandstructured-clone.so(it is not a corpus program). pi codemode's static coverage goes from 83% to 84% (1491 to 1500 of 1778 statements, on pi482c1381a, with the same tree and machine for both). No WebAssembly blocker is left in the codemode chain.What changed
new WebAssembly.Memory(descriptor): readsinitial,maximumandsharedin V8's order and applies V8's EnforceUint32 messages ("must be non-negative", "must be in the unsigned long range", the hexadecimal lower bound). A shared memory is validated, then refused with aTypeError(threads are W9).new WebAssembly.Table(descriptor, value):anyfuncorexternref, with the default fill value, an initial value, and V8's errors.new WebAssembly.Global(descriptor, value):i32,i64,f32,f64,externrefandanyfunc, with V8's defaults for a missing or undefined value and itsv128error.Module,Instance,Memory,TableorGlobalwithoutnewthrows Node'sTypeError.WebAssembly.Module.imports,exportsandcustomSectionsread the descriptor. They return fresh arrays of fresh descriptor objects, and freshArrayBuffercopies of the custom sections. A translated module carries its custom sections only when the program callscustomSections(IrModule.wasmCustomSections), so debug sections cost nothing otherwise.table.get(i)andglobal.valuereturn a callable function for any function a module references, not only for exported functions.table.set,table.grow, theTableconstructor) and in globals, from the same or another instance. quickjs-wasi's loader relies on this forGOT.funcsymbols.Table.grow(n)on an externref table fills withundefined, notnull.Table.get/set/growandMemory.growindex errors now use all of V8's messages.table.setandtable.growuse V8's funcref messages.LinkError; before, it threw a pendingTypeErrorand continued.global.valueOf()answers the value when the receiver's static type isWebAssembly.Global(wasm.valueOf). The generic dyn path forwardsvalueOfonly forDatehandles.growreplaced, so anArrayBuffercreated before a growth never dangles..sopaths spelled the ways.wasmpaths are (require.resolve("quickjs-wasi/url.so")) when the file is a module. WebAssembly shared libraries use.so.Evidence
All differential programs (Node vs. compiled; stdout, stderr and exit code identical) pass in the plain and the sanitized lane (ASan, RC audit, explicit bounds checks). That covers the four new programs and all 14
webassembly-*programs.tests/corpus/webassembly-jsapi.mjs(184 output lines):Module.imports,exportsandcustomSections;tests/corpus/webassembly-wpt-jsapi.mjs, a port of 261 web-platform-testswasm/jsapitests: Global constructor and value/valueOf, Memory constructor and grow, Table constructor, get/set and grow, and Module imports, exports and customSections.WasmModuleBuilderare inline base64 that the same builder produced.nameandlength, property descriptors, branding throughcall),Symbolarguments,Object.isFrozen/isExtensibleof ArrayBuffers (a general scriptc gap), and the old buffer's detachment aftergrow(a documented difference).tests/corpus/webassembly-dylink/runs quickjs-wasi 3.6.2'sdist/extensions.js, unmodified, with its MIT license.quickjs.wasmand an extension.so, shaped as wasi-sdk shared libraries:dylink.0,GOT.memandGOT.func,__memory_baseand__table_base, an unresolved symbol, a symbol resolved by the extension's own export, and data relocations.dylink.0, loading, versions, init, calls in both directions through the shared table, the shared stack pointer, the snapshot-restore load at fixed bases, a second instance, and the loader's errors.spectestmodule now has the reference harness's memory and table (new WebAssembly.Memory({initial: 1, maximum: 2}),new WebAssembly.Table({initial: 10, maximum: 20, element: "anyfunc"})).gen.mjstherefore no longer skips modules that import them.webassembly-spec-imports, addsimports.wast,table.wastandnames.wast: 131 modules and 574 commands.wg-2.0).QuickJS.create({ wasm, extensions: [url, encoding, headers, structured-clone] })through the package's own glue evaluatesURL,URLSearchParams,TextEncoder/TextDecoder,HeadersandstructuredClonescripts with output identical to Node..sodiscovery (ext.sois found, an ELFnative.sois not);Zero cost
scripts/zero-cost-gate.mjswith baseline = WebAssembly W3–W4: translate modules to native code at build time, implement the JavaScript API over them, and send modules to workers #1395's tip78184a986, built--runtime, covers bench + self + corpus shard 1/3: 827 programs.webassembly-typed.tsandwebassembly-worker.ts. They gain the ABI v2 descriptor fields and a call thunk, and the largerscr_wasm.cgrowswebassembly-worker.tsby 1.3%. The gate reports these as differences; they are intended.nmcheck: none of the 28 bench workloads or hello world, built with this branch, has ascr_wasm_*symbol.scr_wasm.c, and no always-linked unit changed. The new libCalls arewasm.*, so they set thewasmlink feature.Validation
scriptc coverage.pnpm lintandpnpm format:checkpass.pnpm test:ts7 --baselines-onlypasses; the full re-record added exactly the four new baselines and changed none.pnpm manifestandpnpm node-compatchange only the evidence list ofglobals: WebAssembly(stillpartial) and its surface-manifest note. The public docs artifact is unchanged.NEXT_DIST_DIR=.next-check pnpm checkpasses (25 route tests).tests/harness/self-hosting-native-driver.test.tspasses on the final tree (2,102 s).Known differences from Node (limitations page updated)
ArrayBuffer. Detaching it needs a registry of views inscr_bytes.c, which every program links, so it would not be free.valueOf()answers a Global's value only on receivers typedWebAssembly.Global. Through an untyped value, and in conversions to a primitive, a Global is the object.What remains
Promise<…> | VmChannelreturns and thewasm: object | Promise<…>members (SC2009);AbortSignal | undefinedfield;settlebinding;vm.callFunction(...).dispose;getRandomValuesas a value;deleteonJSException;Object.assign;BigInt64Array.restoreExtensionsfails in scriptc with "expected number at $[0].dylink.needed[0], got string".parseDylink'sneeded: []is anever[], whose element type maps to f64. That is a type-mapper gap, not a WebAssembly one; the corpus program exercises the same restore path throughloadExtension.🤖 Generated with Claude Code