build.zig
Rama master · réplica de lectura del repositorio original.
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: -- <file.wasm> [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: -- <input.glsl> [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: -- <file>");
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 <tag> | --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`,
// `<stdarg.h>`, 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 <stdio.h>` 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-<arch> 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") },
},
}) });
}