From 7646ab5a6bf5157abdef0f0265fe4e3289e5274f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 26 Oct 2026 22:48:01 -0500 Subject: [PATCH] Rename each shared libCall once when switching to child property tables applyHandleProperties collects the libCalls to rename with a walk of every function body and renames each one in place. A node the IR reaches from several statements was collected once per statement, so its second rename looked up the already renamed name and set fn to undefined. The calls are now collected in a set, so each node is renamed once. The lowering does not share such a node today, because devirtualization rebuilds every function body before this step; the lowered IR of current programs is unchanged. --- .../lowering/handle-properties.test.ts | 26 +++++++++++++++++++ .../frontend/lowering/handle-properties.ts | 5 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/handle-properties.test.ts diff --git a/packages/compiler/src/frontend/lowering/handle-properties.test.ts b/packages/compiler/src/frontend/lowering/handle-properties.test.ts new file mode 100644 index 000000000..3c1d83ac3 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/handle-properties.test.ts @@ -0,0 +1,26 @@ +import { expect, test } from "vitest"; +import { VOID, type IrExpr, type IrFunction, type IrStmt, type SrcLoc } from "../../ir/ir.js"; +import { applyHandleProperties } from "./handle-properties.js"; +import { ENTRY_NAME, type Lowerer } from "./lowerer.js"; + +const loc: SrcLoc = { file: "main.ts", start: 0, end: 1 }; +const fn = (name: string, body: IrStmt[]): IrFunction => ({ + name, + body, + params: [], + locals: [], + returnType: VOID, + loc, +}); + +test("renames a libCall the IR reaches from several statements once", () => { + const call: IrExpr = { kind: "libCall", fn: "dyn.defineProperty", args: [], type: VOID, loc }; + const use: IrStmt = { kind: "exprStmt", expr: call, loc }; + const functions = [fn(ENTRY_NAME, [use, use]), fn("helper", [use])]; + applyHandleProperties({ writesOntoChildren: true } as unknown as Lowerer, functions); + expect(call).toMatchObject({ kind: "libCall", fn: "child.propsDefine" }); + expect(functions[0]!.body[0]).toMatchObject({ + kind: "exprStmt", + expr: { kind: "libCall", fn: "child.propsInstall" }, + }); +}); diff --git a/packages/compiler/src/frontend/lowering/handle-properties.ts b/packages/compiler/src/frontend/lowering/handle-properties.ts index 4af4ca54d..ba098aab5 100644 --- a/packages/compiler/src/frontend/lowering/handle-properties.ts +++ b/packages/compiler/src/frontend/lowering/handle-properties.ts @@ -283,10 +283,11 @@ const RENAMES: ReadonlyMap = new Map([ * property-table entries (in place) and installs the table in %main. */ export function applyHandleProperties(lowerer: Lowerer, functions: IrFunction[]): void { if (!lowerer.writesOntoChildren && !childAdditions(lowerer).any) return; - const calls: (IrExpr & { kind: "libCall" })[] = []; + // A node the IR shares at several places is renamed once. + const calls = new Set(); const visitor = { expr: (e: IrExpr): boolean => { - if (e.kind === "libCall" && RENAMES.has(e.fn)) calls.push(e); + if (e.kind === "libCall" && RENAMES.has(e.fn)) calls.add(e); return true; }, stmt: (): boolean => true,