mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
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.
75 lines
2.0 KiB
TypeScript
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);
|
|
}
|