Blog

JIT, AOT, and Why We Lower to SSA

Compilation tiers in pd-vm and the role Cranelift plays


Recap: The Interpreter Baseline

pd-vm starts with a compact stack-bytecode interpreter. That baseline matters because proxy scripts need very low startup overhead, predictable suspension, and small execution state.

For short-lived request handlers, the interpreter is often enough. But hot loops and stable long-lived programs benefit from native execution. That is where the trace JIT and AOT tiers enter.

The Three Tiers

Bytecode
  -> Interpreter
  -> Trace JIT
     record -> SsaTrace -> Cranelift -> native trace
  -> Whole-program AOT
     CFG -> AotInstruction -> AotSsaProgram -> Cranelift -> native program
InterpreterTrace JITWhole-program AOT
Compilation unitNoneHot loop tracesEntire program
Warm-upZeroAfter repeated executionOne compile before execution
Internal IRBytecodeSsaTraceAotSsaProgram
Suspension modelNative to the VMExit back to VM stateRe-enter through VM-aware checkpoints

Why Not Use SSA in the Interpreter?

SSA is excellent for optimization, but it is the wrong baseline runtime representation here.

1. It expands the working representation. Stack bytecode is compact because operand flow is implicit. SSA makes that flow explicit, which is valuable for optimization but more expensive to carry in the baseline interpreter.

2. It complicates control-flow joins. SSA needs block parameters or phi resolution. A stack machine can keep the continuation model much smaller and simpler.

3. It does not help suspension. The interpreter must pause and resume around host calls, fuel, and epoch deadlines. Stack plus locals is already a compact continuation format. An SSA register file is not.

4. It solves the wrong problem at this tier. The interpreter's job is quick startup, correctness, and simple embedding. SSA becomes useful only once the runtime has decided to optimize.

Why Lower to SSA for Native Code?

Once the target becomes native code, the trade changes.

1. Native codegen needs explicit data flow. Stack effects hide producer-consumer relationships. SSA reconstructs them in a form the backend can optimize.

2. Type specialization becomes practical. The interpreter can branch on runtime tags. Native code wants more explicit value categories so it can choose direct arithmetic and comparison paths.

3. Cranelift already works in SSA terms. Lowering through SSA gives pd-vm a cleaner bridge into the backend rather than asking Cranelift to recover structure from a stack VM directly.

4. Optimization is much easier over SSA. Constant folding, dead-code elimination, and value forwarding are natural in SSA and much less natural over raw stack state.

The Two SSA Pipelines

Trace JIT: SsaTrace

The trace JIT records a hot linear path and lowers that path into SsaTrace. Guards keep the trace honest. If the observed assumptions stop holding, execution exits back to the interpreter.

This is a targeted optimization tier: specialize the hot path without forcing the whole program through a full ahead-of-time pipeline.

Whole-program AOT: AotSsaProgram

The AOT path lowers the entire program into control-flow blocks, then into typed SSA form. That creates a native artifact that can run without interpreter participation on the hot path while still preserving VM-aware resume points for host calls and yields.

The important difference from trace JIT is scope. AOT is whole-program and planned; trace JIT is local and profile-driven.

Why Cranelift?

Cranelift is a pragmatic backend choice for this project.

1. Compile speed matters. pd-vm needs a backend that is compatible with fast trace compilation and practical ahead-of-time generation.

2. The Rust integration is straightforward. Cranelift fits a Rust-native codebase without pulling in a large C++ toolchain or a custom backend effort.

3. The optimization level is good enough for the workload. Proxy scripts spend much of their time in control flow, string handling, and host interaction. For that profile, fast compile time and operational simplicity matter more than chasing the most aggressive optimizer possible.

Where This Falls Short

Summary

SSA is not the baseline runtime format in pd-vm. It is the optimization bridge from compact, suspendable bytecode to native code. The interpreter stays small and cheap; the JIT and AOT tiers reconstruct the explicit data flow they need only when native execution is worth it.