From 3395d051a9032c85184310a4bef332870da74a5e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 31 May 2026 15:31:23 -0700 Subject: [PATCH] fix(parser): WASM scratch layout and scan cache for chunked parse Place parser state above the linked transition table so scans emit ops correctly, and preserve or clear the WASM scan cache across nested and reused parse buffers. Co-authored-by: Cursor --- src/common/parser/EscapeSequenceParserWasm.ts | 30 ++++++++++++++---- src/common/parser/wasm/parser.c | 5 +-- src/common/parser/wasm/parser.wasm | Bin 15166 -> 15166 bytes src/common/parser/wasm/parser.wasm.embed.ts | 2 +- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/common/parser/EscapeSequenceParserWasm.ts b/src/common/parser/EscapeSequenceParserWasm.ts index 3fcbf9f62..3903b6102 100644 --- a/src/common/parser/EscapeSequenceParserWasm.ts +++ b/src/common/parser/EscapeSequenceParserWasm.ts @@ -71,6 +71,13 @@ function loadParams(host: IWasmParseHost, scan: ScanResult, opIndex: number): vo } } +/** True when a chunked WASM scan of `data` is still in progress (e.g. nested parse during print). */ +function isActiveWasmParse(scanCache: IWasmScanCache, data: Uint32Array, length: number): boolean { + return scanCache.data === data + && (scanCache.inputOffset ?? 0) < length + && (scanCache.scan !== undefined || scanCache.chunkStart !== undefined); +} + export function parseWithWasmScanner( host: IWasmParseHost, data: Uint32Array, @@ -85,10 +92,14 @@ export function parseWithWasmScanner( return resumeWasmParse(host, data, length, promiseResult, scanCache); } - scanCache.stateBeforeScan = host.currentState; - scanCache.inputOffset = 0; - scanCache.scan = undefined; - scanCache.opIndex = 0; + if (!isActiveWasmParse(scanCache, data, length)) { + scanCache.stateBeforeScan = host.currentState; + scanCache.inputOffset = 0; + scanCache.scan = undefined; + scanCache.opIndex = 0; + scanCache.data = undefined; + scanCache.chunkStart = undefined; + } return runWasmScanChunks(host, data, length, promiseResult, scanCache); } @@ -123,13 +134,18 @@ function runWasmScanChunks( host.collect = WasmEscapeScanner.collect; } - const result = dispatchScanOps(host, data, scanCache.scan, scanCache.opIndex, scanCache, promiseResult, stateBeforeScan); + const scan = scanCache.scan; + if (!scan) { + throw new Error('Wasm parser scan cache missing during chunked parse'); + } + + const result = dispatchScanOps(host, data, scan, scanCache.opIndex, scanCache, promiseResult, stateBeforeScan); if (result instanceof Promise) { scanCache.inputOffset = pos; return result; } - const nextOffset = scanCache.scan.nextOffset; + const nextOffset = scan.nextOffset; if (nextOffset <= pos) { throw new Error(`Wasm parser scan did not advance (stuck at input offset ${pos})`); } @@ -141,6 +157,8 @@ function runWasmScanChunks( scanCache.inputOffset = length; scanCache.scan = undefined; + scanCache.data = undefined; + scanCache.chunkStart = undefined; } function resumeWasmParse( diff --git a/src/common/parser/wasm/parser.c b/src/common/parser/wasm/parser.c index 0eedaca61..b883ce0dc 100644 --- a/src/common/parser/wasm/parser.c +++ b/src/common/parser/wasm/parser.c @@ -5,9 +5,10 @@ #include "transition_table.h" #include -/* Place scratch memory above static data (transition table ~8.5KB in linear memory 0) */ +/* Static transition table is linked at 65536; scratch must sit above it. */ #define MEM_BASE 65536 -#define MEM_STATE MEM_BASE +#define PARSER_TABLE_BYTE_SIZE (PARSER_TABLE_SIZE * sizeof(uint16_t)) +#define MEM_STATE (((MEM_BASE + PARSER_TABLE_BYTE_SIZE) + 255u) & ~255u) /* ParserWasmState is ~2.3KB; keep header and op buffers past it */ #define MEM_HEADER (MEM_STATE + 2048) #define MEM_KINDS (MEM_HEADER + 64) diff --git a/src/common/parser/wasm/parser.wasm b/src/common/parser/wasm/parser.wasm index b3a9ac1dc5436ac1e119ef6bc579a208be305e02..dba90880df123d62fe5665049c11068f3ca0d633 100755 GIT binary patch literal 15166 zcmd^G&2JmW6`z^?=JHGaNDnHSAx{DX*=P#a-$!kTlL{j7`v4J3D z&;ZRLhoJDGKo4#I2j6QH$tmznhXM`I9C}EB9(vF{{k=E4%Oy=xrj0zUpWF0nawTX&-lb=66Y&aMqX_&RC(WOm;BJkB-Qem$!YWwYq4AG#Hro}6u1LkixEG&&x z(*;2s4W)G(mbRl|yJ=&17zW^)`ATa5hy?QG*L(v4;YU(J0-!|G-_2Fl0pb_j(~3Ex zSa@mlN_zgj~}&RW9qk_`_XJaA6(X*UCwB4%R^ zj{YgA)1*JhLCj$-qG=FMEjxD}Y~qkx0RA&^e|#AIl4mC=z!pIOv^fYP+rXni3)xxF z5M^jS4K?5a0E^*cH3V?)w}Dl_=&dPGRe{05qp$~CQ*bv;wF-r8Ixr!EQ!6k<3av0P zi3|)>6q!N;s42jql6I;^G)_U7_ z^2F+_KUkkM48Xze6YWEf{G`uFhrp6Og2X!T2?20sT(x8{p^7f8e?uXZaF+(&3+=Km z2CJA04ndJapdaH9uFDZQgk})^AP*ZQ@D)VFSqa!$Boorbi zRGTZXn9SB(tX3x(KW z+i|_HT&aqc*GPusPRR(W8UoV_`dULX#V8He4|hio7W7BEq^ExLXGxs<(Wk?9^w%MD zkI+wzdH!pkqSgG@<+-mhH3AhV1KOa}VxNx{K{OG^I?DdmXCi$z!jwp-KqM%Jy7-#V zCZ0)Mw1hE^QT3w{YzaD(yETXczHz2sH4wwEf)a6W3D=7CAvs3&YHH!BOMN*!|CM>3 z|8h#^Y|n2&mr&L?4cJmd5NFTc8Sex2uz_v_*be?i`1|dq96gfLu z2M#WCc8q75ORkaMYEVuyW{< zH(U<-&}%1+8%`l^Al4vmI0Ou3030`n$@kk7q9=m~g^tv&9Ws$inoOdhGX1AfQ5o&! zGmc>X`&Xq8NX3xX4I*q9jDf@3S4Pp|5875Pi8^PI~m&*l1L}Z#YL|Bz% zof!Nl?zZ_%JS=`3>$z;WqOd!~=7Ny&%^BS~wfDQU)6N z7z8s84hkWfe7}LM1Y`rmI|>18Bna!Pl;L4ll;B&y=zDQUnKk2;+m937K8!|`4umug z)D|$Mrm+xwFqaiY!|H4%ogEf2DI6V{N%YwW;*6go5fNaA1#poF%f zD*U}C-J!`Q#x{X{86i%{npf~2L+q)9Bn#i5oipY562^t-;+ZYBMGcX$zuhTI7w1j&_qjtw0e; zKVZQmOhjm1!W!A8ny4WeabiS)f|A9B(jw7bTp{5j2norjeI<~5@hTps;@l_Nb)&KsMu%4&LFdwDFc`h>NV~!kxF{j+a z6EwmKh45`pR6}qt&vTL1az&s&{=#gO3Kkbpda(lePs{-M;~DZVrShM6JK6X2C&_3o z(t%U)KiB9Z%38}6f&PSlKI%ut|8xvrq=$Ot@wb+|;QJW=Y?u=)F1A0X1U47pon^6J zdHii_xgyY?>^~c|7=0(0gTC(yQ_d27;n}Y|{*JX=5$I3&=c7)s{|x7O_p`;{dFAnU zt>ub9f5JZ>mD=$9j*C~>Kj&$19Ni_1l~ z|7Aa)2&=^SXX6&5Uk>J=?}x&avqZmACSDeoi;jPW!?WH6efmWFf7bpld%wl#pC11q zjdPv`J)k;NF>Gwih&yRoA)C<@2J3NaO z`lH{p6uO_o>ht5jV*G!-E`Dd(oz_8&e|f|yy+Pc+_7R47@!7=kQ%4gk#kp__arzJ< znrcsh^J7vfJ}6Nm1LPqmw)LwtC&YP7?Qn zk`&&xe)mo*X^HCr&NbdS+KKxENwe+5gF$>}WBY-u{-%2nZ};Pm^v-^(v+KU`#=f6Qhz<%KH9&xVNRK@*9s3C-CnZ(+D>bKe*>pW_xkZpveEr8 z?(cUyyX&Qcqy1#>!`A*$yk0vvN?P0d@y70cce}MeSijuqc9MRp*W2stZrtr9MEZKI bAK#7pac3vqNFMa!!MZ=#+wE+`_mlqv8HrBr literal 15166 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