Skip to content
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

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

Merged
merged 23 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
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
160 changes: 158 additions & 2 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -3212,8 +3212,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 @@ -3225,11 +3223,169 @@ mergeInto(LibraryManager.library, {
#if MINIMAL_RUNTIME
var f = dynCalls[sig];
#else
#if MAIN_MODULE == 1
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
if (!('dynCall_' + sig in Module)) {
Module['dynCall_' + sig] = createDyncallWrapper(sig);
}
#endif
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
var f = Module['dynCall_' + sig];
#endif
return args && args.length ? f.apply(null, [ptr].concat(args)) : f.call(null, ptr);
},
$dynCall__deps: ['$dynCallLegacy', '$getWasmTableEntry'],
#if MAIN_MODULE == 1
$dynCallLegacy__deps: ['$createDyncallWrapper'],
$createDyncallWrapper__deps: ['$generateFuncType', '$uleb128Encode'],
$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 wrappersig = [
// if return type is j, we will put the upper 32 bits into tempRet0.
sig[0].replace("j", "i"),
"i", // The first argument is the function pointer to call
// in the rest of the argument list, one 64 bit integer is legalized into
// two 32 bit integers.
sig.slice(1).replace("j", "ii"),
].join("");

var typeSectionBody = [
0x03, // number of types = 3
];
generateFuncType(wrappersig, typeSectionBody); // The signature of the wrapper we are generating
generateFuncType(sig, typeSectionBody); // the signature of the function pointer we will call
generateFuncType("vi", typeSectionBody); // the signature of setTempRet0

typeSection = [0x01 /* Type section code */];
uleb128Encode(typeSection.length, typeSection); // length of section in bytes
typeSection.push.apply(typeSection, typeSectionBody);
sections.push(typeSection);

var importSection = [
0x02, // import section code
0x0F, // length of section in bytes
0x02, // number of imports = 2
// Import the wasmTable, which we will call "t"
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
// Import the setTempRet0 function, which we will call "r"
0x01, 0x65, // name "e"
0x01, 0x72, // name "r"
0x00, // importing a function
0x02, // type 2
];
sections.push(importSection);

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

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

var convert_code = [];
if (sig[0] === "j") {
// Add a single extra i64 local. In order to legalize the return value we
// need a local to store it in. Local variables are run length encoded.
convert_code = [
0x01, // One run
0x01, // of length 1
0x7e, // of i64
];
} else {
convert_code.push(0x00); // no local variables (except the arguments)
}

function localGet(j){
convert_code.push(0x20); // local.get
uleb128Encode(j, convert_code);
}

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

convert_code.push(
0x20, 0x00, // local.get 0 (put function pointer on stack)
0x11, 0x01, 0x00, // call_indirect type 1 = wrapped_sig, table 0 = only table
);

if (sig[0] === "j") {
convert_code.push(
// tee into j (after the argument handling loop, j is one past the
// argument list so it points to the i64 local we added)
0x22,
)
uleb128Encode(j, convert_code);
convert_code.push(
0x42, 0x20, // i64.const 32
0x88, // i64.shr_u
0xa7, // i32.wrap_i64
0x10, 0x00, // Call function 0
);
localGet(j);
convert_code.push(
0xa7, // i32.wrap_i64
);
}
convert_code.push(0x0b); // end

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

var bytes = new Uint8Array([].concat.apply([], sections));

// 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
#endif

// Used in library code to get JS function from wasm function pointer.
Expand Down
69 changes: 36 additions & 33 deletions src/library_addfunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,59 +44,62 @@ mergeInto(LibraryManager.library, {
}
return type;
},

// Wraps a JS function as a wasm function with a given signature.
$convertJsFunctionToWasm__deps: ['$uleb128Encode', '$sigToWasmTypes'],
$convertJsFunctionToWasm: function(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 typeSectionBody = [
0x01, // count: 1
0x60, // form: func
];
$generateFuncType__deps: ['$uleb128Encode'],
$generateFuncType : function(sig, target){
var sigRet = sig.slice(0, 1);
var sigParam = sig.slice(1);
var typeCodes = {
'i': 0x7f, // i32
#if MEMORY64
#if MEMORY64
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
'p': 0x7e, // i64
#else
#else
'p': 0x7f, // i32
#endif
#endif
'j': 0x7e, // i64
'f': 0x7d, // f32
'd': 0x7c, // f64
};

// Parameters, length + signatures
uleb128Encode(sigParam.length, typeSectionBody);
target.push(0x60 /* form: func */);
uleb128Encode(sigParam.length, target);
for (var i = 0; i < sigParam.length; ++i) {
#if ASSERTIONS
#if ASSERTIONS
assert(sigParam[i] in typeCodes, 'invalid signature char: ' + sigParam[i]);
#endif
typeSectionBody.push(typeCodes[sigParam[i]]);
#endif
target.push(typeCodes[sigParam[i]]);
}

// Return values, length + signatures
// With no multi-return in MVP, either 0 (void) or 1 (anything else)
if (sigRet == 'v') {
typeSectionBody.push(0x00);
target.push(0x00);
} else {
typeSectionBody.push(0x01, typeCodes[sigRet]);
target = target.concat([0x01, typeCodes[sigRet]]);
hoodmane marked this conversation as resolved.
Show resolved Hide resolved
}
},
// Wraps a JS function as a wasm function with a given signature.
$convertJsFunctionToWasm__deps: ['$uleb128Encode', '$sigToWasmTypes', '$generateFuncType'],
$convertJsFunctionToWasm: function(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 typeSectionBody = [
0x01, // count: 1
];
generateFuncType(sig, typeSectionBody);

// Rest of the module is static
var bytes = [
0x00, 0x61, 0x73, 0x6d, // magic ("\0asm")
Expand Down