281 lines
13 KiB
Markdown
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`.
|