From b00dac886ee7f7ab8b2482894a766229a3c74106 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 23:24:04 -0500 Subject: [PATCH] Copy repeated childless IR nodes one kind at a time unshareNodes copies a repeated childless node through reparseExpr and reparseStmt, which stringify the node and parse the text and so copy its type and location objects as well; on the three.js nodes fixture that takes about 6 s. Spreading the whole IrExpr or IrStmt union instead does not compile in the bootstrap seed build: the optional-read analysis lets the callback of `fn.body.map(stmt)` receive undefined, and a union record copy of a source that may be undefined is not supported (SC1090). copyChildlessExpr and copyChildlessStmt switch over the node kind and spread the narrowed node in each case, which is a plain record copy of that kind. The cases are the kinds mapExprChildren and mapStmtChildren return unchanged; any other kind keeps the encoding's copy. Each copy is a new node object, which is what analyses key their facts by, and shares the type and location objects, as copies of nodes with children already do. The emitted IR is unchanged for every corpus program. unshareNodes takes about 1.8 s on the three.js nodes fixture instead of 6.5 s, and compiling it to LLVM text takes 115 s instead of 123 s (median of five interleaved runs). --- packages/compiler/src/ir/unshare.ts | 70 +++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/ir/unshare.ts b/packages/compiler/src/ir/unshare.ts index 36dd6176f..a83d52a5f 100644 --- a/packages/compiler/src/ir/unshare.ts +++ b/packages/compiler/src/ir/unshare.ts @@ -42,14 +42,15 @@ export function unshareNodes(mod: IrModule): IrModule { } if (affected.size === 0) return mod; // Rebuilding a function copies every node that has children; a repeated - // childless node reads back as a fresh object at each of its sites there. + // childless node gets its own shallow copy at each of its sites there. + // Both kinds of copy keep sharing the type and location objects. const expr = (node: IrExpr): IrExpr => { const copy = mapExprChildren(node, expr, stmt); - return copy !== node || !repeated.has(node) ? copy : reparseExpr(node); + return copy !== node || !repeated.has(node) ? copy : copyChildlessExpr(node); }; const stmt = (node: IrStmt): IrStmt => { const copy = mapStmtChildren(node, expr, stmt); - return copy !== node || !repeated.has(node) ? copy : reparseStmt(node); + return copy !== node || !repeated.has(node) ? copy : copyChildlessStmt(node); }; return { ...mod, @@ -58,3 +59,66 @@ export function unshareNodes(mod: IrModule): IrModule { ), }; } + +/** A shallow copy of a node that mapExprChildren returns unchanged. Each + * case spreads one node kind, so the copy is a plain record copy of that + * kind. Any other kind is copied through the encoding. */ +function copyChildlessExpr(node: IrExpr): IrExpr { + switch (node.kind) { + case "varRef": + return { ...node }; + case "strLit": + return { ...node }; + case "numLit": + return { ...node }; + case "boolLit": + return { ...node }; + case "unitLit": + return { ...node }; + case "closure": + return { ...node }; + case "moduleNsRef": + return { ...node }; + case "incDec": + return { ...node }; + case "chainRecv": + return { ...node }; + case "regexLit": + return { ...node }; + case "templateStrings": + return { ...node }; + case "setNew": + return { ...node }; + case "mapNew": + return { ...node }; + case "dynObjLit": + return { ...node }; + case "selfRef": + return { ...node }; + case "promiseWithResolvers": + return { ...node }; + case "classRef": + return { ...node }; + case "fieldAbsent": + return { ...node }; + default: + return reparseExpr(node); + } +} + +/** A shallow copy of a statement that mapStmtChildren returns unchanged, + * one case per kind as in copyChildlessExpr. */ +function copyChildlessStmt(node: IrStmt): IrStmt { + switch (node.kind) { + case "break": + return { ...node }; + case "continue": + return { ...node }; + case "runtimeFence": + return { ...node }; + case "rethrow": + return { ...node }; + default: + return reparseStmt(node); + } +}