Skip to content

Optimise code size of addFunction. NFC #24594

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

Merged
merged 9 commits into from
Jun 19, 2025
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
105 changes: 49 additions & 56 deletions src/lib/libaddfunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
addToLibrary({
// This gives correct answers for everything less than 2^{14} = 16384
// I hope nobody is contemplating functions with 16384 arguments...
$uleb128Encode: (n, target) => {
$uleb128EncodeWithLen__internal: true,
$uleb128EncodeWithLen: (arr) => {
const n = arr.length;
#if ASSERTIONS
assert(n < 16384);
#endif
if (n < 128) {
target.push(n);
} else {
target.push((n % 128) | 128, n >> 7);
}
// Note: this LEB128 length encoding produces extra byte for n < 128,
// but we don't care as it's only used in a temporary representation.
return [(n % 128) | 128, n >> 7, ...arr];
},
#if WASM_JS_TYPES
// Converts a signature like 'vii' into a description of the wasm types, like
// { parameters: ['i32', 'i32'], results: [] }.
$sigToWasmTypes__internal: true,
$sigToWasmTypes: (sig) => {
#if ASSERTIONS && !WASM_BIGINT
assert(!sig.includes('j'), 'i64 not permitted in function signatures when WASM_BIGINT is disabled');
Expand Down Expand Up @@ -49,49 +50,41 @@ addToLibrary({
return type;
},
#endif
$generateFuncType__deps: ['$uleb128Encode'],
$generateFuncType: (sig, target) => {
var sigRet = sig.slice(0, 1);
var sigParam = sig.slice(1);
var typeCodes = {
'i': 0x7f, // i32
$wasmTypeCodes__internal: true,
// Note: using template literal here instead of plain object
// because jsify serializes objects w/o quotes and Closure will then
// incorrectly mangle the properties.
$wasmTypeCodes: `{
'i': 0x7f, // i32
#if MEMORY64
'p': 0x7e, // i64
'p': 0x7e, // i64
#else
'p': 0x7f, // i32
'p': 0x7f, // i32
#endif
'j': 0x7e, // i64
'f': 0x7d, // f32
'd': 0x7c, // f64
'e': 0x6f, // externref
};

// Parameters, length + signatures
target.push(0x60 /* form: func */);
uleb128Encode(sigParam.length, target);
for (var paramType of sigParam) {
'j': 0x7e, // i64
'f': 0x7d, // f32
'd': 0x7c, // f64
'e': 0x6f, // externref
}`,

$generateTypePack__internal: true,
$generateTypePack__deps: ['$uleb128EncodeWithLen', '$wasmTypeCodes'],
$generateTypePack: (types) => uleb128EncodeWithLen(Array.from(types, (type) => {
var code = wasmTypeCodes[type];
#if ASSERTIONS
assert(paramType in typeCodes, `invalid signature char: ${paramType}`);
assert(code, `invalid signature char: ${type}`);
#endif
target.push(typeCodes[paramType]);
}
return code;
})),

// Return values, length + signatures
// With no multi-return in MVP, either 0 (void) or 1 (anything else)
if (sigRet == 'v') {
target.push(0x00);
} else {
target.push(0x01, typeCodes[sigRet]);
}
},
// Wraps a JS function as a wasm function with a given signature.
#if !WASM2JS
$convertJsFunctionToWasm__deps: [
'$uleb128Encode',
'$uleb128EncodeWithLen',
#if WASM_JS_TYPES
'$sigToWasmTypes',
#endif
'$generateFuncType'
'$generateTypePack'
],
#endif
$convertJsFunctionToWasm: (func, sig) => {
Expand All @@ -111,25 +104,23 @@ addToLibrary({
return new WebAssembly.Function(sigToWasmTypes(sig), func);
}
#endif
// 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 = [
var bytes = Uint8Array.of(
0x00, 0x61, 0x73, 0x6d, // magic ("\0asm")
0x01, 0x00, 0x00, 0x00, // version: 1
0x01, // Type section code
];
// Write the overall length of the type section followed by the body
uleb128Encode(typeSectionBody.length, bytes);
bytes.push(...typeSectionBody);

// The rest of the module is static
bytes.push(
// The module is static, with the exception of the type section, which is
// generated based on the signature passed in.
...uleb128EncodeWithLen([
0x01, // count: 1
0x60 /* form: func */,
// param types
...generateTypePack(sig.slice(1)),
// return types (for now only supporting [] if `void` and single [T] otherwise)
...generateTypePack(sig[0] === 'v' ? '' : sig[0])
]),
// The rest of the module is static
0x02, 0x07, // import section
// (import "e" "f" (func 0 (type 0)))
0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
Expand All @@ -140,7 +131,7 @@ addToLibrary({

// We can compile this wasm module synchronously because it is very small.
// This accepts an import (at "e.f"), that it reroutes to an export (at "f")
var module = new WebAssembly.Module(new Uint8Array(bytes));
var module = new WebAssembly.Module(bytes);
var instance = new WebAssembly.Instance(module, { 'e': { 'f': func } });
var wrappedFunc = instance.exports['f'];
return wrappedFunc;
Expand All @@ -158,17 +149,19 @@ addToLibrary({
if (freeTableIndexes.length) {
return freeTableIndexes.pop();
}
// Grow the table
#if ASSERTIONS
try {
/** @suppress {checkTypes} */
wasmTable.grow({{{ toIndexType('1') }}});
#endif
// Grow the table
return wasmTable['grow']({{{ toIndexType('1') }}});
#if ASSERTIONS
} catch (err) {
if (!(err instanceof RangeError)) {
throw err;
}
throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.';
}
return {{{ from64Expr('wasmTable.length') }}} - 1;
#endif
},

$updateTableMap__deps: ['$getWasmTableEntry'],
Expand Down
7 changes: 2 additions & 5 deletions src/lib/libexports.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ addToLibrary({
if (name[0] == '_') name = name.slice(1);
var exportedFunc = wasmExports[name];
if (exportedFunc) {
// Record the created function pointer to each function object,
// so that if the same function pointer is obtained several times,
// the same address will be returned.
exportedFunc.ptr ||= addFunction(exportedFunc);
return exportedFunc.ptr;
// Note: addFunction automatically caches the created function pointer.
return addFunction(exportedFunc);
}
#if ASSERTIONS
err(`No exported function found by name "{exportedFunc}"`);
Expand Down
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_O0.gzsize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8337
8319
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_O0.jssize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22420
22389
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.gzsize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
11568
11482
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.jssize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
27314
27075
2 changes: 0 additions & 2 deletions test/other/codesize/test_codesize_minimal_O0.expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -925,8 +925,6 @@ Module['FS_createPreloadedFile'] = FS.createPreloadedFile;
'ASSERTIONS',
'ccall',
'cwrap',
'uleb128Encode',
'generateFuncType',
'convertJsFunctionToWasm',
'getEmptyTableSlot',
'updateTableMap',
Expand Down
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_minimal_O0.gzsize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6632
6614
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_minimal_O0.jssize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
17683
17652
2 changes: 1 addition & 1 deletion test/other/test_unoptimized_code_size.js.size
Original file line number Diff line number Diff line change
@@ -1 +1 @@
54162
54121
2 changes: 1 addition & 1 deletion test/other/test_unoptimized_code_size_strict.js.size
Original file line number Diff line number Diff line change
@@ -1 +1 @@
52212
52171