LabHub

Blog

Compilers and Modern Language Runtimes — LLVM, JIT, GC, V8 TurboFan/Maglev, Inline Caching, Escape Analysis, Rust Monomorphization Complete Guide (2025)

한국어English日本語

Why You Should Know Compilers and Runtimes

Reality in 2025:

This post traces "what happens until my code runs."

Part 1 — Compiler vs Interpreter vs JIT — A Spectrum

Classical split

Reality is hybrid

"Is this language compiled or interpreted?" is the wrong question. "What tiers does its runtime have?" is the real one.

Part 2 — LLVM's Dominance

Why LLVM became the standard

Chris Lattner's 2000 PhD project. In 2024:

LLVM IR

define i32 @add(i32 %a, i32 %b) {
  %sum = add i32 %a, %b
  ret i32 %sum
}

Language-neutral IR. Hundreds of optimization passes run on it:

MLIR (2019, Chris Lattner returns)

"Multi-level IR." Not one LLVM IR, but domain-specific IRs at multiple levels.

Why needed: ML frameworks (TensorFlow/PyTorch) manage high-level graphs down to low-level ops across multiple abstractions. LLVM IR alone lacks the expressiveness.

MLIR adoption in 2024:

Part 3 — JIT Masters

V8's four tiers

As of 2024:

Bytecode (Ignition interpreter)
   (hot)
Sparkplug1:1 bytecode-to-machine (non-optimizing, fast codegen)
   (hotter)
Maglev — mid-tier optimizing JIT (2023)
   (very hot)
TurboFan — peak optimization (Sea of Nodes, slow)

Each tier uses type feedback collected below to optimize more aggressively above.

Deoptimization: if assumptions break, fall back. Core mechanism of dynamic-language optimization.

Hidden Class + Inline Caching

JS objects are dynamic; obj.x has no fixed address. V8's answer:

Hidden Class (aka Shape, Map):

Inline Cache (IC):

Lesson: keep JS object shapes stable.

// Bad: conditional property addition
const p = {};
if (cond) p.x = 1;
if (cond2) p.y = 2;

// Good: declare all properties at construction
const p = { x: cond ? 1 : undefined, y: cond2 ? 2 : undefined };

Escape Analysis

"If this object doesn't escape the function → stack-allocate or scalar-replace."

Heap allocation is expensive; GC pressure. Code friendly to escape analysis is much faster.

Tiered Compilation

JVM: C1 (fast JIT) → C2 (aggressive JIT) → (OpenJDK 17+) GraalVM.

LuaJIT: trace-based JIT. Mike Pall's masterpiece. Still cited as the best dynamic-language JIT in the 2020s.

Part 4 — GC Lineage

Mark & Sweep (1960)

Copying GC

Generational GC

Concurrent and Incremental GC

Run GC concurrently or in small chunks to minimize STW.

G1 GC (default in JDK 9+)

ZGC (JDK 11+, Production-Ready in 2023)

Shenandoah (RedHat, JDK 12+)

Go's GC

CPython

V8

Choosing a GC

GCpausethroughputmemory overhead
Parallel (JDK, not default)longhighlow
G1midmidmid
ZGCultra-lowmidmid
Shenandoahultra-lowmidmid
Golowmidlow-mid
CPython RCnear 0low (counters)low

Part 5 — Go Scheduler

G-M-P model

Each P has a local G queue. If empty, work-steals from another P.

Traits

Limitations

Part 6 — Rust and the Power of AOT

Monomorphization

fn max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } }

let x = max(1i32, 2i32);    // generates max::<i32>
let y = max(1.0f64, 2.0);   // generates max::<f64>

Dedicated machine code per type → zero call overhead, optimal inlining. Downside: binary bloat.

Zero-Cost Abstractions

"Code using abstractions is no slower than hand-written equivalent."

Rust's Borrow Checker

Zero-runtime-cost memory safety. Compile-time ownership/lifetime verification.

Rust 2024-2025

Part 7 — Python 3.13's Revolution

Specializing Adaptive Interpreter (PEP 659, 3.11+)

CPython injects shape-specialized bytecode instructions at runtime.

LOAD_ATTRLOAD_ATTR_INSTANCE_VALUE (dict-based object)
LOAD_ATTR_SLOT (__slots__)
LOAD_ATTR_MODULE
...

V8's Inline Cache, brought to CPython.

3.13 additions

PyPy

Part 8 — JavaScript Runtime Landscape

V8 (Chrome, Node.js, Deno)

JavaScriptCore (Safari/WebKit)

SpiderMonkey (Firefox)

Bun picks JSC

Bun uses JSC instead of V8. Part of its beating-Node benchmarks stems from this choice.

Runtime API differences

Part 9 — WebAssembly Runtimes

Covered previously, but from a runtime angle:

RuntimeTraitWhere
V8 + WasmBrowser standardWeb
WasmtimeBytecode AllianceServer WASI
WasmerMultiple backendsEmbedded
WasmEdgeCNCFEdge
Wasmer + CraneliftFast compileDev
Wasmer + LLVMBest codeProd

Part 10 — AOT vs JIT Trade-offs

AOT (Rust, Go, Swift, GraalVM Native Image)

Pros:

Cons:

JIT (V8, JVM C2)

Pros:

Cons:

GraalVM — the bridge

Part 11 — Performance Analysis Workflow

CPU profiling

  1. Flame graph for the big picture.
  2. Identify hotspot functions.
  3. Inspect their assembly (perf annotate or Compiler Explorer).
  4. For JIT output: Node --print-opt-code, V8 logging.

Memory profiling

Tracing

Part 12 — Checklist (12 items)

  1. Latest LTS runtime — V8, JVM, Go, Python all keep improving.
  2. GC tune only after benchmarking — premature optimization is bad.
  3. Keep JS object shape stable — stabilize Hidden Class.
  4. Mind Go escape analysis — check with -gcflags="-m".
  5. Rust: #[inline]/PGO — manual hints often needed.
  6. JVM: JFR always on — continuous prod profiling.
  7. Consider CPython 3.13+ — specialized interpreter gains.
  8. Container CPU awareness — GOMAXPROCS, -XX:ActiveProcessorCount.
  9. Startup-sensitive apps → AOT — Native Image, Go AOT.
  10. Measure JIT warmup — discard initial benchmark numbers.
  11. Minimize allocation hot paths — most perf issues are here.
  12. Compiler Explorer (godbolt.org) — habitual assembly inspection.

Part 13 — 10 Anti-patterns

  1. "This language is fast" — runtime config/structure matters more.
  2. Benchmark with time ./app once — ignoring variance/warmup.
  3. Ignoring JIT warmup — judging from -Xcomp only.
  4. Microbenchmarking with System.nanoTime() instead of JMH — tens of times off.
  5. Generic abuse (monomorphization explosion) — tens of MB binaries.
  6. Tight loops in pure Python — consider NumPy/Cython/Numba.
  7. CPU-bound work on Node main event loop — worker_threads required.
  8. JVM prod without Xms=Xmx — heap-resize cost.
  9. No GC logs — impossible to triage prod incidents.
  10. JVM on container default memory — gets OOM-killed. -XX:+UseContainerSupport is default but verify.

Part 14 — Learning Resources

Closing — Language Is Runtime

"Which language?" is often really "which runtime profile?"

Every runtime is a trade-off. The same language shifts performance profile by config, version, GC choice. An engineer's weapon is concrete runtime knowledge: "I don't break V8's Hidden Class," "I verify Go's escape analysis."

In the LLM-writes-code era, engineers who can explain why code is fast or slow grow more valuable. The answer mostly lives in the compiler and the runtime.

Next — "AI Engineering in Practice" — LLM API architecture, RAG, agents, fine-tuning, vector DBs, evaluation, production ops

After 14 systems posts, the next sits on top of them all: AI applications.

"How to actually ship an AI product." Next post.

Comments

No comments yet.

Sign in to leave a comment