Give every arm of record unions that share arms a collector header

A shape union's arms all carry a collector header when one of them does.
The emitter applied that rule in one pass over the unions in module order,
so a header that a later union gave a shared arm did not reach the other
arms of an earlier union. With First = A | B and Second = B | C, where only
C is cyclic, B got a header through Second, First's trace helper then
called A's trace function, and A had none: the module failed LLVM
verification with "use of undefined value '@sc_rtrace_r0'".

The rule now repeats until no union adds a header. Corpus
5919-record-unions-shared-arms chains Third = D | A, First = A | B and
Second = B | C, so the requirement passes from Second through First to
Third.
This commit is contained in:
Chris Tate
2026-10-18 13:39:12 -05:00
parent cb3d7dbe7c
commit f80eb5fc39
4 changed files with 87 additions and 7 deletions
+19 -7
View File
@@ -1395,13 +1395,25 @@ export class LlEmitter {
this.tracedUnions = traced.unions;
// A shape union's value is whichever record it holds, and the collector
// visits it as that record: when one arm carries a collector header,
// every arm must (nullable-unions.ts).
for (const union of this.nullableUnions.allShapeUnions())
if (
this.tracedUnions.has(union.unionId) ||
union.arms.some((arm) => this.tracedShapes.has(`record:${arm.shapeId}`))
)
for (const arm of union.arms) this.tracedShapes.add(`record:${arm.shapeId}`);
// every arm must (nullable-unions.ts). Unions that share an arm pass a
// header on to each other's arms, so repeat until no union adds one.
const shapeUnions = this.nullableUnions.allShapeUnions();
for (let changed = true; changed;) {
changed = false;
for (const union of shapeUnions) {
if (
!this.tracedUnions.has(union.unionId) &&
!union.arms.some((arm) => this.tracedShapes.has(`record:${arm.shapeId}`))
)
continue;
for (const arm of union.arms) {
const key = `record:${arm.shapeId}`;
if (this.tracedShapes.has(key)) continue;
this.tracedShapes.add(key);
changed = true;
}
}
}
this.shapeHost = {
sizeType: this.sizeType,
cycleBufOffset: this.cycleBufOffset,
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5919-record-unions-shared-arms.ts"
],
"diags": []
}
@@ -0,0 +1,60 @@
// Record unions that share arms: Third = D | A, First = A | B and
// Second = B | C. Only C is cyclic. Every arm of a union whose values the
// cycle collector can visit carries a collector header, and the shared
// arms pass that requirement from Second back through First to Third.
type A = { kind: "a"; v: number };
type B = { kind: "b"; w: number };
type C = { kind: "c"; kids: C[] };
type D = { kind: "d"; label: string };
type Third = D | A;
type First = A | B;
type Second = B | C;
function third(u: Third): string {
return u.kind === "d" ? u.label : `a${u.v}`;
}
function first(u: First): number {
return u.kind === "a" ? u.v : u.w;
}
function second(u: Second): number {
return u.kind === "b" ? u.w : u.kids.length + u.kids.reduce((n, k) => n + second(k), 0);
}
const zs: Third[] = [
{ kind: "d", label: "dee" },
{ kind: "a", v: 7 },
];
console.log(zs.map(third).join(","));
const xs: First[] = [
{ kind: "a", v: 1 },
{ kind: "b", w: 2 },
];
console.log(xs.map(first).join(","));
const leaf: C = { kind: "c", kids: [] };
const ys: Second[] = [
{ kind: "b", w: 3 },
{ kind: "c", kids: [leaf, { kind: "c", kids: [leaf] }] },
];
console.log(ys.map(second).join(","));
// One record held through two unions.
const sharedA: A = { kind: "a", v: 5 };
const sharedB: B = { kind: "b", w: 6 };
const viaThird: Third = sharedA;
const viaFirst: First[] = [sharedA, sharedB];
const viaSecond: Second = sharedB;
console.log(third(viaThird), viaFirst.map(first).join(","), second(viaSecond));
// A cyclic C reachable only through Second, dropped and rebuilt in a loop.
let total = 0;
for (let i = 0; i < 200; i++) {
const node: C = { kind: "c", kids: [] };
node.kids.push(node);
const held: Second[] = [node, { kind: "b", w: i }];
total += held.length + node.kids.length;
}
console.log("total", total);
+2
View File
@@ -2136,6 +2136,7 @@
"5913-prototype-type-error-messages.ts": 3102,
"5914-dyn-in-class-prototype-members.ts": 2530,
"5915-dyn-error-string-conversions.ts": 3023,
"5919-record-unions-shared-arms.ts": 3182,
"5920-promise-try-args.ts": 3028,
"5921-promise-try-args-js.js": 3179,
"5922-matchall-groups-callbacks.ts": 4418,
@@ -5154,6 +5155,7 @@
"5913-prototype-type-error-messages.ts": 3454,
"5914-dyn-in-class-prototype-members.ts": 3792,
"5915-dyn-error-string-conversions.ts": 3851,
"5919-record-unions-shared-arms.ts": 4941,
"5920-promise-try-args.ts": 3195,
"5921-promise-try-args-js.js": 3082,
"5922-matchall-groups-callbacks.ts": 5275,