# 009 — LLVM Backend - **Status:** Draft - **Normative language:** `MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` are to be interpreted as described in RFC 2119. ## 1. Purpose The LLVM backend (`vct.backend.llvm`) translates optimized VIR into textual LLVM IR (the `.ll` assembly format). It is the alternate backend. It runs **after** VIR optimization and is a pure translation: it performs no optimization and no discovery. libVCT never invokes `llvm-as`, `opt`, or `llc`, and never links against LLVM. Converting `.ll` to bitcode, an object file, or an executable is the caller's job. ## 2. Scope This file specifies the backend's contract: 1:1 SSA mapping, the type mapping, the full trait-to-attribute and trait-to-metadata mapping, intrinsic selection, module scaffolding, the LLVM version pin, and determinism. The VIR input is specified in [006 — VIR](006-vir.md); the optimizer that runs before this backend is specified in [007 — VIR Optimizer](007-vir-optimizer.md). The C backend is specified in [008 — C Backend](008-c-backend.md). ## 3. Definitions | Term | Meaning | |---|---| | `.ll` | Textual LLVM assembly. | | Opaque pointer | The LLVM `ptr` type, which carries no pointee type. | | Target triple | The architecture/vendor/OS string that selects codegen. | | Datalayout | The LLVM string describing primitive sizes, alignments, and endianness. | | Metadata | `!name` nodes attached to instructions or the module. | | `!range` | Metadata constraining the possible values of a load or call result. | ## 4. Model VIR is already in SSA form, so the mapping to LLVM is **1:1**. There is no out-of-SSA step here; that step exists only on the C path (see [008 — C Backend](008-c-backend.md)). Each VIR block becomes one LLVM basic block, each VIR instruction becomes one LLVM instruction, and each `Phi` becomes an LLVM `phi`. The output is a **self-contained module**. It carries its own target triple and datalayout, and it declares every external symbol it references before it defines anything. A consumer can pass the module straight to `llvm-as` with no additional environment. ## 5. Normative requirements ### 5.1 Purity and output **R1.** The LLVM backend MUST perform pure translation. It MUST NOT run optimization, inference, or discovery passes, and it MUST NOT modify the VIR it reads. **R2.** The backend MUST emit textual `.ll` only. It MUST NOT invoke `llvm-as`, `opt`, `llc`, or any other LLVM tool, and it MUST NOT link against LLVM libraries. **R3.** The backend MUST emit a self-contained module that includes a target triple and a datalayout derived from that triple. **R4.** The backend MUST emit all declarations before any definition. **R5.** For a fixed input and `Config`, the emitted module MUST be byte-identical across runs and independent of thread count (see [000 — Overview](000-overview.md)). ### 5.2 SSA mapping **R6.** The backend MUST map VIR constructs to LLVM constructs 1:1: **Table 1. VIR to LLVM structural mapping** | VIR | LLVM | |---|---| | Basic block | Basic block with a label | | Instruction | Instruction, in the same order | | `Phi(value, pred)` | `phi` with one incoming pair per predecessor | | Conditional branch | `br i1 , label , label ` | | Switch | `switch` | | Unconditional branch | `br label ` | | Return | `ret` | | Unreachable | `unreachable` | | `alloca` | `alloca` | | `load` | `load` | | `store` | `store` | **R7.** The backend MUST preserve block order given by the VIR module, and MUST preserve instruction order within a block, so that the 1:1 mapping is observable in the text. **R8.** Every VIR value MUST map to exactly one LLVM SSA name. Names MUST be generated deterministically. ### 5.3 Types **R9.** The backend MUST map VIR types to LLVM types as specified in Table 2. **Table 2. VIR type to LLVM type** | VIR type | LLVM type | |---|---| | Integer of N bits, signed or unsigned | `iN` | | `f32` | `float` | | `f64` | `double` | | Pointer `T*` | `ptr` | | Struct | `%struct.Name = type { ... }` | | Union | `%union.Name = type { ... }` | | Array `[N x T]` | `[N x T]` | | `void` | `void` | | Function type | ` ()` | **R10.** The backend MUST pin LLVM 18 or later and MUST use **opaque pointers**. It MUST NOT emit typed pointers such as `i32*`. **R11.** Aggregate GEPs MUST use VIR field indices directly as LLVM struct indices. The backend MUST NOT translate them to byte offsets. **R12.** The backend MUST emit no `!llvm.dbg` metadata in v1. Debug mapping exists only on the C path (see [008 — C Backend](008-c-backend.md)). ### 5.4 Traits to attributes and metadata **R13.** The backend MUST map traits to LLVM attributes and metadata as specified in Table 3. When a trait is absent, the matching attribute or metadata MUST NOT be emitted. **Table 3. Trait to LLVM attribute and metadata mapping** | Trait | LLVM attribute or metadata | |---|---| | `Restrict`, `NoAlias` | `noalias` parameter attribute plus `!alias.scope` and `!noalias` | | `ReadOnly`, `NoWrite` | `readonly` | | `WriteOnly`, `NoRead` | `writeonly` | | `NoRead` + `NoWrite` | `readnone` (spelled `memory(none)` on LLVM 18) | | `NonNull` | `nonnull` | | `Align(n)` | `align n` | | `Range(lo, hi)` | `!range` on loads and calls | | `Assume(pred)` | `llvm.assume` | | `NoInline`, `AlwaysInline` | `noinline` / `alwaysinline` | | `TailCall`, `MustTail`, `NoTail` | `tail` / `musttail` / `none` | | `NoReturn` | `noreturn` | | `HotPath`, `ColdPath` | `!prof` | | `Vectorize` | `!llvm.loop.vectorize.enable` | | `Unroll(n)` | `!llvm.loop.unroll.count` | | `Unreachable` | `unreachable` terminator | | `NoUndef` | Reserved; see R14 | The pairing mirrors the C mapping in [008 — C Backend](008-c-backend.md): a no-write fact takes a read-only annotation, a no-read fact takes a write-only annotation, and their combination takes `readnone`. `ReadOnly`/`WriteOnly` are region-level facts and `NoRead`/`NoWrite` are access-edge primitives (see [002 — Traits](002-traits.md)); the emitted attribute follows the entity the trait attaches to, whether a function or a pointer parameter. **R14.** `NoUndef` is reserved and is only meaningful on the LLVM path. The backend MUST NOT interpret it on the C path. When present here, it MUST translate to the corresponding `noundef` attribute. **R15.** The backend MUST emit real `llvm.assume` calls and real `!range` metadata; it MUST NOT emit placeholders that a consumer would have to fill in. ### 5.5 Intrinsics **R16.** The backend MUST lower the VIR intrinsics of [006 — VIR](006-vir.md) to the LLVM intrinsics listed in Table 4. **Table 4. VIR intrinsic to LLVM intrinsic** | VIR intrinsic | LLVM intrinsic | |---|---| | `memcpy` | `llvm.memcpy.p0.p0.i64` | | `memset` | `llvm.memset.p0.i64` | | `memmove` | `llvm.memmove.p0.p0.i64` | | `sqrt(f32/f64)` | `llvm.sqrt.f32` / `llvm.sqrt.f64` | | `fabs(f32/f64)` | `llvm.fabs.f32` / `llvm.fabs.f64` | | `ctpop(iN)` | `llvm.ctpop.iN` | | `fshl(iN)` | `llvm.fshl.iN` | | `sadd_sat`, `uadd_sat`, `ssub_sat`, `usub_sat` | `llvm.sadd.sat.iN`, `llvm.uadd.sat.iN`, `llvm.ssub.sat.iN`, `llvm.usub.sat.iN` | | `sadd_overflow`, `uadd_overflow`, `ssub_overflow`, `usub_overflow`, `smul_overflow`, `umul_overflow` | `llvm.sadd.with.overflow.iN`, `llvm.uadd.with.overflow.iN`, `llvm.ssub.with.overflow.iN`, `llvm.usub.with.overflow.iN`, `llvm.smul.with.overflow.iN`, `llvm.umul.with.overflow.iN` | **R17.** The backend MUST emit the declaration of every intrinsic it references before the definition that calls it. ### 5.6 Module scaffolding **R18.** The backend MUST emit a `target triple` line taken from the configured target (see [010 — Driver & CLI](010-driver-cli.md)). **R19.** The backend MUST emit a `target datalayout` line derived from that triple, so the module is self-contained. **R20.** The backend MUST emit declarations for all external functions and globals before any definition. **R21.** The backend SHOULD emit deterministic type and global names so that two runs over the same module produce identical text. ## 6. Invariants - **I1.** The emitted module is parseable by `llvm-as` for LLVM 18 or later. - **I2.** There is a 1:1 correspondence between VIR blocks and instructions and the emitted LLVM blocks and instructions. - **I3.** The module is self-contained: triple, datalayout, and all referenced declarations are present. - **I4.** No typed pointers appear anywhere in the output. - **I5.** Declarations precede definitions. - **I6.** Output is deterministic for a fixed input and `Config`, at any thread count. - **I7.** No `!llvm.dbg` metadata appears in v1 output. ## 7. Examples Module scaffolding with a declaration before a definition, opaque pointers, and a `phi`: ```llvm ; ModuleID = 'demo' target triple = "x86_64-unknown-linux-gnu" target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" %struct.Pair = type { i32, i64 } declare i32 @extern_fn(ptr) define i32 @add(i32 %a, i32 %b) { entry: %r = add i32 %a, %b ret i32 %r } define i32 @loop_sum(ptr %p, i32 %n) { entry: br label %loop loop: %i = phi i32 [ 0, %entry ], [ %next, %body ] %acc = phi i32 [ 0, %entry ], [ %acc.next, %body ] %cond = icmp slt i32 %i, %n br i1 %cond, label %body, label %exit body: %v = load i32, ptr %p, align 4, !range !0 %acc.next = add i32 %acc, %v %next = add i32 %i, 1 br label %loop exit: ret i32 %acc } !0 = !{i32 0, i32 100} ``` A `Restrict` parameter and an `Assume` lower like this: ```llvm define void @copy(ptr noalias %dst, ptr noalias %src) { entry: %ok = icmp ne ptr %dst, null call void @llvm.assume(i1 %ok) ret void } ``` ## 8. Cross-references - [000 — Overview](000-overview.md) for determinism and conformance. - [001 — Architecture](001-architecture.md) for module boundaries. - [002 — Traits](002-traits.md) for the trait vocabulary consumed here. - [006 — VIR](006-vir.md) for the input IR and its invariants. - [007 — VIR Optimizer](007-vir-optimizer.md) for the pass pipeline. - [008 — C Backend](008-c-backend.md) for the default C path. - [010 — Driver & CLI](010-driver-cli.md) for the `-target` flag and mode selection. - [011 — Diagnostics](011-diagnostics.md) for error reporting.