diff --git a/emcc.py b/emcc.py index 788dfa07588a6..65ebe28e454fb 100755 --- a/emcc.py +++ b/emcc.py @@ -2386,8 +2386,15 @@ def check_memory_setting(setting): options.memory_init_file = True settings.MEM_INIT_IN_WASM = True - if settings.MAYBE_WASM2JS or settings.AUTODEBUG or settings.LINKABLE or not settings.DISABLE_EXCEPTION_CATCHING: - settings.REQUIRED_EXPORTS += ['getTempRet0', 'setTempRet0'] + if ( + settings.MAYBE_WASM2JS or + settings.AUTODEBUG or + settings.LINKABLE or + settings.INCLUDE_FULL_LIBRARY or + not settings.DISABLE_EXCEPTION_CATCHING or + (settings.MAIN_MODULE == 1 and (settings.DYNCALLS or not settings.WASM_BIGINT)) + ): + settings.REQUIRED_EXPORTS += ["getTempRet0", "setTempRet0"] if settings.LEGALIZE_JS_FFI: settings.REQUIRED_EXPORTS += ['__get_temp_ret', '__set_temp_ret'] diff --git a/src/library.js b/src/library.js index f7a5a6098f4c2..354ecc1426f5c 100644 --- a/src/library.js +++ b/src/library.js @@ -3194,6 +3194,9 @@ mergeInto(LibraryManager.library, { }, #if DYNCALLS || !WASM_BIGINT +#if MAIN_MODULE == 1 + $dynCallLegacy__deps: ['$createDyncallWrapper'], +#endif $dynCallLegacy: function(sig, ptr, args) { #if ASSERTIONS #if MINIMAL_RUNTIME @@ -3212,6 +3215,11 @@ mergeInto(LibraryManager.library, { #if MINIMAL_RUNTIME var f = dynCalls[sig]; #else +#if MAIN_MODULE == 1 + if (!('dynCall_' + sig in Module)) { + Module['dynCall_' + sig] = createDyncallWrapper(sig); + } +#endif var f = Module['dynCall_' + sig]; #endif return args && args.length ? f.apply(null, [ptr].concat(args)) : f.call(null, ptr); diff --git a/src/library_addfunction.js b/src/library_addfunction.js index 3bdab4632b6b7..4e1e48781c5cc 100644 --- a/src/library_addfunction.js +++ b/src/library_addfunction.js @@ -44,28 +44,8 @@ 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 = { @@ -79,24 +59,47 @@ mergeInto(LibraryManager.library, { '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 assert(sigParam[i] in typeCodes, 'invalid signature char: ' + sigParam[i]); #endif - typeSectionBody.push(typeCodes[sigParam[i]]); + 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.push(0x01, typeCodes[sigRet]); + } + }, + // 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") diff --git a/src/library_makeDynCall.js b/src/library_makeDynCall.js new file mode 100644 index 0000000000000..ad6750ebb2bd8 --- /dev/null +++ b/src/library_makeDynCall.js @@ -0,0 +1,154 @@ +/** + * @license + * Copyright 2020 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +mergeInto(LibraryManager.library, { + $createDyncallWrapper__deps: ['$generateFuncType', '$uleb128Encode'], + $createDyncallWrapper: function(sig) { + var sections = []; + 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 + + var typeSection = [0x01 /* Type section code */]; + uleb128Encode(typeSectionBody.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") { + // tee into j (after the argument handling loop, j is one past the + // argument list so it points to the i64 local we added) + convert_code.push(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; + }, +}); diff --git a/src/modules.js b/src/modules.js index cdc4e812867c4..739ba1cc19d6f 100644 --- a/src/modules.js +++ b/src/modules.js @@ -50,6 +50,7 @@ global.LibraryManager = { 'library_stack_trace.js', 'library_wasi.js', 'library_dylink.js', + 'library_makeDynCall.js', 'library_eventloop.js', ]; diff --git a/test/test_other.py b/test/test_other.py index 716ec1d74479e..ac3b8e611df7b 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -12454,6 +12454,11 @@ def test_warn_once(self): ''') self.do_runf('main.c', 'warning: foo\ndone\n') + def test_dyncallwrapper(self): + self.set_setting('MAIN_MODULE', 1) + expected = "2 7\ni: 2 j: 8589934599 f: 3.120000 d: 77.120000" + self.do_runf(test_file('test_runtime_dyncall_wrapper.c'), expected) + def test_compile_with_cache_lock(self): # Verify that, after warming the cache, running emcc does not require the cache lock. # Previously we would acquire the lock during sanity checking (even when the check diff --git a/test/test_runtime_dyncall_wrapper.c b/test/test_runtime_dyncall_wrapper.c new file mode 100644 index 0000000000000..d54a19dd8821a --- /dev/null +++ b/test/test_runtime_dyncall_wrapper.c @@ -0,0 +1,24 @@ +#include +#include "stdint.h" +#include "stdio.h" + +uint64_t f1(uint64_t x){ + return x; +} + +void f2(int i, uint64_t j, float f, double d){ + printf("i: %d j: %lld f: %f d: %lf\n", i, j, f, d); +} + + +int main(){ + EM_ASM({ + var w = createDyncallWrapper("jj"); + console.log(w($0, 2, 7), getTempRet0()); + }, f1); + + EM_ASM({ + var w = createDyncallWrapper("vijfd"); + w($0, 2, 7, 2, 3.12, 77.12); + }, f2); +}