Files
libvct-spec/spec/008-c-backend.md
T

281 lines
13 KiB
Markdown

# 008 — C 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 C backend (`vct.backend.c`) translates optimized VIR into C17 source. It is the
default backend: the driver hands each translation unit to a system C compiler that
produces an object file. The backend's target is **compiler-friendly C**, meaning the
emitted text is shaped so that GCC and Clang recognize it and optimize it well. libVCT
does not try to pre-optimize away everything the C compiler can do itself.
## 2. Scope
This file specifies the backend's contract and the required shape of its output:
translation-unit granularity, out-of-SSA lowering, control-flow reconstruction,
trait-to-C mappings, type and layout emission, name mangling, intrinsic selection,
debug line mapping, and ABI guarantees. The VIR input is specified in
[006 — VIR](006-vir.md); the pipeline that feeds the backend is specified in
[007 — VIR Optimizer](007-vir-optimizer.md) and [010 — Driver & CLI](010-driver-cli.md).
## 3. Definitions
| Term | Meaning |
|---|---|
| TU | Translation unit: one C17 source file. |
| Out-of-SSA | Conversion of SSA values into C variables with explicit assignments on every control-flow path. |
| Critical edge | A CFG edge whose source has multiple successors and whose destination has multiple predecessors. |
| Reducible CFG | A control-flow graph in which every cycle has exactly one loop header. |
| Loop header | The unique block by which control enters a loop. |
| `layout` attribute | The attribute that pins an aggregate's ABI: field offsets, total size, and alignment. |
| Opaque barrier call | A `noinline` function whose body the C compiler cannot inspect, used to stop optimization across a region. |
## 4. Model
The backend is a pure translation stage. It consumes optimized VIR that satisfies the
SSA invariants of [006 — VIR](006-vir.md) and emits C17. It performs no optimization
and no discovery; every fact it uses is read from traits (see
[002 — Traits](002-traits.md)).
Emission is **per function**. A VIR module with N functions produces N independent C
translation units. Each TU carries one function definition plus the declarations it
needs. That independence is what lets emission run in parallel, and it is what lets
the driver concatenate the results in a deterministic order.
## 5. Normative requirements
### 5.1 Translation-unit structure
**R1.** The backend MUST emit exactly one C17 translation unit per VIR function.
**R2.** Emitting one function's TU MUST NOT depend on emitting any other function's TU.
Two functions emitted in any order MUST yield identical per-function text.
**R3.** Each TU MUST be emitted in two passes: first all forward declarations, then all
definitions. Every declaration MUST precede the definition that uses it.
**R4.** The backend MUST assign each function a stable emission index derived only from
module content, so the driver can order TUs deterministically (see
[010 — Driver & CLI](010-driver-cli.md)).
**R5.** For a fixed module and `Config`, the concatenation of per-function TUs MUST be
byte-identical across runs and independent of thread count (see
[000 — Overview](000-overview.md)).
### 5.2 Out-of-SSA
**R6.** The backend MUST convert SSA to C using the algorithm of Boissinot, Brisk,
Caron, and Ferrand, "Revisiting Out-of-SSA Translation for Correctness, Code Quality,
and Efficiency" (CGO 2009), or an algorithm observably equivalent to it. The
conversion MUST handle the lost-copy and swap problems.
**R7.** The backend MUST split all critical edges **before** running out-of-SSA
conversion.
**R8.** After conversion, every SSA value MUST map to exactly one C variable or
expression, and every `Phi` MUST lower to an assignment on each incoming edge, or to a
semantically equivalent construct.
### 5.3 Control-flow reconstruction
**R9.** For reducible CFGs, the backend MUST reconstruct structured C control flow using
`if`/`else`, `while`, `for`, `do`, and `switch` wherever the shape is recognized.
**R10.** For irreducible CFGs, or for regions where no structured shape is recognized,
the backend MUST fall back to `goto` and labels. The fallback MUST preserve semantics.
This is a correctness requirement, not a quality preference.
**R11.** Structured reconstruction SHOULD be preferred over `goto` fallback whenever a
recognized shape exists, because it improves downstream optimization quality.
### 5.4 Compiler-friendly C
**R12.** The backend MUST map traits to C qualifiers, hints, and pragmas as specified
in Table 1. When a trait is absent, the corresponding construct MUST NOT be emitted.
**Table 1. Trait to C mapping**
| Trait | C construct | Notes |
|---|---|---|
| `Restrict`, `NoAlias` | `restrict` | On pointer parameters and local pointers. |
| `ReadOnly`, `NoWrite` | `const` | On the pointee type of a pointer or reference parameter. |
| `NoWrite` | `__attribute__((pure))` | Function level; reads allowed, writes not allowed. |
| `NoRead` + `NoWrite` | `__attribute__((const))` | Function level; no memory access. |
| `NoRead` | No portable C17 construct | No GCC/Clang attribute means "does not read"; omit it. |
| `NonNull` | `nonnull` attribute or `__builtin_unreachable` guard | |
| `NoReturn` | `_Noreturn` | |
| `ColdPath` | `__attribute__((cold))` | |
| `HotPath` | `__attribute__((hot))` | |
| `LikelyCall` | `__builtin_expect(cond, 1)` | On the branch condition. |
| `UnlikelyCall`, `ColdCall` | `__builtin_expect(cond, 0)` | On the branch condition. |
| `AlwaysInline` | `static inline` plus `always_inline` attribute | |
| `NoInline` | `__attribute__((noinline))` | |
| `Align(n)` | `_Alignas(n)` and/or `__builtin_assume_aligned` | |
| `Assume(pred)` | `__builtin_assume(pred)` (Clang) | |
| `Unreachable` | `__builtin_unreachable()` | |
| `Vectorize` | `#pragma GCC ivdep` or `#pragma clang loop vectorize(enable)` | Selected by probed compiler dialect. |
| `NoOptimize`, `complex` | Optimization barrier, see R14 | |
**R13.** The backend MUST select between GCC and Clang spellings of pragmas and
builtins from the compiler dialect probed once by the driver (see
[010 — Driver & CLI](010-driver-cli.md)). When the probed compiler does not support a
construct, the backend MUST omit it rather than emit something that fails to compile,
unless the corresponding request is `Strong`, in which case the denial rules of
[007 — VIR Optimizer](007-vir-optimizer.md) apply.
**R14 (Optimization barrier).** For a region marked `complex` or carrying a `NoOptimize`
request, the backend MUST emit a compiler barrier: a memory fence
(`__asm__ __volatile__("" ::: "memory")` on GCC and Clang) followed by a call to an
opaque function marked `noinline`. The call MUST be a real call that the C compiler
cannot see through.
### 5.5 Types and layout
**R15.** The backend MUST map VIR types to C types as specified in Table 2.
**Table 2. VIR type to C type**
| VIR type | C type |
|---|---|
| Signed integer with a C17 fixed-width type | `intN_t` |
| Unsigned integer with a C17 fixed-width type | `uintN_t` |
| `f32` | `float` |
| `f64` | `double` |
| Pointer `T*` | `T *` |
| Struct | `struct` |
| Union | `union` |
| Array `[N x T]` | `T[N]` |
| Function type | Function declarator or function pointer |
| `void` | `void` |
**R16.** An integer width with no C17 fixed-width type (any width other than 8, 16, 32,
or 64 bits) MUST be diagnosed as unsupported on the C path unless the probed compiler
provides an N-bit integer type, in which case the backend MAY use it.
**R17.** Aggregate GEPs MUST use field indices, never byte offsets. The backend MUST
let the C compiler choose struct layout unless the `layout` attribute pins it.
**R18.** When the `layout` attribute pins an aggregate's ABI, the backend MUST emit explicit
padding members to force the specified offsets and total size, and MUST emit
`_Static_assert` checks on `sizeof` and on `offsetof` for every pinned field.
### 5.6 Naming
**R19.** Exported names MUST be preserved exactly as the frontend supplied them.
Internal names MUST be sanitized into valid C identifiers and uniquified
deterministically.
**R20.** Sanitization and uniquification MUST NOT produce an identifier that collides
with a C keyword, a standard library identifier, or a compiler-reserved identifier. The
reserved-word and collision prefix policy of Table 3 MUST be applied.
**Table 3. Reserved identifier prefix policy for internal names**
| Category | Action |
|---|---|
| C keywords (`int`, `while`, ...) | Prefix with `vct_` |
| C17 standard library names (`memcpy`, `printf`, ...) | Prefix with `vct_` |
| Implementation-reserved names (`_x`, `__x`, `_[A-Z]...`) | Prefix with `vct_` |
| Compiler builtins (`__builtin_*`) | Prefix with `vct_` |
| Post-mangle collisions | Append a stable numeric suffix |
**R21.** The disambiguator used for uniquification MUST be derived only from
deterministic module content. It MUST NOT depend on allocation addresses, hash-table
iteration order, or thread scheduling.
### 5.7 Intrinsics
**R22.** The backend MUST lower the VIR intrinsics of [006 — VIR](006-vir.md) to the matching
`__builtin_*` function where GCC and Clang provide one.
**R23.** For intrinsics with no portable builtin, the backend MUST emit a call to a
portable helper with a stable name. A helper definition MUST be emitted per function,
deterministically, with internal linkage, into every TU whose function references it, so
that per-function text remains a pure function of that function's VIR (R2, I2). A helper
definition MUST NOT be emitted once per module or into a shared, module-level TU.
### 5.8 Debug mapping
**R24.** When debug mapping is enabled, the backend MUST emit `#line` directives that map
emitted C lines to frontend source positions, using the source map registered with the
diagnostics service (see [011 — Diagnostics](011-diagnostics.md)).
**R25.** Debug mapping MUST be flag-gated: enabled when debug mode is on, disabled in
release. When disabled, the backend MUST NOT emit any `#line` directive.
### 5.9 ABI
**R26.** Exported functions MUST match the platform C ABI. Internal functions MUST be
emitted `static`.
**R27.** Struct-by-value arguments and returns MUST follow the C compiler's ABI,
including any constraint introduced by the `layout` attribute.
**R28.** A `MustTail` request MUST be honored with `[[clang::musttail]]` when the probed
compiler is Clang. When the probed compiler is GCC, a `MustTail` request MUST be a hard
error (see [011 — Diagnostics](011-diagnostics.md)). This is the semantic-capability
exception of O-26 in [007 — VIR Optimizer](007-vir-optimizer.md): GCC cannot express the
required tail-call guarantee, so the `Strong` request is fatal rather than a warning.
**R29.** The backend MUST NOT emit implementation-defined constructs beyond those
explicitly permitted here. In particular it MUST NOT emit byte-offset aggregate
accesses, alignment-violating pointer casts, or identifiers outside the reserved prefix
policy.
## 6. Invariants
- **I1.** Each emitted TU is a valid C17 translation unit given the shared declarations.
- **I2.** Per-function text, including any helper definitions it carries, is a pure function of
that function's VIR and the traits it references.
- **I3.** Concatenated output is byte-identical for a fixed input and `Config`, at any
thread count.
- **I4.** Every definition is preceded by a declaration.
- **I5.** No two emitted identifiers collide after mangling.
- **I6.** Every layout-pinned aggregate satisfies its static assertions at compile time.
- **I7.** Every structured-to-`goto` fallback preserves the CFG's reachability and
semantics.
## 7. Examples
A `Phi` merging `%a` and `%b` on the true and false edges of a conditional branch lowers
to a temporary assigned on each edge before out-of-SSA bookkeeping:
```c
int vct_phi0; /* out-of-SSA temporary for a Phi */
if (cond) { vct_phi0 = a; } else { vct_phi0 = b; }
return vct_phi0;
```
A `NoOptimize` region emits a fence followed by an opaque call:
```c
__asm__ __volatile__("" ::: "memory");
vct_opaque_barrier(x);
```
A layout-pinned struct emits padding plus compile-time checks:
```c
struct vct_pair {
int32_t lo;
int32_t vct_pad0;
int64_t hi;
};
_Static_assert(sizeof(struct vct_pair) == 16, "layout");
_Static_assert(offsetof(struct vct_pair, hi) == 8, "layout");
```
## 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.
- [009 — LLVM Backend](009-llvm-backend.md) for the alternate backend.
- [010 — Driver & CLI](010-driver-cli.md) for compiler probing and TU concatenation.
- [011 — Diagnostics](011-diagnostics.md) for error reporting and `#line`.