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