-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathbuild.zig
More file actions
1531 lines (1459 loc) · 84 KB
/
Copy pathbuild.zig
File metadata and controls
1531 lines (1459 loc) · 84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
const std = @import("std");
// TODO(adr-0009): drop zlinter dep when Zig ships @deprecated()
// builtin + -fdeprecated flag (ziglang/zig#22822, accepted on
// urgent milestone, expected 0.17+). Tracked in
// .dev/proposal_watch.md.
//
// D-274 (accepted): this top-level comptime `@import` makes zlinter an
// EAGER dependency — a library consumer pulling zwasm transitively fetches
// the lint tool. `.lazy = true` cannot fix it (the unconditional comptime
// `@import` resolves zlinter regardless of the lazy flag, and zlinter's
// `builder()` build-helper API is only reachable via this `@import`, not via
// `b.lazyDependency`). The eager fetch is a one-time cached cost that
// dissolves entirely when this dep is dropped at Zig 0.17+ (the TODO above),
// so the lazy restructuring is not worth it.
const zlinter = @import("zlinter");
// Single source of truth for the version string: read it from build.zig.zon
// and thread it through `build_options` so `zwasm.version` / `--version`
// can never drift from the published package version (and the tag).
const zon = @import("build.zig.zon");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// ROADMAP §4.6 — coarse, orthogonal feature flags.
// -Dwasm : Wasm spec level (3.0 default)
// -Dwasi : WASI version inclusion
// -Dengine : engine selection (interp / jit / both)
// -Dstrip : strip debug info from the CLI binary
//
// Per-proposal feature gating happens via dispatch-table
// registration (ROADMAP §4.5 / A12), not pervasive build-time
// `if` branches.
const wasm_level = b.option(WasmLevel, "wasm", "Wasm spec level (default 3.0)") orelse .v3_0;
const wasi_level = b.option(WasiLevel, "wasi", "WASI version inclusion: none/p1/p2/p3 ordered tier (default p2; p3=Preview-3 async, ADR-0193)") orelse .p2;
const engine_mode = b.option(EngineMode, "engine", "Engine selection (default both)") orelse .both;
const enable_strip = b.option(bool, "strip", "Strip debug info from the CLI binary") orelse false;
const strip_opt: ?bool = if (enable_strip) true else null;
// ADR-0015 §Decision Part 2 (§9.6 / 6.K.7): -Dsanitize=address
// wires LLVM AddressSanitizer + UBSan via Zig 0.16's
// `module.sanitize_c = .full`. -Dsanitize=thread enables
// ThreadSanitizer. Both Mac aarch64 + Linux x86_64 only —
// Windows ucrt skipped because clang ASan/Win32 needs an MSVC
// redist that doesn't ship through the Nix dev shell.
// Adopted as a weekly Linux x86_64 lane, not per-commit (~2× slower).
const sanitize = b.option(SanitizeMode, "sanitize", "Sanitizer (off / address / thread). Mac+Linux only.") orelse .off;
const is_windows = target.result.os.tag == .windows;
const sanitize_c: ?std.zig.SanitizeC = if (is_windows) null else switch (sanitize) {
.off => null,
.address => .full,
.thread => null,
};
const sanitize_thread: ?bool = if (is_windows) null else switch (sanitize) {
.off, .address => null,
.thread => true,
};
// Bundled into a single value so call sites use one short
// `createSanitizedModule(b, sanitize_opts, .{...})` per module
// instead of `createModule + applySanitize` boilerplate (D-016
// discharge).
const sanitize_opts: SanitizeOpts = .{ .c = sanitize_c, .thread = sanitize_thread };
// Repro task name for `zig build run-repro -Dtask=<name>` per
// ADR-0015 §Decision Part 4. Discovers
// `private/dbg/<task>/repro.zig` and links it against the
// zwasm-lib module. Step is silent when -Dtask is unset.
const repro_task = b.option([]const u8, "task", "Repro task name (private/dbg/<task>/repro.zig)");
// ADR-0028 / D-022: Diagnostic M3-a trace ringbuffer compile-time
// gate. Default false so release builds emit zero trace code in
// hot paths (per ROADMAP §A12). Enable on debug / audit runs via
// `-Dtrace-ringbuffer=true`.
const trace_ringbuffer = b.option(bool, "trace-ringbuffer", "Compile in Diagnostic M3-a trace ringbuffer (default: false)") orelse false;
// ADR-0164 B / D-292: stack-probe + trap-stub diagnostic prints
// (`[stack_probe] …` setup probe + `[d-165] kind=4 …` trap-stub entry count).
// These are D-245/D-165/D-279 Win64 investigation primitives; default false
// so even Debug `zig build test` stderr is clean (the prints fired once per
// process on the first JIT call). Win64 heisenbug (D-279) work re-enables via
// `-Dtrace-stackprobe=true`.
const trace_stackprobe = b.option(bool, "trace-stackprobe", "Compile in the [stack_probe]/[d-165] JIT diagnostic prints (default: false)") orelse false;
// ADR-0115 §3 — `-Dgc=true|false` zero-overhead compile-time
// gate. `false` (default for Phase 10 v0.1 since WasmGC ops
// aren't dispatched yet) means GC heap allocator + collector
// vtable + root walk all skip at runtime; future cycles add
// the dispatch-side comptime check that strips op_gc handlers
// via DCE when `enable_gc=false` (WAMR-equivalent nuclear
// strip per ADR-0115 §3). `true` opts the feature in once
// op_gc lands. Pairs with `Module.needs_gc_heap` parse-time
// predicate — the runtime gate at instantiate already
// skips heap materialisation when needs_gc_heap=false, so
// `enable_gc=false` is the additional source-level strip for
// module-construction code paths.
const enable_gc = b.option(bool, "gc", "Enable WasmGC heap+collector compile-in (default: false; per ADR-0115 §3)") orelse false;
// Zig does NOT bundle compiler-rt into a static library by default (the
// implicit default only covers exe + dynamic lib), so a NON-zig linker
// consuming libzwasm.a is left with undefined `__zig_probe_stack`
// (x86_64-macos) and the `__divti3`-class builtins. The latter are usually
// covered by the consumer's own runtime (clang_rt / libgcc / Rust's
// compiler_builtins); `__zig_probe_stack` has no non-Zig provider, so it
// is a hard link failure (#153). Opt-in, same spelling as v1.
const bundle_compiler_rt = b.option(bool, "compiler-rt", "Bundle Zig compiler-rt into libzwasm.a (default: false; set true for external non-zig linkers)") orelse false;
// ADR-0193 — the Component Model + WASI-P2 host is gated by the WASI
// tier, NOT a separate `-Dcomponent` flag (removed — it duplicated the
// gate and admitted contradictory combos like `-Dwasi=p1 -Dcomponent=true`).
// `wasi_level >= .p2` IS the component substrate (ADR-0181 §1.2 floor;
// wasmtime-standard default). The lean opt-out is now `-Dwasi=p1`, which
// strips the whole `src/feature/component/` + P2-host subsystem (~156 KB
// of a 1.9 MB ReleaseFast binary, measured at ADR-0182) via the same
// comptime fences that read `enable_component`.
const enable_component = @intFromEnum(wasi_level) >= @intFromEnum(WasiLevel.p2);
// ADR-0193 P3 — the P3/async host (component_wasi_p3.zig + component/async.zig)
// compiles only at `wasi_level >= .p3`. At the default `.p2` async is opt-in
// (`-Dwasi=p3`) — a p2 build emits zero p3-async symbols (DCE-assertable).
const enable_wasi_p3 = @intFromEnum(wasi_level) >= @intFromEnum(WasiLevel.p3);
const options = b.addOptions();
options.addOption(WasmLevel, "wasm_level", wasm_level);
options.addOption(WasiLevel, "wasi_level", wasi_level);
options.addOption(EngineMode, "engine_mode", engine_mode);
options.addOption(bool, "trace_ringbuffer", trace_ringbuffer);
options.addOption(bool, "trace_stackprobe", trace_stackprobe);
options.addOption(bool, "enable_gc", enable_gc);
options.addOption(bool, "enable_component", enable_component);
options.addOption(bool, "enable_wasi_p3", enable_wasi_p3);
options.addOption([]const u8, "version", zon.version);
// Build_options as a single shared module so both `core` and
// `exe_mod` (and any other consumer) reference the same Module.
// ADR-0028 requires `src/diagnostic/trace.zig` to import
// `build_options`; the previous double-`addOptions` shape made
// the auto-generated file the root of two modules ("build_options"
// and "build_options0") and broke compilation when both root
// modules (core + exe_mod) appeared in the same `zig build test`
// run. Sharing a single Module via `b.addModule` deduplicates.
const build_options_mod = options.createModule();
// ============================================================
// `core` module — the shared library Module per ADR-0024 D-1.
// Rooted at `src/zwasm.zig` so transitive `@import("../X")`
// chains stay inside `src/` (the subtree restriction Zig 0.16
// enforces). Used as `.root_module` by:
// - libzwasm.a (static lib)
// - test runners (spec / wast / realworld / wasi / etc.)
// - the CLI exe's root_module imports it by name (Bun-style
// self-import + Ghostty-style multi-artifact reuse)
// ADR-0024 D-2 carves out `src/zwasm.zig` as the single
// re-export hub and test loader.
// ============================================================
// Public, named module so external `build.zig.zon` path-dep
// consumers can pull the Zig facade via
// `b.dependency("zwasm", .{}).module("zwasm")` (ADR-0109 / §16.5
// dogfooding). Internal artifacts (CLI exe, examples, test
// runners) reuse this same `*Module` directly. `b.addModule`
// (not `createModule`) is what registers it under the "zwasm"
// name for dependents.
const core = b.addModule("zwasm", .{
.root_source_file = b.path("src/zwasm.zig"),
.target = target,
.optimize = optimize,
.strip = strip_opt,
// §9.3 / 3.3: the C API binding's Engine carries
// `std.heap.c_allocator`, which requires libc linkage.
// Linking unconditionally is fine — zwasm v2 is a libc-
// adjacent runtime (wasm-c-api consumers are C hosts).
.link_libc = true,
});
applySanitize(core, sanitize_opts);
core.addImport("build_options", build_options_mod);
// §9.3 / 3.1: `include/` carries the vendored C API headers
// (wasm.h pinned via ADR-0004). Adding the path here lets
// src/api/* modules `@cImport(@cInclude("wasm.h"))` resolve.
core.addIncludePath(b.path("include"));
// ADR-0024 D-3: self-import. Every leaf in `src/` can write
// `@import("zwasm").<zone>.<symbol>` to reach the central
// re-export hub regardless of nesting depth.
core.addImport("zwasm", core);
// ADR-0177 (D-311) — a ReleaseSafe twin of `core` for the integration
// TEST RUNNERS only. Debug host execution is ~5-10x slower; the runners
// (spec / realworld / wast / edge corpus) are run-time-dominated, so they
// build ReleaseSafe for iteration speed (still full safety checks). The
// `core_tests` UNIT suite stays on Debug `core` (it calls raw `module.entry`
// fn-ptrs that violate the JIT host-boundary callee-saved contract under an
// optimized host — a test-harness pattern, not a production path; production
// routes through the cohort trampoline). Production (`exe`/lib) keeps `core`
// honouring `-Doptimize`. Floor at ReleaseSafe so a plain Debug build still
// runs runners fast; a higher `-Doptimize` (ReleaseFast) wins.
const runner_optimize: std.builtin.OptimizeMode = if (optimize == .Debug) .ReleaseSafe else optimize;
const core_rs = b.createModule(.{
.root_source_file = b.path("src/zwasm.zig"),
.target = target,
.optimize = runner_optimize,
.strip = strip_opt,
.link_libc = true,
});
applySanitize(core_rs, sanitize_opts);
core_rs.addImport("build_options", build_options_mod);
core_rs.addIncludePath(b.path("include"));
core_rs.addImport("zwasm", core_rs);
// CLI exe — separate thin module rooted at `src/cli/main.zig`
// (per ADR-0024 D-4) so `pub fn main` lives in the CLI zone
// and doesn't collide with C hosts' `int main` when they link
// against libzwasm.a.
const exe_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("src/cli/main.zig"),
.target = target,
.optimize = optimize,
.strip = strip_opt,
.link_libc = true,
});
exe_mod.addImport("build_options", build_options_mod);
exe_mod.addIncludePath(b.path("include"));
exe_mod.addImport("zwasm", core);
const exe = b.addExecutable(.{
.name = "zwasm",
.root_module = exe_mod,
});
b.installArtifact(exe);
// `zig build run -- <args>` runs the CLI.
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Run the zwasm executable");
run_step.dependOn(&run_cmd.step);
// `zig build test` — unit tests inline in src/.
//
// Zig's `b.addTest` injects `std.testing.allocator` (a
// leak-detecting `std.heap.DebugAllocator`-backed
// allocator) into every test. Any allocation that escapes a
// test without a matching free prints `error(gpa): memory
// address ... leaked` and fails the run. So `zig build test`
// IS the leak-check gate per §9.2 / 2.5 — no separate
// `--leak-check` step is needed.
// Unit tests run against the `core` module directly — that's
// where the test loader lives (per ADR-0024 D-2). The CLI
// exe's tests come along too via the inline `test "..."`
// blocks in `src/cli/main.zig`.
const core_tests = b.addTest(.{ .root_module = core });
const run_core_tests = b.addRunArtifact(core_tests);
const cli_tests = b.addTest(.{ .root_module = exe_mod });
const run_cli_tests = b.addRunArtifact(cli_tests);
// Close-plan §6 (j) D-153 / direct-implementation route
// (2026-05-21). spectest is the standard Wasm host module
// (canonical: `WebAssembly/spec/interpreter/host/spectest.ml`,
// 56 OCaml lines). Both v1 zwasm and wazero ship it as a
// regular `.wat` that the spec runner auto-registers; we
// adopt the same model.
//
// Pipeline:
// test/spec/spectest.wat (committed source)
// → `wasm-tools parse` (Nix-managed; flake.nix lists wasm-tools)
// → spectest.wasm (in build cache; never committed)
// → WriteFiles bundles {spectest.wasm, spectest_module.zig}
// → createModule wraps it; `@embedFile("spectest.wasm")`
// resolves at compile time of the runner exe
//
// Differential rebuild: Zig tracks the .wat input file's
// hash; unchanged .wat → cached .wasm reused. CI-grade
// reproducibility per user request 2026-05-21.
// D-290: wabt → wasm-tools migration. `wasm-tools parse <wat> -o <wasm>` is
// the wat→wasm equivalent of `wat2wasm` (byte-identical for basic modules;
// spectest.wat is a plain support module). Drops one wabt site from the build.
const spectest_wat2wasm = b.addSystemCommand(&.{ "wasm-tools", "parse" });
spectest_wat2wasm.addFileArg(b.path("test/spec/spectest.wat"));
spectest_wat2wasm.addArg("-o");
const spectest_wasm_path = spectest_wat2wasm.addOutputFileArg("spectest.wasm");
const spectest_wf = b.addWriteFiles();
_ = spectest_wf.addCopyFile(spectest_wasm_path, "spectest.wasm");
const spectest_embed_src = spectest_wf.add("spectest_module.zig",
\\//! Auto-generated by build.zig: wraps the compiled
\\//! spectest.wasm (from test/spec/spectest.wat) as a
\\//! byte slice importable via @import("spectest_module").
\\pub const bytes: []const u8 = @embedFile("spectest.wasm");
\\
);
const spectest_wasm_mod = b.createModule(.{
.root_source_file = spectest_embed_src,
});
// §9.9-III (c)-2.3 D-142 fix (B) attendant: the
// `RegisteredExporter` unit tests in
// `test/spec/spec_assert_runner_base.zig` were authored
// alongside the γ-1/γ-2/γ-3/γ-3.b chunks but never wired
// into `zig build test` (they exist inside a
// `pub fn`-providing module consumed by the three runner
// exes; exe wiring doesn't run `test "..."` blocks). Wire
// them now so the D-142 absent-backing assertion + the
// γ-tests guard the exporter shape going forward.
const spec_assert_base_test_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/spec_assert_runner_base.zig"),
.target = target,
.optimize = optimize,
});
spec_assert_base_test_mod.addImport("zwasm", core);
spec_assert_base_test_mod.addImport("spectest_module", spectest_wasm_mod);
const spec_assert_base_tests = b.addTest(.{ .root_module = spec_assert_base_test_mod });
const run_spec_assert_base_tests = b.addRunArtifact(spec_assert_base_tests);
const test_step = b.step("test", "Run unit tests");
// §10 / 10.T-4: emit_test golden bless workflow entry point.
// Skeleton — auto-bless impl is deferred per design plan §4.7
// until first cluster hits ≥ 10 pending mismatches. Today
// routes through `scripts/bless_emit_tests.sh` which reports
// sidecar status.
const bless_step = b.step("bless", "Apply pending emit_test golden mismatches (10.T-4 skeleton; impl deferred per design plan §4.7)");
const bless_cmd = b.addSystemCommand(&.{ "bash", "scripts/bless_emit_tests.sh" });
bless_step.dependOn(&bless_cmd.step);
test_step.dependOn(&run_core_tests.step);
test_step.dependOn(&run_cli_tests.step);
test_step.dependOn(&run_spec_assert_base_tests.step);
// `zig build test-spec` — drive the frontend over the vendored
// Wasm spec corpus (Phase 1 / §9.1 / 1.8: parser smoke; 1.9
// upgrades to full decode + validate + lower).
//
// Per ADR-0024 D-1, every test runner reuses one shared zwasm module via
// `addImport("zwasm", zwasm_lib_mod)`. ADR-0177 (D-311): that alias now
// points at the ReleaseSafe twin `core_rs` so every integration runner
// builds ReleaseSafe (iteration speed) in one place. `core_tests`/`exe`
// still use Debug `core`.
const zwasm_lib_mod = core_rs;
const spec_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/runner.zig"),
.target = target,
.optimize = optimize,
});
spec_runner_mod.addImport("zwasm", zwasm_lib_mod);
const spec_runner_exe = b.addExecutable(.{
.name = "zwasm-spec-runner",
.root_module = spec_runner_mod,
});
const run_spec_smoke = b.addRunArtifact(spec_runner_exe);
run_spec_smoke.addArg(b.pathFromRoot("test/spec/smoke"));
const run_spec_mvp = b.addRunArtifact(spec_runner_exe);
run_spec_mvp.addArg(b.pathFromRoot("test/spec/wasm-1.0"));
const test_spec_step = b.step("test-spec", "Run the Wasm spec test runner");
test_spec_step.dependOn(&run_spec_smoke.step);
test_spec_step.dependOn(&run_spec_mvp.step);
// `zig build test-edge-cases` — sub-7.5b-iii fixture runner.
// Iterates `test/edge_cases/p7/` and runs each .wasm through
// the ARM64 JIT, comparing against the sibling .expect.
const edge_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/edge_cases/runner.zig"),
.target = target,
.optimize = optimize,
});
edge_runner_mod.addImport("zwasm", zwasm_lib_mod);
const edge_runner_exe = b.addExecutable(.{
.name = "zwasm-edge-runner",
.root_module = edge_runner_mod,
});
// `has_side_effects = true` forces each fixture-runner to re-run
// every invocation. The runner walks its corpus dir at RUNTIME, but
// the dir path is a plain `addArg` string — NOT a tracked build
// input — so without this flag zig caches the run-artifact on the
// exe hash and SKIPS re-running when only fixture files change
// (no src/exe delta). That silently gave fixture-only additions
// FALSE coverage (they passed when run directly but the gate served
// a stale cached result). Tests must always execute; the runner is
// fast (~seconds for the whole corpus).
const run_edge_p7 = b.addRunArtifact(edge_runner_exe);
run_edge_p7.addArg(b.pathFromRoot("test/edge_cases/p7"));
run_edge_p7.has_side_effects = true;
const run_edge_p9 = b.addRunArtifact(edge_runner_exe);
run_edge_p9.addArg(b.pathFromRoot("test/edge_cases/p9"));
run_edge_p9.has_side_effects = true;
const run_edge_p10 = b.addRunArtifact(edge_runner_exe);
run_edge_p10.addArg(b.pathFromRoot("test/edge_cases/p10"));
run_edge_p10.has_side_effects = true;
const run_edge_p17 = b.addRunArtifact(edge_runner_exe);
run_edge_p17.addArg(b.pathFromRoot("test/edge_cases/p17"));
run_edge_p17.has_side_effects = true;
// Realworld p10 result-check (10.TC-JIT IT-5): the same JIT
// edge-runner walks `test/realworld/p10/**`, result-checking any
// toolchain-compiled `.wasm` with a sibling `.expect`
// (clang_musttail → return_call D-205; clang_wasm64 → memory64 D-209).
const run_edge_realworld_p10 = b.addRunArtifact(edge_runner_exe);
run_edge_realworld_p10.addArg(b.pathFromRoot("test/realworld/p10"));
run_edge_realworld_p10.has_side_effects = true;
const test_edge_step = b.step("test-edge-cases", "Run edge-case fixture runner (all hosts post §9.9 / 9.9-j-2b)");
test_edge_step.dependOn(&run_edge_p7.step);
test_edge_step.dependOn(&run_edge_p9.step);
test_edge_step.dependOn(&run_edge_p10.step);
test_edge_step.dependOn(&run_edge_p17.step);
test_edge_step.dependOn(&run_edge_realworld_p10.step);
// `zig build test-spec-jit-compile` — §9.7 / 7.5 first
// sub-chunk. Walks spec corpora and reports whether each
// fixture compiles end-to-end through the JIT pipeline
// (parse + validate + lower + regalloc + ARM64 emit). Mac
// aarch64 only (linker tied to host arch).
const jit_compile_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/jit_compile_runner.zig"),
.target = target,
.optimize = optimize,
});
jit_compile_runner_mod.addImport("zwasm", zwasm_lib_mod);
const jit_compile_runner_exe = b.addExecutable(.{
.name = "zwasm-spec-jit-compile",
.root_module = jit_compile_runner_mod,
});
const run_jit_compile = b.addRunArtifact(jit_compile_runner_exe);
run_jit_compile.addArg(b.pathFromRoot("test/spec/smoke"));
run_jit_compile.addArg(b.pathFromRoot("test/spec/wasm-1.0"));
const test_jit_compile_step = b.step("test-spec-jit-compile", "JIT-compile spec corpus (Mac aarch64 only; §9.7 / 7.5)");
test_jit_compile_step.dependOn(&run_jit_compile.step);
// `zig build test-spec-assert` — §9.7 / 7.5-spec-assertion-driver-a.
// Walks corpus produced by `scripts/regen_spec_1_0_assert.sh`,
// JIT-compiles each `module` and runs each `assert_return`
// through the typed entry helpers (callI32NoArgs / callI32_i32),
// reporting pass / fail / skipped counts. Wired into test-all
// on all hosts at §9.7 / 7.8 close (D-045 chunks 1-14
// discharged; gate green Mac + Linux + Windows).
const spec_assert_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/spec_assert_runner.zig"),
.target = target,
.optimize = optimize,
});
spec_assert_runner_mod.addImport("zwasm", zwasm_lib_mod);
spec_assert_runner_mod.addImport("spectest_module", spectest_wasm_mod);
const spec_assert_runner_exe = b.addExecutable(.{
.name = "zwasm-spec-assert",
.root_module = spec_assert_runner_mod,
});
const run_spec_assert = b.addRunArtifact(spec_assert_runner_exe);
run_spec_assert.addArg(b.pathFromRoot("test/spec/wasm-1.0-assert"));
const test_spec_assert_step = b.step("test-spec-assert", "Run JIT spec assertion runner (all hosts; gate-green at §9.7 / 7.8 close)");
test_spec_assert_step.dependOn(&run_spec_assert.step);
// E1 (ADR-0170): Component Model spec corpus runner. Unlike the
// core-wasm runners it needs the component host API, so it is built
// against a dedicated `zwasm` module forced to a `.p2` WASI floor
// (ADR-0193: component == `wasi_level >= .p2`) regardless of the
// top-level `-Dwasi` (which may be `none`/`p1`). `core_comp` is a
// separate root of `src/zwasm.zig` rooting its own executable — never
// co-compiled with `core` in one exe, so the ADR-0028 dual-
// `build_options`-root hazard does not apply.
const comp_wasi_level: WasiLevel = if (@intFromEnum(wasi_level) >= @intFromEnum(WasiLevel.p2)) wasi_level else .p2;
const comp_options = b.addOptions();
comp_options.addOption(WasmLevel, "wasm_level", wasm_level);
comp_options.addOption(WasiLevel, "wasi_level", comp_wasi_level);
comp_options.addOption(EngineMode, "engine_mode", engine_mode);
comp_options.addOption(bool, "trace_ringbuffer", trace_ringbuffer);
comp_options.addOption(bool, "trace_stackprobe", trace_stackprobe);
comp_options.addOption(bool, "enable_gc", enable_gc);
comp_options.addOption(bool, "enable_component", true);
comp_options.addOption(bool, "enable_wasi_p3", @intFromEnum(comp_wasi_level) >= @intFromEnum(WasiLevel.p3));
comp_options.addOption([]const u8, "version", zon.version);
const comp_options_mod = comp_options.createModule();
const core_comp = b.createModule(.{
.root_source_file = b.path("src/zwasm.zig"),
.target = target,
// ADR-0177 (Revision 2026-06-14): the Component Model spec runner
// (`comp_spec_runner`, 158-manifest corpus in `test-all`) is an
// integration runner — floor it at ReleaseSafe like `core_rs`, else a
// plain Debug `zig build test-all` runs the whole CM corpus ~100× slower.
// `core_comp` is consumed ONLY by that runner (no production component
// exe), so the floor never costs a real Debug build.
.optimize = runner_optimize,
.strip = strip_opt,
.link_libc = true,
});
applySanitize(core_comp, sanitize_opts);
core_comp.addImport("build_options", comp_options_mod);
core_comp.addIncludePath(b.path("include"));
core_comp.addImport("zwasm", core_comp);
const comp_spec_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/component_model_assert_runner.zig"),
.target = target,
.optimize = optimize,
});
comp_spec_runner_mod.addImport("zwasm", core_comp);
const comp_spec_runner_exe = b.addExecutable(.{
.name = "zwasm-component-spec-assert",
.root_module = comp_spec_runner_mod,
});
const run_comp_spec_assert = b.addRunArtifact(comp_spec_runner_exe);
run_comp_spec_assert.addArg(b.pathFromRoot("test/spec/component-model-assert"));
const test_comp_spec_step = b.step("test-component-spec", "Run the Component Model spec corpus runner (E1; ADR-0170)");
test_comp_spec_step.dependOn(&run_comp_spec_assert.step);
// ADR-0193 P3 — the 28 WASI Preview-3 (async) unit tests live in
// `api/component_wasi_p3.zig`, which compiles only at `wasi_level >= .p3`.
// The default `.p2` `zig build test` skips them (the file is unimported),
// so a dedicated module forced to `.p3` runs them regardless of the
// top-level `-Dwasi`. Mirrors `core_comp`'s forced-`.p2` floor above; uses
// the Debug `optimize` (like `core_tests`, not `runner_optimize`) since the
// async tests are unit-suite shape, not run-time-dominated corpus runners.
const p3_options = b.addOptions();
p3_options.addOption(WasmLevel, "wasm_level", wasm_level);
p3_options.addOption(WasiLevel, "wasi_level", .p3);
p3_options.addOption(EngineMode, "engine_mode", engine_mode);
p3_options.addOption(bool, "trace_ringbuffer", trace_ringbuffer);
p3_options.addOption(bool, "trace_stackprobe", trace_stackprobe);
p3_options.addOption(bool, "enable_gc", enable_gc);
p3_options.addOption(bool, "enable_component", true);
p3_options.addOption(bool, "enable_wasi_p3", true);
p3_options.addOption([]const u8, "version", zon.version);
const p3_options_mod = p3_options.createModule();
const core_p3 = b.createModule(.{
.root_source_file = b.path("src/zwasm.zig"),
.target = target,
.optimize = optimize,
.strip = strip_opt,
.link_libc = true,
});
applySanitize(core_p3, sanitize_opts);
core_p3.addImport("build_options", p3_options_mod);
core_p3.addIncludePath(b.path("include"));
core_p3.addImport("zwasm", core_p3);
const p3_tests = b.addTest(.{ .root_module = core_p3 });
const run_p3_tests = b.addRunArtifact(p3_tests);
const test_wasi_p3_step = b.step("test-wasi-p3", "Run the WASI Preview-3 (async) unit tests under a forced -Dwasi=p3 module (ADR-0193 P3)");
test_wasi_p3_step.dependOn(&run_p3_tests.step);
// `zig build test-spec-simd` — §9.9 per ADR-0045. SIMD spec
// assertion runner (parallel to spec_assert_runner). §9.9-a
// foundation: runner skeleton + build.zig wiring + manifest
// format spec. NOT YET aggregated into test-all (deferred to
// §9.9-e per ADR-0045 Consequences §). Manifest population
// begins at §9.9-b.
const simd_assert_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/simd_assert_runner.zig"),
.target = target,
.optimize = optimize,
});
simd_assert_runner_mod.addImport("zwasm", zwasm_lib_mod);
simd_assert_runner_mod.addImport("spectest_module", spectest_wasm_mod);
const simd_assert_runner_exe = b.addExecutable(.{
.name = "zwasm-spec-simd",
.root_module = simd_assert_runner_mod,
});
const run_simd_assert = b.addRunArtifact(simd_assert_runner_exe);
run_simd_assert.addArg(b.pathFromRoot("test/spec/wasm-2.0-simd-assert"));
const test_spec_simd_step = b.step("test-spec-simd", "Run SIMD spec assertion runner (§9.9 per ADR-0045; foundation: 0 manifests until §9.9-b)");
test_spec_simd_step.dependOn(&run_simd_assert.step);
// `zig build test-spec-wasm-2.0-assert` — §9.9 / 9.9-l-1b per
// ADR-0057. Wasm 2.0 non-SIMD scalar spec assertion runner;
// parallel to spec_assert_runner (wasm-1.0) and simd_assert_runner
// (SIMD). All three runners consume `spec_assert_runner_base`
// and differ only in their RunnerCallbacks literal. Corpus
// (`test/spec/wasm-2.0-assert/`) lands in a follow-up chunk
// (k-1 — curated sign-ext / sat-trunc / multi-value /
// call_indirect wast vendor); until then the runner reports
// "corpus not found; 0 manifests" and exits clean so test-all
// stays green.
// §9.9 / 9.9-l-1b-d093-d67 (D-134 probe): force the spec_assert
// non-simd runner to compile single-threaded. The d-65
// investigation surfaced a cross-thread `siglongjmp` hypothesis
// (our `sigsegvHandler` installs OK + fires on intentional null
// deref, but does NOT fire on the real Rosetta-translated x86_64 Linux
// SEGV — strong
// evidence the SEGV is delivered to a worker thread context our
// handler cannot service). Building with `-fsingle-threaded`
// makes `std.Io.Threaded.init` return `.init_single_threaded`
// (per Zig std's `Io/Threaded.zig`), so no
// `Io.Threaded` worker threads can spawn at all. The spec_assert
// runner walks corpora + invokes JIT bodies purely sequentially
// — no `async` / `concurrent` use — so single-threaded is the
// correct execution shape regardless. If the Rosetta-translated x86_64 Linux SEGV
// persists post-d-67, cross-thread is ruled out and the
// hypothesis space narrows to (i) libc-context SEGV (RIP
// captured in libc.so.6 region per d-65 valgrind) or (ii) Zig's
// own `handleSegfaultPosix` chain still firing despite our own
// sigaction (D-134 candidate path (d) — toolchain PR #25227).
const non_simd_assert_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/spec_assert_runner_non_simd.zig"),
.target = target,
.optimize = optimize,
.single_threaded = true,
});
non_simd_assert_runner_mod.addImport("zwasm", zwasm_lib_mod);
non_simd_assert_runner_mod.addImport("spectest_module", spectest_wasm_mod);
// D-148 workaround: force LLVM backend for this binary. The
// self-hosted x86_64 backend miscompiles `callconv(.c)` calls
// with 9 FP scalar args + MEMORY-class return (`callLargesig`
// in entry.zig hits it; large-sig spec fixture is the only
// affected test). See Codeberg ziglang/zig#35343 (also #35329
// for the related aggregate-arg miscompile). Mac aarch64
// already defaults to LLVM, so the override only changes
// x86_64 hosts in Debug. Revert once upstream lands the fix.
const non_simd_assert_runner_exe = b.addExecutable(.{
.name = "zwasm-spec-wasm-2-0-assert",
.root_module = non_simd_assert_runner_mod,
.use_llvm = true,
});
// D-165 cycle 9 diag: install to zig-out/bin/ so D165_DUMP_JIT
// dumps + ad-hoc isolated runs against custom manifest dirs can
// use a stable path (`zig-out/bin/zwasm-spec-wasm-2-0-assert(.exe)`)
// instead of hunting for the latest .zig-cache/o/*/*.exe hash.
b.installArtifact(non_simd_assert_runner_exe);
const run_non_simd_assert = b.addRunArtifact(non_simd_assert_runner_exe);
run_non_simd_assert.addArg(b.pathFromRoot("test/spec/wasm-2.0-assert"));
const test_spec_wasm_2_0_assert_step = b.step("test-spec-wasm-2.0-assert", "Run Wasm 2.0 non-SIMD scalar spec assertion runner (§9.9 / 9.9-l-1b per ADR-0057; corpus lands at k-1)");
test_spec_wasm_2_0_assert_step.dependOn(&run_non_simd_assert.step);
// §17.4 D-301 — official atomics (threads proposal) conformance corpus,
// run by the same non_simd scalar runner (atomics are pure-int scalar).
const run_threads_assert = b.addRunArtifact(non_simd_assert_runner_exe);
run_threads_assert.addArg(b.pathFromRoot("test/spec/threads-assert"));
const test_spec_threads_assert_step = b.step("test-spec-threads-assert", "Run atomics (threads) official spec assertion corpus via the non-SIMD scalar runner (§17.4 / D-301)");
test_spec_threads_assert_step.dependOn(&run_threads_assert.step);
// `zig build test-spec-wasm-3.0-assert` — §10 / 10.T-2b. Wasm 3.0
// assertion runner skeleton; enumerates the baked manifests under
// `test/spec/wasm-3.0-assert/<proposal>/<name>/manifest.txt` and
// reports per-proposal directive counts. JIT-execute + assertion
// matching lands cycle-by-cycle as impl rows 10.M / 10.R / 10.TC /
// 10.E / 10.G adopt the spec_assert_runner_base callbacks pattern.
const wasm_3_0_assert_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/spec_assert_runner_wasm_3_0.zig"),
.target = target,
.optimize = optimize,
});
wasm_3_0_assert_runner_mod.addImport("zwasm", core_rs); // ADR-0177: integration runner → ReleaseSafe
const wasm_3_0_assert_runner_exe = b.addExecutable(.{
.name = "zwasm-spec-wasm-3-0-assert",
.root_module = wasm_3_0_assert_runner_mod,
});
// Installed so the wasmtime-misc native differential sweep (ADR-0192)
// can run gc/memory64/tail-call/function-references/multi-memory
// buckets through the GC/typed-ref-capable native engine runner.
b.installArtifact(wasm_3_0_assert_runner_exe);
const run_wasm_3_0_assert = b.addRunArtifact(wasm_3_0_assert_runner_exe);
run_wasm_3_0_assert.addArg(b.pathFromRoot("test/spec/wasm-3.0-assert"));
const test_spec_wasm_3_0_assert_step = b.step("test-spec-wasm-3.0-assert", "Run Wasm 3.0 spec assertion runner skeleton (§10 / 10.T-2b; 5 sub-corpora enumerated)");
test_spec_wasm_3_0_assert_step.dependOn(&run_wasm_3_0_assert.step);
// In-source test of the runner skeleton (covers PROPOSALS list).
const wasm_3_0_assert_unit_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/spec_assert_runner_wasm_3_0.zig"),
.target = target,
.optimize = optimize,
});
wasm_3_0_assert_unit_mod.addImport("zwasm", core);
const wasm_3_0_assert_unit_tests = b.addTest(.{ .root_module = wasm_3_0_assert_unit_mod });
const run_wasm_3_0_assert_unit = b.addRunArtifact(wasm_3_0_assert_unit_tests);
test_step.dependOn(&run_wasm_3_0_assert_unit.step);
// Corpus-presence guard (ADR-0174 win-harden-I). All five spec-assert
// corpora are COMMITTED (not host-regenerated), so a corpus root that
// fails to open is a REAL error — not a fresh-checkout / pre-regen
// state. The simd / non-simd / wasm-3.0 runners historically printed
// "0 manifests" and exited 0 on a missing root: a silent skip that can
// mask a host-specific path-resolution failure behind a green
// `test-all` (the Windows host OK-verdict-hides-pass=0 anomaly this
// campaign hunts). Each now `exit(1)`s on a missing root, matching the
// wasm-1.0 `spec_assert_runner`. These negative runs pin that on EVERY
// host (incl. the Windows host) — a runner that silently skips its corpus
// turns this build RED.
const absent_corpus = b.pathFromRoot("test/spec/__absent_corpus_negative_test__");
const run_simd_absent = b.addRunArtifact(simd_assert_runner_exe);
run_simd_absent.addArg(absent_corpus);
run_simd_absent.expectExitCode(1);
const run_non_simd_absent = b.addRunArtifact(non_simd_assert_runner_exe);
run_non_simd_absent.addArg(absent_corpus);
run_non_simd_absent.expectExitCode(1);
const run_wasm_3_0_absent = b.addRunArtifact(wasm_3_0_assert_runner_exe);
run_wasm_3_0_absent.addArg(absent_corpus);
run_wasm_3_0_absent.expectExitCode(1);
const test_corpus_presence_step = b.step("test-corpus-presence", "Assert spec-assert runners FAIL (exit 1) on a missing corpus root — no silent skip (ADR-0174)");
test_corpus_presence_step.dependOn(&run_simd_absent.step);
test_corpus_presence_step.dependOn(&run_non_simd_absent.step);
test_corpus_presence_step.dependOn(&run_wasm_3_0_absent.step);
// §10 / 10.E spec corpus runner foundation — manifest parser
// tests. Lands ahead of the dispatcher integration so future
// cycles can wire parsed Directives through cli_run.runWasmCaptured
// against a structured input shape.
const wasm_3_0_manifest_unit_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/wasm_3_0_manifest.zig"),
.target = target,
.optimize = optimize,
});
wasm_3_0_manifest_unit_mod.addImport("zwasm", core);
const wasm_3_0_manifest_unit_tests = b.addTest(.{ .root_module = wasm_3_0_manifest_unit_mod });
const run_wasm_3_0_manifest_unit = b.addRunArtifact(wasm_3_0_manifest_unit_tests);
test_step.dependOn(&run_wasm_3_0_manifest_unit.step);
// §10 / 10.T-3: gc_stress + eh_frequency runner skeletons.
// Impl-body lands when 10.G / 10.E activate (collector vtable +
// FP-walk unwind in place). Until then the runners report
// SKIP-P10-{GC,EH}-GAP and exit 0; their in-source unit tests
// verify the matrix shapes per ADR-0115/0116 + ADR-0114.
const gc_stress_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/runners/gc_stress_runner.zig"),
.target = target,
.optimize = optimize,
});
const gc_stress_runner_tests = b.addTest(.{ .root_module = gc_stress_runner_mod });
const run_gc_stress_runner_tests = b.addRunArtifact(gc_stress_runner_tests);
test_step.dependOn(&run_gc_stress_runner_tests.step);
const eh_frequency_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/runners/eh_frequency_runner.zig"),
.target = target,
.optimize = optimize,
});
const eh_frequency_runner_tests = b.addTest(.{ .root_module = eh_frequency_runner_mod });
const run_eh_frequency_runner_tests = b.addRunArtifact(eh_frequency_runner_tests);
test_step.dependOn(&run_eh_frequency_runner_tests.step);
// `zig build test-spec-wasm-2.0` — wast-directive runner
// (Phase 2 / §9.2 / 2.7). Reads each subdir's manifest.txt
// and processes module / assert_invalid / assert_malformed
// (binary) commands.
const wast_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/spec/wast_runner.zig"),
.target = target,
.optimize = optimize,
});
wast_runner_mod.addImport("zwasm", zwasm_lib_mod);
const wast_runner_exe = b.addExecutable(.{
.name = "zwasm-wast-runner",
.root_module = wast_runner_mod,
});
const run_wast_2_0 = b.addRunArtifact(wast_runner_exe);
run_wast_2_0.addArg(b.pathFromRoot("test/spec/wasm-2.0"));
const test_spec_2_0_step = b.step("test-spec-wasm-2.0", "Run the Wasm 2.0 wast-directive runner");
test_spec_2_0_step.dependOn(&run_wast_2_0.step);
// `zig build test-wasmtime-misc-basic` — Phase 6 / §9.6 / 6.B
// (per ADR-0012). Drives the wast_runner against the
// wasmtime misc_testsuite BATCH1 fixtures vendored under
// `test/wasmtime_misc/wast/basic/` (migrated in 6.B from the
// now-dissolved `test/v1_carry_over/`). Initial set is
// parse + validate only; runtime-asserting coverage lands
// when 6.D re-drives the same corpus through the
// wast_runtime_runner.
const run_wasmtime_misc_basic = b.addRunArtifact(wast_runner_exe);
run_wasmtime_misc_basic.addArg(b.pathFromRoot("test/wasmtime_misc/wast"));
const test_wasmtime_misc_basic_step = b.step("test-wasmtime-misc-basic", "Run the wasmtime misc_testsuite BATCH1 corpus (parse + validate)");
test_wasmtime_misc_basic_step.dependOn(&run_wasmtime_misc_basic.step);
// `zig build test-runtime-runner-smoke` — Phase 6 / §9.6 / 6.A
// (per ADR-0013). Drives the runtime-asserting WAST runner
// against the in-tree smoke fixture (`test/runners/fixtures/`).
// Smoke gate exercises module + assert_return + assert_trap +
// valid; the full wasmtime_misc corpus wires in 6.D.
const wast_runtime_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/runners/wast_runtime_runner.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
wast_runtime_runner_mod.addImport("zwasm", zwasm_lib_mod);
const wast_runtime_runner_exe = b.addExecutable(.{
.name = "zwasm-wast-runtime-runner",
.root_module = wast_runtime_runner_mod,
});
// Installed so the wasmtime-misc differential sweep (ADR-0192)
// can invoke it on an arbitrary generated corpus dir.
b.installArtifact(wast_runtime_runner_exe);
const run_wast_runtime_smoke = b.addRunArtifact(wast_runtime_runner_exe);
run_wast_runtime_smoke.addArg(b.pathFromRoot("test/runners/fixtures"));
const test_runtime_runner_smoke_step = b.step("test-runtime-runner-smoke", "Run the runtime-asserting WAST runner against the smoke fixture");
test_runtime_runner_smoke_step.dependOn(&run_wast_runtime_smoke.step);
// `zig build test-wasmtime-misc-runtime` — Phase 6 / §9.6 / 6.D
// (per ADR-0012). Drives the runtime-asserting runner against
// the same wasmtime_misc corpus as test-wasmtime-misc-basic, but
// consuming `manifest_runtime.txt` (assert_return / assert_trap /
// module / register / invoke) instead of the parse-only
// `manifest.txt`. Surfaces v2 interp behaviour gaps that the
// parse runner cannot see.
//
// **Not wired into `test-all` aggregate**. The current corpus
// panics inside `interp.popOperand`'s assert when a fixture
// exercises an operand-stack discipline bug (one of the 39
// trap-mid-execution patterns ADR-0011 surfaced). 6.E (interp
// behaviour bug investigation) addresses these; once the
// underlying gaps close, this step joins `test-all`.
// Until then, run standalone for triage:
// zig build test-wasmtime-misc-runtime
const run_wasmtime_misc_runtime = b.addRunArtifact(wast_runtime_runner_exe);
run_wasmtime_misc_runtime.addArg(b.pathFromRoot("test/wasmtime_misc/wast"));
const test_wasmtime_misc_runtime_step = b.step("test-wasmtime-misc-runtime", "Run the runtime-asserting WAST runner against the wasmtime_misc corpus (NOT in test-all; surfaces 6.E targets)");
test_wasmtime_misc_runtime_step.dependOn(&run_wasmtime_misc_runtime.step);
// `zig build test-realworld` — parse-smoke a vendored set of
// toolchain-produced .wasm fixtures (Phase 2 / §9.2 / 2.6).
const realworld_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/realworld/runner.zig"),
.target = target,
.optimize = optimize,
});
realworld_runner_mod.addImport("zwasm", zwasm_lib_mod);
const realworld_runner_exe = b.addExecutable(.{
.name = "zwasm-realworld-runner",
.root_module = realworld_runner_mod,
});
const run_realworld = b.addRunArtifact(realworld_runner_exe);
run_realworld.addArg(b.pathFromRoot("test/realworld/wasm"));
// has_side_effects: the corpus dir is a plain `addArg` string (NOT a
// tracked input), so without this the run-artifact is cached on the exe
// hash and SKIPPED when only `.wasm` fixtures change → false coverage
// (same gap as the run_edge_* steps, fixed cyc216; these realworld/wasm
// runners were missed then). See lesson
// `2026-05-30-edge-runner-fixture-cache-false-coverage`.
run_realworld.has_side_effects = true;
const test_realworld_step = b.step("test-realworld", "Run the realworld parse smoke");
test_realworld_step.dependOn(&run_realworld.step);
// `zig build test-fuzz` — §14.3 / D-256 fuzz smoke. Feeds each
// committed seed-corpus file's raw bytes through `parser.parse` +
// the public `Engine.compile` (parse + validate). A decode-error
// return is an EXPECTED reject; a CRASH (panic / SEGV / OOM-loop)
// is a finding — it kills the loader process → red gate. Full
// overnight campaigns ride the §14.3 nightly over a larger
// gitignored `wasm-tools smith` corpus (`gen_fuzz_corpus.sh campaign`).
const fuzz_loader_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/fuzz/fuzz_loader.zig"),
.target = target,
.optimize = optimize,
});
fuzz_loader_mod.addImport("zwasm", zwasm_lib_mod);
const fuzz_loader_exe = b.addExecutable(.{
.name = "zwasm-fuzz-loader",
.root_module = fuzz_loader_mod,
});
const run_fuzz = b.addRunArtifact(fuzz_loader_exe);
run_fuzz.addArg(b.pathFromRoot("test/fuzz/corpus/seed"));
// has_side_effects: the corpus dir is an untracked `addArg` string, so
// without this the run is cached on the exe hash + skipped when only the
// corpus changes (same gap as run_realworld; see that comment).
run_fuzz.has_side_effects = true;
const test_fuzz_step = b.step("test-fuzz", "Run the fuzz smoke over the committed seed corpus (§14.3 / D-256)");
test_fuzz_step.dependOn(&run_fuzz.step);
// `zig build fuzz-campaign` — §14.3 nightly. Runs the loader over the
// larger gitignored campaign corpus (`gen_fuzz_corpus.sh campaign`,
// generated at nightly time on a host with `wasm-tools`). NOT in
// test-all (the campaign dir is absent on a normal checkout).
const run_fuzz_campaign = b.addRunArtifact(fuzz_loader_exe);
run_fuzz_campaign.addArg(b.pathFromRoot("test/fuzz/corpus/campaign"));
run_fuzz_campaign.has_side_effects = true;
const fuzz_campaign_step = b.step("fuzz-campaign", "Run the fuzz loader over the gitignored campaign corpus (§14.3 nightly)");
fuzz_campaign_step.dependOn(&run_fuzz_campaign.step);
// `zig build test-fuzz-exec` (alias `fuzz-diff`) — D-469/D-510 interp-vs-JIT
// EXECUTION differential. Invokes each module's 0-param/single-scalar-result
// exports under the interp AND two JIT lanes (`.auto` guard-page elision +
// `.explicit` inline check, ADR-0202) and gates on value/trap/memory-snapshot
// divergences (a JIT-execute miscompile = a finding). GATING. The campaign
// corpus rides `zwasm-fuzz-exec <dir>` directly (gitignored, like the loader).
const fuzz_exec_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/fuzz/fuzz_exec.zig"),
.target = target,
.optimize = optimize,
});
fuzz_exec_mod.addImport("zwasm", zwasm_lib_mod);
const fuzz_exec_exe = b.addExecutable(.{
.name = "zwasm-fuzz-exec",
.root_module = fuzz_exec_mod,
});
const run_fuzz_exec = b.addRunArtifact(fuzz_exec_exe);
// Curated, hand-written 0-param/scalar modules (value/trap/loop/call cases) —
// the smith seed exports nothing comparable, so this dedicated corpus is what
// makes the committed gate actually compare functions. The wide campaign run
// (122 funcs) rides `zwasm-fuzz-exec test/fuzz/corpus/campaign` directly.
run_fuzz_exec.addArg(b.pathFromRoot("test/fuzz/corpus/exec_seed"));
// D-510 — committed regression corpus (wazero-fuzzcases-style): minimised
// past differential findings + hand-written memory-state / guard-boundary
// exercisers, replayed on every run.
run_fuzz_exec.addArg(b.pathFromRoot("test/fuzz/corpus/regression"));
run_fuzz_exec.has_side_effects = true;
const test_fuzz_exec_step = b.step("test-fuzz-exec", "Interp-vs-JIT execution differential fuzz (D-469)");
test_fuzz_exec_step.dependOn(&run_fuzz_exec.step);
// D-510 — first-class name matching the debt-row/peer vocabulary; same gate.
const fuzz_diff_step = b.step("fuzz-diff", "Interp-vs-JIT differential over the committed corpora (= test-fuzz-exec; D-510)");
fuzz_diff_step.dependOn(&run_fuzz_exec.step);
// `zig build test-aot-diff` — AOT-full-fidelity campaign Phase II: the
// CROSS-PROCESS `.wasm`-vs-`.cwasm` differential. Spawns the real zwasm
// CLI (run / compile / run-artifact) per fixture, so ASLR-staleness bugs
// (D-516 baked helper addresses) that in-process harnesses can't see are
// exercised the way a real deployment would. Known gaps are pinned in the
// runner's expectation table (D-516/D-517/D-518) — the gate trips on any
// UNEXPECTED divergence and on RATCHET-FLIPs (a pinned gap now matching,
// forcing the table update in the fixing PR).
const aot_diff_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/aot/aot_process_diff.zig"),
.target = target,
.optimize = optimize,
});
const aot_diff_exe = b.addExecutable(.{
.name = "zwasm-aot-process-diff",
.root_module = aot_diff_mod,
});
const run_aot_diff = b.addRunArtifact(aot_diff_exe);
run_aot_diff.addArtifactArg(exe); // the zwasm CLI under test
run_aot_diff.addArg(b.pathFromRoot("test/realworld/wasm"));
run_aot_diff.addArg(b.pathFromRoot("test/aot/corpus"));
run_aot_diff.has_side_effects = true;
const test_aot_diff_step = b.step("test-aot-diff", "Cross-process .wasm-vs-.cwasm differential (AOT campaign Phase II)");
test_aot_diff_step.dependOn(&run_aot_diff.step);
// `zig build test-realworld-run` — Phase 6 / §9.6 / 6.1
// chunk b. Drives each fixture through `cli_run.runWasm`
// end-to-end (engine → store → WASI → instantiate → entry
// → wasm_func_call). Outcome categories: PASS / SKIP-WASI /
// SKIP-NOENTRY / FAIL. The gate trips only on FAIL —
// SKIP-WASI counts but is orthogonal to interp-op coverage.
const realworld_run_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/realworld/run_runner.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
realworld_run_runner_mod.addImport("zwasm", zwasm_lib_mod);
const realworld_run_runner_exe = b.addExecutable(.{
.name = "zwasm-realworld-run-runner",
.root_module = realworld_run_runner_mod,
});
const run_realworld_run = b.addRunArtifact(realworld_run_runner_exe);
run_realworld_run.addArg(b.pathFromRoot("test/realworld/wasm"));
run_realworld_run.has_side_effects = true; // fixture-only changes must re-run (cyc216 gap)
const test_realworld_run_step = b.step("test-realworld-run", "Run each realworld fixture end-to-end via cli_run.runWasm");
test_realworld_run_step.dependOn(&run_realworld_run.step);
// `zig build test-realworld-run-jit` — §9.7 / 7.9 chunk a
// baseline. Walks the same corpus and drives each fixture
// through `engine.runner.compileWasm` (the JIT pipeline).
// Reports compile-side coverage: COMPILE-PASS / COMPILE-IMPORTS
// / COMPILE-OP / COMPILE-VAL / FAIL-OTHER. Chunks 7.9-b/c/d
// turn COMPILE-PASS into RUN-PASS by adding host-import
// dispatch + JitRuntime memory init + WASI stub handlers.
const realworld_run_jit_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/realworld/run_runner_jit.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
realworld_run_jit_mod.addImport("zwasm", zwasm_lib_mod);
const realworld_run_jit_exe = b.addExecutable(.{
.name = "zwasm-realworld-run-jit-runner",
.root_module = realworld_run_jit_mod,
});
const run_realworld_run_jit = b.addRunArtifact(realworld_run_jit_exe);
run_realworld_run_jit.addArg(b.pathFromRoot("test/realworld/wasm"));
run_realworld_run_jit.has_side_effects = true; // fixture-only changes must re-run (cyc216 gap)
const test_realworld_run_jit_step = b.step("test-realworld-run-jit", "JIT-compile each realworld fixture (§9.7 / 7.9 baseline)");
test_realworld_run_jit_step.dependOn(&run_realworld_run_jit.step);
// `zig build jit-result-probe-releasesafe` — D-245 RESULT-path gate
// (§15.5 / chunk 1). `check_jit_releasesafe.sh` only exercises the no-arg
// VOID path; the i32 RESULT path (`runner.runI32Export` →
// `entry.invokeAndCheck`) has its own host→JIT callee-saved-clobber seam.
// The bug ONLY manifests in ReleaseSafe, and an exe's optimize does NOT
// propagate to a pre-built `core` module — so this step compiles a FRESH
// `core` PINNED to ReleaseSafe (regardless of the ambient `-Doptimize`)
// plus the probe, then runs it. A non-zero exit = the clobber regressed.
const core_releasesafe = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("src/zwasm.zig"),
.target = target,
.optimize = .ReleaseSafe,
.link_libc = true,
});
core_releasesafe.addImport("build_options", build_options_mod);
core_releasesafe.addIncludePath(b.path("include"));
core_releasesafe.addImport("zwasm", core_releasesafe);
const jit_result_probe_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/jit/releasesafe_result_probe.zig"),
.target = target,
.optimize = .ReleaseSafe,
.link_libc = true,
});
jit_result_probe_mod.addImport("zwasm", core_releasesafe);
const jit_result_probe_exe = b.addExecutable(.{
.name = "zwasm-jit-result-probe",
.root_module = jit_result_probe_mod,
});
const run_jit_result_probe = b.addRunArtifact(jit_result_probe_exe);
run_jit_result_probe.has_side_effects = true; // must run even when nothing else changed
const jit_result_probe_step = b.step("jit-result-probe-releasesafe", "D-245 RESULT-path ReleaseSafe regression probe (runI32Export callee-saved preservation)");
jit_result_probe_step.dependOn(&run_jit_result_probe.step);
// `zig build test-realworld-diff` — Phase 6 / §9.6 / 6.F.
// Spawns `wasmtime run <fixture>` per fixture, captures
// stdout, compares byte-for-byte against
// `cli_run.runWasmCaptured`. Gate is 30+ matches; runner
// SKIPs gracefully when wasmtime is not on PATH (so the
// build remains green on hosts that lack it).
const realworld_diff_runner_mod = createSanitizedModule(b, sanitize_opts, .{
.root_source_file = b.path("test/realworld/diff_runner.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,