Itanium C++ ABI: Contracts Specification¶
1. Introduction¶
This document specifies the Itanium C++ ABI for the Contracts feature introduced in C++26. The primary goal is to define a portable, stable, and extensible interface between compilers and runtime libraries for handling contract violations.
When a contract assertion fails at runtime, the compiler generates a call to one
of the ABI’s contract violation entry points. Each entry point takes a single
void* argument pointing to the head of a __cxa_contract_data_block
chain—a linked list of self-describing data blocks containing violation
information. The entry point constructs a
std::contracts::contract_violation object from the chain and dispatches to
the appropriate violation handler.
Both GCC (libstdc++) and Clang (libc++) implement this specification with byte-compatible ABI headers, enabling cross-compiler interoperability: a violation handler compiled with one compiler/standard library can process violations generated by the other.
2. Overview¶
2.1 Entry Point Responsibilities¶
The contract violation entry points have the following responsibilities:
Optionally prepend an override block to the data chain, encoding the assertion kind, evaluation semantic, and detection mode implied by the entry point’s name.
Construct a
std::contracts::contract_violationobject from the (possibly augmented) data block chain.Check for a per-assertion local handler (P3400) in the chain and call it if present. If the local handler returns “handled”, skip the global handler.
Otherwise, call the user-replaceable global handler
::handle_contract_violation, or the default handler if no replacement is provided.If the evaluation semantic is
enforce, terminate the program (viastd::abort()) after the handler returns.
2.2 Design Goals¶
The ABI is designed to be:
- Stable
Future changes cannot break existing code.
- Extensible
The ABI cannot preclude future extensions to the C++ standard or vendor-specific extensions.
- Efficient
Minimal impact on code generation and code size. Each contract call site reduces to
lea+callwith a single pointer argument.- Portable
Works across different compilers (GCC, Clang) and standard libraries (libc++, libstdc++). Cross-compiler interoperability is demonstrated with both implementations shipping identical ABI wire-format headers.
- Composable
The same data block chain format is used by compiler-generated static data, stack-allocated override blocks, and the P3290 manual violation API, enabling uniform chain-walking without special cases.
3. Data Types¶
3.1 Type Representations¶
Standard Type |
Itanium Type |
Underlying |
|---|---|---|
|
|
See § 3.2 |
|
|
|
|
|
|
|
|
|
The __cxa_ types define exact underlying types, exact enumerator
values, and exact size and alignment. The compiler maps from the standard
library’s representation to the ABI representation at the contract site.
The runtime maps back when the std::contracts::contract_violation
accessors are called.
3.2 Source Location¶
struct __cxa_source_location {
const char* file_name;
const char* function_name;
unsigned line;
unsigned column;
};
This layout matches std::source_location::__impl in both libstdc++ and
libc++.
3.3 Enumerations¶
Each enumeration has an underlying type of uint8_t and fixed
enumerator values independent of any values used by the standard
library’s corresponding types. Value 0x00 is reserved for
the unspecified case in each enumeration, consistent with the default
value when a field is absent from the data chain.
3.3.1 __cxa_assertion_kind_t¶
enum __cxa_assertion_kind_t : uint8_t {
CXA_AK_UNSPECIFIED = 0x00,
CXA_AK_PRE = 0x01,
CXA_AK_POST = 0x02,
CXA_AK_ASSERT = 0x03,
CXA_AK_MANUAL = 0x04,
CXA_AK_CASSERT = 0x05,
CXA_AK_POST_CAPTURE = 0x06,
CXA_AK_IMPLICIT = 0x07,
};
Identifies the kind of contract assertion. CXA_AK_PRE,
CXA_AK_POST, and CXA_AK_ASSERT correspond to the three assertion
kinds defined by the C++ standard. CXA_AK_MANUAL is used by the
P3290 manual violation API (handle_enforced_contract_violation and
handle_observed_contract_violation). CXA_AK_CASSERT is reserved
for C-compatible assertions. CXA_AK_POST_CAPTURE distinguishes the
postcondition capture initialization point from the postcondition
predicate evaluation point. CXA_AK_IMPLICIT identifies an implicit
contract assertion inserted by the implementation to guard a case of
core-language undefined behaviour (P3100).
3.3.2 __cxa_evaluation_semantic_t¶
enum __cxa_evaluation_semantic_t : uint8_t {
CXA_ES_UNSPECIFIED = 0x00,
CXA_ES_IGNORE = 0x01,
CXA_ES_OBSERVE = 0x02,
CXA_ES_ENFORCE = 0x03,
CXA_ES_QUICK_ENFORCE = 0x04,
};
Identifies the evaluation semantic of the contract assertion.
CXA_ES_ENFORCE indicates the program shall terminate (via
std::abort()) after the violation handler returns. CXA_ES_OBSERVE
indicates execution continues. CXA_ES_IGNORE indicates the contract is not checked (no
entry point is called). CXA_ES_QUICK_ENFORCE emits a trap
directly—no entry point, no data, no handler.
3.3.3 __cxa_detection_mode_t¶
enum __cxa_detection_mode_t : uint8_t {
CXA_DM_UNSPECIFIED = 0x00,
CXA_DM_PREDICATE_FALSE = 0x01,
CXA_DM_EVALUATION_EXCEPTION = 0x02,
};
Identifies how the violation was detected. CXA_DM_PREDICATE_FALSE
indicates the contract predicate evaluated to false.
CXA_DM_EVALUATION_EXCEPTION indicates the predicate exited via an
exception.
3.4 Field IDs¶
Each field ID uniquely identifies a piece of information and its encoding. Multiple field IDs can map to the same logical field with different representations (see § 7.3). Accessors search for any field ID belonging to their logical field and take the first match.
3.4.1 Standard Fields¶
Standard field IDs have values less than 0x40. For these fields,
the descriptor data entry contains a byte offset into the containing
data block.
ID |
Name |
Data type at offset |
Default if absent |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Field 0x02 (comment) contains the source text of the contract
predicate. Field 0x03 (message) contains a computed diagnostic
string (P3099), distinct from the predicate text.
Fields 0x04 through 0x06 support P3400 assertion-control objects:
field 0x04 is a type-erased trampoline to the label’s
handle_contract_violation facet, field 0x05 is a trampoline to
the label’s query facet, and field 0x06 is the address of the
assertion-control object instance.
Field 0x0B supports a lazily-populated diagnostic report: the value
at that offset is an __cxa_contract_report_populator (§ 3.6)
pairing a producer-supplied populate callback with an opaque
ctx. The runtime calls populate(ctx) only when
contract_violation::report() is invoked; if the field is absent,
report() returns nullptr without ever calling into a producer.
This lets a producer such as a sanitizer defer building a (potentially
expensive) report string until a handler actually asks for it, and skip
the cost entirely for handlers that never do. Like other fields,
first-found-wins resolution (§ 5.3) applies: only the first
CXA_FIELD_REPORT found in the chain is used.
IDs 0x0C through 0x3F are reserved for future standard fields.
3.4.2 Extended Fields¶
Extended field IDs have values 0x40 and above. For these fields,
the descriptor data entry contains a direct const void* pointer
rather than a byte offset.
Range |
Purpose |
|---|---|
|
Vendor-specific extensions |
|
Reserved for future standard use |
3.5 Function Pointer Types¶
// Local handler: returns 1 if handled, 0 to continue dispatch.
using __cxa_local_handler_fn_t
= int (*)(const void* label_ptr,
const void* contract_violation_ptr);
// Query function: calls the label's query() method.
using __cxa_query_fn_t
= void* (*)(const void* label_ptr,
const void* key,
size_t index);
Both function pointers are stored as link-time relocations in constant data, not filled at runtime.
3.6 Report Populator¶
// Lazily produces a producer-owned diagnostic string. The runtime
// calls ``populate(ctx)`` at most once per accessor call and does not
// cache the result; a producer that wants repeat calls to be cheap
// must cache internally (e.g. keyed off ``ctx``).
struct __cxa_contract_report_populator {
const char* (*populate)(const void* ctx);
const void* ctx;
};
The returned const char* is a NUL-terminated string, owned by the
producer, valid for the lifetime of the contract violation (i.e., at
least as long as the data block chain referencing it remains live). The
runtime never takes ownership of, copies, or frees it.
Unlike the plain function-pointer fields of § 3.5, CXA_FIELD_REPORT’s
value at its offset is this two-word struct, not a bare function pointer,
so accessors retrieve it with the pointer-to-structured-data helper
(__cxa_find_field_ptr, § 5.4) rather than
__cxa_find_field_value. The ctx member lets the producer carry
state (for example, a pointer to a buffer it renders into on first call)
without resorting to global or thread-local variables.
4. Entry Points¶
All entry points have C language linkage (extern "C") and take a
single void* argument pointing to the head of a data block chain.
4.1 Universal Entry Points¶
extern "C"
void __cxa_contract_violation(void* data);
extern "C"
void __cxa_contract_violation_noexcept(void* data) noexcept;
The universal entry points read the evaluation semantic from the data
chain. They cannot be marked [[noreturn]] because the semantic might
be observe.
The _noexcept variant wraps handler dispatch in a noexcept
barrier: if any handler throws, std::terminate() is called.
These entry points are used by the P3290 manual violation API and in any context where the compiler does not know the evaluation semantic at compile time.
4.2 Specialized Entry Points¶
Specialized entry points encode the assertion kind, evaluation semantic, and detection mode in the function name:
__cxa_contract_violation_{kind}_{semantic}_{mode}[_noexcept]
Where:
kind:
pre,post,assert,post_capture,implicitsemantic:
observe,enforcemode:
pf(predicate_false),ex(evaluation_exception)_noexcept suffix: present on the
noexceptvariant
Enforce variants are [[noreturn]]. Each kind/semantic/mode
combination has both a throwing and a noexcept variant, yielding
5 kinds x 2 semantics x 2 modes x 2 noexcept = 40 specialized entry
points.
Observe entry points (return after handler):
Entry Point |
Attributes |
|---|---|
|
(none) |
|
|
|
(none) |
|
|
Enforce entry points (terminate via std::abort() on handler return;
std::terminate() if a handler throws at a _noexcept variant):
Entry Point |
Attributes |
|---|---|
|
|
|
|
|
|
|
|
Where {kind} is one of pre, post, assert,
post_capture, implicit. Additional kinds can be added by defining
new entry points without affecting existing ones.
quick_enforce does not use an entry point—the compiler emits a
trap directly.
Total: 34 entry points (2 universal + 32 specialized).
4.3 Override Block Mechanism¶
When a specialized entry point is called, it constructs a small override block on the stack that encodes the kind, semantic, and mode values implied by its name. This override block is prepended to the caller’s data chain:
// Stack-allocated override block (exposition only)
struct __override_block_t {
const __cxa_descriptor_table_t* descriptor; // offset 0
const __cxa_contract_data_block* next; // offset 8
uint8_t kind; // offset 16
uint8_t semantic; // offset 17
uint8_t mode; // offset 18
};
The override block uses a static const descriptor table with three
entries for fields 0x07 (kind), 0x08 (semantic), and 0x09
(mode). The block’s next pointer links to the original chain
provided by the caller.
Because the runtime uses first-found-wins resolution (§ 5.3), the override values take precedence over any kind/semantic/mode that might also appear in the original chain. This means the compiler does not need to encode kind, semantic, or mode in the static data block—the entry point name determines them.
For the universal entry points, no override block is constructed; the chain is used directly, and the semantic is read from the chain data.
4.4 Call Site Code Generation¶
Each contract call site reduces to a single pointer argument and a call:
; x86-64 example: precondition violation, enforced semantic
lea rdi, [rip + .L_data_block]
call __cxa_contract_violation_pre_enforce_pf
This is 17 bytes per site—53% smaller than assert() (36 bytes).
No per-translation-unit wrappers are needed. The compiler selects the
appropriate entry point based on the assertion kind, evaluation semantic,
and detection mode known at compile time.
4.5 Handler Symbol Resolution¶
The global violation handler is resolved via a weak symbol:
// In the ABI runtime
__attribute__((weak))
void handle_contract_violation(
const std::contracts::contract_violation&);
The runtime checks whether the weak symbol is defined. If it is, the
user-provided handler is called. If not, the runtime calls
std::contracts::invoke_default_contract_violation_handler.
This mechanism matches p2900r14’s description of replacement “in the same
way it would be done for the global operator new and
operator delete.” Users define their handler with the matching
signature and it overrides at link time. No special compiler treatment
or thunking is needed.
4.6 Assert Integration Entry Point¶
The C assert macro integration (p3290) is expanded by the library
<cassert> header, not by the compiler, so it does not use the
void*-data entry points of § 4.1–4.2. Instead the macro expands to a
call to a single shared entry point:
extern "C++" [[noreturn]] void
__cxa_handle_cassert_violation(
const char* comment,
std::source_location loc = std::source_location::current()) noexcept;
Both libstdc++ and libc++ expand assert(EXPR) (when NDEBUG is not
defined and ASSERT_USES_CONTRACTS is defined) to a call to this symbol,
so a single implementation is shared across standard libraries. The default
argument captures the source_location at the macro-expansion site. The
entry point:
Constructs a data block reporting
assertion_kind = CXA_AK_CASSERT,evaluation_semantic = CXA_ES_ENFORCE, anddetection_mode = CXA_DM_PREDICATE_FALSE, withcommentset to the stringized macro argument.Invokes the contract-violation handler via the universal entry point.
Terminates via
std::abort()on any completion of the handler—a normal return (through the enforce post-action) or an escaping exception. To achieve the latter, the entry point wraps the handler dispatch intry { ... } catch (...) { std::abort(); }. This differs from the general_noexceptentry points (§ 6.1), where a throwing handler callsstd::terminate(): the assert integration alwaysabort()``s, preserving classic ``assert()termination semantics irrespective of how the handler exits.
This is a library entry point (extern "C++", taking comment and
source_location); it is distinct from, and additional to, the 34
compiler-facing data-block entry points of § 4.1–4.2.
5. Data Format¶
5.1 Descriptor Table Types¶
5.1.1 __cxa_vendor_id_t¶
enum __cxa_vendor_id_t : uint8_t {
CXA_VENDOR_GENERIC = 0x0,
CXA_VENDOR_GCC = 0x1,
CXA_VENDOR_CLANG = 0x2,
CXA_VENDOR_MSVC = 0x3,
};
Identifies the vendor that produced the descriptor table. Value 0x0
indicates a generic table conforming to this specification. The vendor
ID enables fast-path field reading for known layouts; unknown vendor IDs
fall back to the generic chain-walking algorithm.
5.1.2 __cxa_descriptor_data_t¶
union __cxa_descriptor_data_t {
uintptr_t offset; // standard fields (ID < 0x40)
const void* pointer; // extended fields (ID >= 0x40)
};
For standard fields (field_ids[i] < 0x40), data[i].offset is
the byte offset from the start of the containing __cxa_contract_data_block
to the field’s value.
For extended fields (field_ids[i] >= 0x40), data[i].pointer is
a direct pointer to the data (requires relocation).
5.1.3 __cxa_descriptor_table_t¶
struct __cxa_descriptor_table_t {
uint8_t header;
uint8_t num_entries;
uint8_t field_ids[];
// Followed by padding to align __cxa_descriptor_data_t
// Followed by __cxa_descriptor_data_t data[num_entries]
};
The header byte packs the table version (high 4 bits) and vendor ID
(low 4 bits):
uint8_t table_version() const { return header >> 4; }
uint8_t vendor_id() const { return header & 0x0F; }
This specification defines table version 1.
The field_ids member is a flexible array of num_entries bytes,
followed by padding (if necessary) to achieve the alignment required by
__cxa_descriptor_data_t, followed by num_entries elements of type
__cxa_descriptor_data_t.
The data() accessor handles alignment:
const __cxa_descriptor_data_t* data() const {
uintptr_t p = (uintptr_t)(field_ids + num_entries);
p = (p + alignof(__cxa_descriptor_data_t) - 1)
& ~(alignof(__cxa_descriptor_data_t) - 1);
return (const __cxa_descriptor_data_t*)p;
}
Descriptor tables shall be emitted with static storage duration in a read-only data section.
The runtime shall iterate the descriptor table and ignore any
field_ids values it does not recognize. Fields may appear in any
order. A field ID shall not appear more than once in a single descriptor
table.
5.2 Data Block¶
struct __cxa_contract_data_block {
const __cxa_descriptor_table_t* descriptor; // offset 0
const __cxa_contract_data_block* next; // offset sizeof(void*)
// Field data follows (layout described by descriptor,
// byte offsets relative to the start of this struct).
};
Each data block is one node in a linked list of violation data. The
descriptor pointer identifies which fields are present and where they
are. The next pointer links to the next block in the chain (or
nullptr for the tail).
Field data follows the next pointer in the same allocation. Byte
offsets in the descriptor’s data entries are measured from the start of
the __cxa_contract_data_block struct, not from the end of the header.
5.3 Data Block Chain¶
The void* data argument to every entry point points to the head of a
linked list of data blocks. The runtime walks the chain head-to-tail.
For each logical field, it takes the first occurrence found
(first-found-wins).
This design enables:
- Override blocks
A specialized entry point prepends a small stack-allocated block that overrides kind/semantic/mode, reusing the compiler-generated block(s) for all other fields.
- Shared per-assertion data
The compiler emits a single static const data block per assertion. All evaluation and detection paths for the same assertion reuse this block.
- Stack-constructed blocks
The P3290 manual violation API constructs a data block on the stack with kind, semantic, mode, source location, and comment, then calls the universal entry point.
5.4 Chain-Walking Field Lookup¶
const void*
__cxa_find_field(const __cxa_contract_data_block* chain,
const uint8_t* ids,
uint8_t num_ids);
Walks the chain head-to-tail. For each block, searches the block’s
descriptor for any field ID in the ids array. Returns a pointer to
the field data within the block where the first match was found, or
nullptr if no match exists in the entire chain.
For standard fields (ID < 0x40), the returned pointer is computed as:
(const char*)block + descriptor->data()[i].offset
For extended fields (ID >= 0x40), the returned pointer is:
descriptor->data()[i].pointer
Two typed convenience helpers are provided:
// Scalar value with default
template<typename T>
T __cxa_find_field_value(
const __cxa_contract_data_block* chain,
uint8_t field_id,
T default_value);
// Pointer to structured data
template<typename T>
const T* __cxa_find_field_ptr(
const __cxa_contract_data_block* chain,
uint8_t field_id);
5.5 Compiler-Emitted Data Block Layouts¶
The compiler emits static const data blocks in .rodata. The
descriptor table describes which fields are present. Four layout
variants are used, depending on whether the assertion has a local handler
and/or query function:
Basic (3 descriptor entries):
Offset |
Type |
Field |
|---|---|---|
0 |
|
Descriptor pointer |
|
|
Next pointer ( |
|
|
File name |
|
|
Function name |
|
|
Line number |
|
|
Column number |
|
|
Comment (predicate text) |
|
|
Message (P3099 diagnostic) |
Descriptor field IDs: 0x01 (source_location), 0x02 (comment),
0x03 (message).
Label (5 descriptor entries): Basic layout plus
local_handler (0x04) and label_ptr (0x06).
Query (5 descriptor entries): Basic layout plus
query_function (0x05) and label_ptr (0x06).
Full (6 descriptor entries): Basic layout plus local_handler
(0x04), query_function (0x05), and label_ptr (0x06).
The descriptor table header for each variant encodes version 1 and the
compiler’s vendor ID (CXA_VENDOR_GCC or CXA_VENDOR_CLANG).
Typically one descriptor table instance is shared across all data blocks
of the same variant within a translation unit.
6. Dispatch and APIs¶
6.1 Core Dispatch Logic¶
All entry points share a common dispatch flow:
1. Construct contract_violation from chain pointer.
2. Local handler dispatch (if present):
a. Find local_handler (field 0x04) and label_ptr (field 0x06).
b. If local_handler != nullptr:
- Call: result = local_handler(label_ptr, &cv)
- If result == 1 (handled): skip global handler.
- If result == 0: fall through to global handler.
3. Global handler dispatch:
- If weak symbol ::handle_contract_violation is defined, call it.
- Otherwise, call invoke_default_contract_violation_handler(cv).
4. Post-handler action:
- If semantic == enforce: call std::abort().
- If semantic == observe: return to caller.
An enforced violation whose handler returns normally is terminated with
std::abort() rather than std::terminate(). abort() is not
affected by a user-installed std::terminate handler, giving predictable,
uniform contract-termination regardless of program state.
For _noexcept entry point variants, steps 2-3 are wrapped in a
noexcept barrier. If a handler exits via an exception, std::terminate()
is called (with the exception still active, so that a std::terminate
handler can inspect it). This is the one contract-termination path that
uses std::terminate() rather than std::abort().
For non-noexcept variants, exceptions from handlers propagate normally.
[[noreturn]] on enforce variants is still correct: the function
either terminates or an exception propagates, but it never returns via
normal control flow.
6.2 Override Block Construction¶
Specialized entry points construct an override block on the stack and dispatch:
// Exposition only
void __dispatch_with_override(
void* data, uint8_t kind, uint8_t semantic,
uint8_t mode, bool is_noexcept)
{
__override_block_t ovr;
ovr.descriptor = &__override_desc;
ovr.next = (__cxa_contract_data_block*)data;
ovr.kind = kind;
ovr.semantic = semantic;
ovr.mode = mode;
__cxa_cv_dispatch(
(__cxa_contract_data_block*)&ovr,
semantic, is_noexcept);
}
// Example: pre + enforce + predicate_false
[[noreturn]] extern "C" void
__cxa_contract_violation_pre_enforce_pf(void* data) {
__dispatch_with_override(
data, CXA_AK_PRE, CXA_ES_ENFORCE,
CXA_DM_PREDICATE_FALSE, false);
__builtin_unreachable();
}
The override descriptor table is a static constant with three entries
for field IDs 0x07, 0x08, 0x09 at offsets 16, 17,
18 respectively (immediately after the two pointer-sized header
fields of the override block).
6.3 Compiler-Emitted Code¶
The compiler emits per-assertion data blocks in .rodata and calls
the appropriate entry point directly:
// --- Per-TU descriptor table (in .rodata) ---
static const struct {
uint8_t header;
uint8_t num_entries;
uint8_t field_ids[3];
uint8_t padding[3];
__cxa_descriptor_data_t data[3];
} __gcc_contract_desc_basic = {
(1u << 4) | CXA_VENDOR_GCC, // version=1, vendor=GCC
3,
{ CXA_FIELD_SOURCE_LOCATION, CXA_FIELD_COMMENT,
CXA_FIELD_MESSAGE },
{ 0, 0, 0 },
{
{ 2 * sizeof(void*) }, // source_location
{ 2 * sizeof(void*) + sizeof(__cxa_source_location) }, // comment
{ 2 * sizeof(void*) + sizeof(__cxa_source_location)
+ sizeof(void*) }, // message
}
};
// --- Per-assertion static data (in .rodata) ---
static const struct {
const __cxa_descriptor_table_t* descriptor;
const __cxa_contract_data_block* next;
__cxa_source_location loc;
const char* comment;
const char* message;
} __contract_data_foo_pre = {
(__cxa_descriptor_table_t*)&__gcc_contract_desc_basic,
nullptr,
{ "foo.cpp", "foo", 42, 0 },
"x > 0",
"",
};
// --- At the contract site ---
int foo(int x) pre(x > 0) {
// Compiler emits (when predicate is false):
// lea rdi, [rip + __contract_data_foo_pre]
// call __cxa_contract_violation_pre_enforce_pf
// ...
}
6.4 contract_violation as Chain Wrapper¶
std::contracts::contract_violation holds a single pointer to the
head of a data block chain. All accessors walk the chain to find their
field values:
class contract_violation {
const __cxa_contract_data_block* __chain_;
public:
assertion_kind kind() const noexcept {
auto v = __cxa_find_field_value<uint8_t>(
__chain_, CXA_FIELD_ASSERTION_KIND, 0);
return static_cast<assertion_kind>(v);
}
const char* comment() const noexcept {
auto p = __cxa_find_field_ptr<const char*>(
__chain_, CXA_FIELD_COMMENT);
return p ? *p : "";
}
source_location location() const noexcept {
auto p = __cxa_find_field_ptr<__cxa_source_location>(
__chain_, CXA_FIELD_SOURCE_LOCATION);
if (p)
return source_location::__create_from_pointer(p);
return {};
}
bool is_terminating() const noexcept {
auto sem = __cxa_find_field_value<uint8_t>(
__chain_, CXA_FIELD_EVALUATION_SEMANTIC, 0);
return sem == CXA_ES_ENFORCE
|| sem == CXA_ES_QUICK_ENFORCE;
}
// P3400 query dispatch
void* query_control_object(
const void* key, size_t index = 0) const
{
auto fn = __cxa_find_field_value<__cxa_query_fn_t>(
__chain_, CXA_FIELD_QUERY_FUNCTION, nullptr);
auto label = __cxa_find_field_value<const void*>(
__chain_, CXA_FIELD_LABEL_PTR, nullptr);
if (fn && label)
return fn(label, key, index);
return nullptr;
}
};
There is no construction step: the object is the chain pointer.
This means contract_violation is valid as long as the chain is
valid—which it always is during handler dispatch, since compiler-
generated blocks have static storage duration and override/stack blocks
live on the entry point’s stack frame.
6.5 P3290 Manual Violation API¶
The P3290 library functions construct a data block on the stack and call the universal entry point:
void handle_enforced_contract_violation(
const char* comment, source_location loc)
{
struct {
const __cxa_descriptor_table_t* descriptor;
const __cxa_contract_data_block* next;
__cxa_source_location location;
const char* comment;
uint8_t kind;
uint8_t semantic;
uint8_t mode;
} block = {
&__p3290_desc,
nullptr,
{ loc.file_name(), loc.function_name(),
loc.line(), loc.column() },
comment,
CXA_AK_MANUAL,
CXA_ES_ENFORCE,
CXA_DM_UNSPECIFIED,
};
__cxa_contract_violation((void*)&block);
}
The P3290 descriptor table has 5 entries: source_location (0x01),
comment (0x02), assertion_kind (0x07), evaluation_semantic
(0x08), and detection_mode (0x09).
Because the stack-constructed block has the same format as compiler- generated blocks, the same chain-walking algorithm processes both. No special cases are needed.
6.6 P3400 Local Handler and Query Dispatch¶
When a contract assertion has an associated assertion-control object
(P3400), the compiler emits a trampoline function and stores it in the
data block as field 0x04 (local handler).
The trampoline casts the void* arguments back to their typed
counterparts and calls the label’s handle_contract_violation method:
// Compiler-generated trampoline (internal linkage)
static int __contract_label_handler(
const void* label_ptr,
const void* cv_ptr)
{
auto& label = *(const MyLabel*)label_ptr;
auto& cv = *(const contract_violation*)cv_ptr;
label.handle_contract_violation(cv);
return 1; // handled
}
The trampoline function pointer and the label instance address are stored as link-time relocations in the static data block—no runtime initialization needed.
The query function (field 0x05) follows the same pattern but is
demand-driven: it is only invoked when the handler calls
contract_violation::query_control_object().
7. ABI Evolution Rules¶
7.1 What Can Never Change¶
Invariant |
Reason |
|---|---|
|
Every entry point and accessor depends on this layout. |
Field ID values for existing fields ( |
Compiled into |
ABI enum values for existing enumerators |
Same. |
Entry point signatures (single |
Existing object files call them. |
First-found-wins resolution order |
Override block semantics depend on it. |
|
Shared with |
7.2 What Can Change Safely¶
Change |
Mechanism |
Impact on existing code |
|---|---|---|
New field ID |
New |
Old runtime ignores; old compiler does not emit. |
New entry point |
New function |
Old code does not call it. |
New enum value |
New |
Old runtime passes through or maps to unspecified. |
New vendor ID |
New nibble value |
Old runtime falls back to generic field walk. |
New descriptor table version |
Increment |
Old runtime falls back to generic field walk. |
New representation of existing logical field |
New field ID for same logical field (see § 7.3) |
Old runtime uses old field ID; new runtime prefers new. |
7.3 Representation Evolution via Field IDs¶
A logical field can have multiple field IDs representing different
encodings. For example, a future compact source location encoding would
get a new field ID (e.g., 0x0B). The location() accessor would
search for {0x01, 0x0B} and take the first match.
A compiler targeting new runtimes emits only the new ID. A compiler targeting broad compatibility emits both: the new ID in an earlier block, the old ID in a later block. First-found-wins gives the new runtime the new encoding and the old runtime the old encoding.
7.4 Version Skew Handling¶
New compiler, old runtime: The compiler can use the universal entry point (always available) if targeting older runtimes. New field IDs are ignored by old runtimes. New specialized entry points cause link errors against old runtimes; the compiler falls back to the universal entry point (controlled by a target ABI version flag).
Old compiler, new runtime: New runtimes handle all old entry points and field IDs. Old fields and entry points are supported forever.
Mixed TU compilation (GCC + Clang):
Each TU’s .rodata has descriptor tables with its own vendor ID. The
runtime handles both via the generic field walk. The weak
::handle_contract_violation resolves to one definition (whichever
standard library is linked). contract_violation accessors are from
whichever standard library is linked but implement the same chain-walking
algorithm.
8. Runtime Library Packaging and Symbol Ownership¶
The wire format (§ 3–6) says nothing about which library provides each runtime symbol. This section specifies that packaging, because it is what makes cross-compiler and cross-language interoperability work in practice: which symbols are shared, which are per-standard-library, and how a mixed program links without duplicate or missing definitions.
8.1 Two-Tier Library Model¶
The runtime is partitioned into two tiers, split along the C/C++ boundary:
Tier 1 — the core library (**``libcontracts``).**
A pure-C library whose only external dependency is the C library
(libc). It contains everything that can be implemented without a C++
runtime: chain walking, the dispatch core, the non-noexcept entry
points, the C accessor functions, and the C contract-check helpers. It is
the only contracts library that a pure-C translation unit needs, and the
only contracts library that ever legitimately appears on a link line
shared between two toolchains.
Tier 2 — the C++ runtime tier.
The C++-only symbols, provided by each C++ standard library (libstdc++ /
libc++, or their experimental component while the feature is experimental).
It contains everything that requires C++: the std::contracts::
contract_violation member functions, the diagnostic default handler, the
P3290 C++ API, the assert-integration entry point (§ 4.6), and the
noexcept entry-point variants (which need a C++ exception-handling
barrier to call std::terminate()).
A program uses exactly one Tier 2 implementation (you never link both libstdc++ and libc++), and both Tier 2 implementations export the same ABI symbol names, so an object file compiled by one toolchain resolves its Tier 2 references against whichever standard library the final program links.
The tier boundary is the same on every toolchain; only Tier 1’s membership is a shared, cross-toolchain contract.
8.2 The Core Library (libcontracts)¶
libcontracts is a single interchangeable library across toolchains:
SONAME
libcontracts.so.1.Version node
LIBCONTRACTS_1.0.Depends only on
libc.Exports exactly the symbol set below; a build that exports more or fewer symbols under
LIBCONTRACTS_1.0is non-conforming.
Because the SONAME, version node, and exported set are fixed here, a system
may carry a single libcontracts (e.g. a distribution package) that both
compilers’ implicit -lcontracts resolve against; each toolchain’s own
build is an ABI-identical fallback. The two implementations are maintained
independently (§ 8.6); this specification — not shared source — is the
authority that keeps them compatible.
Membership rule. A symbol belongs to Tier 1 if and only if it is
implementable in standard C with no dependency beyond libc and requires
no C++ exception handling.
Exported symbols (LIBCONTRACTS_1.0):
Symbol(s) |
Role |
|---|---|
|
Chain-walking field lookup (§ 5.4). |
|
Universal (non- |
|
All non- |
|
Non-terminating dispatch primitives; called by the Tier 2
|
|
C accessor functions (comment, file, function, line, column, kind, semantic, detection_mode, is_terminating). |
|
P3290 C API. |
|
Compiler-emitted C contract-check helpers. |
Weak references (declared, not defined, by libcontracts ):
__handle_contract_violation— the C-linkage (optionally-replaced) global handler (§ 8.4).__contract_invoke_default_handler— the C-linkage entry to the default handler, used by the dispatch fallback (§ 8.4).No other undefined references beyond
libc.
8.3 The C++ Runtime Tier¶
Provided by each C++ standard library. While contracts remain an
experimental feature in a toolchain, these symbols may live in that
toolchain’s experimental library component (e.g. libstdc++exp); when the
feature becomes non-experimental they may move into the primary C++ runtime.
Either placement is conforming, because a program links exactly one C++
runtime and these symbols carry the same ABI names regardless of which
library object hosts them.
Membership rule. A symbol belongs to Tier 2 if it names a C++ type in its signature, provides a C++-language facility, or requires a C++ exception-handling barrier.
Symbols provided by every conforming C++ runtime (same ABI names in libstdc++ and libc++):
Symbol(s) |
Role |
|---|---|
|
The |
|
Accessors used by C++ handlers (§ 6.4). |
|
Standard C++ API (P2900): invokes the diagnostic default handler, regardless of any replacement (§ 8.4). Callable by a user’s custom handler to emit the default diagnostics. |
|
C-linkage form of the above: same always-default behavior, callable from C (a C violation handler, or the Tier 1 dispatch fallback). |
|
Implementation default of the replaceable global handler; a user definition overrides it (§ 8.4). |
|
Assert-integration entry point (§ 4.6). |
P3290 C++ API
( |
The C++ manual-violation API (§ 6.5). |
8.4 Handler Resolution Across Tiers¶
Two distinct handler concepts must both be reachable, and the two-tier model keeps them as separate symbols:
The (optionally-replaced) global handler.
Named by the C-linkage symbol __handle_contract_violation. It is what
the dispatch invokes for a violation. A user who defines
handle_contract_violation with the required signature causes the compiler
to emit __handle_contract_violation as a strong C-linkage alias (same
address, zero overhead). Absent a user definition, the symbol is undefined
and the dispatch falls through to the default (below). Using the C-linkage
alias makes this the single cross-language, cross-toolchain handler entry: a
handler written in C or C++, compiled by either toolchain, is reached
identically.
The default handler. The implementation-provided diagnostic behavior, reachable regardless of any replacement through two symbols with identical semantics:
std::contracts::invoke_default_contract_violation_handler(const contract_violation&)— the standard C++ API (P2900). A user’s custom handler calls this to emit the default diagnostics in addition to (or instead of) its own behavior.__contract_invoke_default_handler(const void*)— the C-linkage form of the same function. It exists so that pure-C code can reach the default: a C violation handler (D4299) can call it, andlibcontractsuses it for its dispatch fallback.
Neither of these ever dispatches to a user replacement; they always run the default. This is the functionality P2900 requires and must not be collapsed into the replaceable global handler.
Dispatch resolution in the libcontracts core (pure C), after the
local-handler check (§ 6.1):
/* In libcontracts (Tier 1): both are weak references. */
__attribute__((weak)) void __handle_contract_violation(const void* cv);
__attribute__((weak)) void __contract_invoke_default_handler(const void* cv);
if (&__handle_contract_violation)
__handle_contract_violation(cv); /* user replacement */
else if (&__contract_invoke_default_handler)
__contract_invoke_default_handler(cv); /* implementation default (Tier 2) */
else
__contract_minimal_default(cv); /* Tier 1 libc-only fallback */
Resolution outcomes:
User-provided handler.
__handle_contract_violationis defined (strong alias); the dispatch calls it. The handler may itself callinvoke_default_contract_violation_handler/__contract_invoke_default_handlerto also emit the default diagnostics.No user handler, C++ runtime linked.
__handle_contract_violationis undefined;__contract_invoke_default_handler(Tier 2) is defined and runs the default diagnostic handler.No user handler, no C++ runtime (pure C). Both weak references are unresolved;
libcontractscalls its ownlibc-only__contract_minimal_default, which prints a diagnostic tostderr(in the manner of__assert_fail) and returns. Termination for enforcing semantics is still performed by the post-handler action in the dispatch core (§ 6.1), not by the fallback.
The passed pointer is the data-block chain head; a C++ handler (or
invoke_default_contract_violation_handler) receives it as const
contract_violation& because contract_violation is the chain pointer
(§ 6.4), so the C and C++ signatures are ABI-identical.
The noexcept entry points (Tier 2) do not re-implement dispatch: they
call the Tier 1 __contract_dispatch_core /
__dispatch_with_override_core primitives inside a C++ try { ... }
catch (...) { std::terminate(); } barrier. This is the only place the
noexcept barrier of § 6.1 is realized, and it is necessarily in the
C++ tier.
8.5 Implicit Linkage¶
Conforming toolchains link both tiers automatically from the feature flags,
so neither C nor C++ contract users need explicit -l options:
A C compilation that enables C contracts links
libcontracts(Tier 1) automatically.A C++ compilation that enables contracts (or selects the C++26 dialect) links its C++ runtime’s contract component (Tier 2) automatically, which in turn brings in
libcontracts(Tier 1).
The relative link order is Tier 2 before Tier 1 (the C++ runtime depends on
libcontracts).
8.6 Independent Implementations¶
libcontracts follows the same source-management model as the rest of the
Itanium C++ ABI runtime (libsupc++ for GCC, libc++abi for LLVM):
two independent implementations conforming to one written specification,
not shared source. The implementations are separately licensed (GCC:
GPLv3 + Runtime Library Exception; LLVM: Apache-2.0 with LLVM exception),
which precludes byte-identical source in any case. This specification is
the single source of truth; a cross-compiler, cross-language interop test
suite is the drift detector.
8.7 Symbol-Ownership Invariants¶
The following are added to the invariants of § 7.1 (things that can never change without an ABI break):
Invariant |
Reason |
|---|---|
Tier 1 membership (the |
It is the shared library on mixed-toolchain link lines; adding or removing a symbol changes what every consumer resolves. |
|
Interchangeability across toolchains depends on identical soname and version node; a bump must be coordinated between all implementations. |
|
Cross-language handler resolution depends on this single symbol and its weak/strong discipline. |
The default handler is reachable regardless of replacement via
|
P2900 requires a custom handler to be able to invoke the default diagnostics; C code needs a C-linkage form of the same. |
Tier 2 symbols carry identical ABI names across all C++ runtimes |
Objects from one toolchain resolve Tier 2 references against another toolchain’s linked standard library. |