Advanced 26 min readModule: Module 12: Kotlin Compiler Plugins: FIR Architecture & IR Lowerings
Kotlin 2.0 K2 Compiler: FIR Architecture & IR Transformation
Author custom Kotlin compiler extensions: the K2 compiler pipeline (Frontend Intermediate Representation FIR, type checking, and resolution), Backend IR lowerings (`IrElementTransformerVoid`), and generating bytecode metadata at compile-time.
What You Will Learn in This Lesson
- The K2 Compiler architecture: Source Code -> Lexer/PSI -> FIR (Frontend) -> IR (Backend) -> JVM Bytecode / Native Binary
- How Jetpack Compose, Kotlin Serialization, and Arrow Meta use Kotlin Compiler Plugins to rewrite code
- Implementing a custom `IrGenerationExtension` to inject logging and telemetry instructions
- Inspecting FIR symbol tables and generating synthetic declarations
Introduction & Core Concept
With Kotlin 2.0, JetBrains introduced the K2 compiler with a completely redesigned Frontend Intermediate Representation (FIR). FIR brings 2x faster compilation speeds, unified multiplatform semantic analysis, and first-class APIs for compiler plugins. Unlike Java annotation processors that only generate new source files, Kotlin Compiler Plugins can intercept and rewrite existing Abstract Syntax Tree (AST) nodes during compilation.
WHY DOES THIS MATTER IN THE REAL WORLD?
Frameworks like Jetpack Compose (@Composable function memoization), kotlinx.serialization (auto serializer synthesis), and AtomicFU compile down to high-performance bytecode via Kotlin IR compiler plugins.
Syntax & Structure
kotlin
class CustomIrGenerationExtension : IrGenerationExtension { override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { ... }}Building an IR Lowering Compiler Extension to Auto-Log Functions
kotlinkotlin
12345678910111213141516171819202122232425262728293031323334353637383940414243// Kotlin Compiler Plugin: Custom IR Backend Lowering (Conceptual Extension)package com.kwasacademy.compiler.plugin// 1. Compiler Extension Plugin Entry Point// Implements JetBrains org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtensionclass PerformanceLoggingIrExtension : IrGenerationExtension {override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {// Traverse all classes and functions in the compiled module's IR treemoduleFragment.transformChildrenVoid(object : IrElementTransformerVoid() {override fun visitFunction(declaration: IrFunction): IrStatement {// Check if function is annotated with @TrackExecutionTimeval hasAnnotation = declaration.annotations.any {it.type.asString() == "com.kwasacademy.TrackExecutionTime"}if (hasAnnotation) {println("Compiler Plugin: Injecting timing IR bytecode into ${declaration.name}")// Compiler plugin injects System.currentTimeMillis() bytecode entry and exit points!}return super.visitFunction(declaration)}})}}// 2. User Application Code Consuming the Compiler Plugin:annotation class TrackExecutionTimeclass DatabaseRepository {@TrackExecutionTimefun fetchAllStudents(): List<String> {// Compiler plugin wraps body with timing telemetry automatically at compile time!return listOf("Alex", "Jordan", "Taylor")}}fun main() {println("=== Kotlin 2.0 K2 Compiler FIR / IR Architecture ===")val repo = DatabaseRepository()val students = repo.fetchAllStudents()println("Fetched students: ${students.joinToString()}")println("✅ Function executed with compiler-synthesized telemetry bytecode!")}
Line-by-Line Technical Breakdown
1FIR Symbol Resolution: In K2, FIR nodes are separated into FIR declarations (which store unresolved syntax) and FIR symbols (which store semantic types). This separation allows compiler plugins to query type information lazily without triggering whole-AST parsing locks.
Try It Yourself (Interactive Editor)
Modify the code in real-time and click Run to test live browser output and console logs.
Intelligent Code Runner & Live Sandbox[KOTLIN]
KOTLIN SOURCE EDITOR
Interactive Live CodeCommon Mistakes & How to Avoid Them
#1: Relying on KAPT (Kotlin Annotation Processing Tool) in Kotlin 2.0 projects instead of modern KSP2 or IR plugins.
KAPT generates intermediate Java stubs for Kotlin files, significantly degrading compile times. KSP2 operates directly on FIR trees.
Incorrect / Antipattern
apply plugin: 'kotlin-kapt' // Slow Java stub generation slows down builds by 4xCorrect / Professional Solution
apply plugin: 'com.google.devtools.ksp' // KSP2 runs directly on FIR ASTIndustry Best Practices & Professional Standards
- Migrate from KAPT to KSP2 (Kotlin Symbol Processing) for annotation processing.
- Use Kotlin IR plugins for code mutation and AST transformations.
- Test compiler plugins using `kotlin-compile-testing` library.
Lesson Summary & Core Takeaways
- Kotlin 2.0 K2 architecture splits compilation into FIR (Frontend) and IR (Backend).
- IR Lowerings rewrite syntax trees at compile time with zero runtime reflection overhead.
- KSP2 and IR plugins deliver 2x-4x faster build times across multiplatform projects.