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 all 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
11 changes: 9 additions & 2 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
8 changes: 8 additions & 0 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
59 changes: 31 additions & 28 deletions src/library_addfunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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")
Expand Down
154 changes: 154 additions & 0 deletions src/library_makeDynCall.js
Original file line number Diff line number Diff line change
@@ -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;
},
});
1 change: 1 addition & 0 deletions src/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ global.LibraryManager = {
'library_stack_trace.js',
'library_wasi.js',
'library_dylink.js',
'library_makeDynCall.js',
'library_eventloop.js',
];

Expand Down
5 changes: 5 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions test/test_runtime_dyncall_wrapper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <emscripten.h>
#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);
}