Compilers
GraalVM and Truffle
What if the JIT compiler were written in Java and compiled itself? Graal works exactly that way. And what if a Python interpreter written in Java with a handful of annotations automatically got JIT compilation through partial evaluation, with no separate compiler per language? That is Truffle. GraalVM is not just 'another JVM'. It is a platform where 'language' equals 'interpreter plus annotations', and the compiler comes for free.
- **Quarkus + Native Image** on Red Hat OpenShift: microservices start in 12ms and consume 35MB of RAM. That makes Java competitive with Go for serverless workloads
- **Oracle Database MLE**: JavaScript functions can be called right inside SQL through GraalVM Polyglot. No round trip to an external service
- **TruffleRuby** in production at Shopify (2022): selected Ruby on Rails endpoints were ported to TruffleRuby with 30-40% latency reduction on numeric operations
Partial evaluation in Graal
The Graal compiler is a JIT written in Java that runs inside the JVM. Instead of C++ (like HotSpot C2), Graal uses Java with annotations. The key Truffle idea is partial evaluation (PE) applied to a language interpreter. If an AST interpreter is written with annotations like `@ExplodeLoop` and `@Specialization`, PE unfolds it into specialized native code for a specific program.
PE through Graal delivers real results. TruffleRuby, GraalPython, FastR (R), and Sulong (LLVM IR) reach 80-95% of Java's peak throughput for numeric tasks. It is the first system where a new language gets a JIT compiler just by writing an interpreter with Truffle annotations.
What does Graal Partial Evaluation do with a Truffle interpreter?
Self-optimizing AST interpreters
Self-optimizing AST is a Truffle technique where AST nodes rewrite themselves during execution. The tree starts with a generic node (UninitializedAddNode). On the first call with two ints, that node replaces itself with an IntAddNode. When doubles appear, it replaces itself again. The replacement is in place and does not require recompiling the whole AST.
TruffleRuby (Oracle) overtook JRuby in 2023 on most numeric benchmarks and reached 2-3x speedups over MRI Ruby. FastR beats GNU R by 20-30x for matrix operations. All of this comes from PE plus self-optimizing AST, without writing a specialized compiler per language.
What is the key difference between a self-optimizing AST and a traditional interpreter?
Polyglot runtime
The GraalVM Polyglot API lets you call code in different languages in one process without serialization. Java calls a Python function. Python uses a JavaScript object. JavaScript invokes R for statistics. Everything lives in shared memory. Each language is implemented as a Truffle language and shares a single JVM heap.
Oracle uses GraalVM in production for Oracle Database Multilingual Engine (MLE). SQL queries can call JavaScript functions right inside the database. Cloudflare Workers considered GraalVM as an alternative to V8 for edge computing. The main limitation is JVM startup time around 100ms, which is incompatible with cold-start requirements for serverless.
How does the GraalVM Polyglot API transfer data between Python and JavaScript code?
GraalVM Native Image
Native Image is the AOT (Ahead-of-Time) compiler in GraalVM. It turns a Java application into a native binary without the JVM. Closed-world analysis means every class is known before compilation, and reflection is constrained by configuration. The result is startup around 10ms instead of 500ms on the JVM, and RSS around 50MB instead of 300MB.
Quarkus (Red Hat) and Micronaut were built for Native Image from day one. Dependency injection is resolved at compile time instead of runtime. AWS Lambda with Quarkus Native shows cold start under 50ms against 2-5 seconds for Spring Boot on the JVM. In 2024 GraalVM Community Edition was included in the GDK (Graal Development Kit) and is available in Amazon Corretto.
GraalVM Native Image is always faster than the JVM version of the same application
Native Image starts faster and uses less memory, but peak throughput is often lower because JIT optimizations are unavailable
A JIT adapts to actual load and applies speculative optimizations. AOT code is generated without a runtime profile. For long-running servers the JVM version after warmup is usually faster. For serverless and CLI tools, Native Image wins on startup
What is the 'closed-world assumption' in the context of Native Image?
Key ideas
- The Graal compiler is written in Java and uses partial evaluation. A Truffle interpreter specializes for a specific program and emits native code without writing a separate compiler per language
- Self-optimizing AST: nodes rewrite themselves during execution to match the types actually observed. That is the foundation for TruffleRuby, GraalPython, and FastR
- Native Image compiles Java AOT to a native binary. Startup is 10-50ms instead of 500ms on the JVM, but JIT optimizations are unavailable at runtime
Related topics
GraalVM brings together several compiler concepts:
- JIT basics — Graal replaces C2 (HotSpot) as the JIT backend. It uses the same principles but is written in Java
- Speculative optimizations — Partial Escape Analysis in Graal is an advanced speculative optimization for allocations
- LLVM — Sulong (Truffle LLVM) runs LLVM IR (C/C++/Rust) through Truffle as a guest language
Вопросы для размышления
- Graal is written in Java and compiles Java. Is that a bootstrapping problem? How does GraalVM avoid depending on itself?
- Native Image's closed-world assumption conflicts with popular Java frameworks (Spring, Hibernate) that rely on reflection. How have those frameworks adapted?
- TruffleRuby reaches 80% of Java's speed on numeric code. Why is Ruby still slower than Java even with Truffle?