Skip to content

dynCallLegacy: Fill in dynCall_sig with a Wasm adaptor if it is missing #17328

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

Merged
merged 23 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 153 additions & 2 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -3188,8 +3188,6 @@ mergeInto(LibraryManager.library, {
#if MINIMAL_RUNTIME
assert(typeof dynCalls != 'undefined', 'Global dynCalls dictionary was not generated in the build! Pass -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$dynCall linker flag to include it!');
assert(sig in dynCalls, 'bad function pointer type - no table for sig \'' + sig + '\'');
#else
assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\'');
#endif
if (args && args.length) {
// j (64-bit integer) must be passed in as two numbers [low 32, high 32].
Expand All @@ -3201,11 +3199,164 @@ mergeInto(LibraryManager.library, {
#if MINIMAL_RUNTIME
var f = dynCalls[sig];
#else
if (!('dynCall_' + sig in Module)) {
Module['dynCall_' + sig] = createDyncallWrapper(sig);
}
var f = Module['dynCall_' + sig];
#endif
return args && args.length ? f.apply(null, [ptr].concat(args)) : f.call(null, ptr);
},
#if MINIMAL_RUNTIME
$dynCall__deps: ['$dynCallLegacy', '$getWasmTableEntry'],
#else
$dynCall__deps: ['$dynCallLegacy', '$getWasmTableEntry', '$createDyncallWrapper'],
#endif
#endif
#if DYNCALLS || !WASM_BIGINT
$createDyncallWrapper: function(sig) {
var sections = [];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really huge and unpleasant function.

I get that we might need it under certain circumstances but maybe we can take some steps to limits its impact. How about moving it to its own file? Maybe: src/library_makeDynCall.js
Is there some way we can have folks opt into it? I'm loath to add a new setting. Perhaps we could ask folks to do -lmakeDynCall or --js-library=makeDynCall? (Let me see if either of those will work).

var prelude = [
0x00, 0x61, 0x73, 0x6d, // magic ("\0asm")
0x01, 0x00, 0x00, 0x00, // version: 1
];
sections.push(prelude);
var insig = [sig[0] === "j" ? "i" : sig[0], "i",];
for (var i = 1; i < sig.length; i++) {
var cur = sig[i];
if (cur === "j") {
insig.push("ii")
} else {
insig.push(cur);
}
}
insig = insig.join("");

// The module is static, with the exception of the type section, which is
// generated based on the signature passed in.
var typeSection = [
0x03, // count: 3
].concat(generate_func_type(insig), generate_func_type(sig), generate_func_type("vi"));

// Write the section code and overall length of the type section into the
// section header
typeSection = [0x01 /* Type section code */].concat(
uleb128Encode(typeSection.length),
typeSection
);
sections.push(typeSection);

var importSection = [
0x02, // import section
0x0F, // byte length
0x02, // 2 imports
0x01, 0x65, // name "e"
0x01, 0x74, // name "t"
0x01, 0x70, // importing a table
0x00, // with no max # of elements
0x00, // and min of 0 elements
0x01, 0x65, // name "e"
0x01, 0x72, // name "r"
0x00, // importing a function
0x02, // type 2
];
sections.push(importSection);

var functionSection = [
0x03, // function section
0x02, // length in bytes
0x01, // number of functions
0x00, // type 0
];
sections.push(functionSection);

var exportSection = [
0x07, 0x05, // export section
0x01, // One export
0x01, 0x66, // name "f"
0x00, // type: function
0x01, // the wrapper function (index 0 is ret0)
];
sections.push(exportSection);

var convert_code = [];
if (sig[0] === "j") {
convert_code = [0x01, 0x01, 0x7e];
} else {
convert_code.push(0x00); // no additional local variables
}

var j = 1;
for (var i = 1; i < sig.length; i++) {
if (sig[i] == "j") {
convert_code = convert_code.concat(
[0x20], // local.get
uleb128Encode(j + 1),
[
0xad, // i64.extend_i32_unsigned
0x42, // i64.const
0x20, // 32
0x86, // i64.shl,
0x20, // local.get
],
uleb128Encode(j),
[
0xac, // i64.extend_i32_signed
0x84, // i64.or
]
);
j+=2;
} else {
convert_code.push(0x20); // local.get
convert_code = convert_code.concat(uleb128Encode(j));
j++;
}
}

convert_code = convert_code.concat([
0x20, 0x00, // local.get 0 (put function pointer on stack)
0x11, 0x01, 0x00, // call_indirect 1 0
]);

if (sig[0] === "j") {
convert_code = convert_code.concat([
0x22, // tee
...uleb128Encode(j),
0x42, 0x20, // i64.const 32
0x88, // i64.shr_u
0xa7, // i32.wrap_i64
0x10, 0x00, // Call function 0
0x20, ...uleb128Encode(j), // local.get
0xa7, // i32.wrap_i64
]);
}
convert_code.push(0x0b); // end

var codeSection = [].concat(
[0x01], // one code
uleb128Encode(convert_code.length),
convert_code,
);
codeSection = [0x0A /* Code section code */].concat(
uleb128Encode(codeSection.length),
codeSection
);
sections.push(codeSection);

var bytes = new Uint8Array([].concat.apply([], sections));
const fs = require("fs")
fs.writeFileSync("blah.wasm", bytes);

// We can compile this wasm module synchronously because it is small.
var module = new WebAssembly.Module(bytes);
var instance = new WebAssembly.Instance(module, {
'e': {
't': wasmTable,
'r': setTempRet0,
}
});
var wrappedFunc = instance.exports['f'];
return wrappedFunc;
},
#endif

// Used in library code to get JS function from wasm function pointer.
Expand Down
51 changes: 27 additions & 24 deletions src/runtime_functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,7 @@ function sigToWasmTypes(sig) {
return type;
}

// Wraps a JS function as a wasm function with a given signature.
function convertJsFunctionToWasm(func, sig) {
#if WASM2JS
return func;
#else // WASM2JS

// If the type reflection proposal is available, use the new
// "WebAssembly.Function" constructor.
// Otherwise, construct a minimal wasm module importing the JS function and
// re-exporting it.
if (typeof WebAssembly.Function == "function") {
return new WebAssembly.Function(sigToWasmTypes(sig), func);
}

// The module is static, with the exception of the type section, which is
// generated based on the signature passed in.
var typeSection = [
0x01, // count: 1
0x60, // form: func
];
function generate_func_type(sig){
var sigRet = sig.slice(0, 1);
var sigParam = sig.slice(1);
var typeCodes = {
Expand All @@ -78,22 +59,44 @@ function convertJsFunctionToWasm(func, sig) {
};

// Parameters, length + signatures
typeSection = typeSection.concat(uleb128Encode(sigParam.length));
var result = [ 0x60, /* form: func */];
result = result.concat(uleb128Encode(sigParam.length));
for (var i = 0; i < sigParam.length; ++i) {
#if ASSERTIONS
assert(sigParam[i] in typeCodes, 'invalid signature char: ' + sigParam[i]);
#endif
typeSection.push(typeCodes[sigParam[i]]);
result.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);
result.push(0x00);
} else {
typeSection = typeSection.concat([0x01, typeCodes[sigRet]]);
result = result.concat([0x01, typeCodes[sigRet]]);
}
return result;
}

// Wraps a JS function as a wasm function with a given signature.
function convertJsFunctionToWasm(func, sig) {
#if WASM2JS
return func;
#else // WASM2JS

// If the type reflection proposal is available, use the new
// "WebAssembly.Function" constructor.
// Otherwise, construct a minimal wasm module importing the JS function and
// re-exporting it.
if (typeof WebAssembly.Function == "function") {
return new WebAssembly.Function(sigToWasmTypes(sig), func);
}

// The module is static, with the exception of the type section, which is
// generated based on the signature passed in.
var typeSection = [
0x01, // count: 1
].concat(generate_func_type(sig));
// Write the section code and overall length of the type section into the
// section header
typeSection = [0x01 /* Type section code */].concat(
Expand Down