jank now has its own custom IR<br>jank now has its own custom IRMay 08, 2026 · Jeaye Wilkerson<br>Good news, everyone! jank has a new custom intermediate representation (IR) and we're using it to optimize jank to compete with the JVM. We'll dive into more of that today, but first I want to say thank you to my Github sponsors and to Clojurists Together for sponsoring me this whole year. You all are helping a great deal. I am still searching for a way to continue working on jank full-time with an income which will cover rent and groceries, so if you've not yet chipped in a sponsorship, now's a great time!What is an intermediate representation (IR)?<br>Compilers often represent programs as a more abstract set of instructions than a target CPU instruction set can afford. This has a few added benefits. Firstly, the program can be represented in a way which could later be lowered to different CPU architectures, such as x86_64 or arm64. Since intermediate representations are often higher level than CPU architectures, they can generally be more portable. Secondly, IRs can be specifically designed to represent the program in a way which makes writing certain optimizations easier, such as single static assigment (SSA) form. Finally, IR designers get to choose the level of abstraction of the IR to match the semantics they're aiming to represent, which can either make an IR more general or more specific to a certain language.There are many common popular IRs, such as the JVM's bytecode, the CLR's common intermediate language (CIL), GCC's GIMPLE, LLVM's IR, and so on. Some compilers may move the program through multiple IRs during compilation.Custom IR rationale<br>Historically, jank has not been an optimizing compiler. We've delegated basically all of that work to LLVM, based on the C++ or LLVM IR which we would generate. However, LLVM IR works at a very low level, compared to Clojure. It has no concept of Clojure's vars, transients, persistent data structures, lazy sequences, and so on. Clojure's dynamism is granted by a great deal of both polymorphism and indirection, but this means LLVM has very few optimization opportunities when it's dealing with the LLVM IR from jank.The optimization work done previously on jank helped optimize its runtime, and the compiler itself, but less so the code being compiled by the compiler. In the past two months, I have sought to change this.I wanted an IR which operated at the level of Clojure's semantics. This would be much higher level than LLVM IR and even much higher level than JVM's bytecode. Since I'm not building a general virtual machine (VM) or compiler platform, I don't need to generalize the IR for different languages. I can make jank's IR specifically tailored to jank, which gives us even more power for optimizations. As far as I know, no Clojure dialects have taken this step.Custom IR details<br>I have written a reference for jank's IR in the jank book here. This reference is targeted at people who're working on jank itself, since I'm making no promises on the stability of jank's IR at this point. However, I will copy some of that here to illustrate jank's IR and help provide a mental model for what's to come. Let's examine this simple Clojure function.(defn greet [name]<br>(if (= "jeaye" name)<br>(println "Are you me?!")<br>(println (str "Hello, " name "!"))))jank's IR is stored in memory as C++ data structures, but it is renderable to Clojure data for debugging and testing. This is not full serialization, since it cannot round-trip back into the jank compiler from the IR, due to all of the Clang AST internal data we have on hand. Let's take a look at the jank IR module for this function.{:name user_greet_82687<br>:lifted-vars {clojure_core_SLASH_str_82694 clojure.core/str<br>clojure_core_SLASH_println_82691 clojure.core/println<br>clojure_core_SLASH__EQ__82689 clojure.core/=}<br>:lifted-constants {const_82693 "!"<br>const_82692 "Hello, "<br>const_82690 "Are you me?!"<br>const_82688 "jeaye"}<br>:functions [{:name user_greet_82687_1<br>:blocks [{:name entry<br>:instructions [{:name greet :op :parameter :type "jank::runtime::object_ref"}<br>{:name name :op :parameter :type "jank::runtime::object_ref"}<br>{:name v3 :op :literal :value "jeaye" :type "jank::runtime::obj::persistent_string_ref"}<br>{:name v4 :op :var-deref :var clojure_core_SLASH__EQ__82689 :type "jank::runtime::object_ref"}<br>{:name v5 :op :dynamic-call :fn v4 :args [v3 name] :type "jank::runtime::object_ref"}<br>{:name v7 :op :truthy :value v5 :type "bool"}<br>{:name v8 :op :branch :condition v7 :then if0 :else else1 :merge nil :shadow nil :type "void"}]}<br>{:name if0<br>:instructions [{:name v9 :op :literal :value "Are you me?!" :type "jank::runtime::obj::persistent_string_ref"}<br>{:name v10 :op :var-deref :var clojure_core_SLASH_println_82691 :type "jank::runtime::object_ref"}<br>{:name v11 :op :dynamic-call :fn v10 :args [v9] :type "jank::runtime::object_ref"}<br>{:name v12 :op :ret :value v11 :type "jank::runtime::object_ref"}]}<br>{:name else1<br>:instructions [{:name v13 :op :literal :value...