-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Fix late-binding symbols with JSPI #23619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
ed49619
8ca0942
435fdc3
b6173e2
0c35849
55726a6
c6db63b
c0938e4
40b8a82
02b9753
25d5dfb
7902ceb
efff88b
7d07256
80d02f0
17703e3
3dc7a4f
d2c6102
66e17fa
9833523
9dcde84
5e968c8
c00c75b
185c362
84ab556
7532906
4fb2c77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -582,7 +582,178 @@ var LibraryDylink = { | |
} | ||
}, | ||
#endif | ||
#if JSPI | ||
$getStubImportModule__postset: ` | ||
var stubImportModuleCache = new Map(); | ||
`, | ||
$getStubImportModule__deps: [ | ||
"$generateFuncType", | ||
"$uleb128Encode" | ||
], | ||
// We need to call out to JS to resolve the function, but then make the actual | ||
// onward call from WebAssembly. The JavaScript $resolve function is | ||
// responsible for putting the appropriate function in the table. When the sig | ||
// is ii, the wat for the generated module looks like this: | ||
// | ||
// (module | ||
// (type $r (func )) | ||
// (type $ii (func (param i32) (result i32))) | ||
// (import "e" "t" (table 0 funcref)) | ||
// (import "e" "r" (func $resolve)) | ||
// (global $isResolved (mut i32) i32.const 0) | ||
// | ||
// (func (export "o") (param $p1 i32) (result i32) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use more meaningful names here maybe? What is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well this is the argument to the function that we are going to resolve. We're going to pass it onward. I'm not sure what we call wrapper arguments like this -- in the |
||
// global.get $isResolved | ||
// i32.eqz | ||
// if | ||
// call $resolve | ||
// i32.const 1 | ||
// global.set $isResolved | ||
// end | ||
// local.get $p1 | ||
// i32.const 0 | ||
// call_indirect (type $ii) | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// ) | ||
// ) | ||
$getStubImportModule: (sig) => { | ||
var cached = stubImportModuleCache.get(sig); | ||
if (cached) { | ||
return cached; | ||
} | ||
const bytes = [ | ||
0x00, 0x61, 0x73, 0x6d, // Magic number | ||
0x01, 0x00, 0x00, 0x00, // version 1 | ||
0x01, // Type section code | ||
]; | ||
var typeSectionBody = [ | ||
0x02, // count: 2 | ||
]; | ||
generateFuncType('v', typeSectionBody); | ||
generateFuncType(sig, typeSectionBody); | ||
uleb128Encode(typeSectionBody.length, bytes); | ||
bytes.push(...typeSectionBody); | ||
|
||
// static sections | ||
bytes.push( | ||
// Import section | ||
0x02, 0x0f, | ||
0x02, // 2 imports | ||
0x01, 0x65, // e | ||
0x01, 0x74, // t | ||
// funcref table with no limits | ||
0x01, 0x70, 0x00, 0x00, | ||
|
||
0x01, 0x65, // e | ||
0x01, 0x72, // r | ||
0x00, 0x00, // function of type 0 ('v') | ||
|
||
// Function section | ||
0x03, 0x02, | ||
0x01, 0x01, // One function of type 1 (sig) | ||
|
||
// Globals section | ||
0x06, 0x06, | ||
0x01, // one global | ||
0x7f, 0x01, // i32 mut | ||
0x41, 0x00, 0x0b, // initialized to i32.const 0 | ||
|
||
// Export section | ||
0x07, 0x05, | ||
0x01, // one export | ||
0x01, 0x6f, // o | ||
0x00, 0x01, // function at index 1 | ||
); | ||
bytes.push(0x0a); // Code section | ||
var codeBody = [ | ||
0x00, // 0 locals | ||
0x23, 0x00, // global.get 0 | ||
0x45, // i32.eqz | ||
0x04, // if | ||
0x40, 0x10, 0x00, // Call function 0 | ||
0x41, 0x01, // i32.const 1 | ||
0x24, 0x00, // global.set 0 | ||
0x0b, // end | ||
]; | ||
for (let i = 0; i < sig.length - 1; i++) { | ||
codeBody.push(0x20, i); | ||
} | ||
|
||
codeBody.push( | ||
0x41, 0x00, // i32.const 0 | ||
0x11, 0x01, 0x00, // call_indirect table 0, type 0 | ||
0x0b // end | ||
); | ||
var codeSectionBody = [0x01]; | ||
uleb128Encode(codeBody.length, codeSectionBody); | ||
codeSectionBody.push(...codeBody); | ||
uleb128Encode(codeSectionBody.length, bytes); | ||
bytes.push(...codeSectionBody); | ||
var result = new WebAssembly.Module(new Uint8Array(bytes)); | ||
stubImportModuleCache.set(sig, result); | ||
return result; | ||
}, | ||
|
||
$wasmSigToEmscripten: (type) => { | ||
var lookup = {i32: 'i', i64: 'j', f32: 'f', f64: 'd', externref: 'e'}; | ||
var sig = 'v'; | ||
if (type.results.length) { | ||
sig = lookup[type.results[0]]; | ||
} | ||
for (var v of type.parameters) { | ||
sig += lookup[v]; | ||
} | ||
return sig; | ||
}, | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$getStubImportResolver: (name, sig, table, resolveSymbol) => { | ||
return function r() { | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var res = resolveSymbol(name); | ||
if (res.orig) { | ||
res = res.orig; | ||
} | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
try { | ||
// Attempting to call this with JS function will cause table.set() to fail | ||
table.set(0, res); | ||
} catch (err) { | ||
if (!(err instanceof TypeError)) { | ||
throw err; | ||
} | ||
var wrapped = convertJsFunctionToWasm(res, sig); | ||
table.set(0, wrapped); | ||
} | ||
}; | ||
}, | ||
$addStubImports__deps: [ | ||
"$getStubImportModule", | ||
"$wasmSigToEmscripten", | ||
"$getStubImportResolver" | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
], | ||
$addStubImports: (mod, stubs, resolveSymbol) => { | ||
// Assumes --experimental-wasm-type-reflection to get type field of WebAssembly.Module.imports(). | ||
// TODO: Make this work without it. | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (const {module, name, kind, type} of WebAssembly.Module.imports(mod)) { | ||
if (module !== 'env' || kind !== 'function') { | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue; | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if (name in wasmImports && !wasmImports[name].stub) { | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue; | ||
} | ||
#if !DISABLE_EXCEPTION_CATCHING || SUPPORT_LONGJMP == 'emscripten' | ||
if (name.startsWith("invoke_")) { | ||
// JSPI + JS exceptions probably doesn't work but maybe nobody will | ||
// attempt stack switch inside a try block. | ||
stubs[name] = createInvokeFunction(name.split('_')[1]); | ||
continue; | ||
} | ||
#endif | ||
var sig = wasmSigToEmscripten(type); | ||
var mod = getStubImportModule(sig); | ||
const t = new WebAssembly.Table({element: 'funcref', initial: 1}); | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const r = getStubImportResolver(name, sig, t, resolveSymbol); | ||
const inst = new WebAssembly.Instance(mod, {e: {t, r}}); | ||
stubs[name] = inst.exports.o; | ||
} | ||
}, | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#endif | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Loads a side module from binary data or compiled Module. Returns the module's exports or a | ||
// promise that resolves to its exports if the loadAsync flag is set. | ||
$loadWebAssemblyModule__docs: ` | ||
|
@@ -599,6 +770,9 @@ var LibraryDylink = { | |
'$updateTableMap', | ||
'$wasmTable', | ||
'$addOnPostCtor', | ||
#if JSPI | ||
'$addStubImports', | ||
#endif | ||
], | ||
$loadWebAssemblyModule: (binary, flags, libName, localScope, handle) => { | ||
#if DYLINK_DEBUG | ||
|
@@ -722,16 +896,21 @@ var LibraryDylink = { | |
// Return a stub function that will resolve the symbol | ||
// when first called. | ||
if (!(prop in stubs)) { | ||
#if JSPI | ||
throw new Error("Missing stub for " + prop); | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#else | ||
var resolved; | ||
stubs[prop] = (...args) => { | ||
resolved ||= resolveSymbol(prop); | ||
return resolved(...args); | ||
}; | ||
#endif | ||
} | ||
return stubs[prop]; | ||
} | ||
}; | ||
var proxy = new Proxy({}, proxyHandler); | ||
const stubs = {}; | ||
var proxy = new Proxy(stubs, proxyHandler); | ||
var info = { | ||
'GOT.mem': new Proxy({}, GOTHandler), | ||
'GOT.func': new Proxy({}, GOTHandler), | ||
|
@@ -871,16 +1050,28 @@ var LibraryDylink = { | |
} | ||
|
||
if (flags.loadAsync) { | ||
if (binary instanceof WebAssembly.Module) { | ||
var instance = new WebAssembly.Instance(binary, info); | ||
return Promise.resolve(postInstantiation(binary, instance)); | ||
} | ||
return WebAssembly.instantiate(binary, info).then( | ||
(result) => postInstantiation(result.module, result.instance) | ||
); | ||
return (async function() { | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (binary instanceof WebAssembly.Module) { | ||
#if JSPI | ||
addStubImports(binary, stubs, resolveSymbol); | ||
#endif | ||
var instance = new WebAssembly.Instance(binary, info); | ||
return postInstantiation(binary, instance); | ||
} | ||
#if JSPI | ||
var module = await WebAssembly.compile(binary); | ||
addStubImports(module, stubs, resolveSymbol); | ||
var instance = await WebAssembly.instantiate(module, info); | ||
#else | ||
var {module, instance} = await WebAssembly.instantiate(binary, info); | ||
#endif | ||
return postInstantiation(module, instance); | ||
})(); | ||
hoodmane marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
var module = binary instanceof WebAssembly.Module ? binary : new WebAssembly.Module(binary); | ||
#if JSPI | ||
addStubImports(module, stubs, resolveSymbol); | ||
#endif | ||
var instance = new WebAssembly.Instance(module, info); | ||
return postInstantiation(module, instance); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
side_module_trampoline | ||
side_module_trampoline_a | ||
side_module_trampoline_b | ||
sleeping | ||
slept | ||
done 77 |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#include <stdio.h> | ||
int side_module_trampoline_b(void); | ||
|
||
int side_module_trampoline_a() { | ||
printf("side_module_trampoline_a\n"); | ||
return side_module_trampoline_b(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#include <stdio.h> | ||
int test_wrapper(void); | ||
|
||
int side_module_trampoline_b() { | ||
printf("side_module_trampoline_b\n"); | ||
return test_wrapper(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
5860 | ||
5869 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
12839 | ||
12877 |
Uh oh!
There was an error while loading. Please reload this page.