What do the compiler phases mean?

← Debugging and Troubleshooting · Ref: Q253

EK9 uses a 22-phase compilation pipeline. Each phase performs a specific task and builds on the results of previous phases. Understanding the phases helps you interpret error messages and know when different types of errors are detected.

FRONTEND PHASES (0-9)

These phases read, parse, and validate your source code:

Phase 0 - READING: Reads source files from disk.
Phase 1 - PARSING: Parses EK9 source into an abstract syntax tree (AST) using the ANTLR4 grammar. Syntax errors (missing colons, bad indentation) are caught here.
Phase 2 - SYMBOL_DEFINITION: Creates the symbol table with all defined types, classes, functions, and variables.
Phase 3 - DUPLICATION_CHECK: Detects duplicate type names, duplicate methods, and duplicate properties within the same scope.
Phase 4 - REFERENCE_CHECKS: Validates that all referenced symbols (types, variables, functions) exist somewhere in the codebase.
Phase 5 - EXPLICIT_TYPE_SYMBOL_DEFINITION: Second pass to define and resolve non-inferred template types (generic type parameters).
Phase 6 - TYPE_HIERARCHY_CHECKS: Validates inheritance hierarchies, checks for circular inheritance, verifies 'as open' for extension, and validates override correctness.
Phase 7 - FULL_RESOLUTION: Third pass to define and resolve inferred types and template types. Generics are fully resolved here.
Phase 8 - POST_RESOLUTION_CHECKS: Validates all symbols and template types are fully resolved and consistent.
Phase 9 - PRE_IR_CHECKS: Code flow analysis. Detects unset variables (E08020), missing guards (E08030), uninitialised returns (E08050), and validates DI wiring. This is the most important phase for catching logic errors.

MIDDLE PHASES (10-13)

Phase 10 - PLUGIN_RESOLUTION: Resolves external plugin points.
Phase 11 - IR_GENERATION: Generates the intermediate representation (IR) from the validated AST. The IR is the common format for all backends.
Phase 12 - IR_ANALYSIS: Analyses the IR for optimisation opportunities.
Phase 13 - IR_OPTIMISATION: Applies IR-level optimisations (currently stub implementation).

BACKEND PHASES (14-21)

Phase 14 - CODE_GENERATION_PREPARATION: Prepares for target code generation.
Phase 15 - CODE_GENERATION_CONSTANTS: Generates code for constant values.
Phase 16 - CODE_GENERATION_APPLICATIONS: Generates code for application entry points.
Phase 17 - CODE_GENERATION_AGGREGATES: Generates code for classes, records, traits, and other aggregates.
Phase 18 - CODE_OPTIMISATION: Applies target-specific code optimisations.
Phase 19 - PLUGIN_LINKAGE: Links external plugins into the compiled output.
Phase 20 - APPLICATION_PACKAGING: Packages the compiled application.
Phase 21 - PACKAGING_POST_PROCESSING: Final post-processing and cleanup.

WHERE ERRORS ARE CAUGHT

Most developer errors are caught in phases 2-9 (the frontend). These phases run quickly and provide immediate feedback. The LSP runs through phase 9, which is why you get comprehensive error detection in your editor.

PHASE CONTROL

The '-Cp N' flag stops compilation at phase N. This is not something end users typically need, but it is valuable in two scenarios: AI tools learning EK9 can verify that syntax and types compile correctly without generating runnable programs, and compiler developers can isolate specific phases for testing. For example, '-Cp 9' runs all validation without code generation.

WHY MULTI-PHASE?

A multi-phase approach allows each phase to assume that previous phases have validated their concerns. Phase 9 (flow analysis) does not need to check for duplicate names because phase 3 already did that. This makes each phase simpler, more focused, and more maintainable. It also enables the LSP to stop at phase 9 for fast feedback.

See Q246 for debugging strategies. See Q249 for language server integration. See Q252 for verbose compilation modes.

Example

defines module qa.debugging.compilerphases

  defines class

    Shape as abstract
      name <- String()

      Shape()
        -> shapeName as String
        this.name :=: shapeName

      area() as pure abstract
        <- rtn as Float?

      operator $ as pure
        <- rtn as String: `${name}: area=${area()}`

      override operator ? as pure
        <- rtn as Boolean: name?

    Circle extends Shape
      radius <- Float()

      Circle()
        -> r as Float
        super("Circle")
        this.radius :=: r

      override area() as pure
        <- rtn as Float: 3.14159 * radius * radius

      override operator ? as pure
        <- rtn as Boolean: radius?

    Rectangle extends Shape
      width <- Float()
      height <- Float()

      Rectangle()
        ->
          w as Float
          h as Float
        super("Rectangle")
        this.width :=: w
        this.height :=: h

      override area() as pure
        <- rtn as Float: width * height

      override operator ? as pure
        <- rtn as Boolean: width? and height?

  defines function

    describeShape() as pure
      -> shape as Shape
      <- rtn as String: `${shape}`

  defines program

    CompilerPhasesDemo()
      stdout <- Stdout()

      // Class hierarchy (validated in phase 6)
      circle <- Circle(5.0)
      rect <- Rectangle(3.0, 4.0)

      // Method dispatch (resolved in phase 7)
      stdout.println(describeShape(circle))
      stdout.println(describeShape(rect))

      // Flow analysis (checked in phase 9)
      shapes <- List() of Shape
      shapes += circle
      shapes += rect

      for shape in shapes
        stdout.println(`Shape: ${shape}`)

      // Guard pattern (flow analysis phase 9)
      parsed <- Float("2.5")
      if parsed?
        result <- parsed * parsed
        stdout.println(`Squared: ${result}`)

      // Try/catch (phase 9 validates exception flow)
      try
        stdout.println("Compilation phases demo complete")
      catch
        -> ex as Exception
        stdout.println(`Error: ${ex.reason()}`)

Common mistakes

E50060 — The method 'describe()' does not exist on Circle. Use the standalone function 'describeShape()' which accepts a Shape parameter. See ek9 -h E50060 for details.

Incorrect:

stdout.println(circle.describe())

Correct:

stdout.println(describeShape(circle))
Other ways to ask this
  • How many compilation phases does EK9 have?
  • What happens during EK9 compilation?
  • Why does EK9 use a multi-phase compiler?

Coming from another language?

Java: javac is essentially single-pass with some deferred resolution, no phase control. Python: parsing then bytecode compilation, no intermediate phases exposed. Rust: multi-phase (parsing, name resolution, type checking, borrow checking, MIR, LLVM IR), some phase control with -Z flags. Go: fast single-pass compilation, no phase control. C++: preprocessing, compilation, assembly, linking as separate tools. Kotlin: multi-phase similar to Java, no phase control exposed. EK9: explicit 22-phase pipeline, phase-level control with -Cp flag, LSP stops at phase 9 for fast feedback, clear separation of concerns across phases.

Keywords: compiler, troubleshoot, generation, backend, compile, compilation, analysis, symbol, debug, resolution, multi-phase, frontend, pipeline, flow, parsing, error-message, ir, phase