From 46d862336fa042f75ac6a2813159b52e2432f4ce Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 5 Oct 2026 21:33:30 +0900 Subject: [PATCH 1/2] fix(wasm): track reexport bindings beyond the static capacity The Wasm parser stops recording bindings when its fixed array fills, so later Babel-style reexports disappear from the result. Overflow bindings use the existing per-source analysis memory, and files within the static capacity retain the existing storage path. Signed-off-by: Ruben Bridgewater --- lib/lexer.wasm | Bin 22419 -> 22634 bytes src/lexer.c | 42 ++++++++++++++++++++++++++++++++++++++---- test/_unit.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/lib/lexer.wasm b/lib/lexer.wasm index dc291380c4735361e2b391d5795f8db0ea8131fd..7d819ddacdc893e9bf76e44c5a1550c25be0e126 100755 GIT binary patch delta 4917 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 = bPos + 1; starExportStack->id_end = id_end + 1; while (*bPos == ' ' && bPos > source) @@ -306,7 +313,23 @@ void tryBacktrackAddStarExportBinding (uint16_t* bPos) { break; default: return; } - starExportStack++; + if (starExportStack != STAR_EXPORT_STACK_END) { + starExportStack++; + } + else { + // A require declaration reserves more analysis space than this record consumes. + const uintptr_t alignment = _Alignof(StarExportOverflow); + const uintptr_t address = ((uintptr_t)analysis_head + alignment - 1) & ~(alignment - 1); + StarExportOverflow* overflow = (StarExportOverflow*)address; + analysis_head = (void*)(address + sizeof(StarExportOverflow)); + overflow->binding = *starExportStack; + overflow->next = NULL; + if (lastStarExportOverflow) + lastStarExportOverflow->next = overflow; + else + firstStarExportOverflow = overflow; + lastStarExportOverflow = overflow; + } } } } @@ -858,6 +881,17 @@ void tryParseObjectDefineOrKeys (bool keys) { } curCheckBinding++; } + StarExportOverflow* overflow = firstStarExportOverflow; + while (overflow) { + StarExportBinding* binding = &overflow->binding; + if (id_len == binding->id_end - binding->id_start && + 0 == memcmp(id_start, binding->id_start, id_len * sizeof(uint16_t))) { + addReexport(binding->specifier_start, binding->specifier_end); + pos = revertPos; + return; + } + overflow = overflow->next; + } return; } } diff --git a/test/_unit.js b/test/_unit.js index 719d922..433bb57 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -88,6 +88,50 @@ suite('Lexer', () => { } }); + test('tracks reexport bindings beyond the static capacity', () => { + for (const count of [254, 255, 256, 257, 512, 4096]) { + let source = ''; + for (let index = 0; index < count; index++) { + source += `var dep${index} = require("dep${index}");\n`; + } + for (const index of [0, 254, count - 1]) { + if (index >= count) continue; + const reexport = `Object.keys(dep${index}).forEach(function (key) {` + + ` if (key === "default" || key === "__esModule") return; exports[key] = dep${index}[key]; });`; + assert.deepStrictEqual(parse(source + reexport), { + exports: [], reexports: [`dep${index}`] + }); + } + assert.deepStrictEqual(parse('exports.recovery = 1;'), { + exports: ['recovery'], reexports: [] + }); + assert.throws(() => parse(source + 'const value = "open'), { name: 'Error' }); + assert.deepStrictEqual(parse('exports.recovery = 1;'), { + exports: ['recovery'], reexports: [] + }); + } + }); + + test('shares analysis space between overflow bindings and results', () => { + let source = ''; + for (let index = 0; index < 2048; index++) { + source += 'var a=require("");exports.value=1;__exportStar(require("side"),exports);'; + } + const reexport = 'Object.keys(last).forEach(function (key) {' + + ' if (key === "default" || key === "__esModule") return; exports[key] = last[key]; });'; + source += 'var last=require("last");' + reexport; + assert.deepStrictEqual(parse(source), { exports: ['value'], reexports: ['side', 'last'] }); + const hiddenBindings = '/*' + source.slice(2, source.length - reexport.length - 2) + '*/' + reexport; + assert.deepStrictEqual(parse(hiddenBindings), { + exports: [], reexports: [] + }); + parse(source); + const missingBinding = '/*' + 'x'.repeat(source.length * 2) + '*/' + reexport; + assert.deepStrictEqual(parse(missingBinding), { + exports: [], reexports: [] + }); + }); + test('automatic Wasm initialization', async () => { const lexer = await import('../dist/lexer.mjs?automatic-initialization'); From 0cf614804e5b2351028717890296e284e4b65666 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sun, 11 Oct 2026 12:34:02 +0900 Subject: [PATCH 2/2] refactor(wasm): use one reexport binding list A minimal declaration reserves 34 analysis bytes for a 20-byte Wasm binding record. This lets the existing source allocation hold bindings without a separate overflow representation. Signed-off-by: Ruben Bridgewater --- include-wasm/cjs-module-lexer.h | 4 +- lib/lexer.wasm | Bin 22634 -> 22426 bytes src/lexer.c | 72 ++++++++----------------- test/_unit.js | 92 ++++++++++++++++++++++---------- 4 files changed, 89 insertions(+), 79 deletions(-) diff --git a/include-wasm/cjs-module-lexer.h b/include-wasm/cjs-module-lexer.h index ab2adca..54da2f0 100755 --- a/include-wasm/cjs-module-lexer.h +++ b/include-wasm/cjs-module-lexer.h @@ -21,6 +21,7 @@ struct StarExportBinding { const uint16_t* specifier_end; const uint16_t* id_start; const uint16_t* id_end; + struct StarExportBinding* next; }; typedef struct StarExportBinding StarExportBinding; @@ -43,7 +44,8 @@ const uint16_t* sa (uint32_t utf16Len) { const uint16_t* sourceEnd = source + utf16Len + 1; // ensure source is null terminated *(uint16_t*)(source + utf16Len) = '\0'; - analysis_base = (void*)sourceEnd; + const uintptr_t alignment = _Alignof(Slice); + analysis_base = (void*)(((uintptr_t)sourceEnd + alignment - 1) & ~(alignment - 1)); analysis_head = analysis_base; first_export = NULL; export_write_head = NULL; diff --git a/lib/lexer.wasm b/lib/lexer.wasm index 7d819ddacdc893e9bf76e44c5a1550c25be0e126..4673ec28b0a9d63f8b5a2f9d85dbbad6e993ddc8 100755 GIT binary patch delta 6042 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 delta 6351 zcmb7ITWnlM8J;=k?0W07+nglcIKIR_yA6qR^V$x!6FZk3T-(8c7Lg*<7E$_u1P&lY zY9v%pod*>4A)<)}iGXOe4+yDT6eAbW2P6y%B1o+gDIf(kRRW|CLX=uno}f+m{(ol9 z9(&iJC|U39%s>DAI^RDhZ_UfoKl9FdXU=`)vD$a#;KL#ou~Zj&)nac{3GL56_Nq5h zKd<+yEA(@nz_t=2o=||G@9+&N{W-6y&e!ypWju4fCSzYcJc}c*8bzvax*%{S>C=a6 z9-Tx{$!(N85Z^kDKjrCT1#l9zT5x??mN)UG*dv0knYA480wvdkL@@9DA!~5!x4dnS zG?KAYOEM}>7CgGL)!*c<%=n!<4Og*9s9Gk=BpcX&TTB<2Rl0lH@1JX=Q5NbwF&$K6 zq5k8bS<<5==~ca|Pr7m90+=-b;0r9z;{^}eU=`s~$@73GDh7OqHvA~Uov|siqc#|Z zof=83fLJ>c?0$MxS$vj-(A0%V8-uX;#3Tq-q$mkk@CUv4u~v$_WKI7i=)CrVa34z>QW^M9 zXDi(V&kpvi^b&yVEK~)AFK72oIe;}Gl!==nFG3Mkg<2xkC!w&VUW37+H(IuOHyTv8 zb$81d66svafev7_pk)u|>;tif3k%uYFIx%f*XuUY*Pqv2p|6GY+lN^!l{a3k`r!w8 zk96QL89GsviAAX1fq#&x^}4nlgy3M?N&5PITMy9&sXCq{YV^Ifb^>ZIerbq+0<|t7 zmbhlxnwc03+;o(FUhL^NxV!7u*xjtCjzRS|cjbfP7Rye~UP%S9-nXG+qEWogL~*e| zEX(K0A}SjhuwjE z^Y(j18d8_+{uTR;jK%O*&37!roKUyPGr}Uorv?72v$Qy>i&IyTnbgY zj?W^?HxEynzs^e}NOdBnRCx-ZKP67+2U#C_JzAzPCGU@AoPmXA%K>ei1gs#W-*Su7 zv=|elE~CRG`fdm8>!83mf8`d7HGRA9DYE0CZJ+ajOaExwD_P?odS?5<$FT$-u{Tu+ zLZXHIVlr_(K#qcqyzfrSLY;^x-Q}Z#-HnBkv&7DcPfB^w*s{9hOP(V^jCEZ7fV8JY_ga51>A8ugAPNyuPCZ#LR!Ee-zLY$ibkB5s!R45i(LYMIUc0h4@$ za>)n8-4*@SftW1%(!d}mHKAHlcw;YBw}=QArab%(SMCgS4bYZQS#t#}qN;LbS*AcR z^LNM0n+M89dCv^CkK;BZn=1Hmzy&k3NgWQv;;h7iWasutXP$>R`N$HSY*G6#n{D3vU8 z^~6mwxTB8`Z)V$No$N8#q!XvzL|yN@0}g89KiL6(%YVWr@;rj0Y*5A2Mxx9ZFWhel zI6V5$a74^*AK_N=@W=~O#B;0KU@pYjRXBp%H06|HiG^D;+VhMN-*r=P@oIx`2mpi+ z*~iCU(;E_VS!X1UnKJIJ1C}4q#?HRd-{?4BDM=TFq-L}*ZtzDz`$wCHx z{+7c@92j=ks7>vzYkP~GfLj5ptOJmDex3wP5^)MkCT>Z2Pp(a(&bbxd@INja3EkyX9$Jkdx2Jr-{t20>eY2R;?OW5!aMzCL1IrOX-&8V)QSRqNovc zR6ks&#ztnQ_2mOS6e}=F>E^La)`XfTn}z>2vZLvh(^j;3&YutNY$hX#L4{1_YWsL!s&bnfB3 z760hf6=d}jAA8%Iuj$<=DGE#AfroMhB4uoxJ0TY%R(-CVgMU}fmG&+ffda^VN_!pYb1BqnV+12>_a+{Mkf-o zZzbXRktA1F9{mn!hRc@#9Yh7Sq%R+Ocf!1Bm+=NZg3!PO&W(4@ufoN##tcJQQIxrL z1G(5g9Ltgb?;*QDH4d~lGd?#@5fghPAy7hz+<(AySmtK8!RT1gR~`>*09F31`sOiG z>4+BrZGIHjX_AXWtQ4Mi$WbIMj4bpVXQ}{Xjbj%n?6E(f9xmbyLqDo{+CD+&%nK*wy)|Q$k1~golrh LPk;~oS?c{SW}vf{ diff --git a/src/lexer.c b/src/lexer.c index 8963926..d91a774 100755 --- a/src/lexer.c +++ b/src/lexer.c @@ -6,8 +6,6 @@ const uint16_t __empty_char = '\0'; const uint16_t* EMPTY_CHAR = &__empty_char; // tracked depth of template and brackets #define STACK_DEPTH 2048 -// tracked number of star exports -#define MAX_STAR_EXPORTS 256 const uint16_t* source; bool lastSlashWasDivision; @@ -20,7 +18,6 @@ uint16_t* pos; uint16_t* end; uint16_t* templateStack; uint16_t** openTokenPosStack; -StarExportBinding* starExportStack; bool nextBraceIsClass; uint16_t* lastReexportStart; @@ -36,16 +33,9 @@ uint32_t sourceLen; uint16_t templateStack_[STACK_DEPTH]; uint16_t* openTokenPosStack_[STACK_DEPTH]; bool openClassPosStack[STACK_DEPTH]; -StarExportBinding starExportStack_[MAX_STAR_EXPORTS]; -const StarExportBinding* STAR_EXPORT_STACK_END = &starExportStack_[MAX_STAR_EXPORTS - 1]; - -struct StarExportOverflow { - StarExportBinding binding; - struct StarExportOverflow* next; -}; -typedef struct StarExportOverflow StarExportOverflow; -StarExportOverflow* firstStarExportOverflow; -StarExportOverflow* lastStarExportOverflow; +StarExportBinding starExportBinding_; +StarExportBinding* firstStarExport; +StarExportBinding* lastStarExport; void (*addExport)(const uint16_t*, const uint16_t*); void (*addReexport)(const uint16_t*, const uint16_t*); @@ -96,9 +86,8 @@ uint32_t parseCJS (uint16_t* _source, uint32_t _sourceLen, void (*_addExport)(co error = 0; templateStack = &templateStack_[0]; openTokenPosStack = &openTokenPosStack_[0]; - starExportStack = &starExportStack_[0]; - firstStarExportOverflow = NULL; - lastStarExportOverflow = NULL; + firstStarExport = NULL; + lastStarExport = NULL; nextBraceIsClass = false; pos = (uint16_t*)(source - 1); @@ -298,8 +287,8 @@ void tryBacktrackAddStarExportBinding (uint16_t* bPos) { bPos -= charCodeByteLen(charCode); } if (identifierStart && *bPos == ' ') { - starExportStack->id_start = bPos + 1; - starExportStack->id_end = id_end + 1; + starExportBinding_.id_start = bPos + 1; + starExportBinding_.id_end = id_end + 1; while (*bPos == ' ' && bPos > source) bPos--; switch (*bPos) { @@ -313,23 +302,15 @@ void tryBacktrackAddStarExportBinding (uint16_t* bPos) { break; default: return; } - if (starExportStack != STAR_EXPORT_STACK_END) { - starExportStack++; - } - else { - // A require declaration reserves more analysis space than this record consumes. - const uintptr_t alignment = _Alignof(StarExportOverflow); - const uintptr_t address = ((uintptr_t)analysis_head + alignment - 1) & ~(alignment - 1); - StarExportOverflow* overflow = (StarExportOverflow*)address; - analysis_head = (void*)(address + sizeof(StarExportOverflow)); - overflow->binding = *starExportStack; - overflow->next = NULL; - if (lastStarExportOverflow) - lastStarExportOverflow->next = overflow; - else - firstStarExportOverflow = overflow; - lastStarExportOverflow = overflow; - } + StarExportBinding* binding = (StarExportBinding*)analysis_head; + analysis_head += sizeof(StarExportBinding); + *binding = starExportBinding_; + binding->next = NULL; + if (lastStarExport) + lastStarExport->next = binding; + else + firstStarExport = binding; + lastStarExport = binding; } } } @@ -871,26 +852,15 @@ void tryParseObjectDefineOrKeys (bool keys) { // this is done last because it is a linear search through all require bindings // and by doing this search last after verifying the structure match, // we don't have to worry about the slow search algorithm - StarExportBinding* curCheckBinding = &starExportStack_[0]; - while (curCheckBinding != starExportStack) { - if (id_len == curCheckBinding->id_end - curCheckBinding->id_start && - 0 == memcmp(id_start, curCheckBinding->id_start, id_len * sizeof(uint16_t))) { - addReexport(curCheckBinding->specifier_start, curCheckBinding->specifier_end); - pos = revertPos; - return; - } - curCheckBinding++; - } - StarExportOverflow* overflow = firstStarExportOverflow; - while (overflow) { - StarExportBinding* binding = &overflow->binding; + StarExportBinding* binding = firstStarExport; + while (binding) { if (id_len == binding->id_end - binding->id_start && 0 == memcmp(id_start, binding->id_start, id_len * sizeof(uint16_t))) { addReexport(binding->specifier_start, binding->specifier_end); pos = revertPos; return; } - overflow = overflow->next; + binding = binding->next; } return; } @@ -1017,8 +987,8 @@ bool tryParseRequire (enum RequireType requireType) { addReexport(reexportStart, reexportEnd); return true; default: - starExportStack->specifier_start = reexportStart; - starExportStack->specifier_end = reexportEnd; + starExportBinding_.specifier_start = reexportStart; + starExportBinding_.specifier_end = reexportEnd; return true; } } diff --git a/test/_unit.js b/test/_unit.js index 433bb57..cc6cbfc 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -17,6 +17,25 @@ async function loadParser () { } } +/** + * @param {string} identifier + */ +function createStarReexport (identifier) { + return `Object.keys(${identifier}).forEach(function (key) {` + + ` if (key === "default" || key === "__esModule") return; exports[key] = ${identifier}[key]; });`; +} + +/** + * @param {number} count + */ +function createRequireDeclarations (count) { + let source = ''; + for (let index = 0; index < count; index++) { + source += `var dep${index} = require("dep${index}");\n`; + } + return source; +} + suite('Lexer', () => { suiteSetup(async () => await loadParser()); @@ -88,50 +107,69 @@ suite('Lexer', () => { } }); - test('tracks reexport bindings beyond the static capacity', () => { - for (const count of [254, 255, 256, 257, 512, 4096]) { - let source = ''; - for (let index = 0; index < count; index++) { - source += `var dep${index} = require("dep${index}");\n`; - } + test('tracks reexports across small and large binding sets', () => { + for (const count of [1, 2, 254, 255, 256, 257, 512, 4096]) { + const source = createRequireDeclarations(count); for (const index of [0, 254, count - 1]) { if (index >= count) continue; - const reexport = `Object.keys(dep${index}).forEach(function (key) {` + - ` if (key === "default" || key === "__esModule") return; exports[key] = dep${index}[key]; });`; - assert.deepStrictEqual(parse(source + reexport), { + assert.deepStrictEqual(parse(source + createStarReexport(`dep${index}`)), { exports: [], reexports: [`dep${index}`] }); } - assert.deepStrictEqual(parse('exports.recovery = 1;'), { - exports: ['recovery'], reexports: [] + assert.deepStrictEqual(parse(source + createStarReexport('absent')), { + exports: [], reexports: [] }); - assert.throws(() => parse(source + 'const value = "open'), { name: 'Error' }); - assert.deepStrictEqual(parse('exports.recovery = 1;'), { - exports: ['recovery'], reexports: [] + } + }); + + test('tracks each require declaration form after a large binding set', () => { + const source = createRequireDeclarations(256); + for (const keyword of ['var', 'let', 'const']) { + assert.deepStrictEqual(parse(source + `${keyword} last=require("last");` + createStarReexport('last')), { + exports: [], reexports: ['last'] }); } + assert.deepStrictEqual(parse(source + 'var last=_interopRequireWildcard(require("last"));' + + createStarReexport('last')), { exports: [], reexports: ['last'] }); }); - test('shares analysis space between overflow bindings and results', () => { + test('tracks minimal require declarations with odd and even source lengths', () => { + for (const padding of ['', ' ']) { + const source = padding + 'var a=require("");'.repeat(4096) + + 'var last=require("last");' + createStarReexport('last'); + assert.deepStrictEqual(parse(source), { exports: [], reexports: ['last'] }); + } + }); + + test('shares analysis space between bindings and results', () => { let source = ''; for (let index = 0; index < 2048; index++) { source += 'var a=require("");exports.value=1;__exportStar(require("side"),exports);'; } - const reexport = 'Object.keys(last).forEach(function (key) {' + - ' if (key === "default" || key === "__esModule") return; exports[key] = last[key]; });'; - source += 'var last=require("last");' + reexport; + source += 'var last=require("last");' + createStarReexport('last'); assert.deepStrictEqual(parse(source), { exports: ['value'], reexports: ['side', 'last'] }); - const hiddenBindings = '/*' + source.slice(2, source.length - reexport.length - 2) + '*/' + reexport; - assert.deepStrictEqual(parse(hiddenBindings), { - exports: [], reexports: [] - }); - parse(source); - const missingBinding = '/*' + 'x'.repeat(source.length * 2) + '*/' + reexport; - assert.deepStrictEqual(parse(missingBinding), { - exports: [], reexports: [] - }); }); + test('ignores require declarations inside comments', () => { + const source = '/*' + createRequireDeclarations(257) + '*/' + createStarReexport('dep256'); + assert.deepStrictEqual(parse(source), { exports: [], reexports: [] }); + }); + + for (const outcome of ['successful', 'failed']) { + test(`resets require bindings after a ${outcome} parse`, () => { + const declarations = createRequireDeclarations(257); + const reexport = createStarReexport('dep256'); + if (outcome === 'failed') { + assert.throws(() => parse(declarations + 'const value = "open'), { name: 'Error' }); + } else { + assert.deepStrictEqual(parse(declarations + reexport), { exports: [], reexports: ['dep256'] }); + } + const sourceWithoutBindings = '/*' + 'x'.repeat(declarations.length * 2) + '*/' + reexport; + assert.deepStrictEqual(parse(sourceWithoutBindings), { exports: [], reexports: [] }); + assert.deepStrictEqual(parse('exports.recovery = 1;'), { exports: ['recovery'], reexports: [] }); + }); + } + test('automatic Wasm initialization', async () => { const lexer = await import('../dist/lexer.mjs?automatic-initialization');