Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6ddaa2a
Drop guards on private constructor stores and borrow construction-onl…
cramforce Oct 10, 2026
7df72c0
Borrow references whose locations nothing in their extent can write
cramforce Oct 10, 2026
a8113ac
Move owned locals into return values
cramforce Oct 10, 2026
ec5adcf
Lend optional element reads as borrowed returns
cramforce Oct 10, 2026
9afbabe
Keep the reference-write analysis within the self-hosted compiler's s…
cramforce Oct 10, 2026
0b74483
Record TypeScript preflight/order baselines for the reference-write c…
cramforce Oct 10, 2026
f5cf293
Expect borrowed results and moved returns in the call-lifetime tests
cramforce Oct 11, 2026
6236fc5
Pin the exception-edge release with a later argument that removes the…
cramforce Oct 11, 2026
d2deedb
Merge perf/typed-array-reads into perf/trace-cycle-candidates
cramforce Oct 11, 2026
74029ba
Expect only cycle members to be traced in the self-hosting analysis c…
cramforce Oct 11, 2026
9a90bd8
Merge the fixed perf/trace-cycle-candidates into perf/static-immutabi…
cramforce Oct 11, 2026
ef2c5b6
Merge remote-tracking branch 'origin/main' into HEAD
cramforce Oct 11, 2026
70c443e
Merge remote-tracking branch 'origin/perf/static-immutability' into HEAD
cramforce Oct 11, 2026
8f8c48d
Merge remote-tracking branch 'origin/main' into HEAD
cramforce Oct 11, 2026
9931ea3
Merge remote-tracking branch 'origin/perf/static-immutability' into HEAD
cramforce Oct 11, 2026
58b64fa
Merge pull request #848 from vercel-labs/perf/rc-elision-si
ctate Oct 11, 2026
fa6c698
Merge remote-tracking branch 'origin/main' into HEAD
cramforce Oct 11, 2026
8b5cf78
Update constant-table emission test for exact index checks
ctate Oct 11, 2026
f43ef50
Update byte-loop emission test for exact index checks
ctate Oct 11, 2026
757c48c
Anchor the canonical byte-loop check on the element-check blocks
cramforce Oct 11, 2026
d761cb5
Merge main into perf/static-immutability
ctate Oct 11, 2026
0148f6b
Merge remote-tracking branch 'origin/main' into HEAD
cramforce Oct 11, 2026
f88830c
Merge branch 'main' into perf/static-immutability
cramforce Oct 11, 2026
1c4b34d
Keep publication guards after self-stores and own borrowed-result cal…
ctate Oct 12, 2026
2ea30ed
Merge main into perf/static-immutability
ctate Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions packages/compiler/src/backend/cycle-mutability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,11 @@ export function constructorThisAliases(fn: IrFunction, thisId: string): Set<stri
* field stores and reads (the body length when it never is). Statements run
* in order, so every store in an earlier statement completes while the
* object is still private. Within and after the escaping statement nothing
* is private any more. With `selfStores`, storing `this` into one of its
* own fields is not an escape (it exposes nothing to other code, but it
* does create a cycle). */
* is private any more. */
export function constructorEscapePoint(
fn: IrFunction,
thisId: string,
baseCtor: string | null,
options: { selfStores?: boolean } = {},
): number {
let escaped = false;
const aliases = constructorThisAliases(fn, thisId);
Expand Down Expand Up @@ -338,14 +335,7 @@ export function constructorEscapePoint(
return everyExprChild(e, expr, stmt);
};
const stmt = (s: IrStmt): boolean => {
if (s.kind === "fieldSet" && isThisRef(s.obj)) {
if (options.selfStores === true) {
// `this` itself, possibly wrapped into the field's union type.
const self = s.value.kind === "unionWrap" ? s.value.value : s.value;
if (isThisRef(self)) return wrapper(s.obj) && wrapper(self);
}
return wrapper(s.obj) && expr(s.value);
}
if (s.kind === "fieldSet" && isThisRef(s.obj)) return wrapper(s.obj) && expr(s.value);
if (s.kind === "exprStmt" && isThisRef(s.expr)) return wrapper(s.expr);
// Declaring an alias is not an escape; its uses are checked like `this`.
if (s.kind === "varDecl" && aliases.has(s.localId) && s.init !== null) return wrapper(s.init);
Expand Down
172 changes: 172 additions & 0 deletions packages/compiler/src/backend/immutability.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { expect, test } from "vitest";
import {
DYN,
F64,
STRING,
VOID,
type IrExpr,
type IrModule,
type IrStmt,
type IrType,
} from "../ir/ir.js";
import { computeConstructionFacts, computeImmutableFields, stableGlobals } from "./immutability.js";

const loc = { file: "immutability.ts", start: 0, end: 1 };
const obj = (className: string): IrType => ({ kind: "object", className });
const value = (type: IrType): IrExpr => ({ kind: "varRef", localId: "v", type, loc });
const thisRef = (className: string): IrExpr => ({
kind: "varRef",
localId: "this.0",
type: obj(className),
loc,
});
const setThis = (className: string, field: string): IrStmt => ({
kind: "fieldSet",
obj: thisRef(className),
className,
field,
value: value(F64),
loc,
});
const call = (callee: string, args: IrExpr[]): IrStmt => ({
kind: "exprStmt",
expr: { kind: "call", callee, args, type: VOID, loc },
loc,
});
function ctor(className: string, body: IrStmt[]): IrModule["functions"][number] {
return {
name: `%${className}.constructor`,
params: [{ localId: "this.0", name: "this", type: obj(className) }],
locals: [],
returnType: VOID,
body,
loc,
};
}
function module(
classes: NonNullable<IrModule["classes"]>,
functions: IrModule["functions"],
): IrModule {
return {
irVersion: 15,
sourceFile: loc.file,
entry: "main",
classes,
functions: [
{ name: "main", locals: [], params: [], returnType: VOID, body: [], loc },
...functions,
],
};
}
const fields = (...names: string[]) => names.map((name) => ({ name, type: F64 }));

/** The fields of `className` whose constructor stores are private. */
function privateFields(mod: IrModule, className: string): string[] {
const facts = computeConstructionFacts(mod);
const fn = mod.functions.find((f) => f.name === `%${className}.constructor`)!;
return fn.body
.filter((s): s is Extract<IrStmt, { kind: "fieldSet" }> => s.kind === "fieldSet")
.filter((s) => facts.privateStores.has(s))
.map((s) => s.field);
}

test("constructor stores before this escapes are private", () => {
const mod = module(
[{ name: "P", fields: fields("a", "b"), loc }],
[ctor("P", [setThis("P", "a"), call("register", [thisRef("P")]), setThis("P", "b")])],
);
expect(privateFields(mod, "P")).toEqual(["a"]);
});

test("storing this into its own field ends private construction", () => {
// A later read of the field yields `this` again, so any use of it
// (publishing it, handing it to a worker) would expose the object.
const self: IrStmt = { ...setThis("P", "self"), value: thisRef("P") } as IrStmt;
const mod = module(
[{ name: "P", fields: [...fields("a", "b"), { name: "self", type: obj("P") }], loc }],
[ctor("P", [setThis("P", "a"), self, setThis("P", "b")])],
);
expect(privateFields(mod, "P")).toEqual(["a"]);
});

test("a base constructor's escape makes every subclass store public", () => {
const superCall = call("%Base.constructor", [
{ kind: "upcast", value: thisRef("Sub"), type: obj("Base"), loc },
]);
const mod = module(
[
{ name: "Base", fields: fields("a"), loc },
{ name: "Sub", base: "Base", fields: fields("a", "b"), loc },
],
[
ctor("Base", [setThis("Base", "a"), call("register", [thisRef("Base")])]),
ctor("Sub", [superCall, setThis("Sub", "b")]),
],
);
expect(privateFields(mod, "Base")).toEqual(["a"]);
expect(privateFields(mod, "Sub")).toEqual([]);
});

test("a constructor called on an existing object keeps every guard", () => {
const mod = module([{ name: "P", fields: fields("a"), loc }], [ctor("P", [setThis("P", "a")])]);
expect(privateFields(mod, "P")).toEqual(["a"]);
mod.functions[0]!.body.push(call("%P.constructor", [value(obj("P"))]));
expect(privateFields(mod, "P")).toEqual([]);
});

test("library builds keep every guard", () => {
const mod = module([{ name: "P", fields: fields("a"), loc }], [ctor("P", [setThis("P", "a")])]);
mod.lib = {} as NonNullable<IrModule["lib"]>;
expect(privateFields(mod, "P")).toEqual([]);
});

test("construction-only fields exclude later writes and dynamic stores", () => {
const mod = module(
[{ name: "P", fields: fields("a", "b"), loc }],
[ctor("P", [setThis("P", "a"), setThis("P", "b")])],
);
mod.functions[0]!.body.push(
{ kind: "fieldSet", obj: value(obj("P")), className: "P", field: "b", value: value(F64), loc },
{ kind: "exprStmt", expr: { kind: "dynFrom", value: value(obj("P")), type: DYN, loc }, loc },
);
const facts = computeImmutableFields(mod)!;
expect(facts.classField("P", "a")).toBe(true);
expect(facts.classField("P", "b")).toBe(false);
// A runtime store into a dynamic value can reach P through its capsule.
mod.functions[0]!.body.push({
kind: "exprStmt",
expr: {
kind: "libCall",
fn: "dyn.keySet",
args: [value(DYN), value(STRING), value(DYN)],
type: VOID,
loc,
},
loc,
});
expect(computeImmutableFields(mod)!.classField("P", "a")).toBe(false);
});

test("module constants are stable once assigned exactly once", () => {
const mod = module([], []);
mod.globals = [
{ id: "%g.k", name: "k", type: STRING, mutable: false },
{ id: "%g.m", name: "m", type: STRING, mutable: true },
{ id: "%g.t", name: "t", type: STRING, mutable: false, tdz: true },
{ id: "%g.twice", name: "twice", type: STRING, mutable: false },
];
const assign = (localId: string): IrStmt => ({
kind: "assign",
localId,
value: value(STRING),
loc,
});
mod.functions[0]!.body.push(
assign("%g.k"),
assign("%g.m"),
assign("%g.t"),
assign("%g.twice"),
assign("%g.twice"),
);
expect([...stableGlobals(mod)]).toEqual(["%g.k"]);
});
Loading
Loading