JetBrains/kotlin
Backend IR
Active contributors: Ivan Kylchik, Sergej Jaskiewicz, Wojciech Litewka
compiler/ir/ holds the backend Intermediate Representation: the platform-agnostic tree that every code-generation backend (JVM, JS, Wasm, Native) lowers and emits. Both frontends produce IR — K1 via compiler/ir/ir.psi2ir/ and K2 via compiler/fir/fir2ir/ — so once a program is in IR, the front-end split is no longer visible.
Purpose
Provide a single representation for the compiler's middle and back ends. IR is rich enough to express every Kotlin construct, target-friendly enough to lower into JVM bytecode / JavaScript / Wasm / LLVM IR, and serializable so that multi-module Kotlin projects (Multiplatform, Native, JS, Wasm) can ship klibs.
Directory layout
compiler/ir/
├── ir.tree/ Tree types: IrFile, IrClass, IrFunction, IrCall, ...
├── ir.psi2ir/ K1 (PSI + descriptors) → IR
├── ir.actualization/ expect/actual matching for IR
├── ir.inline/ Inline-function expansion
├── ir.interpreter/ Compile-time evaluation (intrinsics, const eval)
├── ir.objcinterop/ Kotlin/Native Objective-C interop helpers
├── ir.validation/ IR consistency checks (verifier)
├── backend.common/ Shared lowering passes
├── backend.jvm/ JVM-specific lowerings + codegen
├── backend.js/ JS-specific lowerings (output via js/js.ast)
├── backend.wasm/ Wasm-specific lowerings (output via wasm/wasm.ir)
├── backend.native/ Common Native-side IR helpers
├── serialization.common/ Klib IR serialization (target-agnostic)
├── serialization.js/ JS-specific klib serialization
├── serialization.jvm/ JVM-specific klib serialization
├── serialization.native/ Native klib serialization
└── serialization.jklib/ "JKLib" experimental klib variantKey abstractions
| Type | Where | Role |
|---|---|---|
IrElement |
compiler/ir/ir.tree/.../IrElement.kt |
Root of the IR class hierarchy. |
IrFile |
compiler/ir/ir.tree/.../IrFile.kt |
An IR translation of a single source file. |
IrClass, IrFunction, IrProperty, IrField |
compiler/ir/ir.tree/... |
Declaration nodes. |
IrCall, IrConstructorCall, IrGetField, IrSetField |
compiler/ir/ir.tree/... |
Expression nodes. |
IrType |
compiler/ir/ir.tree/.../IrType.kt |
The IR-level type. |
IrSymbol |
compiler/ir/ir.tree/.../symbols |
A reference to a declaration; backed by descriptor (K1) or FIR symbol (K2). |
IrVisitor, IrTransformer |
compiler/ir/ir.tree/.../IrVisitor.kt |
The standard visitor/transform pattern over IR. |
IrLowering |
compiler/ir/backend.common/... |
Common interface for a lowering pass. |
JvmIrIntrinsicGenerator, JsIntrinsics, WasmIntrinsics, ... |
per backend | Backend-specific built-ins / intrinsics tables. |
The IR tree definitions are partially generated — see compiler/ir/ir.tree/tree-generator/. Fields, types, and visitor methods are derived from a structured definition.
How lowering works
A lowering is a pass that takes IR in, emits IR out, and rewrites some construct into a target-friendlier form. Examples:
- Suspend functions → state machines.
- Inline functions → inlined call sites.
- Local classes → top-level classes with captured state.
whenexpressions → cascades ofif/else(or tableswitch on the JVM).- Default arguments → wrapper methods.
Most lowerings live in compiler/ir/backend.common/ and apply to every target. Each backend then layers its own platform-specific lowerings (e.g., compiler/ir/backend.jvm/lower/ for the JVM).
graph LR
Front["FIR / Descriptors"] --> Build["psi2ir / fir2ir"]
Build --> IR["IR tree"]
IR --> CommonLower["backend.common lowerings"]
CommonLower --> JvmLower["backend.jvm lowerings"]
CommonLower --> JsLower["backend.js lowerings"]
CommonLower --> WasmLower["backend.wasm lowerings"]
CommonLower --> NativeLower["backend.native lowerings"]
JvmLower --> JvmEmit["ASM bytecode"]
JsLower --> JsEmit["js.ast → .js"]
WasmLower --> WasmEmit["wasm.ir → .wasm"]
NativeLower --> NativeEmit["LLVM IR → binary"]klibs (Kotlin libraries)
Multiplatform / Native / JS / Wasm code is shipped as klibs — a portable archive of serialized IR plus metadata. The serialization format is shared (compiler/ir/serialization.common/) with target-specific extensions (serialization.js, serialization.native, serialization.jklib).
A klib lets the compiler resolve a multiplatform module without re-running the frontend on the source. It also serves as the unit of incremental linking on Native and JS.
IR validation
compiler/ir/ir.validation/ provides a verifier that checks invariants like "every IrSymbol resolves", "every IrType is well-formed", "no orphan owners". Many tests run the verifier after each lowering to catch lowering bugs early.
Inline-function machinery
compiler/ir/ir.inline/ houses the inline-function expander. Inlining is global to all backends and runs before backend-specific lowerings. Capturing inline lambdas, default value handling, and reified type parameters all live here.
Compile-time evaluation
compiler/ir/ir.interpreter/ provides limited compile-time evaluation — used for const val initialization, default argument evaluation in some cases, and intrinsics. Each backend can opt into using it for additional folding.
Integration points
- Producers:
compiler/ir/ir.psi2ir/andcompiler/fir/fir2ir/. - Consumers: every backend (
backend.jvm,backend.js,backend.wasm,backend.native). - klibs: written by
serialization.*, read back by the deserialization paths in the frontend (e.g.,compiler/fir/fir-deserialization/).
Where to start
When adding a new lowering, find the closest existing one in compiler/ir/backend.common/ (cross-target) or in the relevant backend's lower/ subdirectory. Most lowerings are pattern-matching IrTransformers.
When debugging IR shape, run a irText test (the corresponding .ir.txt files are checked into testData/) — they show the IR before/after a chosen phase.
See per-backend pages:
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.