-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Implement addFunction under wasm backend purely in terms of the wasm table #8255
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -599,48 +599,128 @@ Module['registerFunctions'] = registerFunctions; | |
| #endif // RELOCATABLE | ||
| #endif // EMULATED_FUNCTION_POINTERS | ||
|
|
||
| #if EMULATED_FUNCTION_POINTERS == 0 | ||
| #if WASM_BACKEND && RESERVED_FUNCTION_POINTERS | ||
| var jsCallStartIndex = {{{ JSCALL_START_INDEX }}}; | ||
| var jsCallSigOrder = {{{ JSON.stringify(JSCALL_SIG_ORDER) }}}; | ||
| var jsCallNumSigs = Object.keys(jsCallSigOrder).length; | ||
| var functionPointers = new Array(jsCallNumSigs * {{{ RESERVED_FUNCTION_POINTERS }}}); | ||
| #else // WASM_BACKEND && RESERVED_FUNCTION_POINTERS == 0 | ||
| #if !WASM_BACKEND && EMULATED_FUNCTION_POINTERS == 0 | ||
| var jsCallStartIndex = 1; | ||
| var functionPointers = new Array({{{ RESERVED_FUNCTION_POINTERS }}}); | ||
| #endif // WASM_BACKEND && RESERVED_FUNCTION_POINTERS | ||
| #endif // EMULATED_FUNCTION_POINTERS == 0 | ||
| #endif // !WASM_BACKEND && EMULATED_FUNCTION_POINTERS == 0 | ||
|
|
||
| #if WASM | ||
| // Wraps a JS function as a wasm function with a given signature. | ||
| // In the future, we may get a WebAssembly.Function constructor. Until then, | ||
| // we create a wasm module that takes the JS function as an import with a given | ||
| // signature, and re-exports that as a wasm function. | ||
| function convertJsFunctionToWasm(func, sig) { | ||
|
Contributor
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. Had a bit of fun golfing this down... :-) [edit: now 238 chars, with help from teammates] w=(f,[R,...P],W=WebAssembly,L=x=>[x.length,...x],C=x=>'dfji'.indexOf(x)+124)=>
new W.Instance(new W.Module(new Int8Array([,97,115,109,1,,,,1,...L([1,96,...L(P.
map(C)),...L(R=='v'?[]:[C(R)])]),2,5,1,,,,,7,4,1,,,0])),{'':{'':f}}).exports['']
let convertJsFunctionToWasm = w;
const add = (x, y) => x + y;
const addi = convertJsFunctionToWasm(add, 'iii');
const addd = convertJsFunctionToWasm(add, 'ddd');
const printi = convertJsFunctionToWasm(print, 'vi');
print(addi(1.2, 2.3)); // 3
print(addd(1.2, 2.3)); // 3.5
printi(42.1); // 42
Collaborator
Author
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. If the size of the generated module also reduced? Are you suggesting we use this version?
Contributor
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. No, sorry, we definitely shouldn't use this code. 😄 just had some insomnia last night so I started trying to see how small I could make it, thought you might appreciate. Feel free to ignore, haha
Collaborator
Author
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. Perhaps the most useful wasm module ever on per-byte basis?
Contributor
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. Devil's advocate, should we not use this code? JS optimizers are extremely unlikely to shrink this code as well, and code size wins are code size wins. Though it should be
Contributor
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. Probably best not to use it :) Does it even work with emscripten since it uses ES6 features? Was the old uglify pass ever removed?
Member
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. We do support ES6 now in all the major JS passes (but we didn't remove the old uglify versions because they let us support source maps in asm.js), and can probably assume JS is modern in places where wasm is used (but I'm not sure about that). |
||
| // The module is static, with the exception of the type section, which is | ||
| // generated based on the signature passed in. | ||
| var typeSection = [ | ||
| 0x01, // id: section, | ||
| 0x00, // length: 0 (placeholder) | ||
| 0x01, // count: 1 | ||
| 0x60, // form: func | ||
| ]; | ||
| var sigRet = sig.slice(0, 1); | ||
| var sigParam = sig.slice(1); | ||
| var typeCodes = { | ||
| 'i': 0x7f, // i32 | ||
| 'j': 0x7e, // i64 | ||
| 'f': 0x7d, // f32 | ||
| 'd': 0x7c, // f64 | ||
| }; | ||
|
|
||
| // Parameters, length + signatures | ||
| typeSection.push(sigParam.length); | ||
| for (var i = 0; i < sigParam.length; ++i) { | ||
| typeSection.push(typeCodes[sigParam[i]]); | ||
| } | ||
|
|
||
| // Return values, length + signatures | ||
| // With no multi-return in MVP, either 0 (void) or 1 (anything else) | ||
| if (sigRet == 'v') { | ||
| typeSection.push(0x00); | ||
| } else { | ||
| typeSection = typeSection.concat([0x01, typeCodes[sigRet]]); | ||
| } | ||
|
|
||
| // Write the overall length of the type section back into the section header | ||
| // (excepting the 2 bytes for the section id and length) | ||
| typeSection[1] = typeSection.length - 2; | ||
|
|
||
| // Rest of the module is static | ||
| var bytes = new Uint8Array([ | ||
| 0x00, 0x61, 0x73, 0x6d, // magic ("\0asm") | ||
| 0x01, 0x00, 0x00, 0x00, // version: 1 | ||
| ].concat(typeSection, [ | ||
| 0x02, 0x07, // import section | ||
| // (import "e" "f" (func 0 (type 0))) | ||
| 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00, | ||
| 0x07, 0x05, // export section | ||
| // (export "f" (func 0 (type 0))) | ||
| 0x01, 0x01, 0x66, 0x00, 0x00, | ||
| ])); | ||
|
|
||
| // We can compile this wasm module synchronously because it is very small. | ||
| // This accepts an import (at "e.f"), that it reroutes to an export (at "f") | ||
| var module = new WebAssembly.Module(bytes); | ||
| var instance = new WebAssembly.Instance(module, { | ||
| e: { | ||
| f: func | ||
| } | ||
| }); | ||
| var wrappedFunc = instance.exports.f; | ||
| return wrappedFunc; | ||
| } | ||
|
|
||
| // Add a wasm function to the table. | ||
| // Attempting to call this with JS function will cause of table.set() to fail | ||
| function addWasmFunction(func) { | ||
| function addFunctionWasm(func, sig) { | ||
| var table = wasmTable; | ||
| var ret = table.length; | ||
| table.grow(1); | ||
| table.set(ret, func); | ||
|
|
||
| // Grow the table | ||
| try { | ||
| table.grow(1); | ||
| } catch (err) { | ||
| if (!err instanceof RangeError) { | ||
| throw err; | ||
| } | ||
| throw 'Unable to grow wasm table. Use a higher value for RESERVED_FUNCTION_POINTERS or set ALLOW_TABLE_GROWTH.'; | ||
| } | ||
|
|
||
| // Insert new element | ||
| try { | ||
| // Attempting to call this with JS function will cause of table.set() to fail | ||
| table.set(ret, func); | ||
| } catch (err) { | ||
| if (!err instanceof TypeError) { | ||
| throw err; | ||
|
sbc100 marked this conversation as resolved.
|
||
| } | ||
| assert(typeof sig !== 'undefined', 'Missing signature argument to addFunction'); | ||
| var wrapped = convertJsFunctionToWasm(func, sig); | ||
| table.set(ret, wrapped); | ||
| } | ||
|
|
||
| return ret; | ||
| } | ||
|
|
||
| function removeFunctionWasm(index) { | ||
| // TODO(sbc): Look into implementing this to allow re-using of table slots | ||
| } | ||
| #endif | ||
|
|
||
| // 'sig' parameter is currently only used for LLVM backend under certain | ||
| // circumstance: RESERVED_FUNCTION_POINTERS=1, EMULATED_FUNCTION_POINTERS=0. | ||
| // 'sig' parameter is required for the llvm backend but only when func is not | ||
| // already a WebAssembly function. | ||
| function addFunction(func, sig) { | ||
| #if ASSERTIONS == 2 | ||
| if (typeof sig === 'undefined') { | ||
| err('warning: addFunction(): You should provide a wasm function signature string as a second argument. This is not necessary for asm.js and asm2wasm, but can be required for the LLVM wasm backend, so it is recommended for full portability.'); | ||
| } | ||
| #endif // ASSERTIONS | ||
|
|
||
| #if WASM_BACKEND | ||
| return addFunctionWasm(func, sig); | ||
| #else | ||
|
|
||
| #if EMULATED_FUNCTION_POINTERS == 0 | ||
| #if WASM_BACKEND && RESERVED_FUNCTION_POINTERS | ||
| assert(typeof sig !== 'undefined', | ||
| 'Second argument of addFunction should be a wasm function signature ' + | ||
| 'string'); | ||
| var base = jsCallSigOrder[sig] * {{{ RESERVED_FUNCTION_POINTERS }}}; | ||
| #else // WASM_BACKEND && RESERVED_FUNCTION_POINTERS == 0 | ||
| var base = 0; | ||
| #endif // WASM_BACKEND && RESERVED_FUNCTION_POINTERS | ||
| for (var i = base; i < base + {{{ RESERVED_FUNCTION_POINTERS }}}; i++) { | ||
| if (!functionPointers[i]) { | ||
| functionPointers[i] = func; | ||
|
|
@@ -652,13 +732,9 @@ function addFunction(func, sig) { | |
| #else // EMULATED_FUNCTION_POINTERS == 0 | ||
|
|
||
| #if WASM | ||
| // assume we have been passed a wasm function and can add it to the table | ||
| // directly. | ||
| // TODO(sbc): This assumtion is most likely not valid. Look into ways of | ||
| // creating wasm functions based on JS functions as input. | ||
| return addWasmFunction(func); | ||
| return addFunctionWasm(func, sig); | ||
| #else | ||
| alignFunctionTables(); // XXX we should rely on this being an invariant | ||
| alignFunctionTables(); // TODO: we should rely on this being an invariant | ||
| var tables = getFunctionTables(); | ||
| var ret = -1; | ||
| for (var sig in tables) { | ||
|
|
@@ -668,21 +744,32 @@ function addFunction(func, sig) { | |
| table.push(func); | ||
| } | ||
| return ret; | ||
| #endif | ||
| #endif // WASM | ||
|
|
||
| #endif // EMULATED_FUNCTION_POINTERS == 0 | ||
| #endif // WASM_BACKEND | ||
| } | ||
|
|
||
| function removeFunction(index) { | ||
| #if WASM_BACKEND | ||
|
sbc100 marked this conversation as resolved.
|
||
| removeFunctionWasm(index); | ||
| #else | ||
|
|
||
| #if EMULATED_FUNCTION_POINTERS == 0 | ||
| functionPointers[index-jsCallStartIndex] = null; | ||
| #else | ||
| #if WASM | ||
| removeFunctionWasm(index); | ||
| #else | ||
| alignFunctionTables(); // XXX we should rely on this being an invariant | ||
| var tables = getFunctionTables(); | ||
| for (var sig in tables) { | ||
| tables[sig][index] = null; | ||
| } | ||
| #endif | ||
| #endif // WASM | ||
|
|
||
| #endif // EMULATE_FUNCTION_POINTER_CASTS == 0 | ||
| #endif // WASM_BACKEND | ||
| } | ||
|
|
||
| var funcWrappers = {}; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.