Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Format Specifiers

The cudafe++ diagnostic system uses a custom format specifier language -- not printf -- to expand parameterized error messages. The expansion engine is process_fill_in (sub_4EDCD0, 1,202 decompiled lines in error.c), called by write_message_to_buffer (sub_4EF620, 159 lines) during template string expansion. Each diagnostic record carries a linked list of typed fill-in entries that supply the actual values -- type nodes, entity pointers, strings, integers, source positions -- which the format engine renders into the final message text.

This page documents the specifier syntax, the fill-in kind system, entity-kind dispatch, suffix options, numeric indexing, and the labeled fill-in mechanism.

Specifier Syntax

When write_message_to_buffer walks an error template string (looked up from off_88FAA0[error_code]), it recognizes three format constructs:

SyntaxMeaningExample
%%Literal % character"100%% complete"
%XY...ZnFill-in specifier: letter X, options Y...Z, index n%nfd2, %sq1, %t
%[label]Named label fill-in reference%[class_or_struct]

Positional Specifier Parsing

The parser (sub_4EF620, error.c:4703) processes %XY...Zn specifiers as follows:

// After seeing '%', read next char as specifier letter
char spec_letter = template[pos + 1];      // 'T', 'd', 'n', 'p', 'r', 's', 't', 'u'
pos += 2;

// Collect option characters (a-z, A-Z) into buffer, max 29
int opt_count = 0;
char options[30];
while (true) {
    char c = template[pos];
    if (c >= '0' && c <= '9') {
        // Trailing digit = fill-in index (1-based)
        fill_in_index = c - '0';
        break;
    }
    if ((c & 0xDF) < 'A' || (c & 0xDF) > 'Y') {
        // Not a letter -- end of specifier, index defaults to 1
        fill_in_index = 1;
        break;
    }
    options[opt_count++] = c;
    if (opt_count > 29)
        assertion_handler("error.c", 4739,
            "write_message_to_buffer",
            "construct_text_message:",
            "too many option characters");
    pos++;
}
options[opt_count] = '\0';

process_fill_in(diagnostic_record, spec_letter, options, fill_in_index);

The maximum of 29 option characters is enforced by an assertion. In practice, specifiers use 0--3 option characters.

Fill-In Kinds

The specifier letter maps to a fill-in kind value through a switch on (letter - 84) in process_fill_in (sub_4EDCD0, error.c:4297):

LetterASCIIletter - 84KindPayload TypeDescription
%T8406Type node pointerType name, uppercase rendering ("<int, float>")
%d100160int64Signed decimal integer
%n110264Entity node pointerEntity/symbol name with rich formatting
%p112282Source position cookieSource file + line reference
%r114307byte + pointerTemplate parameter reference
%s115313const char*Plain string
%t116325Type node pointerType name, lowercase rendering ("int")
%u117331uint64Unsigned decimal integer

Any other letter triggers the assertion: "process_fill_in: bad fill-in kind" (error.c:4297).

Usage Frequency Across 3,795 Templates

Measured across all error message templates in off_88FAA0:

SpecifierOccurrencesTypical Context
%s~470String fragments: attribute names, keyword text, flag names
%t~241Type names in mismatch diagnostics
%sq~233Quoted string fragments in CUDA cross-space messages
%n~179Entity names: function, variable, class, template
%p~76Source positions: "declared at line N of file.cu"
%d~60Numeric values: counts, limits, sizes
%T~40Type template parameter lists
%u~20Unsigned counts
%r~10Template parameter back-references

Fill-In Entry Layout

Each fill-in entry is a 40-byte node allocated from a pool (qword_106B490) or heap by alloc_fill_in_entry (sub_4F2DE0):

OffsetSizeFieldDescription
04kindFill-in kind (0--7, from specifier letter mapping)
41used_flagSet to 1 when consumed during expansion
53(padding)--
88nextNext fill-in in linked list
168+payloadUnion, varies by kind (see below)

Payload Layout by Kind

Kind 0 (decimal, %d) / Kind 1 (unsigned, %u) / Kind 3 (string, %s) / Kind 5 (type, %t) / Kind 6 (type, %T):

OffsetSizeField
168value -- int64 for kind 0/1, const char* for kind 3, type node pointer for kind 5/6

Kind 2 (position, %p):

OffsetSizeField
168position_cookie -- initialized to qword_126EFB8 (current source position) at allocation time

Kind 4 (entity name, %n):

OffsetSizeField
168entity_ptr -- pointer to entity node
244scope_index -- initialized to 0xFFFFFFFF (invalid)
281full_qualification_flag
291original_name_flag
301parameter_list_flag
311template_function_flag
321definition_flag
331alternate_original_flag
341template_only_flag

Kind 7 (%r):

OffsetSizeField
161param_byte
177(padding)
248template_scope_ptr

Fill-In Linked List

Fill-in entries attach to the diagnostic record as a singly-linked list:

  • Head pointer: diagnostic record offset 184 (fill_in_list_head)
  • Tail pointer: diagnostic record offset 192 (fill_in_list_tail)

When process_fill_in searches for a matching entry, it walks the list from head, looking for the first entry where node->kind == requested_kind. If the specifier includes an index (e.g., %t2), it skips index - 1 matching entries before consuming the target:

const __m128i *node = *(diagnostic + 184);   // fill_in_list_head
if (!node)
    goto fill_in_not_found;

while (node->kind != requested_kind || --index > 0) {
    node = node->next;                        // offset 8
    if (!node)
        goto fill_in_not_found;
}

node->used_flag = 1;                          // mark consumed (offset 4)
// proceed with kind-specific rendering

If no matching entry is found, process_fill_in triggers an assertion with a diagnostic message identifying the missing fill-in: "specified fill-in (%X, N) not found for error string: \"...\"" (error.c:4317).

After all format specifiers have been expanded, construct_text_message (sub_4EF9D0) iterates the entire fill-in list and asserts that every entry has used_flag == 1. An unconsumed fill-in triggers: "construct_text_message: not all fill-ins used for error string: \"...\"" (error.c:4781).

Numeric Indexing

When a template string must reference multiple fill-ins of the same kind, a trailing digit selects which one:

SpecifierMeaning
%tFirst type fill-in (index 1, default)
%t1First type fill-in (index 1, explicit)
%t2Second type fill-in (index 2)
%n1First entity name fill-in
%n2Second entity name fill-in
%sq1First string fill-in, quoted
%sq2Second string fill-in, quoted

The index is a single digit 0--9. Index 0 behaves identically to index 1 (the counter is pre-decremented before comparison). In practice, most templates use indices 1 and 2; a few use up to 3.

Real template example (CUDA cross-space call, error 3499):

calling a __device__ function(%sq1) from a __host__ function(%sq2) is not allowed

Here %sq1 and %sq2 are both kind 3 (string) with option q (quoted), selecting the first and second string fill-ins respectively. The caller attaches two string fill-ins -- the called function's name and the calling function's name.

Suffix Options

String Options (%s)

The %s specifier accepts only one option character: q for quoted output.

FormRendering
%sRaw string: foo
%sqQuoted string: "foo"

The q option wraps the string in double-quote characters (") and applies colorization if enabled (quote category, code 6 = bold). Any other option character on %s triggers: "process_fill_in: bad option" (error.c:4364).

Multiple q characters are permitted syntactically (the parser loops over all option chars validating each is q) but have no additional effect -- only one layer of quoting is applied.

Entity Name Options (%n)

The %n specifier accepts a rich set of option suffixes that control how an entity is rendered. Options are processed left-to-right, setting flags on the fill-in entry's flag bytes (offsets 28--34):

OptionFlag ByteEffect
foffset 28 (full_qualification)Show fully-qualified name with namespace/class scope chain
ooffset 29 (original_name)Omit the entity kind prefix (suppress "function ", "variable ", etc.)
poffset 30 (parameter_list)Show function parameter types in signature
toffset 31 + offset 28Show template arguments AND full qualification (sets both flags)
aoffset 29 + offset 33Show original name AND alternate/accessibility info
doffset 32 (definition)Append declaration location: " (declared at line N of file.cu)"
Toffset 34 (template_only)Show template specialization context: " (from translation unit ...)"

Options can be combined. Common combinations from the error template table:

SpecifierRendering Example
%nfunction "foo"
%no"foo" (no kind prefix)
%nffunction "ns::cls::foo" (fully qualified)
%nfdfunction "ns::cls::foo" (declared at line 42 of bar.cu)
%ntfunction "ns::cls::foo<int>" (full + template args)
%npfunction "foo" [with parameters shown]
%nTfunction "foo" (from translation unit bar.cu)
%na"foo" based on template argument(s) ...

No Options for Other Kinds

The %d, %u, %p, %t, %T, and %r specifiers reject all option characters:

if (*options != '\0')
    assertion_handler("error.c", 4372,
        "process_fill_in",
        "process_fill_in: bad option", NULL);

Kind-Specific Rendering

Kind 0 -- Signed Decimal (%d)

Renders the 64-bit signed integer payload using snprintf(buf, 20, "%lli", value), then writes the result to the output buffer. The 20-character buffer accommodates the full range of int64_t values including the sign.

Kind 1 -- Unsigned Decimal (%u)

Formats the payload through sub_4F63D0, which renders the unsigned 64-bit value into a dynamically-sized string buffer.

Kind 2 -- Source Position (%p)

Calls sub_4F6820 (form_source_position) with the position cookie from the fill-in payload. The rendering includes:

  • File name (via sub_5B15D0 for display formatting)
  • Line number
  • Contextual text supplied by the caller through three string arguments (prefix, suffix, end-of-source fallback)

The caller passes context strings like " (declared ", ")", "(at end of source)" to frame the position reference. When the position resolves to line 0 or the file is "-" (stdin), alternate formats are used.

Kind 3 -- String (%s / %sq)

Without the q option, writes the string pointer payload directly to the output buffer via strlen + sub_6B9CD0 (buffer append).

With the q option, wraps the string in double quotes with colorization:

if (colorization_active)
    emit_escape(buffer, 6);       // quote color (bold)
write_char(buffer, '"');
write_string(buffer, payload);
if (colorization_active)
    emit_escape(buffer, 1);       // reset
write_char(buffer, '"');

Kind 5 -- Type, Lowercase (%t)

Renders the type node through the type formatting subsystem. The rendering pipeline:

  1. Set byte_10678FA = 1 (name lookup kind = type display mode)
  2. Write opening "
  3. Call sub_600740 (format type for display) with the type node and the entity formatter callback table (qword_1067860)
  4. Write closing "
  5. Check via sub_7BE9C0 if the type has an "aka" (also-known-as) desugared form
  6. If yes, append ' (aka "desugared_type")' -- comparing the rendered forms to avoid redundant output when they are identical

The aka check compares the rendered text of the original type against the desugared type. If they produce identical strings (same length, same content via strncmp), the aka suffix is suppressed by truncating the buffer back to the pre-aka position.

Kind 6 -- Type, Uppercase (%T)

Renders a type template argument list in angle brackets:

write_string(buffer, "\"<");
// Walk the template argument linked list
for (arg = payload; arg != NULL; arg = arg->next) {
    if (arg->kind != 3)   // skip pack expansion markers
        format_template_argument(arg, &entity_formatter);
    if (arg->next && arg->next->kind != 3)
        write_string(buffer, ", ");
}
write_string(buffer, ">\"");

Template argument entries with kind == 3 (at byte offset +8) are pack-expansion markers and are skipped during rendering.

Kind 7 -- Template Parameter Reference (%r)

Renders a template parameter by looking up the parameter entity through sub_5B9EE0 (entity lookup by scope + index). If found and non-null, renders via sub_4F3970 (unqualified entity name). Otherwise, falls back to sub_6011F0 (generic template parameter formatting).

Entity Kind Dispatch (%n)

When processing %n specifiers, process_fill_in reads the entity kind byte at offset 80 of the entity node and dispatches to kind-specific rendering logic. The function first resolves through projection indirection: if entity_kind == 16 (typedef), it follows the pointer at entity->info_ptr->pointed_to; if entity_kind == 24 (resolved namespace alias), it follows entity->info_ptr.

The dispatch handles 25 entity kind values (0--24, with gaps at 14/15/16/24 handled as special cases):

Entity KindValueKind Label StringIndex in off_88FAA0Rendering Logic
keyword0(none -- literal "keyword")--Write keyword ", then the keyword's name string from entity->name_sym->name
concept1(from table)1462Simple: write kind label + quoted name
constant template parameter2"constant" or "nontype"--Check template parameter subkind: type_kind 14 with subkind 2 = "nontype", else "constant"
template parameter3(from table)1464 or 1465Check whether the template parameter is a type parameter (type_kind != 14) → index 1465, else 1464
class4(from table, CUDA-aware)1466--1468CUDA mode: 1467 or 1468 (class vs struct); non-CUDA: 1466
struct5(same as class)1466--1468Same dispatch as class, differentiated by v46 != 5
enum6(from table)1472Simple: write kind label + quoted name
variable7"variable" or "handler parameter"1474 or 1475Check handler-parameter flag (offset 163, bit 0x40). If set: "handler parameter" (index 1474). If variable is a structured binding (offset 162, bit 1): use index 2937. Otherwise: "variable" (index 1475) with optional template context
field8"field" or "member"1480 or 1481CUDA C++ mode: "member" (index 1480); C mode: "field" (index 1481)
member9"member"1480Always "member" with optional template context from scope chain
function10"function" or "deduction guide"1478 or 2892Check linkage kind (offset 166 == 7): deduction guide → index 2892. Otherwise "function" (1478). Walk qualified type chain to strip cv-qualifiers
function overload11(same as function)1478 or 2892Same dispatch as function (case 10), merged in the switch
namespace12(from table)1463Simple: write kind label + quoted name
label13(none)--Write quoted name only, no kind prefix, no type info
typedef (indirect variable)14"variable"1475Dereferences through entity->info_ptr->pointed_to and renders as variable
typedef (indirect function)15"function"1478Dereferences through entity->info_ptr, extracts function entity + routine info
typedef16----Assertion: "form_symbol_summary: projection of projection kind" (error.c:2020). Should have been resolved before dispatch
using declaration17(from table)1479Simple: write kind label + quoted name
parameter18"parameter"1473Simple: write "parameter" + quoted name with type info
class (anonymous/unnamed)19(from table)1469--1471 or 1889Multiple sub-cases: anonymous class bit 0x40 → index 1469; class-template with bit 0x02 → index 1470; deduction_guide bit → index 1889; else index 1471
function template20"function template"1485 (lambda) or kind labelLambda function (offset 189, bit 0x20): index 1485 with scope entity. Otherwise: "function template" with type and parameter info
variable template21(from table)2750Simple: write kind label + quoted name
alias template22(from table)3050Simple: write kind label + quoted name
concept template23(from table)1482Simple: write kind label + quoted name
resolved namespace alias24----Assertion: "form_symbol_summary: projection of projection kind" (same as kind 16). Should have been resolved

Any entity kind value outside 0--24 (excluding the gaps that trigger assertions) hits the default case: "form_symbol_summary: unsupported symbol kind" (error.c:2023).

Entity Rendering Pipeline

For entity kinds that produce a fully-formatted name (most non-trivial cases), the rendering proceeds through these stages:

1. Write entity kind label string (e.g., "function ")
   └── sub_6B9EA0(buffer, kind_label_string)
   └── sub_6B9CD0(buffer, " ", 1)

2. Open quote
   └── Optional colorization: sub_4ECDD0(buffer, 6)   // quote color
   └── sub_6B9CD0(buffer, "\"", 1)

3. Render type prefix (if has_type_info and full_qualification)
   └── sub_5FE8B0(type_node, 0, 1, 0, 0, &entity_formatter)

4. Render qualified or unqualified name
   ├── With template context:  sub_737A00(entity, &entity_formatter)
   └── Without template context: sub_4F3970(entity)

5. Render function parameters (if applicable)
   ├── Full parameter types: sub_5FB270(type, 0, 0, &entity_formatter)
   └── Simple type suffix:   sub_6016F0(type, &entity_formatter)

6. Close quote
   └── sub_6B9CD0(buffer, "\"", 1)
   └── Optional colorization: sub_4ECDD0(buffer, 1)   // reset

7. Append accessibility info (if 'a' option)
   └── " based on template argument(s) "
   └── sub_5FA660(template_arg_list, 0, &entity_formatter)

8. Append declaration location (if 'd' option)
   └── sub_4F6820(position, diag, " (declared ", ")", "(at end of source)")

9. Append translation unit info (if 'T' option)
   └── " (from translation unit <filename>)"

The original_name flag (o option) suppresses steps 1 and 3, rendering only the bare quoted name without a kind prefix or type qualification. The full_qualification flag (f option) enables step 3 and uses sub_737A00 for fully-qualified name rendering in step 4. The parameter_list flag (p option) forces step 5 to include full parameter-type rendering.

Template Context in Entity Names

When dword_126E274 (show template arguments) is non-zero and the entity has template context, the renderer can walk up the template scope chain:

  1. Access the entity's routine info (for functions: offset 88 → offset 192 → offset 16)
  2. Check for the instantiated-from entity (offset 104 of scope info, guarded by !(offset_176 & 1))
  3. If found, use the instantiated-from entity as the display target
  4. For class templates (entity_kind == 20): walk the template parameter chain, rendering <param1, param2, ...> with pack-expansion markers (...) for variadic parameters

CUDA-Specific Entity Rendering

Several entity kinds have CUDA-aware rendering paths:

  • Class/struct (kinds 4/5): When dword_126EFB4 == 2 (CUDA C++ mode) and the entity has an anonymous flag (offset 161, bit 0x80), rendering jumps to the anonymous-class handler (kind 19) instead
  • Field (kind 8): In CUDA C++ mode, the kind label is "member" (index 1480); in C mode, it is "field" (index 1481)
  • Class/struct label selection: In CUDA C++ mode, the kind label index is always 1467; in non-CUDA mode, it depends on whether the entity is class vs struct

Labeled Fill-Ins (%[label])

The %[label] syntax references a named fill-in from the label table at off_D481E0. This mechanism allows error templates to include conditional text fragments that vary based on language mode or compilation context.

Label Table Structure

off_D481E0 is an array of 24-byte entries (3 pointers per entry):

OffsetSizeFieldDescription
08nameLabel name string (e.g., "class_or_struct")
88condition_ptrPointer to condition flag (dword)
164true_indexString table index when *condition_ptr != 0
204false_indexString table index when *condition_ptr == 0

Label Lookup Algorithm

// write_message_to_buffer, error.c:4714
char *label_start = template + pos + 2;      // skip "%["
char *label_end = strchr(template + pos + 1, ']');
if (!label_end)
    assertion_handler("error.c", 4714, "write_message_to_buffer", NULL, NULL);

size_t label_len = label_end - label_start;

// Walk off_D481E0 table
struct label_entry *entry = off_D481E0;
while (entry->name) {
    if (strncmp(entry->name, label_start, label_len) == 0) {
        // Found matching label
        int string_index;
        if (*entry->condition_ptr)
            string_index = entry->true_index;
        else
            string_index = entry->false_index;

        if (string_index > 3794)
            error_text_invalid_code();     // sub_4F2D30

        // Expand the referenced string directly into the buffer
        const char *text = off_88FAA0[string_index];
        write_to_buffer(buffer, text, strlen(text));
        pos = label_end + 1;
        break;
    }
    entry++;   // advance by 24 bytes
}

if (!entry->name) {
    // Label not found -- fatal
    fprintf(stderr, "missing fill-in label: %.*s\n", label_len, label_start);
    assertion_handler("error.c", 430,
        "get_label_fill_in_entry",
        "get_label_fill_in_entry: no label fill-in found", NULL);
}

The label table entries reference string indices in the same off_88FAA0 table used for error messages. This allows a single error template to produce different text depending on compilation mode -- for example, using "class" vs "struct" based on a language-mode flag, or "virtual" vs "" based on a feature flag.

The label text is written directly to the output buffer without further format specifier processing -- labels cannot contain nested % specifiers.

Output Buffer

All rendering targets the global message text buffer at qword_106B488:

  • Initial allocation: 0x400 bytes (1 KB) via sub_6B98A0
  • Dynamic growth: sub_6B9B20 doubles the buffer when capacity is exceeded
  • String append: sub_6B9CD0(buffer, data, length) -- the workhorse write function
  • String write: sub_6B9EA0(buffer, string) -- convenience wrapper (calls strlen + sub_6B9CD0)

The entity display callback infrastructure at qword_1067860 allows the type/name formatting subsystem to write to the same buffer through an indirect call:

VariableAddressPurpose
qword_10678600x1067860Entity formatter callback (set to sub_5B29C0)
qword_10678700x1067870Entity formatter output buffer (set to qword_106B488)
byte_10678F10x10678F1C mode flag (dword_126EFB4 == 1)
byte_10678F40x10678F4Pre-C++11 flag
byte_10678FA0x10678FAName lookup kind (saved/restored around type rendering)
byte_10678FE0x10678FEEntity display flags (saved/restored around %n processing)
byte_10679020x1067902Type desugaring mode flag (saved/restored around %t aka rendering)

Colorization Interaction

When dword_126ECA4 (colorization active) is non-zero, the format engine inserts ANSI escape sequences around quoted names and type references:

ContextColor CodeANSI SequenceVisual
Opening quote (")6 (quote)\033[01mBold
Closing quote (")1 (reset)\033[0mNormal
Type rendering context(inherited)--Inherits from diagnostic severity color

The escape sequences are emitted by sub_4ECDD0(buffer, color_code). The color codes correspond to the categories parsed from EDG_COLORS / GCC_COLORS environment variables during initialization.

Function Map

AddressName (Recovered)SizeRole
0x4EDCD0process_fill_in1,202 linesCore format specifier expansion
0x4EF620write_message_to_buffer159 linesTemplate string walker, % parser
0x4F2DE0alloc_fill_in_entry41 linesPool allocator for 40-byte fill-in nodes
0x4F2D30error_text_invalid_code12 linesAssert on invalid error code (> 3794)
0x4F2930assertion_handler101 lines__noreturn, 5,185 callers
0x4F3480format_assertion_message~100 linesMulti-arg string builder for assertion text
0x4F6820form_source_position~130 linesRender %p source position with file + line
0x4F3970format_entity_unqualified--Render unqualified entity name
0x4F39E0format_entity_with_template--Render entity with template args + accessibility
0x737A00format_qualified_name--Render fully-qualified name through scope chain
0x5FE8B0format_type_with_qualifiers--Render type with cv-qualifiers for %n prefix
0x5FB270format_function_parameters--Render function parameter type list
0x6016F0format_simple_type--Render simple type suffix
0x600740format_type_for_display--Render type for %t specifier
0x7BE9C0has_desugared_type--Check if type has an "aka" form
0x5FA660format_template_argument_list--Render template argument list for %n a option
0x5FA0D0format_template_argument--Render single template argument for %T
0x5B9EE0lookup_entity_by_scope--Entity lookup for %r template parameter
0x4F63D0format_unsigned_decimal--Render unsigned integer for %u
0x6B9CD0buffer_append--Write bytes to dynamic buffer
0x6B9EA0buffer_write_string--Write null-terminated string to buffer
0x4ECDD0emit_colorization_escape--Emit ANSI escape sequence

Cross-References