Skip to content

Commit 704218f

Browse files
authored
dynCallLegacy: Fill in dynCall_sig with a Wasm adaptor if it is missing (emscripten-core#17328)
This makes it possible to e.g. use a Rust side module without -sWASM_BIGINT, in MAIN_MODULE=1 mode.
1 parent 88e0622 commit 704218f

File tree

7 files changed

+232
-30
lines changed

7 files changed

+232
-30
lines changed

emcc.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2386,8 +2386,15 @@ def check_memory_setting(setting):
23862386
options.memory_init_file = True
23872387
settings.MEM_INIT_IN_WASM = True
23882388

2389-
if settings.MAYBE_WASM2JS or settings.AUTODEBUG or settings.LINKABLE or not settings.DISABLE_EXCEPTION_CATCHING:
2390-
settings.REQUIRED_EXPORTS += ['getTempRet0', 'setTempRet0']
2389+
if (
2390+
settings.MAYBE_WASM2JS or
2391+
settings.AUTODEBUG or
2392+
settings.LINKABLE or
2393+
settings.INCLUDE_FULL_LIBRARY or
2394+
not settings.DISABLE_EXCEPTION_CATCHING or
2395+
(settings.MAIN_MODULE == 1 and (settings.DYNCALLS or not settings.WASM_BIGINT))
2396+
):
2397+
settings.REQUIRED_EXPORTS += ["getTempRet0", "setTempRet0"]
23912398

23922399
if settings.LEGALIZE_JS_FFI:
23932400
settings.REQUIRED_EXPORTS += ['__get_temp_ret', '__set_temp_ret']

src/library.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3194,6 +3194,9 @@ mergeInto(LibraryManager.library, {
31943194
},
31953195

31963196
#if DYNCALLS || !WASM_BIGINT
3197+
#if MAIN_MODULE == 1
3198+
$dynCallLegacy__deps: ['$createDyncallWrapper'],
3199+
#endif
31973200
$dynCallLegacy: function(sig, ptr, args) {
31983201
#if ASSERTIONS
31993202
#if MINIMAL_RUNTIME
@@ -3212,6 +3215,11 @@ mergeInto(LibraryManager.library, {
32123215
#if MINIMAL_RUNTIME
32133216
var f = dynCalls[sig];
32143217
#else
3218+
#if MAIN_MODULE == 1
3219+
if (!('dynCall_' + sig in Module)) {
3220+
Module['dynCall_' + sig] = createDyncallWrapper(sig);
3221+
}
3222+
#endif
32153223
var f = Module['dynCall_' + sig];
32163224
#endif
32173225
return args && args.length ? f.apply(null, [ptr].concat(args)) : f.call(null, ptr);

src/library_addfunction.js

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,8 @@ mergeInto(LibraryManager.library, {
4444
}
4545
return type;
4646
},
47-
48-
// Wraps a JS function as a wasm function with a given signature.
49-
$convertJsFunctionToWasm__deps: ['$uleb128Encode', '$sigToWasmTypes'],
50-
$convertJsFunctionToWasm: function(func, sig) {
51-
#if WASM2JS
52-
return func;
53-
#else // WASM2JS
54-
55-
// If the type reflection proposal is available, use the new
56-
// "WebAssembly.Function" constructor.
57-
// Otherwise, construct a minimal wasm module importing the JS function and
58-
// re-exporting it.
59-
if (typeof WebAssembly.Function == "function") {
60-
return new WebAssembly.Function(sigToWasmTypes(sig), func);
61-
}
62-
63-
// The module is static, with the exception of the type section, which is
64-
// generated based on the signature passed in.
65-
var typeSectionBody = [
66-
0x01, // count: 1
67-
0x60, // form: func
68-
];
47+
$generateFuncType__deps: ['$uleb128Encode'],
48+
$generateFuncType : function(sig, target){
6949
var sigRet = sig.slice(0, 1);
7050
var sigParam = sig.slice(1);
7151
var typeCodes = {
@@ -79,24 +59,47 @@ mergeInto(LibraryManager.library, {
7959
'f': 0x7d, // f32
8060
'd': 0x7c, // f64
8161
};
82-
62+
8363
// Parameters, length + signatures
84-
uleb128Encode(sigParam.length, typeSectionBody);
64+
target.push(0x60 /* form: func */);
65+
uleb128Encode(sigParam.length, target);
8566
for (var i = 0; i < sigParam.length; ++i) {
8667
#if ASSERTIONS
8768
assert(sigParam[i] in typeCodes, 'invalid signature char: ' + sigParam[i]);
8869
#endif
89-
typeSectionBody.push(typeCodes[sigParam[i]]);
70+
target.push(typeCodes[sigParam[i]]);
9071
}
91-
72+
9273
// Return values, length + signatures
9374
// With no multi-return in MVP, either 0 (void) or 1 (anything else)
9475
if (sigRet == 'v') {
95-
typeSectionBody.push(0x00);
76+
target.push(0x00);
9677
} else {
97-
typeSectionBody.push(0x01, typeCodes[sigRet]);
78+
target.push(0x01, typeCodes[sigRet]);
79+
}
80+
},
81+
// Wraps a JS function as a wasm function with a given signature.
82+
$convertJsFunctionToWasm__deps: ['$uleb128Encode', '$sigToWasmTypes', '$generateFuncType'],
83+
$convertJsFunctionToWasm: function(func, sig) {
84+
#if WASM2JS
85+
// return func;
86+
#else // WASM2JS
87+
88+
// If the type reflection proposal is available, use the new
89+
// "WebAssembly.Function" constructor.
90+
// Otherwise, construct a minimal wasm module importing the JS function and
91+
// re-exporting it.
92+
if (typeof WebAssembly.Function == "function") {
93+
return new WebAssembly.Function(sigToWasmTypes(sig), func);
9894
}
9995

96+
// The module is static, with the exception of the type section, which is
97+
// generated based on the signature passed in.
98+
var typeSectionBody = [
99+
0x01, // count: 1
100+
];
101+
generateFuncType(sig, typeSectionBody);
102+
100103
// Rest of the module is static
101104
var bytes = [
102105
0x00, 0x61, 0x73, 0x6d, // magic ("\0asm")

src/library_makeDynCall.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/**
2+
* @license
3+
* Copyright 2020 The Emscripten Authors
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
mergeInto(LibraryManager.library, {
8+
$createDyncallWrapper__deps: ['$generateFuncType', '$uleb128Encode'],
9+
$createDyncallWrapper: function(sig) {
10+
var sections = [];
11+
var prelude = [
12+
0x00, 0x61, 0x73, 0x6d, // magic ("\0asm")
13+
0x01, 0x00, 0x00, 0x00, // version: 1
14+
];
15+
sections.push(prelude);
16+
var wrappersig = [
17+
// if return type is j, we will put the upper 32 bits into tempRet0.
18+
sig[0].replace("j", "i"),
19+
"i", // The first argument is the function pointer to call
20+
// in the rest of the argument list, one 64 bit integer is legalized into
21+
// two 32 bit integers.
22+
sig.slice(1).replace("j", "ii"),
23+
].join("");
24+
25+
var typeSectionBody = [
26+
0x03, // number of types = 3
27+
];
28+
generateFuncType(wrappersig, typeSectionBody); // The signature of the wrapper we are generating
29+
generateFuncType(sig, typeSectionBody); // the signature of the function pointer we will call
30+
generateFuncType("vi", typeSectionBody); // the signature of setTempRet0
31+
32+
var typeSection = [0x01 /* Type section code */];
33+
uleb128Encode(typeSectionBody.length, typeSection); // length of section in bytes
34+
typeSection.push.apply(typeSection, typeSectionBody);
35+
sections.push(typeSection);
36+
37+
var importSection = [
38+
0x02, // import section code
39+
0x0F, // length of section in bytes
40+
0x02, // number of imports = 2
41+
// Import the wasmTable, which we will call "t"
42+
0x01, 0x65, // name "e"
43+
0x01, 0x74, // name "t"
44+
0x01, 0x70, // importing a table
45+
0x00, // with no max # of elements
46+
0x00, // and min of 0 elements
47+
// Import the setTempRet0 function, which we will call "r"
48+
0x01, 0x65, // name "e"
49+
0x01, 0x72, // name "r"
50+
0x00, // importing a function
51+
0x02, // type 2
52+
];
53+
sections.push(importSection);
54+
55+
var functionSection = [
56+
0x03, // function section code
57+
0x02, // length of section in bytes
58+
0x01, // number of functions = 1
59+
0x00, // type 0 = wrappersig
60+
];
61+
sections.push(functionSection);
62+
63+
var exportSection = [
64+
0x07, // export section code
65+
0x05, // length of section in bytes
66+
0x01, // One export
67+
0x01, 0x66, // name "f"
68+
0x00, // type: function
69+
0x01, // function index 1 = the wrapper function (index 0 is setTempRet0)
70+
];
71+
sections.push(exportSection);
72+
73+
var convert_code = [];
74+
if (sig[0] === "j") {
75+
// Add a single extra i64 local. In order to legalize the return value we
76+
// need a local to store it in. Local variables are run length encoded.
77+
convert_code = [
78+
0x01, // One run
79+
0x01, // of length 1
80+
0x7e, // of i64
81+
];
82+
} else {
83+
convert_code.push(0x00); // no local variables (except the arguments)
84+
}
85+
86+
function localGet(j) {
87+
convert_code.push(0x20); // local.get
88+
uleb128Encode(j, convert_code);
89+
}
90+
91+
var j = 1;
92+
for (var i = 1; i < sig.length; i++) {
93+
if (sig[i] == "j") {
94+
localGet(j + 1);
95+
convert_code.push(
96+
0xad, // i64.extend_i32_unsigned
97+
0x42, 0x20, // i64.const 32
98+
0x86, // i64.shl,
99+
)
100+
localGet(j);
101+
convert_code.push(
102+
0xac, // i64.extend_i32_signed
103+
0x84, // i64.or
104+
);
105+
j+=2;
106+
} else {
107+
localGet(j);
108+
j++;
109+
}
110+
}
111+
112+
convert_code.push(
113+
0x20, 0x00, // local.get 0 (put function pointer on stack)
114+
0x11, 0x01, 0x00, // call_indirect type 1 = wrapped_sig, table 0 = only table
115+
);
116+
if (sig[0] === "j") {
117+
// tee into j (after the argument handling loop, j is one past the
118+
// argument list so it points to the i64 local we added)
119+
convert_code.push(0x22);
120+
uleb128Encode(j, convert_code);
121+
convert_code.push(
122+
0x42, 0x20, // i64.const 32
123+
0x88, // i64.shr_u
124+
0xa7, // i32.wrap_i64
125+
0x10, 0x00, // Call function 0
126+
);
127+
localGet(j);
128+
convert_code.push(
129+
0xa7, // i32.wrap_i64
130+
);
131+
}
132+
convert_code.push(0x0b); // end
133+
134+
var codeBody = [0x01]; // one code
135+
uleb128Encode(convert_code.length, codeBody);
136+
codeBody.push.apply(codeBody, convert_code);
137+
var codeSection = [0x0A /* Code section code */];
138+
uleb128Encode(codeBody.length, codeSection);
139+
codeSection.push.apply(codeSection, codeBody);
140+
sections.push(codeSection);
141+
142+
var bytes = new Uint8Array([].concat.apply([], sections));
143+
// We can compile this wasm module synchronously because it is small.
144+
var module = new WebAssembly.Module(bytes);
145+
var instance = new WebAssembly.Instance(module, {
146+
'e': {
147+
't': wasmTable,
148+
'r': setTempRet0,
149+
}
150+
});
151+
var wrappedFunc = instance.exports['f'];
152+
return wrappedFunc;
153+
},
154+
});

src/modules.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ global.LibraryManager = {
5050
'library_stack_trace.js',
5151
'library_wasi.js',
5252
'library_dylink.js',
53+
'library_makeDynCall.js',
5354
'library_eventloop.js',
5455
];
5556

test/test_other.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12454,6 +12454,11 @@ def test_warn_once(self):
1245412454
''')
1245512455
self.do_runf('main.c', 'warning: foo\ndone\n')
1245612456

12457+
def test_dyncallwrapper(self):
12458+
self.set_setting('MAIN_MODULE', 1)
12459+
expected = "2 7\ni: 2 j: 8589934599 f: 3.120000 d: 77.120000"
12460+
self.do_runf(test_file('test_runtime_dyncall_wrapper.c'), expected)
12461+
1245712462
def test_compile_with_cache_lock(self):
1245812463
# Verify that, after warming the cache, running emcc does not require the cache lock.
1245912464
# Previously we would acquire the lock during sanity checking (even when the check

test/test_runtime_dyncall_wrapper.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include <emscripten.h>
2+
#include "stdint.h"
3+
#include "stdio.h"
4+
5+
uint64_t f1(uint64_t x){
6+
return x;
7+
}
8+
9+
void f2(int i, uint64_t j, float f, double d){
10+
printf("i: %d j: %lld f: %f d: %lf\n", i, j, f, d);
11+
}
12+
13+
14+
int main(){
15+
EM_ASM({
16+
var w = createDyncallWrapper("jj");
17+
console.log(w($0, 2, 7), getTempRet0());
18+
}, f1);
19+
20+
EM_ASM({
21+
var w = createDyncallWrapper("vijfd");
22+
w($0, 2, 7, 2, 3.12, 77.12);
23+
}, f2);
24+
}

0 commit comments

Comments
 (0)