QUICK START:HTMLCSSJavaScriptTypeScriptPythonSQLReactNext.jsNode.jsLinux & UbuntuKotlinSwiftC# / .NETJavaGoRustC++DSASystem DesignDevOpsCybersecurityAI / ML
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

kotlin
kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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.IrGenerationExtension
class PerformanceLoggingIrExtension : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
// Traverse all classes and functions in the compiled module's IR tree
moduleFragment.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitFunction(declaration: IrFunction): IrStatement {
// Check if function is annotated with @TrackExecutionTime
val 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 TrackExecutionTime
class DatabaseRepository {
@TrackExecutionTime
fun 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 Code

Common 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 4x
Correct / Professional Solution
apply plugin: 'com.google.devtools.ksp' // KSP2 runs directly on FIR AST

Industry 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.