const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); // The core IR library. Freestanding-clean: no libc, no OS, no global state. const vulcan_ir = b.addModule("vulcan-ir", .{ .root_source_file = b.path("libs/vulcan-ir.zig"), .target = target, .optimize = optimize, }); // The target-neutral accelerator kernel ABI: builtins, parameter layout, the launch // metadata a runtime reads, and the per-target tensor capability data. Freestanding-clean. // Depends only on the IR, so the frontends can tag kernels with it without depending on the // target seam. It is declared before the optimizer and the frontends because they import it. const vulcan_gpu = b.addModule("vulcan-gpu", .{ .root_source_file = b.path("libs/vulcan-gpu.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "vulcan-ir", .module = vulcan_ir }}, }); // The optimization framework: target-independent IR analyses and transforms. // Freestanding-clean. Depends on the IR, and on the kernel ABI for the per-target tensor // capability data that `microarch.matmul_recog` asks before it raises a loop nest to a // `matmul`. `vulcan-gpu` imports only the IR, so that edge adds no cycle. const vulcan_opt = b.addModule("vulcan-opt", .{ .root_source_file = b.path("libs/vulcan-opt.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, }, }); // The SPIR-V frontend: read a SPIR-V binary and lower it to Vulcan IR. Freestanding-clean. // Depends on the IR and on the kernel ABI, whose vocabulary it tags kernel parameters with. const vulcan_spirv = b.addModule("vulcan-spirv", .{ .root_source_file = b.path("libs/vulcan-spirv.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, }, }); // The shared static ELF linker: parse relocatable objects, resolve relocations, // bind external calls through per-arch GOT stubs, and wrap the result in a static // executable. Imports only `std` (defines its own ELF/reloc structs), so the target // seam can depend on it without a dependency cycle. const vulcan_link = b.addModule("vulcan-link", .{ .root_source_file = b.path("libs/vulcan-link.zig"), .target = target, .optimize = optimize, }); // The target seam: register sets, ABI, encoding, and codegen per target. Also // sees the SPIR-V frontend so it can execution-validate SPIR-V -> IR -> native, // the kernel ABI so the accelerator backends read the launch contract, and the // shared linker for object linking/JIT/executable emission. const vulcan_target = b.addModule("vulcan-target", .{ .root_source_file = b.path("libs/vulcan-target.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-spirv", .module = vulcan_spirv }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, .{ .name = "vulcan-link", .module = vulcan_link }, }, }); // The WebAssembly frontend: read a Wasm binary, lower it to IR, then JIT and run it. // Lowering depends only on the IR. The engine layer adds host JIT + memory/globals/ // table/imports setup via the target seam. const vulcan_wasm = b.addModule("vulcan-wasm", .{ .root_source_file = b.path("libs/vulcan-wasm.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }); // The GLSL frontend: parse GLSL source and lower it to Vulcan IR, and (via the // SPIR-V writer) emit SPIR-V. const vulcan_glsl = b.addModule("vulcan-glsl", .{ .root_source_file = b.path("libs/vulcan-glsl.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-spirv", .module = vulcan_spirv }, }, }); // The C frontend: parse C source and lower it to Vulcan IR. const vulcan_cc = b.addModule("vulcan-cc", .{ .root_source_file = b.path("libs/vulcan-cc.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }); // The container emitters (PE/flat image) are standalone pure-byte modules, used by // the freestanding proof below. const vulcan_pe = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/pe.zig"), .target = target, .optimize = optimize }); const vulcan_image = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/image.zig"), .target = target, .optimize = optimize }); // The compiler core compiled as a link-free object: if it builds for a no-OS target, // those libraries are usable inside a baremetal/UEFI program (allocator-only, no // libc/syscalls). Built for whatever `-Dtarget` selects. const freestanding_proof = b.addObject(.{ .name = "vulcan-freestanding", .root_module = b.createModule(.{ .root_source_file = b.path("test/freestanding_smoke.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-spirv", .module = vulcan_spirv }, .{ .name = "vulcan-pe", .module = vulcan_pe }, .{ .name = "vulcan-image", .module = vulcan_image }, }, }) }); // The GLSL -> SPIR-V shader compiler executable (declared before the if/else so both // the install/run-glsl step and the spirv tests can reference it). const glsl_cli = b.addExecutable(.{ .name = "vulcan-glsl", .root_module = b.createModule(.{ .root_source_file = b.path("frontends/vulcan-glsl.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "vulcan-glsl", .module = vulcan_glsl }}, }) }); b.installArtifact(glsl_cli); // The Vulcan C Compiler driver executable (clang-compatible CLI surface, SM1). // Hoisted into a `const` module (SM15 M5a) so both the exe and its in-file `parseArgs` // tests below share one module, same pattern as `vulcan_ld_module`. const vcc_module = b.createModule(.{ .root_source_file = b.path("frontends/vcc.zig"), .target = target, .optimize = optimize, .strip = optimize != .debug, .imports = &.{ .{ .name = "vulcan-cc", .module = vulcan_cc }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-link", .module = vulcan_link }, .{ .name = "vulcan-opt", .module = vulcan_opt }, }, }); const vcc_cli = b.addExecutable(.{ .name = "vcc", .root_module = vcc_module }); b.installArtifact(vcc_cli); // End-to-end C frontend compile benchmark. Keep its VCC graph ReleaseFast even when the // surrounding build uses the default Debug mode, so `zig build bench` is meaningful and // comparable to installed Clang/GCC binaries without requiring extra flags. const bench_vcc = addBenchVcc(b, target); const frontend_bench_cli = b.addExecutable(.{ .name = "vulcan-frontend-bench", .root_module = b.createModule(.{ .root_source_file = b.path("tools/frontend-bench.zig"), .target = target, .optimize = .ReleaseFast, }) }); const run_frontend_bench = b.addRunArtifact(frontend_bench_cli); run_frontend_bench.addArtifactArg(bench_vcc); run_frontend_bench.addDirectoryArg(b.path("bench/frontends/c")); _ = run_frontend_bench.addOutputDirectoryArg("frontend-bench"); run_frontend_bench.addPassthruArgs(); run_frontend_bench.stdio = .inherit; const cuda_bench_cli = addCudaBench(b, target); const run_cuda_bench = b.addRunArtifact(cuda_bench_cli); run_cuda_bench.addFileArg(b.path("bench/cuda/vector_mix.ptx")); run_cuda_bench.addFileArg(b.path("bench/cuda/vector_mix.cu")); _ = run_cuda_bench.addOutputDirectoryArg("cuda-bench"); run_cuda_bench.stdio = .inherit; run_cuda_bench.step.dependOn(&run_frontend_bench.step); const bench_step = b.step("bench", "Run the benchmark suite"); bench_step.dependOn(&run_cuda_bench.step); // The `ld.vulcan` linker frontend CLI: a standalone driver over the shared static // linker (`vulcan-link`), so the linker is usable with any compiler's `.o`/`.a` // output, not just Vulcan's own frontends. Only `run`/`main` (what actually ships) // depends on `vulcan-link`; `vulcan-target`/`vulcan-ir` are pulled into the module // solely for the in-file test's synthetic `.o` inputs. Hoisted into a `const` so // both the exe and its test below share one module. const vulcan_ld_module = b.createModule(.{ .root_source_file = b.path("frontends/vulcan-ld.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-link", .module = vulcan_link }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-ir", .module = vulcan_ir }, }, }); const vulcan_ld_cli = b.addExecutable(.{ .name = "ld.vulcan", .root_module = vulcan_ld_module }); b.installArtifact(vulcan_ld_cli); // The deliverable follows `-Dtarget`'s OS: a no-OS target installs the freestanding // proof object. Any hosted/UEFI target installs the Wasm runner, whose `main` is the // host CLI or the boot-time app (chosen at comptime). One target in, one output out. // // The Wasm CLI is held here, outside the branch, so the toolchain test at the test step // can drive the freshly built binary. It stays null on a target that does not build it. var wasm_cli_artifact: ?*std.Build.Step.Compile = null; if (target.result.os.tag == .freestanding) { // An object has no standard install procedure. Install the emitted `.o` directly. const install_obj = b.addInstallBinFile(freestanding_proof.getEmittedBin(), "vulcan-freestanding.o"); b.getInstallStep().dependOn(&install_obj.step); } else { // The Wasm runner JITs to native code, so it only builds for an arch the native // backend supports (UEFI targets among them), not for e.g. wasm32-wasi. const native_arch = switch (target.result.cpu.arch) { .aarch64, .x86_64, .x86, .riscv64 => true, else => false, }; if (native_arch) { const uefi_mod = b.dependency("uefi", .{ .target = target, .optimize = optimize }).module("uefi"); const wasm_cli = b.addExecutable(.{ .name = "vulcan-wasm", .root_module = b.createModule(.{ .root_source_file = b.path("frontends/vulcan-wasm.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-wasm", .module = vulcan_wasm }, .{ .name = "uefi", .module = uefi_mod } }, }) }); b.installArtifact(wasm_cli); wasm_cli_artifact = wasm_cli; const run_cli = b.addRunArtifact(wasm_cli); run_cli.addPassthruArgs(); const run_step = b.step("run-wasm", "Run the host Wasm CLI: -- [export] [i32 args]"); run_step.dependOn(&run_cli.step); } // The GLSL -> SPIR-V shader compiler CLI runner (glsl_cli is declared above). if (target.result.os.tag != .uefi) { const run_glsl = b.addRunArtifact(glsl_cli); run_glsl.addPassthruArgs(); const run_glsl_step = b.step("run-glsl", "Run the GLSL->SPIR-V compiler: -- [stage] [-o out.spv]"); run_glsl_step.dependOn(&run_glsl.step); // The GLSL -> target-disassembly debugging tool: compile a shader and print the // native (or Wasm) code for one of its functions. const disasm_cli = b.addExecutable(.{ .name = "vulcan-disasm", .root_module = b.createModule(.{ .root_source_file = b.path("frontends/vulcan-disasm.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-spirv", .module = vulcan_spirv }, }, }) }); b.installArtifact(disasm_cli); const run_disasm = b.addRunArtifact(disasm_cli); run_disasm.addPassthruArgs(); const run_disasm_step = b.step("run-disasm", "Disassemble an ELF or SPIR-V binary to text assembly: -- "); run_disasm_step.dependOn(&run_disasm.step); // The microarch benchmark harness: JIT-compiles a fixed kernel set with and without // the microarch optimizer and measures the cycle gains for a chosen (or detected) // model. Builds for any hosted target; the non-host-arch and no-perf paths handle // non-aarch64/non-linux gracefully at runtime (see tools/uarch-bench.zig). const uarch_bench_cli = b.addExecutable(.{ .name = "vulcan-uarch-bench", .root_module = b.createModule(.{ .root_source_file = b.path("tools/uarch-bench.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); b.installArtifact(uarch_bench_cli); const run_uarch_bench = b.addRunArtifact(uarch_bench_cli); run_uarch_bench.addPassthruArgs(); const run_uarch_bench_step = b.step("run-uarch-bench", "Benchmark the microarch optimizer: -- [--model | --list | --custom]"); run_uarch_bench_step.dependOn(&run_uarch_bench.step); const opt_bench_cli = b.addExecutable(.{ .name = "vulcan-opt-bench", .root_module = b.createModule(.{ .root_source_file = b.path("tools/opt-bench.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, }, }) }); b.installArtifact(opt_bench_cli); const run_opt_bench = b.addRunArtifact(opt_bench_cli); const run_opt_bench_step = b.step("run-opt-bench", "Benchmark target-independent optimizer compile time"); run_opt_bench_step.dependOn(&run_opt_bench.step); } } // Declared above at top level so the spirv tests can depend on it. const test_step = b.step("test", "Run all tests"); const ir_tests = b.addTest(.{ .root_module = vulcan_ir }); test_step.dependOn(&b.addRunArtifact(ir_tests).step); const opt_tests = b.addTest(.{ .root_module = vulcan_opt }); test_step.dependOn(&b.addRunArtifact(opt_tests).step); const gpu_tests = b.addTest(.{ .root_module = vulcan_gpu }); test_step.dependOn(&b.addRunArtifact(gpu_tests).step); const spirv_tests = b.addTest(.{ .root_module = vulcan_spirv }); test_step.dependOn(&b.addRunArtifact(spirv_tests).step); const target_tests = b.addTest(.{ .root_module = vulcan_target }); test_step.dependOn(&b.addRunArtifact(target_tests).step); // The shared linker's own unit tests (std-only: ELF parsing + RVC-compression // primitives). Object-input link tests live in the riscv64 consumer tests. const link_tests = b.addTest(.{ .root_module = vulcan_link }); test_step.dependOn(&b.addRunArtifact(link_tests).step); // `ld.vulcan`'s own CLI test: builds synthetic `.o`/`.a` inputs, drives `run` // directly (no process spawn), and natively executes the produced ELF. const vulcan_ld_tests = b.addTest(.{ .root_module = vulcan_ld_module }); test_step.dependOn(&b.addRunArtifact(vulcan_ld_tests).step); // `vcc`'s own `parseArgs` tests (SM15 M5a): drives the flag-classification/version-probe // surface IN-PROCESS (no process spawn), same pattern as `vulcan_ld_tests` above. const vcc_tests_cli = b.addTest(.{ .root_module = vcc_module }); test_step.dependOn(&b.addRunArtifact(vcc_tests_cli).step); // Wimmer-Franz allocator target-abstraction unit tests: assert the aarch64 RegDescription // (pools, entry-param pinning, call clobbers, scratch) the shared allocator consumes. const wimmer_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/wimmer_test.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(wimmer_tests).step); // Multi-register (aligned register-span) allocator tests. They run against a SYNTHETIC backend, // because no shipping backend puts one value in more than one architectural register yet. const wimmer_multireg_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/wimmer_multireg_test.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(wimmer_multireg_tests).step); // GPU offload execution tests: lower a kernel to a host loop nest with `vulcan-gpu`, JIT // it for the host, run the whole grid, and assert the buffer it wrote. Every other kernel // test is structural, so this is the one that proves a kernel computes the right numbers. const gpu_offload_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/gpu_offload.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(gpu_offload_tests).step); // Matmul expansion execution tests: rewrite the et-soc tensor `matmul` into a scalar loop // nest with `vulcan-ir.expand`, JIT it for the host, and assert the C matrix it wrote. A // wrong index or a dropped accumulate has the same opcodes as a correct nest, so only running // it tells them apart. This is the reference a tensor lowering gets checked against. const matmul_expand_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/matmul_expand.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(matmul_expand_tests).step); // The Wasm frontend's tests: structural (parsing + lowering) plus the engine. const wasm_tests = b.addTest(.{ .root_module = vulcan_wasm }); test_step.dependOn(&b.addRunArtifact(wasm_tests).step); // Wasm execution tests: lower Wasm to IR, then JIT for the host and run. const wasm_exec = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-wasm/tests/native.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-wasm", .module = vulcan_wasm }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-glsl", .module = vulcan_glsl }, }, }) }); test_step.dependOn(&b.addRunArtifact(wasm_exec).step); // Wasm tests over modules a REAL toolchain produced: `zig cc` builds them at test time, // and the WASI one runs through the freshly built CLI. Every other Wasm test assembles // its module by hand, which is how a loader fault that refused every linker-produced // module stayed invisible. `zig_exe` is the same `zig` that builds this, so the test does // not depend on the PATH. Wired only where the CLI exists (a native arch, hosted or UEFI). if (wasm_cli_artifact) |cli| { const wasm_toolchain_opts = b.addOptions(); wasm_toolchain_opts.addOption([]const u8, "zig_exe", b.graph.zig_exe); wasm_toolchain_opts.addOptionPath("vulcan_wasm_bin", cli.getEmittedBin()); const wasm_toolchain = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-wasm/tests/toolchain.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-wasm", .module = vulcan_wasm }, .{ .name = "build_options", .module = wasm_toolchain_opts.createModule() }, }, }) }); test_step.dependOn(&b.addRunArtifact(wasm_toolchain).step); } // C backend execution tests: emit C from IR, compile with the host `cc`, run, and // cross-check against the native JIT. Skips when `cc` is absent. const c_exec = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/c/tests/native.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-glsl", .module = vulcan_glsl }, .{ .name = "vulcan-opt", .module = vulcan_opt }, }, }) }); test_step.dependOn(&b.addRunArtifact(c_exec).step); // C frontend execution tests: lower C to IR, JIT on the host, and diff against gcc. const cc_exec = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-cc/tests/native.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-cc", .module = vulcan_cc }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-link", .module = vulcan_link }, }, }) }); test_step.dependOn(&b.addRunArtifact(cc_exec).step); // SM11 Task 3: external direct calls, proven end to end - the C frontend lowers a call to // a declared-external function into an undefined-symbol call that the SM10 dynamic linker // resolves to a real `.so` PLT import (native aarch64 + qemu x86_64/i386/riscv64). const cc_external = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-cc/tests/external_linkage.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-cc", .module = vulcan_cc }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-link", .module = vulcan_link }, }, }) }); test_step.dependOn(&b.addRunArtifact(cc_external).step); // SM15 M5c CAPSTONE: the default-executable AUTOLINK, proven by RUNNING the linked binary. // The freshly built `vcc` binary's path is handed to the test through a `build_options` // module (`vcc_cli.getEmittedBin()` also makes the test depend on that binary, so it is // built first). The test drives `vcc hello.c -o hello` with no hand-supplied crt/`-lc`/ // `--dynamic-linker`, runs `./hello`, and asserts its output - skipping when the host // toolchain (gcc/crt/glibc/loader) is absent. const vcc_autolink_opts = b.addOptions(); vcc_autolink_opts.addOptionPath("vcc_bin", vcc_cli.getEmittedBin()); const cc_autolink = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-cc/tests/autolink.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "build_options", .module = vcc_autolink_opts.createModule() }, }, }) }); test_step.dependOn(&b.addRunArtifact(cc_autolink).step); // SM12 Task 1: the variadic frontend foundation (`...`, `__builtin_va_list`, // ``, default argument promotions, the IR call `is_variadic`/`num_fixed` // flags). SM12 Task 2 adds the backend call-side end-to-end tests (a vcc program that // CALLS real glibc `printf`, linked against `libc.so.6` and run under the real `ld.so`), // so the module now also imports `vulcan-target` and `vulcan-link`. const cc_variadic = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-cc/tests/variadic.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-cc", .module = vulcan_cc }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-link", .module = vulcan_link }, }, }) }); test_step.dependOn(&b.addRunArtifact(cc_variadic).step); // SM13 M3a Task 5 (CAPSTONE): VCC's own preprocessor - `SystemPredef` (Task 3) + // `FsResolver` (Task 4) + the char-literal/`__has_*` (Task 1) and variadic-macro (Task 2) // work underneath - reduces the REAL host glibc `#include ` chain to no-error. // Only needs `vulcan-cc` itself (pure preprocessing, no lowering/codegen/linking). const cc_preproc_glibc = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-cc/tests/preproc_glibc.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-cc", .module = vulcan_cc }, }, }) }); test_step.dependOn(&b.addRunArtifact(cc_preproc_glibc).step); // JS backend execution tests: emit JS from IR, run it with Node.js, cross-check against // the native answer. Skips when no Node is found. const js_exec = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/js/tests/native.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-glsl", .module = vulcan_glsl }, .{ .name = "vulcan-opt", .module = vulcan_opt }, }, }) }); test_step.dependOn(&b.addRunArtifact(js_exec).step); // Three-way differential: native JIT vs the C backend (cc) vs the JS backend (node) over // GLSL, requiring all three to agree. Skips gracefully when a tool/host is unavailable. const differential = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/differential.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-glsl", .module = vulcan_glsl }, .{ .name = "vulcan-wasm", .module = vulcan_wasm }, }, }) }); test_step.dependOn(&b.addRunArtifact(differential).step); // Loop-unroll differential oracle: build a loop, unroll one copy under a wide // model, JIT both the original and the unrolled function on the host, and // require identical results for every input. Runs where the native JIT has a // backend (aarch64/x86_64/riscv64/x86). const unroll_diff = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/unroll_differential.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(unroll_diff).step); // Accumulator-splitting unroll differential oracle: build a reduction twice, split-unroll one copy // (main loop with K independent partials + remainder), JIT both on the host, and require identical // results for every input (including trip counts not divisible by K, and below K). const splitunroll_diff = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/splitunroll_differential.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(splitunroll_diff).step); // Induction-variable strength reduction differential oracle: build a loop that addresses a base // pointer plus two further constant offsets twice, run one copy through the late pipeline (where // `ivsr` lives), JIT both on the host, and require identical sums over a real array across // several trip counts (0, 1, and a spread above). Also checks that the base collapses to one // walking pointer rather than three, and that an address that escapes the loop stays refused. const ivsr_diff = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/ivsr_differential.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(ivsr_diff).step); // Loop-vectorizer differential oracle: build a map loop twice, run loopvec (and the full pipeline // so SLP widens it) on one, JIT both, run over real arrays, require bit-identical output for every // trip count (0 / below V / exactly V / multiples / non-multiples). const loopvec_diff = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/loopvec_differential.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(loopvec_diff).step); // Prefetch differential oracle: build a function twice, hand-insert a `prefetch` // hint in one copy, JIT both on the host, and require identical results for every // input. Proves the PRFM (aarch64) / dropped-hint (elsewhere) lowering has no // observable effect. Runs where the native JIT has a backend. const prefetch_diff = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/prefetch_differential.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(prefetch_diff).step); // Vector memory coalescing differential oracle: build a scalar elementwise memory kernel twice, // run one copy through microarch.optimize (which coalesces contiguous scalar loads/stores into // wide vector loads/stores), JIT both on the host, and require identical per-element results, // including the safety case where a store between the loads makes coalescing decline. Runs where // the native JIT has a backend. const vector_mem_diff = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/vector_mem_differential.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(vector_mem_diff).step); // INT8 dot-product differential oracle: build a scalar `sum a[i]*b[i]` int8 reduction twice, // vectorize one copy to SDOT/UDOT under the ampere-altra model, JIT both on the host, and require // identical results across lengths including non-multiples of 16 (exercising the remainder loop), // for both signed (SDOT) and unsigned (UDOT). Aarch64-only; skips elsewhere. const dotprod_diff = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/dotprod_differential.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(dotprod_diff).step); // Microarch optimizer end-to-end harness (spec chunk 10 capstone): three kernels compiled both // plainly and through the full pipeline (microarch.optimize + the model-aware backend compile), // JIT'd on the host, results required identical, cycles logged. Aarch64-only; skips elsewhere. const microarch_e2e = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/microarch_e2e.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-opt", .module = vulcan_opt }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(microarch_e2e).step); // SM10 P2c Task 4: runtime `-target` dispatch. For each of the 4 backends, emits // an `int main(void){return 42;}` object via `native.writeObjectDataFor` (host- // independent - the switch dispatches to that backend's own object writer // regardless of which arch this test binary itself runs on), links + wraps it in // a runnable ELF, and executes it: natively for aarch64, under qemu- for // the other three (skips cleanly if that qemu isn't installed). const cross_target = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/tests/cross_target.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-link", .module = vulcan_link }, }, }) }); test_step.dependOn(&b.addRunArtifact(cross_target).step); // NVIDIA hardware execution. This compiles kernels with the SASS backend and RUNS them on // a real GPU through the nvidia.zig compute dispatch, then reads the buffers back. Every // other NVIDIA test in this repository checks the STRUCTURE of the instruction stream, so // this is the only one that proves the silicon agrees. // // nvidia.zig needs Linux ioctls, so it stays TEST ONLY: no module under libs/ imports it, // and the freestanding proof does not see it. The test skips when no GPU answers. const nvidia_dep = b.dependency("nvidia", .{ .target = target, .optimize = optimize }); const nvidia_execute = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/nvidia/tests/execute.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "nvidia", .module = nvidia_dep.module("nvidia") }, }, }) }); const run_nvidia_execute = b.addRunArtifact(nvidia_execute); test_step.dependOn(&run_nvidia_execute.step); const nvidia_execute_step = b.step("test-nvidia-execute", "Run NVIDIA hardware execution tests"); nvidia_execute_step.dependOn(&run_nvidia_execute.step); // The 64-bit address carry chain. A global pointer add is an IADD3 plus an // IADD3.X that reads the carry, and every other pointer test in this // repository uses buffers whose low halves never overflow, so all of them // pass with the carry dropped. This one places two buffers exactly 4 GiB // apart and makes the carry the only difference between them. const nvidia_carry = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/nvidia/tests/carry.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "nvidia", .module = nvidia_dep.module("nvidia") }, }, }) }); test_step.dependOn(&b.addRunArtifact(nvidia_carry).step); // Address forming and immediate operands. A constant goes in the instruction that // reads it: an ALU operand in the 32-bit immediate field, a byte offset in the // address displacement of LDG/STG/LDS/STS. Both remove a MOV and a register, and the // address fold removes a whole IADD3 carry chain, so a wrong field silently reads or // writes the wrong place. Every test here runs on the GPU and checks the numbers. const nvidia_addressing = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/nvidia/tests/addressing.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "nvidia", .module = nvidia_dep.module("nvidia") }, }, }) }); test_step.dependOn(&b.addRunArtifact(nvidia_addressing).step); // The Glacier GPU target. A Glacier core's instruction words ARE RISC-V words, so the only // thing this proves about encoding is that the shared riscv64 encoders emit them: the kernel // below is diffed against the word stream Glacier's own assembler produces for a program that // runs on an ECP5 FPGA today. Codegen from IR is checked against the same oracle as it lands. const glacier_mandelbrot = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/glacier/tests/mandelbrot.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(glacier_mandelbrot).step); // The same image, lowered from Vulcan IR instead of hand-assembled. A compiler picks other // registers and another instruction order, so the two word streams differ by design. Both run // on a warp model and the FRAMES are diffed, which is the statement that matters. const glacier_lower = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/glacier/tests/lower.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(glacier_lower).step); // The `glacierctl exec kernel.spv` path: a SPIR-V compute module through the SPIR-V lowering, // through the Glacier selector, executed on the warp model. // The other direction: Glacier words lifted back into IR and compiled for the host, so a // kernel runs with no board attached. Diffed against the warp model, which decodes the words // itself and shares none of the lifter. const glacier_lift = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/glacier/tests/lift.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(glacier_lift).step); const glacier_spirv = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/glacier/tests/spirv.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, .{ .name = "vulcan-spirv", .module = vulcan_spirv }, .{ .name = "vulcan-target", .module = vulcan_target }, }, }) }); test_step.dependOn(&b.addRunArtifact(glacier_spirv).step); // Scalar-expanded matmul over deliberately odd global pointers. The expansion is test-owned: // NVIDIA production codegen receives the ordinary scalar IR, then real hardware proves its // bytewise memory lowering preserves the exact footprint and fp32 bits. const nvidia_unaligned_matmul = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-target/nvidia/tests/unaligned_matmul.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-ir", .module = vulcan_ir }, .{ .name = "vulcan-gpu", .module = vulcan_gpu }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "nvidia", .module = nvidia_dep.module("nvidia") }, }, }) }); test_step.dependOn(&b.addRunArtifact(nvidia_unaligned_matmul).step); // GLSL frontend tests: parsing/lowering (IR only), plus execution (GLSL -> IR -> // host JIT -> run) for scalar functions. const glsl_tests = b.addTest(.{ .root_module = vulcan_glsl }); test_step.dependOn(&b.addRunArtifact(glsl_tests).step); const glsl_exec = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("libs/vulcan-glsl/tests/native.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "vulcan-glsl", .module = vulcan_glsl }, .{ .name = "vulcan-spirv", .module = vulcan_spirv }, .{ .name = "vulcan-target", .module = vulcan_target }, .{ .name = "vulcan-opt", .module = vulcan_opt }, }, }) }); test_step.dependOn(&b.addRunArtifact(glsl_exec).step); const cc_tests = b.addTest(.{ .root_module = vulcan_cc }); test_step.dependOn(&b.addRunArtifact(cc_tests).step); // The freestanding object is a compile check too (no run), so `test` keeps the core // building for whatever target is selected. test_step.dependOn(&freestanding_proof.step); } fn addBenchVcc(b: *std.Build, target: std.Build.ResolvedTarget) *std.Build.Step.Compile { const bench_ir = b.addModule("vulcan-bench-ir", .{ .root_source_file = b.path("libs/vulcan-ir.zig"), .target = target, .optimize = .ReleaseFast, }); const bench_gpu = b.addModule("vulcan-bench-gpu", .{ .root_source_file = b.path("libs/vulcan-gpu.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{.{ .name = "vulcan-ir", .module = bench_ir }}, }); const bench_opt = b.addModule("vulcan-bench-opt", .{ .root_source_file = b.path("libs/vulcan-opt.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "vulcan-ir", .module = bench_ir }, .{ .name = "vulcan-gpu", .module = bench_gpu }, }, }); const bench_spirv = b.addModule("vulcan-bench-spirv", .{ .root_source_file = b.path("libs/vulcan-spirv.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "vulcan-ir", .module = bench_ir }, .{ .name = "vulcan-gpu", .module = bench_gpu }, }, }); const bench_link = b.addModule("vulcan-bench-link", .{ .root_source_file = b.path("libs/vulcan-link.zig"), .target = target, .optimize = .ReleaseFast, }); const bench_target = b.addModule("vulcan-bench-target", .{ .root_source_file = b.path("libs/vulcan-target.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "vulcan-ir", .module = bench_ir }, .{ .name = "vulcan-opt", .module = bench_opt }, .{ .name = "vulcan-spirv", .module = bench_spirv }, .{ .name = "vulcan-gpu", .module = bench_gpu }, .{ .name = "vulcan-link", .module = bench_link }, }, }); const bench_cc = b.addModule("vulcan-bench-cc", .{ .root_source_file = b.path("libs/vulcan-cc.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "vulcan-ir", .module = bench_ir }, .{ .name = "vulcan-target", .module = bench_target }, }, }); return b.addExecutable(.{ .name = "vcc-bench", .root_module = b.createModule(.{ .root_source_file = b.path("frontends/vcc.zig"), .target = target, .optimize = .ReleaseFast, .strip = true, .imports = &.{ .{ .name = "vulcan-cc", .module = bench_cc }, .{ .name = "vulcan-target", .module = bench_target }, .{ .name = "vulcan-link", .module = bench_link }, .{ .name = "vulcan-opt", .module = bench_opt }, }, }) }); } fn addCudaBench(b: *std.Build, target: std.Build.ResolvedTarget) *std.Build.Step.Compile { const bench_ir = b.addModule("vulcan-cuda-bench-ir", .{ .root_source_file = b.path("libs/vulcan-ir.zig"), .target = target, .optimize = .ReleaseFast, }); const bench_gpu = b.addModule("vulcan-cuda-bench-gpu", .{ .root_source_file = b.path("libs/vulcan-gpu.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{.{ .name = "vulcan-ir", .module = bench_ir }}, }); const bench_opt = b.addModule("vulcan-cuda-bench-opt", .{ .root_source_file = b.path("libs/vulcan-opt.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "vulcan-ir", .module = bench_ir }, .{ .name = "vulcan-gpu", .module = bench_gpu }, }, }); const bench_spirv = b.addModule("vulcan-cuda-bench-spirv", .{ .root_source_file = b.path("libs/vulcan-spirv.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "vulcan-ir", .module = bench_ir }, .{ .name = "vulcan-gpu", .module = bench_gpu }, }, }); const bench_link = b.addModule("vulcan-cuda-bench-link", .{ .root_source_file = b.path("libs/vulcan-link.zig"), .target = target, .optimize = .ReleaseFast, }); const bench_target = b.addModule("vulcan-cuda-bench-target", .{ .root_source_file = b.path("libs/vulcan-target.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "vulcan-ir", .module = bench_ir }, .{ .name = "vulcan-opt", .module = bench_opt }, .{ .name = "vulcan-spirv", .module = bench_spirv }, .{ .name = "vulcan-gpu", .module = bench_gpu }, .{ .name = "vulcan-link", .module = bench_link }, }, }); const nvidia_dep = b.dependency("nvidia", .{ .target = target, .optimize = .ReleaseFast }); return b.addExecutable(.{ .name = "vulcan-cuda-bench", .root_module = b.createModule(.{ .root_source_file = b.path("tools/cuda-bench.zig"), .target = target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "vulcan-ir", .module = bench_ir }, .{ .name = "vulcan-gpu", .module = bench_gpu }, .{ .name = "vulcan-target", .module = bench_target }, .{ .name = "nvidia", .module = nvidia_dep.module("nvidia") }, }, }) }); }