Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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 src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -3208,13 +3208,15 @@ mergeInto(LibraryManager.library, {
},

#if DYNCALLS || !WASM_BIGINT
#if MAIN_MODULE == 1
// createDyncallWrapper is defined in library_makeDynCall
$dynCallLegacy__deps: ['$createDyncallWrapper'],
#endif
$dynCallLegacy: function(sig, ptr, args) {
#if ASSERTIONS
#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 @@ -3226,6 +3228,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 tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -12384,3 +12384,8 @@ 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)
24 changes: 24 additions & 0 deletions tests/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), tempRet0);
}, f1);

EM_ASM({
var w = createDyncallWrapper("vijfd");
w($0, 2, 7, 2, 3.12, 77.12);
}, f2);
}