diff --git a/emscripten.py b/emscripten.py index e686b11ed7f4d..076852c6756c7 100644 --- a/emscripten.py +++ b/emscripten.py @@ -548,10 +548,7 @@ def finalize_wasm(temp_files, infile, outfile, memfile, DEBUG): args.append('--bigint') if not shared.Settings.USE_LEGACY_DYNCALLS: - if shared.Settings.WASM_BIGINT: - args.append('--no-dyncalls') - else: - args.append('--dyncalls-i64') + args.append('--no-dyncalls') if shared.Settings.LEGALIZE_JS_FFI != 1: args.append('--no-legalize-javascript-ffi') diff --git a/src/library.js b/src/library.js index 129dd30cc3a18..76c374d0b3b0f 100644 --- a/src/library.js +++ b/src/library.js @@ -3773,16 +3773,307 @@ LibraryManager.library = { }, #if USE_LEGACY_DYNCALLS || !WASM_BIGINT - $dynCallLegacy: function(sig, ptr, args) { -#if ASSERTIONS - assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\''); - if (args && args.length) { - // j (64-bit integer) must be passed in as two numbers [low 32, high 32]. - assert(args.length === sig.substring(1).replace(/j/g, '--').length); - } else { - assert(sig.length == 1); + $jitDynCall: function(sig) { + /* + Creates a new dynCall function, that is a wasm function that is called + with a function pointer and arguments and does a call_indirect for us. + To do this we create a tiny wasm module with a single export. + + This takes around 1ms, so it can be noticeable if a lot of dynCalls are + jitted. + + Example output for signature "dif" (returns f64, has 2 params i32, f32): + + 00 61 73 6d + 01 00 00 00 + 01 0e 02 60 type section, func + 03 7f 7f 7d i32 i32 f32 + 01 7c f64 + 60 another func + 02 7f 7d + 01 7c + 02 09 import section + 01 01 61 01 61 01 + 70 00 00 + 03 02 01 00 function section + 07 05 export section + 01 01 61 00 00 + 0a 0d code section + 01 0b 00 20 01 20 02 20 00 11 01 00 0b + + (module + (type $t (func (param $x i32) (param $y f32) (result f64))) + (import "a" "a" (table $t (0 anyref))) + (func "a" (param $ptr i32) (param $x i32) (param $y f32) (result f64) + (call_indirect (type $t) + (local.get $x) + (local.get $y) + (local.get $ptr) + ) + ) + ) + + With legalization, the module for e.g. "vj" could look like + + (module + (type $legal (func (param $fptr i32) (param $low i32) (param $high i32))) + (type $call (func (param $x i64))) + (import "a" "a" (table $t (0 anyref))) + (func "a" (type $legal) (param $fptr i32) (param $low i32) (param $high i32) + (call_indirect (type $call) + (i64.or + (i64.extend_i32_u + (local.get $low) + ) + (i64.shl + (i64.extend_i32_u + (local.get $high) + ) + (i64.const 32) + ) + ) + (local.get $fptr) + ) + ) + ) + + or for "j", + + (module + (type $imported (func (param i32))) + (type $legal (func (param $fptr i32) (result i32))) + (type $call (func (result i64))) + (import "a" "a" (table $t (0 anyref))) + (import "a" "b" (func $setTempRet0 (type $imported))) + (func "a" (type $legal) (param $fptr i32) (result i32) + (local $temp i64) + (call $setTempRet0 + (i32.wrap_i64 + (i64.shr_u + (local.tee $temp + (call_indirect (type $call) + (local.get $fptr) + ) + ) + (i64.const 32) + ) + ) + ) + (i32.wrap_i64 + (local.get $temp) + ) + ) + ) + */ + + var sigRet = sig.slice(0, 1); + var sigParam = sig.slice(1); + + // Prepare for legalization + var illegalReturn = false; + var illegalParams = 0; +#if !WASM_BIGINT + illegalReturn = sigRet === 'j'; + for (var i = 0; i < sigParam.length; i++) { + if (sigParam[i] === 'j') { + illegalParams++; + } + } +#endif + + // Create a tiny wasm module with an exported function to call the table + // for us. + var typeSection = [ + 0x01, // section id + -1, // length (placeholder) + -1, // number of types (placeholder) + ]; + + var numTypes = 0; + + function addType(sig, legalize) { + numTypes++; + + var sigRet = sig.slice(0, 1); + var sigParam = sig.slice(1); + + typeSection.push(0x60); // func + typeSection.push(sigParam.length + (legalize ? illegalParams : 0)); + for (var i = 0; i < sigParam.length; ++i) { +#if !WASM_BIGINT + if (sigParam[i] === 'j' && legalize) { + typeSection.push(wasmTypeCodes['i']); + typeSection.push(wasmTypeCodes['i']); + continue; + } +#endif + typeSection.push(wasmTypeCodes[sigParam[i]]); + } + if (sigRet == 'v') { + typeSection.push(0x00); + } else { + typeSection.push(0x01); +#if !WASM_BIGINT + if (illegalReturn && legalize) { + typeSection.push(wasmTypeCodes['i']); + } else +#endif + { + typeSection.push(wasmTypeCodes[sigRet]); + } + } + } + + // First type: fptr, params (for the exported dyncall itself) + addType( + sigRet + 'i' + sigParam, + {{{ !WASM_BIGINT }}} // optionally legalize for JS + ); + // Second type: no fptr, just params (for the indirect call inside) + addType(sig); +#if !WASM_BIGINT + if (illegalReturn) { + // Third type for setTempRet0. + addType('vi'); + } +#endif + + // 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; + typeSection[2] = numTypes; + + // Import section: + // (import "a" "a" (table $t (0 anyref))) + var importSection = [ + 0x02, // section id + -1, // placeholder for size + -1 // placeholder for number + ]; + var numImports = 1; +#if !WASM_BIGINT + if (illegalReturn) { + // Also import setTempRet0 for the high bits. + numImports++; + importSection.push(0x01, 0x61, 0x01, 0x62, 0x00, 0x02); } + // Table import + importSection.push(0x01, 0x61, 0x01, 0x61, 0x01, 0x70, 0x00, 0x00); #endif + importSection[1] = importSection.length - 2; + importSection[2] = numImports; + + // Function section: declare one function with the first type + var functionSection = [0x03, 0x02, 0x01, 0x00]; + + // Export section: Export the function as "a" + var exportSection = [0x07, 0x05, 0x01, 0x01, 0x61, 0x00, 0x00]; +#if !WASM_BIGINT + if (illegalReturn) { + // If we also imported setTempRet0, the export index is of function 1. + exportSection[exportSection.length - 1] = 0x01; + } +#endif + + // Code section: read the params and do the indirect call. + var codeSection = [ + 0x0a, // section id + -1, // section length (placeholder) + 0x01, // num functions + -1 // function length (placeholder) + ]; +#if !WASM_BIGINT + if (illegalReturn) { + // Add an i64 var to use when splitting up the i64 return value + codeSection.push(0x01, 0x01, wasmTypeCodes['j']); + // The temp index is after the fptr, the params, and the extra legalized + // ones. + var tempIndex = sigParam.length + illegalParams + 1; + } else +#endif + { + codeSection.push(0x00); // no vars + } + + // i64 params are legalized as pairs of i32, i32. "i" tracks the index + // in the true signature, "j" tracks the index of the legalized one. Note + // that j starts at 1 because 0 is the function pointer. + for (var i = 0, j = 1; i < sigParam.length; ++i) { +#if !WASM_BIGINT + if (sigParam[i] === 'j') { + // Receive two i32s and compose an i64 + codeSection.push( + 0x20, // local.get + j++, // index of low 32 bits + 0xAD, // i64.extend_i32_u + 0x20, // local.get + j++, // index of high 32 bits + 0xAD, // i64.extend_i32_u + 0x42, // i64.const 32 + 0x20, + 0x86, // i64.shl + 0x84 // i64.or + ) + continue; + } +#endif + codeSection.push(0x20); // local.get + codeSection.push(j++); // index + } + codeSection.push(0x20); // local.get + codeSection.push(0); // function pointer + codeSection.push(0x11); // call_indirect + codeSection.push(0x01); // second function type + codeSection.push(0x00); // table index 0 +#if !WASM_BIGINT + if (illegalReturn) { + // Split the i64 into parts, return the high bits in tempRet0, and the + // low bits directly. + codeSection.push( + 0x22, tempIndex, // tee the result of the call + 0x42, 0x20, // i64.const 32 + 0x88, // i64.shr_u + 0xA7, // wrap + 0x10, 0x00, // call the import setTempRet0 + 0x20, tempIndex, // get the result of the call again + 0xA7 // wrap + ); + } +#endif + codeSection.push(0x0b); // end function + codeSection[1] = codeSection.length - 2; + codeSection[3] = codeSection.length - 4; + + var bytes = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, // magic ("\0asm") + 0x01, 0x00, 0x00, 0x00, // version: 1 + ].concat(typeSection, importSection, functionSection, exportSection, codeSection)); + + // We can compile this wasm module synchronously because it is very small. + //console.log(sig, illegalReturn, illegalParams, bytes); + var module = new WebAssembly.Module(bytes); + var instance = new WebAssembly.Instance(module, { + 'a': { + 'a': wasmTable +#if !WASM_BIGINT + , 'b': setTempRet0 +#endif + } + }); + return instance.exports['a']; + }, + + $dynCallLegacy__deps: ['$jitDynCall'], + $dynCallLegacy: function(sig, ptr, args) { +#if WASM2JS + var ret = wasmTable.get(ptr).apply(null, args); +console.log('wasm2js!', sig, ptr, args, ' => ', ret, new Error().stack); +setTempRet0(-1); + return ret; +#endif + if (!Module['dynCall_' + sig]) { + Module['dynCall_' + sig] = jitDynCall(sig); + } if (args && args.length) { return Module['dynCall_' + sig].apply(null, [ptr].concat(args)); } @@ -3815,7 +4106,7 @@ LibraryManager.library = { #else #if !WASM_BIGINT // Without WASM_BIGINT support we cannot directly call function with i64 as - // part of thier signature, so we rely the dynCall functions generated by + // part of their signature, so we rely on the dynCall functions generated by // wasm-emscripten-finalize if (sig.indexOf('j') != -1) { return dynCallLegacy(sig, ptr, args); diff --git a/src/runtime_functions.js b/src/runtime_functions.js index 8ab0e501aab75..bebb0f8cea96d 100644 --- a/src/runtime_functions.js +++ b/src/runtime_functions.js @@ -4,6 +4,13 @@ * SPDX-License-Identifier: MIT */ +var wasmTypeCodes = { + 'i': 0x7f, // i32 + 'j': 0x7e, // i64 + 'f': 0x7d, // f32 + 'd': 0x7c, // f64 +}; + // Wraps a JS function as a wasm function with a given signature. function convertJsFunctionToWasm(func, sig) { #if WASM2JS @@ -41,17 +48,11 @@ function convertJsFunctionToWasm(func, sig) { ]; 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]]); + typeSection.push(wasmTypeCodes[sigParam[i]]); } // Return values, length + signatures @@ -59,7 +60,7 @@ function convertJsFunctionToWasm(func, sig) { if (sigRet == 'v') { typeSection.push(0x00); } else { - typeSection = typeSection.concat([0x01, typeCodes[sigRet]]); + typeSection = typeSection.concat([0x01, wasmTypeCodes[sigRet]]); } // Write the overall length of the type section back into the section header diff --git a/tools/shared.py b/tools/shared.py index 65bdf15553fa3..ef83a89f470dd 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1212,17 +1212,7 @@ def make_jscall(sig): @staticmethod def make_dynCall(sig, args): - # wasm2c and asyncify are not yet compatible with direct wasm table calls - if Settings.USE_LEGACY_DYNCALLS or not JS.is_legal_sig(sig): - args = ','.join(args) - if not Settings.MAIN_MODULE and not Settings.SIDE_MODULE: - # Optimize dynCall accesses in the case when not building with dynamic - # linking enabled. - return 'dynCall_%s(%s)' % (sig, args) - else: - return 'Module["dynCall_%s"](%s)' % (sig, args) - else: - return 'wasmTable.get(%s)(%s)' % (args[0], ','.join(args[1:])) + return 'dynCallLegacy("%s", %s, [%s])' % (sig, args[0], ','.join(args[1:])) @staticmethod def make_invoke(sig, named=True):