skip to content

What is FIR in the K2 compiler, and what role does it play?

level: middleimportance: should knowfreq 35%

answer

  1. FIR = Frontend Intermediate Representation
  2. One unified tree vs old PSI + binding traces
  3. Resolved in ordered phases
  4. Lowered to IR for the backend
  5. Matters mainly for compiler plugins

basics

~10 s

FIR is the single data structure K2 uses to represent your code while analyzing it. All checking — names, types, smart casts — happens on this one model, which keeps results consistent and fast.

solid answer

~40 s

FIR stands for Frontend Intermediate Representation. It is the unified tree/graph K2's frontend builds from your source and then progressively resolves through phases (raw FIR from the parser, then name resolution, supertype resolution, type resolution, body and type-inference resolution, and checkers that emit diagnostics). Crucially, every analysis phase reads and enriches the *same* FIR, unlike the old compiler which spread information across multiple structures (PSI plus binding traces). This single-source-of-truth design is why K2 gives more consistent **smart casts** and **type inference** and compiles faster. After the frontend finishes, FIR is lowered into the common **IR (Intermediate Representation)** consumed by the shared backends (JVM/JS/Native). For most engineers FIR is invisible; it matters when writing or debugging compiler plugins, which now target the FIR/IR APIs instead of the legacy frontend.

go deeper

for a junior

Knows FIR is K2's internal model of the code used during compilation.

for a middle

States FIR is a unified representation replacing scattered structures, improving consistency and speed.

for a senior

Describes phased resolution and lowering FIR to IR, and distinguishes the two clearly.

for a principal

Connects FIR's single-source design to plugin-API stability, future feature velocity, and tooling (IDE/analysis) reuse.

## Definition **FIR = Frontend Intermediate Representation.** It is the in-memory model of your program that K2's frontend builds and then refines. "Intermediate representation" just means a structured form *between* raw source text and final output that the compiler is easy to analyze. ## The problem FIR solves The old frontend kept facts about your code in more than one place — the **PSI** (the syntax tree from the parser) plus side tables called **binding traces** that recorded resolved types, references, smart-cast info, etc. Keeping those in sync was error-prone, and different phases could disagree, which is partly why smart casts and overload resolution had inconsistent corner cases. FIR is a **single, mutable-by-phase tree** that becomes more resolved as compilation proceeds. One source of truth ⇒ consistency + speed. ## The resolution phases (conceptually) FIR is built once and then transformed through ordered phases. Roughly: - **Raw FIR** — produced directly from parsing; references are unresolved. - **Import / supertype resolution** — figure out what classes extend. - **Type resolution** — resolve explicitly written types. - **Body resolution & type inference** — infer expression types, resolve calls/overloads, compute smart casts. - **Checkers** — run diagnostic rules and emit errors/warnings. Each phase reads the same FIR and writes more resolved nodes into it. ## After the frontend: lowering to IR When the frontend is done, FIR is **lowered** into the common **IR** that the shared backends consume: ``` source → (parse) → raw FIR → (resolve phases) → resolved FIR → (lower) → IR → JVM/JS/Native ``` Note FIR and IR are different things: **FIR** is the *frontend's* representation; **IR** is the *backend's*. ## Why a normal developer cares Usually you never see FIR. It becomes relevant when you **write or debug compiler plugins** (e.g. a code generator) — K2 plugins hook into FIR/IR extension points, so plugins written against the old frontend internals had to be ported. ## One-line code framing ```kotlin // You write ordinary Kotlin... val x: Any = "hi" if (x is String) x.length // smart cast decided on the FIR for this branch ``` The smart cast above is computed during FIR body resolution — the same model the type checker uses, which is why K2's smart-cast behavior is more uniform.

  • How is FIR different from IR?
    FIR is the frontend's representation used for resolution/inference/diagnostics; IR is the backend's representation that FIR is lowered into to generate JVM/JS/Native output.
  • Why did the old design hurt consistency?
    It scattered resolution facts across the PSI plus side tables (binding traces), so phases could disagree; FIR keeps one source of truth.

saying these in an interview costs you the question

  • Confusing FIR with IR or saying they are the same
  • Claiming FIR is a runtime structure rather than a compile-time one
  • Saying FIR is something developers configure in build files
  • Thinking FIR replaces bytecode
  • Asserting K2 still uses binding traces as the source of truth

context