Skip to content

Commit 9585abd

Browse files
authored
Move addFunction/removeFunction to JS library (#17370)
Because of #17369, these functions to continue to be exported via `EXPORTED_RUNTIME_METHODS`, and ensure good error messages informing users how to include these library functions. Hopefully many more changes like this to come but this is really testing the waters.
1 parent 6f9e532 commit 9585abd

14 files changed

+244
-231
lines changed

ChangeLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ See docs/process.md for more on how version tagging works.
2525
`DEFAULT_LIBRARY_FUNCS_TO_INCLUDE`. This change allows us to transition
2626
runtime functions to JS library functions without the need to folks to add
2727
`DEFAULT_LIBRARY_FUNCS_TO_INCLUDE`. (#17369)
28+
- The `addFunction`/`removeFunction` runtime functions were converted into JS
29+
library functions. This means that won't get included in the output unless
30+
explictly required. Exporting them via `EXPORTED_RUNTIME_METHODS` will
31+
continue to work. For internal usage (without exporting them) they can be
32+
added to `DEFAULT_LIBRARY_FUNCS_TO_INCLUDE`. (#17370)
2833

2934
3.1.15 - 07/01/2022
3035
-------------------

src/library_addfunction.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/**
2+
* @license
3+
* Copyright 2020 The Emscripten Authors
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
mergeInto(LibraryManager.library, {
8+
// This gives correct answers for everything less than 2^{14} = 16384
9+
// I hope nobody is contemplating functions with 16384 arguments...
10+
$uleb128Encode: function(n) {
11+
#if ASSERTIONS
12+
assert(n < 16384);
13+
#endif
14+
if (n < 128) {
15+
return [n];
16+
}
17+
return [(n % 128) | 128, n >> 7];
18+
},
19+
20+
// Converts a signature like 'vii' into a description of the wasm types, like
21+
// { parameters: ['i32', 'i32'], results: [] }.
22+
$sigToWasmTypes: function(sig) {
23+
var typeNames = {
24+
'i': 'i32',
25+
'j': 'i64',
26+
'f': 'f32',
27+
'd': 'f64',
28+
#if MEMORY64
29+
'p': 'i64',
30+
#else
31+
'p': 'i32',
32+
#endif
33+
};
34+
var type = {
35+
parameters: [],
36+
results: sig[0] == 'v' ? [] : [typeNames[sig[0]]]
37+
};
38+
for (var i = 1; i < sig.length; ++i) {
39+
#if ASSERTIONS
40+
assert(sig[i] in typeNames, 'invalid signature char: ' + sig[i]);
41+
#endif
42+
type.parameters.push(typeNames[sig[i]]);
43+
}
44+
return type;
45+
},
46+
47+
// Wraps a JS function as a wasm function with a given signature.
48+
$convertJsFunctionToWasm__deps: ['$uleb128Encode', '$sigToWasmTypes'],
49+
$convertJsFunctionToWasm: function(func, sig) {
50+
#if WASM2JS
51+
return func;
52+
#else // WASM2JS
53+
54+
// If the type reflection proposal is available, use the new
55+
// "WebAssembly.Function" constructor.
56+
// Otherwise, construct a minimal wasm module importing the JS function and
57+
// re-exporting it.
58+
if (typeof WebAssembly.Function == "function") {
59+
return new WebAssembly.Function(sigToWasmTypes(sig), func);
60+
}
61+
62+
// The module is static, with the exception of the type section, which is
63+
// generated based on the signature passed in.
64+
var typeSection = [
65+
0x01, // count: 1
66+
0x60, // form: func
67+
];
68+
var sigRet = sig.slice(0, 1);
69+
var sigParam = sig.slice(1);
70+
var typeCodes = {
71+
'i': 0x7f, // i32
72+
#if MEMORY64
73+
'p': 0x7e, // i64
74+
#else
75+
'p': 0x7f, // i32
76+
#endif
77+
'j': 0x7e, // i64
78+
'f': 0x7d, // f32
79+
'd': 0x7c, // f64
80+
};
81+
82+
// Parameters, length + signatures
83+
typeSection = typeSection.concat(uleb128Encode(sigParam.length));
84+
for (var i = 0; i < sigParam.length; ++i) {
85+
#if ASSERTIONS
86+
assert(sigParam[i] in typeCodes, 'invalid signature char: ' + sigParam[i]);
87+
#endif
88+
typeSection.push(typeCodes[sigParam[i]]);
89+
}
90+
91+
// Return values, length + signatures
92+
// With no multi-return in MVP, either 0 (void) or 1 (anything else)
93+
if (sigRet == 'v') {
94+
typeSection.push(0x00);
95+
} else {
96+
typeSection = typeSection.concat([0x01, typeCodes[sigRet]]);
97+
}
98+
99+
// Write the section code and overall length of the type section into the
100+
// section header
101+
typeSection = [0x01 /* Type section code */].concat(
102+
uleb128Encode(typeSection.length),
103+
typeSection
104+
);
105+
106+
// Rest of the module is static
107+
var bytes = new Uint8Array([
108+
0x00, 0x61, 0x73, 0x6d, // magic ("\0asm")
109+
0x01, 0x00, 0x00, 0x00, // version: 1
110+
].concat(typeSection, [
111+
0x02, 0x07, // import section
112+
// (import "e" "f" (func 0 (type 0)))
113+
0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
114+
0x07, 0x05, // export section
115+
// (export "f" (func 0 (type 0)))
116+
0x01, 0x01, 0x66, 0x00, 0x00,
117+
]));
118+
119+
// We can compile this wasm module synchronously because it is very small.
120+
// This accepts an import (at "e.f"), that it reroutes to an export (at "f")
121+
var module = new WebAssembly.Module(bytes);
122+
var instance = new WebAssembly.Instance(module, {
123+
'e': {
124+
'f': func
125+
}
126+
});
127+
var wrappedFunc = instance.exports['f'];
128+
return wrappedFunc;
129+
#endif // WASM2JS
130+
},
131+
132+
$freeTableIndexes: [],
133+
134+
// Weak map of functions in the table to their indexes, created on first use.
135+
$functionsInTableMap: undefined,
136+
137+
$getEmptyTableSlot__deps: ['$freeTableIndexes'],
138+
$getEmptyTableSlot: function() {
139+
// Reuse a free index if there is one, otherwise grow.
140+
if (freeTableIndexes.length) {
141+
return freeTableIndexes.pop();
142+
}
143+
// Grow the table
144+
try {
145+
wasmTable.grow(1);
146+
} catch (err) {
147+
if (!(err instanceof RangeError)) {
148+
throw err;
149+
}
150+
throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.';
151+
}
152+
return wasmTable.length - 1;
153+
},
154+
155+
$updateTableMap__deps: ['$getWasmTableEntry'],
156+
$updateTableMap: function(offset, count) {
157+
for (var i = offset; i < offset + count; i++) {
158+
var item = getWasmTableEntry(i);
159+
// Ignore null values.
160+
if (item) {
161+
functionsInTableMap.set(item, i);
162+
}
163+
}
164+
},
165+
166+
/**
167+
* Add a function to the table.
168+
* 'sig' parameter is required if the function being added is a JS function.
169+
*/
170+
$addFunction__docs: '/** @param {string=} sig */',
171+
$addFunction__deps: ['$convertJsFunctionToWasm', '$updateTableMap',
172+
'$functionsInTableMap', '$getEmptyTableSlot',
173+
'$getWasmTableEntry', '$setWasmTableEntry'],
174+
$addFunction: function(func, sig) {
175+
#if ASSERTIONS
176+
assert(typeof func != 'undefined');
177+
#endif // ASSERTIONS
178+
179+
// Check if the function is already in the table, to ensure each function
180+
// gets a unique index. First, create the map if this is the first use.
181+
if (!functionsInTableMap) {
182+
functionsInTableMap = new WeakMap();
183+
updateTableMap(0, wasmTable.length);
184+
}
185+
if (functionsInTableMap.has(func)) {
186+
return functionsInTableMap.get(func);
187+
}
188+
189+
// It's not in the table, add it now.
190+
191+
#if ASSERTIONS >= 2
192+
// Make sure functionsInTableMap is actually up to date, that is, that this
193+
// function is not actually in the wasm Table despite not being tracked in
194+
// functionsInTableMap.
195+
for (var i = 0; i < wasmTable.length; i++) {
196+
assert(getWasmTableEntry(i) != func, 'function in Table but not functionsInTableMap');
197+
}
198+
#endif
199+
200+
var ret = getEmptyTableSlot();
201+
202+
// Set the new value.
203+
try {
204+
// Attempting to call this with JS function will cause of table.set() to fail
205+
setWasmTableEntry(ret, func);
206+
} catch (err) {
207+
if (!(err instanceof TypeError)) {
208+
throw err;
209+
}
210+
#if ASSERTIONS
211+
assert(typeof sig != 'undefined', 'Missing signature argument to addFunction: ' + func);
212+
#endif
213+
var wrapped = convertJsFunctionToWasm(func, sig);
214+
setWasmTableEntry(ret, wrapped);
215+
}
216+
217+
functionsInTableMap.set(func, ret);
218+
219+
return ret;
220+
},
221+
222+
$removeFunction__deps: ['$functionsInTableMap', '$freeTableIndexes', '$getWasmTableEntry'],
223+
$removeFunction: function(index) {
224+
functionsInTableMap.delete(getWasmTableEntry(index));
225+
freeTableIndexes.push(index);
226+
},
227+
});
228+

src/library_async.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ mergeInto(LibraryManager.library, {
2020
},
2121

2222
#if ASYNCIFY
23-
$Asyncify__deps: ['$runAndAbortIfError', '$callUserCallback',
23+
$Asyncify__deps: ['$runAndAbortIfError', '$callUserCallback', '$sigToWasmTypes',
2424
#if !MINIMAL_RUNTIME
2525
'$runtimeKeepalivePush', '$runtimeKeepalivePop'
2626
#endif

src/library_dylink.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ var LibraryDylink = {
9595
},
9696

9797
$updateGOT__internal: true,
98-
$updateGOT__deps: ['$GOT', '$isInternalSym'],
98+
$updateGOT__deps: ['$GOT', '$isInternalSym', '$addFunction'],
9999
$updateGOT: function(exports, replace) {
100100
#if DYLINK_DEBUG
101101
err("updateGOT: adding " + Object.keys(exports).length + " symbols");

src/library_exports.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
mergeInto(LibraryManager.library, {
88
emscripten_get_exported_function__sig: 'pp',
9+
emscripten_get_exported_function__deps: ['$addFunction'],
910
emscripten_get_exported_function: function(name) {
1011
name = UTF8ToString(name);
1112
// Wasm backend does not use C name mangling on exports,

src/modules.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ global.LibraryManager = {
3838
let libraries = [
3939
'library.js',
4040
'library_int53.js',
41+
'library_addfunction.js',
4142
'library_formatString.js',
4243
'library_getvalue.js',
4344
'library_math.js',
@@ -441,8 +442,6 @@ function exportRuntime() {
441442
'getFunctionTables',
442443
'alignFunctionTables',
443444
'registerFunctions',
444-
'addFunction',
445-
'removeFunction',
446445
'prettyPrint',
447446
'getCompilerSetting',
448447
'print',
@@ -510,6 +509,7 @@ function exportRuntime() {
510509
runtimeElements.push('intArrayFromBase64');
511510
runtimeElements.push('tryParseAsDataURI');
512511
}
512+
513513
// dynCall_* methods are not hardcoded here, as they
514514
// depend on the file being compiled. check for them
515515
// and add them.

src/preamble_minimal.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ if (Module['doWasm2JS']) {
4949
Module['wasm'] = base64Decode('<<< WASM_BINARY_DATA >>>');
5050
#endif
5151

52-
#include "runtime_functions.js"
5352
#include "runtime_strings.js"
5453

5554
var HEAP8, HEAP16, HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32, HEAPF64,

0 commit comments

Comments
 (0)