docs: add libVCT public specification set (000-015)
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
# 005 — Lowering
|
||||
|
||||
- **Status:** Draft
|
||||
- **Normative language:** `MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` are to be
|
||||
interpreted as described in RFC 2119.
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
Lowering translates annotated HIR into VIR. It is the bridge between the frontend's tree and the
|
||||
SSA/CFG form that the optimizer and both backends consume. It turns structured control flow into
|
||||
basic blocks and terminators, turns variables into SSA values or stack slots according to their
|
||||
traits, and records the correspondence between every source node and the VIR entities it produced.
|
||||
|
||||
Lowering is a **pure translation**. It discovers nothing and proves nothing. Every type, value,
|
||||
mutability fact, aliasing property, and call hint it needs is already present as a trait that the
|
||||
HIR optimizer left on the tree (see [004 — HIR Optimizer](004-hir-optimizer.md)). When a required
|
||||
fact is absent or contradicts another, lowering does not guess: it reports an internal compiler
|
||||
error.
|
||||
|
||||
## 2. Scope
|
||||
|
||||
This file specifies the lowering contract, the translation of structured HIR constructs into a
|
||||
control-flow graph, SSA construction, the trait-driven hybrid memory model, the `Phi` instruction
|
||||
shape, the `LoweringMap`, region optimization barriers, the `defer` fallback, the VIR types that
|
||||
lowering emits, and the invariants the output must satisfy.
|
||||
|
||||
It does not specify the HIR node model (see [003 — HIR](003-hir.md)), the trait vocabulary (see
|
||||
[002 — Traits](002-traits.md)), VIR instruction semantics (see [006 — VIR](006-vir.md)), or the
|
||||
optimizer passes (see [007 — VIR Optimizer](007-vir-optimizer.md)).
|
||||
|
||||
## 3. Definitions and model
|
||||
|
||||
| Term | Meaning |
|
||||
|---|---|
|
||||
| **Annotated HIR** | An HIR tree on which every relevant node carries a complete trait set. |
|
||||
| **Epoch-valid trait** | A trait whose recorded epoch equals the node's current epoch. Stale traits are not readable. A trait is invalidated when a rewrite makes it stale (see [002 — Traits](002-traits.md)). |
|
||||
| **Lowering unit** | One HIR function. Lowering runs independently per function. |
|
||||
| **Basic block** | A maximal straight-line sequence of VIR instructions with one entry and one terminator. |
|
||||
| **Sealed block** | A block for which all predecessors are known. A block with a pending back-edge is unsealed. |
|
||||
| **Current-definition map** | Per-block mapping from a source variable to the SSA value most recently written to it on the path into that point. |
|
||||
| **LoweringMap** | The bidirectional correspondence between HIR nodes and the VIR entities produced from them. |
|
||||
|
||||
Lowering consumes HIR and produces VIR. It reads only the trait contract and HIR node identity
|
||||
(see [001 — Architecture](001-architecture.md)). It MUST NOT read HIR structure to recover a fact
|
||||
that the trait model already carries.
|
||||
|
||||
## 4. Normative requirements
|
||||
|
||||
### 4.1 Contract
|
||||
|
||||
- **L-1.** The input MUST be annotated HIR whose traits are complete and epoch-valid.
|
||||
- **L-2.** The output MUST be a VIR module in which every function is well-formed SSA (see
|
||||
[006 — VIR](006-vir.md)).
|
||||
- **L-3.** Lowering MUST NOT perform discovery. It MUST NOT infer types, constant values,
|
||||
mutability, aliasing, or call effects that are not stated by traits.
|
||||
- **L-4.** Lowering MUST validate trait completeness before it translates a function. A required
|
||||
trait that is missing, stale, or self-contradictory MUST raise an internal-compiler-error
|
||||
diagnostic (see [011 — Diagnostics](011-diagnostics.md)). Lowering MUST NOT substitute a default,
|
||||
a guess, or a conservative fallback for a missing trait.
|
||||
- **L-5.** Lowering MUST be a pure function of the annotated HIR and the `Config`. The same input
|
||||
and `Config` MUST produce byte-identical VIR regardless of thread count, per the determinism
|
||||
guarantee in [000 — Overview](000-overview.md).
|
||||
- **L-6.** Lowering MUST be parallelizable per function. Two functions MUST NOT share mutable
|
||||
lowering state. The only cross-function state is read-only, namely the module's trait graph and
|
||||
the `LoweringMap` for already-lowered functions where a relationship endpoint crosses functions.
|
||||
- **L-7.** Emission order within a function MUST be deterministic. Where a construct admits
|
||||
multiple equivalent orderings (for example, the order of predecessor operands in a `Phi`), the
|
||||
order MUST be fixed by a documented rule, not by hash iteration.
|
||||
|
||||
### 4.2 Structured control flow to CFG
|
||||
|
||||
Lowering is a streaming recursive interpreter over the HIR tree. Structured constructs become
|
||||
blocks connected by terminators. Lowering MUST emit blocks and terminators, not a flat instruction
|
||||
stream with implicit jumps.
|
||||
|
||||
| HIR construct | Required VIR emission |
|
||||
|---|---|
|
||||
| Statement sequence, `block` | Instructions appended to the current block in source order. |
|
||||
| `if (c) T else E` | `condbr c, %then, %else`; a `%then` block, an `%else` block, and a `%join` block. Values live past the construct receive a `Phi` in `%join`. |
|
||||
| `while (c) B` | A `%header` block that evaluates `c`, ending in `condbr c, %body, %exit`; a `%body` block; a `%latch` block ending in `br %header`; an `%exit` block. |
|
||||
| `for (init; c; step) B` | An `%init` block; a `%header` block for `c`; a `%body` block; a `%step` block that runs `step` then branches to `%header`; an `%exit` block. |
|
||||
| `switch (v)` | A `switch` terminator whose case targets and default target are the arm entry blocks. |
|
||||
| `break` | `br` to the exit block of the innermost enclosing loop or `switch`. |
|
||||
| `continue` | `br` to the latch or step block of the innermost enclosing loop. |
|
||||
| `return e` | `ret` of the lowered value of `e`; `ret void` when the function returns void. |
|
||||
| End of a void function | `ret void`. |
|
||||
| End of a non-void function without an explicit return | `unreachable`. |
|
||||
|
||||
- **L-8.** Every basic block MUST be terminated by exactly one terminator.
|
||||
- **L-9.** A block that is not reachable by fallthrough MAY remain unreachable from the entry block
|
||||
as long as it is well-formed; unreachable blocks MAY persist until dead-code elimination removes
|
||||
them (see [006 — VIR](006-vir.md), WF-11).
|
||||
- **L-10.** The entry block MUST have no predecessors.
|
||||
|
||||
### 4.3 Expression translation
|
||||
|
||||
Expressions lower to temporaries and instructions.
|
||||
|
||||
- **L-11.** Each HIR expression that produces a runtime value MUST lower to exactly one VIR value:
|
||||
either an existing constant or a newly emitted instruction result. Aggregate-producing
|
||||
expressions MAY lower to a memory location instead.
|
||||
- **L-12.** Operands MUST be lowered before the instruction that consumes them, in source order
|
||||
(left to right for binary operators, receiver before arguments for calls).
|
||||
- **L-13.** A HIR constant that is comptime must lower to a VIR constant operand and MUST NOT
|
||||
produce a runtime instruction.
|
||||
- **L-14.** A call MUST attach the call-site traits (`Inline`, `AlwaysInline`, `NoInline`,
|
||||
`TailCall`, `MustTail`, `NoTail`, `ColdCall`, `LikelyCall`, `UnlikelyCall`, and the memory-effect
|
||||
requests) to the emitted call entity. Lowering MUST NOT decide whether to inline; that decision
|
||||
belongs to the VIR optimizer (see [007 — VIR Optimizer](007-vir-optimizer.md)).
|
||||
- **L-15.** Reading a variable MUST follow the representation chosen in §4.5: a pure SSA read from
|
||||
the current-definition map, or an explicit `load` from its stack slot.
|
||||
|
||||
### 4.4 SSA construction
|
||||
|
||||
SSA MUST be constructed with the sealed-block algorithm of Braun, Buchwald, Hack, Leißa, Mallon,
|
||||
and Zwinkau, "Simple and Efficient Construction of Static Single Assignment Form" (CC 2013). That
|
||||
algorithm is the reference method; the requirements below are the observable properties it
|
||||
guarantees.
|
||||
|
||||
- **L-16.** Construction MUST be a single pass interleaved with the streaming translation. Lowering
|
||||
MUST NOT run a separate dominance-frontier computation followed by iterated phi placement.
|
||||
- **L-17.** Lowering MUST maintain a current-definition map per block. A write records the new
|
||||
value for the variable in the current block. A read consults the current block first.
|
||||
- **L-18.** On a read for which the current block has no definition, lowering MUST:
|
||||
1. if the block is sealed, recurse to its predecessors, place a `Phi` at the nearest join, and
|
||||
return the phi result;
|
||||
2. if the block is not sealed, create an **incomplete phi** placeholder for the variable in that
|
||||
block and return it; the placeholder is resolved when the block is sealed.
|
||||
- **L-19.** Lowering MUST apply trivial-phi elimination: a phi whose operands all resolve to the same
|
||||
value MUST be replaced by that value, and a phi that references only itself MUST be removed. This
|
||||
keeps construction linear in practice.
|
||||
- **L-20.** Sealing a block MUST fill each incomplete phi with one operand per predecessor, each
|
||||
operand obtained by reading the variable in that predecessor, and MUST then recursively seal
|
||||
successors whose predecessors are now all known.
|
||||
- **L-21.** A loop header MUST NOT be sealed until its back-edge has been emitted. Sealing early is
|
||||
the one mistake that makes the algorithm produce unsound phis.
|
||||
- **L-22.** Phi operand order MUST follow a deterministic predecessor order (for example, ascending
|
||||
block identifier), so that output is reproducible.
|
||||
|
||||
### 4.5 Trait-driven hybrid memory model
|
||||
|
||||
Lowering MUST choose each value's representation from its traits alone.
|
||||
|
||||
| Condition | Representation |
|
||||
|---|---|
|
||||
| `ty` is scalar AND NOT `is_addressed` AND NOT `escapes` AND NOT `is_runtime_mutable` | Pure SSA value held in a register. |
|
||||
| `is_addressed` OR `escapes` OR `is_runtime_mutable` OR the type is an aggregate | Stack slot: an `alloca` plus explicit `load`/`store`. |
|
||||
| `is_volatile` or `is_atomic` holds | Forced to memory, never promoted to SSA. |
|
||||
|
||||
- **L-23.** A scalar that is not addressed, does not escape, and is not runtime-mutable MUST be
|
||||
kept as a pure SSA value.
|
||||
- **L-24.** A value that is addressed, escapes, is runtime-mutable, or has aggregate type MUST be
|
||||
given a stack slot with explicit `load`/`store`.
|
||||
- **L-25.** A value carrying `is_volatile` or `is_atomic` MUST be forced to memory and MUST NOT be
|
||||
promoted to SSA at any point, including by later optimizations.
|
||||
- **L-26.** Lowering's choice is not final for values that carry neither `is_volatile` nor
|
||||
`is_atomic`. `mem2reg` and `SROA` MAY promote a memory value back to SSA when traits confirm
|
||||
that the promotion is safe (see [007 — VIR Optimizer](007-vir-optimizer.md)). Such a promotion
|
||||
MUST preserve the program's observable behavior.
|
||||
- **L-27.** `is_runtime_mutable` means the storage is written at runtime. `may_change_at_runtime`
|
||||
means the observable value may differ between reads because of external state. Lowering MUST
|
||||
consult `is_runtime_mutable` for the representation decision and MUST NOT conflate the two.
|
||||
|
||||
### 4.6 Phi
|
||||
|
||||
VIR has an explicit, LLVM-style `Phi` instruction.
|
||||
|
||||
- **L-28.** A `Phi` MUST record one operand per CFG predecessor of its block. Each operand MUST be a
|
||||
`(value, predecessor-block)` pair.
|
||||
- **L-29.** The number of operands MUST equal the number of predecessors, and each named
|
||||
predecessor MUST be an actual predecessor of the phi's block.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
%loop.header:
|
||||
%i = phi i32 [ 0, %entry ], [ %i.next, %loop.latch ]
|
||||
```
|
||||
|
||||
### 4.7 LoweringMap and trait transfer
|
||||
|
||||
- **L-30.** Lowering MUST build a `LoweringMap` from each HIR node to the one or more VIR entities
|
||||
produced from it, and from each VIR entity back to its source HIR node.
|
||||
- **L-31.** Every VIR entity MUST carry its own `Trait` instance, populated from the source HIR
|
||||
node's trait. Attributes and requests MUST be transferred; the VIR optimizer reads them through
|
||||
the trait contract and does not consult HIR.
|
||||
- **L-32.** Every HIR relationship endpoint MUST be rewritten to the corresponding VIR entity via
|
||||
the `LoweringMap`, so that VIR relationship walks terminate in VIR entities without touching HIR.
|
||||
- **L-33.** The `LoweringMap` MUST be total on HIR nodes that produce a VIR entity and MUST be
|
||||
stable for the lifetime of the module. The diagnostics service uses it to resolve VIR locations
|
||||
back to source (see [011 — Diagnostics](011-diagnostics.md)).
|
||||
- **L-34.** After lowering, VIR MUST depend on the trait contract and HIR node identity only. It
|
||||
MUST NOT depend on HIR structure.
|
||||
|
||||
### 4.8 Region optimization barriers
|
||||
|
||||
- **L-35.** A HIR node marked `complex` or carrying the `NoOptimize` request MUST be lowered inside
|
||||
a **region-level** optimization barrier. The barrier covers the node and the region it dominates
|
||||
as defined by the source construct, and is recorded on the VIR entities in that region.
|
||||
- **L-36.** VIR passes MUST NOT rewrite, reorder, delete, or hoist across a region barrier, and
|
||||
MUST treat the region as an opaque, side-effecting operation for alias and memory-effect
|
||||
analyses. Backends MAY emit a compiler fence for it (see [008 — C Backend](008-c-backend.md)).
|
||||
|
||||
### 4.9 Defer fallback
|
||||
|
||||
- **L-37.** When HIR optimization is disabled (`-O0`), HIR does not expand `defer`, and
|
||||
lowering MUST expand each deferred call at every scope-exit path and then erase the `defer`
|
||||
marker. Expansion order MUST follow the reverse order of registration at each exit path.
|
||||
- **L-38.** When HIR optimization is enabled, HIR has already expanded `defer`; lowering MUST NOT
|
||||
expand it a second time. A residual `defer` marker reaching lowering when HIR optimization is on
|
||||
MUST be treated as an internal inconsistency under L-4.
|
||||
|
||||
### 4.10 VIR type system overview
|
||||
|
||||
The `ty` attribute already holds the node's VIR type (see [002 — Traits](002-traits.md)). It
|
||||
is authoritative: lowering MUST read it and MUST NOT recompute a type from HIR structure.
|
||||
|
||||
| VIR type | Meaning |
|
||||
|---|---|
|
||||
| `void` | No value; the result type of effect-only calls and functions. |
|
||||
| `iN` | An `N`-bit integer. `N` MUST be at least 1. `i1` is the boolean type. |
|
||||
| `f32`, `f64` | IEEE-754 binary32 and binary64. Implementations MAY support `f16`, `f80`, `f128`. |
|
||||
| `ptr<T>` | A pointer to `T`. The LLVM backend maps this to opaque `ptr` (see [009 — LLVM Backend](009-llvm-backend.md)). |
|
||||
| `[N x T]` | A fixed-size array of `N` elements of `T`. |
|
||||
| `{T1, T2, ...}` | A struct with the named field types. |
|
||||
| `union {T1, ...}` | A union of the named member types. |
|
||||
| `fn(T1, ...) -> R` | A function type with parameter types and return type `R`. |
|
||||
|
||||
- **L-39.** Aggregate values are first-class: they MAY be loaded, stored, passed, returned, and
|
||||
named by a `Phi`.
|
||||
- **L-40.** Aggregate indexing MUST use field indices, not byte offsets. The C backend leaves
|
||||
aggregate layout to the C compiler except where the `layout` attribute pins an ABI
|
||||
(see [008 — C Backend](008-c-backend.md)).
|
||||
|
||||
## 5. Invariants
|
||||
|
||||
The VIR module released by lowering MUST satisfy all of the following. They are checked by the
|
||||
verifier after lowering and, in debug builds, after every subsequent pass.
|
||||
|
||||
1. **Single definition.** Every SSA value has exactly one defining instruction.
|
||||
2. **Def dominates use.** Every use is dominated by its definition. A phi operand used in
|
||||
predecessor `P` MUST either dominate `P` or be the phi result itself (the loop case).
|
||||
3. **Terminated blocks.** Every basic block ends in exactly one terminator.
|
||||
4. **Phi arity.** Every `Phi` has one operand per predecessor, and each named predecessor is real.
|
||||
5. **CFG consistency.** Every successor edge from a block has a matching predecessor edge on the
|
||||
target, and the entry block has no predecessors.
|
||||
6. **Type consistency.** Every instruction's operands and result match the VIR type system.
|
||||
7. **LoweringMap total.** Every value-producing HIR node maps to at least one VIR entity, and every
|
||||
VIR entity maps back to a source HIR node.
|
||||
8. **Determinism.** Re-lowering the same input produces byte-identical VIR.
|
||||
|
||||
## 6. Example
|
||||
|
||||
Source:
|
||||
|
||||
```
|
||||
while (i < n) {
|
||||
s = s + a[i];
|
||||
i = i + 1;
|
||||
}
|
||||
```
|
||||
|
||||
Lowered shape (values elided):
|
||||
|
||||
```
|
||||
entry:
|
||||
br %loop.header
|
||||
|
||||
loop.header:
|
||||
%i = phi i32 [ 0, %entry ], [ %i.next, %loop.latch ]
|
||||
%s = phi i32 [ 0, %entry ], [ %s.next, %loop.latch ]
|
||||
%cmp = icmp slt i32 %i, %n
|
||||
condbr %cmp, %loop.body, %loop.exit
|
||||
|
||||
loop.body:
|
||||
%addr = gep [4 x i32] %a, 0, %i
|
||||
%elem = load i32 %addr
|
||||
%s.mid = add i32 %s, %elem
|
||||
br %loop.latch
|
||||
|
||||
loop.latch:
|
||||
%s.next = add i32 %s.mid, 0
|
||||
%i.next = add i32 %i, 1
|
||||
br %loop.header
|
||||
|
||||
loop.exit:
|
||||
ret void
|
||||
```
|
||||
|
||||
`%i` and `%s` are scalars that are neither addressed, escaping, nor runtime-mutable, so they are
|
||||
pure SSA values; their merge points in `loop.header` are phis. If `s` were addressed by a pointer,
|
||||
lowering would instead allocate a slot for it and emit `load`/`store` around each access.
|
||||
|
||||
## 7. Cross-references
|
||||
|
||||
- [000 — Overview](000-overview.md): determinism guarantee and conformance.
|
||||
- [001 — Architecture](001-architecture.md): module boundaries; lowering is the SSA construction site.
|
||||
- [002 — Traits](002-traits.md): attribute, request, and relationship vocabulary; epochs.
|
||||
- [003 — HIR](003-hir.md): the tree lowering consumes, including `defer`.
|
||||
- [004 — HIR Optimizer](004-hir-optimizer.md): who produces the annotated HIR.
|
||||
- [006 — VIR](006-vir.md): the target IR, its instructions, and its invariants.
|
||||
- [007 — VIR Optimizer](007-vir-optimizer.md): who consumes lowered VIR.
|
||||
- [008 — C Backend](008-c-backend.md), [009 — LLVM Backend](009-llvm-backend.md): consumers of VIR.
|
||||
- [011 — Diagnostics](011-diagnostics.md): internal-compiler-error policy and source mapping.
|
||||
Reference in New Issue
Block a user