16 KiB
002 — Traits
- Status: Draft
- Normative language:
MUST,MUST NOT,SHOULD,SHOULD NOT, andMAYare to be interpreted as described in RFC 2119.
1. Purpose
The trait model is the vocabulary libVCT uses to describe facts about IR nodes. It is the contract between the two optimization halves of the library: HIR discovers and derives traits, and VIR reads them and transforms accordingly. VIR performs no discovery of its own. Everything VIR may rely on is an attribute, a request, or a relationship defined here.
2. Scope
This file specifies:
- the
Traitunion of an Attribute, a set of Requests, and a set of Relationships; - the complete v1 attribute set and request catalog;
- relationship flags and relation attributes;
- the query API semantics a trait consumer may assume;
- the trait representation and staleness model;
- request conflict resolution.
Out of scope: how HIR derives attributes (see 004 — HIR Optimizer), how lowering copies traits onto VIR entities (see 005 — Lowering), and how the VIR optimizer acts on requests (see 007 — VIR Optimizer).
Traits are public. Frontends embed trait vocabulary; frontends are not required to derive traits themselves.
3. Definitions
| Term | Definition |
|---|---|
| Node | An arena-owned HIR entity carrying one Trait. |
| Attribute | A fact about a node (its type, value, mutability, and so on). |
| Request | An attribute-derived suggestion from HIR to VIR. Never ad-hoc. |
| Relationship | A directed, traceable edge from a node to another node. |
| Suggested | An attribute or request set by the frontend at build time. |
| Derived | A trait produced by the HIR optimizer. Authoritative. |
| Epoch | A monotonically increasing generation counter used to invalidate stale traits. |
| Consumer | Any component that reads traits. VIR is the primary consumer. |
The Trait is the union of the three sub-structures:
struct Trait {
Attribute attr;
Request[] reqs;
Relation[] rels;
}
A trait attaches to exactly one node. Trait values are per-node and, after HIR optimization, are complete for every node that lowering consumes.
4. Attribute model
Attributes are facts about a node. They share one namespace with two origins:
- Suggested: set by the frontend at build time through the builder API.
- Derived: produced by the HIR optimizer during the cascade. Derived attributes are authoritative.
Every attribute carries an attr_source marker recording whether its current value is suggested
or derived.
4.1 Attribute validation rule
This rule is normative and applies to every suggested attribute:
- When the frontend suggests an attribute, HIR MUST validate the suggestion against what it can prove.
- If the suggested value is wrong or unprovable-in-the-suggested-direction, HIR MUST emit a
warning, MUST overwrite the attribute with the correct value, and MUST mark
attr_source = derived. - The warning MUST be promoted to an error under
-Werror. A frontend MAY opt out for a specific category with-Wno-error=<category>, or for every category with a bare-Wno-error. - A correct suggestion MUST be preserved and continue to report
attr_source = suggesteduntil HIR derives a replacement value.
HIR MUST NOT silently accept a wrong suggestion. A consumer MUST treat derived attributes as authoritative and MUST NOT re-derive them.
4.2 v1 attribute set
| Attribute | Meaning | Typical source |
|---|---|---|
ty |
The node's VIR type. | Both |
const_value |
Present if and only if the value is known at compile time. | Derived |
is_static |
Frontend-declared compile-time value (the frontend-facing static concept). Seeds the comptime evaluator. |
Suggested |
is_comptime |
HIR-proven compile-time value. | Derived |
is_constant |
Value is constant; not necessarily compile-time-evaluable. | Derived |
is_used |
Node has at least one use. | Derived |
is_mutably_used |
Node is mutated through at least one use. | Derived |
is_addressed |
Address taken via pointer or reference. | Derived |
escapes |
Value escapes its defining scope. | Derived |
is_runtime_mutable |
Storage is written at runtime. | Derived |
may_change_at_runtime |
The observable value may differ between reads or executions (external state). Distinct from is_runtime_mutable. |
Derived |
is_volatile |
Accesses are volatile: not eliminated, not reordered, and not promoted to SSA. | Suggested |
is_atomic |
Accesses are atomic and carry a declared memory ordering. | Suggested |
complex |
Frontend-provided; gates the NoOptimize request. |
Suggested |
layout |
Pinned aggregate ABI: field offsets, total size, and alignment. | Suggested |
is_runtime_mutable describes storage writes; may_change_at_runtime describes externally
observable value change. They are independent and both MUST be tracked.
is_static is trusted by HIR as "this intends to be compile-time", but it is still validated: HIR
MUST verify the claim before marking is_comptime.
is_volatile and is_atomic force a value to memory; lowering and later passes MUST NOT
promote such a value to SSA (see 005 — Lowering). The layout attribute pins
an aggregate's ABI; the C backend MUST emit explicit padding and static assertions for a
layout-pinned aggregate (see 008 — C Backend).
5. Request model
Requests are attribute-derived suggestions. A frontend suggesting a request that no attribute supports is invalid and HIR MUST reject it with a diagnostic.
Each request has a strength:
- Soft: a suggestion VIR MAY decline.
- Strong: violating it is likely a bug. A capable consumer MUST honor it or report an
error with a
DenyReasonofIllegalityorContradictsTrait(see 007 — VIR Optimizer).
Every attempted request produces a RequestResult:
struct RequestResult {
Request req;
bool accepted;
DenyReason reason;
string note; // one-line human-readable explanation
}
A denial MUST carry a DenyReason and a one-line note. On acceptance, reason is
unspecified and note MAY be empty.
5.1 DenyReason
| Enumerator | Meaning |
|---|---|
CostModel |
Profitable only under a different cost model. |
Illegality |
The transform would be incorrect. |
AlreadyDone |
No-op; the goal already holds. |
Unsupported |
VIR does not implement it yet. |
ContradictsTrait |
Conflicts with a stronger fact. |
TooLarge |
Exceeds a size budget. |
NoTargetSupport |
The backend or target cannot express it. |
6. Request catalog
The v1 catalog contains 42 requests. Requests marked Strong are noted; all others are Soft.
6.1 Inlining and call edges (10)
| Request | Strength | Meaning |
|---|---|---|
Inline |
Soft | Callee is a candidate for inlining. |
AlwaysInline |
Strong | Callee must be inlined. |
NoInline |
Strong | Callee must not be inlined. |
TailCall |
Soft | Prefer tail-call formation. |
MustTail |
Strong | Tail-call formation is required. |
NoTail |
Strong | Tail-call formation must not occur. |
Devirtualize |
Soft | Call target is expected to resolve to one target. |
ColdCall |
Soft | Call is unlikely; optimize for size. |
LikelyCall |
Soft | Call is likely executed. |
UnlikelyCall |
Soft | Call is unlikely executed. |
6.2 Loops (12)
| Request | Strength | Meaning |
|---|---|---|
Vectorize |
Soft | Vectorize the loop. |
Unroll(factor) |
Soft | Unroll by factor. |
Interleave(count) |
Soft | Interleave count iterations. |
Peel(count) |
Soft | Peel count iterations. |
Distribute |
Soft | Distribute loop bodies. |
Fuse |
Soft | Fuse adjacent loops. |
Jam |
Soft | Fuse loops by jamming. |
Unswitch |
Soft | Unswitch loop-invariant conditions. |
Rotate |
Soft | Rotate the loop. |
LICM |
Soft | Explicit request to hoist loop-invariant code (an action). |
LoopInvariant |
Soft | A proven invariant property VIR may exploit (a property). |
MustProgress |
Soft | The loop is guaranteed to make progress. |
LICM and LoopInvariant are distinct: one requests an action, the other asserts a property. Both
MUST be retained by HIR.
6.3 Memory effects (13)
| Request | Strength | Meaning |
|---|---|---|
NoAlias |
Soft | The value aliases no other relevant value. |
Restrict |
Soft | The value may be marked restrict. |
NonNull |
Soft | The value is not null. |
Align(n) |
Soft | The value is aligned to n. |
ReadOnly |
Soft | Region-level read-only fact. |
WriteOnly |
Soft | Region-level write-only fact. |
NoRead |
Soft | Primitive fact on an access edge: no read occurs. |
NoWrite |
Soft | Primitive fact on an access edge: no write occurs. |
NoCapture |
Soft | The value is not captured by a callee. |
Dereferenceable(n) |
Soft | At least n bytes are dereferenceable. |
Constant |
Soft | The value is immutable. |
NoUndef |
Soft | Reserved; meaningful only on the LLVM path. |
Prefetch |
Soft | Insert a prefetch hint. |
NoRead/NoWrite are primitives on an access edge; ReadOnly/WriteOnly are region-level
facts derived from them. All four are retained, and their scope difference is part of their
meaning.
6.4 Control flow, assumptions, and optimization control (7)
| Request | Category | Strength | Meaning |
|---|---|---|---|
HotPath |
Control flow | Soft | Block is likely executed. |
ColdPath |
Control flow | Soft | Block is unlikely executed. |
Unreachable |
Control flow | Soft | Execution never reaches this point. |
NoReturn |
Control flow | Soft | The call never returns. |
Assume(pred) |
Assumptions | Soft | pred holds at this point. |
Range(lo, hi) |
Assumptions | Soft | The value lies in [lo, hi]. |
NoOptimize |
Optimization control | Strong | The region must not be rewritten. Requested only when the frontend sets complex. |
HIR MUST NOT issue NoOptimize unless complex is set on the node. The resulting region is an
optimization barrier (see 005 — Lowering).
7. Relationships
Relationships are directed edges that HIR leaves for the non-constant world. They are traceable up and down to a terminal node.
7.1 Relationship flags
| Flag | Meaning |
|---|---|
is_offspring |
The edge cannot be traced up. |
is_ancestor |
The edge cannot be traced down. |
is_common |
Branch point; a consumer must choose a direction. |
has_siblings |
The node connects down to two or more nodes. |
is_apex |
The node participates in a cycle (a → b → c → d → a), usually from polymorphism. |
An apex SHOULD normally be ironed out by HIR. A residual apex after the HIR cascade typically arises from polymorphism, and a consumer that walks into one MUST handle the cycle rather than loop forever.
7.2 Relation attributes
Each relationship edge carries a small attribute set:
| Attribute | Meaning |
|---|---|
was_changed |
The related value changed since the relationship was recorded. |
is_mutable |
The related value is mutable. |
is_pointer |
The relationship passes through a pointer. |
8. Query API semantics
The trait query interface is the only channel through which VIR obtains facts. Its guaranteed semantics are:
- A query MUST return the trait values valid for the node's current epoch. A query against a node whose trait is stale for the current epoch MUST fail rather than return stale data.
- A query MUST NOT trigger analysis or re-derivation. It is a read of already-derived facts.
- Reading
const_valuewhen absent MUST be reported as "not compile-time known" and MUST NOT be interpreted as zero or any other value. - Walking a relationship in the available direction(s) MUST terminate at a terminal node or report that the walk hit an apex cycle.
- A consumer MUST NOT invent, weaken, or generalize a fact absent from the trait. VIR must not assume a property that no trait states.
- Query results MUST be deterministic: for a fixed module and epoch, the same query returns the same result regardless of thread count.
9. Representation
Representation has two parts: an embedded dense Trait struct on every node is primary, so
common attributes are read without indirection; an optional sparse side-channel holds rare or
frontend-extensible attributes and MAY be absent. A consumer MUST observe the same logical
trait regardless of which part carries it. The dense struct is the reference representation; the
side-channel is an optimization, not a semantic difference.
10. Staleness
Traits are annotated in place, so a rewrite can invalidate dependent traits. Staleness is resolved with epoch/generation counters:
- Each node carries an epoch. A trait is valid only for the node's current epoch.
- A rewrite that changes a trait MUST advance the affected node's epoch.
- A rewrite that can affect a related node MUST mark that relationship dirty and propagate the invalidation along the relationship edge.
- A derived trait computed from a node whose epoch has advanced MUST be recomputed before it is consumed.
- Epoch counters MUST be sufficient to keep parallel HIR workers from reading stale traits. This is why the model survives parallel passes.
11. Request conflict resolution
Requests from independent attributes can conflict. Resolution is normative:
- Strength ordering.
StrongbeatsSoft. When a Strong and a Soft request conflict, the Strong request wins and the Soft request is discarded without a diagnostic. - Conflicting Strongs. When two Strong requests conflict, HIR MUST emit a diagnostic and MUST reject the frontend suggestion. The derived, authoritative request wins.
- Canonical opposing pairs are
{Inline, AlwaysInline}against{NoInline}, and{TailCall, MustTail}against{NoTail}. In the named pairsInline/NoInlineandTailCall/NoTail, theNo*member is Strong, so those resolve by rule 1; the corresponding Strong-versus-Strong pairs (AlwaysInline/NoInline,MustTail/NoTail) resolve by rule 2. - A rejected suggestion MUST NOT silently disappear; the diagnostic and a remark MUST record the rejection.
12. Invariants
- Every node carries exactly one
Trait. - Every
RequestResultdenial carries both aDenyReasonand a non-emptynote. - A derived attribute is authoritative; consumers MUST NOT re-derive it.
const_valueis present if and only ifis_comptimeholds for the node.- No consumer reads a trait whose epoch is not the node's current epoch.
- The trait vocabulary is versioned independently of the textual IR format (see 013 — Build & Packaging).
13. Example
For y = x + 4 where HIR proves x is compile-time 4, the node x carries is_comptime,
is_constant, and const_value = 4 with attr_source = derived; the fold marks downstream nodes
is_comptime and issues an Inline request when the call is proven small. A frontend that had
suggested is_comptime = false on x receives a warning and sees HIR overwrite it. The full
walkthrough is normative in 014 — Worked Example.