Skip to content

Commit 26ffccd

Browse files
authored
Implement addFunction under wasm backend purely in terms of the wasm table (#8255)
This removes the need for any wasm-side `jsCall` thunks. In order to call `addFunction` either ALLOW_MEMORY_GROWTH or RESERVED_FUNCTION_POINTERS is still required. If a JS function is added its signature is still required.
1 parent 9bff7a2 commit 26ffccd

File tree

7 files changed

+164
-116
lines changed

7 files changed

+164
-116
lines changed

emscripten.py

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -665,15 +665,6 @@ def update_settings_glue(metadata):
665665
shared.Settings.MAX_GLOBAL_ALIGN = metadata['maxGlobalAlign']
666666
shared.Settings.IMPLEMENTED_FUNCTIONS = metadata['implementedFunctions']
667667

668-
# addFunction support for Wasm backend
669-
if shared.Settings.WASM_BACKEND and shared.Settings.RESERVED_FUNCTION_POINTERS > 0:
670-
start_index = metadata['jsCallStartIndex']
671-
# e.g. jsCallFunctionType ['v', 'ii'] -> sig2order{'v': 0, 'ii': 1}
672-
sig2order = {sig: i for i, sig in enumerate(metadata['jsCallFuncType'])}
673-
# Index in the Wasm function table in which jsCall thunk function starts
674-
shared.Settings.JSCALL_START_INDEX = start_index
675-
shared.Settings.JSCALL_SIG_ORDER = sig2order
676-
677668
# Extract the list of function signatures that MAIN_THREAD_EM_ASM blocks in
678669
# the compiled code have, each signature will need a proxy function invoker
679670
# generated for it.
@@ -2156,19 +2147,16 @@ def emscript_wasm_backend(infile, outfile, memfile, libraries, compiler_engine,
21562147
pre = None
21572148

21582149
invoke_funcs = metadata.get('invokeFuncs', [])
2159-
# List of function signatures used in jsCall functions, e.g.['v', 'vi']
2160-
jscall_sigs = metadata.get('jsCallFuncType', [])
21612150

21622151
try:
21632152
del forwarded_json['Variables']['globals']['_llvm_global_ctors'] # not a true variable
21642153
except:
21652154
pass
21662155

2167-
sending = create_sending_wasm(invoke_funcs, jscall_sigs, forwarded_json,
2168-
metadata)
2156+
sending = create_sending_wasm(invoke_funcs, forwarded_json, metadata)
21692157
receiving = create_receiving_wasm(all_implemented)
21702158

2171-
module = create_module_wasm(sending, receiving, invoke_funcs, jscall_sigs, metadata)
2159+
module = create_module_wasm(sending, receiving, invoke_funcs, metadata)
21722160

21732161
write_output_file(outfile, post, module)
21742162
module = None
@@ -2205,9 +2193,7 @@ def debug_copy(src, dst):
22052193
debug_copy(base_source_map, 'base_wasm.map')
22062194

22072195
cmd = [wasm_emscripten_finalize, base_wasm, '-o', wasm,
2208-
'--global-base=%s' % shared.Settings.GLOBAL_BASE,
2209-
('--emscripten-reserved-function-pointers=%d' %
2210-
shared.Settings.RESERVED_FUNCTION_POINTERS)]
2196+
'--global-base=%s' % shared.Settings.GLOBAL_BASE]
22112197
if shared.Settings.DEBUG_LEVEL >= 2 or shared.Settings.PROFILING_FUNCS:
22122198
cmd.append('-g')
22132199
if shared.Settings.LEGALIZE_JS_FFI != 1:
@@ -2294,7 +2280,7 @@ def create_em_js(forwarded_json, metadata):
22942280
return em_js_funcs
22952281

22962282

2297-
def create_sending_wasm(invoke_funcs, jscall_sigs, forwarded_json, metadata):
2283+
def create_sending_wasm(invoke_funcs, forwarded_json, metadata):
22982284
basic_funcs = []
22992285
if shared.Settings.SAFE_HEAP:
23002286
basic_funcs += ['segfault', 'alignfault']
@@ -2310,10 +2296,7 @@ def create_sending_wasm(invoke_funcs, jscall_sigs, forwarded_json, metadata):
23102296
library_funcs = set(k for k, v in forwarded_json['Functions']['libraryFunctions'].items() if v != 2)
23112297
global_funcs = list(library_funcs.difference(set(global_vars)).difference(implemented_functions))
23122298

2313-
jscall_funcs = ['jsCall_' + sig for sig in jscall_sigs]
2314-
2315-
send_items = (basic_funcs + invoke_funcs + jscall_funcs + global_funcs +
2316-
basic_vars + global_vars)
2299+
send_items = (basic_funcs + invoke_funcs + global_funcs + basic_vars + global_vars)
23172300

23182301
def fix_import_name(g):
23192302
if g.startswith('Math_'):
@@ -2361,9 +2344,8 @@ def create_receiving_wasm(exports):
23612344
return '\n'.join(receiving) + '\n'
23622345

23632346

2364-
def create_module_wasm(sending, receiving, invoke_funcs, jscall_sigs, metadata):
2347+
def create_module_wasm(sending, receiving, invoke_funcs, metadata):
23652348
invoke_wrappers = create_invoke_wrappers(invoke_funcs)
2366-
jscall_funcs = create_jscall_funcs(jscall_sigs)
23672349

23682350
module = []
23692351
module.append('var asmGlobalArg = {};\n')
@@ -2375,7 +2357,6 @@ def create_module_wasm(sending, receiving, invoke_funcs, jscall_sigs, metadata):
23752357

23762358
module.append(receiving)
23772359
module.append(invoke_wrappers)
2378-
module.append(jscall_funcs)
23792360
return module
23802361

23812362

@@ -2392,8 +2373,6 @@ def load_metadata_wasm(metadata_raw, DEBUG):
23922373
'externs': [],
23932374
'simd': False,
23942375
'maxGlobalAlign': 0,
2395-
'jsCallStartIndex': 0,
2396-
'jsCallFuncType': [],
23972376
'staticBump': 0,
23982377
'tableSize': 0,
23992378
'initializers': [],
@@ -2443,13 +2422,6 @@ def create_invoke_wrappers(invoke_funcs):
24432422
return invoke_wrappers
24442423

24452424

2446-
def create_jscall_funcs(sigs):
2447-
jscall_funcs = ''
2448-
for i, sig in enumerate(sigs):
2449-
jscall_funcs += '\n' + shared.JS.make_jscall(sig, i) + '\n'
2450-
return jscall_funcs
2451-
2452-
24532425
def treat_as_user_function(name):
24542426
library_functions_in_module = ('setTempRet0', 'getTempRet0', 'stackAlloc',
24552427
'stackSave', 'stackRestore',

src/library.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2215,7 +2215,7 @@ LibraryManager.library = {
22152215
// Insert the function into the wasm table. Since we know the function
22162216
// comes directly from the loaded wasm module we can insert it directly
22172217
// into the table, avoiding any JS interaction.
2218-
return addWasmFunction(result);
2218+
return addFunctionWasm(result);
22192219
#else
22202220
// convert the exported function into a function pointer using our generic
22212221
// JS mechanism.

src/preamble.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,11 @@ Module['asm'] = function(global, env, providedBuffer) {
10191019
env['table'] = wasmTable = new WebAssembly.Table({
10201020
'initial': {{{ getQuoted('WASM_TABLE_SIZE') }}},
10211021
#if !ALLOW_TABLE_GROWTH
1022+
#if WASM_BACKEND
1023+
'maximum': {{{ getQuoted('WASM_TABLE_SIZE') }}} + {{{ RESERVED_FUNCTION_POINTERS }}},
1024+
#else
10221025
'maximum': {{{ getQuoted('WASM_TABLE_SIZE') }}},
1026+
#endif
10231027
#endif // WASM_BACKEND
10241028
'element': 'anyfunc'
10251029
});

src/settings.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,7 @@ var ALIASING_FUNCTION_POINTERS = 0;
281281
// By default we use a wasm Table for function pointers, which is fast and
282282
// efficient. When enabling emulation, we also use the Table *outside* the wasm
283283
// module, exactly as when emulating in asm.js, just replacing the plain JS
284-
// array with a Table. However, Tables have some limitations currently, like not
285-
// being able to assign an arbitrary JS method to them, which we have yet to
286-
// work around.
284+
// array with a Table.
287285
var EMULATED_FUNCTION_POINTERS = 0;
288286

289287
// Allows function pointers to be cast, wraps each call of an incorrect type
@@ -1153,8 +1151,6 @@ var PTHREADS_DEBUG = 0;
11531151

11541152
var MAX_GLOBAL_ALIGN = -1; // received from the backend
11551153
var IMPLEMENTED_FUNCTIONS = []; // received from the backend
1156-
var JSCALL_START_INDEX = 0; // received from the backend
1157-
var JSCALL_SIG_ORDER = {}; // received from the backend
11581154

11591155
// Duplicate function elimination. This coalesces function bodies that are
11601156
// identical, which can happen e.g. if two methods have different C/C++ or LLVM

src/support.js

Lines changed: 117 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -599,48 +599,128 @@ Module['registerFunctions'] = registerFunctions;
599599
#endif // RELOCATABLE
600600
#endif // EMULATED_FUNCTION_POINTERS
601601

602-
#if EMULATED_FUNCTION_POINTERS == 0
603-
#if WASM_BACKEND && RESERVED_FUNCTION_POINTERS
604-
var jsCallStartIndex = {{{ JSCALL_START_INDEX }}};
605-
var jsCallSigOrder = {{{ JSON.stringify(JSCALL_SIG_ORDER) }}};
606-
var jsCallNumSigs = Object.keys(jsCallSigOrder).length;
607-
var functionPointers = new Array(jsCallNumSigs * {{{ RESERVED_FUNCTION_POINTERS }}});
608-
#else // WASM_BACKEND && RESERVED_FUNCTION_POINTERS == 0
602+
#if !WASM_BACKEND && EMULATED_FUNCTION_POINTERS == 0
609603
var jsCallStartIndex = 1;
610604
var functionPointers = new Array({{{ RESERVED_FUNCTION_POINTERS }}});
611-
#endif // WASM_BACKEND && RESERVED_FUNCTION_POINTERS
612-
#endif // EMULATED_FUNCTION_POINTERS == 0
605+
#endif // !WASM_BACKEND && EMULATED_FUNCTION_POINTERS == 0
613606

614607
#if WASM
608+
// Wraps a JS function as a wasm function with a given signature.
609+
// In the future, we may get a WebAssembly.Function constructor. Until then,
610+
// we create a wasm module that takes the JS function as an import with a given
611+
// signature, and re-exports that as a wasm function.
612+
function convertJsFunctionToWasm(func, sig) {
613+
// The module is static, with the exception of the type section, which is
614+
// generated based on the signature passed in.
615+
var typeSection = [
616+
0x01, // id: section,
617+
0x00, // length: 0 (placeholder)
618+
0x01, // count: 1
619+
0x60, // form: func
620+
];
621+
var sigRet = sig.slice(0, 1);
622+
var sigParam = sig.slice(1);
623+
var typeCodes = {
624+
'i': 0x7f, // i32
625+
'j': 0x7e, // i64
626+
'f': 0x7d, // f32
627+
'd': 0x7c, // f64
628+
};
629+
630+
// Parameters, length + signatures
631+
typeSection.push(sigParam.length);
632+
for (var i = 0; i < sigParam.length; ++i) {
633+
typeSection.push(typeCodes[sigParam[i]]);
634+
}
635+
636+
// Return values, length + signatures
637+
// With no multi-return in MVP, either 0 (void) or 1 (anything else)
638+
if (sigRet == 'v') {
639+
typeSection.push(0x00);
640+
} else {
641+
typeSection = typeSection.concat([0x01, typeCodes[sigRet]]);
642+
}
643+
644+
// Write the overall length of the type section back into the section header
645+
// (excepting the 2 bytes for the section id and length)
646+
typeSection[1] = typeSection.length - 2;
647+
648+
// Rest of the module is static
649+
var bytes = new Uint8Array([
650+
0x00, 0x61, 0x73, 0x6d, // magic ("\0asm")
651+
0x01, 0x00, 0x00, 0x00, // version: 1
652+
].concat(typeSection, [
653+
0x02, 0x07, // import section
654+
// (import "e" "f" (func 0 (type 0)))
655+
0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
656+
0x07, 0x05, // export section
657+
// (export "f" (func 0 (type 0)))
658+
0x01, 0x01, 0x66, 0x00, 0x00,
659+
]));
660+
661+
// We can compile this wasm module synchronously because it is very small.
662+
// This accepts an import (at "e.f"), that it reroutes to an export (at "f")
663+
var module = new WebAssembly.Module(bytes);
664+
var instance = new WebAssembly.Instance(module, {
665+
e: {
666+
f: func
667+
}
668+
});
669+
var wrappedFunc = instance.exports.f;
670+
return wrappedFunc;
671+
}
672+
615673
// Add a wasm function to the table.
616-
// Attempting to call this with JS function will cause of table.set() to fail
617-
function addWasmFunction(func) {
674+
function addFunctionWasm(func, sig) {
618675
var table = wasmTable;
619676
var ret = table.length;
620-
table.grow(1);
621-
table.set(ret, func);
677+
678+
// Grow the table
679+
try {
680+
table.grow(1);
681+
} catch (err) {
682+
if (!err instanceof RangeError) {
683+
throw err;
684+
}
685+
throw 'Unable to grow wasm table. Use a higher value for RESERVED_FUNCTION_POINTERS or set ALLOW_TABLE_GROWTH.';
686+
}
687+
688+
// Insert new element
689+
try {
690+
// Attempting to call this with JS function will cause of table.set() to fail
691+
table.set(ret, func);
692+
} catch (err) {
693+
if (!err instanceof TypeError) {
694+
throw err;
695+
}
696+
assert(typeof sig !== 'undefined', 'Missing signature argument to addFunction');
697+
var wrapped = convertJsFunctionToWasm(func, sig);
698+
table.set(ret, wrapped);
699+
}
700+
622701
return ret;
623702
}
703+
704+
function removeFunctionWasm(index) {
705+
// TODO(sbc): Look into implementing this to allow re-using of table slots
706+
}
624707
#endif
625708

626-
// 'sig' parameter is currently only used for LLVM backend under certain
627-
// circumstance: RESERVED_FUNCTION_POINTERS=1, EMULATED_FUNCTION_POINTERS=0.
709+
// 'sig' parameter is required for the llvm backend but only when func is not
710+
// already a WebAssembly function.
628711
function addFunction(func, sig) {
629712
#if ASSERTIONS == 2
630713
if (typeof sig === 'undefined') {
631714
err('warning: addFunction(): You should provide a wasm function signature string as a second argument. This is not necessary for asm.js and asm2wasm, but can be required for the LLVM wasm backend, so it is recommended for full portability.');
632715
}
633716
#endif // ASSERTIONS
634717

718+
#if WASM_BACKEND
719+
return addFunctionWasm(func, sig);
720+
#else
721+
635722
#if EMULATED_FUNCTION_POINTERS == 0
636-
#if WASM_BACKEND && RESERVED_FUNCTION_POINTERS
637-
assert(typeof sig !== 'undefined',
638-
'Second argument of addFunction should be a wasm function signature ' +
639-
'string');
640-
var base = jsCallSigOrder[sig] * {{{ RESERVED_FUNCTION_POINTERS }}};
641-
#else // WASM_BACKEND && RESERVED_FUNCTION_POINTERS == 0
642723
var base = 0;
643-
#endif // WASM_BACKEND && RESERVED_FUNCTION_POINTERS
644724
for (var i = base; i < base + {{{ RESERVED_FUNCTION_POINTERS }}}; i++) {
645725
if (!functionPointers[i]) {
646726
functionPointers[i] = func;
@@ -652,13 +732,9 @@ function addFunction(func, sig) {
652732
#else // EMULATED_FUNCTION_POINTERS == 0
653733

654734
#if WASM
655-
// assume we have been passed a wasm function and can add it to the table
656-
// directly.
657-
// TODO(sbc): This assumtion is most likely not valid. Look into ways of
658-
// creating wasm functions based on JS functions as input.
659-
return addWasmFunction(func);
735+
return addFunctionWasm(func, sig);
660736
#else
661-
alignFunctionTables(); // XXX we should rely on this being an invariant
737+
alignFunctionTables(); // TODO: we should rely on this being an invariant
662738
var tables = getFunctionTables();
663739
var ret = -1;
664740
for (var sig in tables) {
@@ -668,21 +744,32 @@ function addFunction(func, sig) {
668744
table.push(func);
669745
}
670746
return ret;
671-
#endif
747+
#endif // WASM
672748

673749
#endif // EMULATED_FUNCTION_POINTERS == 0
750+
#endif // WASM_BACKEND
674751
}
675752

676753
function removeFunction(index) {
754+
#if WASM_BACKEND
755+
removeFunctionWasm(index);
756+
#else
757+
677758
#if EMULATED_FUNCTION_POINTERS == 0
678759
functionPointers[index-jsCallStartIndex] = null;
760+
#else
761+
#if WASM
762+
removeFunctionWasm(index);
679763
#else
680764
alignFunctionTables(); // XXX we should rely on this being an invariant
681765
var tables = getFunctionTables();
682766
for (var sig in tables) {
683767
tables[sig][index] = null;
684768
}
685-
#endif
769+
#endif // WASM
770+
771+
#endif // EMULATE_FUNCTION_POINTER_CASTS == 0
772+
#endif // WASM_BACKEND
686773
}
687774

688775
var funcWrappers = {};

0 commit comments

Comments
 (0)