· 5 min read

The compiler on the wide machine

A compiler can run on the GPU only if the language it compiles breaks the build into many small independent jobs.

Close-up of a silicon wafer showing a grid of identical microchip dies shimmering in rainbow iridescence.
Rob Bulmahn, CC BY 2.0

Plenty of languages compile programs for GPUs. The idea I keep circling back to runs the other way: a language whose compiler does much of its own work there, thousands of narrow workers turning source into machine code together.

You can't get there by porting the CPU loops to kernels. GPUs reward regular batches and punish branching — NVIDIA's CUDA guide tells you outright to minimize divergent paths inside a warp. And a compiler, at least the ordinary kind, spends its day walking syntax trees, chasing symbol links, and waiting on whatever the previous pass owed it. It's close to the worst GPU workload I can think of.

Compile on, not for#

Worth being precise here, because the two get conflated. NVIDIA's toolchain separates host and device code, lowers the device side to PTX, and assembles binaries for the target GPUs. That's compilation for a GPU, and it's well understood. What I'm describing is stranger: the GPU running parts of the compiler pipeline itself.

Existing compilers already show where the seams are. Rust splits code generation into independent units that LLVM can chew on concurrently, while parsing and macro expansion have stayed serial. Read the parallel compiler notes and the pattern is hard to miss: the compiler goes parallel wherever it can name a piece of work that doesn't have to touch shared state.

A fast compiler begins with a language that leaves fewer secrets between files.

Make the source wide#

A language designed for this machine would bake independence into its semantics. Modules publish explicit signatures, public values carry declared types, and an import is never textual inclusion in disguise. Macros can't peek at the filesystem, or the clock, or another module's private syntax. Type inference stops at the function or module boundary instead of quietly stitching the whole program together.

With those rules in place the compiler can flatten its work. Source text turns into arrays of tokens and symbol records instead of a forest of pointer-linked objects, and type checking becomes a batch of local constraint problems over read-only interface tables. Constant folding, specialization, instruction selection: the same operation applied across hundreds of functions at once, each pass reading immutable input and writing a fresh result, so thousands of workers never queue behind a lock.

A small CPU scheduler still runs the show. It owns the dependency graph, the weird diagnostics, linking, anything that needs a whole-program view, while the GPU gets the broad middle of the pipeline, where hundreds of similar jobs sit ready at the same time. The compiler itself stays alive as a persistent process, holding source and intermediate representation resident so an edit doesn't pay for a trip across the bus.

Incremental compilation is where this gets useful day to day. Query-based compilers already model their work as a dependency graph of repeatable computations, so when an edit dirties one node, the compiler reuses everything unaffected and sends only the newly ready frontier to the GPU. The graph is both a cache and a schedule.

Speed needs a crowd#

A tiny program will probably compile slower this way. Starting a device, launching kernels, and moving data all have fixed costs, and a hundred-line file does nothing to amortize them. The GPU earns its place when a workspace holds enough independent functions, when a build server batches several projects, or when a coding agent wants many candidate changes compiled at once. What it needs is a backlog.

Development builds could favor local optimization and immediate machine code, saving the expensive global work for an explicit release build, and the rare branch-heavy passes would stay on the CPU, which handles them fine. You end up with a heterogeneous compiler, each processor doing the work whose shape suits it.

The surface language might look ordinary; what would mark it out is everything it refuses. No hidden imports, no ambient macros, no global inference, no passes that rewrite shared state in place — each of those conveniences spends exactly the independence the GPU needs. I don't know whether anyone will build this language. But a compiler that answers before you've looked away from the editor seems like a fair trade.