- name
- academic-compilers-language-processors
- description
- Specializes in Compiler Engineering, Language Processors, and Code Optimization building on Compilers Principles, Techniques, and Tools (Dragon Book - Aho, Lam, Sethi, Ullman) and Engineering a Compiler (Cooper, Torczon). Covers Lexical Analysis (Flex/Lex, DFAs, Thompson/Subset Construction), Syntax Analysis (Bison/Yacc, Context-Free Grammars, LL(k), LR(0), SLR(1), LR(1), and LALR(1) Parsers), Semantic Analysis (Symbol Tables with Lexical Scoping, Type Checking, and Hindley-Milner Type Inference), Intermediate Representation (Three-Address Code, Static Single Assignment - SSA with the Cytron Algorithm, Dominance Frontiers, and Phi-nodes), Code Optimization (Loop Invariant Code Motion LICM, Common Subexpression Elimination CSE, Dead Code Elimination DCE, Constant Folding/Propagation), Graph-Coloring Register Allocation (Chaitin-Briggs), and LLVM IR Infrastructure.
# Compiler Engineering and Language Processors (Dragon Book)
This skill establishes the complete architecture of pipelines that translate high-level source code into intermediate representations and optimized machine code, covering the lexical/syntactic front end, semantic analysis, the SSA middle end, and the back end for code generation and register allocation.
---
## 🔄 1. Complete Compilation Pipeline
```mermaid
flowchart LR
A["Source Code"] --> B["Lexical Analysis (Scanner / Flex)"]
B -->|Tokens| C["Syntax Analysis (Parser / Bison)"]
C -->|AST| D["Semantic Analysis (Type Checker)"]
D -->|Decorated AST| E["Intermediate Code Generation"]
E -->|LLVM IR / SSA| F["Code Optimizer (LLVM Opt Passes)"]
F -->|Optimized IR| G["Register Allocation & Code Generation"]
G --> H["Object Code / Native Assembly (x86_64 / ARM / RISC-V)"]
```
---
## 🔡 2. Lexical Analysis: From Regular Expressions to Finite Automata
### 2.1 Thompson Construction (Regex $\to$ NFA-$\varepsilon$)
Converts regular operators ($a|b$, $ab$, $a^*$) into non-deterministic finite automata with $\varepsilon$-transitions linear in the size of the expression.
### 2.2 Subset Construction Algorithm (NFA $\to$ DFA)
- **$\varepsilon$-closure ($\varepsilon\text{-closure}(S)$)**: The set of states reachable from $S$ by empty $\varepsilon$-transitions only.
- **Transition $\delta_{DFA}(T, a) = \varepsilon\text{-closure}(\text{move}(T, a))$**: Maps DFA states to disjoint sets of NFA states.
- **DFA Minimization (Hopcroft's Algorithm)**: Partitions states into distinguishable equivalences with complexity $\mathcal{O}(k |S| \log |S|)$.
---
## 🌲 3. Syntax Analysis: Formal Grammars and Top-Down / Bottom-Up Parsers
### 3.1 FIRST and FOLLOW Sets
Given a grammar $G = (V, \Sigma, R, S)$:
- $\text{FIRST}(\alpha)$: The set of terminals that begin strings derived from $\alpha$.
- $\text{FOLLOW}(A)$: The set of terminals that can appear immediately to the right of the variable $A$ in some sentential form derived from the root $S$.
### 3.2 LL(1) vs LR(1) / LALR(1) Parsing Table
| Parser Family | Direction and Derivation | Common Conflicts | Expressive Power |
| :--- | :--- | :--- | :--- |
| **LL(1) (Top-Down)** | Left-to-right, Leftmost Derivation | FIRST/FIRST and FIRST/FOLLOW conflicts (requires left factoring and elimination of left recursion). | Lowest (does not handle direct left recursion). |
| **LR(0) / SLR(1)** | Left-to-right, Reverse Rightmost Derivation (*Shift-Reduce*) | Shift-Reduce and Reduce-Reduce conflicts when FOLLOW does not discriminate the state. | Intermediate. |
| **LR(1)** | Shift-Reduce with canonical 1-symbol lookahead | State explosion in the canonical table. | Maximum for deterministic context-free grammars. |
| **LALR(1) (Bison/Yacc)** | Merges LR(1) states with the same *core* | May introduce Reduce-Reduce conflicts, but preserves SLR(1) compactness. | Industry standard for production compilers. |
---
## 🏷️ 4. Semantic Analysis and Type Checking
- **Symbol Tables in a Stack of Lexical Scopes**: Support for shadowing, closures, and identifier resolution by block lifetime (`Scope::enter()`, `Scope::exit()`).
- **Type System and Hindley-Milner Inference (Algorithm W)**: Type unification via substitutions of type variables with cycle detection (*occurs check*).
---
## ⚡ 5. Middle-End: Static Single Assignment (SSA Form) and Cytron
In SSA form, each variable is assigned exactly once, and $\phi$ functions (*phi-nodes*) are inserted at dominance frontiers:
### 5.1 Dominance Tree and Dominance Frontier ($DF$)
- A node $d$ dominates $n$ ($d \, \text{dom} \, n$) if every path from the entry node to $n$ passes through $d$.
- The dominance frontier $DF(X)$ contains nodes $Y$ where $X$ dominates a predecessor of $Y$, but $X$ does not strictly dominate $Y$.
- **Cytron Algorithm**: $\phi$-nodes for the variable $v$ are inserted at the transitive closure of the dominance frontiers of the blocks containing assignments to $v$:
$$DF^+(Def(v))$$
```llvm
; LLVM IR representation in canonical SSA
define i32 @fatorial(i32 %n) {
entry:
%cmp = icmp sle i32 %n, 1
br i1 %cmp, label %base, label %recurse
base:
br label %exit
recurse:
%n.sub = sub nsw i32 %n, 1
%call = call i32 @fatorial(i32 %n.sub)
%res.rec = mul nsw i32 %n, %call
br label %exit
exit:
%retval = phi i32 [ 1, %base ], [ %res.rec, %recurse ]
ret i32 %retval
}
```
---
## 🚀 6. Code Optimizations and Back-End
### 6.1 Catalog of SSA Optimizations (LLVM Passes)
1. **Mem2Reg**: Promotes stack allocations (`alloca`) to pure SSA registers via dominance analysis.
2. **GVN (Global Value Numbering) & CSE**: Identifies and eliminates equivalent redundant computations through value numbers.
3. **LICM (Loop Invariant Code Motion)**: Moves loop-independent instructions to the pre-header.
4. **DCE (Dead Code Elimination)**: A backward sweep that eliminates instructions whose results are not read by any live instruction.
5. **Function Inlining**: Replaces direct calls with the expanded function body when the heuristic cost outweighs the call overhead.
### 6.2 Graph-Coloring Register Allocation (Chaitin-Briggs)
- **Interference Graph**: Vertices are temporary variables; edges connect temporaries that are simultaneously live (overlapping *live ranges*).
- **Coloring Algorithm with $K$ Registers**:
1. **Simplify**: Remove nodes with degree $< K$ and push them onto a stack.
2. **Spill**: If all nodes have degree $\ge K$, choose a spill candidate based on memory cost / loop nesting.
3. **Select**: Pop the nodes and assign physical registers of a compatible color with no collisions with neighbors.
---
## 🧮 7. Thinking Low-Level: HLL Constructs to Machine Code (Hyde)
- **Thesis:** write high-level code by mentally simulating the assembly it will produce, and verify by reading compiler output — never trust marketing claims about optimizer quality. After any "optimization" edit, regenerate compiler output and diff it; measure the machine code.
- **Compiler pipeline:** lexical scan → parse (AST) → intermediate code → optimization → code generation; interpreters, compilers and JITs move translation cost to different times and open different optimization windows. Optimization is NP-complete, so production optimizers are heuristic and budgeted; JITs must optimize in milliseconds, which is why ahead-of-time languages typically peak higher.
- **Basic blocks + data-flow analysis** let the compiler prove value ranges, constant states, liveness and deadness — ambiguity in the source bounds what the analysis can conclude.
- **Optimization catalog:** constant folding/propagation, dead-code elimination, common-subexpression elimination, strength reduction (shift/AND for multiply/divide/modulo), induction-variable elimination, loop-invariant code motion. Know the optimization flags: most compilers optimize nothing by default; flags trade speed, space and target CPU.
- **Binding time is the real constant question:** language-definition, compile, link, load or run time; manifest constants cost nothing at runtime.
- **Object files and layout:** sections, relocation records, symbol tables; section alignment causes internal fragmentation — a space vs load-efficiency trade-off. Tooling: assembly output, dumpbin/objdump, javap/ILDasm for bytecode, and before/after assembly diffing as the micro-benchmark method.
- **Representation cost model:** declare wider locals before narrower ones to get alignment without padding waste; power-of-two element sizes make scaled-index array access nearly free; string format choice (zero-terminated vs length-prefixed vs descriptor) drives the cost of every string operation; virtual dispatch adds a vtable pointer per object and an indirect call per method.
- **Semantics that are not optimizations:** short-circuit versus complete boolean evaluation changes correctness (null-guard idioms break under complete evaluation) and skips side effects; switch statement structure (dense case ranges) determines whether the compiler can emit a jump table; undefined evaluation order is a correctness trap.
عرض على GitHub