In 1976, a Bell Labs researcher named Stuart Feldman was annoyed at a coworker. The story, as it's been retold for half a century now, goes something like this: a colleague kept shipping broken binaries because he'd edit a source file and forget to recompile the parts of the program that depended on it. Feldman's fix wasn't a lecture. It was make — a tool whose entire reason for existing was to answer one question correctly, automatically, every single time: what, exactly, needs to be rebuilt right now, and what doesn't?
That question turned out to be one of the hardest, most quietly load-bearing problems in all of software engineering. Fifty years later, we're still answering it badly, most of the time, and most engineers have simply stopped noticing.
Make aged the way anything does when it becomes foundational infrastructure nobody's allowed to break: badly, slowly, and in public. Its dependency model — timestamps, phony targets, recursive invocation — leaked complexity into every project that grew past a few dozen files, and autotools showed up to paper over that with an entire generation-of-generators architecture that, to this day, remains one of the most feared corners of the open-source toolchain. CMake arrived at the turn of the millennium out of Kitware's need to build ITK and VTK across radically different platforms without hand-maintaining a Makefile per OS — a meta-build system that generates other build systems, which is either a brilliant abstraction or a confession that the underlying problem was never actually solved, depending on which decade of CMake you personally suffered through.
And then there's Ninja. Evan Martin built it at Google specifically because Chromium's build had grown so large that Make's dependency-graph evaluation had become a measurable, painful part of every engineer's day — and his answer was almost insultingly minimal: a build file format so sparse that the project's own documentation tells you, directly, that you are not meant to write Ninja files by hand. Ninja doesn't try to be pleasant. It tries to be a fast, dumb, extremely well-behaved execution target for something smarter sitting above it, generating its files for it. That single design decision — radical minimalism as a feature, not a limitation — is one of the most underrated ideas in build tooling history, and it's the closest ancestor to what comes next in this story.
Because none of Make, Autotools, CMake, or Ninja were ever built for the workload I actually live in: assembly-heavy code, small C translation units by the hundreds, embedded and bare-metal targets, mixed-language repositories where the coordination between compiler invocations costs more than the invocations themselves. I started building ForgeZero to solve that gap about a year and a half ago, quietly, on my own time, the way most infrastructure worth trusting actually gets built. It went public in May 2026. This is the deep engineering account of what it actually is.
The Thesis, Stated Plainly
A build system should handle planning, invalidation, and routing. It should not try to reinvent the compiler.
That single sentence is the entire architectural spine of ForgeZero, and everything below is just the consequence of taking it seriously. ForgeZero is not GCC, Clang, Zig, NASM, FASM, GAS, or LD, and it has no interest in becoming any of them. It's the thin, transparent coordination layer that decides what those tools need to do, when, and whether they need to do it again at all — and then gets entirely out of the way.
The value proposition compresses into one relationship:
Build Throughput ≈ Useful Compiler Work
─────────────────────────────────────────
Discovery + Dispatch + Repeated Work + Invalidation Cost
Every serious build-system optimization in ForgeZero attacks the denominator. None of it touches the numerator, because the numerator — actual code generation correctness — belongs to GCC, Clang, Zig, and the assemblers, permanently, and ForgeZero has no ambition to argue with that division of labor.
Filesystem state becomes a source set. The source set becomes a dependency closure. That closure becomes a preprocessing state, then a cache identity, then a set of compiler and assembler actions, then an object graph, then a linker invocation, then a verified artifact.
filesystem state
-> source set
-> dependency closure
-> preprocessing state
-> cache identity
-> compiler/assembler actions
-> object graph
-> linker invocation
-> verified artifact
What makes this more than a diagram is that each arrow is an owned boundary with its own failure class. A discovery failure is not a cache failure. A cache failure is not a linker failure. When something goes wrong, an engineer using ForgeZero is diagnosing one specific, narrow transformation — not reverse-engineering an opaque shell invocation that happened to fail somewhere in the middle of a black box.
Four properties fall directly out of respecting that boundary discipline:
- Observability — the selected backend, target, flags, object paths, cache decisions, and exact linker command are all visible, not inferred.
- Controllability — ABI, compiler, linker, optimization level, sysroot, and output format are never hidden behind a "just trust the abstraction" layer.
- Amortization — unchanged units don't get recompiled, and metadata checks run before the expensive stages, not after.
- Composability — assembly, C-family code, Zig, Gloria, custom preprocessing, and security tooling all live inside one coordinated pipeline instead of five disconnected ones duct-taped together in CI.
Who This Was Actually Built For
This isn't a general-purpose build tool wearing a performance costume. It has specific, named audiences, and it's honest about who they are:
- Assembly engineers — boot code, runtime glue, kernel-adjacent components, flat binaries, hand-managed ELF/PE/Mach-O pipelines, where NASM/FASM/GAS selection and the resulting object format can't be papered over.
- C/C++/Objective-C engineers who need direct, unfiltered access to GCC, Clang, or Zig — their own
-I, -D, -O, sanitizer, and linker flags surviving intact into the actual command line.
- Embedded and cross-compilation engineers targeting ARM, RISC-V, and bare-metal ABIs, where the target triple has to participate directly in backend selection, not get normalized away.
- Release and supply-chain engineers who need reproducible builds, source-tree verification, SBOM generation, SAST-style auditing, and filesystem isolation — as genuinely separate concerns from the build cache, never conflated with it.
- Build infrastructure authors looking to replace a Make/Ninja/shell-script Frankenstein with one coordinator that has a predictable cache model and room for hooks, watchers, and compile databases.
Cache Identity: The Part Everyone Gets Wrong
Here's the mistake that shows up in almost every homegrown build cache: treating "the source bytes didn't change" as sufficient justification for reusing a compiled object. It isn't, and pretending it is produces silent, maddening correctness bugs. The same source file produces a different correct object under a different compiler, a different compiler version, a different target triple, a different sysroot, different optimization flags, different sanitizer policy, or different linker configuration.
ForgeZero's action-level cache identity is built from the sorted inputs, their digests, the action name, and the full environment and toolchain parameters — not the source hash alone. Two layers do the heavy lifting:
Metadata-first invalidation checks file size and modification time before touching the file's bytes at all. If both match a cache entry, the expensive digest step is skipped entirely. Only a mismatch triggers a full keyed digest computation. This is explicitly a performance optimization, not a cryptographic guarantee — and ForgeZero doesn't pretend otherwise. Security-sensitive verification lives in its own, separately-specified verify/SBOM contract, deliberately kept apart from the invalidation fast path.
The disk hash cache format, FZHC3, stores path length, metadata, a context fingerprint, and a 32-byte digest, written via a temp-file-then-rename sequence specifically to avoid leaving a half-written cache entry behind on a crash. Small detail. The kind of detail that separates a cache you can trust from one you quietly stop trusting after the third mysterious corruption.
Two Hashes, One Honest Boundary
This is where ForgeZero's engineering has genuinely moved since it first went public, and it's worth explaining precisely, because the project draws a hard, deliberate line between two hashing systems that solve related but different problems.
BoomBoom Hash is the original incremental-invalidation engine, built as a specialized layer over keyed BLAKE3. Build context — compiler identity, version, target triple, ordered flags — gets domain-separated and hashed into a derived key first, so the same source bytes produce different digests under different build contexts without any cross-contamination between cache domains. Source files are broken into logical chunks — directive regions, brace-delimited blocks, top-level statements, residual spans — each independently digested and placed as a leaf in a flat, heap-layout Merkle tree. Change one chunk, and only the path from that leaf to the root gets recomputed: O(log n) instead of a full O(n) re-aggregation. Past four changed chunks, a persistent worker pool picks up jobs through atomic fetch-and-add over a flat array, each worker holding its own independent keyed BLAKE3 state, with zero shared mutable state at the hasher level.
BB64 is newer, and it exists precisely because BoomBoom Hash, built on a general-purpose cryptographic primitive, is doing more work than some invalidation paths actually need. BB64 is a purpose-built, non-cryptographic 64-bit hash with a hand-written AVX2 Plan 9 assembly hot path, a scalar fallback verified for bit-exact equivalence against the SIMD path, explicit tail handling for inputs of arbitrary length, and SIMD-accelerated byte comparison and delimiter scanning. On an AMD Ryzen 7 PRO 4750U, it measured roughly 16.87 GB/s at 0 B/op and 0 allocs/op — against roughly 0.42 GB/s for FNV-1a and roughly 1.71 GB/s for BLAKE3 on the same benchmark input. That's about 40x FNV-1a and nearly 10x BLAKE3, for a task — PCH cache-key generation, keyed off header content, compiler, target, flags, and positions — that never needed cryptographic guarantees in the first place.
And here's the part that should matter most to anyone evaluating this as infrastructure, not just admiring the throughput number: BB64 is explicitly, deliberately never used for security-sensitive file integrity, package verification, or any cryptographic surface. That boundary isn't an oversight waiting to be "optimized away" later. It's the whole point. A fast non-cryptographic hash and a keyed cryptographic hash are different tools solving different problems, and collapsing that distinction to save one dependency is exactly the kind of shortcut that turns into a CVE two years later. ForgeZero doesn't take it.
Below the Go Standard Library: The Discovery Layer
This is the part that will make a systems engineer sit up, because it's the difference between "a build tool written in Go" and "a build tool written by someone who's actually spent time fighting syscall on Linux.
Source discovery on Linux bypasses the convenient, portable filepath.WalkDir entirely in favor of raw getdents64 traversal via unix.Getdents, paired with openat using O_NOFOLLOW to keep directory traversal from silently following a symlink somewhere it shouldn't. Metadata collection goes through Linux statx for type, inode, size, and modification time in a single call, falling back to Fstatat on kernels where statx isn't reliably available. Directory reads retry on EINTR instead of surfacing a spurious failure, malformed dirents get validated rather than trusted, and path ordering is made deterministic so two runs on the same tree produce the same discovery order — a small property that matters enormously the first time you're trying to reproduce a flaky build. Non-Linux platforms fall back cleanly to filepath.WalkDir, because the portable API contract stays identical even when the backend implementation doesn't.
The payoff isn't hypothetical: manual dirent parsing measurably cut Linux discovery allocations on a 1,024-file benchmark from roughly 3,182 down to 2,088 allocations per operation — about a third fewer allocations, on a code path that runs at the very start of every single build.
io_uring, Used Honestly
ForgeZero's io_uring integration batches file reads into a single submission cycle instead of one syscall per file, drains completion queue entries fully with explicit user-data bounds checking, handles zero-length files without generating an invalid empty READ request, and supports IORING_OP_STATX for metadata collection through the same ring. It enables IORING_SETUP_COOP_TASKRUN where the kernel supports it, with a clean setup fallback for older kernels that don't.
And then there's the detail that actually tells you how this team thinks: IORING_OP_GETDENTS was never wired up, because the target kernel ABI doesn't expose a usable io_uring GETDENTS opcode — so rather than fabricate support for an operation that doesn't reliably exist, directory listing stays on the syscall path where it actually works. That's a small, unglamorous decision. It's also exactly the kind of decision that separates infrastructure you can trust from infrastructure that looks impressive in a README until someone runs it on the wrong kernel version.
Linking, Scheduling, and the False-Sharing Detail Most Projects Never Bother With
Symbol cache decoding is strict — malformed records are rejected outright rather than partially trusted, triggering a fresh extraction through nm, objdump, or readelf instead of silently propagating a corrupted cache entry into a link step. Symbol results move through indexed, preallocated result slots instead of a per-object queue payload, cutting allocation churn on the hot link path. The scheduler supports idempotent shutdown and explicit stopped-state handling, so a second shutdown call doesn't do anything surprising.
And then there's this: worker and scheduler queue producer/consumer counters are deliberately separated across 64-byte cache lines, specifically to avoid false sharing — the scenario where two unrelated atomic counters happen to land on the same CPU cache line and start silently fighting each other for cache coherency traffic under concurrent load, tanking throughput for no reason visible anywhere in the code. This is genuinely advanced systems-engineering territory, the kind of optimization that only shows up in codebases that have actually been profiled under real concurrent load rather than just "made to compile and pass CI."
Gloria: The Language That Refuses to Hide Anything
Sitting inside ForgeZero is Gloria, an experimental language that isn't trying to compete with C, Rust, or a full assembler frontend. It occupies a specific, narrow niche: a minimal language for producing a small, fully-understood fragment of machine code in contexts where a libc, a runtime, and a general-purpose compiler pipeline would be overkill.
source -> lexer -> tokens -> codegen -> machine bytes -> relocation -> raw binary
There's no mandatory intermediate object file, no libc startup sequence, no standard linker script. The emitter writes bytes directly, and ForgeZero writes them out as a raw binary. Functions land in a contiguous output buffer; calls are written first with relocation placeholders, and once the function table is complete, relative displacements get patched directly into the buffer:
disp32 = offset(target) - (offset(call) + 5)
That's the entire mechanism that lets Gloria skip a full object-file linker for small raw images. Compile this:
fn main() {
let a = 10;
let b = 20;
if b > a {
print("hello world lol");
}
}
and disassembling the resulting gloria.bin shows exactly what you'd hope for from a language that refuses to hide anything: an entry jump and stack-frame prologue, 64-bit immediate loads materializing the literals, a cmp/jle pair lowering the comparison, and — because the observed output loop writes directly to 0xb8000, the classic x86 VGA text-memory address — a byte-by-byte character-and-attribute write loop instead of a hidden printf call. Every single opcode in that disassembly traces back to a specific, readable decision the emitter made. That transparency, not competing with production languages, is the entire point of Gloria existing at all.
Security as a Neighbor, Not a Costume
ForgeZero keeps its security surfaces deliberately separate from its performance surfaces, because conflating "this build was fast" with "this build can be trusted" is exactly how supply-chain incidents happen. Path validation constrains CLI paths, symlink boundaries, and FZP include resolution to explicitly allowed roots. A dedicated seal/integrity subsystem tracks trust state for executables and toolchains — a fundamentally different question from "do I need to recompile this file," and the codebase treats it as one. SBOM generation produces a CycloneDX-style component inventory with hashes; a separate audit subsystem runs SAST-style checks for secrets, vendor-specific risk patterns, and configuration violations; manifest-based source-tree verification is kept algorithmically stable specifically so release verification stays interoperable across versions instead of silently drifting.
Cache hits are fast. Cache hits are not proof of integrity. ForgeZero's architecture never lets those two facts get confused with each other.
The Redis Case Study, Read Correctly
The number worth reporting is this one:
fz -p performance -dir redis/src -mode c -out redis-server -j 16
user 27.66 s
system 11.68 s
CPU 1155%
elapsed 3.404 s
Read it precisely, because precision is the entire point of this project. 1155% CPU utilization means the work was genuinely spread across multiple hardware threads, not disguised behind a fast single-thread path. 3.404 seconds elapsed against nearly 40 seconds of combined CPU time shows the bottleneck sitting in parallel compile-and-link work, not in sequential orchestration overhead — which is exactly where you want a build's time to be spent. The dependency closure for that run wasn't trivial: fpconv, hdr_histogram, hiredis, libxxhash, linenoise, lua, and tre were each independently compiled into static archives, then handed to the linker alongside Redis's own translation units — proving the pipeline handles real, dependency-aware, multi-ABI native builds, not just a flat folder of standalone .c files.
The honest framing matters more than the headline number: this is end-to-end orchestration latency on one specific host, toolchain, and cache state — not a claim about compiler speed, and not a number that means anything without the CPU model, governor, storage, toolchain versions, cache warmth, and parallelism level that produced it. ForgeZero's own documentation says exactly that, unprompted, which is a more convincing performance claim than any raw benchmark number could be on its own.
The Go core stays portable while low-level operations are split cleanly across build-tagged files: Linux gets native getdents64/statx/io_uring fast paths, other Unix-like targets get mmap/descriptor-based paths, Windows gets its own executable, security, and filesystem branches entirely. Cross-platform, here, doesn't mean identical capability everywhere — it means an identical semantic contract at the API boundary regardless of which backend is actually doing the work underneath it. Linux can have the fastest path available to it without Windows being forced into an emulation layer pretending to be a kernel it isn't.
The Installer Nobody Notices Until It Breaks
install.sh got rewritten as a strict POSIX sh script with explicit platform and architecture validation, --help and missing-option handling, retry-and-timeout logic on both curl and wget, and remote and local checksum verification before anything gets executed. The insecure mktemp -u pattern — which creates a predictable race window between naming a temp path and actually using it — was replaced with a properly private temporary directory. Writable installs land through a temp-file-then-atomic-rename sequence; privileged installs go through sudo install rather than a raw copy; a full dry-run mode exists that downloads and modifies nothing; cleanup traps make sure temp files don't survive a failed run. It's the least glamorous file in the entire repository, and it's exactly the file that determines whether someone's first five minutes with the project go smoothly or end with a half-installed binary and a GitHub issue.
What ForgeZero Deliberately Doesn't Do — And Why That's Worth Trusting
The most senior-engineer-coded section of this entire project's documentation isn't a feature list. It's a short list of things the team explicitly chose not to build, and it's worth reading as a statement of engineering values as much as a technical note:
- BB64 stays out of every security-sensitive path — integrity checks, package verification, and cryptographic APIs all keep using the keyed BLAKE3-based BoomBoom Hash, on purpose.
IORING_OP_GETDENTS was never faked into existence to check a feature-completeness box, because the kernel ABI in question genuinely doesn't expose a usable opcode for it.
clone3/execveat weren't forced into the process launcher without a proper lifecycle design, because Go's context cancellation, descriptor ownership, and cross-platform launcher contracts all deserve a real design pass, not a shortcut.
- A shared-memory persistent cache wasn't shipped without a versioned format, crash recovery, and an ownership protocol behind it — because a cache that can silently corrupt itself is worse than no cache at all.
- No thousand-fold speedup claims got made without workload-specific measurements standing behind them.
Any team can list what they built. Far fewer are willing to publish, in plain language, what they refused to ship half-finished — and that restraint is a far stronger signal of engineering maturity than another benchmark chart would ever be.
Where This Actually Leaves You
ForgeZero isn't trying to convince you that gcc -O3 needs replacing. It's making a much narrower, much more defensible claim: that the coordination surrounding native compilation — discovery, invalidation, scheduling, dispatch, caching, linking — has its own real, measurable cost, and that cost has been quietly tolerated by an entire industry for decades because Make, Autotools, CMake, and Ninja were each solving the build problem of their own era, not this one. Assembly-heavy, embedded, mixed-language, cache-identity-sensitive native builds were never really the target audience for any of them.
A year and a half of quiet, unglamorous systems work — cache-line-aware schedulers, kernel-ABI-honest io_uring integration, a hash function that knows exactly which problems it's allowed to touch and which ones it isn't — is what it actually takes to make that narrower claim true instead of just plausible. That's the whole pitch. Not magic. Not a thousand-fold speedup. Just a build system that finally stops forgiving itself for not knowing precisely why it did what it just did.