TornadoVM 8.0: Meet TC, TornadoVM's Own Compiler TornadoVM v8.0.0 replaces its vendored Graal compiler with TC, a new pure-Java compiler that reads class files directly and emits CUDA C++, OpenCL C 1.2 or Metal Shading Language, the project announced. The change, merged in PR #1228 across 1,218 files (+40,166 / −119,699 lines), removes Graal, JVMCI and the vendored jdk.internal.vm.ci, letting TornadoVM builds move past the previous --release 22 pin and support JDK 21 through 27, Project Leyden AOT caches and GraalVM Native Image. TornadoVM reports TC passes more of tornado-test than Graal on CUDA and OpenCL, and that jitLLM produces token-identical output at 1.06–1.14× Graal's throughput on CUDA, while existing TaskGraph, TornadoExecutionPlan, KernelContext, @Parallel and @Reduce code runs unchanged. TornadoVM v8.0.0 compiles your kernels with TC, TornadoVM's own compiler. Since the beginning, TornadoVM has turned Java bytecode into GPU code through a vendored, relocated copy of the Graal compiler running on JVMCI. As of this release that compiler is gone. TC reads the class files itself, optimises them in its own SSA IR, and prints CUDA C++, OpenCL C or Metal Shading Language for the device driver to build. Your code doesn't change. TaskGraph , TornadoExecutionPlan , KernelContext , @Parallel and @Reduce work as before, and the kernel ABI, the drivers and the profiler keys stay the same. What changes is everything underneath. At a glance: - No Graal, no JVMCI. PR 1228 https://github.com/beehive-lab/TornadoVM/pull/1228 touches 1,218 files for +40,166 / −119,699 lines. The relocated Graal module, the vendored jdk.internal.vm.ci and the three backends' Graal packages are all deleted. - Test parity or better on every backend. TC passes more of tornado-test than Graal did on CUDA and OpenCL, and every test that passed before still passes. - Same or faster kernels. jitLLM produces token-identical output and runs 1.06–1.14× Graal's throughput on CUDA. - Fast start-up. With JVMCI gone, TornadoVM applications can use a Project Leyden AOT cache or be built into a GraalVM Native Image. - Readable kernels and real diagnostics. Generated code keeps your Java variable names and line numbers. A kernel TC can't compile gets an error code that points at the Java line. 1. Why replace Graal Graal served TornadoVM well for years, but it was built as a CPU JIT for HotSpot, and keeping a frozen copy of it inside a GPU compiler had become the main thing holding the project back: - A bytecode ceiling. The vendored Graal class reader can't read newer class files, so TornadoVM builds were pinned to --release 22 . TornadoVM couldn't follow the JDK. - JVMCI. Graal needs JVMCI and a long list of jdk.internal.vm.ci exports. That made every JDK upgrade awkward and ruled out Leyden AOT caches and GraalVM Native Image. - Size. 644 TornadoVM files imported tornado.graal. , and each of the three backends carried around 160–220 Graal-based classes nodes, LIR and phases . Graal's sea-of-nodes pipeline was designed to emit machine code on a CPU, not source kernels for a GPU driver. - Debuggability. There was no textual IR you could read, diff or write a test against. The replacement was proposed in issue 1227 https://github.com/beehive-lab/TornadoVM/issues/1227 with ten requirements. The main ones were functional parity on every backend, JDK 21 through 27, Leyden and Native Image compatibility, no JVMCI or new third-party dependencies, and kernel performance at least equal to Graal's. The full design, the migration record and all measurements are public in the TC compiler specification https://github.com/mikepapadim/tornado-compiler-spec . 2. What TC is TC is pure Java JDK 21+ and lives in tornado-compiler/ . It doesn't use JVMCI, Graal, or bindings to LLVM, MLIR or NVVM, and it needs neither a GPU nor the TornadoVM runtime to compile. A kernel goes through six stages: class files ─► frontend ─► TC-SSA ─► passes ─► Tornado lowering ─► structuring ─► C core + dialect bytecode Braun SSA block canonicalise, @Parallel, KernelContext, if / while / ─► CUDA C++ NVRTC arguments GVN, LICM, @Reduce, atomics, break ─► OpenCL C 1.2 DCE, unroll kernel ABI ─► Metal MSL - Frontend. TC parses class files itself no reflection or class loading and builds SSA directly from the operand stack and locals. Every value keeps its Java name, source line and bytecode index. - IR. TC-SSA is a typed SSA form that uses block arguments instead of phis, MLIR-style. It spells out Java semantics explicitly: masked shifts, saturating float-to-int conversion, NaN-aware min / max . A verifier checks the IR after every pass in tests, and a printer and parser make it round-trippable text. - Passes. Canonicalisation and constant folding, CFG simplification, dead-code elimination, small-loop unrolling, GVN, LICM and math peepholes. - Tornado lowering. Inlining, and specialisation on what the runtime knows about the actual arguments array lengths, scalar values, object layouts . @Parallel loops become grid-stride loops, and @Reduce becomes work-group reductions. - Structuring. Because the output is source code for a driver compiler, TC rebuilds if / while / break itself instead of emitting a flat control-flow graph full of goto s. - Emission. One C-family printer and thin per-target dialects. Adding a target means adding a dialect types, address spaces, intrinsic spellings plus a description of what the device can do. The passes and lowering are shared. 3. Kernels you can read Here is a grid-stride vector addition with KernelContext : public static void add KernelContext ctx, FloatArray a, FloatArray b, FloatArray c { for int i = ctx.globalIdx; i < a.getSize ; i += ctx.globalGroupSizeX { c.set i, a.get i + b.get i ; } } TC turns it into this CUDA: extern "C" global void C3Kernels add long long kernel context, unsigned char constant region, unsigned char local region, int atomics, unsigned char a, unsigned char b, unsigned char c { int a len, gsize x, i; float a data, b data, c data; i = int blockIdx.x blockDim.x + threadIdx.x ; // C3Kernels.java:34 a len = int long long a ; // C3Kernels.java:34 gsize x = int gridDim.x blockDim.x ; // C3Kernels.java:34 a data = float a + 16LL ; // C3Kernels.java:35 b data = float b + 16LL ; // C3Kernels.java:35 c data = float c + 16LL ; // C3Kernels.java:35 while i < a len { c data i = a data i + b data i ; // C3Kernels.java:35 i = i + gsize x; // C3Kernels.java:34 } } With TC, the loop is a while , the variables are named a , i and a len , and every line points back to the Java line it came from. When a profiler such as Nsight Compute points at a hot line of generated CUDA, the comment on that line leads straight back to your Java. The examples in the specification https://github.com/mikepapadim/tornado-compiler-spec/blob/main/compiler-replacement/examples.md follow this kernel and others through every stage: bytecode, TC-SSA before and after ABI lowering, the structured IR, and the final CUDA beside Graal's. When TC can't compile a kernel, it doesn't fall over inside a compiler phase. You get a stable code TC-E1xxx frontend, E2xxx IR, E3xxx Tornado lowering, E4xxx target, E9xxx internal , the Java line and bytecode index, and a hint. The runtime maps the code's category to the matching TornadoVM exception. 4. Parity and performance The bar for removing Graal was simple: TC must pass every tornado-test that Graal passes, with no fallback to Graal. It does, and on CUDA and OpenCL it passes more. The table shows the number of tornado-test unit tests that pass on each device: | Device | Tests passed, Graal | Tests passed, TC v8.0.0 | |---|---|---| | CUDA, RTX 4090 | 1516 | 1539 | | OpenCL, RTX 4090 | 1027 | 1057 | | OpenCL, Intel UHD 770 no fp64 | 932 | 934 | | Metal, Apple M3 Pro | 942 | 941