Programming Language Theory

JIT Compilation

JavaScript running in a browser often beats Python. That feels paradoxical: a dynamic language with type coercion outpacing 'just another language'. The secret: V8 Turbofan JIT with speculative optimizations acts like a self-tuning compiler, specializing on the actual types and patterns of the code it runs.

  • **LuaJIT in OpenResty**: Nginx + LuaJIT handles 1M req/sec on a single server. Cloudflare uses this for edge computing. LuaJIT tracing JIT often beats C on numeric workloads.
  • **V8 in Node.js**: Node 18+ adds the Maglev JIT (a new baseline compiler). The TypeScript compiler is written in TypeScript and runs on V8's JIT, compiling 100K lines of TS in 3 seconds.
  • **GraalVM**: one JIT for Java, JavaScript, Python, Ruby, R. Polyglot JIT: Python code calling Java libraries compiles together without cross-language call overhead.

Tracing JIT

A tracing JIT records a concrete execution path (a trace) through a hot loop and compiles just that path into native code. LuaJIT uses a tracing JIT and is one of the fastest dynamic languages, often beating Python by 100x. A trace includes inlined calls, type specialization, and elimination of dynamic dispatch.

LuaJIT often outpaces C for numeric work thanks to automatic vectorization of the trace. Nginx uses LuaJIT to handle HTTP requests in nginx-module-lua. Cloudflare Workers use V8 (a method JIT) for isolation. Different approaches for different use cases.

What is a 'side exit' in a tracing JIT?

Method JIT

A method JIT (HotSpot JVM, V8 Turbofan) compiles entire methods rather than traces. Tiers: interpreter -> baseline JIT (fast compilation, no optimizations) -> optimizing JIT (slow compilation, aggressive optimizations). HotSpot ships two: C1 (client) and C2 (server).

Why does V8 use multiple compilation tiers instead of a single optimizing compiler?

Deoptimization

Deoptimization is the rollback from optimized code to the interpreter when a speculation breaks. In V8: if a function was optimized for Number + Number, the first call with a string triggers a deoptimization. Frequent deopt patterns are a major performance pitfall.

Why can `delete object.property` trigger deoptimization in V8?

Speculative Optimizations

Speculative optimizations compile code with assumptions that may be false. Inline caches (ICs) cache the type of the object the dispatch was performed on. Polymorphic IC: several types. Megamorphic IC: too many types, so optimization is turned off.

JIT compilation always makes code faster than interpretation

JIT has warmup time. A function called once is slower under a JIT than in an AOT-compiled language. JIT wins only on hot execution paths.

Serverless functions are a concrete example. Every cold start spawns a new JVM or V8 without a warmed-up JIT. AWS Lambda with Java 17: first request 500 ms, next requests 5 ms. GraalVM Native Image or AOT compilation solves this at the cost of losing some JIT optimizations.

What happens when an Inline Cache becomes megamorphic?

Summary

  • **Tracing JIT**: records a hot path (LuaJIT). Specializes on a concrete trace. Side exit on guard failure.
  • **Method JIT**: compiles an entire method (V8, HotSpot). Tiered: interpreter -> baseline -> optimizing. Feedback from the profiler.
  • **Deoptimization**: speculation broken means falling back to the interpreter. delete, type changes, megamorphic ICs are triggers.
  • **Inline Cache**: monomorphic (1 type) -> polymorphic (2-4) -> megamorphic (5+). Megamorphic equals generic lookup, no optimization.

Related topics

JIT combines compiler techniques with runtime profiling:

  • IR and optimizations — JIT applies the same optimizations as an AOT compiler (inlining, DCE, constant folding), but driven by runtime data.
  • WebAssembly — WASM was created to sidestep JIT unpredictability: predictable performance without warmup and deoptimization.

Вопросы для размышления

  • JIT specializes code on concrete types. But TypeScript is statically typed. Why does V8 still need a JIT instead of compiling straight to native code?
  • GraalVM polyglot JIT: Python calls Java without overhead. How is that possible when Python and Java have completely different object models and memory layouts?
  • The serverless cold start problem: JVM cold start is 300-500 ms. GraalVM Native Image: 10 ms start but no JIT optimizations. How do you choose a runtime for different workloads?

Связанные уроки

  • arch-06-pipelining
JIT Compilation

0

1

Sign In