Skip to content

Commit e6d15b1

Browse files
committed
feat(threads): resolve non-JIT PR-249 tail (#428)
1 parent 4a1d962 commit e6d15b1

8 files changed

Lines changed: 823 additions & 376 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,10 @@ jobs:
7171
cmd: zig build threads-test -Dthreads-shard-index=3 -Dthreads-shard-count=4
7272
- name: threads-nogil-witness
7373
cmd: zig build threads-test -Dthreads-parallel-js=true -Dthreads-case=sync/condition-wait-notify.js
74-
# Reference-only corpus hygiene: fail if a non-promoted PR-249 file
75-
# loses its blocker classification or if one of the nearest
76-
# promotion probes starts passing or changes failure shape without the
77-
# allowlist/docs being updated in the same change.
74+
# PR-249 corpus hygiene: reject inventory/disposition drift and verify
75+
# the bounded terminal-premise probes against the installed runner.
7876
- name: threads-reference-audit
79-
cmd: zig build threads-reference-audit && python3 tools/threads-reference-audit.py --run-probes --expect-current-blockers --probe-timeout 60
77+
cmd: zig build threads-reference-audit threads-reference-probes
8078
- name: benchmark-comparison-harness
8179
cmd: zig build benchmark-comparison-test
8280
- name: wasm-feature-profiles

build.zig

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,20 +1384,21 @@ pub fn build(b: *std.Build) void {
13841384
"--check-inventory",
13851385
"--self-test-inventory",
13861386
});
1387-
const threads_reference_audit_step = b.step("threads-reference-audit", "Audit remaining reference-only PR-249 files");
1387+
const threads_reference_audit_step = b.step("threads-reference-audit", "Audit promoted, blocked, and terminal PR-249 files");
13881388
threads_reference_audit_step.dependOn(&threads_reference_audit_cmd.step);
13891389

13901390
const threads_reference_probes_cmd = b.addSystemCommand(&.{
13911391
"python3",
13921392
"tools/threads-reference-audit.py",
13931393
"--fail-on-uncategorized",
1394-
"--run-probes",
1395-
"--expect-current-blockers",
1394+
"--run-disposition-probes",
1395+
"--expect-terminal-dispositions",
13961396
"--skip-timeout-probes",
13971397
"--probe-timeout",
13981398
"20",
13991399
});
1400-
const threads_reference_probes_step = b.step("threads-reference-probes", "Verify quick PR-249 reference-only promotion blockers");
1400+
threads_reference_probes_cmd.step.dependOn(&threads_test_install.step);
1401+
const threads_reference_probes_step = b.step("threads-reference-probes", "Verify quick PR-249 terminal dispositions");
14011402
threads_reference_probes_step.dependOn(&threads_reference_probes_cmd.step);
14021403

14031404
// Concurrent-JS fuzzer: `zig build threadfuzz [-Dtsan] [-Dfuzz-iters=N] [-Dfuzz-seed=S]`.

conformance/threads_test.zig

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const allowlist = [_][]const u8{
2727
"dw1-sort-comparator-callsite-shapes.js",
2828
"dw1-sort-comparator-iterator-host.js",
2929
"dw1-sort-comparator-osr.js",
30+
"dw2-marklistset-storm.js",
3031
"api/condition-basic.js",
3132
"api/condition-async-wait.js",
3233
"api/condition-wait-termination.js",
@@ -65,6 +66,11 @@ const allowlist = [_][]const u8{
6566
"bench/megamorphic-access.js",
6667
"bench/transition-heavy-constructor.js",
6768
"congc-t1-window-split.js",
69+
"congc-t3-barrier-storm.js",
70+
"congc-t4-alloc-steal-storm.js",
71+
"congc-t5-celllock-audit.js",
72+
"congc-t9-attach-exit-churn.js",
73+
"congc-t11-diagnostics.js",
6874
"cve/mc-aint-terminate-notify-park-race.js",
6975
"cve/mc-code-deferred-fire-stale-window.js",
7076
"cve/mc-code-sleep-through-jettison-isb.js",
@@ -77,6 +83,8 @@ const allowlist = [_][]const u8{
7783
"cve/mc-gc-blocked-native-roots.js",
7884
"cve/mc-gc-finreg-cross-thread-gc.js",
7985
"cve/mc-gc-thread-shell-finalizer-storm.js",
86+
"cve/mc-gc-weakgcmap-registry-vs-prune.js",
87+
"cve/mc-grow-buffer-storm.js",
8088
"cve/mc-grow-s4-detach-nullvec-repro.js",
8189
"cve/mc-grow-wasm-relocating-grow.js",
8290
"cve/mc-hand-dead-registrant-settle.js",
@@ -284,6 +292,29 @@ fn usesBenchHarness(name: []const u8) bool {
284292
std.mem.eql(u8, name, "jit/fires-per-sec.js");
285293
}
286294

295+
fn appendCaseSource(gpa: std.mem.Allocator, buf: *std.ArrayListUnmanaged(u8), name: []const u8, source: []const u8) !void {
296+
if (builtin.sanitize_thread and std.mem.eql(u8, name, "dw2-marklistset-storm.js")) {
297+
// Preserve two worker sort/apply lanes plus the main mutator and an
298+
// explicit GC requester,
299+
// and the explicit r=16 GC request while bounding TSan's >100x cost.
300+
// Normal builds execute the untouched 16 x 120 shape; the sanitizer
301+
// gate executes 2 x 18 plus main/reference passes. Fail closed if the
302+
// pinned fixture changes instead of silently running another shape.
303+
const workers_needle = "const W = HAVE_THREADS ? 16 : 1;";
304+
const rounds_needle = "const ROUNDS = 120;";
305+
const workers_at = std.mem.indexOf(u8, source, workers_needle) orelse return error.CorpusFixtureDrift;
306+
const rounds_at = std.mem.indexOf(u8, source, rounds_needle) orelse return error.CorpusFixtureDrift;
307+
if (rounds_at <= workers_at) return error.CorpusFixtureDrift;
308+
try buf.appendSlice(gpa, source[0..workers_at]);
309+
try buf.appendSlice(gpa, "const W = HAVE_THREADS ? 2 : 1;");
310+
try buf.appendSlice(gpa, source[workers_at + workers_needle.len .. rounds_at]);
311+
try buf.appendSlice(gpa, "const ROUNDS = 18;");
312+
try buf.appendSlice(gpa, source[rounds_at + rounds_needle.len ..]);
313+
return;
314+
}
315+
try buf.appendSlice(gpa, source);
316+
}
317+
287318
fn parallelJsBudgetSkip(name: []const u8) bool {
288319
_ = name;
289320
return false;
@@ -717,7 +748,7 @@ pub fn main(init: std.process.Init) !void {
717748
try buf.appendSlice(gpa, vmstate_workload_src);
718749
try buf.appendSlice(gpa, "\n");
719750
}
720-
try buf.appendSlice(gpa, test_src);
751+
try appendCaseSource(gpa, &buf, name, test_src);
721752

722753
// Per-file configs, mirroring their run-tests.sh / //@ runDefault
723754
// lines: blocking-gate runs can-block-is-false; thread-id-bounds runs
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
{
2+
"schema_version": 1,
3+
"issue": 428,
4+
"date": "2026-07-21",
5+
"source_head": "3a14f2a821ac56fcb01d1c765200be7e9dfdb458",
6+
"runner": {
7+
"build": "zig build threads-test-bin -Doptimize=ReleaseFast",
8+
"default": "zig-out/bin/threads-test one <case>",
9+
"parallel_js": "zig-out/bin/threads-test parallel-js one <case>",
10+
"tsan_parallel_js": "zig build threads-test-bin -Dtsan=true -Doptimize=ReleaseSafe; zig-out/bin/threads-test parallel-js one <promoted-case>",
11+
"tsan_ci_debug": "zig build threads-test-bin -Dtsan=true; zig-out/bin/threads-test parallel-js one dw2-marklistset-storm.js",
12+
"tsan_dw2_profile": "2 workers x 18 rounds plus main; worker seed 0 requests GC at r=16 while worker seed 1 and main mutate (normal build remains 16 x 120)"
13+
},
14+
"summary": {
15+
"cases": 14,
16+
"mode_runs": 28,
17+
"promoted": 8,
18+
"terminal_dispositions": 6,
19+
"tsan_mode_runs": 8,
20+
"tsan_pass": 8,
21+
"tsan_ci_debug_runs": 1,
22+
"tsan_ci_debug_pass": 1
23+
},
24+
"cases": [
25+
{
26+
"case": "api/wasm-refused-sd7.js",
27+
"resolution": "terminal-disposition",
28+
"default": { "status": "fail", "elapsed_ms": 2 },
29+
"parallel_js": { "status": "fail", "elapsed_ms": 2 },
30+
"evidence": "expected TypeError but got no-throw"
31+
},
32+
{
33+
"case": "congc-t2-lockorder-lint.js",
34+
"resolution": "terminal-disposition",
35+
"default": { "status": "pass", "elapsed_ms": 1 },
36+
"parallel_js": { "status": "pass", "elapsed_ms": 2 },
37+
"evidence": "only the trailing arithmetic sanity branch executes"
38+
},
39+
{
40+
"case": "congc-t3-barrier-storm.js",
41+
"resolution": "promoted",
42+
"default": { "status": "pass", "elapsed_ms": 1112 },
43+
"parallel_js": { "status": "pass", "elapsed_ms": 1785 },
44+
"tsan_parallel_js": { "status": "pass", "elapsed_ms": 244001 }
45+
},
46+
{
47+
"case": "congc-t4-alloc-steal-storm.js",
48+
"resolution": "promoted",
49+
"default": { "status": "pass", "elapsed_ms": 717 },
50+
"parallel_js": { "status": "pass", "elapsed_ms": 1309 },
51+
"tsan_parallel_js": { "status": "pass", "elapsed_ms": 150481 }
52+
},
53+
{
54+
"case": "congc-t5-celllock-audit.js",
55+
"resolution": "promoted",
56+
"default": { "status": "pass", "elapsed_ms": 500 },
57+
"parallel_js": { "status": "pass", "elapsed_ms": 720 },
58+
"tsan_parallel_js": { "status": "pass", "elapsed_ms": 42376 }
59+
},
60+
{
61+
"case": "congc-t8-stop-interleaving.js",
62+
"resolution": "terminal-disposition",
63+
"default": { "status": "pass", "elapsed_ms": 3 },
64+
"parallel_js": { "status": "pass", "elapsed_ms": 2 },
65+
"evidence": "the sharedHeapTest-gated body is not entered"
66+
},
67+
{
68+
"case": "congc-t9-attach-exit-churn.js",
69+
"resolution": "promoted",
70+
"default": { "status": "pass", "elapsed_ms": 190 },
71+
"parallel_js": { "status": "pass", "elapsed_ms": 329 },
72+
"tsan_parallel_js": { "status": "pass", "elapsed_ms": 21689 }
73+
},
74+
{
75+
"case": "congc-t11-diagnostics.js",
76+
"resolution": "promoted",
77+
"default": { "status": "pass", "elapsed_ms": 131 },
78+
"parallel_js": { "status": "pass", "elapsed_ms": 395 },
79+
"tsan_parallel_js": { "status": "pass", "elapsed_ms": 27777 }
80+
},
81+
{
82+
"case": "cve/mc-df-arraycopy-relabel.js",
83+
"resolution": "terminal-disposition",
84+
"default": { "status": "fail", "elapsed_ms": 2 },
85+
"parallel_js": { "status": "fail", "elapsed_ms": 2 },
86+
"evidence": "RangeError: offset is out of bounds"
87+
},
88+
{
89+
"case": "cve/mc-gc-weakgcmap-registry-vs-prune.js",
90+
"resolution": "promoted",
91+
"default": { "status": "pass", "elapsed_ms": 247 },
92+
"parallel_js": { "status": "pass", "elapsed_ms": 321 },
93+
"tsan_parallel_js": { "status": "pass", "elapsed_ms": 10293 }
94+
},
95+
{
96+
"case": "cve/mc-grow-buffer-storm.js",
97+
"resolution": "promoted",
98+
"default": { "status": "pass", "elapsed_ms": 1215 },
99+
"parallel_js": { "status": "pass", "elapsed_ms": 1434 },
100+
"tsan_parallel_js": { "status": "pass", "elapsed_ms": 61561 }
101+
},
102+
{
103+
"case": "cve/mc-life-creator-thread-dies.js",
104+
"resolution": "terminal-disposition",
105+
"default": { "status": "fail", "elapsed_ms": 4 },
106+
"parallel_js": { "status": "fail", "elapsed_ms": 5 },
107+
"evidence": "TypeError: Cannot construct a TypedArray on a detached buffer"
108+
},
109+
{
110+
"case": "dw2-marklistset-storm.js",
111+
"resolution": "promoted",
112+
"default": { "status": "pass", "elapsed_ms": 4512 },
113+
"parallel_js": { "status": "pass", "elapsed_ms": 5909 },
114+
"tsan_parallel_js": { "status": "pass", "elapsed_ms": 26726 },
115+
"tsan_ci_debug": { "status": "pass", "elapsed_ms": 326285 }
116+
},
117+
{
118+
"case": "w16-c1-prevent-collection.js",
119+
"resolution": "terminal-disposition",
120+
"default": { "status": "pass", "elapsed_ms": 3823 },
121+
"parallel_js": { "status": "pass", "elapsed_ms": 7890 },
122+
"evidence": "snapshot/preventCollection branches are unavailable"
123+
}
124+
]
125+
}

0 commit comments

Comments
 (0)