15 KiB
006 — VIR
- Status: Draft
- Normative language:
MUST,MUST NOT,SHOULD,SHOULD NOT, andMAYare to be interpreted as described in RFC 2119.
1. Purpose
VIR (Vox IR) is the low-level instruction IR of libVCT. It is an SSA form over a control-flow graph. Every optimization above the backends operates on VIR, and both the C backend and the LLVM backend consume VIR. Lowering produces it (see 005 — Lowering); the VIR optimizer transforms it (see 007 — VIR Optimizer).
VIR is internal and is intended to be machine-generated. A textual form exists for tools, dumps, and round-trip tests, but it is not a supported authoring surface: programs are built through the HIR builder or as fixtures for tests (see 003 — HIR, 012 — Testing).
2. Scope
This file specifies the VIR SSA/CFG model, the VIR type system, the instruction set, and the well-formedness invariants every VIR module must satisfy. It does not specify how VIR is produced (see 005 — Lowering), how it is optimized (see 007 — VIR Optimizer), or how it is emitted (see 008 — C Backend, 009 — LLVM Backend).
3. SSA/CFG model
A VIR module contains functions. A function has a name, a signature, and a non-empty list of basic blocks; one block is the entry block. Every function is either a definition or a declaration. A declaration has a signature and no blocks.
A basic block has a label, unique within its function, and an ordered list of instructions ending in exactly one terminator. The terminator is the last instruction of the block.
An SSA value has a type and is defined by exactly one instruction. Values are referenced by
name. Following the LLVM convention, SSA names written %name are numbered values; unnumbered
temporaries are assigned deterministic numbers at print time.
The CFG is the directed graph whose nodes are blocks and whose edges are taken from
terminators. Edge b -> c means c is a successor of b and b is a predecessor of c. The
entry block has no predecessors.
Domination. Block a dominates block b if every path from the entry block to b passes
through a. Domination is reflexive; a strictly dominates b when a and b differ. An
instruction d dominates a use u when the block containing d dominates the block containing
u, or d and u are in the same block and d appears before u.
SSA form. Every value has exactly one definition, and every use is dominated by its definition.
The only relaxation is the Phi instruction, whose operands are conceptually evaluated at the end
of the corresponding predecessor.
Def-use. Each use of a value is a (user-instruction, operand-index) pair. The def-use graph
is the union of those pairs over all instructions. Passes that rewrite an operand MUST update both
sides of the edge.
4. Type system
VIR types are strictly typed. There are no implicit conversions; a conversion is an explicit cast instruction.
| Type | Meaning |
|---|---|
void |
No value. The result of effect-only calls and functions. |
iN |
An N-bit two's-complement integer, N >= 1. i1 is boolean. |
f32, f64 |
IEEE-754 binary32 and binary64. f16, f80, f128 MAY be supported. |
ptr<T> |
A pointer to T. |
[N x T] |
An array of N elements of type T. |
{T1, ..., Tn} |
A struct with the listed field types in order. A packed variant MAY exist. |
union {T1, ...} |
A union of the listed member types. |
fn(T1, ...) -> R |
A function type: parameter types and return type R. A variadic variant MAY exist. |
- V-1.
iNMUST accept any widthN >= 1; the implementation MUST NOT restrict integers to a fixed set of power-of-two widths. - V-2.
voidMUST NOT be used as the type of a value. It is valid only as a function result type and as the result type of a call that produces no value. - V-3. Aggregate types (
[N x T], struct, union) are first-class: they MAY be operands and results ofload,store,phi,select, and calls. - V-4. Pointer element types are used for typing
load,store, and indexedgep. They are a convenience for the C backend; the LLVM backend emits opaqueptrand relies on the instructions' operand types for correctness (see 009 — LLVM Backend).
5. Instruction set
An instruction has an opcode, zero or more operands, an optional result type and value, and optional flags. The catalog below is normative for opcode meaning; the exact textual spelling is specified by the dump format and is not an authoring interface.
5.1 Terminators
| Opcode | Syntax (conceptual) | Meaning |
|---|---|---|
br |
br %target |
Unconditional edge. |
condbr |
condbr %cond, %iftrue, %iffalse |
Conditional edge on an i1 value. |
switch |
switch %value, %default [ case ... ] |
Multi-way edge on an integer value. |
ret |
ret void / ret %value |
Return from the function. |
unreachable |
unreachable |
Marks a point that cannot be reached at runtime. |
- V-5. Every block MUST end in exactly one terminator.
- V-6. A
condbrcondition MUST have typei1. - V-7. A
switchvalue MUST have integer type; each case constant MUST fit that type. - V-8. A
retoperand MUST match the function's declared return type, and a void function MUST useret void.
5.2 Memory instructions
| Opcode | Syntax (conceptual) | Meaning |
|---|---|---|
alloca |
%p = alloca T [, align A] |
Allocate T on the stack; result type is ptr<T>. |
load |
%v = load T, ptr<T> %p [, align A] [, volatile] |
Read a T from memory. |
store |
store T %v, ptr<T> %p [, align A] [, volatile] |
Write a T to memory. |
gep |
%p = gep T, ptr<T> %base, idx... |
Compute a pointer to an indexed element or field. |
memcpy |
call ... |
Copy memory; also allowed as a dedicated opcode. |
memset |
call ... |
Fill memory; also allowed as a dedicated opcode. |
memmove |
call ... |
Overlapping copy; also allowed as a dedicated opcode. |
- V-9. A
loadof typeTMUST take a pointer of typeptr<T>; astoreMUST agree onT. - V-10. A
gepMUST index into aggregates by field index, not by byte offset. The first index traverses the pointer; subsequent indices traverse arrays and struct fields. - V-11. A
volatileload or store MUST NOT be reordered with respect to any other volatile or atomic access, and MUST NOT be eliminated even when its result is unused.
5.3 Arithmetic and bitwise instructions
| Category | Opcodes |
|---|---|
| Integer binary | add, sub, mul, udiv, sdiv, urem, srem |
| Integer bitwise | and, or, xor |
| Shifts | shl, lshr, ashr |
| Floating binary | fadd, fsub, fmul, fdiv, frem |
| Floating unary | fneg |
- V-12. Integer arithmetic MAY carry
nsw(no signed wrap) andnuw(no unsigned wrap) flags. A result on which overflow occurs under an asserted flag is poison; an implementation that does not model poison MUST NOT emit such a flag unless the overflow is provably impossible. - V-13. Floating-point instructions MAY carry the fast-math flags
nnan,ninf,nsz,arcp,contract,afn, andreassoc. A flag MUST only be set when the corresponding trait orConfiglicenses it. Fast-math flags MUST NOT be applied at optimization levels below-Ofastunless explicitly configured. - V-14.
udiv/sdiv/urem/sremby zero is undefined behavior. An implementation that models this MUST NOT fold such a division to an arbitrary value.
5.4 Comparison instructions
| Opcode | Predicates |
|---|---|
icmp |
eq, ne, slt, sle, sgt, sge, ult, ule, ugt, uge |
fcmp |
oeq, one, olt, ole, ogt, oge, ord, uno, ueq, une, ult, ule, ugt, uge, true, false |
- V-15. The result of
icmpandfcmpMUST have typei1. - V-16. Integer comparison predicates beginning with
sare signed; those beginning withuare unsigned.
5.5 Conversion instructions
| Opcode | Meaning |
|---|---|
trunc |
Narrow an integer. |
zext, sext |
Widen an integer, zero- or sign-extending. |
fptrunc, fpext |
Narrow or widen a float. |
fptoui, fptosi |
Float to integer. |
uitofp, sitofp |
Integer to float. |
ptrtoint, inttoptr |
Pointer/integer conversion. |
bitcast |
Reinterpret bits; requires equal size and no pointer-to-pointer element change in the C backend. |
- V-17. A conversion MUST be explicit. There MUST be no implicit widening, narrowing, or signedness change anywhere else in the instruction set.
5.6 Other instructions
| Opcode | Syntax (conceptual) | Meaning |
|---|---|---|
phi |
%v = phi T [ v1, %b1 ], [ v2, %b2 ], ... |
SSA merge at a join. |
select |
%v = select %cond, %a, %b |
Choose %a or %b on an i1. |
call |
%v = call R @callee(args...) [flags] |
Call a function or intrinsic. |
atomicrmw |
%v = atomicrmw op, ptr<T> %p, %x ordering |
Atomic read-modify-write, result is the old value. |
cmpxchg |
%v = cmpxchg ptr<T> %p, %expected, %new ordering |
Atomic compare-and-exchange. |
fence |
fence ordering |
Memory ordering fence. |
- V-18. A
phihas one operand per predecessor, each a(value, predecessor-block)pair. Its result type MUST equal each operand's type. - V-19. A
selectcondition MUST have typei1, and its two value operands MUST share a type that equals the result type. - V-20. A
callresult value MAY be absent when the callee returnsvoid. Call-site attributes (Inline,NoInline,TailCall,MustTail,NoTail,ColdCall,NoReturn, and the memory-effect requests) MUST be carried on the call and read from its trait, not re-derived. - V-21. Non-atomic accesses MUST NOT be reordered across an atomic access or a fence in a way that violates the ordering the access declares.
5.7 Intrinsics
A VIR intrinsic is a well-known callee referenced by a call instruction. The v1 intrinsic set is
fixed:
| Intrinsic | Signature | Meaning |
|---|---|---|
memcpy |
(ptr<T>, ptr<T>, iN) -> void |
Copy N bytes between non-overlapping regions. |
memset |
(ptr<T>, i8, iN) -> void |
Fill N bytes with the given byte value. |
memmove |
(ptr<T>, ptr<T>, iN) -> void |
Copy N bytes between possibly overlapping regions. |
sqrt |
(f32) -> f32, (f64) -> f64 |
IEEE-754 square root. |
fabs |
(f32) -> f32, (f64) -> f64 |
Absolute value. |
ctpop |
(iN) -> iN |
Count of set bits. |
fshl |
(iN, iN, iN) -> iN |
Funnel shift left. |
sadd_sat, uadd_sat, ssub_sat, usub_sat |
(iN, iN) -> iN |
Saturating add and subtract. |
sadd_overflow, uadd_overflow, ssub_overflow, usub_overflow, smul_overflow, umul_overflow |
(iN, iN) -> {iN, i1} |
Wrapping result plus an overflow flag. |
memcpy, memset, and memmove MAY also appear as dedicated opcodes (§5.2); the other intrinsics
appear only as calls.
- V-25. An intrinsic
callMUST use the canonical name in the table above and operands that match its signature. Its result type follows the signature; the overflow intrinsics return the struct{iN, i1}. - V-26. The intrinsic names are reserved. An implementation MUST recognize them as intrinsics rather than as user-defined callees, and MUST NOT allow a module to redefine them.
6. Well-formedness invariants
A VIR module is well-formed when it satisfies all of the following. The verifier checks them after lowering and, in debug builds, after every pass (see 012 — Testing).
- WF-1 (single definition). Every SSA value is defined exactly once in its function.
- WF-2 (def dominates use). Every use is dominated by its definition, with the phi exception:
an operand
(v, P)of a phi requiresvto dominateP, or to be the phi's own result whenPis a loop back-edge predecessor. - WF-3 (termination). Every basic block ends in exactly one terminator, and no terminator appears before the end of a block.
- WF-4 (phi arity). The number of phi operands equals the number of the phi block's predecessors, and each named predecessor is an actual predecessor.
- WF-5 (CFG consistency). For every successor edge
b -> cfrom a terminator,clistsbas a predecessor. The entry block has no predecessors. - WF-6 (operand types). Every instruction's operands and result conform to §4 and §5.
- WF-7 (return type). Every
retagrees with the function's declared return type. - WF-8 (function isolation). No instruction references a value defined in another function.
- WF-9 (declared callees). Every
callnames a function that is either defined in the module or declared in it. - WF-10 (alloca result). An
allocaofTproduces aptr<T>, and everyload/storethrough it agrees onT. - WF-11 (dead blocks). A block unreachable from the entry block MAY remain, but it MUST itself be well-formed.
Verification policy:
- V-22. In debug builds, the verifier MUST run after lowering and after every optimization pass. A violation MUST assert.
- V-23. In release builds, verification MUST be available behind a flag (the driver's
--verify) and MUST NOT run by default. A violation detected in release MUST raise an internal-compiler-error diagnostic as specified in 011 — Diagnostics. - V-24. An implementation MUST NOT "repair" a detected violation silently.
7. Textual form
The textual form prints functions, blocks, instructions, and types in a stable order. It exists
for dumps, tooling, and round-trip tests. The round-trip property is
print(parse(print(m))) == print(m) (see 012 — Testing). The parser is not a
supported frontend: it MAY assume the input was produced by the printer.
8. Example
define i32 @sum(i32 %n) {
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:
%s.mid = add i32 %s, %i
br %loop.latch
loop.latch:
%i.next = add i32 %i, 1
%s.next = add i32 %s.mid, 0
br %loop.header
loop.exit:
ret i32 %s
}
The two phis are required because loop.header has two predecessors, entry and loop.latch.
WF-4 requires both phis to carry exactly two operands, and WF-2 requires each operand to dominate
its named predecessor.
9. Cross-references
- 000 — Overview: determinism, non-goals (no hand-authored VIR).
- 001 — Architecture: VIR module placement and visibility.
- 002 — Traits: the only channel by which VIR receives facts.
- 005 — Lowering: constructs and validates VIR.
- 007 — VIR Optimizer: transforms VIR under the invariants here.
- 008 — C Backend: out-of-SSA and C emission.
- 009 — LLVM Backend: 1:1 VIR to LLVM mapping.
- 011 — Diagnostics: internal-compiler-error policy.
- 012 — Testing: verifier, round-trip, and differential tests.