From b2fd0b8fff1d36587b3ae1209f91f8be40a7071c Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sat, 29 Aug 2026 00:46:58 +0200 Subject: [PATCH 1/2] perf: speed up UTF-16 source copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Node 24.20.0, the eight-file corpus improves from 13,921.11 to 10,368.47 µs per sweep. The corpus contains 3,629,072 UTF-16 code units, with seven trials excluding the best and worst results. A 128-code-unit cutoff preserves the loop where Buffer setup regresses supported older runtimes. --- src/lexer.js | 7 ++++++- test/_unit.js | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/lexer.js b/src/lexer.js index 30bca50..58f28c8 100755 --- a/src/lexer.js +++ b/src/lexer.js @@ -1,6 +1,7 @@ let wasm; const isLE = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1; +const hasBuffer = typeof Buffer !== 'undefined'; export function parse (source, name = '@') { if (!wasm) @@ -14,7 +15,11 @@ export function parse (source, name = '@') { wasm.memory.grow(Math.ceil(extraMem / 65536)); const addr = wasm.sa(len); - (isLE ? copyLE : copyBE)(source, new Uint16Array(wasm.memory.buffer, addr, len)); + // Buffer setup is slower than the loop for short sources on supported Node.js releases. + if (source.length >= 128 && hasBuffer) + Buffer.from(wasm.memory.buffer, addr, (len - 1) * 2).write(source, 'utf16le'); + else + (isLE ? copyLE : copyBE)(source, new Uint16Array(wasm.memory.buffer, addr, len)); const err_code = wasm.parseCJS(addr, source.length, 0, 0, 0); diff --git a/test/_unit.js b/test/_unit.js index 0458f17..89cec51 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -758,6 +758,23 @@ suite('Lexer', () => { }); }); + test('browser UTF-16 source copy', async () => { + const originalBuffer = global.Buffer; + global.Buffer = undefined; + let browserLexer; + try { + browserLexer = await import('../dist/lexer.mjs?browser-utf16-copy'); + } + finally { + global.Buffer = originalBuffer; + } + browserLexer.initSync(); + const source = `const value = '${'𓀀'.repeat(40000)}'; exports.value = value`; + const { exports, reexports } = browserLexer.parse(source); + assert.deepStrictEqual(exports, ['value']); + assert.deepStrictEqual(reexports, []); + }); + test('Simple import', () => { const source = ` import test from "test"; From 3b0966ba8af3e2c0babe43cdc7da6c6769747725 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 31 Aug 2026 12:38:09 +0200 Subject: [PATCH 2/2] perf: use Buffer for larger UTF-16 copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Node.js 22.23.1 with V8 12.4.254.21-node.56, parsing the eight-file sample corpus improved from 15,122 to 11,180 μs per sweep and from 15,093 to 11,184 μs in a fresh process. --- src/lexer.js | 4 ++-- test/_unit.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/lexer.js b/src/lexer.js index 58f28c8..6655280 100755 --- a/src/lexer.js +++ b/src/lexer.js @@ -15,8 +15,8 @@ export function parse (source, name = '@') { wasm.memory.grow(Math.ceil(extraMem / 65536)); const addr = wasm.sa(len); - // Buffer setup is slower than the loop for short sources on supported Node.js releases. - if (source.length >= 128 && hasBuffer) + // Buffer setup is slower than the loop for short sources. + if (source.length >= 64 && hasBuffer) Buffer.from(wasm.memory.buffer, addr, (len - 1) * 2).write(source, 'utf16le'); else (isLE ? copyLE : copyBE)(source, new Uint16Array(wasm.memory.buffer, addr, len)); diff --git a/test/_unit.js b/test/_unit.js index 89cec51..4f989a4 100755 --- a/test/_unit.js +++ b/test/_unit.js @@ -775,6 +775,34 @@ suite('Lexer', () => { assert.deepStrictEqual(reexports, []); }); + test('Node.js UTF-16 source copy cutoff', async function testNodeUtf16CopyCutoff () { + const nodeLexer = await import('../dist/lexer.mjs?node-utf16-copy-cutoff'); + nodeLexer.initSync(); + + const originalBuffer = global.Buffer; + let copies = 0; + global.Buffer = { + /** + * @param {ArrayBuffer} arrayBuffer + * @param {number} byteOffset + * @param {number} length + */ + from (arrayBuffer, byteOffset, length) { + copies++; + return originalBuffer.from(arrayBuffer, byteOffset, length); + } + }; + try { + nodeLexer.parse(' '.repeat(63)); + assert.strictEqual(copies, 0); + nodeLexer.parse(' '.repeat(64)); + assert.strictEqual(copies, 1); + } + finally { + global.Buffer = originalBuffer; + } + }); + test('Simple import', () => { const source = ` import test from "test";