From 54aba6a60658d518594e2817076dfd4d6d75b746 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 27 Oct 2026 13:27:37 -0700 Subject: [PATCH 1/2] Build the scr_map semantics test at -O0 The scr_map unit test compiles 23 runtime translation units with ASan/UBSan, and that build is essentially the whole test: 90s of clang against a 2.1s binary run. scr_hash.c alone takes 29s at -O1 because the sanitizer instruments its unrolled digest rounds; scr_json.c and scr_lib.c add 28s more. The test then normally ran within 20s of its 120s timeout, and whenever a lane shared the box it repeatedly timed out in both the plain and sanitized lanes. The link set cannot shrink: scr_map.c's dyn ops need scr_dyn_* from scr_json.c, scr_lib.c supplies the remaining date/math/number/perf symbols, and scr_lib.c needs scr_hash.c. The test asserts Map semantics under sanitizers, not codegen, and its cost checks are relative bounds, so build it at -O0: every assertion and sanitizer check still runs and the build drops to about 15s. Eight concurrent copies now take 20-22s each instead of 90s, with the timeout unchanged. Co-Authored-By: Claude Opus 5.5 --- packages/runtime/src/scr_map.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/scr_map.test.ts b/packages/runtime/src/scr_map.test.ts index b567884fc..035b3a0eb 100644 --- a/packages/runtime/src/scr_map.test.ts +++ b/packages/runtime/src/scr_map.test.ts @@ -14,7 +14,14 @@ test("number-key maps keep Map semantics across linear, direct and hashed indexe try { await exec("clang", [ "-std=c11", - "-O1", + // The runtime link set includes scr_json.c, scr_lib.c and scr_hash.c. + // With ASan/UBSan their optimized builds dominate the test's cost: -O1 + // takes about 90s (scr_hash.c alone 29s, its unrolled digest rounds + // instrumented) and regularly starved past this test's timeout when the + // lane shared the box. The test checks Map semantics under sanitizers, + // not codegen, so -O0 keeps every assertion and sanitizer check and + // builds in about 15s. + "-O0", "-Wall", "-Wextra", "-fsanitize=address,undefined", From 458fe7d54a4a59a3cb96a134b67de58f42ff7962 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 27 Oct 2026 15:27:08 -0700 Subject: [PATCH 2/2] Give the three-library release and sanitized cases 1200s timeouts The test compiles the three npm package twice, dev and release, and both cases shared one 600s budget through test.each. Release compiles the whole library into one LLVM module that scriptc-llvm-codegen optimizes and codegens in a single process on a single core: 426s of the 477s total run in the native-archive phase alone. Across sandbox lanes release measured 331, 335, 339, 345, 348, 350, 358, 428, 450, 511, 521 and 581s, so whole- box speed varies about 1.7x and the slowest lanes sat within 20s of the budget; the sanitized dev case hit 600s twice. There is no regression, the work is fixed and single-threaded. Generate the two cases in a loop so each gets its own timeout: 1200_000ms for release and for dev under SCRIPTC_SAN, and the unchanged 600_000ms else. Test names are byte-identical to the .each %s expansion, so skipUnownedTest keeps assigning each case to the same shard. Co-Authored-By: Claude Opus 5.5 --- tests/harness/three-library.test.ts | 138 +++++++++++++++------------- 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/tests/harness/three-library.test.ts b/tests/harness/three-library.test.ts index 23aa4c5e6..b14c9b779 100644 --- a/tests/harness/three-library.test.ts +++ b/tests/harness/three-library.test.ts @@ -7,59 +7,68 @@ import { skipUnownedTest } from "./shard.js"; const fixture = join(import.meta.dirname, "../library-mode/wasm"); const hasClang = spawnSync("clang", ["--version"]).status === 0; +// Release compiles the whole three.js library into one LLVM module that a +// single process optimizes and codegens on one core, which takes 5-10 +// minutes; sanitized dev builds are slower still. Lanes measured 331-581s for +// release against the old 600s budget, so both get 1_200_000ms. The plain dev +// case runs at about a fifth of release and keeps 600_000ms. +function timeoutFor(optimization: "dev" | "release"): number { + return optimization === "release" || process.env["SCRIPTC_SAN"] === "1" ? 1_200_000 : 600_000; +} // The two optimizations take similar time (about 10 minutes each under ASan): // SCRIPTC_TEST_SHARD runs them on different shards (tests/harness/shard.ts). // skipUnownedTest partitions a describe block's tests. describe("three.js native library", () => { beforeEach((context) => skipUnownedTest(context, {})); - test.skipIf(!hasClang).each(["dev", "release"] as const)( - "three.js sends animated geometry, materials and resource lifecycle to a native host in %s", - async (optimization) => { - const directory = await mkdtemp("/tmp/scriptc-three-library-"); - const sanitize = process.env["SCRIPTC_SAN"] === "1"; - try { - const profile = JSON.parse(await readFile(join(fixture, "three.json"), "utf8")); - const profilePath = join(directory, "profile.json"); - await writeFile( - profilePath, - JSON.stringify({ - ...profile, - name: "three-native-embedding", - entry: join(fixture, "three.mjs"), - optimization, - }), - ); - const built = await compileLibrary({ profilePath, outDir: directory, sanitize }); - if (!built.ok) - throw new Error( - built.diagnostics - .map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`) - .join("\n"), + for (const optimization of ["dev", "release"] as const) { + test.skipIf(!hasClang)( + `three.js sends animated geometry, materials and resource lifecycle to a native host in ${optimization}`, + async () => { + const directory = await mkdtemp("/tmp/scriptc-three-library-"); + const sanitize = process.env["SCRIPTC_SAN"] === "1"; + try { + const profile = JSON.parse(await readFile(join(fixture, "three.json"), "utf8")); + const profilePath = join(directory, "profile.json"); + await writeFile( + profilePath, + JSON.stringify({ + ...profile, + name: "three-native-embedding", + entry: join(fixture, "three.mjs"), + optimization, + }), ); - const binary = join(directory, "host"); - const linked = spawnSync( - "clang", - [ - "-std=c11", - ...(sanitize ? ["-fsanitize=address,undefined"] : []), - join(fixture, "three-native.c"), - built.archivePath, - "-lm", - "-o", - binary, - ], - { encoding: "utf8" }, - ); - expect(linked.status, linked.stderr).toBe(0); - const actual = spawnSync(binary, [], { encoding: "utf8", timeout: 30_000 }); - expect(actual.status, actual.stderr).toBe(0); - const reference = spawnSync( - process.execPath, - [ - "--no-warnings", - "--input-type=module", - "-e", - ` + const built = await compileLibrary({ profilePath, outDir: directory, sanitize }); + if (!built.ok) + throw new Error( + built.diagnostics + .map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`) + .join("\n"), + ); + const binary = join(directory, "host"); + const linked = spawnSync( + "clang", + [ + "-std=c11", + ...(sanitize ? ["-fsanitize=address,undefined"] : []), + join(fixture, "three-native.c"), + built.archivePath, + "-lm", + "-o", + binary, + ], + { encoding: "utf8" }, + ); + expect(linked.status, linked.stderr).toBe(0); + const actual = spawnSync(binary, [], { encoding: "utf8", timeout: 30_000 }); + expect(actual.status, actual.stderr).toBe(0); + const reference = spawnSync( + process.execPath, + [ + "--no-warnings", + "--input-type=module", + "-e", + ` const events=[]; for (const name of ["vertex","surface","resource"]) globalThis[name]=(...values)=>events.push([name,...values.map(value=>Number(value.toFixed(9)))]); const {frame,dispose}=await import(${JSON.stringify(join(fixture, "three.mjs"))}); @@ -67,21 +76,22 @@ describe("three.js native library", () => { dispose(); console.log(JSON.stringify(events)); `, - ], - { encoding: "utf8" }, - ); - expect(reference.status, reference.stderr).toBe(0); - const events = JSON.parse(reference.stdout); - expect( - actual.stdout - .trim() - .split("\n") - .map((line) => JSON.parse(line)), - ).toEqual([...events, ...events]); - } finally { - await rm(directory, { recursive: true, force: true }); - } - }, - 600_000, - ); + ], + { encoding: "utf8" }, + ); + expect(reference.status, reference.stderr).toBe(0); + const events = JSON.parse(reference.stdout); + expect( + actual.stdout + .trim() + .split("\n") + .map((line) => JSON.parse(line)), + ).toEqual([...events, ...events]); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }, + timeoutFor(optimization), + ); + } });