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_violation object 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 (via std::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 + call with 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

std::source_location

__cxa_source_location

See § 3.2

std::contracts::assertion_kind

__cxa_assertion_kind_t

uint8_t

std::contracts::evaluation_semantic

__cxa_evaluation_semantic_t

uint8_t

std::contracts::detection_mode

__cxa_detection_mode_t

uint8_t

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

0x01

CXA_FIELD_SOURCE_LOCATION

__cxa_source_location

{"", "", 0, 0}

0x02

CXA_FIELD_COMMENT

const char*

""

0x03

CXA_FIELD_MESSAGE

const char*

""

0x04

CXA_FIELD_LOCAL_HANDLER

__cxa_local_handler_fn_t

nullptr

0x05

CXA_FIELD_QUERY_FUNCTION

__cxa_query_fn_t

nullptr

0x06

CXA_FIELD_LABEL_PTR

const void*

nullptr

0x07

CXA_FIELD_ASSERTION_KIND

uint8_t (__cxa_assertion_kind_t)

0 (unspecified)

0x08

CXA_FIELD_EVALUATION_SEMANTIC

uint8_t (__cxa_evaluation_semantic_t)

0 (unspecified)

0x09

CXA_FIELD_DETECTION_MODE

uint8_t (__cxa_detection_mode_t)

0 (unspecified)

0x0A

CXA_FIELD_EXCEPTION_PTR

void*

nullptr

0x0B

CXA_FIELD_REPORT

__cxa_contract_report_populator

nullptr (no populator)

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

0x40 - 0x7F

Vendor-specific extensions

0x80 - 0xFF

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, implicit

  • semantic: observe, enforce

  • mode: pf (predicate_false), ex (evaluation_exception)

  • _noexcept suffix: present on the noexcept variant

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

__cxa_contract_violation_{kind}_observe_pf

(none)

__cxa_contract_violation_{kind}_observe_pf_noexcept

noexcept

__cxa_contract_violation_{kind}_observe_ex

(none)

__cxa_contract_violation_{kind}_observe_ex_noexcept

noexcept

Enforce entry points (terminate via std::abort() on handler return; std::terminate() if a handler throws at a _noexcept variant):

Entry Point

Attributes

__cxa_contract_violation_{kind}_enforce_pf

[[noreturn]]

__cxa_contract_violation_{kind}_enforce_pf_noexcept

[[noreturn]] noexcept

__cxa_contract_violation_{kind}_enforce_ex

[[noreturn]]

__cxa_contract_violation_{kind}_enforce_ex_noexcept

[[noreturn]] noexcept

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:

  1. Constructs a data block reporting assertion_kind = CXA_AK_CASSERT, evaluation_semantic = CXA_ES_ENFORCE, and detection_mode = CXA_DM_PREDICATE_FALSE, with comment set to the stringized macro argument.

  2. Invokes the contract-violation handler via the universal entry point.

  3. 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 in try { ... } catch (...) { std::abort(); }. This differs from the general _noexcept entry points (§ 6.1), where a throwing handler calls std::terminate(): the assert integration always abort()``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

const __cxa_descriptor_table_t*

Descriptor pointer

sizeof(void*)

const __cxa_contract_data_block*

Next pointer (nullptr)

2*sizeof(void*)

const char*

File name

3*sizeof(void*)

const char*

Function name

4*sizeof(void*)

unsigned

Line number

4*sizeof(void*) + 4

unsigned

Column number

5*sizeof(void*)

const char*

Comment (predicate text)

6*sizeof(void*)

const char*

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

__cxa_contract_data_block starts with descriptor* then next*

Every entry point and accessor depends on this layout.

Field ID values for existing fields (0x01–0x0A)

Compiled into .rodata in existing binaries.

ABI enum values for existing enumerators

Same.

Entry point signatures (single void* argument)

Existing object files call them.

First-found-wins resolution order

Override block semantics depend on it.

__cxa_source_location layout

Shared with source_location::__impl.

7.2 What Can Change Safely

Change

Mechanism

Impact on existing code

New field ID

New uint8_t value

Old runtime ignores; old compiler does not emit.

New entry point

New function

Old code does not call it.

New enum value

New uint8_t value

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.0 is 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

__cxa_find_field

Chain-walking field lookup (§ 5.4).

__cxa_contract_violation

Universal (non-noexcept) entry point (§ 4.1).

__cxa_contract_violation_{kind}_{observe,enforce}_{pf,ex}

All non-noexcept specialized entry points (§ 4.2).

__contract_dispatch_core, __dispatch_with_override_core

Non-terminating dispatch primitives; called by the Tier 2 noexcept wrappers (§ 8.4).

stdc_contract_violation_*

C accessor functions (comment, file, function, line, column, kind, semantic, detection_mode, is_terminating).

stdc_handle_{enforced,observed,quick_enforced}_contract_violation_explicit

P3290 C API.

__c_contract_check_{enforce,observe}

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

__cxa_contract_violation_noexcept and all __cxa_contract_violation_..._noexcept variants

The noexcept entry points (§ 4.1–4.2). Each wraps a Tier 1 dispatch primitive in try { ... } catch (...) { std::terminate(); } (§ 8.4); this is why they cannot live in the pure-C Tier 1.

std::contracts::contract_violation member functions (_ZNKSt9contracts18contract_violation*)

Accessors used by C++ handlers (§ 6.4).

std::contracts::invoke_default_contract_violation_handler

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.

__contract_invoke_default_handler (C-linkage)

C-linkage form of the above: same always-default behavior, callable from C (a C violation handler, or the Tier 1 dispatch fallback).

handle_contract_violation (weak default)

Implementation default of the replaceable global handler; a user definition overrides it (§ 8.4).

__cxa_handle_cassert_violation

Assert-integration entry point (§ 4.6).

P3290 C++ API (handle_{enforced,observed,quick_enforced}_contract_violation and their nothrow_t overloads)

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, and libcontracts uses 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_violation is defined (strong alias); the dispatch calls it. The handler may itself call invoke_default_contract_violation_handler / __contract_invoke_default_handler to also emit the default diagnostics.

  • No user handler, C++ runtime linked. __handle_contract_violation is 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; libcontracts calls its own libc-only __contract_minimal_default, which prints a diagnostic to stderr (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 LIBCONTRACTS_1.0 exported set of § 8.2)

It is the shared library on mixed-toolchain link lines; adding or removing a symbol changes what every consumer resolves.

libcontracts SONAME libcontracts.so.1 and version node LIBCONTRACTS_1.0

Interchangeability across toolchains depends on identical soname and version node; a bump must be coordinated between all implementations.

__handle_contract_violation is the C-linkage (optionally-replaced) global-handler entry point (weak reference in Tier 1; strong alias emitted by the compiler for a user handler)

Cross-language handler resolution depends on this single symbol and its weak/strong discipline.

The default handler is reachable regardless of replacement via invoke_default_contract_violation_handler (C++) and __contract_invoke_default_handler (C-linkage), both always-default

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.