From b9241fe8f68f502cfdc6cf3ee150203a8228c948 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 8 Oct 2026 16:55:10 -0500 Subject: [PATCH 1/3] Fix bootstrap checks after stacked CI changes - Require the bootstrap seed while accepting scalar and list workflow dependencies. - Preserve stacked CI validation and instrumented seed safeguards. --- tests/harness/bootstrap-seed.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/harness/bootstrap-seed.test.ts b/tests/harness/bootstrap-seed.test.ts index 101119c3c..08d03203e 100644 --- a/tests/harness/bootstrap-seed.test.ts +++ b/tests/harness/bootstrap-seed.test.ts @@ -43,7 +43,7 @@ test("CI restores each seed only into its matching instrumented bootstrap lane", jobs: Record< string, { - needs?: string; + needs?: string | string[]; strategy: { matrix: { flavor?: string[]; include?: { flavor: string }[] } }; env: Record; steps: { @@ -58,7 +58,8 @@ test("CI restores each seed only into its matching instrumented bootstrap lane", }; const seed = workflow.jobs.bootstrap_seed!; const checks = workflow.jobs.bootstrap!; - expect(checks.needs).toBe("bootstrap_seed"); + const dependencies = Array.isArray(checks.needs) ? checks.needs : [checks.needs]; + expect(dependencies).toContain("bootstrap_seed"); expect( seed.steps .filter((step) => step.uses === "./.github/actions/native-packages") From a52126b87117d472dec01509b3e9309e206d3555 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 8 Oct 2026 17:40:35 -0500 Subject: [PATCH 2/3] Fix native bootstrap callback narrowing - Check callback IR shapes before selecting the fast path. - Align the emitter contract with inline cycle deallocation. --- .../src/frontend/lowering/containers/array-callbacks.ts | 8 ++++++-- tests/harness/self-hosting-llvm-emitter-cases.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 00847a558..87b00589a 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -416,8 +416,12 @@ function withFastCallback(fn: IrFunction, fastT: CallbackType | null, elem: IrTy return null; const at = e.args.findIndex(isValueRead); if (at < 0) return null; - const read = e.args[at] as IrExpr & { kind: "ternary" }; - const state = (read.cond as IrExpr & { kind: "bin" }).left as IrExpr & { kind: "arrayState" }; + const read = e.args[at]!; + if (read.kind !== "ternary") return null; + const condition = read.cond; + if (condition.kind !== "bin") return null; + const state = condition.left; + if (state.kind !== "arrayState") return null; const fastArgs = e.args.slice(); fastArgs[at] = { kind: "arrayGet", diff --git a/tests/harness/self-hosting-llvm-emitter-cases.ts b/tests/harness/self-hosting-llvm-emitter-cases.ts index 8b3b00c96..8db8b76b5 100644 --- a/tests/harness/self-hosting-llvm-emitter-cases.ts +++ b/tests/harness/self-hosting-llvm-emitter-cases.ts @@ -62,7 +62,7 @@ export function llvmEmitterCases(): LlvmEmitterCase[] { "@scr_str_release", "@scr_arr_release", ); - find("recursive record trace and teardown").contains.push("@scr_cyc_alloc", "@scr_cyc_free"); + find("recursive record trace and teardown").contains.push("@scr_cyc_alloc", "@scr_cyc_live"); find("closure capture boxes").contains.push("@sc_retain_box", "@scr_closure_new"); find("synchronous callback environment").contains.push( "alloca { %ScrClosure, [1 x ptr] }", From df227082a0f76c69003c031c9530cf4e3e45028a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 8 Oct 2026 18:21:03 -0500 Subject: [PATCH 3/3] Fix console ordering checks under pipe backpressure - Keep merged-pipe ordering probes small enough for deterministic Node output. - Preserve large-write coverage for shared files and independent streams. --- tests/fixtures/console-io/merged-order.ts | 23 ++++++++++--------- tests/harness/console-io.test.ts | 28 ++++++++++++++++++----- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/tests/fixtures/console-io/merged-order.ts b/tests/fixtures/console-io/merged-order.ts index 5ae9f9412..b7ac9ec65 100644 --- a/tests/fixtures/console-io/merged-order.ts +++ b/tests/fixtures/console-io/merged-order.ts @@ -1,8 +1,9 @@ -// stdout and stderr redirected to ONE descriptor (2>&1): every console and -// process stream write must land in source order, as Node submits each chunk -// before returning. Covers single-string and multi-argument lines, raw writes -// without newlines, lines larger than the runtime's on-stack buffer and the -// 64 KiB stdio buffer, and writes from a later event-loop turn. +// With --small, the complete output fits below pipe capacity so Node's +// independent asynchronous stdout/stderr queues cannot interleave chunks. +// The default covers large writes to a shared file or separate pipes, +// including lines beyond the runtime's stack buffer and 64 KiB stdio buffer. +const large = process.argv[2] !== "--small"; + function repeat(unit: string, count: number): string { let out = ""; for (let i = 0; i < count; i++) out += unit; @@ -20,12 +21,12 @@ for (let i = 0; i < 50; i++) { if (i % 3 === 0) console.error("loop err", i); else console.log(`loop out ${i}`); } -console.log(repeat("o", 5000)); -console.error(repeat("e", 5000)); -console.log("big", repeat("O", 70000), 1); -console.error(repeat("E", 70000)); -process.stdout.write(repeat("w", 3000)); -process.stderr.write(repeat("v", 3000) + "\n"); +console.log(repeat("o", large ? 5000 : 50)); +console.error(repeat("e", large ? 5000 : 50)); +console.log("big", repeat("O", large ? 70000 : 100), 1); +console.error(repeat("E", large ? 70000 : 100)); +process.stdout.write(repeat("w", large ? 3000 : 50)); +process.stderr.write(repeat("v", large ? 3000 : 50) + "\n"); console.log("|end of sync"); setTimeout(() => { console.error("timer err"); diff --git a/tests/harness/console-io.test.ts b/tests/harness/console-io.test.ts index 90b7fac25..874b1b544 100644 --- a/tests/harness/console-io.test.ts +++ b/tests/harness/console-io.test.ts @@ -153,21 +153,37 @@ describe(`console/process output visibility${sanitize ? " (sanitized)" : ""}`, ( expect(nativeRes.signal).toBe(nodeRes.signal); }); - /* stdout and stderr share one pipe or one regular file (2>&1), so the - * bytes record the exact submission order across both streams. */ + /* A bounded probe cannot fill the shared pipe: Node submits these writes + * immediately. With backpressure, its independent asynchronous stdout and + * stderr queues can interleave larger chunks, so compare large pipe output + * per stream and retain the shared-file ordering check below. */ const posix = process.platform !== "win32"; test.skipIf(!posix)("stdout/stderr merged into one pipe keep Node's order", async () => { const probe = await build("merged-order"); const merged = (cmd: string, args: string[]) => runToClose("sh", ["-c", 'exec "$@" 2>&1', "sh", cmd, ...args]); const [nodeRes, nativeRes] = await Promise.all([ - merged("node", [probe.sourceFile]), - merged(probe.binary, []), + merged("node", [probe.sourceFile, "--small"]), + merged(probe.binary, ["--small"]), ]); expect(nodeRes.code).toBe(0); expect(nativeRes.code).toBe(0); - expect(nativeRes.stdout.length).toBe(nodeRes.stdout.length); - expect(nativeRes.stdout.equals(nodeRes.stdout)).toBe(true); + expect(nodeRes.stdout.length).toBeLessThan(4096); + expect(nativeRes.stdout).toEqual(nodeRes.stdout); + }); + + test("large stdout/stderr pipe writes match Node per stream", async () => { + const probe = await build("merged-order"); + const [nodeRes, nativeRes] = await Promise.all([ + runToClose("node", [probe.sourceFile]), + runToClose(probe.binary, []), + ]); + expect(nodeRes.code).toBe(0); + expect(nativeRes.code).toBe(0); + expect(nodeRes.stdout.length).toBeGreaterThan(65536); + expect(nodeRes.stderr.length).toBeGreaterThan(65536); + expect(nativeRes.stdout).toEqual(nodeRes.stdout); + expect(nativeRes.stderr).toEqual(nodeRes.stderr); }); test.skipIf(!posix)("stdout/stderr merged into one file keep Node's order", async () => {