Files
scriptc/tests/harness/self-hosting-backend-cases.ts
Chris Tate 48e00aa779 feat(compiler): run optimization and backend analyses natively (#497)
- Compile scalar-record optimization and backend analyses on both native backends
- Use exhaustive typed IR traversal and shared cycle analysis
- Verify optimized IR and executable parity with focused sanitizer coverage
2026-09-27 16:41:16 -05:00

125 lines
9.4 KiB
TypeScript

import { arrayOf, BOOL, F64, STRING, VOID, funcOf, type IrExpr, type IrFunction, type IrModule, type IrStmt, type IrType } from "../../packages/compiler/src/ir/ir.js";
import { IR_VERSION } from "../../packages/compiler/src/ir/serialize.js";
const loc = { file: "backend-analysis.ts", start: 0, end: 1 };
const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc });
const ref = (localId: string, type: IrType = F64): IrExpr => ({ kind: "varRef", localId, type, loc });
const stmt = (expr: IrExpr): IrStmt => ({ kind: "exprStmt", expr, loc });
const call = (callee: string): IrExpr => ({ kind: "call", callee, args: [], type: VOID, loc });
const fn = (name: string, body: IrStmt[] = []): IrFunction => ({ name, params: [], locals: [], body, returnType: VOID, loc });
const module = (functions: IrFunction[] = [fn("main")]): IrModule => ({ irVersion: IR_VERSION, sourceFile: loc.file, entry: "main", functions });
export interface BackendAnalysisCase {
name: string;
module: IrModule;
mayThrow?: string[];
indirect?: boolean;
shapes?: string[];
unions?: string[];
tables?: string[];
loops?: string[];
}
/** Structural cases isolate analysis decisions independently of the runtime
* APIs they describe. Execution/validation is covered separately with real
* frontend output and native optimization followed by executable emission. */
export function backendAnalysisCases(): BackendAnalysisCase[] {
const cases: BackendAnalysisCase[] = [{ name: "empty", module: module(), mayThrow: [], indirect: false, shapes: [], unions: [], tables: [], loops: [] }];
const failure = fn("failure", [{ kind: "throw", value: num(1), loc }]);
const caller = fn("main", [stmt(call("middle"))]);
const middle = fn("middle", [stmt(call("failure"))]);
cases.push({ name: "transitive calls", module: module([caller, middle, failure]), mayThrow: ["failure", "middle", "main"], indirect: false });
const closure: IrExpr = { kind: "closure", fnName: "middle", captures: [], type: funcOf([], VOID), loc };
cases.push({ name: "indirect fixpoint", module: module([
fn("main", [stmt({ kind: "callValue", callee: closure, args: [], type: VOID, loc })]), middle, failure,
]), mayThrow: ["failure", "middle", "main"], indirect: true });
for (const suspension of ["async", "generator"] as const) {
const suspended: IrFunction = suspension === "async" ? { ...failure, async: true }
: { ...failure, generator: { yieldT: F64, nextT: F64, resultType: { kind: "record", shapeId: "result" } } };
cases.push({ name: `${suspension} call does not unwind`, module: module([
fn("main", [stmt(call("failure")), stmt({ ...closure, fnName: "failure" })]), suspended,
]), mayThrow: ["failure"], indirect: false });
}
const nested: IrStmt = { kind: "tryCatch", catchLocalId: null, catchBody: null, finallyBody: [
stmt({ kind: "seqExpr", stmts: [stmt(call("failure"))], result: num(0), type: F64, loc }),
], tryBody: [], loc };
cases.push({ name: "statement hidden in finally expression", module: module([fn("main", [nested]), failure]), mayThrow: ["failure", "main"] });
cases.push({ name: "lazy operands still contribute", module: module([
fn("main", [stmt({ kind: "ternary", cond: { kind: "boolLit", value: false, type: BOOL, loc }, then: call("failure"), else_: num(0), type: F64, loc })]), failure,
]), mayThrow: ["failure", "main"] });
const recursive = module([fn("main", [stmt(call("a"))]), fn("a", [stmt(call("b"))]), fn("b", [stmt(call("a")), stmt(call("failure"))]), failure]);
cases.push({ name: "recursive call graph", module: recursive, mayThrow: ["failure", "b", "a", "main"] });
const local = { id: "value", name: "value", type: F64, mutable: true, boxed: true as const, tdz: true as const };
for (const initializes of [true, false]) {
const entry = fn("main", [{ kind: "assign", localId: local.id, value: num(1), ...(initializes ? { initializes: true as const } : {}), loc }]);
entry.locals = [local];
cases.push({ name: `TDZ initialize=${initializes}`, module: module([entry]), mayThrow: initializes ? [] : ["main"] });
}
const virtual = module([fn("main", [stmt({ kind: "virtualCall", className: "Base", method: "run", args: [], type: VOID, loc })]), { ...failure, name: "%Child.run" }]);
virtual.classes = [{ name: "Base", fields: [], methods: ["run"], loc }, { name: "Child", base: "Base", fields: [], methods: ["run"], loc }];
cases.push({ name: "virtual dispatch", module: virtual, mayThrow: ["%Child.run", "main"] });
const construction = module([fn("main", [stmt({ kind: "newValue", callee: { kind: "classRef", className: "Base", type: { kind: "classval", className: "Base" }, loc }, args: [], type: { kind: "object", className: "Base" }, loc })]), { ...failure, name: "%Child.constructor" }]);
construction.classes = virtual.classes;
cases.push({ name: "indirect derived constructor", module: construction, mayThrow: ["%Child.constructor", "main"] });
const adapter = module([fn("main", [stmt({ kind: "dynCheck", value: ref("unknown", { kind: "dyn" }), type: funcOf([], VOID), loc })])]);
cases.push({ name: "synthetic checked callback", module: adapter, mayThrow: ["main"], indirect: true });
const graph = module();
const record = (shapeId: string): IrType => ({ kind: "record", shapeId });
graph.records = [
{ id: "outer", fields: [{ name: "next", type: arrayOf(record("cycle")) }] },
{ id: "cycle", fields: [], indexValue: { kind: "union", unionId: "recursive" } },
{ id: "leaf", fields: [{ name: "value", type: STRING }] },
{ id: "chain", fields: [{ name: "next", type: record("leaf") }] },
];
graph.unions = [{ id: "recursive", arms: [record("cycle"), { kind: "nullT" }] }, { id: "acyclic", arms: [record("chain"), F64] }];
cases.push({ name: "shape and union fixed point", module: graph, shapes: ["record:outer", "record:cycle"], unions: ["recursive"] });
const hierarchy = module();
hierarchy.classes = [
{ name: "Child", base: "Base", fields: [{ name: "callback", type: funcOf([], VOID) }], loc },
{ name: "Sibling", base: "Base", fields: [], loc }, { name: "Base", fields: [], loc },
];
cases.push({ name: "hierarchy uniform tracing", module: hierarchy, shapes: ["object:Child", "object:Sibling", "object:Base"] });
const tableId = "%g.table";
const tableType = arrayOf(F64);
const tableRead: IrExpr = { kind: "arrIntrinsic", method: "getNumber", receiver: ref(tableId, tableType), args: [num(0)], type: F64, loc };
const table = module([fn("main", [
{ kind: "assign", localId: tableId, value: { kind: "arrayLit", elems: [num(2), num(-0), num(Infinity)], type: tableType, loc }, loc }, stmt(tableRead),
])]);
table.globals = [{ id: tableId, name: "table", type: tableType, mutable: false }];
cases.push({ name: "numeric table", module: table, tables: [tableId] });
for (const use of ["capture", "index-write", "reassign", "length", "hidden-call"]) {
const changed = structuredClone(table);
const entry = changed.functions[0]!;
const mutation: IrStmt = { kind: "arraySet", arr: ref(tableId, tableType), index: num(0), value: num(3), loc };
if (use === "capture") entry.body.push(stmt({ kind: "closure", fnName: "callback", captures: [tableId], type: funcOf([], VOID), loc }));
if (use === "index-write") entry.body.push(stmt({ ...tableRead, args: [{ kind: "seqExpr", stmts: [mutation], result: num(0), type: F64, loc }] } as IrExpr));
if (use === "reassign") entry.body.push(entry.body[0]!);
if (use === "length") entry.body.push({ kind: "arraySetLength", arr: ref(tableId, tableType), length: num(1), loc });
if (use === "hidden-call") changed.functions.push(fn("unused", [stmt({ kind: "call", callee: "sink", args: [ref(tableId, tableType)], type: VOID, loc })]));
cases.push({ name: `table rejects ${use}`, module: changed, tables: [] });
}
const bytes: IrType = { kind: "bytes", elem: "u8" };
const loop: IrStmt & { kind: "for" } = { kind: "for",
init: { kind: "varDecl", localId: "i", init: num(0), loc },
cond: { kind: "bin", op: "<", left: ref("i"), right: { kind: "bytesIntrinsic", method: "length", receiver: ref("bytes", bytes), args: [], type: F64, loc }, type: BOOL, loc },
update: stmt({ kind: "incDec", localId: "i", op: "+", prefix: false, type: F64, loc }), body: [], loc,
};
const loops = module([fn("main", [{ kind: "block", body: [loop], loc }])]);
loops.functions[0]!.locals = [{ id: "i", name: "i", type: F64, mutable: true }, { id: "bytes", name: "bytes", type: bytes, mutable: false }];
cases.push({ name: "nested integer loop", module: loops, loops: ["i"] });
for (const mutation of ["expression", "finally", "capture", "negative-zero"]) {
const changed = structuredClone(loops);
const block = changed.functions[0]!.body[0] as IrStmt & { kind: "block" };
const changedLoop = block.body[0] as IrStmt & { kind: "for" };
if (mutation === "expression") changedLoop.body.push(stmt({ kind: "seqExpr", stmts: [], result: { kind: "assignExpr", localId: "i", value: num(2), type: F64, loc }, type: F64, loc }));
if (mutation === "finally") changedLoop.body.push({ kind: "tryCatch", tryBody: [], catchBody: null, catchLocalId: null, finallyBody: [{ kind: "assign", localId: "i", value: num(2), loc }], loc });
if (mutation === "capture") changed.functions[0]!.locals[0]!.boxed = true;
if (mutation === "negative-zero") changedLoop.init = { kind: "varDecl", localId: "i", init: num(-0), loc };
cases.push({ name: `integer loop rejects ${mutation}`, module: changed, loops: [] });
}
return cases;
}