Skip to content

Instantly share code, notes, and snippets.

@codegraphtheory
Created November 9, 2025 22:26
Show Gist options
  • Select an option

  • Save codegraphtheory/4ef8001b6c495625d0fec84db60ab73c to your computer and use it in GitHub Desktop.

Select an option

Save codegraphtheory/4ef8001b6c495625d0fec84db60ab73c to your computer and use it in GitHub Desktop.
Designing future compilers

Designing future compilers

This post explains why a radically different design is needed for future generation Compilers, how current compilers work and outline a vision for how future compilers might work.

Why a radically different compiler is needed

Simply put, modern Compilers were invented to transliterate between human-readable coding languages and machine-readable assembly code. Over time, they've also implemented optimizations to create faster software, guardrails to enforce good coding practices, and features targeting alternative platforms.

With the advent of the Large Language Model (LLM) and coding Agents built on top of them, the key responsibility of the compiler shifts from a feedback system for a human writing a program to a feedback system for the operator of a coding agent. That operator could be either a human being or an orchestration agent.

Compilers should therefore work a bit differently:

  • Diagnostic output should be as verbose as possible, outputting the entire diagnostic trace including machine-readable information that LLMs can make sense of, rather than prioritizing concise, human-readable output.
  • Usage and access patterns should be declarative and literal where the compiler frontend and compiled output give clear, complete and deterministic information about operations of resulting programs at runtime.
  • Assembler code is no longer the default output mode of choice. Programs should output model weights, structured tool calls, or text instruction for LLM-based agents preferentially to machine-runnable MIPS code.

Implementing the above changes would improve the safety and predictability of generated code and enable more context-rich and flexible patterns of interoperation.

What can be changed?

As engineers, we should start this discussion with a feasibility analysis. What is objectively within our power to change?

While Simon Willison posits LLMs could encourage new coding languages and efforts like LMQL have attempted to formalize grammars codified for LLMs, let's be realistic.

New coding languages will face a "cold-start problem" or at least a massive headwind in bootstrapping enough high-quality training data that agents can learn to write the langauge well. Datasets existing models are trained on, such as this very website you're reading this on, have been heavily reviewed, organized, and iteratively improved over decades. Replicating training data of this quality and variety would be non-trivial.

Therefore, I disagree that a new compiler frontend will catch on or be economically useful in the near term. I believe we can create massive productivity and economic value by creating new intermediate representations (IR) and/or compiler backends.

In the next doc, I'll summarize how a modern compiler works to the extend necessary to explain the direction I see this going in the future. In the third and final page, I'll describe my vision for the future.

How modern compilers work

This file will briefly explain the operations of modern optimizing compilers in order to give context for my vision for future compilers. If you are already very familiar with the operations of a compiler, feel free to skim this file or skip over it entirely!

Modern compilers are sophisticated tools that transform high-level source code into executable machine code. They typically operate in three main stages: the frontend, the intermediate representation (IR), and the backend. This modular design allows for flexibility—different frontends can handle various programming languages, while backends target specific hardware architectures. Below, I'll break it down step by step, with a simple diagram to illustrate the flow.

Compiler pipeline overview

Here's a high-level flowchart of the process (rendered as ASCII art for simplicity; imagine this as a directed graph in a real diagram tool):

Source Code (e.g., C++, Rust) 
       ↓
[Frontend: Lexing → Parsing → Semantic Analysis → AST Generation]
       ↓
[Intermediate Representation (IR): Platform-independent code for optimizations]
       ↓
[Backend: IR Optimization → Code Generation → Assembly/Machine Code]
       ↓
Executable Binary

This pipeline ensures that optimizations can be applied efficiently without tying them to a specific language or hardware.

Frontend: from source code to Abstract Syntax Tree (AST)

The frontend is where the compiler first interacts with the human-written (or agent-generated) source code. Its primary job is to parse the input language and convert it into a structured form that the compiler can work with internally. This stage is language-specific—for example, a C++ frontend handles C++ syntax, while a Rust frontend deals with Rust's borrow checker rules.

Key steps include:

  • Lexing (Tokenization): Breaks the source code into tokens like keywords (if, while), identifiers (variable names), operators (+, =), and literals (numbers, strings).
  • Parsing: Analyzes the tokens to build a parse tree or Abstract Syntax Tree (AST) based on the language's grammar rules. This checks for syntactic correctness (e.g., matching parentheses).
  • Semantic Analysis: Performs deeper checks, like type checking, ensuring variables are declared before use, and resolving references. It might also generate initial error messages if something doesn't make sense logically.

By the end, the frontend outputs an AST or a preliminary IR, which represents the program's structure without platform details. Think of it as translating messy human intent into a clean, tree-like blueprint.

Step Purpose Example Input/Output
Lexing Tokenize raw text int x = 5; → Tokens: int, x, =, 5, ;
Parsing Build syntax tree Tokens → AST node: Declaration(type=int, name=x, value=5)
Semantic Analysis Check meaning AST → Validated AST with types resolved, or errors like "undeclared variable"

Intermediate Representation (IR): the optimization hub

Once the frontend has done its job, the compiler converts the AST into an Intermediate Representation—a simplified, language-agnostic form of the code. IR is like a neutral ground where most optimizations happen, detached from both the source language and the target machine.

Popular IR formats include LLVM IR (used in compilers like Clang and Rustc), which looks like a low-level assembly but is still abstract. Optimizations here might include:

  • Dead code elimination (removing unused parts).
  • Loop unrolling or inlining functions for performance.
  • Constant folding (pre-computing expressions like 2 + 3 into 5).

The IR stage is crucial because it's reusable across languages and targets, making compilers more efficient to build and maintain. In modern optimizing compilers, multiple passes over the IR refine the code iteratively.

Backend: targeting the machine

The backend takes the optimized IR and generates code tailored to a specific architecture (e.g., x86, ARM) or platform (e.g., WebAssembly for browsers). This is where the compiler gets hardware-specific.

Key steps:

  • IR to Machine Code Translation: Maps IR instructions to assembly or object code.
  • Target-Specific Optimizations: Adjusts for things like register allocation, instruction scheduling, or vectorization to exploit CPU features.
  • Assembly and Linking: Outputs assembly code, which an assembler turns into machine code, then links with libraries to create an executable.

The result? A binary that's fast and efficient on the intended hardware. For example, GCC's backend might produce x86 assembly, while emitting debug info or symbols for tools like debuggers.

Component Role Output Example
Code Generator Translate IR to assembly IR instr: add %1, %2 → x86: add rax, rbx
Optimizer Hardware tweaks Adjust for cache sizes or SIMD instructions
Assembler/Linker Finalize binary Assembly → Executable file (e.g., .exe)

This breakdown shows how compilers are engineered for modularity and performance today. In my vision for future compilers (coming in the next file), we'll rethink these stages to better support LLM-driven coding agents, shifting focus from human-centric outputs to agent-friendly ones like verbose diagnostics and alternative representations.

Vision for Future Compilers

While I can't share my complete vision or a working implementation today, here are the three most important pieces of the vision.

Declarative Dependency Graphs

Represent dependencies as explicit, literal graphs in IR, providing deterministic runtime operation info for LLMs to analyze without execution. This turns IR into a transparent blueprint for safer agent reasoning.

Why This Drastically Improves Life for Coders Working with Agents

Debugging agent-generated code often involves risky executions to uncover dependencies. These graphs offer a visual map, letting agents refactor preemptively for safety or parallelism, cutting iteration time and boosting reliability so coders focus on design.

Example Graph

For a data processing program (ASCII art; serialized as JSON in practice):

Input
 ↓
ProcessA
 ├── ProcessB
 │   ↓
 │ Merge
 └── ProcessC
     ↓
   Merge
     ↓
 Output

This highlights parallel paths for LLM optimizations.

Verbose, Hierarchical Diagnostics Graphs

Output multi-level diagnostic trees (e.g., XML/JSON) covering compilation steps and hypothetical failures for LLM debugging, evolving errors into navigable structures.

Why This Drastically Improves Life for Coders Working with Agents

Vague errors frustrate agent workflows, requiring manual fixes. These graphs provide detailed branches with "what-ifs," enabling agents to drill down and suggest resolutions autonomously, speeding debugging and reducing interventions for safer scaling.

Example Graph

For a loop error (ASCII; real as structured data):

Root: Pipeline
 ├── Semantic: Warning - Unused Var
 │   └── Hypothetical: Null Risk
 └── Optimization
     └── Loop Unroll: Error - Overflow
         ├── Actual: Aborted
         └── Safe: Cast to Long

Agents parse to propose fixes like type changes.

Collaborative Compilation Modes

Enable multi-agent compilation by distributing IR subgraphs to specialized LLMs for parallel optimization, breaking IR into modular parts for concurrent work.

Why This Drastically Improves Life for Coders Working with Agents

Sequential compilation bottlenecks large projects with agents. This mode delegates subgraphs to experts (e.g., security, performance), enabling faster builds, synergistic optimizations, and cross-verification, turning workflows into efficient orchestrations.

Example Graph

For an ML pipeline (ASCII; exported as files):

Full IR
 ├── Sub1: Data Load (I/O agent)
 │   ├── Read
 │   └── Parse
 ├── Sub2: Training (ML agent)
 │   ├── Init
 │   └── Backprop
 └── Sub3: Inference (Perf agent)
     └── Predict

Parallel tweaks reassemble for GPU gains.

Conclusion

If you're interested in developer tools for the future of human-agent collaboration, check out @supermodeltools on X or try our extension or API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment