From 61d66aaa572e54a0d6493dd13adc05488afd5cc8 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Mon, 16 Jun 2025 22:00:24 +0100 Subject: [PATCH 1/8] Optimise code size of addFunction. NFC --- src/lib/libaddfunction.js | 86 ++++++++----------- .../codesize/test_codesize_hello_O0.gzsize | 2 +- .../codesize/test_codesize_hello_O0.jssize | 2 +- .../test_codesize_hello_dylink.gzsize | 2 +- .../test_codesize_hello_dylink.jssize | 2 +- .../test_codesize_minimal_O0.expected.js | 5 +- .../codesize/test_codesize_minimal_O0.gzsize | 2 +- .../codesize/test_codesize_minimal_O0.jssize | 2 +- test/other/test_unoptimized_code_size.js.size | 2 +- .../test_unoptimized_code_size_strict.js.size | 2 +- 10 files changed, 46 insertions(+), 61 deletions(-) diff --git a/src/lib/libaddfunction.js b/src/lib/libaddfunction.js index 6dcd2fdc86575..ecc448eb45f34 100644 --- a/src/lib/libaddfunction.js +++ b/src/lib/libaddfunction.js @@ -7,15 +7,14 @@ 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: (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 @@ -49,49 +48,36 @@ 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: { + '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 - }; + '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) { + $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) => { @@ -111,25 +97,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, @@ -140,7 +124,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; diff --git a/test/other/codesize/test_codesize_hello_O0.gzsize b/test/other/codesize/test_codesize_hello_O0.gzsize index 1d7bdf0051254..a665dbc2e401e 100644 --- a/test/other/codesize/test_codesize_hello_O0.gzsize +++ b/test/other/codesize/test_codesize_hello_O0.gzsize @@ -1 +1 @@ -8337 +8347 diff --git a/test/other/codesize/test_codesize_hello_O0.jssize b/test/other/codesize/test_codesize_hello_O0.jssize index ae2d95929cd70..069e415a5e169 100644 --- a/test/other/codesize/test_codesize_hello_O0.jssize +++ b/test/other/codesize/test_codesize_hello_O0.jssize @@ -1 +1 @@ -22420 +22441 diff --git a/test/other/codesize/test_codesize_hello_dylink.gzsize b/test/other/codesize/test_codesize_hello_dylink.gzsize index b1a76a5094248..fd188d8227069 100644 --- a/test/other/codesize/test_codesize_hello_dylink.gzsize +++ b/test/other/codesize/test_codesize_hello_dylink.gzsize @@ -1 +1 @@ -11562 +11535 diff --git a/test/other/codesize/test_codesize_hello_dylink.jssize b/test/other/codesize/test_codesize_hello_dylink.jssize index fb51bc599279b..a50b24c46a189 100644 --- a/test/other/codesize/test_codesize_hello_dylink.jssize +++ b/test/other/codesize/test_codesize_hello_dylink.jssize @@ -1 +1 @@ -27310 +27210 diff --git a/test/other/codesize/test_codesize_minimal_O0.expected.js b/test/other/codesize/test_codesize_minimal_O0.expected.js index f5ebe4cceb97c..c6bec47cf7d89 100644 --- a/test/other/codesize/test_codesize_minimal_O0.expected.js +++ b/test/other/codesize/test_codesize_minimal_O0.expected.js @@ -925,8 +925,8 @@ Module['FS_createPreloadedFile'] = FS.createPreloadedFile; 'ASSERTIONS', 'ccall', 'cwrap', - 'uleb128Encode', - 'generateFuncType', + 'uleb128EncodeWithLen', + 'generateTypePack', 'convertJsFunctionToWasm', 'getEmptyTableSlot', 'updateTableMap', @@ -1065,6 +1065,7 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'readEmAsmArgsArray', 'wasmTable', 'noExitRuntime', + 'wasmTypeCodes', 'freeTableIndexes', 'functionsInTableMap', 'setValue', diff --git a/test/other/codesize/test_codesize_minimal_O0.gzsize b/test/other/codesize/test_codesize_minimal_O0.gzsize index 50b4cb68cc3dd..b853b07327478 100644 --- a/test/other/codesize/test_codesize_minimal_O0.gzsize +++ b/test/other/codesize/test_codesize_minimal_O0.gzsize @@ -1 +1 @@ -6632 +6642 diff --git a/test/other/codesize/test_codesize_minimal_O0.jssize b/test/other/codesize/test_codesize_minimal_O0.jssize index 69a914f0d2d8f..f597ac149bf7b 100644 --- a/test/other/codesize/test_codesize_minimal_O0.jssize +++ b/test/other/codesize/test_codesize_minimal_O0.jssize @@ -1 +1 @@ -17683 +17704 diff --git a/test/other/test_unoptimized_code_size.js.size b/test/other/test_unoptimized_code_size.js.size index 2813a74486589..7da14654133b4 100644 --- a/test/other/test_unoptimized_code_size.js.size +++ b/test/other/test_unoptimized_code_size.js.size @@ -1 +1 @@ -54162 +54188 diff --git a/test/other/test_unoptimized_code_size_strict.js.size b/test/other/test_unoptimized_code_size_strict.js.size index 7228df63718d7..4c921f9c39c6b 100644 --- a/test/other/test_unoptimized_code_size_strict.js.size +++ b/test/other/test_unoptimized_code_size_strict.js.size @@ -1 +1 @@ -52212 +52238 From 71cd88c4d3ae96be675e4503e111cfa02ef05515 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 17 Jun 2025 18:50:07 +0100 Subject: [PATCH 2/8] Return wasmTable.grow --- src/lib/libaddfunction.js | 4 +--- test/other/codesize/test_codesize_hello_dylink.gzsize | 2 +- test/other/codesize/test_codesize_hello_dylink.jssize | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/libaddfunction.js b/src/lib/libaddfunction.js index ecc448eb45f34..a4430a9947170 100644 --- a/src/lib/libaddfunction.js +++ b/src/lib/libaddfunction.js @@ -144,15 +144,13 @@ addToLibrary({ } // Grow the table try { - /** @suppress {checkTypes} */ - wasmTable.grow({{{ toIndexType('1') }}}); + return wasmTable.grow({{{ toIndexType('1') }}}); } catch (err) { if (!(err instanceof RangeError)) { throw err; } throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.'; } - return {{{ from64Expr('wasmTable.length') }}} - 1; }, $updateTableMap__deps: ['$getWasmTableEntry'], diff --git a/test/other/codesize/test_codesize_hello_dylink.gzsize b/test/other/codesize/test_codesize_hello_dylink.gzsize index fd188d8227069..8d0c88f7e07b3 100644 --- a/test/other/codesize/test_codesize_hello_dylink.gzsize +++ b/test/other/codesize/test_codesize_hello_dylink.gzsize @@ -1 +1 @@ -11535 +11533 diff --git a/test/other/codesize/test_codesize_hello_dylink.jssize b/test/other/codesize/test_codesize_hello_dylink.jssize index a50b24c46a189..74ac479ffe0b3 100644 --- a/test/other/codesize/test_codesize_hello_dylink.jssize +++ b/test/other/codesize/test_codesize_hello_dylink.jssize @@ -1 +1 @@ -27210 +27199 From 1a874597ffac4c975b987ef8a0cbb09aa8b82a70 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 17 Jun 2025 18:50:30 +0100 Subject: [PATCH 3/8] Only augment error under ASSERTIONS --- src/lib/libaddfunction.js | 4 ++++ test/other/codesize/test_codesize_hello_dylink.gzsize | 2 +- test/other/codesize/test_codesize_hello_dylink.jssize | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/libaddfunction.js b/src/lib/libaddfunction.js index a4430a9947170..cfb4c64d9b26b 100644 --- a/src/lib/libaddfunction.js +++ b/src/lib/libaddfunction.js @@ -142,15 +142,19 @@ addToLibrary({ if (freeTableIndexes.length) { return freeTableIndexes.pop(); } +#if ASSERTIONS // Grow the table try { +#endif 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.'; } +#endif }, $updateTableMap__deps: ['$getWasmTableEntry'], diff --git a/test/other/codesize/test_codesize_hello_dylink.gzsize b/test/other/codesize/test_codesize_hello_dylink.gzsize index 8d0c88f7e07b3..442aa8c21c8e3 100644 --- a/test/other/codesize/test_codesize_hello_dylink.gzsize +++ b/test/other/codesize/test_codesize_hello_dylink.gzsize @@ -1 +1 @@ -11533 +11477 diff --git a/test/other/codesize/test_codesize_hello_dylink.jssize b/test/other/codesize/test_codesize_hello_dylink.jssize index 74ac479ffe0b3..3899de74d426a 100644 --- a/test/other/codesize/test_codesize_hello_dylink.jssize +++ b/test/other/codesize/test_codesize_hello_dylink.jssize @@ -1 +1 @@ -27199 +27067 From 410bda6833cc94872e571ba339941be044b2c3df Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 17 Jun 2025 19:53:50 +0100 Subject: [PATCH 4/8] Reuse addFunction's caching --- src/lib/libexports.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/libexports.js b/src/lib/libexports.js index 062d70d2a87c0..0ccf93e436249 100644 --- a/src/lib/libexports.js +++ b/src/lib/libexports.js @@ -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}"`); From 9874c9f78e1b1ee34989527d3e49514ce6d0acfb Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 17 Jun 2025 22:10:03 +0100 Subject: [PATCH 5/8] Address review comments --- src/lib/libaddfunction.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/libaddfunction.js b/src/lib/libaddfunction.js index cfb4c64d9b26b..b5e4340076863 100644 --- a/src/lib/libaddfunction.js +++ b/src/lib/libaddfunction.js @@ -7,6 +7,7 @@ addToLibrary({ // This gives correct answers for everything less than 2^{14} = 16384 // I hope nobody is contemplating functions with 16384 arguments... + $uleb128EncodeWithLen__internal: true, $uleb128EncodeWithLen: (arr) => { const n = arr.length; #if ASSERTIONS @@ -19,6 +20,7 @@ addToLibrary({ #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'); @@ -48,6 +50,7 @@ addToLibrary({ return type; }, #endif + $wasmTypeCodes__internal: true, $wasmTypeCodes: { 'i': 0x7f, // i32 #if MEMORY64 @@ -61,11 +64,12 @@ addToLibrary({ 'e': 0x6f, // externref }, + $generateTypePack__internal: true, $generateTypePack__deps: ['$uleb128EncodeWithLen', '$wasmTypeCodes'], - $generateTypePack: (types) => uleb128EncodeWithLen(Array.from(types, type => { + $generateTypePack: (types) => uleb128EncodeWithLen(Array.from(types, (type) => { var code = wasmTypeCodes[type]; #if ASSERTIONS - assert(code, `invalid signature char: ${type}`); + assert(code, `invalid signature char: ${type}`); #endif return code; })), @@ -143,9 +147,9 @@ addToLibrary({ return freeTableIndexes.pop(); } #if ASSERTIONS - // Grow the table try { -#endif + #endif + // Grow the table return wasmTable.grow({{{ toIndexType('1') }}}); #if ASSERTIONS } catch (err) { From 60a3bdfc3bc6cf82605612bd142a6529edf40d19 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 18 Jun 2025 14:38:55 +0100 Subject: [PATCH 6/8] Fix for jsify + Closure --- src/lib/libaddfunction.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/libaddfunction.js b/src/lib/libaddfunction.js index b5e4340076863..ab5088806cb2b 100644 --- a/src/lib/libaddfunction.js +++ b/src/lib/libaddfunction.js @@ -51,7 +51,10 @@ addToLibrary({ }, #endif $wasmTypeCodes__internal: true, - $wasmTypeCodes: { + // 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 @@ -62,7 +65,7 @@ addToLibrary({ 'f': 0x7d, // f32 'd': 0x7c, // f64 'e': 0x6f, // externref - }, + }`, $generateTypePack__internal: true, $generateTypePack__deps: ['$uleb128EncodeWithLen', '$wasmTypeCodes'], From cc9c938abdf7af0c88d9603ac31130dc7fcb78c8 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 18 Jun 2025 15:53:08 +0100 Subject: [PATCH 7/8] Rebaseline --- test/other/codesize/test_codesize_hello_O0.gzsize | 2 +- test/other/codesize/test_codesize_hello_O0.jssize | 2 +- test/other/codesize/test_codesize_hello_dylink.jssize | 2 +- test/other/codesize/test_codesize_minimal_O0.expected.js | 3 --- test/other/codesize/test_codesize_minimal_O0.gzsize | 2 +- test/other/codesize/test_codesize_minimal_O0.jssize | 2 +- test/other/test_unoptimized_code_size.js.size | 2 +- test/other/test_unoptimized_code_size_strict.js.size | 2 +- 8 files changed, 7 insertions(+), 10 deletions(-) diff --git a/test/other/codesize/test_codesize_hello_O0.gzsize b/test/other/codesize/test_codesize_hello_O0.gzsize index a665dbc2e401e..9c8bcfc9f63d7 100644 --- a/test/other/codesize/test_codesize_hello_O0.gzsize +++ b/test/other/codesize/test_codesize_hello_O0.gzsize @@ -1 +1 @@ -8347 +8319 diff --git a/test/other/codesize/test_codesize_hello_O0.jssize b/test/other/codesize/test_codesize_hello_O0.jssize index 069e415a5e169..e5a981de8ef40 100644 --- a/test/other/codesize/test_codesize_hello_O0.jssize +++ b/test/other/codesize/test_codesize_hello_O0.jssize @@ -1 +1 @@ -22441 +22389 diff --git a/test/other/codesize/test_codesize_hello_dylink.jssize b/test/other/codesize/test_codesize_hello_dylink.jssize index 3899de74d426a..9f856a7eecbff 100644 --- a/test/other/codesize/test_codesize_hello_dylink.jssize +++ b/test/other/codesize/test_codesize_hello_dylink.jssize @@ -1 +1 @@ -27067 +27071 diff --git a/test/other/codesize/test_codesize_minimal_O0.expected.js b/test/other/codesize/test_codesize_minimal_O0.expected.js index c6bec47cf7d89..3bcd46da675ff 100644 --- a/test/other/codesize/test_codesize_minimal_O0.expected.js +++ b/test/other/codesize/test_codesize_minimal_O0.expected.js @@ -925,8 +925,6 @@ Module['FS_createPreloadedFile'] = FS.createPreloadedFile; 'ASSERTIONS', 'ccall', 'cwrap', - 'uleb128EncodeWithLen', - 'generateTypePack', 'convertJsFunctionToWasm', 'getEmptyTableSlot', 'updateTableMap', @@ -1065,7 +1063,6 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'readEmAsmArgsArray', 'wasmTable', 'noExitRuntime', - 'wasmTypeCodes', 'freeTableIndexes', 'functionsInTableMap', 'setValue', diff --git a/test/other/codesize/test_codesize_minimal_O0.gzsize b/test/other/codesize/test_codesize_minimal_O0.gzsize index b853b07327478..b411359393386 100644 --- a/test/other/codesize/test_codesize_minimal_O0.gzsize +++ b/test/other/codesize/test_codesize_minimal_O0.gzsize @@ -1 +1 @@ -6642 +6614 diff --git a/test/other/codesize/test_codesize_minimal_O0.jssize b/test/other/codesize/test_codesize_minimal_O0.jssize index f597ac149bf7b..8b7d4198a7095 100644 --- a/test/other/codesize/test_codesize_minimal_O0.jssize +++ b/test/other/codesize/test_codesize_minimal_O0.jssize @@ -1 +1 @@ -17704 +17652 diff --git a/test/other/test_unoptimized_code_size.js.size b/test/other/test_unoptimized_code_size.js.size index 7da14654133b4..9486f81d383c6 100644 --- a/test/other/test_unoptimized_code_size.js.size +++ b/test/other/test_unoptimized_code_size.js.size @@ -1 +1 @@ -54188 +54121 diff --git a/test/other/test_unoptimized_code_size_strict.js.size b/test/other/test_unoptimized_code_size_strict.js.size index 4c921f9c39c6b..85eb753232dcc 100644 --- a/test/other/test_unoptimized_code_size_strict.js.size +++ b/test/other/test_unoptimized_code_size_strict.js.size @@ -1 +1 @@ -52238 +52171 From 0e5a2a89488c127bd9db328f6d48f54f6c822b7f Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 19 Jun 2025 01:32:16 +0100 Subject: [PATCH 8/8] Suppress Closure warning --- src/lib/libaddfunction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/libaddfunction.js b/src/lib/libaddfunction.js index ab5088806cb2b..eaf93f9ef963b 100644 --- a/src/lib/libaddfunction.js +++ b/src/lib/libaddfunction.js @@ -153,7 +153,7 @@ addToLibrary({ try { #endif // Grow the table - return wasmTable.grow({{{ toIndexType('1') }}}); + return wasmTable['grow']({{{ toIndexType('1') }}}); #if ASSERTIONS } catch (err) { if (!(err instanceof RangeError)) {