12 KiB
003 — HIR
- Status: Draft
- Normative language:
MUST,MUST NOT,SHOULD,SHOULD NOT, andMAYare to be interpreted as described in RFC 2119.
1. Purpose
HIR (High-level IR) is the tree-shaped abstract syntax that a frontend builds and libVCT annotates in place. It is the discovery half of the two-stage IR: HIR establishes node identity, attaches traits, and performs cheap folds. Lowering consumes annotated HIR and produces VIR (see 005 — Lowering).
This file specifies the HIR node model, the node kinds, node identity, the builder/emitter contract,
the defer statement, and the textual HIR format.
2. Scope
In scope: the HIR data model and its observable contract toward frontends, the HIR optimizer, and lowering. Out of scope: the trait vocabulary itself (see 002 — Traits), the optimizer's pass framework and cascade (see 004 — HIR Optimizer), and the HIR-to-VIR mapping (see 005 — Lowering).
A frontend that embeds libVCT interacts with HIR through the builder API and the trait query API. The textual HIR format is a tool surface, not a frontend surface.
3. Node model
Key properties, all normative:
- HIR is tree-shaped. Every node except the module root has exactly one parent. A node has an ordered list of children whose meaning is determined by the node kind.
- HIR is arena-owned. Nodes are allocated from the module's arena and are never individually freed. An arena is reclaimed wholesale (see 001 — Architecture and 013 — Build & Packaging).
- HIR is annotated in place. Every node carries one embedded
Trait(see 002 — Traits) that the HIR optimizer mutates as it derives facts. There is no separate side table for the common case. - HIR is high-level. It preserves structured control flow (
if/while/for/switch/block) and expressions; it is not SSA and has no phi-nodes. - Nodes are mutable during the HIR optimizer's cascade and become read-only to a consumer once traits are epoch-valid. A frontend MUST NOT mutate a module after finishing it.
Every node exposes at least:
struct HirNode {
NodeId id; // stable identity, unique within the module
NodeKind kind;
Span span; // optional frontend source span
Trait trait; // attribute + requests + relationships
NodeId[] children;
}
span MAY be absent. Absence MUST degrade diagnostics gracefully and MUST NOT affect
optimization correctness.
4. Node kinds
The following is the reference node set for v1. A frontend MUST build modules only from these kinds. Additional kinds are reserved and require a specification change. A frontend MAY use any subset of the set.
4.1 Structural kinds
| Kind | Description | Children / payload |
|---|---|---|
Module |
Root of one translation unit. | functions, globals |
Function |
A function definition or declaration. | params, body Block |
Param |
A function parameter. | none |
Block |
An ordered statement scope. | statements |
4.2 Declaration kinds
| Kind | Description | Children / payload |
|---|---|---|
GlobalVar |
A module-level storage object. | optional init expression |
LocalVar |
A local binding with optional initializer. | optional init expression |
A declaration MUST carry a ty attribute. A declaration whose initializer is compile-time
known MUST be eligible for is_comptime/const_value annotation by the optimizer.
4.3 Statement kinds
| Kind | Description | Children / payload |
|---|---|---|
ExprStmt |
Evaluate an expression for effect. | expression |
Return |
Return from the enclosing function. | optional value |
If |
Two-way branch. | condition, then-block, optional else-block |
While |
Pre-test loop. | condition, body |
For |
C-style loop. | optional init, optional condition, optional step, body |
Switch |
Multi-way branch. | subject, cases |
Case |
One switch arm. | optional value, body |
Break |
Exit the innermost loop or switch. | none |
Continue |
Continue the innermost loop. | none |
Defer |
Scope-exit action; see the defer section below. |
deferred expression or statement |
4.4 Expression kinds
| Kind | Description | Children / payload |
|---|---|---|
Literal |
Integer, float, string, boolean, or null constant. | literal payload |
NameRef |
Reference to a declared name. | resolved target identity |
Unary |
Prefix or postfix unary operator. | operand |
Binary |
Binary operator. | left, right |
Assign |
Assignment to an lvalue. | target, value |
Call |
Function or intrinsic call. | callee, arguments |
Member |
Aggregate field access. | base, field index |
Index |
Pointer or array element access. | base, index |
Cast |
Type conversion. | operand, target type |
Every expression node MUST carry a ty attribute once HIR has validated it. Literal nodes
MUST additionally carry const_value when their value is representable in the type.
5. Node identity
- Every node MUST have a
NodeIdthat is unique within itsModule. - Identity MUST be assigned deterministically from construction order, so that two runs over the same input produce the same identity assignment.
- Identity MUST remain stable across trait annotation and optimizer rewrites of the same node.
- A deleted node's identity MUST NOT be reused for a different node.
- VIR depends on HIR for traits and node identity only (see 001 — Architecture). Lowering records the mapping from HIR identity to VIR entities (see 005 — Lowering).
Identity is the join key that lets diagnostics, relationships, and the lowering map refer to the same node across stages.
6. The defer statement
HIR carries defer as a real high-level statement. Its observable semantics:
- A
Defernode names a body that runs at every scope exit of the enclosingBlock. - Scope exits include normal fall-through,
Break,Continue, andReturn. - Deferred bodies within one scope run in reverse declaration order (last declared runs first).
- A backend MUST NOT ever observe a
Defernode. Before VIR is produced, everyDefermarker MUST be erased with zero runtime residue. The behavior MUST be identical to writing the deferred body textually at each exit point. - Expansion is performed by the HIR optimizer when HIR optimization is enabled. If HIR
optimization is disabled (
-O0, see 004 — HIR Optimizer), lowering itself MUST expanddeferat every scope exit (see 005 — Lowering). - Expansion MUST be deterministic. When a scope has multiple exits, the emitted bodies MUST appear in a stable, specified order so that output is byte-identical across runs.
Because expansion is always resolved before VIR, defer costs nothing at runtime.
7. Builder and emitter contract
7.1 Frontend-facing builder
The builder is the frontend's only construction surface. It provides operations to create each
node kind, link children, set a node's type and value, and attach frontend-suggested traits
(including is_static and optimization hints). Its contract:
- The builder MUST assign node identity per the node identity section above.
- The builder MUST allow a frontend to construct a module without supplying derived attributes. Derived facts are HIR's responsibility, not the frontend's.
- The builder MUST accept a frontend-suggested attribute or request, record it with
attr_source = suggested, and defer validation to HIR. finish(module)MUST validate every suggested trait. Validation follows the rule in 002 — Traits: on a wrong or unprovable suggestion HIR emits a warning (an error under-Werror), overwrites the value with the correct one, and marks itderived. A correct suggestion is preserved.- After
finish, the builder MUST reject further structural mutation of that module by the frontend. Optimization mutates in place internally, under epoch guards.
7.2 Trait attachment
The frontend attaches a Trait to each node. The trait is incomplete at build time: the frontend
supplies ty, is_static, and complex where it knows them, and the HIR optimizer fills in the
rest. A node lacking a derived attribute is not an error at build time; it becomes one only if
lowering finds the trait incomplete (see 005 — Lowering).
HIR MUST NOT require a frontend to compute is_used, escapes, is_addressed,
const_value, or any other derived attribute.
7.3 Textual emitter
The textual emitter is a distinct, non-authoring surface (see the textual HIR format section). It MUST be able to serialize a finished HIR module and to parse that serialization back into an equivalent module.
8. Textual HIR format
HIR has a textual form used solely for tools and round-trip tests. It is explicitly NOT an authoring surface: frontends embed the library and build HIR through the builder API. The format is versioned independently of the library and of the trait vocabulary (see 013 — Build & Packaging).
The format carries, per node: kind, identity, child references, type, and the node's trait (with
each attribute's value and attr_source, its requests and strengths, and its relationships). The
reference round-trip property is:
print(parse(print(m))) == print(m)
A conforming implementation MUST satisfy this property for any module it can emit. Parsing MUST NOT be required to accept hand-written input beyond what the emitter produces.
Illustrative fragment (shape only, not a complete grammar):
fn @main() -> i32 {
%v1: i32 = literal 4 ; attr: const_value=4 is_comptime=true (derived)
%v2: i32 = binary add %v1, 4 ; req: Inline (soft)
defer %v3
ret %v2
}
9. Invariants
- Every non-root node has exactly one parent; the module root has none.
- Every node has a unique
NodeIdwithin its module, and ids are not reused. - Every node carries exactly one
Trait. - After
finish, every suggested attribute has been validated and either preserved or overwritten. - No
Defernode survives into VIR. - HIR is not SSA and MUST NOT be required to answer SSA questions.
- Frontend-supplied types and values are suggestions, never authoritative facts.
10. Example
A frontend builds:
fn main() -> int {
x = 4;
y = x + 4;
z = foo(x, y);
a = sqrt(z);
println(a);
}
It creates Function, LocalVar, Binary, Call, and Return nodes, sets each node's ty,
attaches is_static where known, and calls finish. HIR then validates the suggestions, derives
const_value for x, y, and the folded sqrt result, and issues an Inline request for
foo (see 004 — HIR Optimizer). Content that defer would add is expanded
at scope exits, so the emitted VIR contains no defer marker. The full walkthrough is normative in
014 — Worked Example.