Files
scriptc/tests/corpus/integer-recursion-records-workers.ts
T
Chris Tate 677c74c78a Give integer clones borrowed records and worker programs
Integer clones run a pure number function on int32 values and fall back to
the ordinary body when a value leaves the int32 range. Until now a clone
could only take numbers, booleans and borrowed number arrays, and worker
programs had no clones. Recursive functions that walk a record graph, such
as a tree count, always ran on doubles.

A clone now also takes borrowed record parameters:

- A parameter qualifies when its type is a record or `R | null` for a
  record `R`, and its function borrows it.
- The clone reads number fields (exactly int32, otherwise it falls back),
  boolean fields and reference fields of those records in place, tests
  references against `null`, narrows them to the record arm (`null` falls
  back, so the ordinary body reports it), and passes them to other clones.
- A clone has no effect, so the records it reaches stay alive and unchanged
  while it runs: it reads them without retaining them.
- Fields that a clone cannot read in place (inline unions, optional
  properties, other kinds) keep the function on the ordinary path, as do
  class instances.

ELF worker executables now have clones too:

- The stack guard is the thread's own.
- A clone polls the thread's pending word, which a stop request also sets,
  when it is called and every 64 iterations of a loop over plain tail
  calls. On a set word it falls back, and its dispatcher forwards the
  pending exception or stop. Accumulating loops need no poll: their virtual
  stack bounds them.
- Worker programs on other targets keep no clones.

New corpus programs: integer-recursion-records.ts and
integer-recursion-records-workers.ts.
2026-10-27 21:02:26 -05:00

75 lines
2.0 KiB
TypeScript

// Pure integer recursion over record graphs on worker threads: tree
// counts, sums that leave the int32 range, and a stack overflow the worker
// catches.
import { isMainThread, parentPort, Worker, workerData } from "node:worker_threads";
interface Tree {
left: Tree | null;
right: Tree | null;
}
interface Item {
weight: number;
next: Item | null;
}
interface Task {
depth: number;
weight: number;
}
interface Report {
text: string;
}
function build(depth: number): Tree {
return depth > 0 ? { left: build(depth - 1), right: build(depth - 1) } : { left: null, right: null };
}
function count(node: Tree): number {
if (node.left === null || node.right === null) return 1;
return 1 + count(node.left) + count(node.right);
}
function total(item: Item): number {
return item.next === null ? item.weight : item.weight + total(item.next);
}
function run({ depth, weight }: Task): string {
let check = 0;
for (let i = 0; i < 4; i++) check += count(build(depth));
let head: Item | null = null;
for (let i = 0; i < 8; i++) head = { weight, next: head };
const ring: Item = { weight: 1, next: null };
ring.next = { weight: 2, next: ring };
let overflow = "";
try {
overflow = String(total(ring));
} catch (e) {
overflow = `${e instanceof RangeError} ${(e as Error).message}`;
}
return `depth ${depth}: check ${check}, total ${total(head!)}, ring ${overflow}`;
}
if (isMainThread) {
const tasks: Task[] = [
{ depth: 4, weight: 3 },
{ depth: 12, weight: 1e9 },
{ depth: 8, weight: 0.5 },
];
const results = await Promise.all(
tasks.map(
(task) =>
new Promise<string>((resolve) => {
const worker = new Worker(new URL(import.meta.url), { workerData: task });
worker.on("message", (report: Report) => resolve(report.text));
}),
),
);
for (const line of results) console.log(line);
console.log("main", run({ depth: 10, weight: -2147483648 }));
} else {
const report: Report = { text: run(workerData as Task) };
parentPort!.postMessage(report);
}