mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Native async bodies whose only suspension points are their own awaits now compile to LLVM switched coroutines, the lowering WASI async bodies and native synchronous generators already use. A suspended call keeps a heap frame of the values live across its awaits instead of a fiber with its own stack. Bodies that reach other suspension points (async generator consumption, dyn awaits, node:test subtests), module evaluation bodies, dyn results, worker and library programs, and Windows keep their fibers. The emitter selects bodies by emitting them as usual first: that text may suspend only through await runtime calls and await-call entries, and may call no function that parks. The coroutine form is then emitted and checked again. Each await prepares its continuation in the runtime (a pending promise's waiter list, or one READY-queue turn, continuing in place exactly where a fiber's hop would) and extracts the settled result after resuming. The eager prefix runs on the spawner's stack and moves to a fresh stack near the stack guard, so deep async recursion keeps working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
// Many concurrently suspended stackless async calls: twenty thousand calls
|
|
// park on timers and on one shared pending promise at once, chains of
|
|
// nested awaits suspend at their leaves, and each continuation still
|
|
// resumes in Node's order with its own locals intact.
|
|
const count = 20000;
|
|
let resolveGate: (v: number) => void = () => {};
|
|
const gate = new Promise<number>((resolve) => {
|
|
resolveGate = resolve;
|
|
});
|
|
const order: number[] = [];
|
|
|
|
function sleep(ms: number): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
|
|
async function parked(i: number): Promise<number> {
|
|
const local = i * 3;
|
|
const label = "task" + i;
|
|
const g = await gate;
|
|
if (i % 4000 === 0) order.push(i);
|
|
await sleep(i % 3);
|
|
return local + g + label.length;
|
|
}
|
|
|
|
async function chain(depth: number, i: number): Promise<number> {
|
|
if (depth === 0) {
|
|
const g = await gate;
|
|
return g + i;
|
|
}
|
|
const below = await chain(depth - 1, i);
|
|
return below + depth;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const tasks: Promise<number>[] = [];
|
|
for (let i = 0; i < count; i++) tasks.push(parked(i));
|
|
const chains: Promise<number>[] = [];
|
|
for (let i = 0; i < 500; i++) chains.push(chain(12, i));
|
|
console.log("spawned", tasks.length, chains.length);
|
|
await sleep(1);
|
|
resolveGate(5);
|
|
let sum = 0;
|
|
for (const t of tasks) sum += await t;
|
|
let chainSum = 0;
|
|
for (const c of chains) chainSum += await c;
|
|
console.log("sum", sum, "chains", chainSum, "order", order.join(","));
|
|
}
|
|
|
|
void main();
|