Introduction to Compilers and Programming Languages
What compilation must preserve, from source text to machine instructions
About this course
A compiler end to end: lexing and parsing into an abstract syntax tree, interpreters and operational semantics, scope and type checking, lowering to an intermediate representation, dataflow analysis and SSA, behaviour-preserving optimisation, instruction selection and register allocation, calling conventions, and the evidence that the whole thing is correct.
What you will be able to do
- Turn source text into tokens and an AST, with errors that name a real source location
- State what execution means with operational semantics, scope rules and a type checker
- Lower a program to an IR with explicit control flow, basic blocks and SSA
- Prove an optimisation preserves behaviour, traps included, before applying it
- Select instructions, allocate registers and test the compiler differentially
What this course covers
- Observation contract for optimization
- Straight-line scope and IR boundary assumptions
- Roots of backward analysis: which instructions cannot be removed
- Forward constant propagation: two-map design and invariant
- Constant folding rules including known-overflow preservation
- Mutation and shadowing in forward propagation
- Backward dead-value elimination rules
- Interaction between forward and backward passes
- Algebraic identities do not justify unsafe rewrites
- Static rejection precedes optimization; negative controls validate tests
- Compiler correctness and observable behavior
- Sprig integer semantics and overflow
- Two-phase failure boundary
- Scanning: tokenisation and source spans
- Parsing: syntax trees, precedence and associativity
- Name resolution and scope rules
- Reference interpreter execution model
- Stack machine execution and the expression stack invariant
- Compilation contract, testing strategy and limits
- Machine Configuration and Execution State
- Work-List Evaluation Order and Continuation Frames
- Fuel, Trace Caps, and Suspension vs. Completion
- Environment, Name Resolution, and Declaration Semantics
- Conditional (if-zero) and Selective Evaluation
- Calling Convention and Runtime Distinction
- Language Subset and Observation Contract
- Argument Evaluation Order and Staging
- Activation Records and Frame Layout
- Continuations and Return Mechanics
- Register Convention: Caller-Saved and Callee-Saved Obligations
- Recursive Activation Stack and Unfinished Work
- Failure Semantics: Traps, Depth Limits, and Observation Boundaries
- Compiler Expression Invariant and Target Validation
- Typed Three-Address Intermediate Representation
- Source Language Obligations Carried into the Target
- Fresh Temporaries vs. Mutable Binding Slots
- Lowering: Translating Source Expressions to Instruction Sequences
- Shadowing, Block Cleanup, and Binding Lifetimes
- Checked Arithmetic, Overflow Semantics, and Source Focus Retention
- Simulation Argument, Differential Testing, and Negative Controls
- Sprig F15 language scope and compiler pipeline
- Observation model and equivalence relation
- Driver commands and exit-status contract
- Front end: scanning, parsing, and validation
- Lowering invariant and register calling convention
- Target legality vs. translation correctness
- Differential testing, mutation, and oracle limits
- Failure reduction: predicate, metric, and limits
- Bundle integrity, packaging, and claim boundaries
- Control Flow Graphs and Basic Blocks
- Reachability and Dominance
- Immediate Dominators and the Dominator Tree
- Static Single Assignment Form and Phi Instructions
- Phi Operand Availability and SSA Verification
- Dominance Frontiers and Phi Placement
- Behavioral Preservation and Guarded Overflow
- Scanner obligations: complete match and source provenance
- Cursor trace and skipped-region accounting
- Token record fields and their consumers
- Half-open spans and the EOF marker
- Longest-match identifier recognition and keyword classification
- Integer token recognition and separation from literal validation
- Minus token, comment/whitespace skipping, and scanner termination
- Coordinate units: code points, bytes, and display positions
- Audit strategy: invariants, negative controls, and performance accounting
- Names, Declarations, Bindings, and Values
- Lexical Scope and the Resolver Scope Stack
- Source-Order Visibility and the Initializer Boundary
- Shadowing vs. Duplicate Declaration
- Mutable Bindings, Assignment, and the Mutability Check
- Static Resolution Failures and the Two-Phase Model
- The Resolved Tree, Runtime Store, and Commit Boundary
- Resolver Invariant and Scope-Based Renaming
- Instruction selection problem and target machine contract
- Preservation contract and observable equivalence
- Virtual register machine instruction set
- Copy-before-destructive-arithmetic pattern
- Overflow flag adjacency rule and checked arithmetic template
- Small-immediate range and register fallback
- Selector algorithm: pattern matching, commutativity, and dead-load removal
- Cost model and its limits
- Diagnostic metadata, type preservation, and scope correctness
- Test strategy and scope of the implementation
- Liveness Analysis via Backward Scan
- Register Pressure and Spill Motivation
- Interference Graph Construction
- Graph-Coloring Register Assignment (Simplify and Select)
- Spill Code Rewriting with Scratch Registers
- The Home Invariant and Replay Correctness
- Limitations and Extensions of the Model
- Types vs. Values and the Limits of Static Checking
- Typing Judgments and Operator Signatures
- Building a Derivation Tree Bottom-Up
- Types Belonging to Resolved Declaration Identities
- The Checked Tree as Compiler Output
- Structured Diagnostics and Phase Ordering
- The Type-Soundness Invariant, Runtime Obligations, and Carry-Forward Ideas
- Data flow analysis overview and motivation
- Fact domains, program points, and the 'may' semantics
- Lattice structure and boundary conditions
- Transfer functions: gen/kill and use/def
- Work-list iteration and termination
- Least fixed point and schedule independence
- Liveness analysis and backward information flow
- Distributivity, path feasibility limits, and safe use of results
- Ambiguity in context-free grammars
- Context-free grammar components and derivations
- Concrete parse trees and their validity checks
- Layered grammar encoding precedence and associativity
- Role of parentheses and unary minus in grammar
- Concrete vs. abstract syntax trees and grammar testing
- Separating grammatical structure from semantic validity
- Parser responsibilities and cursor contract
- Recursive descent parsing with grammar levels
- Primary parsing: operands, prefix negation, and parentheses
- Binding power (Pratt) parsing
- Abstract syntax tree design and source location metadata
- Literal range checking and arithmetic boundary handling
- Precise syntax diagnostics and entry-point completeness
Syllabus
From Source to Syntax
- L1 · From Source Code to Observable Behavior
- L2 · Lexical Analysis and Source Locations
- L3 · Grammars Ambiguity and Parse Trees
- L4 · Building a Parser and Abstract Syntax Tree
Meaning: Interpreters, Names and Types
- L5 · Interpreters and Operational Semantics
- L6 · Names Bindings and Lexical Scope
- L7 · Types Type Checking and Useful Diagnostics
Lowering and Analysis
- L8 · Lowering to Intermediate Representation
- L9 · Control Flow Basic Blocks and SSA
- L10 · Dataflow Analysis and Fixed Points
Optimisation and Code Generation
- L11 · Optimizations That Preserve Behavior
- L12 · Instruction Selection and Machine Constraints
- L13 · Liveness and Register Allocation
The Whole Compiler
- L14 · Functions Calling Conventions and Runtime Support
- L15 · Build Test and Audit the Complete Compiler