From 9cf8662099e0c5ccb0d245d043a7ee56bf4506cc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Wed, 21 Oct 2026 08:35:02 -0500 Subject: [PATCH] Walk IR child lists with loops instead of per-node closures everyExprChild, everyStmtChild, mapExprChildren and mapStmtChildren passed an arrow function to every() or map() for each child list. In the native compiler that arrow is a closure allocated and released at every visited node, and each element passes through Array.prototype.every's callback adapter. The child lists are now walked with index loops in the same order, so every IR pass that traverses the module visits the same nodes with less work per node. --- packages/compiler/src/ir/traverse.ts | 191 ++++++++++++++++----------- 1 file changed, 112 insertions(+), 79 deletions(-) diff --git a/packages/compiler/src/ir/traverse.ts b/packages/compiler/src/ir/traverse.ts index 3e27cdc36..04ce483ab 100644 --- a/packages/compiler/src/ir/traverse.ts +++ b/packages/compiler/src/ir/traverse.ts @@ -1,5 +1,43 @@ import type { IrExpr, IrModule, IrStmt, IrType, SrcLoc } from "./ir.js"; +// Child lists are walked with these loops rather than with `every` or `map` +// and an arrow function: the arrow would be a new closure at every visited +// node, which the native compiler allocates and releases per node. + +function everyOf(children: readonly T[], visit: (child: T) => boolean): boolean { + const length = children.length; + for (let i = 0; i < length; i++) if (!visit(children[i]!)) return false; + return true; +} + +function everyValue( + children: readonly { value: IrExpr }[], + expr: (expr: IrExpr) => boolean, +): boolean { + const length = children.length; + for (let i = 0; i < length; i++) if (!expr(children[i]!.value)) return false; + return true; +} + +function everyEntry( + children: readonly { key: IrExpr; value: IrExpr }[], + expr: (expr: IrExpr) => boolean, +): boolean { + const length = children.length; + for (let i = 0; i < length; i++) { + const child = children[i]!; + if (!expr(child.key) || !expr(child.value)) return false; + } + return true; +} + +function mapOf(children: readonly T[], map: (child: T) => T): T[] { + const length = children.length; + const result: T[] = []; + for (let i = 0; i < length; i++) result.push(map(children[i]!)); + return result; +} + /** The type tree contains structural children; named shapes are references. * Module traversal visits each shape definition separately. */ export function everyTypeChild(node: IrType, visit: (node: IrType) => boolean): boolean { @@ -134,13 +172,13 @@ export function everyExprChild( case "varRef": return true; case "call": - return node.args.every((child) => expr(child)); + return everyOf(node.args, expr); case "dynFrom": return expr(node.value); case "strLit": return true; case "libCall": - return node.args.every((child) => expr(child)); + return everyOf(node.args, expr); case "dynCheck": return expr(node.value); case "strEq": @@ -158,13 +196,13 @@ export function everyExprChild( case "unitLit": return true; case "seqExpr": - return node.stmts.every((child) => stmt(child)) && expr(node.result); + return everyOf(node.stmts, stmt) && expr(node.result); case "dynCall": return ( expr(node.callee) && (node.receiver === undefined || expr(node.receiver)) && (node.calleeNameValue === undefined || expr(node.calleeNameValue)) && - node.args.every((child) => expr(child)) + everyOf(node.args, expr) ); case "dynTest": return expr(node.value); @@ -203,15 +241,15 @@ export function everyExprChild( case "chainRecv": return true; case "strIntrinsic": - return expr(node.receiver) && node.args.every((child) => expr(child)); + return expr(node.receiver) && everyOf(node.args, expr); case "regexLit": return true; case "templateStrings": return true; case "regexIntrinsic": - return expr(node.receiver) && node.args.every((child) => expr(child)); + return expr(node.receiver) && everyOf(node.args, expr); case "arrayLit": - return node.elems.every((child) => expr(child)); + return everyOf(node.elems, expr); case "arrayNewLen": return expr(node.length); case "arrayGet": @@ -221,28 +259,26 @@ export function everyExprChild( case "arrayState": return expr(node.arr) && expr(node.index); case "arrIntrinsic": - return expr(node.receiver) && node.args.every((child) => expr(child)); + return expr(node.receiver) && everyOf(node.args, expr); case "bytesNew": return node.source === null || expr(node.source); case "bytesIntrinsic": - return expr(node.receiver) && node.args.every((child) => expr(child)); + return expr(node.receiver) && everyOf(node.args, expr); case "mapNew": - return ( - node.seed === undefined || node.seed.every((child) => expr(child.key) && expr(child.value)) - ); + return node.seed === undefined || everyEntry(node.seed, expr); case "mapIntrinsic": - return expr(node.receiver) && node.args.every((child) => expr(child)); + return expr(node.receiver) && everyOf(node.args, expr); case "setNew": return node.seed === undefined || expr(node.seed); case "setIntrinsic": - return expr(node.receiver) && node.args.every((child) => expr(child)); + return expr(node.receiver) && everyOf(node.args, expr); case "ffiCall": - return node.args.every((child) => expr(child)); + return everyOf(node.args, expr); case "callValue": return ( expr(node.callee) && (node.receiver === undefined || expr(node.receiver)) && - node.args.every((child) => expr(child)) + everyOf(node.args, expr) ); case "selfRef": return true; @@ -259,11 +295,11 @@ export function everyExprChild( case "promiseWithResolvers": return true; case "new": - return node.args.every((child) => expr(child)); + return everyOf(node.args, expr); case "classRef": return true; case "newValue": - return expr(node.callee) && node.args.every((child) => expr(child)); + return expr(node.callee) && everyOf(node.args, expr); case "instanceOfValue": return expr(node.value) && expr(node.classValue); case "promiseVoidWiden": @@ -273,11 +309,11 @@ export function everyExprChild( case "instanceOf": return expr(node.value); case "virtualCall": - return node.args.every((child) => expr(child)); + return everyOf(node.args, expr); case "recordLit": - return node.fields.every((child) => expr(child.value)); + return everyValue(node.fields, expr); case "recordClone": - return expr(node.source) && node.overrides.every((child) => expr(child.value)); + return expr(node.source) && everyValue(node.overrides, expr); case "recordGet": return expr(node.obj); case "recordKeyGet": @@ -286,15 +322,12 @@ export function everyExprChild( return ( expr(node.recv) && (node.calleeNameValue === undefined || expr(node.calleeNameValue)) && - node.args.every((child) => expr(child)) + everyOf(node.args, expr) ); case "dynArrLit": - return node.elems.every((child) => expr(child)); + return everyOf(node.elems, expr); case "dynObjLit": - return ( - node.fields === undefined || - node.fields.every((child) => expr(child.key) && expr(child.value)) - ); + return node.fields === undefined || everyEntry(node.fields, expr); case "dynKeyGet": return expr(node.key) && expr(node.value); case "dynHasKey": @@ -338,7 +371,7 @@ export function everyExprChild( case "unionEq": return expr(node.left) && expr(node.right); case "intrinsic": - return node.args.every((child) => expr(child)); + return everyOf(node.args, expr); case "jsonStringify": return expr(node.value); } @@ -360,8 +393,8 @@ export function everyStmtChild( case "if": return ( expr(node.cond) && - node.then.every((child) => stmt(child)) && - (node.else_ === null || node.else_.every((child) => stmt(child))) + everyOf(node.then, stmt) && + (node.else_ === null || everyOf(node.else_, stmt)) ); case "varDecl": return node.init === null || expr(node.init); @@ -370,27 +403,29 @@ export function everyStmtChild( case "assign": return expr(node.value); case "block": - return node.body.every((child) => stmt(child)); + return everyOf(node.body, stmt); case "fieldSet": return expr(node.obj) && expr(node.value); case "while": - return expr(node.cond) && node.body.every((child) => stmt(child)); + return expr(node.cond) && everyOf(node.body, stmt); case "doWhile": - return node.body.every((child) => stmt(child)) && expr(node.cond); - case "switch": - return ( - expr(node.disc) && - node.cases.every( - (child) => - (child.test === null || expr(child.test)) && child.body.every((child) => stmt(child)), - ) - ); + return everyOf(node.body, stmt) && expr(node.cond); + case "switch": { + if (!expr(node.disc)) return false; + const length = node.cases.length; + for (let i = 0; i < length; i++) { + const child = node.cases[i]!; + if (child.test !== null && !expr(child.test)) return false; + if (!everyOf(child.body, stmt)) return false; + } + return true; + } case "for": return ( (node.init === null || stmt(node.init)) && (node.cond === null || expr(node.cond)) && (node.update === null || stmt(node.update)) && - node.body.every((child) => stmt(child)) + everyOf(node.body, stmt) ); case "arraySet": return expr(node.arr) && expr(node.index) && expr(node.value); @@ -403,7 +438,7 @@ export function everyStmtChild( case "bytesSet": return expr(node.arr) && expr(node.index) && expr(node.value); case "forOf": - return expr(node.iterable) && node.body.every((child) => stmt(child)); + return expr(node.iterable) && everyOf(node.body, stmt); case "recordSet": return expr(node.obj) && expr(node.value); case "recordKeySet": @@ -422,9 +457,9 @@ export function everyStmtChild( return true; case "tryCatch": return ( - node.tryBody.every((child) => stmt(child)) && - (node.catchBody === null || node.catchBody.every((child) => stmt(child))) && - (node.finallyBody === null || node.finallyBody.every((child) => stmt(child))) + everyOf(node.tryBody, stmt) && + (node.catchBody === null || everyOf(node.catchBody, stmt)) && + (node.finallyBody === null || everyOf(node.finallyBody, stmt)) ); } node satisfies never; @@ -443,13 +478,13 @@ export function mapExprChildren( case "varRef": return node; case "call": - return { ...node, args: node.args.map((child) => expr(child)) }; + return { ...node, args: mapOf(node.args, expr) }; case "dynFrom": return { ...node, value: expr(node.value) }; case "strLit": return node; case "libCall": - return { ...node, args: node.args.map((child) => expr(child)) }; + return { ...node, args: mapOf(node.args, expr) }; case "dynCheck": return { ...node, value: expr(node.value) }; case "strEq": @@ -467,12 +502,12 @@ export function mapExprChildren( case "unitLit": return node; case "seqExpr": - return { ...node, stmts: node.stmts.map((child) => stmt(child)), result: expr(node.result) }; + return { ...node, stmts: mapOf(node.stmts, stmt), result: expr(node.result) }; case "dynCall": { const result: Extract = { ...node, callee: expr(node.callee) }; if (node.receiver !== undefined) result.receiver = expr(node.receiver); if (node.calleeNameValue !== undefined) result.calleeNameValue = expr(node.calleeNameValue); - result.args = node.args.map((child) => expr(child)); + result.args = mapOf(node.args, expr); return result; } case "dynTest": @@ -515,7 +550,7 @@ export function mapExprChildren( return { ...node, receiver: expr(node.receiver), - args: node.args.map((child) => expr(child)), + args: mapOf(node.args, expr), }; case "regexLit": return node; @@ -525,10 +560,10 @@ export function mapExprChildren( return { ...node, receiver: expr(node.receiver), - args: node.args.map((child) => expr(child)), + args: mapOf(node.args, expr), }; case "arrayLit": - return { ...node, elems: node.elems.map((child) => expr(child)) }; + return { ...node, elems: mapOf(node.elems, expr) }; case "arrayNewLen": return { ...node, length: expr(node.length) }; case "arrayGet": @@ -541,7 +576,7 @@ export function mapExprChildren( return { ...node, receiver: expr(node.receiver), - args: node.args.map((child) => expr(child)), + args: mapOf(node.args, expr), }; case "bytesNew": return { ...node, source: node.source === null ? null : expr(node.source) }; @@ -549,7 +584,7 @@ export function mapExprChildren( return { ...node, receiver: expr(node.receiver), - args: node.args.map((child) => expr(child)), + args: mapOf(node.args, expr), }; case "mapNew": return node.seed === undefined @@ -566,7 +601,7 @@ export function mapExprChildren( return { ...node, receiver: expr(node.receiver), - args: node.args.map((child) => expr(child)), + args: mapOf(node.args, expr), }; case "setNew": return node.seed === undefined ? node : { ...node, seed: expr(node.seed) }; @@ -574,19 +609,18 @@ export function mapExprChildren( return { ...node, receiver: expr(node.receiver), - args: node.args.map((child) => expr(child)), + args: mapOf(node.args, expr), }; case "ffiCall": - return { ...node, args: node.args.map((child) => expr(child)) }; + return { ...node, args: mapOf(node.args, expr) }; case "callValue": { const callee = expr(node.callee); - if (node.receiver === undefined) - return { ...node, callee, args: node.args.map((child) => expr(child)) }; + if (node.receiver === undefined) return { ...node, callee, args: mapOf(node.args, expr) }; return { ...node, callee, receiver: expr(node.receiver), - args: node.args.map((child) => expr(child)), + args: mapOf(node.args, expr), }; } case "selfRef": @@ -604,11 +638,11 @@ export function mapExprChildren( case "promiseWithResolvers": return node; case "new": - return { ...node, args: node.args.map((child) => expr(child)) }; + return { ...node, args: mapOf(node.args, expr) }; case "classRef": return node; case "newValue": - return { ...node, callee: expr(node.callee), args: node.args.map((child) => expr(child)) }; + return { ...node, callee: expr(node.callee), args: mapOf(node.args, expr) }; case "instanceOfValue": return { ...node, value: expr(node.value), classValue: expr(node.classValue) }; case "promiseVoidWiden": @@ -618,7 +652,7 @@ export function mapExprChildren( case "instanceOf": return { ...node, value: expr(node.value) }; case "virtualCall": - return { ...node, args: node.args.map((child) => expr(child)) }; + return { ...node, args: mapOf(node.args, expr) }; case "recordLit": return { ...node, @@ -637,11 +671,11 @@ export function mapExprChildren( case "dynInvoke": { const result: Extract = { ...node, recv: expr(node.recv) }; if (node.calleeNameValue !== undefined) result.calleeNameValue = expr(node.calleeNameValue); - result.args = node.args.map((child) => expr(child)); + result.args = mapOf(node.args, expr); return result; } case "dynArrLit": - return { ...node, elems: node.elems.map((child) => expr(child)) }; + return { ...node, elems: mapOf(node.elems, expr) }; case "dynObjLit": return node.fields === undefined ? node @@ -696,7 +730,7 @@ export function mapExprChildren( case "unionEq": return { ...node, left: expr(node.left), right: expr(node.right) }; case "intrinsic": - return { ...node, args: node.args.map((child) => expr(child)) }; + return { ...node, args: mapOf(node.args, expr) }; case "jsonStringify": return { ...node, value: expr(node.value) }; } @@ -719,8 +753,8 @@ export function mapStmtChildren( return { ...node, cond: expr(node.cond), - then: node.then.map((child) => stmt(child)), - else_: node.else_ === null ? null : node.else_.map((child) => stmt(child)), + then: mapOf(node.then, stmt), + else_: node.else_ === null ? null : mapOf(node.else_, stmt), }; case "varDecl": return { ...node, init: node.init === null ? null : expr(node.init) }; @@ -729,13 +763,13 @@ export function mapStmtChildren( case "assign": return { ...node, value: expr(node.value) }; case "block": - return { ...node, body: node.body.map((child) => stmt(child)) }; + return { ...node, body: mapOf(node.body, stmt) }; case "fieldSet": return { ...node, obj: expr(node.obj), value: expr(node.value) }; case "while": - return { ...node, cond: expr(node.cond), body: node.body.map((child) => stmt(child)) }; + return { ...node, cond: expr(node.cond), body: mapOf(node.body, stmt) }; case "doWhile": - return { ...node, body: node.body.map((child) => stmt(child)), cond: expr(node.cond) }; + return { ...node, body: mapOf(node.body, stmt), cond: expr(node.cond) }; case "switch": return { ...node, @@ -743,7 +777,7 @@ export function mapStmtChildren( cases: node.cases.map((child) => ({ ...child, test: child.test === null ? null : expr(child.test), - body: child.body.map((child) => stmt(child)), + body: mapOf(child.body, stmt), })), }; case "for": @@ -752,7 +786,7 @@ export function mapStmtChildren( init: node.init === null ? null : stmt(node.init), cond: node.cond === null ? null : expr(node.cond), update: node.update === null ? null : stmt(node.update), - body: node.body.map((child) => stmt(child)), + body: mapOf(node.body, stmt), }; case "arraySet": return { ...node, arr: expr(node.arr), index: expr(node.index), value: expr(node.value) }; @@ -768,7 +802,7 @@ export function mapStmtChildren( return { ...node, iterable: expr(node.iterable), - body: node.body.map((child) => stmt(child)), + body: mapOf(node.body, stmt), }; case "recordSet": return { ...node, obj: expr(node.obj), value: expr(node.value) }; @@ -789,10 +823,9 @@ export function mapStmtChildren( case "tryCatch": return { ...node, - tryBody: node.tryBody.map((child) => stmt(child)), - catchBody: node.catchBody === null ? null : node.catchBody.map((child) => stmt(child)), - finallyBody: - node.finallyBody === null ? null : node.finallyBody.map((child) => stmt(child)), + tryBody: mapOf(node.tryBody, stmt), + catchBody: node.catchBody === null ? null : mapOf(node.catchBody, stmt), + finallyBody: node.finallyBody === null ? null : mapOf(node.finallyBody, stmt), }; } node satisfies never;