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

IL Tree Walking

The IL tree walking framework is the backbone of every operation that must visit the complete IL graph: debug display, device code marking, IL serialization, and IL copying for template instantiation. The framework lives in il_walk.c (with entry-kind dispatch logic auto-generated from walk_entry.h). It provides a generic, callback-driven traversal engine consisting of two core functions: walk_file_scope_il (sub_60E4F0), which orchestrates the top-level iteration over all global entry-kind lists, and walk_entry_and_subtree (sub_604170), which recursively descends into a single entry's children according to the IL schema. Five global function-pointer slots allow each client to customize the walk's behavior without modifying the walker itself.

The framework follows a strict separation of traversal and action. The walker knows how to navigate the IL graph; the callbacks decide what to do at each node. This design enables the same walker to serve four fundamentally different purposes: pretty-printing, transitive-closure marking, pointer remapping during copy, and entry filtering during serialization.

Key Facts

PropertyValue
Source fileil_walk.c (EDG 6.6)
Header (auto-generated dispatch)walk_entry.h
Assert path/dvs/p4/build/sw/rel/gpgpu/toolkit/r13.0/compiler/drivers/compiler/edg/EDG_6.6/src/il_walk.c
Top-level file-scope walkersub_60E4F0 (walk_file_scope_il), 2043 lines
Recursive entry walkersub_604170 (walk_entry_and_subtree), 7763 lines / 42KB
Routine-scope walkersub_610200 (walk_routine_scope_il), 108 lines
Hash table resetsub_603B30 (clear_walk_hash_table), 23 lines
Anonymous union lookupsub_603FE0 (find_parent_var_of_anon_union_type), 127 lines
Entry kinds covered85 (switch cases 0--84)
Recursive self-calls~330 (in walk_entry_and_subtree)
Callback slots5 global function pointers

Callback Slot Architecture

The five callback slots are stored as global function pointers. Before any walk, the caller saves all five values, installs its own set, and restores the originals on exit. This save/restore discipline makes walks re-entrant -- a callback can itself trigger a nested walk with different callbacks.

AddressSlot NameSignaturePurpose
qword_126FB88entry_callbackentry_ptr(entry_ptr, entry_kind)Called for each entry visited; may return a replacement pointer
qword_126FB80string_callbackvoid(char_ptr, string_kind, byte_length)Called for each string field; string_kind is 24 (id_name), 25 (string_text), or 26 (other_text); byte_length is strlen+1 for kinds 24/26, field-based for kind 25
qword_126FB78pre_walk_checkint(entry_ptr, entry_kind)Called before descending into an entry; returns nonzero to skip the subtree
qword_126FB70entry_replaceentry_ptr(entry_ptr, entry_kind)Called to remap an entry pointer (used during IL copy to translate old pointers to new ones)
qword_126FB68entry_filterentry_ptr(entry_ptr, entry_kind)Called on linked-list heads to filter entries; returning NULL removes the entry from the list

The pre_walk_check slot is the only one whose return value controls flow: nonzero means "already handled, skip this subtree." The keep-in-il pass uses this to avoid revisiting already-marked entries (preventing infinite recursion on cyclic references). The entry_replace slot is used during IL copy operations to translate pointers from the source IL region to the destination region.

Walk State Globals

In addition to the five callback slots, four state variables track the walker's context:

AddressNameDescription
dword_126FB5Cis_file_scope_walk1 during walk_file_scope_il, 0 during walk_routine_scope_il
dword_126FB58is_secondary_il1 if the current scope belongs to the secondary IL region
dword_106B644current_il_regionToggles per IL region; used to stamp bit 2 of entry flags
dword_126FB60walk_mode_flagsBitmask controlling walk behavior (e.g., strip template info)

All four are saved and restored alongside the callback slots, making the entire walk context atomically swappable.

walk_file_scope_il (sub_60E4F0)

This is the central traversal entry point. Every operation that needs to visit the entire file-scope IL calls this function with its desired callbacks. It takes six arguments:

void walk_file_scope_il(
    entry_callback_t   a1,   // entry visitor (qword_126FB88)
    string_callback_t  a2,   // string visitor (qword_126FB80)
    entry_replace_t    a3,   // pointer remapper (qword_126FB70)
    entry_filter_t     a4,   // list filter (qword_126FB68)
    pre_walk_check_t   a5,   // pre-visit gate (qword_126FB78)
    int                a6    // walk_mode_flags (dword_126FB60)
);

Initialization

The function begins by saving all five callback slots and all four walk state variables, then installs the caller's values:

// Save current state
saved_entry_cb      = qword_126FB88;
saved_string_cb     = qword_126FB80;
saved_pre_walk      = qword_126FB78;
saved_entry_replace = qword_126FB70;
saved_entry_filter  = qword_126FB68;
saved_is_file_scope = dword_126FB5C;
saved_is_secondary  = dword_126FB58;
saved_il_region     = dword_106B644;
saved_mode_flags    = dword_126FB60;

// Install new callbacks
qword_126FB88 = a1;
qword_126FB80 = a2;
qword_126FB70 = a3;
qword_126FB68 = a4;
qword_126FB78 = a5;
dword_126FB60 = a6;
dword_126FB5C = 1;  // mark as file-scope walk

An assertion fires if pre_walk_check is NULL and the primary scope is in secondary IL (bit 1 of flags byte set):

if (!a5 && is_secondary)
    assert_fail("il_walk.c", 270, "walk_file_scope_il");

This prevents unguarded walks into secondary IL regions, which would produce incorrect results because secondary entries need canonical-entry delegation.

Walk Order

The function visits IL entries in a fixed, deterministic order. This order is significant for serialization (the IL binary format expects entries in this exact sequence) and for display (the --dump_il output follows this structure).

Phase 1: Primary scope (kind 23)

primary_scope = xmmword_126EB60[1];  // second qword of IL header

// If entry_replace callback exists, remap the scope pointer first
if (entry_replace)
    primary_scope = entry_replace(primary_scope, 23);

// Determine IL region flags from the scope's prefix byte
is_secondary_il = (*(primary_scope - 8) & 0x02) != 0;
current_il_region = ((*(primary_scope - 8) >> 2) ^ 1) & 1;

walk_entry_and_subtree(primary_scope, 23);

The scope entry (kind 23) is walked first because it is the root of the scope tree. Walking the scope recursively visits all nested scopes and their member lists.

Phase 2: Source file entries (kind 1)

for (entry = xmmword_126EB60[0]; entry; entry = entry->child_file) {
    if (entry_filter && !entry_filter(entry, 1))
        continue;  // filtered out
    walk_entry_and_subtree(entry, 1);
}

Source file entries form a linked list via offset +56 (child_file). Each entry holds the file name, full path, and name_as_written strings.

Phase 3: main_routine pointer and string entries

Before walking strings, the function remaps the main_routine pointer from the IL header:

// main_routine (qword_126EB70, IL header + 0x10)
if (entry_replace) {
    il_header.main_routine = entry_replace(il_header.main_routine, 11);
    // Also remap compiler_version through entry_replace
    compiler_version = entry_replace(compiler_version, 26);
}

Then two string entries from the IL header are walked as "other text" (kind 26):

// compiler_version (qword_126EB78, IL header + 0x18)
if (compiler_version) {
    if (trace_verbosity > 4)
        fprintf(s, "Walking IL tree, string entry kind = %s\n", "other text");
    if (string_callback)
        string_callback(compiler_version, 26, strlen(compiler_version) + 1);
}

// time_of_compilation (qword_126EB80, IL header + 0x20) -- same pattern
if (entry_replace)
    time_of_compilation = entry_replace(time_of_compilation, 26);
if (time_of_compilation) {
    if (string_callback)
        string_callback(time_of_compilation, 26, strlen(time_of_compilation) + 1);
}

Strings are walked with kind 26 (other_text) and the string callback receives the raw character pointer, the kind, and the length including the null terminator.

Phase 4: Orphaned entities list (kind 55)

for (entry = qword_126EBA0; entry; entry = entry->next) {
    if (entry_filter && !entry_filter(entry, 55))
        continue;
    walk_entry_and_subtree(entry, 55);
}

Kind 55 entries are orphaned entities -- declarations that lost their parent scope (e.g., after template instantiation cleanup). They are stored in a separate linked list headed at qword_126EBA0.

Phase 5: Global entry-kind lists (kinds 1--72)

The bulk of the walk iterates 45 global linked lists, one per entry kind. Each list head is stored at a fixed address in the 0x126E610--0x126EA80 range, with 16-byte spacing. The complete walk order, verified from the decompiled sub_60E4F0:

#Global AddressKindEntry Kind Name
1qword_126E6101source_file_entry
2qword_126E6202constant
3qword_126E6303param_type
4qword_126E6404routine_type_supplement
5qword_126E6505routine_type_extra
6qword_126E6606type
7qword_126E6707variable
8qword_126E6808field
9qword_126E6909exception_specification
10qword_126E6A010exception_spec_type
11qword_126E6B011routine
12qword_126E6C012label
13qword_126E6D013expr_node
14qword_126E6E014(reserved)
15qword_126E6F015(reserved)
16qword_126E70016switch_case_entry
17qword_126E71017switch_info
18qword_126E72018handler
19qword_126E73019try_supplement
20qword_126E74020asm_supplement
21qword_126E75021statement
22qword_126E76022object_lifetime
23qword_126E77023scope
24qword_126E7B027template_parameter
25qword_126E7C028namespace
26qword_126E7D029using_declaration
27qword_126E7E030dynamic_init
28qword_126E81033overriding_virtual_func
29qword_126E82034(reserved)
30qword_126E83035derivation_path
31qword_126E84036base_class_derivation
32qword_126E85037(reserved)
33qword_126E86038(reserved)
34qword_126E87039class_info
35qword_126E88040(reserved)
36qword_126E89041constructor_init
37qword_126E8A042asm_entry
38qword_126E8E046lambda
39qword_126E8F047lambda_capture
40qword_126E90048attribute
41qword_126E9D061template_param
42qword_126E9B059template_decl
43qword_126E9E062name_reference
44qword_126E9F063name_qualifier
45qword_126EA8072attribute (C++11)

Note the gaps in the walk order: kinds 24-26 (base_class, string_text, other_text), 31-32 (local_static_variable_init, vla_dimension), 43-45 (asm_operand, asm_clobber, reserved), and 49-58 (element_position through hidden_name) are skipped. These entry kinds are either embedded inline within parent entries, accessed only through the recursive descent of walk_entry_and_subtree, or have no file-scope lists. Also note that kinds 59 and 61 appear out-of-order (61 before 59) -- this is verified in the binary.

For each non-empty list, the walk applies the entry_replace callback (if present) to each entry before descending, and follows the next pointer (at offset -16 in the raw allocation, which is the next_in_list link in the entry prefix).

Phase 6: Special trailing lists

Three additional lists are walked after the main kind-indexed sequence:

// seq_number_lookup entries (kind 64) at qword_126EBE8
for (entry = qword_126EBE8; entry; entry = entry->next) {
    if (entry_filter) ...
    walk_entry_and_subtree(entry, 64);
}

// External declarations (kind 6) at qword_126EBE0
// -- uses entry_filter with kind 6 and follows offset +104 links

// Kind 83 entries at qword_126EC00
for (entry = qword_126EC00; entry; entry = entry->next) {
    if (entry_filter) ...
    walk_entry_and_subtree(entry, 83);
}

Cleanup

After all phases complete, the function restores all saved state:

dword_126FB5C = saved_is_file_scope;
dword_126FB58 = saved_is_secondary;
dword_106B644 = saved_il_region;
dword_126FB60 = saved_mode_flags;
qword_126FB88 = saved_entry_cb;
qword_126FB80 = saved_string_cb;
qword_126FB78 = saved_pre_walk;
qword_126FB70 = saved_entry_replace;
qword_126FB68 = saved_entry_filter;

If tracing is active (dword_126EFC8), the function emits trace-leave via sub_48AFD0.

walk_entry_and_subtree (sub_604170)

This is the recursive engine -- the second-largest function in the entire cudafe++ binary at 7763 lines / 42KB of decompiled code. It takes an entry pointer and its kind, then recursively walks every child entry according to the IL schema.

Entry Protocol

Before descending into any entry, the function executes a two-path check:

while (true) {
    if (pre_walk_check != NULL) {
        // Callback path: delegate decision to the callback
        if (pre_walk_check(entry, entry_kind))
            return;  // callback says skip
    } else {
        // Default path: check flags
        flags = *(entry - 8);

        // If not file-scope walk and entry has file-scope bit: skip
        if (!is_file_scope_walk && (flags & 0x01))
            return;

        // If entry's il_region bit matches current_il_region: skip
        if (((flags & 0x04) != 0) == current_il_region)
            return;

        // Stamp the entry's il_region bit to match current region
        *(entry - 8) = (4 * (current_il_region & 1)) | (flags & 0xFB);
    }

    // Trace output at verbosity > 4
    if (trace_verbosity > 4)
        fprintf(s, "Walking IL tree, entry kind = %s\n",
                il_entry_kind_names[entry_kind]);

    // Dispatch on entry kind
    switch ((char)entry_kind) { ... }
}

The while(true) loop structure exists because certain cases (particularly linked-list tails) use continue to re-enter the check with a new entry, avoiding redundant function-call overhead for tail chains.

The default-path flags check serves two purposes:

  1. Scope isolation: File-scope entries encountered during a routine-scope walk are skipped (they belong to the outer walk).
  2. Region tracking: The current_il_region toggle prevents visiting the same entry twice within a single walk -- once stamped, an entry's bit 2 matches current_il_region, and the equality check causes the walker to skip it.

Entry Kind Dispatch

The giant switch covers all 85 entry kinds. Each case knows the exact layout of that entry type and recursively calls walk_entry_and_subtree on every child pointer. The three callbacks are invoked at appropriate points:

  • entry_replace: Called on each child pointer before recursion, potentially replacing it with a remapped pointer.
  • string_callback: Called on string fields (file names, identifier text), receiving the string pointer, kind 26, and byte length including null terminator.
  • entry_filter: Called on linked-list head pointers, returning NULL to remove the entry from the list.

Coverage by Entry Kind

The following table shows the major entry kinds and what the walker visits for each:

KindNameChildren Walked
1source_file_entryfile_name (string, kind 26 at [0]), full_name (string, kind 26 at [1]), name_as_written (string, kind 26 at [2]), child file list (kind 1, linked via offset +56 at [5]), associated entry at [6] (kind 1), module info at [8] (kind 82)
2constantType refs at [14]/[15] (kind 6), expression at [16] (kind 13); sub-switch on constant_kind byte at +148 (see below)
3parameterType (kind 6), declared_type (kind 6), default_arg_expr (kind 13), attributes (kind 72)
6typeBase type (kind 6), member field list (kind 8), template info (kind 58), scope (kind 23), base class list (kind 24), class_info supplement (kind 39)
7variableType (kind 6), initializer expression (kind 13), attributes (kind 72), declared_type (kind 6)
8fieldNext field (kind 8), type (kind 6), bit_size_constant (kind 2)
9exception_specType list (kind 10), noexcept expression (kind 13)
11routineReturn type (kind 6), parameter list (kind 3), body scope (kind 23), template info (kind 58), exception spec (kind 9), attributes (kind 72)
12labelBreak label (kind 12), continue label (kind 12)
13expressionSub-expressions (kind 13), operand entries, type references (kind 6); sub-switch on expression operator covers ~120 operator kinds
16switch_caseStatement (kind 21), case_value constant (kind 2)
17switch_infoCase list (kind 16), default case (kind 16), sorted case array
18catch_entryParameter (kind 7), statement (kind 21), dynamic_init expression (kind 13)
21statementSub-statements (kind 21), expressions (kind 13), labels (kind 12); sub-switch on statement kind
22object_lifetimeVariable (kind 7), lifetime scope boundary
23scopeVariables list (kind 7), routines list (kind 11), types list (kind 6), nested scopes (kind 23), namespaces (kind 28), using-declarations (kind 29), hidden names (kind 56), labels (kind 12)
24base_classNext (kind 24), type (kind 6), derived_class (kind 6), offset expression
27template_parameterDefault value, constraint expression (kind 60), template param supplement
28namespaceAssociated scope (kind 23), flags
29using_declarationTarget entity, position, access specifier
30dynamic_initExpression (kind 13), associated variable (kind 7)
39class_infoConstructor initializer list (kind 41), friend list, base class list (kind 24)
41constructor_initNext (kind 41), member/base expression, initializer expression
55orphaned_entitiesEntity list, scope reference
58templateTemplate parameter list (kind 61), body, specializations list
72attributeAttribute arguments (kind 73), next attribute (kind 72)
80subobject_pathLinked list (kind 80), each entry walked recursively

Constant Entry Sub-Switch (Case 2)

The constant entry handler is one of the most complex cases. After walking two type references ([14], [15] as kind 6) and one expression ([16] as kind 13), it dispatches on the constant_kind byte at entry + 148:

// Walk shared fields first
walk(a1[14], 6);   // type
walk(a1[15], 6);   // declared_type
walk(a1[16], 13);  // associated expression

// Strip template info if walk_mode_flags set
if (walk_mode_flags)
    a1[17] = 0;

switch (constant_kind) {
    case 0:  /* ck_error */
    case 1:  /* ck_integer */
    case 3:  /* ck_float */
    case 5:  /* ck_imaginary */
    case 14: /* ck_void */
        break;  // leaf constants, no children

    case 2:  /* ck_string */
        // Walk string data at [20] as string_text (kind 25)
        // Length comes from [19] (not strlen -- may have embedded NULs)
        if (string_callback)
            string_callback(a1[20], 25, a1[19]);
        break;

    case 4:  /* ck_complex */
        walk(a1[19], 27);   // template_parameter (real/imaginary parts)
        break;

    case 6:  /* ck_address -- 7 sub-kinds at entry+152 */
        switch (address_sub_kind) {
            case 0: entry_replace(a1[20], 11);  break;  // routine
            case 1: entry_replace(a1[20], 7);   break;  // variable
            case 2: case 3:
                walk(a1[20], 2);                break;  // constant (recurse)
            case 4: entry_replace(a1[20], 6);   break;  // type (typeid)
            case 5: walk(a1[20], 6);            break;  // type (uuidof, recurse)
            case 6: entry_replace(a1[20], 12);  break;  // label
            default: error("bad address const kind");
        }
        // Then walk subobject_path list at [22] (kind 80)
        break;

    case 7:  /* ck_ptr_to_member */
        entry_replace(a1[19], 36);   // derivation_path
        walk(a1[20], 62);            // name_reference
        // Conditional: if a1[21] & 2, replace [22] as routine(11)
        //             else replace [22] as field(8)
        break;

    case 8:  /* ck_label_difference */
        walk(a1[20], 2);             // constant (recurse)
        break;

    case 9:  /* ck_dynamic_init */
        walk(a1[19], 30);            // dynamic_init entry
        break;

    case 10: /* ck_aggregate */
        // Linked list of constants at [19], each via offset +104
        for each constant in list: walk(entry, 2);
        entry_replace(a1[20], 2);    // tail constant
        break;

    case 11: /* ck_init_repeat */
        walk(a1[19], 2);            // repeated constant
        break;

    case 12: /* ck_template_param -- 15 sub-kinds at entry+152 */
        // Another sub-switch with cases 0-13 + default error
        break;

    case 13: /* ck_designator */
        walk(a1[20], 2);            // constant value
        break;

    case 15: /* ck_reflection */
        // Walk [20] with kind from entry+152 byte
        break;
}

The walk_mode_flags field zeroing (a1[17] = 0) strips template parameter constant info during IL binary output. This is the template-stripping behavior controlled by argument a6 of walk_file_scope_il.

String Entry Handling

String fields within entries are walked with three distinct string kind values:

String KindValueDisplay NameUsed ForLength Source
id_name24"id name"Identifier names (variable, function, field names)strlen(str) + 1
string_text25"string text"String literal content (for ck_string constants)Constant's length field [19]
other_text26"other text"File names, compiler version, compilation time, asm textstrlen(str) + 1

The string_text kind (25) is special: its length comes from the enclosing constant entry's [19] field rather than strlen, because C/C++ string literals may contain embedded null bytes. All other string kinds use strlen(str) + 1.

Error Strings

The function contains diagnostic strings from walk_entry.h that fire on unexpected sub-kind values:

StringLineTriggers When
"walk_entry_and_subtree: bad address const kind"883Unknown address_constant_kind in constant entry (kind 2, sub-kind 6)
"walk_entry_and_subtree: bad template param constant kind"1035Unknown template_param_constant_kind in constant entry (kind 2, sub-kind 12)
"walk_entry_and_subtree: bad constant kind"1051Unknown constant_kind in constant entry (kind 2)

All three errors reference walk_entry.h as the source file and walk_entry_and_subtree as the function name, confirming the dispatch code is generated from the header file.

walk_routine_scope_il (sub_610200)

The routine-scope counterpart of walk_file_scope_il. It takes a routine index and walks that routine's scope chain:

void walk_routine_scope_il(int routine_index, ...) {
    // Same 5-callback + 4-state save/restore pattern
    // Trace: "walk_routine_scope_il"
    // Assert: il_walk.c, line 376

    dword_126FB5C = 0;  // NOT file-scope walk

    scope = qword_126EB90[routine_index];  // routine_scope_array
    while (scope) {
        walk_entry_and_subtree(scope, 23);
        if (entry_replace)
            scope = entry_replace(scope, 23);
        scope = scope->next;
    }
}

The key difference from walk_file_scope_il is that is_file_scope_walk is set to 0, which changes the entry protocol in walk_entry_and_subtree: entries with the file-scope bit set in their flags byte are skipped, because they belong to the file-scope IL and should not be processed during a routine-scope walk.

Callers and Use Cases

The walk framework serves four distinct purposes. Each caller installs a different callback configuration.

IL Display

The --dump_il debug output uses the walk framework with display_il_entry (sub_5F4930) as the entry callback:

// sub_5F76B0 (display_il_header)
walk_file_scope_il(
    display_il_entry,    // a1: entry callback = sub_5F4930
    NULL,                // a2: no string callback
    NULL,                // a3: no replace
    NULL,                // a4: no filter
    NULL,                // a5: no pre-walk check
    0                    // a6: no special flags
);

With all callbacks NULL except entry_callback, the walker visits every entry in walk order and calls display_il_entry on each, which dispatches on entry kind to print formatted field dumps. The pre_walk_check is NULL, so the default flags-based skip logic applies -- the current_il_region toggle prevents double-visiting.

Keep-in-IL Marking

The device code selection pass (mark_to_keep_in_il, sub_610420) installs the prune callback as pre_walk_check and NULL for everything else:

// sub_610420 (mark_to_keep_in_il)
qword_126FB88 = NULL;                    // no entry callback
qword_126FB80 = NULL;                    // no string callback
qword_126FB78 = prune_keep_in_il_walk;   // sub_617310
qword_126FB70 = NULL;                    // no replace
qword_126FB68 = NULL;                    // no filter

The prune_keep_in_il_walk callback (sub_617310) sets bit 7 (0x80) of each entry's flags byte and returns 1 for already-marked entries (preventing infinite recursion). The actual subtree walk is handled by a specialized copy of walk_entry_and_subtree (sub_6115E0, walk_tree_and_set_keep_in_il, 4649 lines) that directly sets the keep bit on every reachable child rather than using callbacks. See Keep-in-IL for the full mechanism.

IL Serialization

IL binary output (when IL_SHOULD_BE_WRITTEN_TO_FILE would be enabled, or for device IL output) uses all five callback slots:

  • entry_callback: Records each entry's position in the output stream
  • string_callback: Serializes string data with length prefix
  • entry_replace: Translates IL pointers to output-stream offsets
  • entry_filter: Skips entries that should not appear in the output (e.g., entries without keep_in_il for device IL)
  • pre_walk_check: Prevents re-serializing entries already written

IL Copy (Template Instantiation)

When EDG instantiates a template, it copies the template's IL subtree into a new region. The copy operation uses entry_replace to remap all pointers from the source region to the destination:

  • entry_replace: For each child pointer, allocates a new entry in the destination region, copies the source entry's contents, and returns the new pointer
  • string_callback: Copies string data into the destination region
  • pre_walk_check: Tracks which entries have already been copied (using the visited-set hash table at qword_126FB50)

Hash Table for Visited Set

The walk framework includes a visited-set hash table for cycles and deduplication:

AddressNameDescription
qword_126FB50hash_table_arrayPointer to hash table bucket array
dword_126FB48hash_table_countNumber of entries in hash table
qword_126FB40visited_setPointer to visited-set data
dword_126FB30visited_countNumber of visited entries

The hash table is reset by sub_603B30 (clear_walk_hash_table) before each walk operation. It uses open addressing and is primarily employed during IL copy operations to map source entry pointers to their destination counterparts.

Helper Functions

Several helper functions support the walk framework:

AddressIdentityLinesPurpose
sub_603B30clear_walk_hash_table23Zeros the visited-set hash table (qword_126FB50, dword_126FB48)
sub_603FE0find_parent_var_of_anon_union_type127Searches scope member lists for the variable that owns an anonymous union type
sub_603BB0find_var_in_nested_scopes333Recursively searches nested scopes for a variable (deeply unrolled, 8+ levels)
sub_603B00(trivial getter)9Walk-state accessor
sub_610200walk_routine_scope_il108Routine-scope walker (counterpart to walk_file_scope_il)

Keep-in-IL Specialized Walkers

The keep-in-il pass uses parallel implementations of the walk framework that bypass the callback mechanism for performance:

AddressIdentityLinesPurpose
sub_6115E0walk_tree_and_set_keep_in_il4649File-scope variant -- sets bit 7 directly on every reachable entry
sub_618660walk_entry_and_set_keep_in_il_routine_scope3728Routine-scope variant
sub_61CE20--sub_620190(keep-in-il helpers)variousPer-kind helpers for template args, exception specs, array bounds, expressions, statements

These specialized walkers are structurally identical to walk_entry_and_subtree but replace callback invocations with direct *(entry - 8) |= 0x80 operations. They exist as separate functions rather than callback-based walks because the keep-in-il marking is performance-critical -- it runs on every CUDA compilation, and eliminating the function-pointer indirection across ~330 recursive calls provides measurable speedup.

Global Entry-Kind List Layout

The per-kind linked lists are stored in a contiguous global array starting at 0x126E600, with 16-byte stride. The formula 0x126E600 + kind * 0x10 gives the list head for most entry kinds up to kind 72. The complete walk order with all 51 lists (45 from Phase 5, 3 from Phase 6, plus orphaned entities, source files, and seq_number_lookup) is documented in the Phase 5 table above.

The three trailing lists (Phase 6) are stored outside the contiguous array at separate addresses in the IL header/footer region:

AddressKindPurposeNext-Pointer Strategy
qword_126EBE864Sequence number lookup entriesStandard next_in_list at node prefix
qword_126EBE06External declarations (type list)Type-specific next at offset +104
qword_126EC0083Module declarations (C++20)Standard next_in_list at node prefix

The external declarations list (qword_126EBE0) is notable: it walks entries as kind 6 (type) but uses a different linked-list strategy (offset +104 rather than the standard prefix next pointer). This is because the external declarations list is a secondary index over type entries that are also present in the main type list at qword_126E660.

Walk Order Diagram

walk_file_scope_il(callbacks...)
  |
  +-- [save 5 callbacks + 4 state vars]
  +-- [install caller's callbacks]
  |
  +-- Phase 1: walk_entry_and_subtree(primary_scope, 23)
  |     |
  |     +-- Recursively visits all nested scopes,
  |         their member lists (vars, routines, types),
  |         and all subtrees
  |
  +-- Phase 2: source_file list (kind 1)
  |     +-- for each file: walk(file, 1)
  |         +-- walks file_name, full_name, child files
  |
  +-- Phase 3: main_routine + string entries
  |     +-- entry_replace(main_routine, 11)
  |     +-- string_callback(compiler_version, 26, len)
  |     +-- string_callback(time_of_compilation, 26, len)
  |
  +-- Phase 4: orphaned_entities list (kind 55)
  |     +-- for each orphan: walk(orphan, 55)
  |
  +-- Phase 5: global lists (kinds 1, 2, 3, ..., 72)
  |     +-- for each kind:
  |           for each entry in list:
  |             entry_replace(entry, kind)
  |             walk(entry, kind)
  |
  +-- Phase 6: trailing lists (kinds 64, 6-ext, 83)
  |
  +-- [restore saved state]

Diagnostic Strings

StringSourceCondition
"walk_file_scope_il"sub_60E4F0Trace enter (dword_126EFC8 nonzero)
"walk_routine_scope_il"sub_610200Trace enter
"Walking IL tree, entry kind = %s\n"sub_604170dword_126EFCC > 4
"Walking IL tree, string entry kind = %s\n"sub_604170 / sub_60E4F0dword_126EFCC > 4
"walk_entry_and_subtree: bad address const kind"sub_604170Unknown address constant sub-kind (walk_entry.h:883)
"walk_entry_and_subtree: bad template param constant kind"sub_604170Unknown template param constant sub-kind (walk_entry.h:1035)
"walk_entry_and_subtree: bad constant kind"sub_604170Unknown constant kind (walk_entry.h:1051)
"find_parent_var_of_anon_union_type"sub_603FE0Assert at lines 511, 523
"find_parent_var_of_anon_union_type: var not found"sub_603FE0Variable lookup failed

Function Map

AddressIdentityConfidenceLinesEDG Source
sub_60E4F0walk_file_scope_il99%2043il_walk.c:270
sub_604170walk_entry_and_subtree99%7763il_walk.c / walk_entry.h
sub_610200walk_routine_scope_il98%108il_walk.c:376
sub_603B30clear_walk_hash_table85%23il_walk.c
sub_603FE0find_parent_var_of_anon_union_type99%127il_walk.c:511
sub_603BB0find_var_in_nested_scopes85%333il_walk.c
sub_603B00(trivial walk-state accessor)80%9il_walk.c
sub_6115E0walk_tree_and_set_keep_in_il98%4649il_walk.c
sub_618660walk_entry_and_set_keep_in_il_routine_scope88%3728il_walk.c
sub_61CE20(keep-in-il helper: template args)80%100il_walk.c
sub_61D0C0(keep-in-il helper: exception spec)80%108il_walk.c
sub_61D330(keep-in-il helper: array bound)80%97il_walk.c
sub_61D570(keep-in-il helper: overriding virtual)80%120il_walk.c
sub_61D7F0(keep-in-il helper: base class)80%69il_walk.c
sub_61D9B0(keep-in-il helper: attributes)80%202il_walk.c
sub_61DEC0(keep-in-il helper: using-decl)80%101il_walk.c
sub_61E160(keep-in-il helper: object lifetime)80%76il_walk.c
sub_61E370(keep-in-il helper: expressions)80%369il_walk.c
sub_61ECF0(keep-in-il helper: statements)80%466il_walk.c
sub_61F420(keep-in-il helper: additional exprs)80%631il_walk.c
sub_61FEA0(keep-in-il helper: decl sequence)80%173il_walk.c

Cross-References