zig0

my attempts at zig bootstrapping in C
Log | Files | Refs | README | LICENSE

astgen.c (411494B) - Raw


      1 // astgen.c — AST to ZIR conversion, ported from lib/std/zig/AstGen.zig.
      2 //
      3 // Structural translation of AstGen.zig into C.
      4 // Each function corresponds to a Zig function with the same name,
      5 // with line references to Zig 0.15.1 AstGen.zig.
      6 
      7 #include "astgen.h"
      8 #include "common.h"
      9 #include <assert.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 
     13 // --- Declaration.Flags.Id enum (Zir.zig:2724) ---
     14 
     15 typedef enum {
     16     DECL_ID_UNNAMED_TEST,
     17     DECL_ID_TEST,
     18     DECL_ID_DECLTEST,
     19     DECL_ID_COMPTIME,
     20     DECL_ID_CONST_SIMPLE,
     21     DECL_ID_CONST_TYPED,
     22     DECL_ID_CONST,
     23     DECL_ID_PUB_CONST_SIMPLE,
     24     DECL_ID_PUB_CONST_TYPED,
     25     DECL_ID_PUB_CONST,
     26     DECL_ID_EXTERN_CONST_SIMPLE,
     27     DECL_ID_EXTERN_CONST,
     28     DECL_ID_PUB_EXTERN_CONST_SIMPLE,
     29     DECL_ID_PUB_EXTERN_CONST,
     30     DECL_ID_EXPORT_CONST,
     31     DECL_ID_PUB_EXPORT_CONST,
     32     DECL_ID_VAR_SIMPLE,
     33     DECL_ID_VAR,
     34     DECL_ID_VAR_THREADLOCAL,
     35     DECL_ID_PUB_VAR_SIMPLE,
     36     DECL_ID_PUB_VAR,
     37     DECL_ID_PUB_VAR_THREADLOCAL,
     38     DECL_ID_EXTERN_VAR,
     39     DECL_ID_EXTERN_VAR_THREADLOCAL,
     40     DECL_ID_PUB_EXTERN_VAR,
     41     DECL_ID_PUB_EXTERN_VAR_THREADLOCAL,
     42     DECL_ID_EXPORT_VAR,
     43     DECL_ID_EXPORT_VAR_THREADLOCAL,
     44     DECL_ID_PUB_EXPORT_VAR,
     45     DECL_ID_PUB_EXPORT_VAR_THREADLOCAL,
     46 } DeclFlagsId;
     47 
     48 // --- Import tracking (AstGen.zig:265) ---
     49 
     50 typedef struct {
     51     uint32_t name; // NullTerminatedString index
     52     uint32_t token; // Ast.TokenIndex
     53 } ImportEntry;
     54 
     55 // --- AstGen internal context (mirrors AstGen struct, AstGen.zig:153) ---
     56 
     57 typedef struct {
     58     const Ast* tree;
     59     ZirInstTag* inst_tags;
     60     ZirInstData* inst_datas;
     61     uint32_t inst_len;
     62     uint32_t inst_cap;
     63     uint32_t* extra;
     64     uint32_t extra_len;
     65     uint32_t extra_cap;
     66     uint8_t* string_bytes;
     67     uint32_t string_bytes_len;
     68     uint32_t string_bytes_cap;
     69     // String dedup table: stores positions in string_bytes that are
     70     // registered for deduplication (mirrors AstGen.string_table).
     71     // Only strings added via identAsString/strLitAsString (non-embedded-null)
     72     // are registered. Multiline strings are NOT registered.
     73     uint32_t* string_table;
     74     uint32_t string_table_len;
     75     uint32_t string_table_cap;
     76     uint32_t source_offset;
     77     uint32_t source_line;
     78     uint32_t source_column;
     79     ImportEntry* imports;
     80     uint32_t imports_len;
     81     uint32_t imports_cap;
     82     // Namespace decl table: maps string indices to node indices.
     83     // Populated by scanContainer, used by identifier resolution.
     84     uint32_t* decl_names; // string indices
     85     uint32_t* decl_nodes; // node indices
     86     uint32_t decl_table_len;
     87     uint32_t decl_table_cap;
     88     // Shared dynamic array for GenZir instructions (AstGen.zig:11796).
     89     // Sub-blocks share this array and track their slice via
     90     // instructions_top.
     91     uint32_t* scratch_instructions;
     92     uint32_t scratch_inst_len;
     93     uint32_t scratch_inst_cap;
     94     // Scratch extra array for call arguments (mirrors AstGen.scratch in Zig).
     95     // Used to collect body lengths + body instructions before copying to
     96     // extra.
     97     uint32_t* scratch_extra;
     98     uint32_t scratch_extra_len;
     99     uint32_t scratch_extra_cap;
    100     // Return type ref for the current function (set during fnDecl/testDecl).
    101     uint32_t fn_ret_ty; // ZirInstRef
    102     // Pointer to the fn_block GenZir for the current function (AstGen.zig:45).
    103     void* fn_block; // GenZir*
    104     // ref_table: deferred REF instructions (AstGen.zig:58-68).
    105     // Key = operand inst index, Value = ref inst index.
    106     uint32_t* ref_table_keys;
    107     uint32_t* ref_table_vals;
    108     uint32_t ref_table_len;
    109     uint32_t ref_table_cap;
    110     // nodes_need_rl: set of AST node indices that need result locations.
    111     // Populated by astRlAnnotate() pre-pass (AstRlAnnotate.zig).
    112     uint32_t* nodes_need_rl;
    113     uint32_t nodes_need_rl_len;
    114     uint32_t nodes_need_rl_cap;
    115     bool has_compile_errors;
    116 } AstGenCtx;
    117 
    118 static void setCompileError(AstGenCtx* ag, const char* where, int line) {
    119     (void)where;
    120     (void)line;
    121     ag->has_compile_errors = true;
    122 }
    123 #define SET_ERROR(ag) setCompileError(ag, __func__, __LINE__)
    124 
    125 // Set fn_block pointer on AstGenCtx. The caller is responsible for saving
    126 // and restoring the previous value before the pointed-to GenZir goes out
    127 // of scope (AstGen.zig:45).
    128 static void setFnBlock(AstGenCtx* ag, void* block) { ag->fn_block = block; }
    129 
    130 // --- ref_table operations (AstGen.zig:58-68) ---
    131 // Simple linear-scan hash table for deferred REF instructions.
    132 
    133 // Returns pointer to existing value if key found, NULL if not found.
    134 static uint32_t* refTableGet(AstGenCtx* ag, uint32_t key) {
    135     for (uint32_t i = 0; i < ag->ref_table_len; i++) {
    136         if (ag->ref_table_keys[i] == key)
    137             return &ag->ref_table_vals[i];
    138     }
    139     return NULL;
    140 }
    141 
    142 // getOrPut: returns pointer to value slot; sets *found to true if existed.
    143 static uint32_t* refTableGetOrPut(AstGenCtx* ag, uint32_t key, bool* found) {
    144     for (uint32_t i = 0; i < ag->ref_table_len; i++) {
    145         if (ag->ref_table_keys[i] == key) {
    146             *found = true;
    147             return &ag->ref_table_vals[i];
    148         }
    149     }
    150     *found = false;
    151     if (ag->ref_table_len >= ag->ref_table_cap) {
    152         uint32_t new_cap = ag->ref_table_cap == 0 ? 16 : ag->ref_table_cap * 2;
    153         ag->ref_table_keys
    154             = realloc(ag->ref_table_keys, new_cap * sizeof(uint32_t));
    155         ag->ref_table_vals
    156             = realloc(ag->ref_table_vals, new_cap * sizeof(uint32_t));
    157         ag->ref_table_cap = new_cap;
    158     }
    159     uint32_t idx = ag->ref_table_len++;
    160     ag->ref_table_keys[idx] = key;
    161     return &ag->ref_table_vals[idx];
    162 }
    163 
    164 // fetchRemove: if key exists, remove it and return true with *val set.
    165 static bool refTableFetchRemove(AstGenCtx* ag, uint32_t key, uint32_t* val) {
    166     for (uint32_t i = 0; i < ag->ref_table_len; i++) {
    167         if (ag->ref_table_keys[i] == key) {
    168             *val = ag->ref_table_vals[i];
    169             // Swap with last element.
    170             ag->ref_table_len--;
    171             if (i < ag->ref_table_len) {
    172                 ag->ref_table_keys[i] = ag->ref_table_keys[ag->ref_table_len];
    173                 ag->ref_table_vals[i] = ag->ref_table_vals[ag->ref_table_len];
    174             }
    175             return true;
    176         }
    177     }
    178     return false;
    179 }
    180 
    181 // --- Result location (AstGen.zig:11808) ---
    182 // Simplified version of ResultInfo.Loc.
    183 // Defined here (before GenZir) because GenZir.break_result_info uses it.
    184 
    185 // ResultInfo.Context (AstGen.zig:371-386).
    186 typedef enum {
    187     RI_CTX_NONE,
    188     RI_CTX_RETURN,
    189     RI_CTX_ERROR_HANDLING_EXPR,
    190     RI_CTX_SHIFT_OP,
    191     RI_CTX_FN_ARG,
    192     RI_CTX_CONST_INIT,
    193     RI_CTX_ASSIGNMENT,
    194 } ResultCtx;
    195 
    196 typedef enum {
    197     RL_NONE, // Just compute the value.
    198     RL_REF, // Compute a pointer to the value.
    199     RL_DISCARD, // Compute but discard (emit ensure_result_non_error).
    200     RL_TY, // Coerce to specific type.
    201     RL_COERCED_TY, // Coerce to specific type, result is the coercion.
    202     RL_PTR, // Store result to typed pointer. data=alloc inst, src_node=node.
    203     RL_INFERRED_PTR, // Store result to inferred pointer. data=alloc inst.
    204     RL_REF_COERCED_TY, // Ref with pointer type. data=ptr_ty_inst.
    205 } ResultLocTag;
    206 
    207 typedef struct {
    208     ResultLocTag tag;
    209     uint32_t data; // ZirInstRef: ty_inst for TY/COERCED_TY, alloc inst for
    210                    // PTR/INFERRED_PTR.
    211     uint32_t src_node; // Only used for RL_PTR.
    212     ResultCtx ctx; // ResultInfo.Context (AstGen.zig:371).
    213 } ResultLoc;
    214 
    215 #define RL_NONE_VAL                                                           \
    216     ((ResultLoc) {                                                            \
    217         .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE })
    218 #define RL_REF_VAL                                                            \
    219     ((ResultLoc) {                                                            \
    220         .tag = RL_REF, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE })
    221 #define RL_DISCARD_VAL                                                        \
    222     ((ResultLoc) {                                                            \
    223         .tag = RL_DISCARD, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE })
    224 #define RL_IS_REF(rl) ((rl).tag == RL_REF || (rl).tag == RL_REF_COERCED_TY)
    225 
    226 // --- Scope types (AstGen.zig:11621-11768) ---
    227 
    228 typedef enum {
    229     SCOPE_GEN_ZIR,
    230     SCOPE_LOCAL_VAL,
    231     SCOPE_LOCAL_PTR,
    232     SCOPE_DEFER_NORMAL,
    233     SCOPE_DEFER_ERROR,
    234     SCOPE_NAMESPACE,
    235     SCOPE_TOP,
    236     SCOPE_LABEL,
    237 } ScopeTag;
    238 
    239 typedef struct Scope {
    240     ScopeTag tag;
    241 } Scope;
    242 
    243 // --- GenZir scope (mirrors GenZir struct, AstGen.zig:11772) ---
    244 //
    245 // Sub-blocks share the parent AstGenCtx's scratch_instructions array and
    246 // record their starting offset (instructions_top). This mirrors the upstream
    247 // GenZir.instructions / instructions_top design (AstGen.zig:11796-11850).
    248 
    249 typedef struct {
    250     Scope base; // tag = SCOPE_GEN_ZIR
    251     Scope* parent;
    252     AstGenCtx* astgen;
    253     uint32_t decl_node_index;
    254     uint32_t decl_line;
    255     bool is_comptime;
    256     bool is_inline; // true for inline for/while, labeled blocks in comptime
    257     bool c_import; // true inside @cImport block
    258     uint32_t instructions_top; // start index in shared array
    259     uint32_t break_block; // UINT32_MAX = none (AstGen.zig:11780)
    260     uint32_t continue_block; // UINT32_MAX = none (AstGen.zig:11784)
    261     // Label for labeled blocks (AstGen.zig:11800, 11869-11874).
    262     uint32_t label_token; // UINT32_MAX = no label
    263     uint32_t label_block_inst; // the BLOCK instruction index
    264     ResultLoc break_result_info; // RL for break values
    265 } GenZir;
    266 
    267 // Scope.LocalVal (AstGen.zig:11682).
    268 // This is always a `const` local and the `inst` is a value type, not a
    269 // pointer.
    270 typedef struct {
    271     Scope base; // tag = SCOPE_LOCAL_VAL
    272     Scope* parent;
    273     GenZir* gen_zir;
    274     uint32_t inst; // ZirInstRef
    275     uint32_t token_src; // Ast.TokenIndex
    276     uint32_t name; // NullTerminatedString (string table index)
    277 } ScopeLocalVal;
    278 
    279 // Scope.LocalPtr (AstGen.zig:11704).
    280 // This could be a `const` or `var` local. It has a pointer instead of a value.
    281 typedef struct {
    282     Scope base; // tag = SCOPE_LOCAL_PTR
    283     Scope* parent;
    284     GenZir* gen_zir;
    285     uint32_t ptr; // ZirInstRef
    286     uint32_t token_src; // Ast.TokenIndex
    287     uint32_t name; // NullTerminatedString (string table index)
    288     bool maybe_comptime;
    289 } ScopeLocalPtr;
    290 
    291 // Scope.Defer (AstGen.zig:11741).
    292 typedef struct {
    293     Scope base; // tag = SCOPE_DEFER_NORMAL or SCOPE_DEFER_ERROR
    294     Scope* parent;
    295     uint32_t index;
    296     uint32_t len;
    297 } ScopeDefer;
    298 
    299 // Scope.Label — for labeled blocks and loops.
    300 typedef struct {
    301     Scope base; // tag = SCOPE_LABEL
    302     Scope* parent;
    303     uint32_t label_name; // NullTerminatedString
    304     uint32_t block_inst; // instruction index (not ref)
    305 } ScopeLabel;
    306 
    307 // --- GenZir instruction helpers (AstGen.zig:11830-11850) ---
    308 
    309 // Returns the number of instructions in this scope.
    310 static uint32_t gzInstructionsLen(const GenZir* gz) {
    311     return gz->astgen->scratch_inst_len - gz->instructions_top;
    312 }
    313 
    314 // Returns pointer to start of this scope's instructions in the shared array.
    315 static const uint32_t* gzInstructionsSlice(const GenZir* gz) {
    316     return gz->astgen->scratch_instructions + gz->instructions_top;
    317 }
    318 
    319 // Mirrors GenZir.instructionsSliceUpto (AstGen.zig:11835).
    320 // Returns instructions from gz up to (but not including) stacked_gz's start.
    321 static uint32_t gzInstructionsLenUpto(
    322     const GenZir* gz, const GenZir* stacked_gz) {
    323     return stacked_gz->instructions_top - gz->instructions_top;
    324 }
    325 
    326 static const uint32_t* gzInstructionsSliceUpto(
    327     const GenZir* gz, const GenZir* stacked_gz) {
    328     (void)stacked_gz; // used only for length computation
    329     return gz->astgen->scratch_instructions + gz->instructions_top;
    330 }
    331 
    332 // Mirrors GenZir.unstack (AstGen.zig:11822).
    333 // Restores the shared array length to this scope's start.
    334 static void gzUnstack(GenZir* gz) {
    335     gz->astgen->scratch_inst_len = gz->instructions_top;
    336 }
    337 
    338 // Append an instruction index to this scope's portion of the shared array.
    339 static void gzAppendInstruction(GenZir* gz, uint32_t inst_idx) {
    340     AstGenCtx* ag = gz->astgen;
    341     if (ag->scratch_inst_len >= ag->scratch_inst_cap) {
    342         uint32_t new_cap
    343             = ag->scratch_inst_cap > 0 ? ag->scratch_inst_cap * 2 : 64;
    344         uint32_t* p
    345             = realloc(ag->scratch_instructions, new_cap * sizeof(uint32_t));
    346         if (!p)
    347             exit(1);
    348         ag->scratch_instructions = p;
    349         ag->scratch_inst_cap = new_cap;
    350     }
    351     ag->scratch_instructions[ag->scratch_inst_len++] = inst_idx;
    352 }
    353 
    354 // Mirrors GenZir.makeSubBlock (AstGen.zig:11852).
    355 static GenZir makeSubBlock(GenZir* parent, Scope* scope) {
    356     GenZir sub;
    357     memset(&sub, 0, sizeof(sub));
    358     sub.base.tag = SCOPE_GEN_ZIR;
    359     sub.parent = scope;
    360     sub.astgen = parent->astgen;
    361     sub.decl_node_index = parent->decl_node_index;
    362     sub.decl_line = parent->decl_line;
    363     sub.is_comptime = parent->is_comptime;
    364     sub.c_import = parent->c_import;
    365     sub.instructions_top = parent->astgen->scratch_inst_len;
    366     sub.break_block = UINT32_MAX;
    367     sub.continue_block = UINT32_MAX;
    368     sub.label_token = UINT32_MAX;
    369     return sub;
    370 }
    371 
    372 // --- Capacity helpers ---
    373 
    374 static void ensureExtraCapacity(AstGenCtx* ag, uint32_t additional) {
    375     uint32_t needed = ag->extra_len + additional;
    376     if (needed > ag->extra_cap) {
    377         uint32_t new_cap = ag->extra_cap * 2;
    378         if (new_cap < needed)
    379             new_cap = needed;
    380         uint32_t* p = realloc(ag->extra, new_cap * sizeof(uint32_t));
    381         if (!p)
    382             exit(1);
    383         ag->extra = p;
    384         ag->extra_cap = new_cap;
    385     }
    386 }
    387 
    388 static void ensureInstCapacity(AstGenCtx* ag, uint32_t additional) {
    389     uint32_t needed = ag->inst_len + additional;
    390     if (needed > ag->inst_cap) {
    391         uint32_t new_cap = ag->inst_cap * 2;
    392         if (new_cap < needed)
    393             new_cap = needed;
    394         ZirInstTag* t = realloc(ag->inst_tags, new_cap * sizeof(ZirInstTag));
    395         ZirInstData* d
    396             = realloc(ag->inst_datas, new_cap * sizeof(ZirInstData));
    397         if (!t || !d)
    398             exit(1);
    399         ag->inst_tags = t;
    400         ag->inst_datas = d;
    401         ag->inst_cap = new_cap;
    402     }
    403 }
    404 
    405 static void ensureStringBytesCapacity(AstGenCtx* ag, uint32_t additional) {
    406     uint32_t needed = ag->string_bytes_len + additional;
    407     if (needed > ag->string_bytes_cap) {
    408         uint32_t new_cap = ag->string_bytes_cap * 2;
    409         if (new_cap < needed)
    410             new_cap = needed;
    411         uint8_t* p = realloc(ag->string_bytes, new_cap * sizeof(uint8_t));
    412         if (!p)
    413             exit(1);
    414         ag->string_bytes = p;
    415         ag->string_bytes_cap = new_cap;
    416     }
    417 }
    418 
    419 // --- Extra data helpers ---
    420 
    421 static uint32_t addExtraU32(AstGenCtx* ag, uint32_t value) {
    422     ensureExtraCapacity(ag, 1);
    423     uint32_t idx = ag->extra_len;
    424     ag->extra[ag->extra_len++] = value;
    425     return idx;
    426 }
    427 
    428 // --- Instruction helpers ---
    429 
    430 // Mirrors AstGen.reserveInstructionIndex (AstGen.zig:12902).
    431 static uint32_t reserveInstructionIndex(AstGenCtx* ag) {
    432     ensureInstCapacity(ag, 1);
    433     uint32_t idx = ag->inst_len;
    434     memset(&ag->inst_datas[idx], 0, sizeof(ZirInstData));
    435     ag->inst_tags[idx] = (ZirInstTag)0;
    436     ag->inst_len++;
    437     return idx;
    438 }
    439 
    440 // Forward declarations.
    441 static int32_t tokenIndexToRelative(const GenZir* gz, uint32_t token);
    442 static uint32_t firstToken(const Ast* tree, uint32_t node);
    443 static bool nodesNeedRlContains(const AstGenCtx* ag, uint32_t node);
    444 
    445 // Mirrors GenZir.makeUnTok (AstGen.zig:12520).
    446 // Allocates an instruction but does NOT add to GenZir body.
    447 // Returns the raw instruction INDEX (not a Ref).
    448 static uint32_t makeUnTok(
    449     GenZir* gz, ZirInstTag tag, uint32_t operand, uint32_t abs_tok_index) {
    450     AstGenCtx* ag = gz->astgen;
    451     ensureInstCapacity(ag, 1);
    452     uint32_t idx = ag->inst_len;
    453     ZirInstData data;
    454     data.un_tok.src_tok = tokenIndexToRelative(gz, abs_tok_index);
    455     data.un_tok.operand = operand;
    456     ag->inst_tags[idx] = tag;
    457     ag->inst_datas[idx] = data;
    458     ag->inst_len++;
    459     return idx; // Raw index, NOT a Ref.
    460 }
    461 
    462 // Mirrors GenZir.add (AstGen.zig:13162).
    463 // Appends an instruction and records it in the GenZir body.
    464 // Returns the instruction index as a Ref (index + ZIR_INST_REF_START_INDEX).
    465 static uint32_t addInstruction(GenZir* gz, ZirInstTag tag, ZirInstData data) {
    466     AstGenCtx* ag = gz->astgen;
    467     ensureInstCapacity(ag, 1);
    468     uint32_t idx = ag->inst_len;
    469     ag->inst_tags[idx] = tag;
    470     ag->inst_datas[idx] = data;
    471     ag->inst_len++;
    472     // Record in sub-block body.
    473     gzAppendInstruction(gz, idx);
    474     return idx + ZIR_REF_START_INDEX; // toRef()
    475 }
    476 
    477 // Mirrors GenZir.addInt (AstGen.zig:12238).
    478 static uint32_t addInt(GenZir* gz, uint64_t integer) {
    479     ZirInstData data;
    480     data.int_val = integer;
    481     return addInstruction(gz, ZIR_INST_INT, data);
    482 }
    483 
    484 // Mirrors GenZir.add for bin data (Zir.zig:1877).
    485 // Creates an instruction with bin data (lhs + rhs stored in inst_datas).
    486 static uint32_t addBin(
    487     GenZir* gz, ZirInstTag tag, uint32_t lhs, uint32_t rhs) {
    488     ZirInstData data;
    489     data.bin.lhs = lhs;
    490     data.bin.rhs = rhs;
    491     return addInstruction(gz, tag, data);
    492 }
    493 
    494 // Mirrors GenZir.addPlNode (AstGen.zig:12308).
    495 // Creates an instruction with pl_node data and 2-word payload.
    496 static uint32_t addPlNodeBin(
    497     GenZir* gz, ZirInstTag tag, uint32_t node, uint32_t lhs, uint32_t rhs) {
    498     AstGenCtx* ag = gz->astgen;
    499     ensureExtraCapacity(ag, 2);
    500     uint32_t payload_index = ag->extra_len;
    501     ag->extra[ag->extra_len++] = lhs;
    502     ag->extra[ag->extra_len++] = rhs;
    503     ZirInstData data;
    504     data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
    505     data.pl_node.payload_index = payload_index;
    506     return addInstruction(gz, tag, data);
    507 }
    508 
    509 // Mirrors addPlNode for 3-operand payloads (e.g. ArrayTypeSentinel).
    510 static uint32_t addPlNodeTriple(GenZir* gz, ZirInstTag tag, uint32_t node,
    511     uint32_t a, uint32_t b, uint32_t c) {
    512     AstGenCtx* ag = gz->astgen;
    513     ensureExtraCapacity(ag, 3);
    514     uint32_t payload_index = ag->extra_len;
    515     ag->extra[ag->extra_len++] = a;
    516     ag->extra[ag->extra_len++] = b;
    517     ag->extra[ag->extra_len++] = c;
    518     ZirInstData data;
    519     data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
    520     data.pl_node.payload_index = payload_index;
    521     return addInstruction(gz, tag, data);
    522 }
    523 
    524 // Checks if an AST identifier node is the single underscore `_`.
    525 // Used for inferred array length detection in [_]T patterns.
    526 // Intentionally does NOT support @"_" syntax (matches upstream).
    527 static bool isUnderscoreIdent(const Ast* tree, uint32_t ident_node) {
    528     uint32_t id_tok = tree->nodes.main_tokens[ident_node];
    529     uint32_t id_start = tree->tokens.starts[id_tok];
    530     if (tree->source[id_start] != '_')
    531         return false;
    532     if (id_start + 1 >= tree->source_len)
    533         return true;
    534     char next = tree->source[id_start + 1];
    535     return !((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z')
    536         || next == '_' || (next >= '0' && next <= '9'));
    537 }
    538 
    539 // Mirrors GenZir.addUnNode (AstGen.zig:12406).
    540 static uint32_t addUnNode(
    541     GenZir* gz, ZirInstTag tag, uint32_t operand, uint32_t node) {
    542     ZirInstData data;
    543     data.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
    544     data.un_node.operand = operand;
    545     return addInstruction(gz, tag, data);
    546 }
    547 
    548 // Mirrors GenZir.addUnTok (AstGen.zig:12497).
    549 static uint32_t addUnTok(
    550     GenZir* gz, ZirInstTag tag, uint32_t operand, uint32_t abs_tok_index) {
    551     ZirInstData data;
    552     data.un_tok.src_tok = tokenIndexToRelative(gz, abs_tok_index);
    553     data.un_tok.operand = operand;
    554     return addInstruction(gz, tag, data);
    555 }
    556 
    557 // Mirrors GenZir.addStrTok (AstGen.zig:12349).
    558 static uint32_t addStrTok(
    559     GenZir* gz, ZirInstTag tag, uint32_t str_index, uint32_t token) {
    560     ZirInstData data;
    561     data.str_tok.start = str_index;
    562     data.str_tok.src_tok = tokenIndexToRelative(gz, token);
    563     return addInstruction(gz, tag, data);
    564 }
    565 
    566 // Mirrors GenZir.addPlNodePayloadIndex (AstGen.zig:12332).
    567 static uint32_t addPlNodePayloadIndex(
    568     GenZir* gz, ZirInstTag tag, uint32_t node, uint32_t payload_index) {
    569     ZirInstData data;
    570     data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
    571     data.pl_node.payload_index = payload_index;
    572     return addInstruction(gz, tag, data);
    573 }
    574 
    575 // --- Source cursor (AstGen.zig:13335-13359) ---
    576 
    577 // Mirrors AstGen.advanceSourceCursor (AstGen.zig:13342).
    578 static void advanceSourceCursor(AstGenCtx* ag, uint32_t end) {
    579     const char* source = ag->tree->source;
    580     uint32_t i = ag->source_offset;
    581     uint32_t line = ag->source_line;
    582     uint32_t column = ag->source_column;
    583     assert(i <= end);
    584     while (i < end) {
    585         if (source[i] == '\n') {
    586             line++;
    587             column = 0;
    588         } else {
    589             column++;
    590         }
    591         i++;
    592     }
    593     ag->source_offset = i;
    594     ag->source_line = line;
    595     ag->source_column = column;
    596 }
    597 
    598 // Mirrors tree.firstToken (Ast.zig:596).
    599 // Recurse through nodes to find the first token.
    600 static uint32_t firstToken(const Ast* tree, uint32_t node) {
    601     uint32_t n = node;
    602     while (1) {
    603         AstNodeTag tag = tree->nodes.tags[n];
    604         switch (tag) {
    605         case AST_NODE_ROOT:
    606             return 0;
    607 
    608         // Return main_token directly (Ast.zig:602-643).
    609         case AST_NODE_TEST_DECL:
    610         case AST_NODE_ERRDEFER:
    611         case AST_NODE_DEFER:
    612         case AST_NODE_BOOL_NOT:
    613         case AST_NODE_NEGATION:
    614         case AST_NODE_BIT_NOT:
    615         case AST_NODE_NEGATION_WRAP:
    616         case AST_NODE_ADDRESS_OF:
    617         case AST_NODE_TRY:
    618         case AST_NODE_AWAIT:
    619         case AST_NODE_OPTIONAL_TYPE:
    620         case AST_NODE_SWITCH:
    621         case AST_NODE_SWITCH_COMMA:
    622         case AST_NODE_IF_SIMPLE:
    623         case AST_NODE_IF:
    624         case AST_NODE_SUSPEND:
    625         case AST_NODE_RESUME:
    626         case AST_NODE_CONTINUE:
    627         case AST_NODE_BREAK:
    628         case AST_NODE_RETURN:
    629         case AST_NODE_ANYFRAME_TYPE:
    630         case AST_NODE_IDENTIFIER:
    631         case AST_NODE_ANYFRAME_LITERAL:
    632         case AST_NODE_CHAR_LITERAL:
    633         case AST_NODE_NUMBER_LITERAL:
    634         case AST_NODE_UNREACHABLE_LITERAL:
    635         case AST_NODE_STRING_LITERAL:
    636         case AST_NODE_MULTILINE_STRING_LITERAL:
    637         case AST_NODE_GROUPED_EXPRESSION:
    638         case AST_NODE_BUILTIN_CALL_TWO:
    639         case AST_NODE_BUILTIN_CALL_TWO_COMMA:
    640         case AST_NODE_BUILTIN_CALL:
    641         case AST_NODE_BUILTIN_CALL_COMMA:
    642         case AST_NODE_ERROR_SET_DECL:
    643         case AST_NODE_COMPTIME:
    644         case AST_NODE_NOSUSPEND:
    645         case AST_NODE_ASM_SIMPLE:
    646         case AST_NODE_ASM:
    647         case AST_NODE_ARRAY_TYPE:
    648         case AST_NODE_ARRAY_TYPE_SENTINEL:
    649         case AST_NODE_ERROR_VALUE:
    650         case AST_NODE_PTR_TYPE_ALIGNED:
    651         case AST_NODE_PTR_TYPE_SENTINEL:
    652         case AST_NODE_PTR_TYPE:
    653         case AST_NODE_PTR_TYPE_BIT_RANGE:
    654             return tree->nodes.main_tokens[n];
    655 
    656         // Return main_token - 1: dot-prefixed inits and enum_literal
    657         // (Ast.zig:645-654).
    658         case AST_NODE_ARRAY_INIT_DOT:
    659         case AST_NODE_ARRAY_INIT_DOT_COMMA:
    660         case AST_NODE_ARRAY_INIT_DOT_TWO:
    661         case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA:
    662         case AST_NODE_STRUCT_INIT_DOT:
    663         case AST_NODE_STRUCT_INIT_DOT_COMMA:
    664         case AST_NODE_STRUCT_INIT_DOT_TWO:
    665         case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA:
    666         case AST_NODE_ENUM_LITERAL:
    667             return tree->nodes.main_tokens[n] - 1;
    668 
    669         // Recurse into LHS: all binary ops and compound expressions
    670         // (Ast.zig:656-733).
    671         case AST_NODE_CATCH:
    672         case AST_NODE_EQUAL_EQUAL:
    673         case AST_NODE_BANG_EQUAL:
    674         case AST_NODE_LESS_THAN:
    675         case AST_NODE_GREATER_THAN:
    676         case AST_NODE_LESS_OR_EQUAL:
    677         case AST_NODE_GREATER_OR_EQUAL:
    678         case AST_NODE_ASSIGN_MUL:
    679         case AST_NODE_ASSIGN_DIV:
    680         case AST_NODE_ASSIGN_MOD:
    681         case AST_NODE_ASSIGN_ADD:
    682         case AST_NODE_ASSIGN_SUB:
    683         case AST_NODE_ASSIGN_SHL:
    684         case AST_NODE_ASSIGN_SHL_SAT:
    685         case AST_NODE_ASSIGN_SHR:
    686         case AST_NODE_ASSIGN_BIT_AND:
    687         case AST_NODE_ASSIGN_BIT_XOR:
    688         case AST_NODE_ASSIGN_BIT_OR:
    689         case AST_NODE_ASSIGN_MUL_WRAP:
    690         case AST_NODE_ASSIGN_ADD_WRAP:
    691         case AST_NODE_ASSIGN_SUB_WRAP:
    692         case AST_NODE_ASSIGN_MUL_SAT:
    693         case AST_NODE_ASSIGN_ADD_SAT:
    694         case AST_NODE_ASSIGN_SUB_SAT:
    695         case AST_NODE_ASSIGN:
    696         case AST_NODE_MERGE_ERROR_SETS:
    697         case AST_NODE_MUL:
    698         case AST_NODE_DIV:
    699         case AST_NODE_MOD:
    700         case AST_NODE_ARRAY_MULT:
    701         case AST_NODE_MUL_WRAP:
    702         case AST_NODE_MUL_SAT:
    703         case AST_NODE_ADD:
    704         case AST_NODE_SUB:
    705         case AST_NODE_ARRAY_CAT:
    706         case AST_NODE_ADD_WRAP:
    707         case AST_NODE_SUB_WRAP:
    708         case AST_NODE_ADD_SAT:
    709         case AST_NODE_SUB_SAT:
    710         case AST_NODE_SHL:
    711         case AST_NODE_SHL_SAT:
    712         case AST_NODE_SHR:
    713         case AST_NODE_BIT_AND:
    714         case AST_NODE_BIT_XOR:
    715         case AST_NODE_BIT_OR:
    716         case AST_NODE_ORELSE:
    717         case AST_NODE_BOOL_AND:
    718         case AST_NODE_BOOL_OR:
    719         case AST_NODE_SLICE_OPEN:
    720         case AST_NODE_ARRAY_ACCESS:
    721         case AST_NODE_ARRAY_INIT_ONE:
    722         case AST_NODE_ARRAY_INIT_ONE_COMMA:
    723         case AST_NODE_SWITCH_RANGE:
    724         case AST_NODE_ERROR_UNION:
    725         case AST_NODE_FOR_RANGE:
    726         case AST_NODE_CALL_ONE:
    727         case AST_NODE_CALL_ONE_COMMA:
    728         case AST_NODE_STRUCT_INIT_ONE:
    729         case AST_NODE_STRUCT_INIT_ONE_COMMA:
    730         case AST_NODE_CALL:
    731         case AST_NODE_CALL_COMMA:
    732         case AST_NODE_STRUCT_INIT:
    733         case AST_NODE_STRUCT_INIT_COMMA:
    734         case AST_NODE_SLICE:
    735         case AST_NODE_SLICE_SENTINEL:
    736         case AST_NODE_ARRAY_INIT:
    737         case AST_NODE_ARRAY_INIT_COMMA:
    738         case AST_NODE_FIELD_ACCESS:
    739         case AST_NODE_UNWRAP_OPTIONAL:
    740         case AST_NODE_DEREF:
    741         case AST_NODE_ASYNC_CALL_ONE:
    742         case AST_NODE_ASYNC_CALL_ONE_COMMA:
    743         case AST_NODE_ASYNC_CALL:
    744         case AST_NODE_ASYNC_CALL_COMMA:
    745             n = tree->nodes.datas[n].lhs;
    746             continue;
    747 
    748         // Var decls: scan backwards for modifiers (Ast.zig:771-792).
    749         case AST_NODE_GLOBAL_VAR_DECL:
    750         case AST_NODE_LOCAL_VAR_DECL:
    751         case AST_NODE_SIMPLE_VAR_DECL:
    752         case AST_NODE_ALIGNED_VAR_DECL: {
    753             uint32_t mt = tree->nodes.main_tokens[n];
    754             uint32_t i = mt;
    755             while (i > 0) {
    756                 TokenizerTag tt = tree->tokens.tags[i - 1];
    757                 if (tt == TOKEN_KEYWORD_EXTERN || tt == TOKEN_KEYWORD_EXPORT
    758                     || tt == TOKEN_KEYWORD_PUB
    759                     || tt == TOKEN_KEYWORD_THREADLOCAL
    760                     || tt == TOKEN_KEYWORD_COMPTIME
    761                     || tt == TOKEN_STRING_LITERAL) {
    762                     i--;
    763                 } else {
    764                     break;
    765                 }
    766             }
    767             return i;
    768         }
    769         // Fn decls: scan backwards for modifiers (Ast.zig:737-759).
    770         case AST_NODE_FN_DECL:
    771         case AST_NODE_FN_PROTO_SIMPLE:
    772         case AST_NODE_FN_PROTO_MULTI:
    773         case AST_NODE_FN_PROTO_ONE:
    774         case AST_NODE_FN_PROTO: {
    775             uint32_t mt = tree->nodes.main_tokens[n];
    776             uint32_t i = mt;
    777             while (i > 0) {
    778                 TokenizerTag tt = tree->tokens.tags[i - 1];
    779                 if (tt == TOKEN_KEYWORD_EXTERN || tt == TOKEN_KEYWORD_EXPORT
    780                     || tt == TOKEN_KEYWORD_PUB || tt == TOKEN_KEYWORD_INLINE
    781                     || tt == TOKEN_KEYWORD_NOINLINE
    782                     || tt == TOKEN_STRING_LITERAL) {
    783                     i--;
    784                 } else {
    785                     break;
    786                 }
    787             }
    788             return i;
    789         }
    790         // Container fields: check for preceding comptime (Ast.zig:761-769).
    791         case AST_NODE_CONTAINER_FIELD_INIT:
    792         case AST_NODE_CONTAINER_FIELD_ALIGN:
    793         case AST_NODE_CONTAINER_FIELD: {
    794             uint32_t mt = tree->nodes.main_tokens[n];
    795             if (mt > 0 && tree->tokens.tags[mt - 1] == TOKEN_KEYWORD_COMPTIME)
    796                 return mt - 1;
    797             return mt;
    798         }
    799         // Blocks: check for label (Ast.zig:794-805).
    800         case AST_NODE_BLOCK:
    801         case AST_NODE_BLOCK_SEMICOLON:
    802         case AST_NODE_BLOCK_TWO:
    803         case AST_NODE_BLOCK_TWO_SEMICOLON: {
    804             uint32_t lbrace = tree->nodes.main_tokens[n];
    805             if (lbrace >= 2 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON
    806                 && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER)
    807                 return lbrace - 2;
    808             return lbrace;
    809         }
    810         // Fallback for any remaining node types.
    811         default:
    812             return tree->nodes.main_tokens[n];
    813         }
    814     }
    815 }
    816 
    817 // Mirrors AstGen.advanceSourceCursorToNode (AstGen.zig:13335).
    818 static void advanceSourceCursorToNode(AstGenCtx* ag, uint32_t node) {
    819     uint32_t ft = firstToken(ag->tree, node);
    820     uint32_t token_start = ag->tree->tokens.starts[ft];
    821     (void)0; // cursor backward check disabled temporarily
    822     advanceSourceCursor(ag, token_start);
    823 }
    824 
    825 // Mirrors maybeAdvanceSourceCursorToMainToken (AstGen.zig:13324).
    826 // Skips advancing when in comptime scope (matching upstream behavior).
    827 static void advanceSourceCursorToMainToken(
    828     AstGenCtx* ag, const GenZir* gz, uint32_t node) {
    829     if (gz->is_comptime)
    830         return;
    831     uint32_t main_tok = ag->tree->nodes.main_tokens[node];
    832     uint32_t token_start = ag->tree->tokens.starts[main_tok];
    833     advanceSourceCursor(ag, token_start);
    834 }
    835 
    836 // --- Token helpers ---
    837 
    838 // Mirrors GenZir.tokenIndexToRelative (AstGen.zig:11897).
    839 // Returns destination - base as i32.
    840 static int32_t tokenIndexToRelative(const GenZir* gz, uint32_t token) {
    841     uint32_t base = firstToken(gz->astgen->tree, gz->decl_node_index);
    842     return (int32_t)token - (int32_t)base;
    843 }
    844 
    845 // --- String bytes helpers ---
    846 
    847 // Search for an existing null-terminated string in string_bytes.
    848 // Returns the index if found, or UINT32_MAX if not found.
    849 // Mirrors string_table dedup (AstGen.zig:11564).
    850 // Find a string in string_table (registered strings only).
    851 // Mirrors AstGen.string_table hash table lookup.
    852 static uint32_t findExistingString(
    853     const AstGenCtx* ag, const char* str, uint32_t len) {
    854     for (uint32_t k = 0; k < ag->string_table_len; k++) {
    855         uint32_t pos = ag->string_table[k];
    856         // Compare: string at pos is null-terminated in string_bytes.
    857         const char* existing = (const char*)ag->string_bytes + pos;
    858         uint32_t existing_len = (uint32_t)strlen(existing);
    859         if (existing_len == len && memcmp(existing, str, len) == 0) {
    860             return pos;
    861         }
    862     }
    863     return UINT32_MAX;
    864 }
    865 
    866 // Register a string position in the string table for deduplication.
    867 static void registerString(AstGenCtx* ag, uint32_t pos) {
    868     if (ag->string_table_len >= ag->string_table_cap) {
    869         uint32_t new_cap = ag->string_table_cap * 2;
    870         if (new_cap < 64)
    871             new_cap = 64;
    872         uint32_t* p = realloc(ag->string_table, new_cap * sizeof(uint32_t));
    873         if (!p)
    874             exit(1);
    875         ag->string_table = p;
    876         ag->string_table_cap = new_cap;
    877     }
    878     ag->string_table[ag->string_table_len++] = pos;
    879 }
    880 
    881 // Mirrors AstGen.tokenIdentEql (AstGen.zig:6148-6152).
    882 // Compares two identifier tokens by source text without touching string_bytes.
    883 static bool tokenIdentEql(const Ast* tree, uint32_t tok1, uint32_t tok2) {
    884     uint32_t s1 = tree->tokens.starts[tok1];
    885     uint32_t s2 = tree->tokens.starts[tok2];
    886     uint32_t e1 = tree->tokens.starts[tok1 + 1];
    887     uint32_t e2 = tree->tokens.starts[tok2 + 1];
    888     // Token length includes trailing whitespace in starts delta, but for
    889     // identifiers the actual content is a contiguous alphanumeric/underscore
    890     // run. Compute actual identifier lengths.
    891     uint32_t len1 = 0;
    892     while (s1 + len1 < e1) {
    893         char c = tree->source[s1 + len1];
    894         if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
    895                 || (c >= '0' && c <= '9') || c == '_'))
    896             break;
    897         len1++;
    898     }
    899     uint32_t len2 = 0;
    900     while (s2 + len2 < e2) {
    901         char c = tree->source[s2 + len2];
    902         if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
    903                 || (c >= '0' && c <= '9') || c == '_'))
    904             break;
    905         len2++;
    906     }
    907     return len1 == len2
    908         && memcmp(tree->source + s1, tree->source + s2, len1) == 0;
    909 }
    910 
    911 // Forward declaration for strLitAsString (used by identAsString for @"..."
    912 // quoted identifiers with escapes).
    913 static void strLitAsString(AstGenCtx* ag, uint32_t str_lit_token,
    914     uint32_t* out_index, uint32_t* out_len);
    915 
    916 // Mirrors AstGen.identAsString (AstGen.zig:11530).
    917 // Handles both bare identifiers and @"..." quoted identifiers.
    918 static uint32_t identAsString(AstGenCtx* ag, uint32_t ident_token) {
    919     uint32_t start = ag->tree->tokens.starts[ident_token];
    920     const char* source = ag->tree->source;
    921 
    922     if (source[start] == '@' && start + 1 < ag->tree->source_len
    923         && source[start + 1] == '"') {
    924         // Quoted identifier: @"name" (AstGen.zig:11297-11308).
    925         // Extract content between quotes, handling escapes.
    926         uint32_t si, sl;
    927         // str_lit_token refers to the same token, content starts after @"
    928         // We reuse strLitAsString but offset by 1 to skip '@'.
    929         // Actually, strLitAsString expects a token whose source starts
    930         // with '"'. The @"..." token starts with '@'. We need to handle
    931         // the offset manually.
    932         uint32_t content_start = start + 2; // skip @"
    933         uint32_t content_end = content_start;
    934         while (
    935             content_end < ag->tree->source_len && source[content_end] != '"')
    936             content_end++;
    937         // Check for escapes.
    938         bool has_escapes = false;
    939         for (uint32_t j = content_start; j < content_end; j++) {
    940             if (source[j] == '\\') {
    941                 has_escapes = true;
    942                 break;
    943             }
    944         }
    945 
    946         if (!has_escapes) {
    947             uint32_t content_len = content_end - content_start;
    948             uint32_t existing
    949                 = findExistingString(ag, source + content_start, content_len);
    950             if (existing != UINT32_MAX)
    951                 return existing;
    952             uint32_t str_index = ag->string_bytes_len;
    953             ensureStringBytesCapacity(ag, content_len + 1);
    954             memcpy(ag->string_bytes + ag->string_bytes_len,
    955                 source + content_start, content_len);
    956             ag->string_bytes_len += content_len;
    957             ag->string_bytes[ag->string_bytes_len++] = 0;
    958             registerString(ag, str_index);
    959             return str_index;
    960         }
    961 
    962         // With escapes: use strLitAsString-like decoding.
    963         strLitAsString(ag, ident_token, &si, &sl);
    964         return si;
    965     }
    966 
    967     // Bare identifier: scan alphanumeric + underscore.
    968     uint32_t end = start;
    969     while (end < ag->tree->source_len) {
    970         char ch = source[end];
    971         if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
    972             || (ch >= '0' && ch <= '9') || ch == '_') {
    973             end++;
    974         } else {
    975             break;
    976         }
    977     }
    978     uint32_t ident_len = end - start;
    979 
    980     // Check for existing string (dedup).
    981     uint32_t existing = findExistingString(ag, source + start, ident_len);
    982     if (existing != UINT32_MAX)
    983         return existing;
    984 
    985     uint32_t str_index = ag->string_bytes_len;
    986     ensureStringBytesCapacity(ag, ident_len + 1);
    987     memcpy(ag->string_bytes + ag->string_bytes_len, source + start, ident_len);
    988     ag->string_bytes_len += ident_len;
    989     ag->string_bytes[ag->string_bytes_len++] = 0;
    990     registerString(ag, str_index);
    991     return str_index;
    992 }
    993 
    994 // Mirrors AstGen.strLitAsString (AstGen.zig:11553).
    995 // Decodes string literal, checks for embedded nulls.
    996 // If embedded null found: store raw bytes without trailing null, no dedup.
    997 // Otherwise: dedup via string_table, add trailing null.
    998 static void strLitAsString(AstGenCtx* ag, uint32_t str_lit_token,
    999     uint32_t* out_index, uint32_t* out_len) {
   1000     uint32_t tok_start = ag->tree->tokens.starts[str_lit_token];
   1001     const char* source = ag->tree->source;
   1002 
   1003     // Skip opening quote.
   1004     uint32_t i = tok_start + 1;
   1005     // Find closing quote, skipping escaped characters.
   1006     uint32_t raw_end = i;
   1007     while (raw_end < ag->tree->source_len) {
   1008         if (source[raw_end] == '\\') {
   1009             raw_end += 2; // skip escape + escaped char
   1010         } else if (source[raw_end] == '"') {
   1011             break;
   1012         } else {
   1013             raw_end++;
   1014         }
   1015     }
   1016 
   1017     // Check if there are any escape sequences.
   1018     bool has_escapes = false;
   1019     for (uint32_t j = i; j < raw_end; j++) {
   1020         if (source[j] == '\\') {
   1021             has_escapes = true;
   1022             break;
   1023         }
   1024     }
   1025 
   1026     if (!has_escapes) {
   1027         // Fast path: no escapes, no embedded nulls possible.
   1028         uint32_t content_len = raw_end - i;
   1029         uint32_t existing = findExistingString(ag, source + i, content_len);
   1030         if (existing != UINT32_MAX) {
   1031             *out_index = existing;
   1032             *out_len = content_len;
   1033             return;
   1034         }
   1035         uint32_t str_index = ag->string_bytes_len;
   1036         ensureStringBytesCapacity(ag, content_len + 1);
   1037         memcpy(
   1038             ag->string_bytes + ag->string_bytes_len, source + i, content_len);
   1039         ag->string_bytes_len += content_len;
   1040         ag->string_bytes[ag->string_bytes_len++] = 0;
   1041         registerString(ag, str_index);
   1042         *out_index = str_index;
   1043         *out_len = content_len;
   1044         return;
   1045     }
   1046 
   1047     // Slow path: process escape sequences (AstGen.zig:11558).
   1048     // Decode directly into string_bytes (like upstream).
   1049     uint32_t str_index = ag->string_bytes_len;
   1050     uint32_t max_len = raw_end - i;
   1051     ensureStringBytesCapacity(ag, max_len + 1);
   1052     while (i < raw_end) {
   1053         if (source[i] == '\\') {
   1054             i++;
   1055             if (i >= raw_end)
   1056                 break;
   1057             switch (source[i]) {
   1058             case 'n':
   1059                 ag->string_bytes[ag->string_bytes_len++] = '\n';
   1060                 break;
   1061             case 'r':
   1062                 ag->string_bytes[ag->string_bytes_len++] = '\r';
   1063                 break;
   1064             case 't':
   1065                 ag->string_bytes[ag->string_bytes_len++] = '\t';
   1066                 break;
   1067             case '\\':
   1068                 ag->string_bytes[ag->string_bytes_len++] = '\\';
   1069                 break;
   1070             case '\'':
   1071                 ag->string_bytes[ag->string_bytes_len++] = '\'';
   1072                 break;
   1073             case '"':
   1074                 ag->string_bytes[ag->string_bytes_len++] = '"';
   1075                 break;
   1076             case 'x': {
   1077                 // \xNN hex escape.
   1078                 uint8_t val = 0;
   1079                 for (int k = 0; k < 2 && i + 1 < raw_end; k++) {
   1080                     i++;
   1081                     char c = source[i];
   1082                     if (c >= '0' && c <= '9')
   1083                         val = (uint8_t)(val * 16 + (uint8_t)(c - '0'));
   1084                     else if (c >= 'a' && c <= 'f')
   1085                         val = (uint8_t)(val * 16 + 10 + (uint8_t)(c - 'a'));
   1086                     else if (c >= 'A' && c <= 'F')
   1087                         val = (uint8_t)(val * 16 + 10 + (uint8_t)(c - 'A'));
   1088                 }
   1089                 ag->string_bytes[ag->string_bytes_len++] = val;
   1090                 break;
   1091             }
   1092             case 'u': {
   1093                 // \u{NNNNNN} unicode escape (string_literal.zig:194-231).
   1094                 // Skip past '{'.
   1095                 i++;
   1096                 // Parse hex digits until '}'.
   1097                 uint32_t codepoint = 0;
   1098                 while (i + 1 < raw_end) {
   1099                     i++;
   1100                     char c = source[i];
   1101                     if (c >= '0' && c <= '9') {
   1102                         codepoint = codepoint * 16 + (uint32_t)(c - '0');
   1103                     } else if (c >= 'a' && c <= 'f') {
   1104                         codepoint = codepoint * 16 + 10 + (uint32_t)(c - 'a');
   1105                     } else if (c >= 'A' && c <= 'F') {
   1106                         codepoint = codepoint * 16 + 10 + (uint32_t)(c - 'A');
   1107                     } else {
   1108                         // Must be '}', done.
   1109                         break;
   1110                     }
   1111                 }
   1112                 // Encode codepoint as UTF-8 (unicode.zig:53-82).
   1113                 if (codepoint <= 0x7F) {
   1114                     ag->string_bytes[ag->string_bytes_len++]
   1115                         = (uint8_t)codepoint;
   1116                 } else if (codepoint <= 0x7FF) {
   1117                     ag->string_bytes[ag->string_bytes_len++]
   1118                         = (uint8_t)(0xC0 | (codepoint >> 6));
   1119                     ag->string_bytes[ag->string_bytes_len++]
   1120                         = (uint8_t)(0x80 | (codepoint & 0x3F));
   1121                 } else if (codepoint <= 0xFFFF) {
   1122                     ag->string_bytes[ag->string_bytes_len++]
   1123                         = (uint8_t)(0xE0 | (codepoint >> 12));
   1124                     ag->string_bytes[ag->string_bytes_len++]
   1125                         = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F));
   1126                     ag->string_bytes[ag->string_bytes_len++]
   1127                         = (uint8_t)(0x80 | (codepoint & 0x3F));
   1128                 } else {
   1129                     ag->string_bytes[ag->string_bytes_len++]
   1130                         = (uint8_t)(0xF0 | (codepoint >> 18));
   1131                     ag->string_bytes[ag->string_bytes_len++]
   1132                         = (uint8_t)(0x80 | ((codepoint >> 12) & 0x3F));
   1133                     ag->string_bytes[ag->string_bytes_len++]
   1134                         = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F));
   1135                     ag->string_bytes[ag->string_bytes_len++]
   1136                         = (uint8_t)(0x80 | (codepoint & 0x3F));
   1137                 }
   1138                 break;
   1139             }
   1140             default:
   1141                 ag->string_bytes[ag->string_bytes_len++] = (uint8_t)source[i];
   1142                 break;
   1143             }
   1144         } else {
   1145             ag->string_bytes[ag->string_bytes_len++] = (uint8_t)source[i];
   1146         }
   1147         i++;
   1148     }
   1149     uint32_t decoded_len = ag->string_bytes_len - str_index;
   1150     uint8_t* key = ag->string_bytes + str_index;
   1151 
   1152     // Check for embedded null bytes (AstGen.zig:11560).
   1153     // If found, skip dedup and don't add trailing null.
   1154     bool has_embedded_null = false;
   1155     for (uint32_t j = 0; j < decoded_len; j++) {
   1156         if (key[j] == 0) {
   1157             has_embedded_null = true;
   1158             break;
   1159         }
   1160     }
   1161     if (has_embedded_null) {
   1162         *out_index = str_index;
   1163         *out_len = decoded_len;
   1164         return;
   1165     }
   1166 
   1167     // Dedup against string_table (AstGen.zig:11564-11585).
   1168     uint32_t existing = findExistingString(ag, (const char*)key, decoded_len);
   1169     if (existing != UINT32_MAX) {
   1170         // Shrink back (AstGen.zig:11570).
   1171         ag->string_bytes_len = str_index;
   1172         *out_index = existing;
   1173         *out_len = decoded_len;
   1174         return;
   1175     }
   1176 
   1177     // New entry: add trailing null and register.
   1178     ensureStringBytesCapacity(ag, 1);
   1179     ag->string_bytes[ag->string_bytes_len++] = 0;
   1180     registerString(ag, str_index);
   1181     *out_index = str_index;
   1182     *out_len = decoded_len;
   1183 }
   1184 
   1185 // --- Declaration helpers ---
   1186 
   1187 // Mirrors GenZir.makeDeclaration (AstGen.zig:12906).
   1188 static uint32_t makeDeclaration(AstGenCtx* ag, uint32_t node) {
   1189     ensureInstCapacity(ag, 1);
   1190     uint32_t idx = ag->inst_len;
   1191     ag->inst_tags[idx] = ZIR_INST_DECLARATION;
   1192     ZirInstData data;
   1193     memset(&data, 0, sizeof(data));
   1194     data.declaration.src_node = node;
   1195     // payload_index is set later by setDeclaration.
   1196     ag->inst_datas[idx] = data;
   1197     ag->inst_len++;
   1198     return idx;
   1199 }
   1200 
   1201 // Mirrors GenZir.makeBreakCommon (AstGen.zig:12667).
   1202 // Creates a break_inline instruction with a Break payload in extra.
   1203 // Records the instruction in the GenZir body.
   1204 static uint32_t makeBreakInline(GenZir* gz, uint32_t block_inst,
   1205     uint32_t operand, int32_t operand_src_node) {
   1206     AstGenCtx* ag = gz->astgen;
   1207     ensureInstCapacity(ag, 1);
   1208     ensureExtraCapacity(ag, 2);
   1209 
   1210     // Write Zir.Inst.Break payload to extra (Zir.zig:2489).
   1211     uint32_t payload_index = ag->extra_len;
   1212     ag->extra[ag->extra_len++] = (uint32_t)operand_src_node;
   1213     ag->extra[ag->extra_len++] = block_inst;
   1214 
   1215     uint32_t idx = ag->inst_len;
   1216     ag->inst_tags[idx] = ZIR_INST_BREAK_INLINE;
   1217     ZirInstData data;
   1218     data.break_data.operand = operand;
   1219     data.break_data.payload_index = payload_index;
   1220     ag->inst_datas[idx] = data;
   1221     ag->inst_len++;
   1222 
   1223     // Record in sub-block body.
   1224     gzAppendInstruction(gz, idx);
   1225     return idx;
   1226 }
   1227 
   1228 // Mirrors GenZir.makeBlockInst (AstGen.zig:12890).
   1229 // Creates a pl_node instruction with payload_index left as 0 (set later).
   1230 // Does NOT append to gz's instruction list.
   1231 // Returns instruction index (not a ref).
   1232 static uint32_t makeBlockInst(
   1233     AstGenCtx* ag, ZirInstTag tag, const GenZir* gz, uint32_t node) {
   1234     ensureInstCapacity(ag, 1);
   1235     uint32_t idx = ag->inst_len;
   1236     ag->inst_tags[idx] = tag;
   1237     ZirInstData data;
   1238     memset(&data, 0, sizeof(data));
   1239     data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   1240     data.pl_node.payload_index = 0; // set later
   1241     ag->inst_datas[idx] = data;
   1242     ag->inst_len++;
   1243     return idx;
   1244 }
   1245 
   1246 // Mirrors appendPossiblyRefdBodyInst (AstGen.zig:13675-13683).
   1247 // Appends body_inst first, then recursively appends ref_table entry.
   1248 static void appendPossiblyRefdBodyInst(AstGenCtx* ag, uint32_t body_inst) {
   1249     ag->extra[ag->extra_len++] = body_inst;
   1250     uint32_t ref_inst;
   1251     if (refTableFetchRemove(ag, body_inst, &ref_inst)) {
   1252         appendPossiblyRefdBodyInst(ag, ref_inst);
   1253     }
   1254 }
   1255 
   1256 // Mirrors appendBodyWithFixupsExtraRefsArrayList (AstGen.zig:13659-13673).
   1257 // First processes extra_refs (e.g. param_insts), prepending their ref_table
   1258 // entries. Then writes body instructions with ref_table fixups.
   1259 static void appendBodyWithFixupsExtraRefs(AstGenCtx* ag, const uint32_t* body,
   1260     uint32_t body_len, const uint32_t* extra_refs, uint32_t extra_refs_len) {
   1261     for (uint32_t i = 0; i < extra_refs_len; i++) {
   1262         uint32_t ref_inst;
   1263         if (refTableFetchRemove(ag, extra_refs[i], &ref_inst)) {
   1264             appendPossiblyRefdBodyInst(ag, ref_inst);
   1265         }
   1266     }
   1267     for (uint32_t i = 0; i < body_len; i++) {
   1268         appendPossiblyRefdBodyInst(ag, body[i]);
   1269     }
   1270 }
   1271 
   1272 // Scratch extra capacity helper (for call arg bodies).
   1273 static void ensureScratchExtraCapacity(AstGenCtx* ag, uint32_t additional) {
   1274     uint32_t needed = ag->scratch_extra_len + additional;
   1275     if (needed > ag->scratch_extra_cap) {
   1276         uint32_t new_cap = ag->scratch_extra_cap * 2;
   1277         if (new_cap < needed)
   1278             new_cap = needed;
   1279         if (new_cap < 64)
   1280             new_cap = 64;
   1281         uint32_t* p = realloc(ag->scratch_extra, new_cap * sizeof(uint32_t));
   1282         if (!p)
   1283             exit(1);
   1284         ag->scratch_extra = p;
   1285         ag->scratch_extra_cap = new_cap;
   1286     }
   1287 }
   1288 
   1289 // Like appendPossiblyRefdBodyInst but appends to scratch_extra instead of
   1290 // extra.
   1291 static void appendPossiblyRefdBodyInstScratch(
   1292     AstGenCtx* ag, uint32_t body_inst) {
   1293     ag->scratch_extra[ag->scratch_extra_len++] = body_inst;
   1294     uint32_t ref_inst;
   1295     if (refTableFetchRemove(ag, body_inst, &ref_inst)) {
   1296         ensureScratchExtraCapacity(ag, 1);
   1297         appendPossiblyRefdBodyInstScratch(ag, ref_inst);
   1298     }
   1299 }
   1300 
   1301 // Mirrors countBodyLenAfterFixupsExtraRefs (AstGen.zig:13694-13711).
   1302 static uint32_t countBodyLenAfterFixupsExtraRefs(AstGenCtx* ag,
   1303     const uint32_t* body, uint32_t body_len, const uint32_t* extra_refs,
   1304     uint32_t extra_refs_len) {
   1305     uint32_t count = body_len;
   1306     for (uint32_t i = 0; i < body_len; i++) {
   1307         uint32_t check_inst = body[i];
   1308         const uint32_t* ref;
   1309         while ((ref = refTableGet(ag, check_inst)) != NULL) {
   1310             count++;
   1311             check_inst = *ref;
   1312         }
   1313     }
   1314     for (uint32_t i = 0; i < extra_refs_len; i++) {
   1315         uint32_t check_inst = extra_refs[i];
   1316         const uint32_t* ref;
   1317         while ((ref = refTableGet(ag, check_inst)) != NULL) {
   1318             count++;
   1319             check_inst = *ref;
   1320         }
   1321     }
   1322     return count;
   1323 }
   1324 
   1325 // Mirrors countBodyLenAfterFixups (AstGen.zig:13686-13688).
   1326 static uint32_t countBodyLenAfterFixups(
   1327     AstGenCtx* ag, const uint32_t* body, uint32_t body_len) {
   1328     return countBodyLenAfterFixupsExtraRefs(ag, body, body_len, NULL, 0);
   1329 }
   1330 
   1331 // Mirrors GenZir.setBlockBody (AstGen.zig:11949).
   1332 // Writes Block payload (body_len + instruction indices) to extra.
   1333 // Sets the instruction's payload_index. Unstacks gz.
   1334 static void setBlockBody(AstGenCtx* ag, GenZir* gz, uint32_t inst) {
   1335     uint32_t raw_body_len = gzInstructionsLen(gz);
   1336     const uint32_t* body = gzInstructionsSlice(gz);
   1337     uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len);
   1338     ensureExtraCapacity(ag, 1 + body_len);
   1339     uint32_t payload_index = ag->extra_len;
   1340     ag->extra[ag->extra_len++] = body_len;
   1341     for (uint32_t i = 0; i < raw_body_len; i++) {
   1342         appendPossiblyRefdBodyInst(ag, body[i]);
   1343     }
   1344     ag->inst_datas[inst].pl_node.payload_index = payload_index;
   1345     gzUnstack(gz);
   1346 }
   1347 
   1348 // Mirrors GenZir.setTryBody (AstGen.zig:11997).
   1349 // Writes Try payload (operand + body_len + instruction indices) to extra.
   1350 // Sets the instruction's payload_index. Unstacks gz.
   1351 static void setTryBody(
   1352     AstGenCtx* ag, GenZir* gz, uint32_t inst, uint32_t operand) {
   1353     uint32_t raw_body_len = gzInstructionsLen(gz);
   1354     const uint32_t* body = gzInstructionsSlice(gz);
   1355     uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len);
   1356     ensureExtraCapacity(ag, 2 + body_len);
   1357     uint32_t payload_index = ag->extra_len;
   1358     ag->extra[ag->extra_len++] = operand; // Try.operand
   1359     ag->extra[ag->extra_len++] = body_len; // Try.body_len
   1360     for (uint32_t i = 0; i < raw_body_len; i++) {
   1361         appendPossiblyRefdBodyInst(ag, body[i]);
   1362     }
   1363     ag->inst_datas[inst].pl_node.payload_index = payload_index;
   1364     gzUnstack(gz);
   1365 }
   1366 
   1367 // Mirrors GenZir.setBlockComptimeBody (AstGen.zig:11972).
   1368 // Like setBlockBody but prepends comptime_reason before body_len.
   1369 // Asserts inst is a BLOCK_COMPTIME.
   1370 static void setBlockComptimeBody(
   1371     AstGenCtx* ag, GenZir* gz, uint32_t inst, uint32_t comptime_reason) {
   1372     uint32_t raw_body_len = gzInstructionsLen(gz);
   1373     const uint32_t* body = gzInstructionsSlice(gz);
   1374     uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len);
   1375     ensureExtraCapacity(ag, 2 + body_len);
   1376     uint32_t payload_index = ag->extra_len;
   1377     ag->extra[ag->extra_len++] = comptime_reason;
   1378     ag->extra[ag->extra_len++] = body_len;
   1379     for (uint32_t i = 0; i < raw_body_len; i++) {
   1380         appendPossiblyRefdBodyInst(ag, body[i]);
   1381     }
   1382     ag->inst_datas[inst].pl_node.payload_index = payload_index;
   1383     gzUnstack(gz);
   1384 }
   1385 
   1386 // Mirrors GenZir.addBreak (AstGen.zig:12623).
   1387 // Creates a ZIR_INST_BREAK instruction.
   1388 static uint32_t addBreak(GenZir* gz, ZirInstTag tag, uint32_t block_inst,
   1389     uint32_t operand, int32_t operand_src_node) {
   1390     AstGenCtx* ag = gz->astgen;
   1391     ensureInstCapacity(ag, 1);
   1392     ensureExtraCapacity(ag, 2);
   1393 
   1394     uint32_t payload_index = ag->extra_len;
   1395     ag->extra[ag->extra_len++] = (uint32_t)operand_src_node;
   1396     ag->extra[ag->extra_len++] = block_inst;
   1397 
   1398     uint32_t idx = ag->inst_len;
   1399     ag->inst_tags[idx] = tag;
   1400     ZirInstData data;
   1401     data.break_data.operand = operand;
   1402     data.break_data.payload_index = payload_index;
   1403     ag->inst_datas[idx] = data;
   1404     ag->inst_len++;
   1405     gzAppendInstruction(gz, idx);
   1406     return idx;
   1407 }
   1408 
   1409 // Mirrors GenZir.addCondBr (AstGen.zig:12834).
   1410 // Creates condbr instruction placeholder with src_node set.
   1411 // Payload is filled later by setCondBrPayload.
   1412 static uint32_t addCondBr(GenZir* gz, ZirInstTag tag, uint32_t node) {
   1413     AstGenCtx* ag = gz->astgen;
   1414     ensureInstCapacity(ag, 1);
   1415     uint32_t idx = ag->inst_len;
   1416     ag->inst_tags[idx] = tag;
   1417     ZirInstData data;
   1418     memset(&data, 0, sizeof(data));
   1419     data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   1420     data.pl_node.payload_index = 0; // set later
   1421     ag->inst_datas[idx] = data;
   1422     ag->inst_len++;
   1423     gzAppendInstruction(gz, idx);
   1424     return idx;
   1425 }
   1426 
   1427 // Mirrors setCondBrPayload (AstGen.zig:6501).
   1428 // Writes CondBr payload: {condition, then_body_len, else_body_len} then
   1429 // then_body instructions, then else_body instructions. Unstacks both scopes.
   1430 // IMPORTANT: then_gz and else_gz are stacked (else on top of then), so
   1431 // then's instructions must use instructionsSliceUpto(else_gz) to avoid
   1432 // including else_gz's instructions in then's body.
   1433 static void setCondBrPayload(AstGenCtx* ag, uint32_t condbr_inst,
   1434     uint32_t condition, GenZir* then_gz, GenZir* else_gz) {
   1435     uint32_t raw_then_len = gzInstructionsLenUpto(then_gz, else_gz);
   1436     const uint32_t* then_body = gzInstructionsSliceUpto(then_gz, else_gz);
   1437     uint32_t raw_else_len = gzInstructionsLen(else_gz);
   1438     const uint32_t* else_body = gzInstructionsSlice(else_gz);
   1439 
   1440     uint32_t then_len = countBodyLenAfterFixups(ag, then_body, raw_then_len);
   1441     uint32_t else_len = countBodyLenAfterFixups(ag, else_body, raw_else_len);
   1442 
   1443     ensureExtraCapacity(ag, 3 + then_len + else_len);
   1444     uint32_t payload_index = ag->extra_len;
   1445     ag->extra[ag->extra_len++] = condition; // CondBr.condition
   1446     ag->extra[ag->extra_len++] = then_len; // CondBr.then_body_len
   1447     ag->extra[ag->extra_len++] = else_len; // CondBr.else_body_len
   1448     for (uint32_t i = 0; i < raw_then_len; i++)
   1449         appendPossiblyRefdBodyInst(ag, then_body[i]);
   1450     for (uint32_t i = 0; i < raw_else_len; i++)
   1451         appendPossiblyRefdBodyInst(ag, else_body[i]);
   1452 
   1453     ag->inst_datas[condbr_inst].pl_node.payload_index = payload_index;
   1454     gzUnstack(else_gz);
   1455     gzUnstack(then_gz);
   1456 }
   1457 
   1458 // Does this Declaration.Flags.Id have a name? (Zir.zig:2762)
   1459 static bool declIdHasName(DeclFlagsId id) {
   1460     return id != DECL_ID_UNNAMED_TEST && id != DECL_ID_COMPTIME;
   1461 }
   1462 
   1463 // Does this Declaration.Flags.Id have a lib name? (Zir.zig:2771)
   1464 static bool declIdHasLibName(DeclFlagsId id) {
   1465     switch (id) {
   1466     case DECL_ID_EXTERN_CONST:
   1467     case DECL_ID_PUB_EXTERN_CONST:
   1468     case DECL_ID_EXTERN_VAR:
   1469     case DECL_ID_EXTERN_VAR_THREADLOCAL:
   1470     case DECL_ID_PUB_EXTERN_VAR:
   1471     case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL:
   1472         return true;
   1473     default:
   1474         return false;
   1475     }
   1476 }
   1477 
   1478 // Does this Declaration.Flags.Id have a type body? (Zir.zig:2783)
   1479 static bool declIdHasTypeBody(DeclFlagsId id) {
   1480     switch (id) {
   1481     case DECL_ID_UNNAMED_TEST:
   1482     case DECL_ID_TEST:
   1483     case DECL_ID_DECLTEST:
   1484     case DECL_ID_COMPTIME:
   1485     case DECL_ID_CONST_SIMPLE:
   1486     case DECL_ID_PUB_CONST_SIMPLE:
   1487     case DECL_ID_VAR_SIMPLE:
   1488     case DECL_ID_PUB_VAR_SIMPLE:
   1489         return false;
   1490     default:
   1491         return true;
   1492     }
   1493 }
   1494 
   1495 // Does this Declaration.Flags.Id have a value body? (Zir.zig:2800)
   1496 static bool declIdHasValueBody(DeclFlagsId id) {
   1497     switch (id) {
   1498     case DECL_ID_EXTERN_CONST_SIMPLE:
   1499     case DECL_ID_EXTERN_CONST:
   1500     case DECL_ID_PUB_EXTERN_CONST_SIMPLE:
   1501     case DECL_ID_PUB_EXTERN_CONST:
   1502     case DECL_ID_EXTERN_VAR:
   1503     case DECL_ID_EXTERN_VAR_THREADLOCAL:
   1504     case DECL_ID_PUB_EXTERN_VAR:
   1505     case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL:
   1506         return false;
   1507     default:
   1508         return true;
   1509     }
   1510 }
   1511 
   1512 // Does this Declaration.Flags.Id have special bodies? (Zir.zig:2815)
   1513 static bool declIdHasSpecialBodies(DeclFlagsId id) {
   1514     switch (id) {
   1515     case DECL_ID_UNNAMED_TEST:
   1516     case DECL_ID_TEST:
   1517     case DECL_ID_DECLTEST:
   1518     case DECL_ID_COMPTIME:
   1519     case DECL_ID_CONST_SIMPLE:
   1520     case DECL_ID_CONST_TYPED:
   1521     case DECL_ID_PUB_CONST_SIMPLE:
   1522     case DECL_ID_PUB_CONST_TYPED:
   1523     case DECL_ID_EXTERN_CONST_SIMPLE:
   1524     case DECL_ID_PUB_EXTERN_CONST_SIMPLE:
   1525     case DECL_ID_VAR_SIMPLE:
   1526     case DECL_ID_PUB_VAR_SIMPLE:
   1527         return false;
   1528     default:
   1529         return true;
   1530     }
   1531 }
   1532 
   1533 // Mirrors setDeclaration (AstGen.zig:13883).
   1534 // Full version with type/align/linksection/addrspace/value bodies.
   1535 typedef struct {
   1536     uint32_t src_line;
   1537     uint32_t src_column;
   1538     DeclFlagsId id;
   1539     uint32_t name; // NullTerminatedString index
   1540     uint32_t lib_name; // NullTerminatedString index (UINT32_MAX=none)
   1541     const uint32_t* type_body;
   1542     uint32_t type_body_len;
   1543     const uint32_t* align_body;
   1544     uint32_t align_body_len;
   1545     const uint32_t* linksection_body;
   1546     uint32_t linksection_body_len;
   1547     const uint32_t* addrspace_body;
   1548     uint32_t addrspace_body_len;
   1549     const uint32_t* value_body;
   1550     uint32_t value_body_len;
   1551 } SetDeclArgs;
   1552 
   1553 static void setDeclaration(
   1554     AstGenCtx* ag, uint32_t decl_inst, SetDeclArgs args) {
   1555     DeclFlagsId id = args.id;
   1556     bool has_name = declIdHasName(id);
   1557     bool has_lib_name = declIdHasLibName(id);
   1558     bool has_type_body_field = declIdHasTypeBody(id);
   1559     bool has_special_bodies = declIdHasSpecialBodies(id);
   1560     bool has_value_body_field = declIdHasValueBody(id);
   1561 
   1562     uint32_t type_len
   1563         = countBodyLenAfterFixups(ag, args.type_body, args.type_body_len);
   1564     uint32_t align_len
   1565         = countBodyLenAfterFixups(ag, args.align_body, args.align_body_len);
   1566     uint32_t linksection_len = countBodyLenAfterFixups(
   1567         ag, args.linksection_body, args.linksection_body_len);
   1568     uint32_t addrspace_len = countBodyLenAfterFixups(
   1569         ag, args.addrspace_body, args.addrspace_body_len);
   1570     uint32_t value_len
   1571         = countBodyLenAfterFixups(ag, args.value_body, args.value_body_len);
   1572 
   1573     uint32_t need = 6; // src_hash[4] + flags[2]
   1574     if (has_name)
   1575         need++;
   1576     if (has_lib_name)
   1577         need++;
   1578     if (has_type_body_field)
   1579         need++;
   1580     if (has_special_bodies)
   1581         need += 3;
   1582     if (has_value_body_field)
   1583         need++;
   1584     need += type_len + align_len + linksection_len + addrspace_len + value_len;
   1585     ensureExtraCapacity(ag, need);
   1586 
   1587     uint32_t payload_start = ag->extra_len;
   1588 
   1589     // src_hash (4 words): zero-filled; hash comparison skipped in tests.
   1590     ag->extra[ag->extra_len++] = 0;
   1591     ag->extra[ag->extra_len++] = 0;
   1592     ag->extra[ag->extra_len++] = 0;
   1593     ag->extra[ag->extra_len++] = 0;
   1594 
   1595     // Declaration.Flags: packed struct(u64) { src_line: u30, src_column: u29,
   1596     // id: u5 } (Zir.zig:2719)
   1597     uint64_t flags = 0;
   1598     flags |= (uint64_t)(args.src_line & 0x3FFFFFFFu);
   1599     flags |= (uint64_t)(args.src_column & 0x1FFFFFFFu) << 30;
   1600     flags |= (uint64_t)((uint32_t)id & 0x1Fu) << 59;
   1601     ag->extra[ag->extra_len++] = (uint32_t)(flags & 0xFFFFFFFFu);
   1602     ag->extra[ag->extra_len++] = (uint32_t)(flags >> 32);
   1603 
   1604     if (has_name)
   1605         ag->extra[ag->extra_len++] = args.name;
   1606     if (has_lib_name) {
   1607         ag->extra[ag->extra_len++]
   1608             = (args.lib_name != UINT32_MAX) ? args.lib_name : 0;
   1609     }
   1610     if (has_type_body_field)
   1611         ag->extra[ag->extra_len++] = type_len;
   1612     if (has_special_bodies) {
   1613         ag->extra[ag->extra_len++] = align_len;
   1614         ag->extra[ag->extra_len++] = linksection_len;
   1615         ag->extra[ag->extra_len++] = addrspace_len;
   1616     }
   1617     if (has_value_body_field)
   1618         ag->extra[ag->extra_len++] = value_len;
   1619 
   1620     for (uint32_t i = 0; i < args.type_body_len; i++)
   1621         appendPossiblyRefdBodyInst(ag, args.type_body[i]);
   1622     for (uint32_t i = 0; i < args.align_body_len; i++)
   1623         appendPossiblyRefdBodyInst(ag, args.align_body[i]);
   1624     for (uint32_t i = 0; i < args.linksection_body_len; i++)
   1625         appendPossiblyRefdBodyInst(ag, args.linksection_body[i]);
   1626     for (uint32_t i = 0; i < args.addrspace_body_len; i++)
   1627         appendPossiblyRefdBodyInst(ag, args.addrspace_body[i]);
   1628     for (uint32_t i = 0; i < args.value_body_len; i++)
   1629         appendPossiblyRefdBodyInst(ag, args.value_body[i]);
   1630 
   1631     ag->inst_datas[decl_inst].declaration.payload_index = payload_start;
   1632 }
   1633 
   1634 // --- StructDecl.Small packing (Zir.zig StructDecl.Small) ---
   1635 
   1636 typedef struct {
   1637     bool has_captures_len;
   1638     bool has_fields_len;
   1639     bool has_decls_len;
   1640     bool has_backing_int;
   1641     bool known_non_opv;
   1642     bool known_comptime_only;
   1643     uint8_t name_strategy; // 2 bits
   1644     uint8_t layout; // 2 bits
   1645     bool any_default_inits;
   1646     bool any_comptime_fields;
   1647     bool any_aligned_fields;
   1648 } StructDeclSmall;
   1649 
   1650 static uint16_t packStructDeclSmall(StructDeclSmall s) {
   1651     uint16_t r = 0;
   1652     if (s.has_captures_len)
   1653         r |= (1u << 0);
   1654     if (s.has_fields_len)
   1655         r |= (1u << 1);
   1656     if (s.has_decls_len)
   1657         r |= (1u << 2);
   1658     if (s.has_backing_int)
   1659         r |= (1u << 3);
   1660     if (s.known_non_opv)
   1661         r |= (1u << 4);
   1662     if (s.known_comptime_only)
   1663         r |= (1u << 5);
   1664     r |= (uint16_t)(s.name_strategy & 0x3u) << 6;
   1665     r |= (uint16_t)(s.layout & 0x3u) << 8;
   1666     if (s.any_default_inits)
   1667         r |= (1u << 10);
   1668     if (s.any_comptime_fields)
   1669         r |= (1u << 11);
   1670     if (s.any_aligned_fields)
   1671         r |= (1u << 12);
   1672     return r;
   1673 }
   1674 
   1675 // Mirrors GenZir.setStruct (AstGen.zig:12935).
   1676 // Writes StructDecl payload and optional length fields.
   1677 // The caller appends captures, backing_int, decls, fields, bodies after.
   1678 static void setStruct(AstGenCtx* ag, uint32_t inst, uint32_t src_node,
   1679     StructDeclSmall small, uint32_t captures_len, uint32_t fields_len,
   1680     uint32_t decls_len) {
   1681     ensureExtraCapacity(ag, 6 + 3);
   1682 
   1683     uint32_t payload_index = ag->extra_len;
   1684 
   1685     // fields_hash (4 words): zero-filled; hash comparison skipped in tests.
   1686     ag->extra[ag->extra_len++] = 0;
   1687     ag->extra[ag->extra_len++] = 0;
   1688     ag->extra[ag->extra_len++] = 0;
   1689     ag->extra[ag->extra_len++] = 0;
   1690 
   1691     ag->extra[ag->extra_len++] = ag->source_line;
   1692     ag->extra[ag->extra_len++] = src_node;
   1693 
   1694     if (small.has_captures_len)
   1695         ag->extra[ag->extra_len++] = captures_len;
   1696     if (small.has_fields_len)
   1697         ag->extra[ag->extra_len++] = fields_len;
   1698     if (small.has_decls_len)
   1699         ag->extra[ag->extra_len++] = decls_len;
   1700 
   1701     ag->inst_tags[inst] = ZIR_INST_EXTENDED;
   1702     ZirInstData data;
   1703     memset(&data, 0, sizeof(data));
   1704     data.extended.opcode = (uint16_t)ZIR_EXT_STRUCT_DECL;
   1705     data.extended.small = packStructDeclSmall(small);
   1706     data.extended.operand = payload_index;
   1707     ag->inst_datas[inst] = data;
   1708 }
   1709 
   1710 // --- scanContainer (AstGen.zig:13384) ---
   1711 
   1712 // Add a name→node entry to the decl table.
   1713 static void addDeclToTable(
   1714     AstGenCtx* ag, uint32_t name_str_index, uint32_t node) {
   1715     if (ag->decl_table_len >= ag->decl_table_cap) {
   1716         uint32_t new_cap = ag->decl_table_cap > 0 ? ag->decl_table_cap * 2 : 8;
   1717         uint32_t* n = realloc(ag->decl_names, new_cap * sizeof(uint32_t));
   1718         uint32_t* d = realloc(ag->decl_nodes, new_cap * sizeof(uint32_t));
   1719         if (!n || !d)
   1720             exit(1);
   1721         ag->decl_names = n;
   1722         ag->decl_nodes = d;
   1723         ag->decl_table_cap = new_cap;
   1724     }
   1725     ag->decl_names[ag->decl_table_len] = name_str_index;
   1726     ag->decl_nodes[ag->decl_table_len] = node;
   1727     ag->decl_table_len++;
   1728 }
   1729 
   1730 // Mirrors scanContainer (AstGen.zig:13384).
   1731 // Also populates the decl table (namespace.decls) for identifier resolution.
   1732 static uint32_t scanContainer(
   1733     AstGenCtx* ag, const uint32_t* members, uint32_t member_count) {
   1734     const Ast* tree = ag->tree;
   1735     uint32_t decl_count = 0;
   1736     for (uint32_t i = 0; i < member_count; i++) {
   1737         uint32_t member = members[i];
   1738         AstNodeTag tag = tree->nodes.tags[member];
   1739         switch (tag) {
   1740         case AST_NODE_GLOBAL_VAR_DECL:
   1741         case AST_NODE_LOCAL_VAR_DECL:
   1742         case AST_NODE_SIMPLE_VAR_DECL:
   1743         case AST_NODE_ALIGNED_VAR_DECL: {
   1744             decl_count++;
   1745             uint32_t name_token = tree->nodes.main_tokens[member] + 1;
   1746             uint32_t name_str = identAsString(ag, name_token);
   1747             addDeclToTable(ag, name_str, member);
   1748             break;
   1749         }
   1750         case AST_NODE_FN_PROTO_SIMPLE:
   1751         case AST_NODE_FN_PROTO_MULTI:
   1752         case AST_NODE_FN_PROTO_ONE:
   1753         case AST_NODE_FN_PROTO:
   1754         case AST_NODE_FN_DECL: {
   1755             decl_count++;
   1756             uint32_t name_token = tree->nodes.main_tokens[member] + 1;
   1757             uint32_t name_str = identAsString(ag, name_token);
   1758             addDeclToTable(ag, name_str, member);
   1759             break;
   1760         }
   1761         // Container fields: add field name to string table for ordering
   1762         // (AstGen.zig:13509).
   1763         case AST_NODE_CONTAINER_FIELD_INIT:
   1764         case AST_NODE_CONTAINER_FIELD_ALIGN:
   1765         case AST_NODE_CONTAINER_FIELD: {
   1766             uint32_t main_token = tree->nodes.main_tokens[member];
   1767             identAsString(ag, main_token);
   1768             break;
   1769         }
   1770         case AST_NODE_COMPTIME:
   1771             decl_count++;
   1772             break;
   1773         case AST_NODE_TEST_DECL: {
   1774             decl_count++;
   1775             // Process test name string to match upstream string table
   1776             // ordering (AstGen.zig:13465-13500).
   1777             uint32_t test_name_token = tree->nodes.main_tokens[member] + 1;
   1778             TokenizerTag tt = tree->tokens.tags[test_name_token];
   1779             if (tt == TOKEN_STRING_LITERAL) {
   1780                 uint32_t si, sl;
   1781                 strLitAsString(ag, test_name_token, &si, &sl);
   1782             } else if (tt == TOKEN_IDENTIFIER) {
   1783                 identAsString(ag, test_name_token);
   1784             }
   1785             break;
   1786         }
   1787         default:
   1788             break;
   1789         }
   1790     }
   1791     return decl_count;
   1792 }
   1793 
   1794 // --- Import tracking ---
   1795 
   1796 static void addImport(AstGenCtx* ag, uint32_t name_index, uint32_t token) {
   1797     // Check for duplicates.
   1798     for (uint32_t i = 0; i < ag->imports_len; i++) {
   1799         if (ag->imports[i].name == name_index)
   1800             return;
   1801     }
   1802     if (ag->imports_len >= ag->imports_cap) {
   1803         uint32_t new_cap = ag->imports_cap > 0 ? ag->imports_cap * 2 : 4;
   1804         ImportEntry* p = realloc(ag->imports, new_cap * sizeof(ImportEntry));
   1805         if (!p)
   1806             exit(1);
   1807         ag->imports = p;
   1808         ag->imports_cap = new_cap;
   1809     }
   1810     ag->imports[ag->imports_len].name = name_index;
   1811     ag->imports[ag->imports_len].token = token;
   1812     ag->imports_len++;
   1813 }
   1814 
   1815 // Write imports list to extra (AstGen.zig:227-244).
   1816 static void writeImports(AstGenCtx* ag) {
   1817     if (ag->imports_len == 0) {
   1818         ag->extra[ZIR_EXTRA_IMPORTS] = 0;
   1819         return;
   1820     }
   1821     uint32_t need = 1 + ag->imports_len * 2;
   1822     ensureExtraCapacity(ag, need);
   1823     uint32_t imports_index = ag->extra_len;
   1824     ag->extra[ag->extra_len++] = ag->imports_len;
   1825     for (uint32_t i = 0; i < ag->imports_len; i++) {
   1826         ag->extra[ag->extra_len++] = ag->imports[i].name;
   1827         ag->extra[ag->extra_len++] = ag->imports[i].token;
   1828     }
   1829     ag->extra[ZIR_EXTRA_IMPORTS] = imports_index;
   1830 }
   1831 
   1832 // ri.br() (AstGen.zig:274-282): convert coerced_ty to ty for branching.
   1833 static inline ResultLoc rlBr(ResultLoc rl) {
   1834     if (rl.tag == RL_COERCED_TY) {
   1835         return (ResultLoc) {
   1836             .tag = RL_TY, .data = rl.data, .src_node = 0, .ctx = rl.ctx
   1837         };
   1838     }
   1839     return rl;
   1840 }
   1841 
   1842 // setBreakResultInfo (AstGen.zig:11905-11926): compute break result info
   1843 // from parent RL. Converts coerced_ty → ty, discard → discard, else passes
   1844 // through. For ptr/inferred_ptr, converts to ty/none respectively.
   1845 static ResultLoc breakResultInfo(
   1846     GenZir* gz, ResultLoc parent_rl, uint32_t node, bool need_rl) {
   1847     // First: compute block_ri (AstGen.zig:7639-7646).
   1848     // When need_rl is true, forward the rl as-is (don't convert ptr→ty).
   1849     ResultLoc block_ri;
   1850     if (need_rl) {
   1851         block_ri = parent_rl;
   1852     } else {
   1853         switch (parent_rl.tag) {
   1854         case RL_PTR: {
   1855             uint32_t ptr_ty
   1856                 = addUnNode(gz, ZIR_INST_TYPEOF, parent_rl.data, node);
   1857             uint32_t ty = addUnNode(gz, ZIR_INST_ELEM_TYPE, ptr_ty, node);
   1858             block_ri = (ResultLoc) {
   1859                 .tag = RL_TY, .data = ty, .src_node = 0, .ctx = parent_rl.ctx
   1860             };
   1861             break;
   1862         }
   1863         case RL_INFERRED_PTR:
   1864             block_ri = (ResultLoc) {
   1865                 .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = parent_rl.ctx
   1866             };
   1867             break;
   1868         default:
   1869             block_ri = parent_rl;
   1870             break;
   1871         }
   1872     }
   1873     // Then: setBreakResultInfo (AstGen.zig:11910-11925).
   1874     switch (block_ri.tag) {
   1875     case RL_COERCED_TY:
   1876         return (ResultLoc) { .tag = RL_TY,
   1877             .data = block_ri.data,
   1878             .src_node = 0,
   1879             .ctx = block_ri.ctx };
   1880     case RL_DISCARD:
   1881         // Don't forward ctx (AstGen.zig:11916-11920).
   1882         return RL_DISCARD_VAL;
   1883     default:
   1884         return block_ri;
   1885     }
   1886 }
   1887 
   1888 // resultType (AstGen.zig:341-351): extract result type from RL.
   1889 // Returns 0 if no result type available.
   1890 static uint32_t rlResultType(GenZir* gz, ResultLoc rl, uint32_t node) {
   1891     switch (rl.tag) {
   1892     case RL_TY:
   1893     case RL_COERCED_TY:
   1894         return rl.data;
   1895     case RL_REF_COERCED_TY:
   1896         // AstGen.zig:345: .ref_coerced_ty => |ptr_ty| gz.addUnNode(.elem_type,
   1897         // ptr_ty, node)
   1898         return addUnNode(gz, ZIR_INST_ELEM_TYPE, rl.data, node);
   1899     case RL_PTR: {
   1900         // typeof(ptr) -> elem_type (AstGen.zig:346-349).
   1901         uint32_t ptr_ty = addUnNode(gz, ZIR_INST_TYPEOF, rl.data, node);
   1902         return addUnNode(gz, ZIR_INST_ELEM_TYPE, ptr_ty, node);
   1903     }
   1904     default:
   1905         return 0;
   1906     }
   1907 }
   1908 
   1909 // rvalue (AstGen.zig:11051-11224): apply result location wrapping.
   1910 static uint32_t rvalue(
   1911     GenZir* gz, ResultLoc rl, uint32_t result, uint32_t node) {
   1912     switch (rl.tag) {
   1913     case RL_NONE:
   1914     case RL_COERCED_TY:
   1915         return result;
   1916     case RL_DISCARD:
   1917         // ensure_result_non_error (AstGen.zig:11071-11074).
   1918         addUnNode(gz, ZIR_INST_ENSURE_RESULT_NON_ERROR, result, node);
   1919         return ZIR_REF_VOID_VALUE;
   1920     case RL_REF:
   1921     case RL_REF_COERCED_TY: {
   1922         // coerce_ptr_elem_ty for ref_coerced_ty (AstGen.zig:11077-11083).
   1923         uint32_t coerced_result = result;
   1924         if (rl.tag == RL_REF_COERCED_TY) {
   1925             coerced_result = addPlNodeBin(
   1926                 gz, ZIR_INST_COERCE_PTR_ELEM_TY, node, rl.data, result);
   1927         }
   1928         AstGenCtx* ag = gz->astgen;
   1929         uint32_t src_token = firstToken(ag->tree, node);
   1930         // If result is not an instruction index (e.g. a well-known ref),
   1931         // emit ref directly (AstGen.zig:11091-11092).
   1932         if (coerced_result < ZIR_REF_START_INDEX) {
   1933             return addUnTok(gz, ZIR_INST_REF, coerced_result, src_token);
   1934         }
   1935         // Deduplication via ref_table (AstGen.zig:11093-11097).
   1936         uint32_t result_index = coerced_result - ZIR_REF_START_INDEX;
   1937         bool found;
   1938         uint32_t* val_ptr = refTableGetOrPut(ag, result_index, &found);
   1939         if (!found) {
   1940             *val_ptr = makeUnTok(gz, ZIR_INST_REF, coerced_result, src_token);
   1941         }
   1942         return *val_ptr + ZIR_REF_START_INDEX;
   1943     }
   1944     case RL_TY: {
   1945         // Quick elimination of common, unnecessary type coercions
   1946         // (AstGen.zig:11099-11209).
   1947 #define RC(t, v) (((uint64_t)(t) << 32) | (uint64_t)(v))
   1948         uint64_t combined = RC(rl.data, result);
   1949         switch (combined) {
   1950         // Identity: type of result is already correct
   1951         // (AstGen.zig:11109-11176).
   1952         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U1_TYPE):
   1953         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U8_TYPE):
   1954         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I8_TYPE):
   1955         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U16_TYPE):
   1956         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U29_TYPE):
   1957         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I16_TYPE):
   1958         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U32_TYPE):
   1959         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I32_TYPE):
   1960         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U64_TYPE):
   1961         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I64_TYPE):
   1962         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U128_TYPE):
   1963         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I128_TYPE):
   1964         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_USIZE_TYPE):
   1965         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ISIZE_TYPE):
   1966         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_CHAR_TYPE):
   1967         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_SHORT_TYPE):
   1968         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_USHORT_TYPE):
   1969         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_INT_TYPE):
   1970         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_UINT_TYPE):
   1971         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_LONG_TYPE):
   1972         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_ULONG_TYPE):
   1973         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_LONGLONG_TYPE):
   1974         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_ULONGLONG_TYPE):
   1975         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_LONGDOUBLE_TYPE):
   1976         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F16_TYPE):
   1977         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F32_TYPE):
   1978         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F64_TYPE):
   1979         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F80_TYPE):
   1980         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F128_TYPE):
   1981         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ANYOPAQUE_TYPE):
   1982         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_BOOL_TYPE):
   1983         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_VOID_TYPE):
   1984         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_TYPE_TYPE):
   1985         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ANYERROR_TYPE):
   1986         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_COMPTIME_INT_TYPE):
   1987         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_COMPTIME_FLOAT_TYPE):
   1988         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_NORETURN_TYPE):
   1989         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ANYFRAME_TYPE):
   1990         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_NULL_TYPE):
   1991         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_UNDEFINED_TYPE):
   1992         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ENUM_LITERAL_TYPE):
   1993         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_PTR_USIZE_TYPE):
   1994         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_PTR_CONST_COMPTIME_INT_TYPE):
   1995         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_MANYPTR_U8_TYPE):
   1996         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_MANYPTR_CONST_U8_TYPE):
   1997         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_MANYPTR_CONST_U8_SENTINEL_0_TYPE):
   1998         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_SLICE_CONST_U8_TYPE):
   1999         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE):
   2000         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE):
   2001         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_GENERIC_POISON_TYPE):
   2002         case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_EMPTY_TUPLE_TYPE):
   2003         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ZERO):
   2004         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ONE):
   2005         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_NEGATIVE_ONE):
   2006         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_UNDEF_USIZE):
   2007         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ZERO_USIZE):
   2008         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ONE_USIZE):
   2009         case RC(ZIR_REF_U1_TYPE, ZIR_REF_UNDEF_U1):
   2010         case RC(ZIR_REF_U1_TYPE, ZIR_REF_ZERO_U1):
   2011         case RC(ZIR_REF_U1_TYPE, ZIR_REF_ONE_U1):
   2012         case RC(ZIR_REF_U8_TYPE, ZIR_REF_ZERO_U8):
   2013         case RC(ZIR_REF_U8_TYPE, ZIR_REF_ONE_U8):
   2014         case RC(ZIR_REF_U8_TYPE, ZIR_REF_FOUR_U8):
   2015         case RC(ZIR_REF_BOOL_TYPE, ZIR_REF_UNDEF_BOOL):
   2016         case RC(ZIR_REF_BOOL_TYPE, ZIR_REF_BOOL_TRUE):
   2017         case RC(ZIR_REF_BOOL_TYPE, ZIR_REF_BOOL_FALSE):
   2018         case RC(ZIR_REF_VOID_TYPE, ZIR_REF_VOID_VALUE):
   2019             return result;
   2020         // Conversions (AstGen.zig:11178-11202).
   2021         case RC(ZIR_REF_BOOL_TYPE, ZIR_REF_UNDEF):
   2022             return ZIR_REF_UNDEF_BOOL;
   2023         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_UNDEF):
   2024             return ZIR_REF_UNDEF_USIZE;
   2025         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_UNDEF_U1):
   2026             return ZIR_REF_UNDEF_USIZE;
   2027         case RC(ZIR_REF_U1_TYPE, ZIR_REF_UNDEF):
   2028             return ZIR_REF_UNDEF_U1;
   2029         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ZERO):
   2030             return ZIR_REF_ZERO_USIZE;
   2031         case RC(ZIR_REF_U1_TYPE, ZIR_REF_ZERO):
   2032             return ZIR_REF_ZERO_U1;
   2033         case RC(ZIR_REF_U8_TYPE, ZIR_REF_ZERO):
   2034             return ZIR_REF_ZERO_U8;
   2035         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ONE):
   2036             return ZIR_REF_ONE_USIZE;
   2037         case RC(ZIR_REF_U1_TYPE, ZIR_REF_ONE):
   2038             return ZIR_REF_ONE_U1;
   2039         case RC(ZIR_REF_U8_TYPE, ZIR_REF_ONE):
   2040             return ZIR_REF_ONE_U8;
   2041         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ZERO_USIZE):
   2042             return ZIR_REF_ZERO;
   2043         case RC(ZIR_REF_U1_TYPE, ZIR_REF_ZERO_USIZE):
   2044             return ZIR_REF_ZERO_U1;
   2045         case RC(ZIR_REF_U8_TYPE, ZIR_REF_ZERO_USIZE):
   2046             return ZIR_REF_ZERO_U8;
   2047         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ONE_USIZE):
   2048             return ZIR_REF_ONE;
   2049         case RC(ZIR_REF_U1_TYPE, ZIR_REF_ONE_USIZE):
   2050             return ZIR_REF_ONE_U1;
   2051         case RC(ZIR_REF_U8_TYPE, ZIR_REF_ONE_USIZE):
   2052             return ZIR_REF_ONE_U8;
   2053         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ZERO_U1):
   2054             return ZIR_REF_ZERO;
   2055         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ZERO_U8):
   2056             return ZIR_REF_ZERO;
   2057         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ZERO_U1):
   2058             return ZIR_REF_ZERO_USIZE;
   2059         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ZERO_U8):
   2060             return ZIR_REF_ZERO_USIZE;
   2061         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ONE_U1):
   2062             return ZIR_REF_ONE;
   2063         case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ONE_U8):
   2064             return ZIR_REF_ONE;
   2065         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ONE_U1):
   2066             return ZIR_REF_ONE_USIZE;
   2067         case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ONE_U8):
   2068             return ZIR_REF_ONE_USIZE;
   2069         default: {
   2070             ZirInstTag as_tag = (rl.ctx == RI_CTX_SHIFT_OP)
   2071                 ? ZIR_INST_AS_SHIFT_OPERAND
   2072                 : ZIR_INST_AS_NODE;
   2073             return addPlNodeBin(gz, as_tag, node, rl.data, result);
   2074         }
   2075         }
   2076 #undef RC
   2077     }
   2078     case RL_PTR:
   2079         // store_node (AstGen.zig:11211-11216).
   2080         addPlNodeBin(gz, ZIR_INST_STORE_NODE,
   2081             rl.src_node != 0 ? rl.src_node : node, rl.data, result);
   2082         return ZIR_REF_VOID_VALUE;
   2083     case RL_INFERRED_PTR:
   2084         // store_to_inferred_ptr (AstGen.zig:11218-11223).
   2085         addPlNodeBin(
   2086             gz, ZIR_INST_STORE_TO_INFERRED_PTR, node, rl.data, result);
   2087         return ZIR_REF_VOID_VALUE;
   2088     }
   2089     return result;
   2090 }
   2091 
   2092 // rvalueNoCoercePreRef (AstGen.zig:11042-11049): like rvalue but does NOT
   2093 // emit coerce_ptr_elem_ty for RL_REF_COERCED_TY. Used for local var refs.
   2094 static uint32_t rvalueNoCoercePreRef(
   2095     GenZir* gz, ResultLoc rl, uint32_t result, uint32_t node) {
   2096     if (rl.tag == RL_REF_COERCED_TY) {
   2097         ResultLoc ref_rl = rl;
   2098         ref_rl.tag = RL_REF;
   2099         return rvalue(gz, ref_rl, result, node);
   2100     }
   2101     return rvalue(gz, rl, result, node);
   2102 }
   2103 
   2104 // --- Expression evaluation (AstGen.zig:634) ---
   2105 
   2106 // Forward declarations.
   2107 static uint32_t expr(GenZir* gz, Scope* scope, uint32_t node);
   2108 // --- DefersToEmit (AstGen.zig:3008) ---
   2109 #define DEFER_NORMAL_ONLY 0
   2110 #define DEFER_BOTH_SANS_ERR 1
   2111 
   2112 // --- DeferCounts (AstGen.zig:2966) ---
   2113 typedef struct {
   2114     bool have_any;
   2115     bool have_normal;
   2116     bool have_err;
   2117     bool need_err_code;
   2118 } DeferCounts;
   2119 static DeferCounts countDefers(const Scope* outer_scope, Scope* inner_scope);
   2120 
   2121 static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
   2122 static void assignStmt(GenZir* gz, Scope* scope, uint32_t infix_node);
   2123 static void assignOp(
   2124     GenZir* gz, Scope* scope, uint32_t infix_node, ZirInstTag op_tag);
   2125 static uint32_t shiftOp(
   2126     GenZir* gz, Scope* scope, uint32_t node, ZirInstTag tag);
   2127 static void emitDbgStmt(GenZir* gz, uint32_t line, uint32_t column);
   2128 static void genDefers(
   2129     GenZir* gz, const Scope* outer_scope, Scope* inner_scope, int which);
   2130 static void emitDbgStmtForceCurrentIndex(
   2131     GenZir* gz, uint32_t line, uint32_t column);
   2132 static void emitDbgNode(GenZir* gz, uint32_t node);
   2133 static void addDbgVar(
   2134     GenZir* gz, ZirInstTag tag, uint32_t name, uint32_t inst);
   2135 static bool addEnsureResult(
   2136     GenZir* gz, uint32_t maybe_unused_result, uint32_t statement);
   2137 static void blockExprStmts(
   2138     GenZir* gz, Scope* scope, const uint32_t* statements, uint32_t stmt_count);
   2139 static uint32_t fullBodyExpr(
   2140     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
   2141 static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node);
   2142 static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node,
   2143     const uint32_t* members, uint32_t members_len);
   2144 static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node,
   2145     const uint32_t* members, uint32_t members_len);
   2146 static uint32_t blockExprExpr(
   2147     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
   2148 static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
   2149 static uint32_t forExpr(
   2150     GenZir* gz, Scope* scope, uint32_t node, bool is_statement);
   2151 static uint32_t orelseCatchExpr(
   2152     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_catch);
   2153 static uint32_t arrayInitDotExpr(
   2154     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
   2155 static uint32_t switchExpr(
   2156     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node);
   2157 static uint32_t whileExpr(
   2158     GenZir* gz, Scope* scope, uint32_t node, bool is_statement);
   2159 #define EVAL_TO_ERROR_NEVER 0
   2160 #define EVAL_TO_ERROR_ALWAYS 1
   2161 #define EVAL_TO_ERROR_MAYBE 2
   2162 static int nodeMayEvalToError(const Ast* tree, uint32_t node);
   2163 static bool nodeMayAppendToErrorTrace(const Ast* tree, uint32_t node);
   2164 static void addSaveErrRetIndex(GenZir* gz, uint32_t operand);
   2165 static void addRestoreErrRetIndexBlock(
   2166     GenZir* gz, uint32_t block_inst, uint32_t operand, uint32_t node);
   2167 static void restoreErrRetIndex(GenZir* gz, uint32_t block_inst, ResultLoc rl,
   2168     uint32_t node, uint32_t result);
   2169 static uint32_t identAsString(AstGenCtx* ag, uint32_t token);
   2170 static uint32_t lastToken(const Ast* tree, uint32_t node);
   2171 static uint32_t simpleBinOp(
   2172     GenZir* gz, Scope* scope, uint32_t node, ZirInstTag tag);
   2173 
   2174 // Mirrors GenZir.endsWithNoReturn (AstGen.zig:11770).
   2175 static bool endsWithNoReturn(GenZir* gz) {
   2176     uint32_t len = gzInstructionsLen(gz);
   2177     if (len == 0)
   2178         return false;
   2179     uint32_t last = gzInstructionsSlice(gz)[len - 1];
   2180     ZirInstTag tag = gz->astgen->inst_tags[last];
   2181     switch (tag) {
   2182     case ZIR_INST_BREAK:
   2183     case ZIR_INST_BREAK_INLINE:
   2184     case ZIR_INST_CONDBR:
   2185     case ZIR_INST_CONDBR_INLINE:
   2186     case ZIR_INST_COMPILE_ERROR:
   2187     case ZIR_INST_RET_NODE:
   2188     case ZIR_INST_RET_LOAD:
   2189     case ZIR_INST_RET_IMPLICIT:
   2190     case ZIR_INST_RET_ERR_VALUE:
   2191     case ZIR_INST_UNREACHABLE:
   2192     case ZIR_INST_REPEAT:
   2193     case ZIR_INST_REPEAT_INLINE:
   2194     case ZIR_INST_PANIC:
   2195     case ZIR_INST_TRAP:
   2196     case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW:
   2197     case ZIR_INST_SWITCH_CONTINUE:
   2198         return true;
   2199     default:
   2200         return false;
   2201     }
   2202 }
   2203 
   2204 // Mirrors GenZir.refIsNoReturn (AstGen.zig:11885).
   2205 static bool refIsNoReturn(GenZir* gz, uint32_t inst_ref) {
   2206     if (inst_ref == ZIR_REF_UNREACHABLE_VALUE)
   2207         return true;
   2208     if (inst_ref >= ZIR_REF_START_INDEX) {
   2209         uint32_t inst_index = inst_ref - ZIR_REF_START_INDEX;
   2210         ZirInstTag tag = gz->astgen->inst_tags[inst_index];
   2211         switch (tag) {
   2212         case ZIR_INST_BREAK:
   2213         case ZIR_INST_BREAK_INLINE:
   2214         case ZIR_INST_CONDBR:
   2215         case ZIR_INST_CONDBR_INLINE:
   2216         case ZIR_INST_COMPILE_ERROR:
   2217         case ZIR_INST_RET_NODE:
   2218         case ZIR_INST_RET_LOAD:
   2219         case ZIR_INST_RET_IMPLICIT:
   2220         case ZIR_INST_RET_ERR_VALUE:
   2221         case ZIR_INST_UNREACHABLE:
   2222         case ZIR_INST_REPEAT:
   2223         case ZIR_INST_REPEAT_INLINE:
   2224         case ZIR_INST_PANIC:
   2225         case ZIR_INST_TRAP:
   2226         case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW:
   2227         case ZIR_INST_SWITCH_CONTINUE:
   2228             return true;
   2229         default:
   2230             return false;
   2231         }
   2232     }
   2233     return false;
   2234 }
   2235 
   2236 static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node);
   2237 
   2238 // SimpleComptimeReason (std.zig:727) — values used in block_comptime payload.
   2239 #define COMPTIME_REASON_TYPE 29
   2240 #define COMPTIME_REASON_ARRAY_SENTINEL 30
   2241 #define COMPTIME_REASON_POINTER_SENTINEL 31
   2242 #define COMPTIME_REASON_SLICE_SENTINEL 32
   2243 #define COMPTIME_REASON_ARRAY_LENGTH 33
   2244 #define COMPTIME_REASON_ALIGN 50
   2245 #define COMPTIME_REASON_ADDRSPACE 51
   2246 #define COMPTIME_REASON_COMPTIME_KEYWORD 53
   2247 #define COMPTIME_REASON_SWITCH_ITEM 56
   2248 
   2249 // Mirrors comptimeExpr2 (AstGen.zig:1982).
   2250 // Evaluates a node in a comptime block_comptime scope.
   2251 static uint32_t comptimeExpr(
   2252     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, uint32_t reason) {
   2253     // Skip wrapping when already in comptime context (AstGen.zig:1990).
   2254     if (gz->is_comptime)
   2255         return exprRl(gz, scope, rl, node);
   2256     // Optimization: certain node types are trivially comptime and don't need
   2257     // a block_comptime wrapper (AstGen.zig:1997-2046).
   2258     AstGenCtx* ag = gz->astgen;
   2259     AstNodeTag tag = ag->tree->nodes.tags[node];
   2260     switch (tag) {
   2261     // Identifier handling (AstGen.zig:2000-2003):
   2262     // Upstream calls identifier() with force_comptime which resolves
   2263     // primitives/int types directly and only wraps others in block_comptime.
   2264     // We mirror this by resolving primitives here and falling through for
   2265     // non-primitives.
   2266     case AST_NODE_IDENTIFIER: {
   2267         uint32_t prim = tryResolvePrimitiveIdent(gz, node);
   2268         if (prim != ZIR_REF_NONE)
   2269             return prim;
   2270         break; // non-primitive: fall through to block_comptime wrapping
   2271     }
   2272     case AST_NODE_NUMBER_LITERAL:
   2273     case AST_NODE_CHAR_LITERAL:
   2274     case AST_NODE_STRING_LITERAL:
   2275     case AST_NODE_MULTILINE_STRING_LITERAL:
   2276     case AST_NODE_ENUM_LITERAL:
   2277     case AST_NODE_ERROR_VALUE:
   2278     // Type expressions that force comptime eval of sub-expressions
   2279     // (AstGen.zig:2017-2042).
   2280     case AST_NODE_ERROR_UNION:
   2281     case AST_NODE_MERGE_ERROR_SETS:
   2282     case AST_NODE_OPTIONAL_TYPE:
   2283     case AST_NODE_PTR_TYPE_ALIGNED:
   2284     case AST_NODE_PTR_TYPE_SENTINEL:
   2285     case AST_NODE_PTR_TYPE:
   2286     case AST_NODE_PTR_TYPE_BIT_RANGE:
   2287     case AST_NODE_ARRAY_TYPE:
   2288     case AST_NODE_ARRAY_TYPE_SENTINEL:
   2289     case AST_NODE_FN_PROTO_SIMPLE:
   2290     case AST_NODE_FN_PROTO_MULTI:
   2291     case AST_NODE_FN_PROTO_ONE:
   2292     case AST_NODE_FN_PROTO:
   2293     case AST_NODE_CONTAINER_DECL:
   2294     case AST_NODE_CONTAINER_DECL_TRAILING:
   2295     case AST_NODE_CONTAINER_DECL_ARG:
   2296     case AST_NODE_CONTAINER_DECL_ARG_TRAILING:
   2297     case AST_NODE_CONTAINER_DECL_TWO:
   2298     case AST_NODE_CONTAINER_DECL_TWO_TRAILING:
   2299     case AST_NODE_TAGGED_UNION:
   2300     case AST_NODE_TAGGED_UNION_TRAILING:
   2301     case AST_NODE_TAGGED_UNION_ENUM_TAG:
   2302     case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING:
   2303     case AST_NODE_TAGGED_UNION_TWO:
   2304     case AST_NODE_TAGGED_UNION_TWO_TRAILING:
   2305         return exprRl(gz, scope, rl, node);
   2306     default:
   2307         break;
   2308     }
   2309     // General case: wrap in block_comptime (AstGen.zig:2078-2096).
   2310     uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK_COMPTIME, gz, node);
   2311     GenZir block_scope = makeSubBlock(gz, scope);
   2312     block_scope.is_comptime = true;
   2313     // Transform RL to type-only (AstGen.zig:2084-2090).
   2314     ResultLoc ty_only_rl;
   2315     uint32_t res_ty = rlResultType(gz, rl, node);
   2316     if (res_ty != 0)
   2317         ty_only_rl = (ResultLoc) {
   2318             .tag = RL_COERCED_TY, .data = res_ty, .src_node = 0, .ctx = rl.ctx
   2319         };
   2320     else
   2321         ty_only_rl = (ResultLoc) {
   2322             .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = rl.ctx
   2323         };
   2324     uint32_t result = exprRl(&block_scope, scope, ty_only_rl, node);
   2325     addBreak(&block_scope, ZIR_INST_BREAK_INLINE, block_inst, result,
   2326         AST_NODE_OFFSET_NONE);
   2327     setBlockComptimeBody(ag, &block_scope, block_inst, reason);
   2328     gzAppendInstruction(gz, block_inst);
   2329     return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node);
   2330 }
   2331 
   2332 // Mirrors typeExpr (AstGen.zig:394).
   2333 static uint32_t typeExpr(GenZir* gz, Scope* scope, uint32_t node) {
   2334     ResultLoc rl = { .tag = RL_COERCED_TY,
   2335         .data = ZIR_REF_TYPE_TYPE,
   2336         .src_node = 0,
   2337         .ctx = RI_CTX_NONE };
   2338     return comptimeExpr(gz, scope, rl, node, COMPTIME_REASON_TYPE);
   2339 }
   2340 
   2341 // Mirrors numberLiteral (AstGen.zig:8544).
   2342 // Parses integer and float literals, returns appropriate ZIR ref.
   2343 static uint32_t numberLiteral(GenZir* gz, uint32_t node) {
   2344     AstGenCtx* ag = gz->astgen;
   2345     uint32_t num_token = ag->tree->nodes.main_tokens[node];
   2346     uint32_t tok_start = ag->tree->tokens.starts[num_token];
   2347     const char* source = ag->tree->source;
   2348 
   2349     // Determine token length by scanning to next non-number character.
   2350     uint32_t tok_end = tok_start;
   2351     while (tok_end < ag->tree->source_len
   2352         && ((source[tok_end] >= '0' && source[tok_end] <= '9')
   2353             || source[tok_end] == '_' || source[tok_end] == '.'
   2354             || source[tok_end] == 'x' || source[tok_end] == 'o'
   2355             || source[tok_end] == 'b'
   2356             || (source[tok_end] >= 'a' && source[tok_end] <= 'f')
   2357             || (source[tok_end] >= 'A' && source[tok_end] <= 'F'))) {
   2358         tok_end++;
   2359     }
   2360 
   2361     // Parse the integer value (simplified: decimal and hex).
   2362     uint64_t value = 0;
   2363     bool is_hex = false;
   2364     uint32_t pos = tok_start;
   2365     if (tok_end - tok_start >= 2 && source[tok_start] == '0'
   2366         && source[tok_start + 1] == 'x') {
   2367         is_hex = true;
   2368         pos = tok_start + 2;
   2369     }
   2370 
   2371     if (is_hex) {
   2372         for (; pos < tok_end; pos++) {
   2373             if (source[pos] == '_')
   2374                 continue;
   2375             if (source[pos] >= '0' && source[pos] <= '9')
   2376                 value = value * 16 + (uint64_t)(source[pos] - '0');
   2377             else if (source[pos] >= 'a' && source[pos] <= 'f')
   2378                 value = value * 16 + 10 + (uint64_t)(source[pos] - 'a');
   2379             else if (source[pos] >= 'A' && source[pos] <= 'F')
   2380                 value = value * 16 + 10 + (uint64_t)(source[pos] - 'A');
   2381         }
   2382     } else {
   2383         for (; pos < tok_end; pos++) {
   2384             if (source[pos] == '_')
   2385                 continue;
   2386             if (source[pos] == '.')
   2387                 break; // float — not handled yet
   2388             if (source[pos] >= '0' && source[pos] <= '9')
   2389                 value = value * 10 + (uint64_t)(source[pos] - '0');
   2390         }
   2391     }
   2392 
   2393     // Special cases for 0 and 1 (AstGen.zig:8687-8703).
   2394     if (value == 0)
   2395         return ZIR_REF_ZERO;
   2396     if (value == 1)
   2397         return ZIR_REF_ONE;
   2398 
   2399     return addInt(gz, value);
   2400 }
   2401 
   2402 // Mirrors builtinCall (AstGen.zig:9191), @import case (AstGen.zig:9242).
   2403 static uint32_t builtinCallImport(GenZir* gz, Scope* scope, uint32_t node) {
   2404     (void)scope;
   2405     AstGenCtx* ag = gz->astgen;
   2406     const Ast* tree = ag->tree;
   2407 
   2408     // For builtin_call_two: data.lhs = first arg node.
   2409     AstData node_data = tree->nodes.datas[node];
   2410     uint32_t operand_node = node_data.lhs;
   2411 
   2412     assert(tree->nodes.tags[operand_node] == AST_NODE_STRING_LITERAL);
   2413     uint32_t str_lit_token = tree->nodes.main_tokens[operand_node];
   2414 
   2415     uint32_t str_index, str_len;
   2416     strLitAsString(ag, str_lit_token, &str_index, &str_len);
   2417 
   2418     // Write Import payload to extra (Zir.Inst.Import: res_ty, path).
   2419     ensureExtraCapacity(ag, 2);
   2420     uint32_t payload_index = ag->extra_len;
   2421     ag->extra[ag->extra_len++] = ZIR_REF_NONE; // res_ty = .none
   2422     ag->extra[ag->extra_len++] = str_index; // path
   2423 
   2424     // Create .import instruction with pl_tok data.
   2425     ZirInstData data;
   2426     data.pl_tok.src_tok = tokenIndexToRelative(gz, str_lit_token);
   2427     data.pl_tok.payload_index = payload_index;
   2428     uint32_t result_ref = addInstruction(gz, ZIR_INST_IMPORT, data);
   2429 
   2430     // Track import (AstGen.zig:9269).
   2431     addImport(ag, str_index, str_lit_token);
   2432 
   2433     return result_ref;
   2434 }
   2435 
   2436 // Mirrors cImport (AstGen.zig:10011).
   2437 static uint32_t cImportExpr(GenZir* gz, Scope* scope, uint32_t node) {
   2438     AstGenCtx* ag = gz->astgen;
   2439     AstData nd = ag->tree->nodes.datas[node];
   2440     uint32_t body_node = nd.lhs; // first arg = body
   2441 
   2442     uint32_t block_inst = makeBlockInst(ag, ZIR_INST_C_IMPORT, gz, node);
   2443 
   2444     GenZir block_scope = makeSubBlock(gz, scope);
   2445     block_scope.is_comptime = true;
   2446     block_scope.c_import = true;
   2447 
   2448     // Use fullBodyExpr to inline unlabeled block body (AstGen.zig:10028).
   2449     fullBodyExpr(&block_scope, &block_scope.base, RL_NONE_VAL, body_node);
   2450 
   2451     // ensure_result_used on gz (parent), not block_scope (AstGen.zig:10029).
   2452     addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, ZIR_REF_VOID_VALUE, node);
   2453 
   2454     // break_inline (AstGen.zig:10030-10032).
   2455     makeBreakInline(
   2456         &block_scope, block_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE);
   2457 
   2458     setBlockBody(ag, &block_scope, block_inst);
   2459     // block_scope unstacked now, can add to gz.
   2460     gzAppendInstruction(gz, block_inst);
   2461 
   2462     return block_inst + ZIR_REF_START_INDEX; // toRef()
   2463 }
   2464 
   2465 // Mirrors simpleCBuiltin (AstGen.zig:9938).
   2466 static uint32_t simpleCBuiltin(GenZir* gz, Scope* scope, uint32_t node,
   2467     uint32_t operand_node, uint16_t ext_tag) {
   2468     AstGenCtx* ag = gz->astgen;
   2469 
   2470     // Evaluate operand as comptime string.
   2471     uint32_t operand = expr(gz, scope, operand_node);
   2472 
   2473     // Emit extended instruction with UnNode payload (AstGen.zig:9954).
   2474     ensureExtraCapacity(ag, 2);
   2475     uint32_t payload_index = ag->extra_len;
   2476     ag->extra[ag->extra_len++]
   2477         = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index);
   2478     ag->extra[ag->extra_len++] = operand;
   2479 
   2480     ZirInstData data;
   2481     data.extended.opcode = ext_tag;
   2482     data.extended.small = 0xAAAAu; // undefined (addExtendedPayload passes
   2483                                    // undefined for small)
   2484     data.extended.operand = payload_index;
   2485     addInstruction(gz, ZIR_INST_EXTENDED, data);
   2486 
   2487     return ZIR_REF_VOID_VALUE;
   2488 }
   2489 
   2490 // Mirrors builtinCall (AstGen.zig:9191) dispatch.
   2491 static uint32_t builtinCall(
   2492     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   2493     AstGenCtx* ag = gz->astgen;
   2494     const Ast* tree = ag->tree;
   2495 
   2496     uint32_t builtin_token = tree->nodes.main_tokens[node];
   2497     uint32_t tok_start = tree->tokens.starts[builtin_token];
   2498     const char* source = tree->source;
   2499 
   2500     // Identify builtin name from source.
   2501     // Skip '@' prefix and scan identifier.
   2502     uint32_t name_start = tok_start + 1; // skip '@'
   2503     uint32_t name_end = name_start;
   2504     while (name_end < tree->source_len
   2505         && ((source[name_end] >= 'a' && source[name_end] <= 'z')
   2506             || (source[name_end] >= 'A' && source[name_end] <= 'Z')
   2507             || source[name_end] == '_')) {
   2508         name_end++;
   2509     }
   2510     uint32_t name_len = name_end - name_start;
   2511 
   2512     // clang-format off
   2513     if (name_len == 6 && memcmp(source + name_start, "import", 6) == 0)
   2514         return builtinCallImport(gz, scope, node);
   2515     if (name_len == 7 && memcmp(source + name_start, "cImport", 7) == 0)
   2516         return cImportExpr(gz, scope, node);
   2517     if (name_len == 8 && memcmp(source + name_start, "cInclude", 8) == 0) {
   2518         AstData nd = tree->nodes.datas[node];
   2519         return simpleCBuiltin(gz, scope, node, nd.lhs, (uint16_t)ZIR_EXT_C_INCLUDE);
   2520     }
   2521     // @intCast — typeCast pattern (AstGen.zig:9416, 9807-9826).
   2522     if (name_len == 7 && memcmp(source + name_start, "intCast", 7) == 0) {
   2523         advanceSourceCursorToMainToken(ag, gz, node);
   2524         uint32_t saved_line = ag->source_line - gz->decl_line;
   2525         uint32_t saved_col = ag->source_column;
   2526         uint32_t result_type = rlResultType(gz, rl, node);
   2527         AstData nd = tree->nodes.datas[node];
   2528         uint32_t operand = expr(gz, scope, nd.lhs);
   2529         emitDbgStmt(gz, saved_line, saved_col);
   2530         return addPlNodeBin(gz, ZIR_INST_INT_CAST, node,
   2531             result_type, operand);
   2532     }
   2533     // @embedFile (AstGen.zig:9626).
   2534     if (name_len == 9 && memcmp(source + name_start, "embedFile", 9) == 0) {
   2535         AstData nd = tree->nodes.datas[node];
   2536         uint32_t operand = expr(gz, scope, nd.lhs);
   2537         return addUnNode(gz, ZIR_INST_EMBED_FILE, operand, node);
   2538     }
   2539     // @intFromEnum (AstGen.zig:9478).
   2540     if (name_len == 11 && memcmp(source + name_start, "intFromEnum", 11) == 0) {
   2541         AstData nd = tree->nodes.datas[node];
   2542         uint32_t operand = expr(gz, scope, nd.lhs);
   2543         return addUnNode(gz, ZIR_INST_INT_FROM_ENUM, operand, node);
   2544     }
   2545     // @tagName (AstGen.zig:9407) — simpleUnOp with dbg_stmt.
   2546     if (name_len == 7 && memcmp(source + name_start, "tagName", 7) == 0) {
   2547         advanceSourceCursorToMainToken(ag, gz, node);
   2548         uint32_t saved_line = ag->source_line - gz->decl_line;
   2549         uint32_t saved_col = ag->source_column;
   2550         AstData nd = tree->nodes.datas[node];
   2551         uint32_t operand = expr(gz, scope, nd.lhs);
   2552         emitDbgStmt(gz, saved_line, saved_col);
   2553         return addUnNode(gz, ZIR_INST_TAG_NAME, operand, node);
   2554     }
   2555     // @as (AstGen.zig:8909-8920).
   2556     if (name_len == 2 && memcmp(source + name_start, "as", 2) == 0) {
   2557         AstData nd = tree->nodes.datas[node];
   2558         uint32_t dest_type = typeExpr(gz, scope, nd.lhs);
   2559         ResultLoc as_rl = { .tag = RL_TY, .data = dest_type, .src_node = 0,
   2560             .ctx = rl.ctx };
   2561         uint32_t operand = exprRl(gz, scope, as_rl, nd.rhs);
   2562         return rvalue(gz, rl, operand, node);
   2563     }
   2564     // @truncate — typeCast pattern (AstGen.zig:9417, 9807-9826).
   2565     if (name_len == 8 && memcmp(source + name_start, "truncate", 8) == 0) {
   2566         advanceSourceCursorToMainToken(ag, gz, node);
   2567         uint32_t saved_line = ag->source_line - gz->decl_line;
   2568         uint32_t saved_col = ag->source_column;
   2569         uint32_t result_type = rlResultType(gz, rl, node);
   2570         AstData nd = tree->nodes.datas[node];
   2571         uint32_t operand = expr(gz, scope, nd.lhs);
   2572         emitDbgStmt(gz, saved_line, saved_col);
   2573         return addPlNodeBin(gz, ZIR_INST_TRUNCATE, node,
   2574             result_type, operand);
   2575     }
   2576     // @ptrCast — typeCast pattern (AstGen.zig:9056, 9807-9826).
   2577     if (name_len == 7 && memcmp(source + name_start, "ptrCast", 7) == 0) {
   2578         advanceSourceCursorToMainToken(ag, gz, node);
   2579         uint32_t saved_line = ag->source_line - gz->decl_line;
   2580         uint32_t saved_col = ag->source_column;
   2581         uint32_t result_type = rlResultType(gz, rl, node);
   2582         AstData nd = tree->nodes.datas[node];
   2583         uint32_t operand = expr(gz, scope, nd.lhs);
   2584         emitDbgStmt(gz, saved_line, saved_col);
   2585         return addPlNodeBin(gz, ZIR_INST_PTR_CAST, node,
   2586             result_type, operand);
   2587     }
   2588     // @enumFromInt — typeCast pattern (AstGen.zig:9414, 9807-9826).
   2589     if (name_len == 11 && memcmp(source + name_start, "enumFromInt", 11) == 0) {
   2590         advanceSourceCursorToMainToken(ag, gz, node);
   2591         uint32_t saved_line = ag->source_line - gz->decl_line;
   2592         uint32_t saved_col = ag->source_column;
   2593         uint32_t result_type = rlResultType(gz, rl, node);
   2594         AstData nd = tree->nodes.datas[node];
   2595         uint32_t operand = expr(gz, scope, nd.lhs);
   2596         emitDbgStmt(gz, saved_line, saved_col);
   2597         return addPlNodeBin(gz, ZIR_INST_ENUM_FROM_INT, node,
   2598             result_type, operand);
   2599     }
   2600     // @bitCast (AstGen.zig:8944-8958, dispatched at 9313).
   2601     if (name_len == 7 && memcmp(source + name_start, "bitCast", 7) == 0) {
   2602         uint32_t result_type = rlResultType(gz, rl, node);
   2603         AstData nd = tree->nodes.datas[node];
   2604         uint32_t operand = expr(gz, scope, nd.lhs);
   2605         return addPlNodeBin(gz, ZIR_INST_BITCAST, node,
   2606             result_type, operand);
   2607     }
   2608     // @memcpy (AstGen.zig:9631-9637).
   2609     if (name_len == 6 && memcmp(source + name_start, "memcpy", 6) == 0) {
   2610         AstData nd = tree->nodes.datas[node];
   2611         uint32_t dst = expr(gz, scope, nd.lhs);
   2612         uint32_t src = expr(gz, scope, nd.rhs);
   2613         addPlNodeBin(gz, ZIR_INST_MEMCPY, node, dst, src);
   2614         return ZIR_REF_VOID_VALUE;
   2615     }
   2616     // @memset (AstGen.zig:9638-9647).
   2617     if (name_len == 6 && memcmp(source + name_start, "memset", 6) == 0) {
   2618         AstData nd = tree->nodes.datas[node];
   2619         uint32_t lhs = expr(gz, scope, nd.lhs);
   2620         uint32_t lhs_ty = addUnNode(gz, ZIR_INST_TYPEOF, lhs, nd.lhs);
   2621         uint32_t elem_ty =
   2622             addUnNode(gz, ZIR_INST_INDEXABLE_PTR_ELEM_TYPE, lhs_ty, nd.lhs);
   2623         ResultLoc val_rl = {
   2624             .tag = RL_COERCED_TY, .data = elem_ty, .src_node = 0};
   2625         uint32_t val = exprRl(gz, scope, val_rl, nd.rhs);
   2626         addPlNodeBin(gz, ZIR_INST_MEMSET, node, lhs, val);
   2627         return ZIR_REF_VOID_VALUE;
   2628     }
   2629     // @min (AstGen.zig:9155).
   2630     if (name_len == 3 && memcmp(source + name_start, "min", 3) == 0) {
   2631         AstData nd = tree->nodes.datas[node];
   2632         uint32_t a = expr(gz, scope, nd.lhs);
   2633         uint32_t b = expr(gz, scope, nd.rhs);
   2634         return addPlNodeBin(gz, ZIR_INST_MIN, node, a, b);
   2635     }
   2636     // @max (AstGen.zig:9155).
   2637     if (name_len == 3 && memcmp(source + name_start, "max", 3) == 0) {
   2638         AstData nd = tree->nodes.datas[node];
   2639         uint32_t a = expr(gz, scope, nd.lhs);
   2640         uint32_t b = expr(gz, scope, nd.rhs);
   2641         return addPlNodeBin(gz, ZIR_INST_MAX, node, a, b);
   2642     }
   2643     // clang-format on
   2644 
   2645     // TODO: handle other builtins.
   2646     SET_ERROR(ag);
   2647     return ZIR_REF_VOID_VALUE;
   2648 }
   2649 
   2650 // --- identifier (AstGen.zig:8282) ---
   2651 // Simplified: handles decl_val resolution for container-level declarations.
   2652 
   2653 // Tries to resolve an identifier as a primitive type or integer type.
   2654 // Returns the ZIR ref if it's a primitive/int type, or ZIR_REF_NONE.
   2655 // Mirrors primitive_instrs + integer type checks in identifier()
   2656 // (AstGen.zig:8298-8337).
   2657 static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node) {
   2658     AstGenCtx* ag = gz->astgen;
   2659     uint32_t ident_token = ag->tree->nodes.main_tokens[node];
   2660     uint32_t tok_start = ag->tree->tokens.starts[ident_token];
   2661     const char* source = ag->tree->source;
   2662     uint32_t tok_end = tok_start;
   2663     while (tok_end < ag->tree->source_len
   2664         && ((source[tok_end] >= 'a' && source[tok_end] <= 'z')
   2665             || (source[tok_end] >= 'A' && source[tok_end] <= 'Z')
   2666             || (source[tok_end] >= '0' && source[tok_end] <= '9')
   2667             || source[tok_end] == '_'))
   2668         tok_end++;
   2669     uint32_t tok_len = tok_end - tok_start;
   2670 
   2671     // Check well-known primitive refs (primitive_instrs map,
   2672     // AstGen.zig:10236-10281).
   2673     // clang-format off
   2674     if (tok_len == 2 && memcmp(source+tok_start, "u1", 2) == 0) return ZIR_REF_U1_TYPE;
   2675     if (tok_len == 2 && memcmp(source+tok_start, "u8", 2) == 0) return ZIR_REF_U8_TYPE;
   2676     if (tok_len == 2 && memcmp(source+tok_start, "i8", 2) == 0) return ZIR_REF_I8_TYPE;
   2677     if (tok_len == 3 && memcmp(source+tok_start, "u16", 3) == 0) return ZIR_REF_U16_TYPE;
   2678     if (tok_len == 3 && memcmp(source+tok_start, "i16", 3) == 0) return ZIR_REF_I16_TYPE;
   2679     if (tok_len == 3 && memcmp(source+tok_start, "u29", 3) == 0) return ZIR_REF_U29_TYPE;
   2680     if (tok_len == 3 && memcmp(source+tok_start, "u32", 3) == 0) return ZIR_REF_U32_TYPE;
   2681     if (tok_len == 3 && memcmp(source+tok_start, "i32", 3) == 0) return ZIR_REF_I32_TYPE;
   2682     if (tok_len == 3 && memcmp(source+tok_start, "u64", 3) == 0) return ZIR_REF_U64_TYPE;
   2683     if (tok_len == 3 && memcmp(source+tok_start, "i64", 3) == 0) return ZIR_REF_I64_TYPE;
   2684     if (tok_len == 4 && memcmp(source+tok_start, "u128", 4) == 0) return ZIR_REF_U128_TYPE;
   2685     if (tok_len == 4 && memcmp(source+tok_start, "i128", 4) == 0) return ZIR_REF_I128_TYPE;
   2686     if (tok_len == 5 && memcmp(source+tok_start, "usize", 5) == 0) return ZIR_REF_USIZE_TYPE;
   2687     if (tok_len == 5 && memcmp(source+tok_start, "isize", 5) == 0) return ZIR_REF_ISIZE_TYPE;
   2688     if (tok_len == 6 && memcmp(source+tok_start, "c_char", 6) == 0) return ZIR_REF_C_CHAR_TYPE;
   2689     if (tok_len == 7 && memcmp(source+tok_start, "c_short", 7) == 0) return ZIR_REF_C_SHORT_TYPE;
   2690     if (tok_len == 8 && memcmp(source+tok_start, "c_ushort", 8) == 0) return ZIR_REF_C_USHORT_TYPE;
   2691     if (tok_len == 5 && memcmp(source+tok_start, "c_int", 5) == 0) return ZIR_REF_C_INT_TYPE;
   2692     if (tok_len == 6 && memcmp(source+tok_start, "c_uint", 6) == 0) return ZIR_REF_C_UINT_TYPE;
   2693     if (tok_len == 6 && memcmp(source+tok_start, "c_long", 6) == 0) return ZIR_REF_C_LONG_TYPE;
   2694     if (tok_len == 7 && memcmp(source+tok_start, "c_ulong", 7) == 0) return ZIR_REF_C_ULONG_TYPE;
   2695     if (tok_len == 10 && memcmp(source+tok_start, "c_longlong", 10) == 0) return ZIR_REF_C_LONGLONG_TYPE;
   2696     if (tok_len == 11 && memcmp(source+tok_start, "c_ulonglong", 11) == 0) return ZIR_REF_C_ULONGLONG_TYPE;
   2697     if (tok_len == 14 && memcmp(source+tok_start, "comptime_float", 14) == 0) return ZIR_REF_COMPTIME_FLOAT_TYPE;
   2698     if (tok_len == 12 && memcmp(source+tok_start, "comptime_int", 12) == 0) return ZIR_REF_COMPTIME_INT_TYPE;
   2699     if (tok_len == 3 && memcmp(source+tok_start, "f16", 3) == 0) return ZIR_REF_F16_TYPE;
   2700     if (tok_len == 3 && memcmp(source+tok_start, "f32", 3) == 0) return ZIR_REF_F32_TYPE;
   2701     if (tok_len == 3 && memcmp(source+tok_start, "f64", 3) == 0) return ZIR_REF_F64_TYPE;
   2702     if (tok_len == 3 && memcmp(source+tok_start, "f80", 3) == 0) return ZIR_REF_F80_TYPE;
   2703     if (tok_len == 4 && memcmp(source+tok_start, "f128", 4) == 0) return ZIR_REF_F128_TYPE;
   2704     if (tok_len == 9 && memcmp(source+tok_start, "anyopaque", 9) == 0) return ZIR_REF_ANYOPAQUE_TYPE;
   2705     if (tok_len == 4 && memcmp(source+tok_start, "bool", 4) == 0) return ZIR_REF_BOOL_TYPE;
   2706     if (tok_len == 4 && memcmp(source+tok_start, "void", 4) == 0) return ZIR_REF_VOID_TYPE;
   2707     if (tok_len == 4 && memcmp(source+tok_start, "type", 4) == 0) return ZIR_REF_TYPE_TYPE;
   2708     if (tok_len == 8 && memcmp(source+tok_start, "anyerror", 8) == 0) return ZIR_REF_ANYERROR_TYPE;
   2709     if (tok_len == 8 && memcmp(source+tok_start, "noreturn", 8) == 0) return ZIR_REF_NORETURN_TYPE;
   2710     if (tok_len == 4 && memcmp(source+tok_start, "true", 4) == 0) return ZIR_REF_BOOL_TRUE;
   2711     if (tok_len == 5 && memcmp(source+tok_start, "false", 5) == 0) return ZIR_REF_BOOL_FALSE;
   2712     if (tok_len == 4 && memcmp(source+tok_start, "null", 4) == 0) return ZIR_REF_NULL_VALUE;
   2713     if (tok_len == 9 && memcmp(source+tok_start, "undefined", 9) == 0) return ZIR_REF_UNDEF;
   2714     // clang-format on
   2715 
   2716     // Integer type detection: u29, i13, etc. (AstGen.zig:8304-8336).
   2717     if (tok_len >= 2
   2718         && (source[tok_start] == 'u' || source[tok_start] == 'i')) {
   2719         // Zig Signedness enum: unsigned=1, signed=0
   2720         uint8_t signedness = (source[tok_start] == 'u') ? 1 : 0;
   2721         uint16_t bit_count = 0;
   2722         bool valid = true;
   2723         for (uint32_t k = tok_start + 1; k < tok_end; k++) {
   2724             if (source[k] >= '0' && source[k] <= '9') {
   2725                 bit_count
   2726                     = (uint16_t)(bit_count * 10 + (uint16_t)(source[k] - '0'));
   2727             } else {
   2728                 valid = false;
   2729                 break;
   2730             }
   2731         }
   2732         if (valid && bit_count > 0) {
   2733             ZirInstData data;
   2734             data.int_type.src_node
   2735                 = (int32_t)node - (int32_t)gz->decl_node_index;
   2736             data.int_type.signedness = signedness;
   2737             data.int_type._pad = 0;
   2738             data.int_type.bit_count = bit_count;
   2739             return addInstruction(gz, ZIR_INST_INT_TYPE, data);
   2740         }
   2741     }
   2742     return ZIR_REF_NONE;
   2743 }
   2744 
   2745 static uint32_t identifierExpr(
   2746     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   2747     AstGenCtx* ag = gz->astgen;
   2748     uint32_t ident_token = ag->tree->nodes.main_tokens[node];
   2749 
   2750     // Check for primitive types FIRST (AstGen.zig:8298-8338).
   2751     uint32_t prim = tryResolvePrimitiveIdent(gz, node);
   2752     if (prim != ZIR_REF_NONE)
   2753         return rvalue(gz, rl, prim, node);
   2754 
   2755     // Scope chain walk (AstGen.zig:8340-8461).
   2756     uint32_t name_str = identAsString(ag, ident_token);
   2757     for (Scope* s = scope; s != NULL;) {
   2758         switch (s->tag) {
   2759         case SCOPE_LOCAL_VAL: {
   2760             ScopeLocalVal* lv = (ScopeLocalVal*)s;
   2761             if (lv->name == name_str)
   2762                 return rvalueNoCoercePreRef(gz, rl, lv->inst, node);
   2763             s = lv->parent;
   2764             continue;
   2765         }
   2766         case SCOPE_LOCAL_PTR: {
   2767             ScopeLocalPtr* lp = (ScopeLocalPtr*)s;
   2768             if (lp->name == name_str) {
   2769                 if (RL_IS_REF(rl))
   2770                     return lp->ptr;
   2771                 return addUnNode(gz, ZIR_INST_LOAD, lp->ptr, node);
   2772             }
   2773             s = lp->parent;
   2774             continue;
   2775         }
   2776         case SCOPE_GEN_ZIR: {
   2777             GenZir* gzs = (GenZir*)s;
   2778             s = gzs->parent;
   2779             continue;
   2780         }
   2781         case SCOPE_DEFER_NORMAL:
   2782         case SCOPE_DEFER_ERROR: {
   2783             ScopeDefer* sd = (ScopeDefer*)s;
   2784             s = sd->parent;
   2785             continue;
   2786         }
   2787         case SCOPE_LABEL: {
   2788             ScopeLabel* sl = (ScopeLabel*)s;
   2789             s = sl->parent;
   2790             continue;
   2791         }
   2792         case SCOPE_NAMESPACE:
   2793         case SCOPE_TOP:
   2794             goto decl_table;
   2795         }
   2796     }
   2797 decl_table:
   2798 
   2799     // Decl table lookup (AstGen.zig:8462-8520).
   2800     for (uint32_t i = 0; i < ag->decl_table_len; i++) {
   2801         if (ag->decl_names[i] == name_str) {
   2802             ZirInstTag itag
   2803                 = (RL_IS_REF(rl)) ? ZIR_INST_DECL_REF : ZIR_INST_DECL_VAL;
   2804             ZirInstData data;
   2805             data.str_tok.start = name_str;
   2806             data.str_tok.src_tok = tokenIndexToRelative(gz, ident_token);
   2807             return addInstruction(gz, itag, data);
   2808         }
   2809     }
   2810 
   2811     SET_ERROR(ag);
   2812     return ZIR_REF_VOID_VALUE;
   2813 }
   2814 
   2815 // --- fieldAccess (AstGen.zig:6154) ---
   2816 // Simplified: emits field_val instruction with Field payload.
   2817 
   2818 static uint32_t fieldAccessExpr(
   2819     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   2820     AstGenCtx* ag = gz->astgen;
   2821     const Ast* tree = ag->tree;
   2822     AstData nd = tree->nodes.datas[node];
   2823 
   2824     // data.lhs = object node, data.rhs = field identifier token.
   2825     uint32_t object_node = nd.lhs;
   2826     uint32_t field_ident = nd.rhs;
   2827 
   2828     // Get field name as string (AstGen.zig:6180).
   2829     uint32_t str_index = identAsString(ag, field_ident);
   2830 
   2831     // Evaluate the LHS object expression (AstGen.zig:6181).
   2832     // For .ref rl, LHS is also evaluated with .ref (AstGen.zig:6161).
   2833     ResultLoc lhs_rl = (RL_IS_REF(rl)) ? RL_REF_VAL : RL_NONE_VAL;
   2834     uint32_t lhs = exprRl(gz, scope, lhs_rl, object_node);
   2835 
   2836     // Emit dbg_stmt for the dot token (AstGen.zig:6183-6184).
   2837     advanceSourceCursorToMainToken(ag, gz, node);
   2838     {
   2839         uint32_t line = ag->source_line - gz->decl_line;
   2840         uint32_t column = ag->source_column;
   2841         emitDbgStmt(gz, line, column);
   2842     }
   2843 
   2844     // Emit field_val instruction with Field payload (AstGen.zig:6186-6189).
   2845     ensureExtraCapacity(ag, 2);
   2846     uint32_t payload_index = ag->extra_len;
   2847     ag->extra[ag->extra_len++] = lhs; // Field.lhs
   2848     ag->extra[ag->extra_len++] = str_index; // Field.field_name_start
   2849 
   2850     // .ref → field_ptr, else → field_val (AstGen.zig:6160-6164).
   2851     ZirInstTag ftag
   2852         = (RL_IS_REF(rl)) ? ZIR_INST_FIELD_PTR : ZIR_INST_FIELD_VAL;
   2853     ZirInstData data;
   2854     data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   2855     data.pl_node.payload_index = payload_index;
   2856     uint32_t access = addInstruction(gz, ftag, data);
   2857     // For ref, return directly; otherwise apply rvalue (AstGen.zig:6161-6164).
   2858     if (RL_IS_REF(rl))
   2859         return access;
   2860     return rvalue(gz, rl, access, node);
   2861 }
   2862 
   2863 // --- ptrType (AstGen.zig:3833) ---
   2864 
   2865 static uint32_t ptrTypeExpr(GenZir* gz, Scope* scope, uint32_t node) {
   2866     AstGenCtx* ag = gz->astgen;
   2867     const Ast* tree = ag->tree;
   2868     AstNodeTag tag = tree->nodes.tags[node];
   2869     AstData nd = tree->nodes.datas[node];
   2870     uint32_t main_tok = tree->nodes.main_tokens[node];
   2871 
   2872     // child_type is always in rhs for all ptr_type variants.
   2873     uint32_t child_type_node = nd.rhs;
   2874 
   2875     // Determine size from main_token (Ast.zig:2122-2131).
   2876     // Pointer.Size: one=0, many=1, slice=2, c=3.
   2877     uint8_t size;
   2878     TokenizerTag main_tok_tag = tree->tokens.tags[main_tok];
   2879     if (main_tok_tag == TOKEN_ASTERISK
   2880         || main_tok_tag == TOKEN_ASTERISK_ASTERISK) {
   2881         size = 0; // one
   2882     } else {
   2883         assert(main_tok_tag == TOKEN_L_BRACKET);
   2884         TokenizerTag next_tag = tree->tokens.tags[main_tok + 1];
   2885         if (next_tag == TOKEN_ASTERISK) {
   2886             // [*c]T vs [*]T: c-pointer if next-next is identifier.
   2887             if (tree->tokens.tags[main_tok + 2] == TOKEN_IDENTIFIER)
   2888                 size = 3; // c
   2889             else
   2890                 size = 1; // many
   2891         } else {
   2892             size = 2; // slice
   2893         }
   2894     }
   2895 
   2896     // Determine sentinel, align, addrspace, bit_range nodes from AST variant
   2897     // (Ast.zig:1656-1696).
   2898     uint32_t sentinel_node = UINT32_MAX;
   2899     uint32_t align_node = UINT32_MAX;
   2900     uint32_t addrspace_node = UINT32_MAX;
   2901     uint32_t bit_range_start = UINT32_MAX;
   2902     uint32_t bit_range_end = UINT32_MAX;
   2903 
   2904     if (tag == AST_NODE_PTR_TYPE_ALIGNED) {
   2905         // opt_node_and_node: lhs = optional align_node (0=none), rhs = child.
   2906         if (nd.lhs != 0)
   2907             align_node = nd.lhs;
   2908     } else if (tag == AST_NODE_PTR_TYPE_SENTINEL) {
   2909         // opt_node_and_node: lhs = optional sentinel (0=none), rhs = child.
   2910         if (nd.lhs != 0)
   2911             sentinel_node = nd.lhs;
   2912     } else if (tag == AST_NODE_PTR_TYPE) {
   2913         // extra_and_node: lhs = extra index to AstPtrType, rhs = child_type.
   2914         const AstPtrType* pt
   2915             = (const AstPtrType*)(tree->extra_data.arr + nd.lhs);
   2916         if (pt->sentinel != UINT32_MAX)
   2917             sentinel_node = pt->sentinel;
   2918         if (pt->align_node != UINT32_MAX)
   2919             align_node = pt->align_node;
   2920         if (pt->addrspace_node != UINT32_MAX)
   2921             addrspace_node = pt->addrspace_node;
   2922     } else if (tag == AST_NODE_PTR_TYPE_BIT_RANGE) {
   2923         // extra_and_node: lhs = extra index to AstPtrTypeBitRange.
   2924         const AstPtrTypeBitRange* pt
   2925             = (const AstPtrTypeBitRange*)(tree->extra_data.arr + nd.lhs);
   2926         if (pt->sentinel != UINT32_MAX)
   2927             sentinel_node = pt->sentinel;
   2928         align_node = pt->align_node;
   2929         if (pt->addrspace_node != UINT32_MAX)
   2930             addrspace_node = pt->addrspace_node;
   2931         bit_range_start = pt->bit_range_start;
   2932         bit_range_end = pt->bit_range_end;
   2933     }
   2934 
   2935     // Scan tokens between main_token and child_type to find const/volatile/
   2936     // allowzero (Ast.zig:2139-2164).
   2937     bool has_const = false;
   2938     bool has_volatile = false;
   2939     bool has_allowzero = false;
   2940     {
   2941         uint32_t i;
   2942         if (sentinel_node != UINT32_MAX) {
   2943             i = lastToken(tree, sentinel_node) + 1;
   2944         } else if (size == 1 || size == 3) {
   2945             // many or c: start after main_token.
   2946             i = main_tok + 1;
   2947         } else {
   2948             i = main_tok;
   2949         }
   2950         uint32_t end = firstToken(tree, child_type_node);
   2951         while (i < end) {
   2952             TokenizerTag tt = tree->tokens.tags[i];
   2953             if (tt == TOKEN_KEYWORD_ALLOWZERO) {
   2954                 has_allowzero = true;
   2955             } else if (tt == TOKEN_KEYWORD_CONST) {
   2956                 has_const = true;
   2957             } else if (tt == TOKEN_KEYWORD_VOLATILE) {
   2958                 has_volatile = true;
   2959             } else if (tt == TOKEN_KEYWORD_ALIGN) {
   2960                 // Skip over align expression.
   2961                 if (bit_range_end != UINT32_MAX)
   2962                     i = lastToken(tree, bit_range_end) + 1;
   2963                 else if (align_node != UINT32_MAX)
   2964                     i = lastToken(tree, align_node) + 1;
   2965             }
   2966             i++;
   2967         }
   2968     }
   2969 
   2970     // Evaluate element type (AstGen.zig:3847).
   2971     uint32_t elem_type = typeExpr(gz, scope, child_type_node);
   2972 
   2973     // Evaluate trailing expressions (AstGen.zig:3856-3897).
   2974     uint32_t sentinel_ref = ZIR_REF_NONE;
   2975     uint32_t align_ref = ZIR_REF_NONE;
   2976     uint32_t addrspace_ref = ZIR_REF_NONE;
   2977     uint32_t bit_start_ref = ZIR_REF_NONE;
   2978     uint32_t bit_end_ref = ZIR_REF_NONE;
   2979     uint32_t trailing_count = 0;
   2980 
   2981     if (sentinel_node != UINT32_MAX) {
   2982         uint32_t reason = (size == 2) ? COMPTIME_REASON_SLICE_SENTINEL
   2983                                       : COMPTIME_REASON_POINTER_SENTINEL;
   2984         ResultLoc srl = {
   2985             .tag = RL_TY, .data = elem_type, .src_node = 0, .ctx = RI_CTX_NONE
   2986         };
   2987         sentinel_ref = comptimeExpr(gz, scope, srl, sentinel_node, reason);
   2988         trailing_count++;
   2989     }
   2990     if (addrspace_node != UINT32_MAX) {
   2991         // Upstream creates addrspace_ty via addBuiltinValue, we don't have
   2992         // that yet, so pass RL_NONE (matching previous behavior).
   2993         addrspace_ref = comptimeExpr(
   2994             gz, scope, RL_NONE_VAL, addrspace_node, COMPTIME_REASON_ADDRSPACE);
   2995         trailing_count++;
   2996     }
   2997     if (align_node != UINT32_MAX) {
   2998         ResultLoc arl = { .tag = RL_COERCED_TY,
   2999             .data = ZIR_REF_U29_TYPE,
   3000             .src_node = 0,
   3001             .ctx = RI_CTX_NONE };
   3002         align_ref
   3003             = comptimeExpr(gz, scope, arl, align_node, COMPTIME_REASON_ALIGN);
   3004         trailing_count++;
   3005     }
   3006     if (bit_range_start != UINT32_MAX) {
   3007         ResultLoc brl = { .tag = RL_COERCED_TY,
   3008             .data = ZIR_REF_U16_TYPE,
   3009             .src_node = 0,
   3010             .ctx = RI_CTX_NONE };
   3011         bit_start_ref = comptimeExpr(
   3012             gz, scope, brl, bit_range_start, COMPTIME_REASON_TYPE);
   3013         bit_end_ref = comptimeExpr(
   3014             gz, scope, brl, bit_range_end, COMPTIME_REASON_TYPE);
   3015         trailing_count += 2;
   3016     }
   3017 
   3018     // Build PtrType payload: { elem_type, src_node } + trailing
   3019     // (AstGen.zig:3905-3921).
   3020     ensureExtraCapacity(ag, 2 + trailing_count);
   3021     uint32_t payload_index = ag->extra_len;
   3022     ag->extra[ag->extra_len++] = elem_type;
   3023     ag->extra[ag->extra_len++]
   3024         = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index);
   3025     if (sentinel_ref != ZIR_REF_NONE)
   3026         ag->extra[ag->extra_len++] = sentinel_ref;
   3027     if (align_ref != ZIR_REF_NONE)
   3028         ag->extra[ag->extra_len++] = align_ref;
   3029     if (addrspace_ref != ZIR_REF_NONE)
   3030         ag->extra[ag->extra_len++] = addrspace_ref;
   3031     if (bit_start_ref != ZIR_REF_NONE) {
   3032         ag->extra[ag->extra_len++] = bit_start_ref;
   3033         ag->extra[ag->extra_len++] = bit_end_ref;
   3034     }
   3035 
   3036     // Build flags packed byte (AstGen.zig:3927-3934).
   3037     uint8_t flags = 0;
   3038     if (has_allowzero)
   3039         flags |= (1 << 0); // is_allowzero
   3040     if (!has_const)
   3041         flags |= (1 << 1); // is_mutable
   3042     if (has_volatile)
   3043         flags |= (1 << 2); // is_volatile
   3044     if (sentinel_ref != ZIR_REF_NONE)
   3045         flags |= (1 << 3); // has_sentinel
   3046     if (align_ref != ZIR_REF_NONE)
   3047         flags |= (1 << 4); // has_align
   3048     if (addrspace_ref != ZIR_REF_NONE)
   3049         flags |= (1 << 5); // has_addrspace
   3050     if (bit_start_ref != ZIR_REF_NONE)
   3051         flags |= (1 << 6); // has_bit_range
   3052 
   3053     ZirInstData data;
   3054     data.ptr_type.flags = flags;
   3055     data.ptr_type.size = size;
   3056     data.ptr_type._pad = 0;
   3057     data.ptr_type.payload_index = payload_index;
   3058     return addInstruction(gz, ZIR_INST_PTR_TYPE, data);
   3059 }
   3060 
   3061 // --- arrayType (AstGen.zig:940) ---
   3062 
   3063 static uint32_t arrayTypeExpr(GenZir* gz, Scope* scope, uint32_t node) {
   3064     AstGenCtx* ag = gz->astgen;
   3065     const Ast* tree = ag->tree;
   3066     AstData nd = tree->nodes.datas[node];
   3067 
   3068     // data.lhs = length expr node, data.rhs = element type node.
   3069     // Check for `_` identifier → compile error (AstGen.zig:3950-3953).
   3070     if (tree->nodes.tags[nd.lhs] == AST_NODE_IDENTIFIER
   3071         && isUnderscoreIdent(tree, nd.lhs)) {
   3072         SET_ERROR(ag);
   3073         return ZIR_REF_VOID_VALUE;
   3074     }
   3075     ResultLoc len_rl = { .tag = RL_COERCED_TY,
   3076         .data = ZIR_REF_USIZE_TYPE,
   3077         .src_node = 0,
   3078         .ctx = RI_CTX_NONE };
   3079     uint32_t len
   3080         = comptimeExpr(gz, scope, len_rl, nd.lhs, COMPTIME_REASON_TYPE);
   3081     uint32_t elem_type = typeExpr(gz, scope, nd.rhs);
   3082     return addPlNodeBin(gz, ZIR_INST_ARRAY_TYPE, node, len, elem_type);
   3083 }
   3084 
   3085 // --- arrayInitExpr (AstGen.zig:1431) ---
   3086 // Simplified: handles typed array init with inferred [_] length.
   3087 
   3088 static uint32_t arrayInitExpr(
   3089     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   3090     AstGenCtx* ag = gz->astgen;
   3091     const Ast* tree = ag->tree;
   3092     AstNodeTag tag = tree->nodes.tags[node];
   3093     AstData nd = tree->nodes.datas[node];
   3094 
   3095     // Get elements and type expression based on the variant.
   3096     uint32_t type_expr_node = 0;
   3097     uint32_t elem_buf[2];
   3098     const uint32_t* elements = NULL;
   3099     uint32_t elem_count = 0;
   3100 
   3101     switch (tag) {
   3102     case AST_NODE_ARRAY_INIT_ONE:
   3103     case AST_NODE_ARRAY_INIT_ONE_COMMA: {
   3104         type_expr_node = nd.lhs;
   3105         if (nd.rhs != 0) {
   3106             elem_buf[0] = nd.rhs;
   3107             elements = elem_buf;
   3108             elem_count = 1;
   3109         }
   3110         break;
   3111     }
   3112     case AST_NODE_ARRAY_INIT:
   3113     case AST_NODE_ARRAY_INIT_COMMA: {
   3114         // data = node_and_extra: lhs = type_expr, rhs = extra_index.
   3115         // extra[rhs] = SubRange.start, extra[rhs+1] = SubRange.end.
   3116         // Elements are extra_data[start..end].
   3117         type_expr_node = nd.lhs;
   3118         uint32_t extra_idx = nd.rhs;
   3119         uint32_t range_start = tree->extra_data.arr[extra_idx];
   3120         uint32_t range_end = tree->extra_data.arr[extra_idx + 1];
   3121         elements = tree->extra_data.arr + range_start;
   3122         elem_count = range_end - range_start;
   3123         break;
   3124     }
   3125     default:
   3126         SET_ERROR(ag);
   3127         return ZIR_REF_VOID_VALUE;
   3128     }
   3129 
   3130     if (type_expr_node == 0 || elem_count == 0) {
   3131         SET_ERROR(ag);
   3132         return ZIR_REF_VOID_VALUE;
   3133     }
   3134 
   3135     // Check if the type is [_]T (inferred length) (AstGen.zig:1446-1474).
   3136     if (tree->nodes.tags[type_expr_node] == AST_NODE_ARRAY_TYPE) {
   3137         AstData type_nd = tree->nodes.datas[type_expr_node];
   3138         uint32_t elem_count_node = type_nd.lhs;
   3139         uint32_t elem_type_node = type_nd.rhs;
   3140 
   3141         // Check if elem_count is `_` identifier.
   3142         if (tree->nodes.tags[elem_count_node] == AST_NODE_IDENTIFIER
   3143             && isUnderscoreIdent(tree, elem_count_node)) {
   3144             // Inferred length: addInt(elem_count) (AstGen.zig:1452).
   3145             uint32_t len_inst = addInt(gz, elem_count);
   3146             uint32_t elem_type = typeExpr(gz, scope, elem_type_node);
   3147             uint32_t array_type_inst = addPlNodeBin(
   3148                 gz, ZIR_INST_ARRAY_TYPE, type_expr_node, len_inst, elem_type);
   3149 
   3150             // arrayInitExprTyped (AstGen.zig:1484-1513, 1598-1642).
   3151             // Only RL_REF produces array_init_ref; all other RLs use
   3152             // array_init + rvalue (AstGen.zig:1507-1511).
   3153             bool is_ref = (rl.tag == RL_REF);
   3154             uint32_t operands_len = elem_count + 1;
   3155             ensureExtraCapacity(ag, 1 + operands_len);
   3156             uint32_t payload_index = ag->extra_len;
   3157             ag->extra[ag->extra_len++] = operands_len;
   3158             ag->extra[ag->extra_len++] = array_type_inst;
   3159             uint32_t extra_start = ag->extra_len;
   3160             ag->extra_len += elem_count;
   3161             for (uint32_t i = 0; i < elem_count; i++) {
   3162                 // Use elem_type as coercion target for each element.
   3163                 ResultLoc elem_rl = {
   3164                     .tag = RL_COERCED_TY, .data = elem_type, .src_node = 0
   3165                 };
   3166                 uint32_t elem_ref = exprRl(gz, scope, elem_rl, elements[i]);
   3167                 ag->extra[extra_start + i] = elem_ref;
   3168             }
   3169             ZirInstTag init_tag
   3170                 = is_ref ? ZIR_INST_ARRAY_INIT_REF : ZIR_INST_ARRAY_INIT;
   3171             ZirInstData idata;
   3172             idata.pl_node.src_node
   3173                 = (int32_t)node - (int32_t)gz->decl_node_index;
   3174             idata.pl_node.payload_index = payload_index;
   3175             uint32_t result = addInstruction(gz, init_tag, idata);
   3176             if (is_ref)
   3177                 return result;
   3178             return rvalue(gz, rl, result, node);
   3179         }
   3180     }
   3181 
   3182     // Non-inferred length: evaluate type normally.
   3183     SET_ERROR(ag);
   3184     return ZIR_REF_VOID_VALUE;
   3185 }
   3186 
   3187 // --- simpleBinOp (AstGen.zig:2204) ---
   3188 
   3189 static uint32_t simpleBinOp(
   3190     GenZir* gz, Scope* scope, uint32_t node, ZirInstTag op_tag) {
   3191     AstGenCtx* ag = gz->astgen;
   3192     AstData nd = ag->tree->nodes.datas[node];
   3193     uint32_t lhs = exprRl(gz, scope, RL_NONE_VAL, nd.lhs);
   3194     // For arithmetic ops, advance cursor before RHS (AstGen.zig:6245-6256).
   3195     uint32_t saved_line = 0, saved_col = 0;
   3196     bool need_dbg = false;
   3197     if (op_tag == ZIR_INST_ADD || op_tag == ZIR_INST_SUB
   3198         || op_tag == ZIR_INST_MUL || op_tag == ZIR_INST_DIV
   3199         || op_tag == ZIR_INST_MOD_REM) {
   3200         if (!gz->is_comptime) {
   3201             advanceSourceCursorToMainToken(ag, gz, node);
   3202         }
   3203         saved_line = ag->source_line - gz->decl_line;
   3204         saved_col = ag->source_column;
   3205         need_dbg = true;
   3206     }
   3207     uint32_t rhs = exprRl(gz, scope, RL_NONE_VAL, nd.rhs);
   3208     if (need_dbg) {
   3209         emitDbgStmt(gz, saved_line, saved_col);
   3210     }
   3211     return addPlNodeBin(gz, op_tag, node, lhs, rhs);
   3212 }
   3213 
   3214 // --- shiftOp (AstGen.zig:9978) ---
   3215 
   3216 static uint32_t shiftOp(
   3217     GenZir* gz, Scope* scope, uint32_t node, ZirInstTag tag) {
   3218     AstGenCtx* ag = gz->astgen;
   3219     AstData nd = ag->tree->nodes.datas[node];
   3220     uint32_t lhs = exprRl(gz, scope, RL_NONE_VAL, nd.lhs);
   3221 
   3222     advanceSourceCursorToMainToken(ag, gz, node);
   3223     uint32_t saved_line = ag->source_line - gz->decl_line;
   3224     uint32_t saved_col = ag->source_column;
   3225 
   3226     uint32_t log2_int_type
   3227         = addUnNode(gz, ZIR_INST_TYPEOF_LOG2_INT_TYPE, lhs, nd.lhs);
   3228     ResultLoc rhs_rl = { .tag = RL_TY,
   3229         .data = log2_int_type,
   3230         .src_node = 0,
   3231         .ctx = RI_CTX_SHIFT_OP };
   3232     uint32_t rhs = exprRl(gz, scope, rhs_rl, nd.rhs);
   3233 
   3234     emitDbgStmt(gz, saved_line, saved_col);
   3235 
   3236     return addPlNodeBin(gz, tag, node, lhs, rhs);
   3237 }
   3238 
   3239 // --- multilineStringLiteral (AstGen.zig:8645) ---
   3240 // Port of strLitNodeAsString for multiline strings.
   3241 static uint32_t multilineStringLiteral(
   3242     GenZir* gz, Scope* scope, uint32_t node) {
   3243     (void)scope;
   3244     AstGenCtx* ag = gz->astgen;
   3245     const Ast* tree = ag->tree;
   3246     AstData nd = tree->nodes.datas[node];
   3247     uint32_t start_tok = nd.lhs;
   3248     uint32_t end_tok = nd.rhs;
   3249 
   3250     uint32_t str_index = ag->string_bytes_len;
   3251 
   3252     // First line: no preceding newline.
   3253     for (uint32_t tok_i = start_tok; tok_i <= end_tok; tok_i++) {
   3254         uint32_t tok_start = tree->tokens.starts[tok_i];
   3255         const char* source = tree->source;
   3256         // Skip leading `\\` (2 chars).
   3257         uint32_t content_start = tok_start + 2;
   3258         // Find end of line.
   3259         uint32_t content_end = content_start;
   3260         while (content_end < tree->source_len && source[content_end] != '\n')
   3261             content_end++;
   3262         uint32_t line_len = content_end - content_start;
   3263 
   3264         if (tok_i > start_tok) {
   3265             // Prepend newline for lines after the first.
   3266             ensureStringBytesCapacity(ag, line_len + 1);
   3267             ag->string_bytes[ag->string_bytes_len++] = '\n';
   3268         } else {
   3269             ensureStringBytesCapacity(ag, line_len);
   3270         }
   3271         memcpy(ag->string_bytes + ag->string_bytes_len, source + content_start,
   3272             line_len);
   3273         ag->string_bytes_len += line_len;
   3274     }
   3275 
   3276     uint32_t len = ag->string_bytes_len - str_index;
   3277     ensureStringBytesCapacity(ag, 1);
   3278     ag->string_bytes[ag->string_bytes_len++] = 0; // null terminator
   3279 
   3280     ZirInstData data;
   3281     data.str.start = str_index;
   3282     data.str.len = len;
   3283     return addInstruction(gz, ZIR_INST_STR, data);
   3284 }
   3285 
   3286 // --- ret (AstGen.zig:8119) ---
   3287 static uint32_t retExpr(GenZir* gz, Scope* scope, uint32_t node) {
   3288     AstGenCtx* ag = gz->astgen;
   3289     const Ast* tree = ag->tree;
   3290 
   3291     // Ensure debug line/column information is emitted for this return
   3292     // expression (AstGen.zig:8141-8144).
   3293     if (!gz->is_comptime) {
   3294         emitDbgNode(gz, node);
   3295     }
   3296     uint32_t ret_lc_line = ag->source_line - gz->decl_line;
   3297     uint32_t ret_lc_column = ag->source_column;
   3298 
   3299     // AstGen.zig:8123: return outside function is an error.
   3300     if (ag->fn_block == NULL) {
   3301         SET_ERROR(ag);
   3302         return ZIR_REF_UNREACHABLE_VALUE;
   3303     }
   3304     const Scope* defer_outer = &((GenZir*)ag->fn_block)->base;
   3305 
   3306     AstData nd = tree->nodes.datas[node];
   3307     uint32_t operand_node = nd.lhs; // optional
   3308 
   3309     if (operand_node == 0) {
   3310         // Void return (AstGen.zig:8148-8156).
   3311         genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY);
   3312         // Restore error trace unconditionally (AstGen.zig:8153).
   3313         ZirInstData rdata;
   3314         rdata.un_node.operand = ZIR_REF_NONE;
   3315         rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   3316         addInstruction(
   3317             gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata);
   3318         addUnNode(gz, ZIR_INST_RET_NODE, ZIR_REF_VOID_VALUE, node);
   3319         return ZIR_REF_UNREACHABLE_VALUE;
   3320     }
   3321 
   3322     // Fast path: return error.Foo (AstGen.zig:8159-8175).
   3323     if (tree->nodes.tags[operand_node] == AST_NODE_ERROR_VALUE) {
   3324         uint32_t error_token = tree->nodes.main_tokens[operand_node] + 2;
   3325         uint32_t err_name_str = identAsString(ag, error_token);
   3326         DeferCounts dc = countDefers(defer_outer, scope);
   3327         if (!dc.need_err_code) {
   3328             genDefers(gz, defer_outer, scope, DEFER_BOTH_SANS_ERR);
   3329             emitDbgStmt(gz, ret_lc_line, ret_lc_column);
   3330             addStrTok(gz, ZIR_INST_RET_ERR_VALUE, err_name_str, error_token);
   3331             return ZIR_REF_UNREACHABLE_VALUE;
   3332         }
   3333         // need_err_code path: not implemented yet, fall through to general.
   3334     }
   3335 
   3336     // Evaluate operand with result location (AstGen.zig:8178-8186).
   3337     // If nodes_need_rl contains this return node, use ptr-based RL;
   3338     // otherwise use coerced_ty.
   3339     ResultLoc ret_rl = RL_NONE_VAL;
   3340     bool use_ptr = nodesNeedRlContains(ag, node);
   3341     uint32_t ret_ptr_inst = 0;
   3342     if (use_ptr) {
   3343         // Create ret_ptr instruction (AstGen.zig:8179).
   3344         ZirInstData rpdata;
   3345         rpdata.node = (int32_t)node - (int32_t)gz->decl_node_index;
   3346         ret_ptr_inst = addInstruction(gz, ZIR_INST_RET_PTR, rpdata);
   3347         ret_rl.tag = RL_PTR;
   3348         ret_rl.data = ret_ptr_inst;
   3349     } else if (ag->fn_ret_ty != 0) {
   3350         ret_rl.tag = RL_COERCED_TY;
   3351         ret_rl.data = ag->fn_ret_ty;
   3352     }
   3353     ret_rl.ctx = RI_CTX_RETURN;
   3354     uint32_t operand = exprRl(gz, scope, ret_rl, operand_node);
   3355 
   3356     // Emit RESTORE_ERR_RET_INDEX based on nodeMayEvalToError
   3357     // (AstGen.zig:8188-8253).
   3358     int eval_to_err = nodeMayEvalToError(tree, operand_node);
   3359     if (eval_to_err == EVAL_TO_ERROR_NEVER) {
   3360         // Returning non-error: pop error trace unconditionally
   3361         // (AstGen.zig:8190-8198).
   3362         genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY);
   3363         ZirInstData rdata;
   3364         rdata.un_node.operand = ZIR_REF_NONE;
   3365         rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   3366         addInstruction(
   3367             gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata);
   3368         emitDbgStmt(gz, ret_lc_line, ret_lc_column);
   3369         // addRet (AstGen.zig:13188-13194).
   3370         if (use_ptr) {
   3371             addUnNode(gz, ZIR_INST_RET_LOAD, ret_ptr_inst, node);
   3372         } else {
   3373             addUnNode(gz, ZIR_INST_RET_NODE, operand, node);
   3374         }
   3375         return ZIR_REF_UNREACHABLE_VALUE;
   3376     } else if (eval_to_err == EVAL_TO_ERROR_ALWAYS) {
   3377         // .always: emit both error defers and regular defers
   3378         // (AstGen.zig:8200-8206).
   3379         uint32_t err_code = use_ptr
   3380             ? addUnNode(gz, ZIR_INST_LOAD, ret_ptr_inst, node)
   3381             : operand;
   3382         (void)err_code;
   3383         // TODO: genDefers with .both = err_code when errdefer is implemented.
   3384         genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY);
   3385         emitDbgStmt(gz, ret_lc_line, ret_lc_column);
   3386         if (use_ptr) {
   3387             addUnNode(gz, ZIR_INST_RET_LOAD, ret_ptr_inst, node);
   3388         } else {
   3389             addUnNode(gz, ZIR_INST_RET_NODE, operand, node);
   3390         }
   3391         return ZIR_REF_UNREACHABLE_VALUE;
   3392     } else {
   3393         // .maybe (AstGen.zig:8208-8252).
   3394         DeferCounts dc = countDefers(defer_outer, scope);
   3395         if (!dc.have_err) {
   3396             // Only regular defers; no branch needed (AstGen.zig:8210-8220).
   3397             genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY);
   3398             emitDbgStmt(gz, ret_lc_line, ret_lc_column);
   3399             uint32_t result = use_ptr
   3400                 ? addUnNode(gz, ZIR_INST_LOAD, ret_ptr_inst, node)
   3401                 : operand;
   3402             ZirInstData rdata;
   3403             rdata.un_node.operand = result;
   3404             rdata.un_node.src_node
   3405                 = (int32_t)node - (int32_t)gz->decl_node_index;
   3406             addInstruction(gz, ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY, rdata);
   3407             if (use_ptr) {
   3408                 addUnNode(gz, ZIR_INST_RET_LOAD, ret_ptr_inst, node);
   3409             } else {
   3410                 addUnNode(gz, ZIR_INST_RET_NODE, operand, node);
   3411             }
   3412             return ZIR_REF_UNREACHABLE_VALUE;
   3413         }
   3414         // have_err path: emit conditional branch (not yet implemented).
   3415         // Fall through to simplified path.
   3416         genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY);
   3417         emitDbgStmt(gz, ret_lc_line, ret_lc_column);
   3418         if (use_ptr) {
   3419             addUnNode(gz, ZIR_INST_RET_LOAD, ret_ptr_inst, node);
   3420         } else {
   3421             addUnNode(gz, ZIR_INST_RET_NODE, operand, node);
   3422         }
   3423         return ZIR_REF_UNREACHABLE_VALUE;
   3424     }
   3425 }
   3426 
   3427 // --- calleeExpr (AstGen.zig:10183) ---
   3428 // Returns: 0 = direct call, 1 = field call.
   3429 
   3430 typedef struct {
   3431     bool is_field;
   3432     uint32_t obj_ptr; // for field calls: ref to object
   3433     uint32_t field_name_start; // for field calls: string index
   3434     uint32_t direct; // for direct calls: ref to callee
   3435 } Callee;
   3436 
   3437 static Callee calleeExpr(
   3438     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t fn_expr_node) {
   3439     AstGenCtx* ag = gz->astgen;
   3440     const Ast* tree = ag->tree;
   3441     AstNodeTag tag = tree->nodes.tags[fn_expr_node];
   3442 
   3443     if (tag == AST_NODE_FIELD_ACCESS) {
   3444         AstData nd = tree->nodes.datas[fn_expr_node];
   3445         uint32_t object_node = nd.lhs;
   3446         uint32_t field_ident = nd.rhs;
   3447         uint32_t str_index = identAsString(ag, field_ident);
   3448         // Evaluate object with .ref rl (AstGen.zig:10207).
   3449         uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, object_node);
   3450 
   3451         // Advance to main token (the `.` dot) — not first token
   3452         // (AstGen.zig:10209).
   3453         advanceSourceCursorToMainToken(ag, gz, fn_expr_node);
   3454         {
   3455             uint32_t line = ag->source_line - gz->decl_line;
   3456             uint32_t column = ag->source_column;
   3457             emitDbgStmt(gz, line, column);
   3458         }
   3459 
   3460         Callee c;
   3461         c.is_field = true;
   3462         c.obj_ptr = lhs;
   3463         c.field_name_start = str_index;
   3464         c.direct = 0;
   3465         return c;
   3466     }
   3467 
   3468     // enum_literal callee: decl literal call syntax (AstGen.zig:10217-10233).
   3469     if (tag == AST_NODE_ENUM_LITERAL) {
   3470         uint32_t res_ty = rlResultType(gz, rl, fn_expr_node);
   3471         if (res_ty != 0) {
   3472             uint32_t str_index
   3473                 = identAsString(ag, tree->nodes.main_tokens[fn_expr_node]);
   3474             uint32_t callee = addPlNodeBin(gz, ZIR_INST_DECL_LITERAL_NO_COERCE,
   3475                 fn_expr_node, res_ty, str_index);
   3476             Callee c;
   3477             c.is_field = false;
   3478             c.direct = callee;
   3479             c.obj_ptr = 0;
   3480             c.field_name_start = 0;
   3481             return c;
   3482         }
   3483         // No result type: fall through to expr with rl=none.
   3484     }
   3485 
   3486     // Default: direct call (AstGen.zig:10235).
   3487     Callee c;
   3488     c.is_field = false;
   3489     c.direct = expr(gz, scope, fn_expr_node);
   3490     c.obj_ptr = 0;
   3491     c.field_name_start = 0;
   3492     return c;
   3493 }
   3494 
   3495 // --- callExpr (AstGen.zig:10058) ---
   3496 static uint32_t callExpr(
   3497     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   3498     AstGenCtx* ag = gz->astgen;
   3499     const Ast* tree = ag->tree;
   3500     AstNodeTag tag = tree->nodes.tags[node];
   3501     AstData nd = tree->nodes.datas[node];
   3502 
   3503     // Extract callee and args from AST.
   3504     uint32_t fn_expr_node;
   3505     uint32_t arg_buf[2];
   3506     const uint32_t* args = NULL;
   3507     uint32_t args_len = 0;
   3508     uint32_t lparen_tok;
   3509 
   3510     switch (tag) {
   3511     case AST_NODE_CALL_ONE:
   3512     case AST_NODE_CALL_ONE_COMMA: {
   3513         fn_expr_node = nd.lhs;
   3514         lparen_tok = tree->nodes.main_tokens[node];
   3515         if (nd.rhs != 0) {
   3516             arg_buf[0] = nd.rhs;
   3517             args = arg_buf;
   3518             args_len = 1;
   3519         }
   3520         break;
   3521     }
   3522     case AST_NODE_CALL:
   3523     case AST_NODE_CALL_COMMA: {
   3524         fn_expr_node = nd.lhs;
   3525         lparen_tok = tree->nodes.main_tokens[node];
   3526         uint32_t extra_idx = nd.rhs;
   3527         uint32_t range_start = tree->extra_data.arr[extra_idx];
   3528         uint32_t range_end = tree->extra_data.arr[extra_idx + 1];
   3529         args = tree->extra_data.arr + range_start;
   3530         args_len = range_end - range_start;
   3531         break;
   3532     }
   3533     default:
   3534         SET_ERROR(ag);
   3535         return ZIR_REF_VOID_VALUE;
   3536     }
   3537 
   3538     Callee callee = calleeExpr(gz, scope, rl, fn_expr_node);
   3539 
   3540     // dbg_stmt before call (AstGen.zig:10078-10083).
   3541     {
   3542         advanceSourceCursor(ag, tree->tokens.starts[lparen_tok]);
   3543         uint32_t line = ag->source_line - gz->decl_line;
   3544         uint32_t column = ag->source_column;
   3545         emitDbgStmtForceCurrentIndex(gz, line, column);
   3546     }
   3547 
   3548     // Reserve instruction slot for call (AstGen.zig:10093).
   3549     uint32_t call_index = ag->inst_len;
   3550     ensureInstCapacity(ag, 1);
   3551     memset(&ag->inst_datas[call_index], 0, sizeof(ZirInstData));
   3552     ag->inst_tags[call_index] = (ZirInstTag)0;
   3553     ag->inst_len++;
   3554     gzAppendInstruction(gz, call_index);
   3555 
   3556     // Process arguments in sub-blocks (AstGen.zig:10096-10116).
   3557     // Upstream uses a separate scratch array; we use a local buffer for body
   3558     // lengths and append body instructions to scratch_extra, then copy all
   3559     // to extra after the call payload.
   3560     uint32_t call_inst = call_index + ZIR_REF_START_INDEX;
   3561     ResultLoc arg_rl = { .tag = RL_COERCED_TY,
   3562         .data = call_inst,
   3563         .src_node = 0,
   3564         .ctx = RI_CTX_FN_ARG };
   3565 
   3566     // Use scratch_extra to collect body lengths + body instructions,
   3567     // mirroring upstream's scratch array (AstGen.zig:10096-10116).
   3568     uint32_t scratch_top = ag->scratch_extra_len;
   3569     // Reserve space for cumulative body lengths (one per arg).
   3570     ensureScratchExtraCapacity(ag, args_len);
   3571     ag->scratch_extra_len += args_len;
   3572 
   3573     for (uint32_t i = 0; i < args_len; i++) {
   3574         GenZir arg_block = makeSubBlock(gz, scope);
   3575         uint32_t arg_ref
   3576             = exprRl(&arg_block, &arg_block.base, arg_rl, args[i]);
   3577 
   3578         // break_inline with param_node src (AstGen.zig:10108).
   3579         int32_t param_src
   3580             = (int32_t)args[i] - (int32_t)arg_block.decl_node_index;
   3581         makeBreakInline(&arg_block, call_index, arg_ref, param_src);
   3582 
   3583         // Append arg_block body to scratch_extra (with ref_table fixups).
   3584         uint32_t raw_body_len = gzInstructionsLen(&arg_block);
   3585         const uint32_t* body = gzInstructionsSlice(&arg_block);
   3586         uint32_t fixup_len = countBodyLenAfterFixups(ag, body, raw_body_len);
   3587         ensureScratchExtraCapacity(ag, fixup_len);
   3588         for (uint32_t j = 0; j < raw_body_len; j++) {
   3589             appendPossiblyRefdBodyInstScratch(ag, body[j]);
   3590         }
   3591         // Record cumulative body length (AstGen.zig:10114).
   3592         ag->scratch_extra[scratch_top + i]
   3593             = ag->scratch_extra_len - scratch_top;
   3594         gzUnstack(&arg_block);
   3595     }
   3596 
   3597     // Build call payload (AstGen.zig:10118-10168).
   3598     // Upstream layout: [flags, callee/obj_ptr, field_name_start], then
   3599     // body_lengths + body_instructions from scratch.
   3600     // Flags layout (packed): modifier:u3, ensure_result_used:bool,
   3601     // pop_error_return_trace:bool, args_len:u27.
   3602     // pop_error_return_trace = !propagate_error_trace
   3603     // (AstGen.zig:10121-10124).
   3604     bool propagate_error_trace
   3605         = (rl.ctx == RI_CTX_ERROR_HANDLING_EXPR || rl.ctx == RI_CTX_RETURN
   3606             || rl.ctx == RI_CTX_FN_ARG || rl.ctx == RI_CTX_CONST_INIT);
   3607     uint32_t flags = (propagate_error_trace ? 0u : (1u << 4))
   3608         | ((args_len & 0x7FFFFFFu) << 5); // args_len
   3609 
   3610     if (callee.is_field) {
   3611         // FieldCall: {flags, obj_ptr, field_name_start} (AstGen.zig:10148).
   3612         ensureExtraCapacity(ag, 3 + (ag->scratch_extra_len - scratch_top));
   3613         uint32_t payload_index = ag->extra_len;
   3614         ag->extra[ag->extra_len++] = flags;
   3615         ag->extra[ag->extra_len++] = callee.obj_ptr;
   3616         ag->extra[ag->extra_len++] = callee.field_name_start;
   3617         // Append scratch data (body lengths + body instructions).
   3618         if (args_len != 0) {
   3619             memcpy(ag->extra + ag->extra_len, ag->scratch_extra + scratch_top,
   3620                 (ag->scratch_extra_len - scratch_top) * sizeof(uint32_t));
   3621             ag->extra_len += ag->scratch_extra_len - scratch_top;
   3622         }
   3623         ag->inst_tags[call_index] = ZIR_INST_FIELD_CALL;
   3624         ag->inst_datas[call_index].pl_node.src_node
   3625             = (int32_t)node - (int32_t)gz->decl_node_index;
   3626         ag->inst_datas[call_index].pl_node.payload_index = payload_index;
   3627     } else {
   3628         // Call: {flags, callee} (AstGen.zig:10128).
   3629         ensureExtraCapacity(ag, 2 + (ag->scratch_extra_len - scratch_top));
   3630         uint32_t payload_index = ag->extra_len;
   3631         ag->extra[ag->extra_len++] = flags;
   3632         ag->extra[ag->extra_len++] = callee.direct;
   3633         // Append scratch data (body lengths + body instructions).
   3634         if (args_len != 0) {
   3635             memcpy(ag->extra + ag->extra_len, ag->scratch_extra + scratch_top,
   3636                 (ag->scratch_extra_len - scratch_top) * sizeof(uint32_t));
   3637             ag->extra_len += ag->scratch_extra_len - scratch_top;
   3638         }
   3639         ag->inst_tags[call_index] = ZIR_INST_CALL;
   3640         ag->inst_datas[call_index].pl_node.src_node
   3641             = (int32_t)node - (int32_t)gz->decl_node_index;
   3642         ag->inst_datas[call_index].pl_node.payload_index = payload_index;
   3643     }
   3644 
   3645     // Restore scratch (AstGen.zig:10097 defer).
   3646     ag->scratch_extra_len = scratch_top;
   3647 
   3648     return call_index + ZIR_REF_START_INDEX;
   3649 }
   3650 
   3651 // --- structInitExpr (AstGen.zig:1674) ---
   3652 // Simplified: handles .{} (empty tuple), .{.a = b} (anon init).
   3653 static uint32_t structInitExpr(
   3654     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   3655     AstGenCtx* ag = gz->astgen;
   3656     const Ast* tree = ag->tree;
   3657     AstNodeTag tag = tree->nodes.tags[node];
   3658     AstData nd = tree->nodes.datas[node];
   3659 
   3660     // Extract type_expr and fields.
   3661     uint32_t type_expr_node = 0; // 0 = anonymous (.{...})
   3662     uint32_t field_buf[2];
   3663     const uint32_t* fields = NULL;
   3664     uint32_t fields_len = 0;
   3665 
   3666     switch (tag) {
   3667     case AST_NODE_STRUCT_INIT_DOT_TWO:
   3668     case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: {
   3669         // .{.a = lhs, .b = rhs}
   3670         uint32_t idx = 0;
   3671         if (nd.lhs != 0)
   3672             field_buf[idx++] = nd.lhs;
   3673         if (nd.rhs != 0)
   3674             field_buf[idx++] = nd.rhs;
   3675         fields = field_buf;
   3676         fields_len = idx;
   3677         break;
   3678     }
   3679     case AST_NODE_STRUCT_INIT_DOT:
   3680     case AST_NODE_STRUCT_INIT_DOT_COMMA: {
   3681         uint32_t start = nd.lhs;
   3682         uint32_t end = nd.rhs;
   3683         fields = tree->extra_data.arr + start;
   3684         fields_len = end - start;
   3685         break;
   3686     }
   3687     case AST_NODE_STRUCT_INIT_ONE:
   3688     case AST_NODE_STRUCT_INIT_ONE_COMMA: {
   3689         type_expr_node = nd.lhs;
   3690         if (nd.rhs != 0) {
   3691             field_buf[0] = nd.rhs;
   3692             fields = field_buf;
   3693             fields_len = 1;
   3694         }
   3695         break;
   3696     }
   3697     case AST_NODE_STRUCT_INIT:
   3698     case AST_NODE_STRUCT_INIT_COMMA: {
   3699         type_expr_node = nd.lhs;
   3700         uint32_t extra_idx = nd.rhs;
   3701         uint32_t range_start = tree->extra_data.arr[extra_idx];
   3702         uint32_t range_end = tree->extra_data.arr[extra_idx + 1];
   3703         fields = tree->extra_data.arr + range_start;
   3704         fields_len = range_end - range_start;
   3705         break;
   3706     }
   3707     default:
   3708         SET_ERROR(ag);
   3709         return ZIR_REF_VOID_VALUE;
   3710     }
   3711 
   3712     if (type_expr_node == 0 && fields_len == 0) {
   3713         // .{} — depends on result location (AstGen.zig:1687-1698).
   3714         if (rl.tag == RL_REF_COERCED_TY) {
   3715             return addUnNode(
   3716                 gz, ZIR_INST_STRUCT_INIT_EMPTY_REF_RESULT, rl.data, node);
   3717         }
   3718         if (rl.tag == RL_TY || rl.tag == RL_COERCED_TY) {
   3719             return addUnNode(
   3720                 gz, ZIR_INST_STRUCT_INIT_EMPTY_RESULT, rl.data, node);
   3721         }
   3722         if (rl.tag == RL_DISCARD) {
   3723             return ZIR_REF_VOID_VALUE;
   3724         }
   3725         return ZIR_REF_EMPTY_TUPLE;
   3726     }
   3727 
   3728     // Pre-register all field names to match upstream string ordering.
   3729     // Upstream has a duplicate name check (AstGen.zig:1756-1806) that
   3730     // adds all field names to string_bytes before evaluating values.
   3731     for (uint32_t i = 0; i < fields_len; i++) {
   3732         uint32_t name_token = firstToken(tree, fields[i]) - 2;
   3733         identAsString(ag, name_token);
   3734     }
   3735 
   3736     if (type_expr_node == 0 && fields_len > 0) {
   3737         // structInitExprPtr for RL_PTR (AstGen.zig:1843-1846, 1934-1964).
   3738         if (rl.tag == RL_PTR) {
   3739             uint32_t struct_ptr_inst
   3740                 = addUnNode(gz, ZIR_INST_OPT_EU_BASE_PTR_INIT, rl.data, node);
   3741             // Block payload: body_len = fields_len.
   3742             ensureExtraCapacity(ag, 1 + fields_len);
   3743             uint32_t payload_index = ag->extra_len;
   3744             ag->extra[ag->extra_len++] = fields_len;
   3745             uint32_t items_start = ag->extra_len;
   3746             ag->extra_len += fields_len;
   3747 
   3748             for (uint32_t i = 0; i < fields_len; i++) {
   3749                 uint32_t field_init = fields[i];
   3750                 uint32_t name_token = firstToken(tree, field_init) - 2;
   3751                 uint32_t str_index = identAsString(ag, name_token);
   3752                 // struct_init_field_ptr (AstGen.zig:1954-1957).
   3753                 uint32_t field_ptr
   3754                     = addPlNodeBin(gz, ZIR_INST_STRUCT_INIT_FIELD_PTR,
   3755                         field_init, struct_ptr_inst, str_index);
   3756                 ag->extra[items_start + i]
   3757                     = field_ptr - ZIR_REF_START_INDEX; // .toIndex()
   3758                 // Evaluate init with ptr RL (AstGen.zig:1960).
   3759                 ResultLoc ptr_rl = { .tag = RL_PTR,
   3760                     .data = field_ptr,
   3761                     .src_node = 0,
   3762                     .ctx = rl.ctx };
   3763                 exprRl(gz, scope, ptr_rl, field_init);
   3764             }
   3765             addPlNodePayloadIndex(
   3766                 gz, ZIR_INST_VALIDATE_PTR_STRUCT_INIT, node, payload_index);
   3767             return ZIR_REF_VOID_VALUE;
   3768         }
   3769         // Anonymous struct init with RL type (AstGen.zig:1706-1731).
   3770         if (rl.tag == RL_TY || rl.tag == RL_COERCED_TY) {
   3771             uint32_t ty_inst = rl.data;
   3772             // validate_struct_init_result_ty (AstGen.zig:1840).
   3773             addUnNode(
   3774                 gz, ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY, ty_inst, node);
   3775             // structInitExprTyped (AstGen.zig:1896-1931).
   3776             ensureExtraCapacity(ag, 3 + fields_len * 2);
   3777             uint32_t payload_index = ag->extra_len;
   3778             ag->extra[ag->extra_len++] = node;
   3779             ag->extra[ag->extra_len++] = ag->source_line;
   3780             ag->extra[ag->extra_len++] = fields_len;
   3781             uint32_t items_start = ag->extra_len;
   3782             ag->extra_len += fields_len * 2;
   3783             for (uint32_t i = 0; i < fields_len; i++) {
   3784                 uint32_t field_init = fields[i];
   3785                 uint32_t name_token = firstToken(tree, field_init) - 2;
   3786                 uint32_t str_index = identAsString(ag, name_token);
   3787                 uint32_t field_ty_inst
   3788                     = addPlNodeBin(gz, ZIR_INST_STRUCT_INIT_FIELD_TYPE,
   3789                         field_init, ty_inst, str_index);
   3790                 ResultLoc elem_rl = {
   3791                     .tag = RL_COERCED_TY, .data = field_ty_inst, .src_node = 0
   3792                 };
   3793                 uint32_t init_ref = exprRl(gz, scope, elem_rl, field_init);
   3794                 ag->extra[items_start + i * 2]
   3795                     = field_ty_inst - ZIR_REF_START_INDEX;
   3796                 ag->extra[items_start + i * 2 + 1] = init_ref;
   3797             }
   3798             return addPlNodePayloadIndex(
   3799                 gz, ZIR_INST_STRUCT_INIT, node, payload_index);
   3800         }
   3801         // Anonymous struct init without RL type (AstGen.zig:1864).
   3802         // StructInitAnon payload: abs_node, abs_line, fields_len.
   3803         ensureExtraCapacity(ag, 3 + fields_len * 2);
   3804         uint32_t payload_index = ag->extra_len;
   3805         ag->extra[ag->extra_len++] = node; // abs_node
   3806         ag->extra[ag->extra_len++] = ag->source_line; // abs_line
   3807         ag->extra[ag->extra_len++] = fields_len;
   3808         // Reserve space for field entries.
   3809         uint32_t items_start = ag->extra_len;
   3810         ag->extra_len += fields_len * 2;
   3811 
   3812         for (uint32_t i = 0; i < fields_len; i++) {
   3813             uint32_t field_init = fields[i];
   3814             // field name is 2 tokens before the field init's first token.
   3815             uint32_t name_token = firstToken(tree, field_init) - 2;
   3816             uint32_t str_index = identAsString(ag, name_token);
   3817             uint32_t init_ref = expr(gz, scope, field_init);
   3818             ag->extra[items_start + i * 2] = str_index;
   3819             ag->extra[items_start + i * 2 + 1] = init_ref;
   3820         }
   3821 
   3822         return addPlNodePayloadIndex(
   3823             gz, ZIR_INST_STRUCT_INIT_ANON, node, payload_index);
   3824     }
   3825 
   3826     // Typed init: evaluate type, emit struct_init_empty or struct_init.
   3827     if (type_expr_node != 0 && fields_len == 0) {
   3828         // Check for [_]T{} pattern (AstGen.zig:1707-1753).
   3829         AstNodeTag type_tag = tree->nodes.tags[type_expr_node];
   3830         if (type_tag == AST_NODE_ARRAY_TYPE
   3831             || type_tag == AST_NODE_ARRAY_TYPE_SENTINEL) {
   3832             AstData type_nd = tree->nodes.datas[type_expr_node];
   3833             uint32_t elem_count_node = type_nd.lhs;
   3834             if (tree->nodes.tags[elem_count_node] == AST_NODE_IDENTIFIER
   3835                 && isUnderscoreIdent(tree, elem_count_node)) {
   3836                 // Inferred length with 0 fields → length 0.
   3837                 if (type_tag == AST_NODE_ARRAY_TYPE) {
   3838                     uint32_t elem_type = typeExpr(gz, scope, type_nd.rhs);
   3839                     uint32_t array_type_inst
   3840                         = addPlNodeBin(gz, ZIR_INST_ARRAY_TYPE, type_expr_node,
   3841                             ZIR_REF_ZERO_USIZE, elem_type);
   3842                     return rvalue(gz, rl,
   3843                         addUnNode(gz, ZIR_INST_STRUCT_INIT_EMPTY,
   3844                             array_type_inst, node),
   3845                         node);
   3846                 }
   3847                 // ARRAY_TYPE_SENTINEL: extra[rhs] = sentinel, extra[rhs+1]
   3848                 // = elem_type
   3849                 uint32_t sentinel_node = tree->extra_data.arr[type_nd.rhs];
   3850                 uint32_t elem_type_node
   3851                     = tree->extra_data.arr[type_nd.rhs + 1];
   3852                 uint32_t elem_type = typeExpr(gz, scope, elem_type_node);
   3853                 ResultLoc sent_rl = { .tag = RL_COERCED_TY,
   3854                     .data = elem_type,
   3855                     .src_node = 0,
   3856                     .ctx = RI_CTX_NONE };
   3857                 uint32_t sentinel = comptimeExpr(gz, scope, sent_rl,
   3858                     sentinel_node, COMPTIME_REASON_ARRAY_SENTINEL);
   3859                 uint32_t array_type_inst = addPlNodeTriple(gz,
   3860                     ZIR_INST_ARRAY_TYPE_SENTINEL, type_expr_node,
   3861                     ZIR_REF_ZERO_USIZE, elem_type, sentinel);
   3862                 return rvalue(gz, rl,
   3863                     addUnNode(
   3864                         gz, ZIR_INST_STRUCT_INIT_EMPTY, array_type_inst, node),
   3865                     node);
   3866             }
   3867         }
   3868         uint32_t ty_inst = typeExpr(gz, scope, type_expr_node);
   3869         return rvalue(gz, rl,
   3870             addUnNode(gz, ZIR_INST_STRUCT_INIT_EMPTY, ty_inst, node), node);
   3871     }
   3872 
   3873     // Typed struct init with fields (AstGen.zig:1808-1818).
   3874     if (type_expr_node != 0 && fields_len > 0) {
   3875         uint32_t ty_inst = typeExpr(gz, scope, type_expr_node);
   3876         addUnNode(gz, ZIR_INST_VALIDATE_STRUCT_INIT_TY, ty_inst, node);
   3877 
   3878         // structInitExprTyped (AstGen.zig:1896-1931).
   3879         // StructInit payload: abs_node, abs_line, fields_len.
   3880         ensureExtraCapacity(ag, 3 + fields_len * 2);
   3881         uint32_t payload_index = ag->extra_len;
   3882         ag->extra[ag->extra_len++] = node; // abs_node
   3883         ag->extra[ag->extra_len++] = ag->source_line; // abs_line
   3884         ag->extra[ag->extra_len++] = fields_len;
   3885         // Reserve space for field items (field_type + init each).
   3886         uint32_t items_start = ag->extra_len;
   3887         ag->extra_len += fields_len * 2;
   3888 
   3889         for (uint32_t i = 0; i < fields_len; i++) {
   3890             uint32_t field_init = fields[i];
   3891             uint32_t name_token = firstToken(tree, field_init) - 2;
   3892             uint32_t str_index = identAsString(ag, name_token);
   3893             // struct_init_field_type (AstGen.zig:1918-1921).
   3894             uint32_t field_ty_inst
   3895                 = addPlNodeBin(gz, ZIR_INST_STRUCT_INIT_FIELD_TYPE, field_init,
   3896                     ty_inst, str_index);
   3897             // Evaluate init with coerced_ty (AstGen.zig:1924).
   3898             ResultLoc elem_rl = { .tag = RL_COERCED_TY,
   3899                 .data = field_ty_inst,
   3900                 .src_node = 0,
   3901                 .ctx = rl.ctx };
   3902             uint32_t init_ref = exprRl(gz, scope, elem_rl, field_init);
   3903             ag->extra[items_start + i * 2]
   3904                 = field_ty_inst - ZIR_REF_START_INDEX; // .toIndex()
   3905             ag->extra[items_start + i * 2 + 1] = init_ref;
   3906         }
   3907 
   3908         bool is_ref = (RL_IS_REF(rl));
   3909         ZirInstTag init_tag
   3910             = is_ref ? ZIR_INST_STRUCT_INIT_REF : ZIR_INST_STRUCT_INIT;
   3911         return addPlNodePayloadIndex(gz, init_tag, node, payload_index);
   3912     }
   3913 
   3914     SET_ERROR(ag);
   3915     return ZIR_REF_VOID_VALUE;
   3916 }
   3917 
   3918 // --- tryExpr (AstGen.zig:5957) ---
   3919 static uint32_t tryExpr(GenZir* gz, Scope* scope, uint32_t node) {
   3920     AstGenCtx* ag = gz->astgen;
   3921     AstData nd = ag->tree->nodes.datas[node];
   3922     uint32_t operand_node = nd.lhs;
   3923 
   3924     if (!gz->is_comptime) {
   3925         emitDbgNode(gz, node);
   3926     }
   3927     uint32_t try_lc_line = ag->source_line - gz->decl_line;
   3928     uint32_t try_lc_column = ag->source_column;
   3929 
   3930     // Evaluate operand (AstGen.zig:5993-6001).
   3931     ResultLoc operand_rl = RL_NONE_VAL;
   3932     operand_rl.ctx = RI_CTX_ERROR_HANDLING_EXPR;
   3933     uint32_t operand = exprRl(gz, scope, operand_rl, operand_node);
   3934 
   3935     // Create try block instruction (AstGen.zig:6007).
   3936     uint32_t try_inst = makeBlockInst(ag, ZIR_INST_TRY, gz, node);
   3937     gzAppendInstruction(gz, try_inst);
   3938 
   3939     // Else scope: extract error code, return it (AstGen.zig:6012-6025).
   3940     GenZir else_scope = makeSubBlock(gz, scope);
   3941 
   3942     uint32_t err_code
   3943         = addUnNode(&else_scope, ZIR_INST_ERR_UNION_CODE, operand, node);
   3944 
   3945     // Emit defers for error path (AstGen.zig:6019).
   3946     if (ag->fn_block != NULL) {
   3947         const Scope* fn_block_scope = &((GenZir*)ag->fn_block)->base;
   3948         genDefers(&else_scope, fn_block_scope, scope, DEFER_BOTH_SANS_ERR);
   3949     }
   3950 
   3951     // Emit dbg_stmt at try keyword for error return tracing (AstGen.zig:6020).
   3952     emitDbgStmt(&else_scope, try_lc_line, try_lc_column);
   3953 
   3954     // ret_node with error code (AstGen.zig:6021).
   3955     addUnNode(&else_scope, ZIR_INST_RET_NODE, err_code, node);
   3956 
   3957     setTryBody(ag, &else_scope, try_inst, operand);
   3958     // else_scope unstacked by setTryBody.
   3959 
   3960     return try_inst + ZIR_REF_START_INDEX; // toRef()
   3961 }
   3962 
   3963 // --- boolBinOp (AstGen.zig:6274) ---
   3964 // Short-circuiting boolean and/or.
   3965 
   3966 static uint32_t boolBinOp(
   3967     GenZir* gz, Scope* scope, uint32_t node, ZirInstTag zir_tag) {
   3968     AstGenCtx* ag = gz->astgen;
   3969     AstData nd = ag->tree->nodes.datas[node];
   3970     uint32_t lhs_node = nd.lhs;
   3971     uint32_t rhs_node = nd.rhs;
   3972 
   3973     // Evaluate LHS (AstGen.zig:6285).
   3974     uint32_t lhs = expr(gz, scope, lhs_node);
   3975 
   3976     // Reserve the bool_br instruction (payload set later)
   3977     // (AstGen.zig:6286).
   3978     uint32_t bool_br = reserveInstructionIndex(ag);
   3979     gzAppendInstruction(gz, bool_br);
   3980 
   3981     // Evaluate RHS in sub-block (AstGen.zig:6288-6293).
   3982     GenZir rhs_scope = makeSubBlock(gz, scope);
   3983     uint32_t rhs = expr(&rhs_scope, &rhs_scope.base, rhs_node);
   3984 
   3985     if (!ag->has_compile_errors) {
   3986         // break_inline from rhs to bool_br (AstGen.zig:6292).
   3987         makeBreakInline(&rhs_scope, bool_br, rhs,
   3988             (int32_t)rhs_node - (int32_t)rhs_scope.decl_node_index);
   3989     }
   3990 
   3991     // setBoolBrBody (AstGen.zig:6294, 11929-11944).
   3992     uint32_t raw_body_len = gzInstructionsLen(&rhs_scope);
   3993     const uint32_t* body = gzInstructionsSlice(&rhs_scope);
   3994     uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len);
   3995     ensureExtraCapacity(ag, 2 + body_len);
   3996     uint32_t payload_index = ag->extra_len;
   3997     ag->extra[ag->extra_len++] = lhs; // BoolBr.lhs
   3998     ag->extra[ag->extra_len++] = body_len; // BoolBr.body_len
   3999     for (uint32_t i = 0; i < raw_body_len; i++)
   4000         appendPossiblyRefdBodyInst(ag, body[i]);
   4001     gzUnstack(&rhs_scope);
   4002 
   4003     // Fill in the bool_br instruction.
   4004     ag->inst_tags[bool_br] = zir_tag;
   4005     ag->inst_datas[bool_br].pl_node.src_node
   4006         = (int32_t)node - (int32_t)gz->decl_node_index;
   4007     ag->inst_datas[bool_br].pl_node.payload_index = payload_index;
   4008 
   4009     return bool_br + ZIR_REF_START_INDEX;
   4010 }
   4011 
   4012 // Mirrors expr (AstGen.zig:634) — main expression dispatcher.
   4013 static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   4014     AstGenCtx* ag = gz->astgen;
   4015     if (node == 0) {
   4016         SET_ERROR(ag);
   4017         return ZIR_REF_VOID_VALUE;
   4018     }
   4019     AstNodeTag tag = ag->tree->nodes.tags[node];
   4020     AstData nd = ag->tree->nodes.datas[node];
   4021 
   4022     switch (tag) {
   4023     case AST_NODE_NUMBER_LITERAL:
   4024         return rvalue(gz, rl, numberLiteral(gz, node), node);
   4025     case AST_NODE_BUILTIN_CALL_TWO:
   4026     case AST_NODE_BUILTIN_CALL_TWO_COMMA:
   4027         return rvalue(gz, rl, builtinCall(gz, scope, rl, node), node);
   4028     case AST_NODE_FIELD_ACCESS:
   4029         return fieldAccessExpr(gz, scope, rl, node);
   4030     case AST_NODE_IDENTIFIER:
   4031         return identifierExpr(gz, scope, rl, node);
   4032     case AST_NODE_STRING_LITERAL: {
   4033         // Mirrors stringLiteral (AstGen.zig:8626).
   4034         uint32_t str_lit_token = ag->tree->nodes.main_tokens[node];
   4035         uint32_t str_index, str_len;
   4036         strLitAsString(ag, str_lit_token, &str_index, &str_len);
   4037         ZirInstData data;
   4038         data.str.start = str_index;
   4039         data.str.len = str_len;
   4040         uint32_t str_result = addInstruction(gz, ZIR_INST_STR, data);
   4041         return rvalue(gz, rl, str_result, node);
   4042     }
   4043     // address_of (AstGen.zig:953-960): evaluate operand with .ref rl.
   4044     case AST_NODE_ADDRESS_OF: {
   4045         uint32_t operand_node = ag->tree->nodes.datas[node].lhs;
   4046         // Check for result type to emit validate_ref_ty (AstGen.zig:954-956).
   4047         uint32_t res_ty = rlResultType(gz, rl, node);
   4048         ResultLoc operand_rl;
   4049         if (res_ty != 0) {
   4050             addUnTok(gz, ZIR_INST_VALIDATE_REF_TY, res_ty,
   4051                 firstToken(ag->tree, node));
   4052             // Pass ref_coerced_ty so init expressions can use the type
   4053             // (AstGen.zig:958).
   4054             operand_rl = (ResultLoc) {
   4055                 .tag = RL_REF_COERCED_TY, .data = res_ty, .src_node = 0
   4056             };
   4057         } else {
   4058             operand_rl = RL_REF_VAL;
   4059         }
   4060         uint32_t result = exprRl(gz, scope, operand_rl, operand_node);
   4061         return rvalue(gz, rl, result, node);
   4062     }
   4063     // ptr_type (AstGen.zig:1077-1081).
   4064     case AST_NODE_PTR_TYPE_ALIGNED:
   4065     case AST_NODE_PTR_TYPE_SENTINEL:
   4066     case AST_NODE_PTR_TYPE:
   4067     case AST_NODE_PTR_TYPE_BIT_RANGE:
   4068         return rvalue(gz, rl, ptrTypeExpr(gz, scope, node), node);
   4069     // array_type (AstGen.zig:940).
   4070     case AST_NODE_ARRAY_TYPE:
   4071         return rvalue(gz, rl, arrayTypeExpr(gz, scope, node), node);
   4072     // array_init variants (AstGen.zig:836-856).
   4073     case AST_NODE_ARRAY_INIT:
   4074     case AST_NODE_ARRAY_INIT_COMMA:
   4075     case AST_NODE_ARRAY_INIT_ONE:
   4076     case AST_NODE_ARRAY_INIT_ONE_COMMA:
   4077         return arrayInitExpr(gz, scope, rl, node);
   4078     // array_cat (AstGen.zig:772): ++ binary operator.
   4079     case AST_NODE_ARRAY_CAT:
   4080         return rvalue(
   4081             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_ARRAY_CAT), node);
   4082     // grouped_expression (AstGen.zig:1100): passthrough.
   4083     case AST_NODE_GROUPED_EXPRESSION:
   4084         return exprRl(gz, scope, rl, ag->tree->nodes.datas[node].lhs);
   4085     // unreachable_literal (AstGen.zig:846-854).
   4086     case AST_NODE_UNREACHABLE_LITERAL: {
   4087         emitDbgNode(gz, node);
   4088         ZirInstData udata;
   4089         memset(&udata, 0, sizeof(udata));
   4090         udata.unreachable_data.src_node
   4091             = (int32_t)node - (int32_t)gz->decl_node_index;
   4092         addInstruction(gz, ZIR_INST_UNREACHABLE, udata);
   4093         return ZIR_REF_UNREACHABLE_VALUE;
   4094     }
   4095     // enum_literal (AstGen.zig:993).
   4096     case AST_NODE_ENUM_LITERAL: {
   4097         uint32_t ident_token = ag->tree->nodes.main_tokens[node];
   4098         uint32_t str_index = identAsString(ag, ident_token);
   4099         // If result type available, emit decl_literal (AstGen.zig:993-1003).
   4100         uint32_t res_ty = rlResultType(gz, rl, node);
   4101         if (res_ty != 0) {
   4102             uint32_t res = addPlNodeBin(
   4103                 gz, ZIR_INST_DECL_LITERAL, node, res_ty, str_index);
   4104             // decl_literal does the coercion for us (AstGen.zig:1001).
   4105             // Only need rvalue for ptr/inferred_ptr/ref_coerced_ty.
   4106             if (rl.tag == RL_TY || rl.tag == RL_COERCED_TY)
   4107                 return res;
   4108             return rvalue(gz, rl, res, node);
   4109         }
   4110         return rvalue(gz, rl,
   4111             addStrTok(gz, ZIR_INST_ENUM_LITERAL, str_index, ident_token),
   4112             node);
   4113     }
   4114     // multiline_string_literal (AstGen.zig:8645).
   4115     case AST_NODE_MULTILINE_STRING_LITERAL:
   4116         return rvalue(gz, rl, multilineStringLiteral(gz, scope, node), node);
   4117     // return (AstGen.zig:856).
   4118     case AST_NODE_RETURN:
   4119         return retExpr(gz, scope, node);
   4120     // call (AstGen.zig:783-790).
   4121     case AST_NODE_CALL_ONE:
   4122     case AST_NODE_CALL_ONE_COMMA:
   4123     case AST_NODE_CALL:
   4124     case AST_NODE_CALL_COMMA:
   4125         return rvalue(gz, rl, callExpr(gz, scope, rl, node), node);
   4126     // struct_init (AstGen.zig:836-839).
   4127     case AST_NODE_STRUCT_INIT_DOT_TWO:
   4128     case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA:
   4129     case AST_NODE_STRUCT_INIT_DOT:
   4130     case AST_NODE_STRUCT_INIT_DOT_COMMA:
   4131     case AST_NODE_STRUCT_INIT_ONE:
   4132     case AST_NODE_STRUCT_INIT_ONE_COMMA:
   4133     case AST_NODE_STRUCT_INIT:
   4134     case AST_NODE_STRUCT_INIT_COMMA:
   4135         return structInitExpr(gz, scope, rl, node);
   4136     // container_decl (AstGen.zig:1083-1098).
   4137     case AST_NODE_CONTAINER_DECL:
   4138     case AST_NODE_CONTAINER_DECL_TRAILING:
   4139     case AST_NODE_CONTAINER_DECL_TWO:
   4140     case AST_NODE_CONTAINER_DECL_TWO_TRAILING:
   4141     case AST_NODE_CONTAINER_DECL_ARG:
   4142     case AST_NODE_CONTAINER_DECL_ARG_TRAILING:
   4143     case AST_NODE_TAGGED_UNION:
   4144     case AST_NODE_TAGGED_UNION_TRAILING:
   4145     case AST_NODE_TAGGED_UNION_TWO:
   4146     case AST_NODE_TAGGED_UNION_TWO_TRAILING:
   4147     case AST_NODE_TAGGED_UNION_ENUM_TAG:
   4148     case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING:
   4149         return rvalue(gz, rl, containerDecl(gz, scope, node), node);
   4150     // try (AstGen.zig:831).
   4151     case AST_NODE_TRY:
   4152         return rvalue(gz, rl, tryExpr(gz, scope, node), node);
   4153     // Comparison operators (AstGen.zig:714-726).
   4154     case AST_NODE_EQUAL_EQUAL:
   4155         return rvalue(
   4156             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_EQ), node);
   4157     case AST_NODE_BANG_EQUAL:
   4158         return rvalue(
   4159             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_NEQ), node);
   4160     case AST_NODE_LESS_THAN:
   4161         return rvalue(
   4162             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_LT), node);
   4163     case AST_NODE_GREATER_THAN:
   4164         return rvalue(
   4165             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_GT), node);
   4166     case AST_NODE_LESS_OR_EQUAL:
   4167         return rvalue(
   4168             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_LTE), node);
   4169     case AST_NODE_GREATER_OR_EQUAL:
   4170         return rvalue(
   4171             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_GTE), node);
   4172     // Arithmetic (AstGen.zig:656-698).
   4173     case AST_NODE_ADD:
   4174         return rvalue(
   4175             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_ADD), node);
   4176     case AST_NODE_SUB:
   4177         return rvalue(
   4178             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_SUB), node);
   4179     case AST_NODE_MUL:
   4180         return rvalue(
   4181             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_MUL), node);
   4182     case AST_NODE_DIV:
   4183         return rvalue(
   4184             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_DIV), node);
   4185     case AST_NODE_MOD:
   4186         return rvalue(
   4187             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_MOD), node);
   4188     // Bitwise (AstGen.zig:700-712).
   4189     case AST_NODE_BIT_AND:
   4190         return rvalue(
   4191             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_BIT_AND), node);
   4192     case AST_NODE_BIT_OR:
   4193         return rvalue(
   4194             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_BIT_OR), node);
   4195     case AST_NODE_BIT_XOR:
   4196         return rvalue(
   4197             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_XOR), node);
   4198     case AST_NODE_SHL:
   4199         return rvalue(gz, rl, shiftOp(gz, scope, node, ZIR_INST_SHL), node);
   4200     case AST_NODE_SHR:
   4201         return rvalue(gz, rl, shiftOp(gz, scope, node, ZIR_INST_SHR), node);
   4202     // Boolean operators (AstGen.zig:728-731) — special: boolBinOp.
   4203     case AST_NODE_BOOL_AND:
   4204         return rvalue(
   4205             gz, rl, boolBinOp(gz, scope, node, ZIR_INST_BOOL_BR_AND), node);
   4206     case AST_NODE_BOOL_OR:
   4207         return rvalue(
   4208             gz, rl, boolBinOp(gz, scope, node, ZIR_INST_BOOL_BR_OR), node);
   4209     // Unary operators (AstGen.zig:919-938).
   4210     case AST_NODE_BOOL_NOT:
   4211         return rvalue(gz, rl,
   4212             addUnNode(gz, ZIR_INST_BOOL_NOT, expr(gz, scope, nd.lhs), node),
   4213             node);
   4214     case AST_NODE_BIT_NOT:
   4215         return rvalue(gz, rl,
   4216             addUnNode(gz, ZIR_INST_BIT_NOT, expr(gz, scope, nd.lhs), node),
   4217             node);
   4218     case AST_NODE_NEGATION:
   4219         return rvalue(gz, rl,
   4220             addUnNode(gz, ZIR_INST_NEGATE, expr(gz, scope, nd.lhs), node),
   4221             node);
   4222     case AST_NODE_NEGATION_WRAP:
   4223         return rvalue(gz, rl,
   4224             addUnNode(gz, ZIR_INST_NEGATE_WRAP, expr(gz, scope, nd.lhs), node),
   4225             node);
   4226     // deref (AstGen.zig:942-951).
   4227     case AST_NODE_DEREF: {
   4228         uint32_t lhs = expr(gz, scope, nd.lhs);
   4229         addUnNode(gz, ZIR_INST_VALIDATE_DEREF, lhs, node);
   4230         if (RL_IS_REF(rl))
   4231             return lhs;
   4232         return rvalue(gz, rl, addUnNode(gz, ZIR_INST_LOAD, lhs, node), node);
   4233     }
   4234     // optional_type (AstGen.zig:961-964).
   4235     case AST_NODE_OPTIONAL_TYPE:
   4236         return rvalue(gz, rl,
   4237             addUnNode(
   4238                 gz, ZIR_INST_OPTIONAL_TYPE, typeExpr(gz, scope, nd.lhs), node),
   4239             node);
   4240     // unwrap_optional (AstGen.zig:966-985).
   4241     case AST_NODE_UNWRAP_OPTIONAL: {
   4242         uint32_t lhs = expr(gz, scope, nd.lhs);
   4243         advanceSourceCursorToMainToken(ag, gz, node);
   4244         uint32_t saved_line = ag->source_line - gz->decl_line;
   4245         uint32_t saved_col = ag->source_column;
   4246         emitDbgStmt(gz, saved_line, saved_col);
   4247         return rvalue(gz, rl,
   4248             addUnNode(gz, ZIR_INST_OPTIONAL_PAYLOAD_SAFE, lhs, node), node);
   4249     }
   4250     // error_union type (AstGen.zig:788-797).
   4251     case AST_NODE_ERROR_UNION: {
   4252         uint32_t lhs = typeExpr(gz, scope, nd.lhs);
   4253         uint32_t rhs = typeExpr(gz, scope, nd.rhs);
   4254         return rvalue(gz, rl,
   4255             addPlNodeBin(gz, ZIR_INST_ERROR_UNION_TYPE, node, lhs, rhs), node);
   4256     }
   4257     // char_literal (AstGen.zig:8662-8675).
   4258     case AST_NODE_CHAR_LITERAL: {
   4259         uint32_t main_tok = ag->tree->nodes.main_tokens[node];
   4260         uint32_t tok_start = ag->tree->tokens.starts[main_tok];
   4261         const char* src = ag->tree->source;
   4262         uint32_t ci = tok_start + 1; // skip opening quote
   4263         uint64_t char_val;
   4264         if (src[ci] == '\\') {
   4265             // Escape sequence (AstGen.zig:8668-8675).
   4266             ci++;
   4267             switch (src[ci]) {
   4268             case 'n':
   4269                 char_val = '\n';
   4270                 break;
   4271             case 'r':
   4272                 char_val = '\r';
   4273                 break;
   4274             case 't':
   4275                 char_val = '\t';
   4276                 break;
   4277             case '\\':
   4278                 char_val = '\\';
   4279                 break;
   4280             case '\'':
   4281                 char_val = '\'';
   4282                 break;
   4283             case '"':
   4284                 char_val = '"';
   4285                 break;
   4286             case 'x': {
   4287                 // \xNN hex escape.
   4288                 uint8_t val = 0;
   4289                 for (int k = 0; k < 2; k++) {
   4290                     ci++;
   4291                     char c = src[ci];
   4292                     if (c >= '0' && c <= '9')
   4293                         val = (uint8_t)(val * 16 + (uint8_t)(c - '0'));
   4294                     else if (c >= 'a' && c <= 'f')
   4295                         val = (uint8_t)(val * 16 + 10 + (uint8_t)(c - 'a'));
   4296                     else if (c >= 'A' && c <= 'F')
   4297                         val = (uint8_t)(val * 16 + 10 + (uint8_t)(c - 'A'));
   4298                 }
   4299                 char_val = val;
   4300                 break;
   4301             }
   4302             case 'u': {
   4303                 // \u{NNNNNN} unicode escape (string_literal.zig:194-231).
   4304                 // Skip past '{'.
   4305                 ci++;
   4306                 uint32_t codepoint = 0;
   4307                 while (true) {
   4308                     ci++;
   4309                     char c = src[ci];
   4310                     if (c >= '0' && c <= '9')
   4311                         codepoint = codepoint * 16 + (uint32_t)(c - '0');
   4312                     else if (c >= 'a' && c <= 'f')
   4313                         codepoint = codepoint * 16 + 10 + (uint32_t)(c - 'a');
   4314                     else if (c >= 'A' && c <= 'F')
   4315                         codepoint = codepoint * 16 + 10 + (uint32_t)(c - 'A');
   4316                     else
   4317                         break; // Must be '}'.
   4318                 }
   4319                 char_val = codepoint;
   4320                 break;
   4321             }
   4322             default:
   4323                 char_val = (uint8_t)src[ci];
   4324                 break;
   4325             }
   4326         } else {
   4327             char_val = (uint64_t)(uint8_t)src[ci];
   4328         }
   4329         return rvalue(gz, rl, addInt(gz, char_val), node);
   4330     }
   4331     // arrayAccess (AstGen.zig:6192-6221).
   4332     case AST_NODE_ARRAY_ACCESS: {
   4333         if (RL_IS_REF(rl)) {
   4334             uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs);
   4335             advanceSourceCursorToMainToken(ag, gz, node);
   4336             uint32_t saved_line = ag->source_line - gz->decl_line;
   4337             uint32_t saved_col = ag->source_column;
   4338             uint32_t rhs = expr(gz, scope, nd.rhs);
   4339             emitDbgStmt(gz, saved_line, saved_col);
   4340             return addPlNodeBin(gz, ZIR_INST_ELEM_PTR_NODE, node, lhs, rhs);
   4341         }
   4342         uint32_t lhs = expr(gz, scope, nd.lhs);
   4343         advanceSourceCursorToMainToken(ag, gz, node);
   4344         uint32_t saved_line = ag->source_line - gz->decl_line;
   4345         uint32_t saved_col = ag->source_column;
   4346         uint32_t rhs = expr(gz, scope, nd.rhs);
   4347         emitDbgStmt(gz, saved_line, saved_col);
   4348         return rvalue(gz, rl,
   4349             addPlNodeBin(gz, ZIR_INST_ELEM_VAL_NODE, node, lhs, rhs), node);
   4350     }
   4351     // slice (AstGen.zig:882-939).
   4352     case AST_NODE_SLICE_OPEN: {
   4353         // (AstGen.zig:908-937).
   4354         uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs);
   4355         advanceSourceCursorToMainToken(ag, gz, node);
   4356         uint32_t saved_line = ag->source_line - gz->decl_line;
   4357         uint32_t saved_col = ag->source_column;
   4358         ResultLoc usize_rl = { .tag = RL_COERCED_TY,
   4359             .data = ZIR_REF_USIZE_TYPE,
   4360             .src_node = 0,
   4361             .ctx = RI_CTX_NONE };
   4362         uint32_t start = exprRl(gz, scope, usize_rl, nd.rhs);
   4363         emitDbgStmt(gz, saved_line, saved_col);
   4364         return rvalue(gz, rl,
   4365             addPlNodeBin(gz, ZIR_INST_SLICE_START, node, lhs, start), node);
   4366     }
   4367     case AST_NODE_SLICE: {
   4368         // Slice[rhs]: { start, end } (AstGen.zig:908-937).
   4369         const Ast* stree = ag->tree;
   4370         uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs);
   4371         advanceSourceCursorToMainToken(ag, gz, node);
   4372         uint32_t saved_line = ag->source_line - gz->decl_line;
   4373         uint32_t saved_col = ag->source_column;
   4374         uint32_t start_node = stree->extra_data.arr[nd.rhs];
   4375         uint32_t end_node = stree->extra_data.arr[nd.rhs + 1];
   4376         ResultLoc usize_rl = { .tag = RL_COERCED_TY,
   4377             .data = ZIR_REF_USIZE_TYPE,
   4378             .src_node = 0,
   4379             .ctx = RI_CTX_NONE };
   4380         uint32_t start_ref = exprRl(gz, scope, usize_rl, start_node);
   4381         uint32_t end_ref = exprRl(gz, scope, usize_rl, end_node);
   4382         emitDbgStmt(gz, saved_line, saved_col);
   4383         ensureExtraCapacity(ag, 3);
   4384         uint32_t payload_index = ag->extra_len;
   4385         ag->extra[ag->extra_len++] = lhs;
   4386         ag->extra[ag->extra_len++] = start_ref;
   4387         ag->extra[ag->extra_len++] = end_ref;
   4388         ZirInstData data;
   4389         data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   4390         data.pl_node.payload_index = payload_index;
   4391         return rvalue(
   4392             gz, rl, addInstruction(gz, ZIR_INST_SLICE_END, data), node);
   4393     }
   4394     case AST_NODE_SLICE_SENTINEL: {
   4395         // SliceSentinel[rhs]: { start, end, sentinel }
   4396         // (AstGen.zig:908-925).
   4397         const Ast* stree = ag->tree;
   4398         uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs);
   4399         advanceSourceCursorToMainToken(ag, gz, node);
   4400         uint32_t saved_line = ag->source_line - gz->decl_line;
   4401         uint32_t saved_col = ag->source_column;
   4402         uint32_t start_node = stree->extra_data.arr[nd.rhs];
   4403         uint32_t end_node = stree->extra_data.arr[nd.rhs + 1];
   4404         uint32_t sentinel_node = stree->extra_data.arr[nd.rhs + 2];
   4405         // start/end coerced to usize (AstGen.zig:911-912).
   4406         ResultLoc usize_rl = { .tag = RL_COERCED_TY,
   4407             .data = ZIR_REF_USIZE_TYPE,
   4408             .src_node = 0,
   4409             .ctx = RI_CTX_NONE };
   4410         uint32_t start_ref = exprRl(gz, scope, usize_rl, start_node);
   4411         uint32_t end_ref = (end_node != 0)
   4412             ? exprRl(gz, scope, usize_rl, end_node)
   4413             : ZIR_REF_NONE;
   4414         // sentinel: create slice_sentinel_ty and coerce (AstGen.zig:913-916).
   4415         uint32_t sentinel_ty
   4416             = addUnNode(gz, ZIR_INST_SLICE_SENTINEL_TY, lhs, node);
   4417         ResultLoc sent_rl = { .tag = RL_COERCED_TY,
   4418             .data = sentinel_ty,
   4419             .src_node = 0,
   4420             .ctx = RI_CTX_NONE };
   4421         uint32_t sentinel_ref = exprRl(gz, scope, sent_rl, sentinel_node);
   4422         emitDbgStmt(gz, saved_line, saved_col);
   4423         ensureExtraCapacity(ag, 4);
   4424         uint32_t payload_index = ag->extra_len;
   4425         ag->extra[ag->extra_len++] = lhs;
   4426         ag->extra[ag->extra_len++] = start_ref;
   4427         ag->extra[ag->extra_len++] = end_ref;
   4428         ag->extra[ag->extra_len++] = sentinel_ref;
   4429         ZirInstData data;
   4430         data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   4431         data.pl_node.payload_index = payload_index;
   4432         return rvalue(
   4433             gz, rl, addInstruction(gz, ZIR_INST_SLICE_SENTINEL, data), node);
   4434     }
   4435     // orelse (AstGen.zig:6031-6142).
   4436     case AST_NODE_ORELSE:
   4437         return orelseCatchExpr(gz, scope, rl, node, false);
   4438     // catch (AstGen.zig:6031-6142).
   4439     case AST_NODE_CATCH:
   4440         return orelseCatchExpr(gz, scope, rl, node, true);
   4441     // Block expressions (AstGen.zig:984-992).
   4442     case AST_NODE_BLOCK_TWO:
   4443     case AST_NODE_BLOCK_TWO_SEMICOLON:
   4444     case AST_NODE_BLOCK:
   4445     case AST_NODE_BLOCK_SEMICOLON:
   4446         return rvalue(gz, rl, blockExprExpr(gz, scope, rl, node), node);
   4447     // Anonymous array init (AstGen.zig:1119-1127).
   4448     case AST_NODE_ARRAY_INIT_DOT_TWO:
   4449     case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA:
   4450     case AST_NODE_ARRAY_INIT_DOT:
   4451     case AST_NODE_ARRAY_INIT_DOT_COMMA:
   4452         return arrayInitDotExpr(gz, scope, rl, node);
   4453     // if (AstGen.zig:1013-1024).
   4454     case AST_NODE_IF_SIMPLE:
   4455     case AST_NODE_IF:
   4456         return ifExpr(gz, scope, rlBr(rl), node);
   4457     // for (AstGen.zig:1043-1060).
   4458     case AST_NODE_FOR_SIMPLE:
   4459     case AST_NODE_FOR:
   4460         return rvalue(gz, rl, forExpr(gz, scope, node, false), node);
   4461     // Merge error sets (AstGen.zig:788-797).
   4462     case AST_NODE_MERGE_ERROR_SETS: {
   4463         uint32_t lhs = typeExpr(gz, scope, nd.lhs);
   4464         uint32_t rhs = typeExpr(gz, scope, nd.rhs);
   4465         return rvalue(gz, rl,
   4466             addPlNodeBin(gz, ZIR_INST_MERGE_ERROR_SETS, node, lhs, rhs), node);
   4467     }
   4468     // Wrapping arithmetic.
   4469     case AST_NODE_ADD_WRAP:
   4470         return rvalue(
   4471             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_ADDWRAP), node);
   4472     case AST_NODE_SUB_WRAP:
   4473         return rvalue(
   4474             gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_SUBWRAP), node);
   4475     // break (AstGen.zig:2150-2237).
   4476     case AST_NODE_BREAK: {
   4477         uint32_t opt_break_label = nd.lhs; // UINT32_MAX = none
   4478         uint32_t opt_rhs = nd.rhs; // 0 = none
   4479 
   4480         // Walk scope chain to find target block (AstGen.zig:2157-2187).
   4481         for (Scope* s = scope; s != NULL;) {
   4482             if (s->tag == SCOPE_GEN_ZIR) {
   4483                 GenZir* block_gz = (GenZir*)s;
   4484                 uint32_t block_inst = UINT32_MAX;
   4485                 if (opt_break_label != UINT32_MAX) {
   4486                     // Labeled break: check label on GenZir.
   4487                     // Use direct source text comparison, not identAsString,
   4488                     // to avoid adding label names to string_bytes
   4489                     // (AstGen.zig:2176 uses tokenIdentEql).
   4490                     if (block_gz->label_token != UINT32_MAX
   4491                         && tokenIdentEql(ag->tree, opt_break_label,
   4492                             block_gz->label_token)) {
   4493                         block_inst = block_gz->label_block_inst;
   4494                     }
   4495                 } else {
   4496                     // Unlabeled break: check break_block.
   4497                     if (block_gz->break_block != UINT32_MAX)
   4498                         block_inst = block_gz->break_block;
   4499                 }
   4500                 if (block_inst != UINT32_MAX) {
   4501                     // Found target (AstGen.zig:2188-2228).
   4502                     ZirInstTag break_tag = block_gz->is_inline
   4503                         ? ZIR_INST_BREAK_INLINE
   4504                         : ZIR_INST_BREAK;
   4505                     if (opt_rhs == 0) {
   4506                         // Void break (AstGen.zig:2195-2206).
   4507                         rvalue(gz, block_gz->break_result_info,
   4508                             ZIR_REF_VOID_VALUE, node);
   4509                         genDefers(gz, s, scope, DEFER_NORMAL_ONLY);
   4510                         if (!block_gz->is_comptime) {
   4511                             ZirInstData rdata;
   4512                             rdata.un_node.operand
   4513                                 = block_inst + ZIR_REF_START_INDEX;
   4514                             rdata.un_node.src_node
   4515                                 = (int32_t)node - (int32_t)gz->decl_node_index;
   4516                             addInstruction(gz,
   4517                                 ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL,
   4518                                 rdata);
   4519                         }
   4520                         addBreak(gz, break_tag, block_inst, ZIR_REF_VOID_VALUE,
   4521                             AST_NODE_OFFSET_NONE);
   4522                     } else {
   4523                         // Value break (AstGen.zig:2208-2228).
   4524                         uint32_t operand = exprRl(
   4525                             gz, scope, block_gz->break_result_info, opt_rhs);
   4526                         genDefers(gz, s, scope, DEFER_NORMAL_ONLY);
   4527                         if (!block_gz->is_comptime)
   4528                             restoreErrRetIndex(gz, block_inst,
   4529                                 block_gz->break_result_info, opt_rhs, operand);
   4530                         switch (block_gz->break_result_info.tag) {
   4531                         case RL_PTR:
   4532                         case RL_DISCARD:
   4533                             addBreak(gz, break_tag, block_inst,
   4534                                 ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE);
   4535                             break;
   4536                         default:
   4537                             addBreak(gz, break_tag, block_inst, operand,
   4538                                 (int32_t)opt_rhs
   4539                                     - (int32_t)gz->decl_node_index);
   4540                             break;
   4541                         }
   4542                     }
   4543                     return ZIR_REF_UNREACHABLE_VALUE;
   4544                 }
   4545                 s = block_gz->parent;
   4546             } else if (s->tag == SCOPE_LOCAL_VAL) {
   4547                 s = ((ScopeLocalVal*)s)->parent;
   4548             } else if (s->tag == SCOPE_LOCAL_PTR) {
   4549                 s = ((ScopeLocalPtr*)s)->parent;
   4550             } else if (s->tag == SCOPE_DEFER_NORMAL
   4551                 || s->tag == SCOPE_DEFER_ERROR) {
   4552                 s = ((ScopeDefer*)s)->parent;
   4553             } else if (s->tag == SCOPE_LABEL) {
   4554                 s = ((ScopeLabel*)s)->parent;
   4555             } else {
   4556                 break;
   4557             }
   4558         }
   4559         SET_ERROR(ag);
   4560         return ZIR_REF_UNREACHABLE_VALUE;
   4561     }
   4562     // continue (AstGen.zig:2246-2340).
   4563     case AST_NODE_CONTINUE: {
   4564         // Walk scope chain to find GenZir with continue_block.
   4565         for (Scope* s = scope; s != NULL;) {
   4566             if (s->tag == SCOPE_GEN_ZIR) {
   4567                 GenZir* gz2 = (GenZir*)s;
   4568                 if (gz2->continue_block != UINT32_MAX) {
   4569                     genDefers(gz, s, scope, DEFER_NORMAL_ONLY);
   4570                     ZirInstTag break_tag = gz2->is_inline
   4571                         ? ZIR_INST_BREAK_INLINE
   4572                         : ZIR_INST_BREAK;
   4573                     if (break_tag == ZIR_INST_BREAK_INLINE) {
   4574                         // AstGen.zig:2328-2330.
   4575                         addUnNode(gz, ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW,
   4576                             gz2->continue_block + ZIR_REF_START_INDEX, node);
   4577                     }
   4578                     // Restore error return index (AstGen.zig:2333-2334).
   4579                     if (!gz2->is_comptime) {
   4580                         ZirInstData rdata;
   4581                         rdata.un_node.operand
   4582                             = gz2->continue_block + ZIR_REF_START_INDEX;
   4583                         rdata.un_node.src_node
   4584                             = (int32_t)node - (int32_t)gz->decl_node_index;
   4585                         addInstruction(gz,
   4586                             ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL,
   4587                             rdata);
   4588                     }
   4589                     addBreak(gz, break_tag, gz2->continue_block,
   4590                         ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE);
   4591                     return ZIR_REF_UNREACHABLE_VALUE;
   4592                 }
   4593                 s = gz2->parent;
   4594             } else if (s->tag == SCOPE_LOCAL_VAL) {
   4595                 s = ((ScopeLocalVal*)s)->parent;
   4596             } else if (s->tag == SCOPE_LOCAL_PTR) {
   4597                 s = ((ScopeLocalPtr*)s)->parent;
   4598             } else if (s->tag == SCOPE_DEFER_NORMAL
   4599                 || s->tag == SCOPE_DEFER_ERROR) {
   4600                 s = ((ScopeDefer*)s)->parent;
   4601             } else if (s->tag == SCOPE_LABEL) {
   4602                 s = ((ScopeLabel*)s)->parent;
   4603             } else {
   4604                 break;
   4605             }
   4606         }
   4607         SET_ERROR(ag);
   4608         return ZIR_REF_UNREACHABLE_VALUE;
   4609     }
   4610     // comptime (AstGen.zig:1104-1105).
   4611     case AST_NODE_COMPTIME: {
   4612         // comptimeExprAst / comptimeExpr2 (AstGen.zig:2104, 1982).
   4613         uint32_t body_node = nd.lhs;
   4614 
   4615         // If already comptime, just pass through (AstGen.zig:1990-1992).
   4616         if (gz->is_comptime)
   4617             return exprRl(gz, scope, rl, body_node);
   4618 
   4619         // Create comptime block (AstGen.zig:2078-2098).
   4620         uint32_t block_inst
   4621             = makeBlockInst(ag, ZIR_INST_BLOCK_COMPTIME, gz, node);
   4622         GenZir block_scope = makeSubBlock(gz, scope);
   4623         block_scope.is_comptime = true;
   4624 
   4625         // Transform RL to type-only (AstGen.zig:2084-2090).
   4626         // Runtime-to-comptime boundary: can't pass runtime pointers.
   4627         ResultLoc ty_only_rl;
   4628         uint32_t res_ty = rlResultType(gz, rl, node);
   4629         if (res_ty != 0)
   4630             ty_only_rl = (ResultLoc) { .tag = RL_COERCED_TY,
   4631                 .data = res_ty,
   4632                 .src_node = 0,
   4633                 .ctx = rl.ctx };
   4634         else
   4635             ty_only_rl = (ResultLoc) {
   4636                 .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = rl.ctx
   4637             };
   4638 
   4639         uint32_t result = exprRl(&block_scope, scope, ty_only_rl, body_node);
   4640         addBreak(&block_scope, ZIR_INST_BREAK_INLINE, block_inst, result,
   4641             AST_NODE_OFFSET_NONE);
   4642         setBlockComptimeBody(
   4643             ag, &block_scope, block_inst, COMPTIME_REASON_COMPTIME_KEYWORD);
   4644         gzAppendInstruction(gz, block_inst);
   4645 
   4646         // Apply rvalue to handle RL_PTR etc (AstGen.zig:2098).
   4647         return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node);
   4648     }
   4649     // switch (AstGen.zig:1072-1078).
   4650     case AST_NODE_SWITCH:
   4651     case AST_NODE_SWITCH_COMMA:
   4652         return switchExpr(gz, scope, rlBr(rl), node);
   4653     // while (AstGen.zig:1037-1042).
   4654     case AST_NODE_WHILE_SIMPLE:
   4655     case AST_NODE_WHILE_CONT:
   4656     case AST_NODE_WHILE:
   4657         return rvalue(gz, rl, whileExpr(gz, scope, node, false), node);
   4658     // error_value (AstGen.zig:1005-1010).
   4659     case AST_NODE_ERROR_VALUE: {
   4660         uint32_t error_token = nd.rhs;
   4661         uint32_t str = identAsString(ag, error_token);
   4662         return rvalue(gz, rl,
   4663             addStrTok(gz, ZIR_INST_ERROR_VALUE, str, error_token), node);
   4664     }
   4665     // error_set_decl (AstGen.zig:5905-5955).
   4666     case AST_NODE_ERROR_SET_DECL: {
   4667         AstData esd = ag->tree->nodes.datas[node];
   4668         uint32_t lbrace = esd.lhs;
   4669         uint32_t rbrace = esd.rhs;
   4670         // Reserve 1 extra word for ErrorSetDecl.fields_len.
   4671         ensureExtraCapacity(ag, 1 + (rbrace - lbrace));
   4672         uint32_t payload_index = ag->extra_len;
   4673         ag->extra_len++; // placeholder for fields_len
   4674         uint32_t fields_len = 0;
   4675         for (uint32_t tok = lbrace + 1; tok < rbrace; tok++) {
   4676             TokenizerTag ttag = ag->tree->tokens.tags[tok];
   4677             if (ttag == TOKEN_DOC_COMMENT || ttag == TOKEN_COMMA)
   4678                 continue;
   4679             if (ttag == TOKEN_IDENTIFIER) {
   4680                 uint32_t str_index = identAsString(ag, tok);
   4681                 ensureExtraCapacity(ag, 1);
   4682                 ag->extra[ag->extra_len++] = str_index;
   4683                 fields_len++;
   4684             }
   4685         }
   4686         ag->extra[payload_index] = fields_len;
   4687         return rvalue(gz, rl,
   4688             addPlNodePayloadIndex(
   4689                 gz, ZIR_INST_ERROR_SET_DECL, node, payload_index),
   4690             node);
   4691     }
   4692     // assign in expr context (AstGen.zig:1011-1014).
   4693     case AST_NODE_ASSIGN:
   4694         assignStmt(gz, scope, node);
   4695         return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node);
   4696     // Compound assignment operators (AstGen.zig:685-744).
   4697     case AST_NODE_ASSIGN_ADD:
   4698         assignOp(gz, scope, node, ZIR_INST_ADD);
   4699         return ZIR_REF_VOID_VALUE;
   4700     case AST_NODE_ASSIGN_SUB:
   4701         assignOp(gz, scope, node, ZIR_INST_SUB);
   4702         return ZIR_REF_VOID_VALUE;
   4703     case AST_NODE_ASSIGN_MUL:
   4704         assignOp(gz, scope, node, ZIR_INST_MUL);
   4705         return ZIR_REF_VOID_VALUE;
   4706     case AST_NODE_ASSIGN_DIV:
   4707         assignOp(gz, scope, node, ZIR_INST_DIV);
   4708         return ZIR_REF_VOID_VALUE;
   4709     case AST_NODE_ASSIGN_MOD:
   4710         assignOp(gz, scope, node, ZIR_INST_MOD_REM);
   4711         return ZIR_REF_VOID_VALUE;
   4712     case AST_NODE_ASSIGN_BIT_AND:
   4713         assignOp(gz, scope, node, ZIR_INST_BIT_AND);
   4714         return ZIR_REF_VOID_VALUE;
   4715     case AST_NODE_ASSIGN_BIT_OR:
   4716         assignOp(gz, scope, node, ZIR_INST_BIT_OR);
   4717         return ZIR_REF_VOID_VALUE;
   4718     case AST_NODE_ASSIGN_BIT_XOR:
   4719         assignOp(gz, scope, node, ZIR_INST_XOR);
   4720         return ZIR_REF_VOID_VALUE;
   4721     case AST_NODE_ASSIGN_ADD_WRAP:
   4722         assignOp(gz, scope, node, ZIR_INST_ADDWRAP);
   4723         return ZIR_REF_VOID_VALUE;
   4724     case AST_NODE_ASSIGN_SUB_WRAP:
   4725         assignOp(gz, scope, node, ZIR_INST_SUBWRAP);
   4726         return ZIR_REF_VOID_VALUE;
   4727     case AST_NODE_ASSIGN_MUL_WRAP:
   4728         assignOp(gz, scope, node, ZIR_INST_MULWRAP);
   4729         return ZIR_REF_VOID_VALUE;
   4730     case AST_NODE_ASSIGN_ADD_SAT:
   4731         assignOp(gz, scope, node, ZIR_INST_ADD_SAT);
   4732         return ZIR_REF_VOID_VALUE;
   4733     case AST_NODE_ASSIGN_SUB_SAT:
   4734         assignOp(gz, scope, node, ZIR_INST_SUB_SAT);
   4735         return ZIR_REF_VOID_VALUE;
   4736     case AST_NODE_ASSIGN_MUL_SAT:
   4737         assignOp(gz, scope, node, ZIR_INST_MUL_SAT);
   4738         return ZIR_REF_VOID_VALUE;
   4739     default:
   4740         SET_ERROR(ag);
   4741         return ZIR_REF_VOID_VALUE;
   4742     }
   4743 }
   4744 
   4745 static uint32_t expr(GenZir* gz, Scope* scope, uint32_t node) {
   4746     return exprRl(gz, scope, RL_NONE_VAL, node);
   4747 }
   4748 
   4749 // --- blockExprExpr (AstGen.zig:2388-2536) ---
   4750 // Handles block expressions (labeled and unlabeled).
   4751 // Unlabeled blocks just execute statements and return void.
   4752 // Labeled blocks (blk: { ... break :blk val; }) need a block instruction.
   4753 
   4754 static uint32_t blockExprExpr(
   4755     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   4756     (void)rl;
   4757     AstGenCtx* ag = gz->astgen;
   4758     const Ast* tree = ag->tree;
   4759     AstNodeTag tag = tree->nodes.tags[node];
   4760     AstData nd = tree->nodes.datas[node];
   4761 
   4762     // Extract statements.
   4763     uint32_t stmt_buf[2];
   4764     const uint32_t* statements = NULL;
   4765     uint32_t stmt_count = 0;
   4766 
   4767     switch (tag) {
   4768     case AST_NODE_BLOCK_TWO:
   4769     case AST_NODE_BLOCK_TWO_SEMICOLON: {
   4770         uint32_t idx = 0;
   4771         if (nd.lhs != 0)
   4772             stmt_buf[idx++] = nd.lhs;
   4773         if (nd.rhs != 0)
   4774             stmt_buf[idx++] = nd.rhs;
   4775         statements = stmt_buf;
   4776         stmt_count = idx;
   4777         break;
   4778     }
   4779     case AST_NODE_BLOCK:
   4780     case AST_NODE_BLOCK_SEMICOLON: {
   4781         uint32_t start = nd.lhs;
   4782         uint32_t end = nd.rhs;
   4783         statements = tree->extra_data.arr + start;
   4784         stmt_count = end - start;
   4785         break;
   4786     }
   4787     default:
   4788         SET_ERROR(ag);
   4789         return ZIR_REF_VOID_VALUE;
   4790     }
   4791 
   4792     // Check if labeled (AstGen.zig:2397-2402).
   4793     // A labeled block has: identifier colon before the lbrace.
   4794     uint32_t lbrace = tree->nodes.main_tokens[node];
   4795     bool is_labeled
   4796         = (lbrace >= 2 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON
   4797             && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER);
   4798 
   4799     if (!is_labeled) {
   4800         if (!gz->is_comptime) {
   4801             // Non-comptime unlabeled block (AstGen.zig:2404-2425).
   4802             // Create block_inst FIRST, add to gz, then process body.
   4803             uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node);
   4804             gzAppendInstruction(gz, block_inst);
   4805 
   4806             GenZir block_scope = makeSubBlock(gz, scope);
   4807             blockExprStmts(
   4808                 &block_scope, &block_scope.base, statements, stmt_count);
   4809 
   4810             if (!endsWithNoReturn(&block_scope)) {
   4811                 // restore_err_ret_index on gz (AstGen.zig:2420).
   4812                 ZirInstData rdata;
   4813                 rdata.un_node.operand = block_inst + ZIR_REF_START_INDEX;
   4814                 rdata.un_node.src_node
   4815                     = (int32_t)node - (int32_t)gz->decl_node_index;
   4816                 addInstruction(
   4817                     gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata);
   4818                 // break on block_scope (AstGen.zig:2422).
   4819                 addBreak(&block_scope, ZIR_INST_BREAK, block_inst,
   4820                     ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE);
   4821             }
   4822             setBlockBody(ag, &block_scope, block_inst);
   4823         } else {
   4824             // Comptime unlabeled block: inline statements
   4825             // (AstGen.zig:2426-2429).
   4826             GenZir sub_gz = makeSubBlock(gz, scope);
   4827             blockExprStmts(&sub_gz, &sub_gz.base, statements, stmt_count);
   4828         }
   4829         return ZIR_REF_VOID_VALUE;
   4830     }
   4831 
   4832     // Labeled block (AstGen.zig:2466-2536).
   4833     // Note: upstream blockExpr always passes force_comptime=false.
   4834     uint32_t label_token = lbrace - 2;
   4835 
   4836     // Compute break result info (AstGen.zig:2484-2492).
   4837     bool need_rl = nodesNeedRlContains(ag, node);
   4838     ResultLoc break_ri = breakResultInfo(gz, rl, node, need_rl);
   4839     bool need_result_rvalue = (break_ri.tag != rl.tag);
   4840 
   4841     // Reserve the block instruction (AstGen.zig:2500-2501).
   4842     uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node);
   4843     gzAppendInstruction(gz, block_inst);
   4844 
   4845     GenZir block_scope = makeSubBlock(gz, scope);
   4846     // Set label on block_scope (AstGen.zig:2504-2508).
   4847     block_scope.label_token = label_token;
   4848     block_scope.label_block_inst = block_inst;
   4849     block_scope.break_result_info = break_ri;
   4850 
   4851     // Process statements (AstGen.zig:2512).
   4852     blockExprStmts(&block_scope, &block_scope.base, statements, stmt_count);
   4853 
   4854     if (!endsWithNoReturn(&block_scope)) {
   4855         // Emit restore_err_ret_index (AstGen.zig:2515).
   4856         ZirInstData rdata;
   4857         rdata.un_node.operand = block_inst + ZIR_REF_START_INDEX;
   4858         rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   4859         addInstruction(
   4860             gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata);
   4861         // rvalue + break (AstGen.zig:2516-2518).
   4862         uint32_t result = rvalue(
   4863             gz, block_scope.break_result_info, ZIR_REF_VOID_VALUE, node);
   4864         addBreak(&block_scope, ZIR_INST_BREAK, block_inst, result,
   4865             AST_NODE_OFFSET_NONE);
   4866     }
   4867 
   4868     setBlockBody(ag, &block_scope, block_inst);
   4869 
   4870     // AstGen.zig:2531-2534.
   4871     if (need_result_rvalue)
   4872         return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node);
   4873     return block_inst + ZIR_REF_START_INDEX;
   4874 }
   4875 
   4876 // --- arrayInitDotExpr (AstGen.zig:1576-1595) ---
   4877 // Handles anonymous array init: `.{a, b, c}`.
   4878 // Emits array_init_anon instruction with MultiOp payload.
   4879 
   4880 static uint32_t arrayInitDotExpr(
   4881     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   4882     AstGenCtx* ag = gz->astgen;
   4883     const Ast* tree = ag->tree;
   4884     AstNodeTag tag = tree->nodes.tags[node];
   4885     AstData nd = tree->nodes.datas[node];
   4886 
   4887     // Extract elements.
   4888     uint32_t elem_buf[2];
   4889     const uint32_t* elements = NULL;
   4890     uint32_t elem_count = 0;
   4891 
   4892     switch (tag) {
   4893     case AST_NODE_ARRAY_INIT_DOT_TWO:
   4894     case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: {
   4895         uint32_t idx = 0;
   4896         if (nd.lhs != 0)
   4897             elem_buf[idx++] = nd.lhs;
   4898         if (nd.rhs != 0)
   4899             elem_buf[idx++] = nd.rhs;
   4900         elements = elem_buf;
   4901         elem_count = idx;
   4902         break;
   4903     }
   4904     case AST_NODE_ARRAY_INIT_DOT:
   4905     case AST_NODE_ARRAY_INIT_DOT_COMMA: {
   4906         uint32_t start = nd.lhs;
   4907         uint32_t end = nd.rhs;
   4908         elements = tree->extra_data.arr + start;
   4909         elem_count = end - start;
   4910         break;
   4911     }
   4912     default:
   4913         SET_ERROR(ag);
   4914         return ZIR_REF_VOID_VALUE;
   4915     }
   4916 
   4917     // Dispatch based on RL (AstGen.zig:1515-1572).
   4918     switch (rl.tag) {
   4919     case RL_NONE: {
   4920         // arrayInitExprAnon (AstGen.zig:1576-1595).
   4921         ensureExtraCapacity(ag, 1 + elem_count);
   4922         uint32_t payload_index = ag->extra_len;
   4923         ag->extra[ag->extra_len++] = elem_count;
   4924         uint32_t extra_start = ag->extra_len;
   4925         ag->extra_len += elem_count;
   4926         for (uint32_t i = 0; i < elem_count; i++) {
   4927             uint32_t elem_ref = expr(gz, scope, elements[i]);
   4928             ag->extra[extra_start + i] = elem_ref;
   4929         }
   4930         return addPlNodePayloadIndex(
   4931             gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index);
   4932     }
   4933     case RL_TY:
   4934     case RL_COERCED_TY: {
   4935         // validate_array_init_result_ty + arrayInitExprTyped
   4936         // (AstGen.zig:1534-1539).
   4937         uint32_t result_ty = rl.data;
   4938         // Emit ArrayInit { ty, init_count } payload for
   4939         // validate_array_init_result_ty.
   4940         ensureExtraCapacity(ag, 2);
   4941         uint32_t val_payload = ag->extra_len;
   4942         ag->extra[ag->extra_len++] = result_ty;
   4943         ag->extra[ag->extra_len++] = elem_count;
   4944         addPlNodePayloadIndex(
   4945             gz, ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY, node, val_payload);
   4946 
   4947         // arrayInitExprTyped (AstGen.zig:1598-1642) with elem_ty=none.
   4948         uint32_t operands_len = elem_count + 1; // +1 for type
   4949         ensureExtraCapacity(ag, 1 + operands_len);
   4950         uint32_t payload_index = ag->extra_len;
   4951         ag->extra[ag->extra_len++] = operands_len;
   4952         ag->extra[ag->extra_len++] = result_ty;
   4953         uint32_t extra_start = ag->extra_len;
   4954         ag->extra_len += elem_count;
   4955         for (uint32_t i = 0; i < elem_count; i++) {
   4956             // array_init_elem_type uses bin data (AstGen.zig:1626-1632).
   4957             uint32_t elem_ty
   4958                 = addBin(gz, ZIR_INST_ARRAY_INIT_ELEM_TYPE, result_ty, i);
   4959             ResultLoc elem_rl
   4960                 = { .tag = RL_COERCED_TY, .data = elem_ty, .src_node = 0 };
   4961             uint32_t elem_ref = exprRl(gz, scope, elem_rl, elements[i]);
   4962             ag->extra[extra_start + i] = elem_ref;
   4963         }
   4964         return addPlNodePayloadIndex(
   4965             gz, ZIR_INST_ARRAY_INIT, node, payload_index);
   4966     }
   4967     case RL_INFERRED_PTR: {
   4968         // arrayInitExprAnon + rvalue (AstGen.zig:1545-1551).
   4969         ensureExtraCapacity(ag, 1 + elem_count);
   4970         uint32_t payload_index = ag->extra_len;
   4971         ag->extra[ag->extra_len++] = elem_count;
   4972         uint32_t extra_start = ag->extra_len;
   4973         ag->extra_len += elem_count;
   4974         for (uint32_t i = 0; i < elem_count; i++) {
   4975             uint32_t elem_ref = expr(gz, scope, elements[i]);
   4976             ag->extra[extra_start + i] = elem_ref;
   4977         }
   4978         uint32_t result = addPlNodePayloadIndex(
   4979             gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index);
   4980         return rvalue(gz, rl, result, node);
   4981     }
   4982     case RL_DISCARD: {
   4983         // Evaluate and discard each element (AstGen.zig:1517-1522).
   4984         for (uint32_t i = 0; i < elem_count; i++) {
   4985             exprRl(gz, scope, RL_DISCARD_VAL, elements[i]);
   4986         }
   4987         return ZIR_REF_VOID_VALUE;
   4988     }
   4989     case RL_REF: {
   4990         // arrayInitExprAnon + ref (AstGen.zig:1523-1526).
   4991         ensureExtraCapacity(ag, 1 + elem_count);
   4992         uint32_t payload_index = ag->extra_len;
   4993         ag->extra[ag->extra_len++] = elem_count;
   4994         uint32_t extra_start = ag->extra_len;
   4995         ag->extra_len += elem_count;
   4996         for (uint32_t i = 0; i < elem_count; i++) {
   4997             uint32_t elem_ref = expr(gz, scope, elements[i]);
   4998             ag->extra[extra_start + i] = elem_ref;
   4999         }
   5000         uint32_t result = addPlNodePayloadIndex(
   5001             gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index);
   5002         return rvalue(gz, rl, result, node);
   5003     }
   5004     case RL_REF_COERCED_TY: {
   5005         // validate_array_init_ref_ty + arrayInitExprTyped
   5006         // (AstGen.zig:1527-1532).
   5007         uint32_t ptr_ty_inst = rl.data;
   5008         ensureExtraCapacity(ag, 2);
   5009         uint32_t val_payload = ag->extra_len;
   5010         ag->extra[ag->extra_len++] = ptr_ty_inst;
   5011         ag->extra[ag->extra_len++] = elem_count;
   5012         uint32_t dest_arr_ty_inst = addPlNodePayloadIndex(
   5013             gz, ZIR_INST_VALIDATE_ARRAY_INIT_REF_TY, node, val_payload);
   5014 
   5015         // arrayInitExprTyped with elem_ty=none, is_ref=true.
   5016         uint32_t operands_len = elem_count + 1;
   5017         ensureExtraCapacity(ag, 1 + operands_len);
   5018         uint32_t ai_payload = ag->extra_len;
   5019         ag->extra[ag->extra_len++] = operands_len;
   5020         ag->extra[ag->extra_len++] = dest_arr_ty_inst;
   5021         uint32_t extra_start2 = ag->extra_len;
   5022         ag->extra_len += elem_count;
   5023         for (uint32_t i = 0; i < elem_count; i++) {
   5024             // array_init_elem_type uses bin data (AstGen.zig:1626-1632).
   5025             uint32_t elem_ty = addBin(
   5026                 gz, ZIR_INST_ARRAY_INIT_ELEM_TYPE, dest_arr_ty_inst, i);
   5027             ResultLoc elem_rl
   5028                 = { .tag = RL_COERCED_TY, .data = elem_ty, .src_node = 0 };
   5029             uint32_t elem_ref = exprRl(gz, scope, elem_rl, elements[i]);
   5030             ag->extra[extra_start2 + i] = elem_ref;
   5031         }
   5032         return addPlNodePayloadIndex(
   5033             gz, ZIR_INST_ARRAY_INIT_REF, node, ai_payload);
   5034     }
   5035     case RL_PTR: {
   5036         // arrayInitExprPtr (AstGen.zig:1541-1543, 1645-1672).
   5037         uint32_t array_ptr_inst
   5038             = addUnNode(gz, ZIR_INST_OPT_EU_BASE_PTR_INIT, rl.data, node);
   5039         // Block payload: body_len = elem_count.
   5040         ensureExtraCapacity(ag, 1 + elem_count);
   5041         uint32_t payload_index = ag->extra_len;
   5042         ag->extra[ag->extra_len++] = elem_count;
   5043         uint32_t items_start = ag->extra_len;
   5044         ag->extra_len += elem_count;
   5045 
   5046         for (uint32_t i = 0; i < elem_count; i++) {
   5047             // array_init_elem_ptr: ElemPtrImm{ptr, index}.
   5048             uint32_t elem_ptr_inst = addPlNodeBin(gz,
   5049                 ZIR_INST_ARRAY_INIT_ELEM_PTR, elements[i], array_ptr_inst, i);
   5050             ag->extra[items_start + i]
   5051                 = elem_ptr_inst - ZIR_REF_START_INDEX; // .toIndex()
   5052             // Evaluate element with ptr RL (AstGen.zig:1668).
   5053             ResultLoc ptr_rl = { .tag = RL_PTR,
   5054                 .data = elem_ptr_inst,
   5055                 .src_node = 0,
   5056                 .ctx = rl.ctx };
   5057             exprRl(gz, scope, ptr_rl, elements[i]);
   5058         }
   5059         addPlNodePayloadIndex(
   5060             gz, ZIR_INST_VALIDATE_PTR_ARRAY_INIT, node, payload_index);
   5061         return ZIR_REF_VOID_VALUE;
   5062     }
   5063     }
   5064 
   5065     // Fallback: anon init + rvalue.
   5066     ensureExtraCapacity(ag, 1 + elem_count);
   5067     uint32_t payload_index = ag->extra_len;
   5068     ag->extra[ag->extra_len++] = elem_count;
   5069     uint32_t extra_start = ag->extra_len;
   5070     ag->extra_len += elem_count;
   5071     for (uint32_t i = 0; i < elem_count; i++) {
   5072         uint32_t elem_ref = expr(gz, scope, elements[i]);
   5073         ag->extra[extra_start + i] = elem_ref;
   5074     }
   5075     uint32_t result = addPlNodePayloadIndex(
   5076         gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index);
   5077     return rvalue(gz, rl, result, node);
   5078 }
   5079 
   5080 // --- ifExpr (AstGen.zig:6300-6528) ---
   5081 // Handles if and if_simple expressions.
   5082 // Pattern: block_scope with condbr → then/else branches → setCondBrPayload.
   5083 
   5084 static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   5085     AstGenCtx* ag = gz->astgen;
   5086     const Ast* tree = ag->tree;
   5087     bool need_rl = nodesNeedRlContains(ag, node);
   5088     ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl);
   5089     AstNodeTag tag = tree->nodes.tags[node];
   5090     AstData nd = tree->nodes.datas[node];
   5091 
   5092     uint32_t cond_node = nd.lhs;
   5093     uint32_t then_node, else_node;
   5094 
   5095     if (tag == AST_NODE_IF_SIMPLE) {
   5096         then_node = nd.rhs;
   5097         else_node = 0;
   5098     } else {
   5099         // AST_NODE_IF: rhs is index into extra → If{then_expr, else_expr}
   5100         then_node = tree->extra_data.arr[nd.rhs];
   5101         else_node = tree->extra_data.arr[nd.rhs + 1];
   5102     }
   5103 
   5104     // Detect payload capture: if (cond) |x| (AstGen.zig Ast.fullIf).
   5105     // payload_pipe = lastToken(cond_expr) + 2; if pipe → payload_token + 1.
   5106     uint32_t payload_token = 0; // 0 = no payload
   5107     uint32_t last_cond_tok = lastToken(tree, cond_node);
   5108     uint32_t pipe_tok = last_cond_tok + 2;
   5109     if (pipe_tok < tree->tokens.len
   5110         && tree->tokens.tags[pipe_tok] == TOKEN_PIPE) {
   5111         payload_token = pipe_tok + 1; // identifier token
   5112     }
   5113 
   5114     // Detect error token: then_expr else |e| (AstGen.zig Ast.fullIf).
   5115     uint32_t error_token = 0;
   5116     if (else_node != 0) {
   5117         uint32_t else_tok = lastToken(tree, then_node) + 1; // "else" keyword
   5118         if (else_tok + 1 < tree->tokens.len
   5119             && tree->tokens.tags[else_tok + 1] == TOKEN_PIPE) {
   5120             error_token = else_tok + 2;
   5121         }
   5122     }
   5123 
   5124     // Create block_scope (AstGen.zig:6326-6328).
   5125     GenZir block_scope = makeSubBlock(gz, scope);
   5126 
   5127     // Emit DBG_STMT for condition (AstGen.zig:6335).
   5128     // NOTE: upstream emits into parent_gz AFTER block_scope is created,
   5129     // so the dbg_stmt ends up in block_scope's range (shared array).
   5130     emitDbgNode(gz, cond_node);
   5131 
   5132     // Evaluate condition (AstGen.zig:6335-6363).
   5133     uint32_t cond_inst; // the value (optional/err-union/bool)
   5134     uint32_t bool_bit; // the boolean for condbr
   5135     if (error_token != 0) {
   5136         // Error union condition: if (err_union) |val| else |err|.
   5137         // (AstGen.zig:6341).
   5138         ResultLoc cond_rl = RL_NONE_VAL;
   5139         cond_rl.ctx = RI_CTX_ERROR_HANDLING_EXPR;
   5140         cond_inst
   5141             = exprRl(&block_scope, &block_scope.base, cond_rl, cond_node);
   5142         bool_bit = addUnNode(
   5143             &block_scope, ZIR_INST_IS_NON_ERR, cond_inst, cond_node);
   5144     } else if (payload_token != 0) {
   5145         // Optional condition: if (optional) |val|.
   5146         cond_inst = expr(&block_scope, &block_scope.base, cond_node);
   5147         bool_bit = addUnNode(
   5148             &block_scope, ZIR_INST_IS_NON_NULL, cond_inst, cond_node);
   5149     } else {
   5150         // Bool condition (AstGen.zig:6356-6362).
   5151         cond_inst = expr(&block_scope, &block_scope.base, cond_node);
   5152         bool_bit = cond_inst;
   5153     }
   5154 
   5155     uint32_t condbr = addCondBr(&block_scope, ZIR_INST_CONDBR, node);
   5156     uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node);
   5157     setBlockBody(ag, &block_scope, block_inst);
   5158     gzAppendInstruction(gz, block_inst);
   5159 
   5160     // Then branch (AstGen.zig:6372-6441).
   5161     GenZir then_scope = makeSubBlock(gz, scope);
   5162     Scope* then_sub_scope = &then_scope.base;
   5163     ScopeLocalVal payload_val_scope;
   5164     memset(&payload_val_scope, 0, sizeof(payload_val_scope));
   5165 
   5166     if (error_token != 0 && payload_token != 0) {
   5167         // Error union with payload: unwrap payload (AstGen.zig:6379-6407).
   5168         uint32_t payload_inst = addUnNode(&then_scope,
   5169             ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE, cond_inst, then_node);
   5170         uint32_t ident_name = identAsString(ag, payload_token);
   5171         payload_val_scope = (ScopeLocalVal) {
   5172             .base = { .tag = SCOPE_LOCAL_VAL },
   5173             .parent = &then_scope.base,
   5174             .gen_zir = &then_scope,
   5175             .inst = payload_inst,
   5176             .token_src = payload_token,
   5177             .name = ident_name,
   5178         };
   5179         addDbgVar(&then_scope, ZIR_INST_DBG_VAR_VAL, ident_name, payload_inst);
   5180         then_sub_scope = &payload_val_scope.base;
   5181     } else if (payload_token != 0) {
   5182         // Optional with payload: unwrap optional (AstGen.zig:6408-6431).
   5183         uint32_t payload_inst = addUnNode(&then_scope,
   5184             ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE, cond_inst, then_node);
   5185         uint32_t ident_name = identAsString(ag, payload_token);
   5186         payload_val_scope = (ScopeLocalVal) {
   5187             .base = { .tag = SCOPE_LOCAL_VAL },
   5188             .parent = &then_scope.base,
   5189             .gen_zir = &then_scope,
   5190             .inst = payload_inst,
   5191             .token_src = payload_token,
   5192             .name = ident_name,
   5193         };
   5194         addDbgVar(&then_scope, ZIR_INST_DBG_VAR_VAL, ident_name, payload_inst);
   5195         then_sub_scope = &payload_val_scope.base;
   5196     }
   5197 
   5198     // Use fullBodyExpr for then body (AstGen.zig:6437).
   5199     uint32_t then_result
   5200         = fullBodyExpr(&then_scope, then_sub_scope, break_rl, then_node);
   5201     if (!endsWithNoReturn(&then_scope)) {
   5202         addBreak(&then_scope, ZIR_INST_BREAK, block_inst, then_result,
   5203             (int32_t)then_node - (int32_t)gz->decl_node_index);
   5204     }
   5205 
   5206     // Else branch (AstGen.zig:6443-6489).
   5207     GenZir else_scope = makeSubBlock(gz, scope);
   5208 
   5209     // save_err_ret_index (AstGen.zig:6448-6449).
   5210     bool do_err_trace = ag->fn_ret_ty != 0 && error_token != 0;
   5211     if (do_err_trace && nodeMayAppendToErrorTrace(tree, cond_node))
   5212         addSaveErrRetIndex(&else_scope, ZIR_REF_NONE);
   5213 
   5214     if (else_node != 0) {
   5215         Scope* else_sub_scope = &else_scope.base;
   5216         ScopeLocalVal error_val_scope;
   5217         memset(&error_val_scope, 0, sizeof(error_val_scope));
   5218 
   5219         if (error_token != 0) {
   5220             // Error capture: else |err| (AstGen.zig:6452-6475).
   5221             uint32_t err_inst = addUnNode(
   5222                 &else_scope, ZIR_INST_ERR_UNION_CODE, cond_inst, cond_node);
   5223             uint32_t err_name = identAsString(ag, error_token);
   5224             error_val_scope = (ScopeLocalVal) {
   5225                 .base = { .tag = SCOPE_LOCAL_VAL },
   5226                 .parent = &else_scope.base,
   5227                 .gen_zir = &else_scope,
   5228                 .inst = err_inst,
   5229                 .token_src = error_token,
   5230                 .name = err_name,
   5231             };
   5232             addDbgVar(&else_scope, ZIR_INST_DBG_VAR_VAL, err_name, err_inst);
   5233             else_sub_scope = &error_val_scope.base;
   5234         }
   5235 
   5236         // Use fullBodyExpr for else body (AstGen.zig:6478).
   5237         uint32_t else_result
   5238             = fullBodyExpr(&else_scope, else_sub_scope, break_rl, else_node);
   5239         if (!endsWithNoReturn(&else_scope)) {
   5240             // Restore error return index (AstGen.zig:6480-6482).
   5241             if (do_err_trace)
   5242                 restoreErrRetIndex(
   5243                     &else_scope, block_inst, break_rl, else_node, else_result);
   5244             addBreak(&else_scope, ZIR_INST_BREAK, block_inst, else_result,
   5245                 (int32_t)else_node - (int32_t)gz->decl_node_index);
   5246         }
   5247     } else {
   5248         addBreak(&else_scope, ZIR_INST_BREAK, block_inst, ZIR_REF_VOID_VALUE,
   5249             AST_NODE_OFFSET_NONE);
   5250     }
   5251 
   5252     // Wire up condbr (AstGen.zig:6491).
   5253     setCondBrPayload(ag, condbr, bool_bit, &then_scope, &else_scope);
   5254 
   5255     // AstGen.zig:6493-6497.
   5256     bool need_result_rvalue = (break_rl.tag != rl.tag);
   5257     if (need_result_rvalue)
   5258         return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node);
   5259     return block_inst + ZIR_REF_START_INDEX;
   5260 }
   5261 
   5262 // --- forExpr (AstGen.zig:6819-7125) ---
   5263 // Handles for_simple and for (multi-input).
   5264 // Supports both indexable and for_range inputs.
   5265 
   5266 #define FOR_MAX_INPUTS 16
   5267 
   5268 static uint32_t forExpr(
   5269     GenZir* gz, Scope* scope, uint32_t node, bool is_statement) {
   5270     AstGenCtx* ag = gz->astgen;
   5271     const Ast* tree = ag->tree;
   5272     AstData nd = tree->nodes.datas[node];
   5273     AstNodeTag node_tag = tree->nodes.tags[node];
   5274 
   5275     // Detect inline keyword (AstGen.zig:6847).
   5276     uint32_t main_token = tree->nodes.main_tokens[node];
   5277     bool is_inline = (main_token > 0
   5278         && tree->tokens.tags[main_token - 1] == TOKEN_KEYWORD_INLINE);
   5279 
   5280     // Extract input nodes and body/else nodes.
   5281     // FOR_SIMPLE: lhs = input node, rhs = body (Ast.zig:1960-1968).
   5282     // FOR: lhs = extra_data index, rhs = packed AstFor (Ast.zig:1970-1981).
   5283     uint32_t input_nodes[FOR_MAX_INPUTS];
   5284     uint32_t num_inputs;
   5285     uint32_t body_node;
   5286     if (node_tag == AST_NODE_FOR_SIMPLE) {
   5287         input_nodes[0] = nd.lhs;
   5288         num_inputs = 1;
   5289         body_node = nd.rhs;
   5290     } else {
   5291         uint32_t extra_idx = nd.lhs;
   5292         AstFor for_data;
   5293         memcpy(&for_data, &nd.rhs, sizeof(AstFor));
   5294         num_inputs = for_data.inputs;
   5295         if (num_inputs == 0 || num_inputs > FOR_MAX_INPUTS) {
   5296             SET_ERROR(ag);
   5297             return ZIR_REF_VOID_VALUE;
   5298         }
   5299         for (uint32_t i = 0; i < num_inputs; i++)
   5300             input_nodes[i] = tree->extra_data.arr[extra_idx + i];
   5301         body_node = tree->extra_data.arr[extra_idx + num_inputs];
   5302     }
   5303 
   5304     // Per-input arrays (AstGen.zig:6858-6862).
   5305     uint32_t indexables[FOR_MAX_INPUTS];
   5306     uint32_t lens[FOR_MAX_INPUTS][2]; // [ref0, ref1] per input
   5307 
   5308     // Allocate index counter (AstGen.zig:6865-6874).
   5309     ZirInstTag alloc_tag
   5310         = is_inline ? ZIR_INST_ALLOC_COMPTIME_MUT : ZIR_INST_ALLOC;
   5311     uint32_t index_ptr = addUnNode(gz, alloc_tag, ZIR_REF_USIZE_TYPE, node);
   5312     addPlNodeBin(gz, ZIR_INST_STORE_NODE, node, index_ptr, ZIR_REF_ZERO_USIZE);
   5313 
   5314     // Compute payload_token (AstGen.zig fullForComponents:2349-2350).
   5315     // payload_token = lastToken(inputs[last]) + 3 + has_comma
   5316     uint32_t last_cond_tok = lastToken(tree, input_nodes[num_inputs - 1]);
   5317     bool has_comma = (last_cond_tok + 1 < tree->tokens.len
   5318         && tree->tokens.tags[last_cond_tok + 1] == TOKEN_COMMA);
   5319     uint32_t payload_token = last_cond_tok + 3 + (has_comma ? 1 : 0);
   5320 
   5321     // Process each input (AstGen.zig:6878-6925).
   5322     uint32_t capture_token = payload_token;
   5323     for (uint32_t i = 0; i < num_inputs; i++) {
   5324         uint32_t input = input_nodes[i];
   5325         // Advance capture_token past this capture's ident (+comma).
   5326         bool capture_is_ref
   5327             = (tree->tokens.tags[capture_token] == TOKEN_ASTERISK);
   5328         uint32_t ident_tok = capture_token + (capture_is_ref ? 1u : 0u);
   5329         capture_token = ident_tok + 2; // skip ident + comma/pipe
   5330 
   5331         emitDbgNode(gz, input);
   5332 
   5333         if (tree->nodes.tags[input] == AST_NODE_FOR_RANGE) {
   5334             // Range input (AstGen.zig:6892-6916).
   5335             AstData range_nd = tree->nodes.datas[input];
   5336             uint32_t start_node = range_nd.lhs;
   5337             uint32_t end_node = range_nd.rhs;
   5338 
   5339             // AstGen.zig:6897-6902: expr with .rl = .{ .ty = .usize_type }
   5340             ResultLoc usize_rl
   5341                 = { .tag = RL_TY, .data = ZIR_REF_USIZE_TYPE, .src_node = 0 };
   5342             uint32_t start_val = exprRl(gz, scope, usize_rl, start_node);
   5343 
   5344             uint32_t end_val = ZIR_REF_NONE;
   5345             if (end_node != 0) {
   5346                 end_val = exprRl(gz, scope, usize_rl, end_node);
   5347             }
   5348 
   5349             if (end_val == ZIR_REF_NONE) {
   5350                 lens[i][0] = ZIR_REF_NONE;
   5351                 lens[i][1] = ZIR_REF_NONE;
   5352             } else {
   5353                 lens[i][0] = start_val;
   5354                 lens[i][1] = end_val;
   5355             }
   5356 
   5357             // Check if start is trivially zero.
   5358             bool start_is_zero = false;
   5359             if (tree->nodes.tags[start_node] == AST_NODE_NUMBER_LITERAL) {
   5360                 uint32_t tok = tree->nodes.main_tokens[start_node];
   5361                 uint32_t ts = tree->tokens.starts[tok];
   5362                 if (tree->source[ts] == '0'
   5363                     && (ts + 1 >= tree->source_len
   5364                         || tree->source[ts + 1] < '0'
   5365                         || tree->source[ts + 1] > '9'))
   5366                     start_is_zero = true;
   5367             }
   5368             indexables[i] = start_is_zero ? ZIR_REF_NONE : start_val;
   5369         } else {
   5370             // Regular indexable (AstGen.zig:6918-6923).
   5371             uint32_t indexable = expr(gz, scope, input);
   5372             indexables[i] = indexable;
   5373             lens[i][0] = indexable;
   5374             lens[i][1] = ZIR_REF_NONE;
   5375         }
   5376     }
   5377 
   5378     // Emit for_len as MultiOp (AstGen.zig:6933-6942).
   5379     uint32_t len;
   5380     {
   5381         uint32_t operands_len = num_inputs * 2;
   5382         ensureExtraCapacity(ag, 1 + operands_len);
   5383         uint32_t payload_index = ag->extra_len;
   5384         ag->extra[ag->extra_len++] = operands_len;
   5385         for (uint32_t i = 0; i < num_inputs; i++) {
   5386             ag->extra[ag->extra_len++] = lens[i][0];
   5387             ag->extra[ag->extra_len++] = lens[i][1];
   5388         }
   5389         ZirInstData data;
   5390         data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index;
   5391         data.pl_node.payload_index = payload_index;
   5392         len = addInstruction(gz, ZIR_INST_FOR_LEN, data);
   5393     }
   5394 
   5395     // Create loop (AstGen.zig:6944-6956).
   5396     ZirInstTag loop_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_LOOP;
   5397     uint32_t loop_inst = makeBlockInst(ag, loop_tag, gz, node);
   5398 
   5399     GenZir loop_scope = makeSubBlock(gz, scope);
   5400     loop_scope.is_inline = is_inline;
   5401 
   5402     // Load index (AstGen.zig:6955-6956).
   5403     // We need to finish loop_scope later once we have the deferred refs from
   5404     // then_scope. However, the load must be removed from instructions in the
   5405     // meantime or it appears to be part of parent_gz.
   5406     uint32_t index = addUnNode(&loop_scope, ZIR_INST_LOAD, index_ptr, node);
   5407     ag->scratch_inst_len--; // pop from loop_scope (AstGen.zig:6956)
   5408 
   5409     // Condition: added to cond_scope (AstGen.zig:6958-6962).
   5410     GenZir cond_scope = makeSubBlock(gz, &loop_scope.base);
   5411     uint32_t cond
   5412         = addPlNodeBin(&cond_scope, ZIR_INST_CMP_LT, node, index, len);
   5413 
   5414     // Create condbr + block (AstGen.zig:6967-6974).
   5415     ZirInstTag condbr_tag
   5416         = is_inline ? ZIR_INST_CONDBR_INLINE : ZIR_INST_CONDBR;
   5417     uint32_t condbr = addCondBr(&cond_scope, condbr_tag, node);
   5418     ZirInstTag block_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_BLOCK;
   5419     uint32_t cond_block = makeBlockInst(ag, block_tag, &loop_scope, node);
   5420     setBlockBody(ag, &cond_scope, cond_block);
   5421 
   5422     loop_scope.break_block = loop_inst;
   5423     loop_scope.continue_block = cond_block; // AstGen.zig:6974
   5424 
   5425     // Then branch: loop body (AstGen.zig:6982-7065).
   5426     GenZir then_scope = makeSubBlock(gz, &cond_scope.base);
   5427 
   5428     // Set up capture scopes for all inputs (AstGen.zig:6986-7045).
   5429     ScopeLocalVal capture_scopes[FOR_MAX_INPUTS];
   5430     Scope* body_scope_parent = &then_scope.base;
   5431     {
   5432         capture_token = payload_token;
   5433         for (uint32_t i = 0; i < num_inputs; i++) {
   5434             uint32_t input = input_nodes[i];
   5435             bool capture_is_ref
   5436                 = (tree->tokens.tags[capture_token] == TOKEN_ASTERISK);
   5437             uint32_t ident_tok = capture_token + (capture_is_ref ? 1u : 0u);
   5438             capture_token = ident_tok + 2;
   5439 
   5440             // Check if discard (AstGen.zig:6999).
   5441             uint32_t ts = tree->tokens.starts[ident_tok];
   5442             bool is_discard = (tree->source[ts] == '_'
   5443                 && (ts + 1 >= tree->source_len
   5444                     || !((tree->source[ts + 1] >= 'a'
   5445                              && tree->source[ts + 1] <= 'z')
   5446                         || (tree->source[ts + 1] >= 'A'
   5447                             && tree->source[ts + 1] <= 'Z')
   5448                         || tree->source[ts + 1] == '_'
   5449                         || (tree->source[ts + 1] >= '0'
   5450                             && tree->source[ts + 1] <= '9'))));
   5451             if (is_discard)
   5452                 continue;
   5453 
   5454             // Compute capture inst (AstGen.zig:7004-7028).
   5455             uint32_t capture_inst;
   5456             bool is_counter = (tree->nodes.tags[input] == AST_NODE_FOR_RANGE);
   5457 
   5458             if (indexables[i] == ZIR_REF_NONE) {
   5459                 // Start=0 counter: use index directly.
   5460                 capture_inst = index;
   5461             } else if (is_counter) {
   5462                 // Counter with nonzero start: add.
   5463                 capture_inst = addPlNodeBin(
   5464                     &then_scope, ZIR_INST_ADD, input, indexables[i], index);
   5465             } else if (capture_is_ref) {
   5466                 // Indexable by ref: elem_ptr.
   5467                 capture_inst = addPlNodeBin(&then_scope, ZIR_INST_ELEM_PTR,
   5468                     input, indexables[i], index);
   5469             } else {
   5470                 // Indexable by val: elem_val.
   5471                 capture_inst = addPlNodeBin(&then_scope, ZIR_INST_ELEM_VAL,
   5472                     input, indexables[i], index);
   5473             }
   5474 
   5475             uint32_t name_str = identAsString(ag, ident_tok);
   5476             capture_scopes[i] = (ScopeLocalVal) {
   5477                 .base = { .tag = SCOPE_LOCAL_VAL },
   5478                 .parent = body_scope_parent,
   5479                 .gen_zir = &then_scope,
   5480                 .inst = capture_inst,
   5481                 .token_src = ident_tok,
   5482                 .name = name_str,
   5483             };
   5484             // AstGen.zig:7040.
   5485             addDbgVar(
   5486                 &then_scope, ZIR_INST_DBG_VAR_VAL, name_str, capture_inst);
   5487             body_scope_parent = &capture_scopes[i].base;
   5488         }
   5489     }
   5490 
   5491     // Execute body (AstGen.zig:7047-7048).
   5492     uint32_t then_result
   5493         = fullBodyExpr(&then_scope, body_scope_parent, RL_NONE_VAL, body_node);
   5494     addEnsureResult(&then_scope, then_result, body_node);
   5495 
   5496     // dbg_stmt + dbg_empty_stmt (AstGen.zig:7052-7061).
   5497     advanceSourceCursor(ag, tree->tokens.starts[lastToken(tree, body_node)]);
   5498     emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column);
   5499     {
   5500         ZirInstData ext_data;
   5501         ext_data.extended.opcode = (uint16_t)ZIR_EXT_DBG_EMPTY_STMT;
   5502         ext_data.extended.small = 0;
   5503         ext_data.extended.operand = 0;
   5504         addInstruction(gz, ZIR_INST_EXTENDED, ext_data);
   5505     }
   5506 
   5507     ZirInstTag break_tag = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK;
   5508     addBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE,
   5509         AST_NODE_OFFSET_NONE);
   5510 
   5511     // Else branch: break out of loop (AstGen.zig:7066-7091).
   5512     GenZir else_scope = makeSubBlock(gz, &cond_scope.base);
   5513     addBreak(&else_scope, break_tag, loop_inst, ZIR_REF_VOID_VALUE,
   5514         AST_NODE_OFFSET_NONE);
   5515 
   5516     setCondBrPayload(ag, condbr, cond, &then_scope, &else_scope);
   5517 
   5518     // then_scope and else_scope unstacked now. Resurrect loop_scope to
   5519     // finally finish it (AstGen.zig:7095-7113).
   5520     {
   5521         // Reset loop_scope instructions and re-add index + cond_block.
   5522         loop_scope.instructions_top = ag->scratch_inst_len;
   5523         gzAppendInstruction(&loop_scope, index - ZIR_REF_START_INDEX);
   5524         gzAppendInstruction(&loop_scope, cond_block);
   5525 
   5526         // Increment the index variable (AstGen.zig:7100-7108).
   5527         uint32_t index_plus_one = addPlNodeBin(
   5528             &loop_scope, ZIR_INST_ADD_UNSAFE, node, index, ZIR_REF_ONE_USIZE);
   5529         addPlNodeBin(
   5530             &loop_scope, ZIR_INST_STORE_NODE, node, index_ptr, index_plus_one);
   5531 
   5532         // Repeat (AstGen.zig:7110-7111).
   5533         ZirInstTag repeat_tag
   5534             = is_inline ? ZIR_INST_REPEAT_INLINE : ZIR_INST_REPEAT;
   5535         ZirInstData repeat_data;
   5536         memset(&repeat_data, 0, sizeof(repeat_data));
   5537         repeat_data.node = (int32_t)node - (int32_t)loop_scope.decl_node_index;
   5538         addInstruction(&loop_scope, repeat_tag, repeat_data);
   5539 
   5540         setBlockBody(ag, &loop_scope, loop_inst);
   5541     }
   5542     gzAppendInstruction(gz, loop_inst);
   5543 
   5544     uint32_t result = loop_inst + ZIR_REF_START_INDEX;
   5545 
   5546     // Emit ensure_result_used when used as statement (AstGen.zig:7121-7123).
   5547     if (is_statement) {
   5548         addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, result, node);
   5549     }
   5550 
   5551     return result;
   5552 }
   5553 
   5554 // --- orelseCatchExpr (AstGen.zig:6031-6142) ---
   5555 // Handles `lhs orelse rhs` and `lhs catch rhs`.
   5556 
   5557 static uint32_t orelseCatchExpr(
   5558     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_catch) {
   5559     AstGenCtx* ag = gz->astgen;
   5560     const Ast* tree = ag->tree;
   5561     AstData nd = tree->nodes.datas[node];
   5562 
   5563     bool do_err_trace = is_catch && ag->fn_ret_ty != 0;
   5564 
   5565     // breakResultInfo (AstGen.zig:6046-6058).
   5566     bool need_rl = nodesNeedRlContains(ag, node);
   5567     ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl);
   5568     bool need_result_rvalue = (break_rl.tag != rl.tag);
   5569 
   5570     // Create block_scope (AstGen.zig:6062-6063).
   5571     GenZir block_scope = makeSubBlock(gz, scope);
   5572 
   5573     // Evaluate operand in block_scope (AstGen.zig:6066-6074).
   5574     ResultLoc operand_rl = RL_NONE_VAL;
   5575     if (do_err_trace) {
   5576         operand_rl.ctx = RI_CTX_ERROR_HANDLING_EXPR;
   5577     }
   5578     uint32_t operand
   5579         = exprRl(&block_scope, &block_scope.base, operand_rl, nd.lhs);
   5580 
   5581     // Check condition in block_scope (AstGen.zig:6075).
   5582     ZirInstTag test_tag
   5583         = is_catch ? ZIR_INST_IS_NON_ERR : ZIR_INST_IS_NON_NULL;
   5584     uint32_t condition = addUnNode(&block_scope, test_tag, operand, node);
   5585 
   5586     // condbr in block_scope (AstGen.zig:6076).
   5587     uint32_t condbr = addCondBr(&block_scope, ZIR_INST_CONDBR, node);
   5588 
   5589     // Create block in parent gz (AstGen.zig:6078-6081).
   5590     uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node);
   5591     setBlockBody(ag, &block_scope, block_inst);
   5592     // block_scope unstacked now.
   5593     gzAppendInstruction(gz, block_inst);
   5594 
   5595     // Then branch: unwrap payload (AstGen.zig:6083-6092).
   5596     GenZir then_scope = makeSubBlock(&block_scope, scope);
   5597     ZirInstTag unwrap_tag = is_catch ? ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE
   5598                                      : ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE;
   5599     uint32_t unwrapped = addUnNode(&then_scope, unwrap_tag, operand, node);
   5600     // Apply rvalue coercion unless rl is ref/ref_coerced_ty
   5601     // (AstGen.zig:6088-6091).
   5602     uint32_t then_result = (rl.tag == RL_REF || rl.tag == RL_REF_COERCED_TY)
   5603         ? unwrapped
   5604         : rvalue(&then_scope, break_rl, unwrapped, node);
   5605     addBreak(&then_scope, ZIR_INST_BREAK, block_inst, then_result,
   5606         (int32_t)node - (int32_t)gz->decl_node_index);
   5607 
   5608     // Else branch: evaluate RHS (AstGen.zig:6094-6131).
   5609     GenZir else_scope = makeSubBlock(&block_scope, scope);
   5610 
   5611     // save_err_ret_index (AstGen.zig:6099-6100).
   5612     if (do_err_trace && nodeMayAppendToErrorTrace(tree, nd.lhs))
   5613         addSaveErrRetIndex(&else_scope, ZIR_REF_NONE);
   5614 
   5615     // Use fullBodyExpr (not expr) to inline unlabeled blocks
   5616     // (AstGen.zig:6125).
   5617     uint32_t else_result
   5618         = fullBodyExpr(&else_scope, &else_scope.base, break_rl, nd.rhs);
   5619     if (!endsWithNoReturn(&else_scope)) {
   5620         // restoreErrRetIndex (AstGen.zig:6128-6129).
   5621         if (do_err_trace)
   5622             restoreErrRetIndex(
   5623                 &else_scope, block_inst, break_rl, nd.rhs, else_result);
   5624         addBreak(&else_scope, ZIR_INST_BREAK, block_inst, else_result,
   5625             (int32_t)nd.rhs - (int32_t)gz->decl_node_index);
   5626     }
   5627 
   5628     setCondBrPayload(ag, condbr, condition, &then_scope, &else_scope);
   5629 
   5630     // AstGen.zig:6137-6141.
   5631     if (need_result_rvalue)
   5632         return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node);
   5633     return block_inst + ZIR_REF_START_INDEX;
   5634 }
   5635 
   5636 // --- whileExpr (AstGen.zig:6529-6805) ---
   5637 // Handles while_simple.
   5638 // Structure: loop { cond_block { cond, condbr }, repeat }
   5639 // condbr → then { continue_block { body, break continue }, break cond }
   5640 //        → else { break loop }
   5641 
   5642 static uint32_t whileExpr(
   5643     GenZir* gz, Scope* scope, uint32_t node, bool is_statement) {
   5644     AstGenCtx* ag = gz->astgen;
   5645     const Ast* tree = ag->tree;
   5646     AstData nd = tree->nodes.datas[node];
   5647 
   5648     // Detect inline keyword (AstGen.zig:6558).
   5649     uint32_t main_token = tree->nodes.main_tokens[node];
   5650     bool is_inline = (main_token > 0
   5651         && tree->tokens.tags[main_token - 1] == TOKEN_KEYWORD_INLINE);
   5652 
   5653     // WHILE_SIMPLE: lhs = cond_expr, rhs = body.
   5654     uint32_t cond_node = nd.lhs;
   5655     uint32_t body_node = nd.rhs;
   5656 
   5657     // Create loop instruction (AstGen.zig:6562-6564).
   5658     ZirInstTag loop_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_LOOP;
   5659     uint32_t loop_inst = makeBlockInst(ag, loop_tag, gz, node);
   5660     gzAppendInstruction(gz, loop_inst);
   5661 
   5662     GenZir loop_scope = makeSubBlock(gz, scope);
   5663     loop_scope.is_inline = is_inline;
   5664 
   5665     // Evaluate condition in cond_scope (AstGen.zig:6571-6607).
   5666     GenZir cond_scope = makeSubBlock(&loop_scope, &loop_scope.base);
   5667     // Emit debug node for the condition expression (AstGen.zig:6579).
   5668     emitDbgNode(&cond_scope, cond_node);
   5669     uint32_t cond = expr(&cond_scope, &cond_scope.base, cond_node);
   5670 
   5671     // Create condbr + cond_block (AstGen.zig:6609-6615).
   5672     ZirInstTag condbr_tag
   5673         = is_inline ? ZIR_INST_CONDBR_INLINE : ZIR_INST_CONDBR;
   5674     uint32_t condbr = addCondBr(&cond_scope, condbr_tag, node);
   5675     ZirInstTag block_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_BLOCK;
   5676     uint32_t cond_block = makeBlockInst(ag, block_tag, &loop_scope, node);
   5677     setBlockBody(ag, &cond_scope, cond_block); // unstacks cond_scope
   5678     gzAppendInstruction(&loop_scope, cond_block);
   5679 
   5680     // Create continue_block (AstGen.zig:6694).
   5681     uint32_t continue_block = makeBlockInst(ag, block_tag, &loop_scope, node);
   5682 
   5683     // Add repeat to loop_scope (AstGen.zig:6696-6697).
   5684     {
   5685         ZirInstTag repeat_tag
   5686             = is_inline ? ZIR_INST_REPEAT_INLINE : ZIR_INST_REPEAT;
   5687         ZirInstData repeat_data;
   5688         memset(&repeat_data, 0, sizeof(repeat_data));
   5689         repeat_data.node = (int32_t)node - (int32_t)loop_scope.decl_node_index;
   5690         addInstruction(&loop_scope, repeat_tag, repeat_data);
   5691     }
   5692 
   5693     // Set loop body and configure break/continue (AstGen.zig:6699-6701).
   5694     setBlockBody(ag, &loop_scope, loop_inst); // unstacks loop_scope
   5695     loop_scope.break_block = loop_inst;
   5696     loop_scope.continue_block = continue_block;
   5697 
   5698     // Stack then_scope (AstGen.zig:6708-6709).
   5699     GenZir then_scope = makeSubBlock(gz, &cond_scope.base);
   5700 
   5701     // Add continue_block to then_scope (AstGen.zig:6716).
   5702     gzAppendInstruction(&then_scope, continue_block);
   5703 
   5704     // Create continue_scope inside then_scope (AstGen.zig:6725).
   5705     GenZir continue_scope = makeSubBlock(&then_scope, &then_scope.base);
   5706 
   5707     // Execute body (AstGen.zig:6727-6730).
   5708     emitDbgNode(&continue_scope, body_node);
   5709     fullBodyExpr(
   5710         &continue_scope, &continue_scope.base, RL_NONE_VAL, body_node);
   5711 
   5712     // Break continue_block if not noreturn (AstGen.zig:6735-6747).
   5713     if (!endsWithNoReturn(&continue_scope)) {
   5714         // dbg_stmt + dbg_empty_stmt (AstGen.zig:6737-6745).
   5715         advanceSourceCursor(
   5716             ag, tree->tokens.starts[lastToken(tree, body_node)]);
   5717         emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column);
   5718         {
   5719             ZirInstData ext_data;
   5720             ext_data.extended.opcode = (uint16_t)ZIR_EXT_DBG_EMPTY_STMT;
   5721             ext_data.extended.small = 0;
   5722             ext_data.extended.operand = 0;
   5723             addInstruction(gz, ZIR_INST_EXTENDED, ext_data);
   5724         }
   5725         ZirInstTag break_tag
   5726             = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK;
   5727         addBreak(&continue_scope, break_tag, continue_block,
   5728             ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE);
   5729     }
   5730     setBlockBody(ag, &continue_scope, continue_block);
   5731 
   5732     // Break cond_block from then_scope (AstGen.zig:7064).
   5733     {
   5734         ZirInstTag break_tag
   5735             = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK;
   5736         addBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE,
   5737             AST_NODE_OFFSET_NONE);
   5738     }
   5739 
   5740     // Else scope: break loop with void (AstGen.zig:6785-6788).
   5741     GenZir else_scope = makeSubBlock(gz, &cond_scope.base);
   5742     {
   5743         ZirInstTag break_tag
   5744             = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK;
   5745         addBreak(&else_scope, break_tag, loop_inst, ZIR_REF_VOID_VALUE,
   5746             AST_NODE_OFFSET_NONE);
   5747     }
   5748 
   5749     // Wire up condbr (AstGen.zig:6795).
   5750     setCondBrPayload(ag, condbr, cond, &then_scope, &else_scope);
   5751 
   5752     uint32_t result = loop_inst + ZIR_REF_START_INDEX;
   5753 
   5754     // Emit ensure_result_used when used as statement (AstGen.zig:6812-6813).
   5755     if (is_statement) {
   5756         addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, result, node);
   5757     }
   5758 
   5759     return result;
   5760 }
   5761 
   5762 // --- switchExpr (AstGen.zig:7625-8117) ---
   5763 // Handles switch and switch_comma expressions.
   5764 // Encoding: switch_block pl_node with SwitchBlock extra payload.
   5765 
   5766 static uint32_t switchExpr(
   5767     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   5768     AstGenCtx* ag = gz->astgen;
   5769     const Ast* tree = ag->tree;
   5770     bool need_rl = nodesNeedRlContains(ag, node);
   5771     ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl);
   5772     AstData nd = tree->nodes.datas[node];
   5773 
   5774     // AST_NODE_SWITCH: lhs = condition node, rhs = extra index for SubRange.
   5775     // SubRange[rhs] = { cases_start, cases_end }.
   5776     // Case nodes are at extra_data[cases_start..cases_end].
   5777     uint32_t cond_node = nd.lhs;
   5778     uint32_t extra_idx = nd.rhs;
   5779     uint32_t cases_start = tree->extra_data.arr[extra_idx];
   5780     uint32_t cases_end = tree->extra_data.arr[extra_idx + 1];
   5781     const uint32_t* case_nodes_arr = tree->extra_data.arr + cases_start;
   5782     uint32_t case_count = cases_end - cases_start;
   5783 
   5784     // Save operand source location before evaluating (AstGen.zig:7774-7775).
   5785     advanceSourceCursorToNode(ag, cond_node);
   5786     uint32_t operand_lc_line = ag->source_line - gz->decl_line;
   5787     uint32_t operand_lc_col = ag->source_column;
   5788 
   5789     // Evaluate switch operand (AstGen.zig:7777).
   5790     uint32_t cond_ref = expr(gz, scope, cond_node);
   5791 
   5792     // --- First pass: categorize cases (AstGen.zig:7671-7762) ---
   5793     uint32_t scalar_cases_len = 0;
   5794     uint32_t multi_cases_len = 0;
   5795     bool has_else = false;
   5796 
   5797     for (uint32_t ci = 0; ci < case_count; ci++) {
   5798         uint32_t cn = case_nodes_arr[ci];
   5799         AstNodeTag ct = tree->nodes.tags[cn];
   5800         AstData cd = tree->nodes.datas[cn];
   5801 
   5802         switch (ct) {
   5803         case AST_NODE_SWITCH_CASE_ONE:
   5804         case AST_NODE_SWITCH_CASE_INLINE_ONE:
   5805             if (cd.lhs == 0)
   5806                 has_else = true;
   5807             else if (tree->nodes.tags[cd.lhs] == AST_NODE_SWITCH_RANGE)
   5808                 multi_cases_len++;
   5809             else
   5810                 scalar_cases_len++;
   5811             break;
   5812         case AST_NODE_SWITCH_CASE:
   5813         case AST_NODE_SWITCH_CASE_INLINE:
   5814             multi_cases_len++;
   5815             break;
   5816         default:
   5817             break;
   5818         }
   5819     }
   5820 
   5821     // Sema expects a dbg_stmt immediately before switch_block
   5822     // (AstGen.zig:7806).
   5823     emitDbgStmtForceCurrentIndex(gz, operand_lc_line, operand_lc_col);
   5824     // --- Create switch_block instruction (AstGen.zig:7809) ---
   5825     uint32_t switch_inst = makeBlockInst(ag, ZIR_INST_SWITCH_BLOCK, gz, node);
   5826 
   5827     // --- Single-pass evaluation in source order (AstGen.zig:7849-8027) ---
   5828     // Case table + payload buffer pattern (like upstream scratch).
   5829     // Table layout: [else?] [scalar_0..N] [multi_0..N]
   5830     // Each entry points to the start of that case's data in the buffer.
   5831     uint32_t table_size
   5832         = (has_else ? 1 : 0) + scalar_cases_len + multi_cases_len;
   5833     uint32_t else_tbl = 0;
   5834     uint32_t scalar_tbl = (has_else ? 1 : 0);
   5835     uint32_t multi_tbl = scalar_tbl + scalar_cases_len;
   5836 
   5837     uint32_t pay_cap = table_size + case_count * 16;
   5838     uint32_t* pay = malloc(pay_cap * sizeof(uint32_t));
   5839     uint32_t pay_len = table_size;
   5840 
   5841     uint32_t scalar_ci = 0;
   5842     uint32_t multi_ci = 0;
   5843 
   5844     for (uint32_t ci = 0; ci < case_count; ci++) {
   5845         uint32_t cn = case_nodes_arr[ci];
   5846         AstNodeTag ct = tree->nodes.tags[cn];
   5847         AstData cd = tree->nodes.datas[cn];
   5848         uint32_t hdr = pay_len;
   5849         uint32_t prong_info_slot = 0;
   5850 
   5851         // Ensure capacity for items (generous estimate).
   5852         if (pay_len + 32 > pay_cap) {
   5853             pay_cap *= 2;
   5854             uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t));
   5855             if (!p)
   5856                 abort();
   5857             pay = p;
   5858         }
   5859 
   5860         switch (ct) {
   5861         case AST_NODE_SWITCH_CASE_ONE:
   5862         case AST_NODE_SWITCH_CASE_INLINE_ONE:
   5863             if (cd.lhs == 0) {
   5864                 // Else: [prong_info, body...]
   5865                 pay[else_tbl] = hdr;
   5866                 prong_info_slot = pay_len++;
   5867             } else if (tree->nodes.tags[cd.lhs] == AST_NODE_SWITCH_RANGE) {
   5868                 // Single range → multi case:
   5869                 // [items_len=0, ranges_len=1, prong_info, first, last]
   5870                 pay[multi_tbl + multi_ci++] = hdr;
   5871                 pay[pay_len++] = 0;
   5872                 pay[pay_len++] = 1;
   5873                 prong_info_slot = pay_len++;
   5874                 AstData rng = tree->nodes.datas[cd.lhs];
   5875                 pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, rng.lhs,
   5876                     COMPTIME_REASON_SWITCH_ITEM);
   5877                 pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, rng.rhs,
   5878                     COMPTIME_REASON_SWITCH_ITEM);
   5879             } else {
   5880                 // Scalar: [item_ref, prong_info, body...]
   5881                 pay[scalar_tbl + scalar_ci++] = hdr;
   5882                 pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, cd.lhs,
   5883                     COMPTIME_REASON_SWITCH_ITEM);
   5884                 prong_info_slot = pay_len++;
   5885             }
   5886             break;
   5887         case AST_NODE_SWITCH_CASE:
   5888         case AST_NODE_SWITCH_CASE_INLINE: {
   5889             // Multi-item: SubRange[lhs] of items, rhs = body.
   5890             pay[multi_tbl + multi_ci++] = hdr;
   5891             uint32_t ist = tree->extra_data.arr[cd.lhs];
   5892             uint32_t ien = tree->extra_data.arr[cd.lhs + 1];
   5893             uint32_t nitems = 0, nranges = 0;
   5894             for (uint32_t j = ist; j < ien; j++) {
   5895                 if (tree->nodes.tags[tree->extra_data.arr[j]]
   5896                     == AST_NODE_SWITCH_RANGE)
   5897                     nranges++;
   5898                 else
   5899                     nitems++;
   5900             }
   5901             pay[pay_len++] = nitems;
   5902             pay[pay_len++] = nranges;
   5903             prong_info_slot = pay_len++;
   5904             // Non-range items.
   5905             for (uint32_t j = ist; j < ien; j++) {
   5906                 uint32_t item = tree->extra_data.arr[j];
   5907                 if (tree->nodes.tags[item] != AST_NODE_SWITCH_RANGE) {
   5908                     if (pay_len + 2 > pay_cap) {
   5909                         pay_cap *= 2;
   5910                         uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t));
   5911                         if (!p)
   5912                             abort();
   5913                         pay = p;
   5914                     }
   5915                     pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, item,
   5916                         COMPTIME_REASON_SWITCH_ITEM);
   5917                 }
   5918             }
   5919             // Range pairs.
   5920             for (uint32_t j = ist; j < ien; j++) {
   5921                 uint32_t item = tree->extra_data.arr[j];
   5922                 if (tree->nodes.tags[item] == AST_NODE_SWITCH_RANGE) {
   5923                     AstData rng = tree->nodes.datas[item];
   5924                     if (pay_len + 2 > pay_cap) {
   5925                         pay_cap *= 2;
   5926                         uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t));
   5927                         if (!p)
   5928                             abort();
   5929                         pay = p;
   5930                     }
   5931                     pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL,
   5932                         rng.lhs, COMPTIME_REASON_SWITCH_ITEM);
   5933                     pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL,
   5934                         rng.rhs, COMPTIME_REASON_SWITCH_ITEM);
   5935                 }
   5936             }
   5937             break;
   5938         }
   5939         default:
   5940             continue;
   5941         }
   5942 
   5943         // Evaluate body (AstGen.zig:7997-8026).
   5944         uint32_t body_node = cd.rhs;
   5945         GenZir case_scope = makeSubBlock(gz, scope);
   5946 
   5947         // Note: upstream regular switchExpr (AstGen.zig:7625) does NOT emit
   5948         // save_err_ret_index. Only switchExprErrUnion (AstGen.zig:7524) does.
   5949 
   5950         // Use fullBodyExpr to process body inline (AstGen.zig:8009).
   5951         uint32_t result
   5952             = fullBodyExpr(&case_scope, &case_scope.base, break_rl, body_node);
   5953         if (!refIsNoReturn(gz, result)) {
   5954             addBreak(&case_scope, ZIR_INST_BREAK, switch_inst, result,
   5955                 (int32_t)body_node - (int32_t)gz->decl_node_index);
   5956         }
   5957         uint32_t body_len = gzInstructionsLen(&case_scope);
   5958         const uint32_t* body = gzInstructionsSlice(&case_scope);
   5959 
   5960         pay[prong_info_slot] = body_len & 0x0FFFFFFFu;
   5961 
   5962         if (pay_len + body_len > pay_cap) {
   5963             while (pay_len + body_len > pay_cap)
   5964                 pay_cap *= 2;
   5965             uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t));
   5966             if (!p)
   5967                 abort();
   5968             pay = p;
   5969         }
   5970         for (uint32_t i = 0; i < body_len; i++)
   5971             pay[pay_len++] = body[i];
   5972         gzUnstack(&case_scope);
   5973     }
   5974 
   5975     // --- Serialize to extra in payload order (AstGen.zig:8036-8110) ---
   5976     ensureExtraCapacity(ag,
   5977         2 + (uint32_t)(multi_cases_len > 0 ? 1 : 0) + pay_len - table_size);
   5978     uint32_t payload_index = ag->extra_len;
   5979 
   5980     ag->extra[ag->extra_len++] = cond_ref;
   5981 
   5982     uint32_t bits = 0;
   5983     if (multi_cases_len > 0)
   5984         bits |= 1u;
   5985     if (has_else)
   5986         bits |= (1u << 1);
   5987     bits |= (scalar_cases_len & 0x1FFFFFFu) << 7;
   5988     ag->extra[ag->extra_len++] = bits;
   5989 
   5990     if (multi_cases_len > 0)
   5991         ag->extra[ag->extra_len++] = multi_cases_len;
   5992 
   5993     // Else prong.
   5994     if (has_else) {
   5995         uint32_t si = pay[else_tbl];
   5996         uint32_t bl = pay[si] & 0x0FFFFFFFu;
   5997         for (uint32_t i = 0; i < 1 + bl; i++)
   5998             ag->extra[ag->extra_len++] = pay[si + i];
   5999     }
   6000     // Scalar cases.
   6001     for (uint32_t i = 0; i < scalar_cases_len; i++) {
   6002         uint32_t si = pay[scalar_tbl + i];
   6003         uint32_t bl = pay[si + 1] & 0x0FFFFFFFu;
   6004         for (uint32_t j = 0; j < 2 + bl; j++)
   6005             ag->extra[ag->extra_len++] = pay[si + j];
   6006     }
   6007     // Multi cases.
   6008     for (uint32_t i = 0; i < multi_cases_len; i++) {
   6009         uint32_t si = pay[multi_tbl + i];
   6010         uint32_t ni = pay[si];
   6011         uint32_t nr = pay[si + 1];
   6012         uint32_t bl = pay[si + 2] & 0x0FFFFFFFu;
   6013         uint32_t total = 3 + ni + nr * 2 + bl;
   6014         for (uint32_t j = 0; j < total; j++)
   6015             ag->extra[ag->extra_len++] = pay[si + j];
   6016     }
   6017 
   6018     free(pay);
   6019 
   6020     ag->inst_datas[switch_inst].pl_node.payload_index = payload_index;
   6021     gzAppendInstruction(gz, switch_inst);
   6022 
   6023     // AstGen.zig:8112-8115.
   6024     bool need_result_rvalue = (break_rl.tag != rl.tag);
   6025     if (need_result_rvalue)
   6026         return rvalue(gz, rl, switch_inst + ZIR_REF_START_INDEX, node);
   6027     return switch_inst + ZIR_REF_START_INDEX;
   6028 }
   6029 
   6030 // --- rvalue (AstGen.zig:11029) ---
   6031 // Simplified: handles .none and .discard result locations.
   6032 
   6033 static uint32_t rvalueDiscard(GenZir* gz, uint32_t result, uint32_t src_node) {
   6034     // .discard => emit ensure_result_non_error, return .void_value
   6035     // (AstGen.zig:11071-11074)
   6036     ZirInstData data;
   6037     data.un_node.src_node = (int32_t)src_node - (int32_t)gz->decl_node_index;
   6038     data.un_node.operand = result;
   6039     addInstruction(gz, ZIR_INST_ENSURE_RESULT_NON_ERROR, data);
   6040     return ZIR_REF_VOID_VALUE;
   6041 }
   6042 
   6043 // --- emitDbgNode / emitDbgStmt (AstGen.zig:3422, 13713) ---
   6044 
   6045 static void emitDbgStmt(GenZir* gz, uint32_t line, uint32_t column) {
   6046     if (gz->is_comptime)
   6047         return;
   6048     // Check if last instruction is already dbg_stmt; if so, update it.
   6049     // (AstGen.zig:13715-13724)
   6050     AstGenCtx* ag = gz->astgen;
   6051     uint32_t gz_len = gzInstructionsLen(gz);
   6052     if (gz_len > 0) {
   6053         uint32_t last = gzInstructionsSlice(gz)[gz_len - 1];
   6054         if (ag->inst_tags[last] == ZIR_INST_DBG_STMT) {
   6055             ag->inst_datas[last].dbg_stmt.line = line;
   6056             ag->inst_datas[last].dbg_stmt.column = column;
   6057             return;
   6058         }
   6059     }
   6060     ZirInstData data;
   6061     data.dbg_stmt.line = line;
   6062     data.dbg_stmt.column = column;
   6063     addInstruction(gz, ZIR_INST_DBG_STMT, data);
   6064 }
   6065 
   6066 // Mirrors emitDbgStmtForceCurrentIndex (AstGen.zig:13739-13760).
   6067 static void emitDbgStmtForceCurrentIndex(
   6068     GenZir* gz, uint32_t line, uint32_t column) {
   6069     AstGenCtx* ag = gz->astgen;
   6070     uint32_t gz_len = gzInstructionsLen(gz);
   6071     if (gz_len > 0
   6072         && gzInstructionsSlice(gz)[gz_len - 1] == ag->inst_len - 1) {
   6073         uint32_t last = ag->inst_len - 1;
   6074         if (ag->inst_tags[last] == ZIR_INST_DBG_STMT) {
   6075             ag->inst_datas[last].dbg_stmt.line = line;
   6076             ag->inst_datas[last].dbg_stmt.column = column;
   6077             return;
   6078         }
   6079     }
   6080     ZirInstData data;
   6081     data.dbg_stmt.line = line;
   6082     data.dbg_stmt.column = column;
   6083     addInstruction(gz, ZIR_INST_DBG_STMT, data);
   6084 }
   6085 
   6086 static void emitDbgNode(GenZir* gz, uint32_t node) {
   6087     if (gz->is_comptime)
   6088         return;
   6089     AstGenCtx* ag = gz->astgen;
   6090     advanceSourceCursorToNode(ag, node);
   6091     uint32_t line = ag->source_line - gz->decl_line;
   6092     uint32_t column = ag->source_column;
   6093     emitDbgStmt(gz, line, column);
   6094 }
   6095 
   6096 // --- assign (AstGen.zig:3434) ---
   6097 // Handles `_ = expr` discard pattern.
   6098 
   6099 static void assignStmt(GenZir* gz, Scope* scope, uint32_t infix_node) {
   6100     emitDbgNode(gz, infix_node);
   6101     const AstGenCtx* ag = gz->astgen;
   6102     const Ast* tree = ag->tree;
   6103 
   6104     AstData nd = tree->nodes.datas[infix_node];
   6105     uint32_t lhs = nd.lhs;
   6106     uint32_t rhs = nd.rhs;
   6107 
   6108     // Check if LHS is `_` identifier for discard (AstGen.zig:3440-3446).
   6109     if (tree->nodes.tags[lhs] == AST_NODE_IDENTIFIER) {
   6110         uint32_t ident_tok = tree->nodes.main_tokens[lhs];
   6111         uint32_t tok_start = tree->tokens.starts[ident_tok];
   6112         if (tree->source[tok_start] == '_'
   6113             && (tok_start + 1 >= tree->source_len
   6114                 || !((tree->source[tok_start + 1] >= 'a'
   6115                          && tree->source[tok_start + 1] <= 'z')
   6116                     || (tree->source[tok_start + 1] >= 'A'
   6117                         && tree->source[tok_start + 1] <= 'Z')
   6118                     || tree->source[tok_start + 1] == '_'
   6119                     || (tree->source[tok_start + 1] >= '0'
   6120                         && tree->source[tok_start + 1] <= '9')))) {
   6121             // Discard: evaluate RHS with .discard result location.
   6122             uint32_t result = expr(gz, scope, rhs);
   6123             rvalueDiscard(gz, result, rhs);
   6124             return;
   6125         }
   6126     }
   6127 
   6128     // Non-discard assignment: evaluate LHS as lvalue, pass ptr rl to RHS.
   6129     // (AstGen.zig:3448-3452).
   6130     {
   6131         uint32_t lhs_ptr = exprRl(gz, scope, RL_REF_VAL, lhs);
   6132         ResultLoc ptr_rl
   6133             = { .tag = RL_PTR, .data = lhs_ptr, .src_node = infix_node };
   6134         (void)exprRl(gz, scope, ptr_rl, rhs);
   6135     }
   6136 }
   6137 
   6138 // --- assignOp (AstGen.zig:3731) ---
   6139 // Handles compound assignment operators (+=, -=, *=, etc.).
   6140 
   6141 static void assignOp(
   6142     GenZir* gz, Scope* scope, uint32_t infix_node, ZirInstTag op_tag) {
   6143     emitDbgNode(gz, infix_node);
   6144     AstGenCtx* ag = gz->astgen;
   6145     const Ast* tree = ag->tree;
   6146 
   6147     AstData nd = tree->nodes.datas[infix_node];
   6148     uint32_t lhs_node = nd.lhs;
   6149     uint32_t rhs_node = nd.rhs;
   6150 
   6151     // Evaluate LHS as lvalue pointer (AstGen.zig:3742).
   6152     uint32_t lhs_ptr = exprRl(gz, scope, RL_REF_VAL, lhs_node);
   6153 
   6154     // Advance cursor for add/sub/mul/div/mod_rem (AstGen.zig:3744-3747).
   6155     uint32_t cursor_line = 0, cursor_col = 0;
   6156     bool need_dbg = false;
   6157     if (op_tag == ZIR_INST_ADD || op_tag == ZIR_INST_SUB
   6158         || op_tag == ZIR_INST_MUL || op_tag == ZIR_INST_DIV
   6159         || op_tag == ZIR_INST_MOD_REM) {
   6160         if (!gz->is_comptime) {
   6161             advanceSourceCursorToMainToken(ag, gz, infix_node);
   6162         }
   6163         cursor_line = ag->source_line - gz->decl_line;
   6164         cursor_col = ag->source_column;
   6165         need_dbg = true;
   6166     }
   6167 
   6168     // Load current value (AstGen.zig:3748).
   6169     uint32_t lhs = addUnNode(gz, ZIR_INST_LOAD, lhs_ptr, infix_node);
   6170 
   6171     // Determine RHS result type (AstGen.zig:3750-3766).
   6172     uint32_t rhs_res_ty;
   6173     if (op_tag == ZIR_INST_ADD || op_tag == ZIR_INST_SUB) {
   6174         // Emit inplace_arith_result_ty extended instruction.
   6175         uint16_t inplace_op
   6176             = (op_tag == ZIR_INST_ADD) ? 0 : 1; // add_eq=0, sub_eq=1
   6177         ZirInstData ext_data;
   6178         memset(&ext_data, 0, sizeof(ext_data));
   6179         ext_data.extended.opcode = (uint16_t)ZIR_EXT_INPLACE_ARITH_RESULT_TY;
   6180         ext_data.extended.small = inplace_op;
   6181         ext_data.extended.operand = lhs;
   6182         rhs_res_ty = addInstruction(gz, ZIR_INST_EXTENDED, ext_data);
   6183     } else {
   6184         rhs_res_ty = addUnNode(gz, ZIR_INST_TYPEOF, lhs, infix_node);
   6185     }
   6186 
   6187     // Evaluate RHS with type coercion (AstGen.zig:3768).
   6188     uint32_t rhs_raw = expr(gz, scope, rhs_node);
   6189     uint32_t rhs
   6190         = addPlNodeBin(gz, ZIR_INST_AS_NODE, rhs_node, rhs_res_ty, rhs_raw);
   6191 
   6192     // Emit debug statement for arithmetic ops (AstGen.zig:3770-3775).
   6193     if (need_dbg) {
   6194         emitDbgStmt(gz, cursor_line, cursor_col);
   6195     }
   6196 
   6197     // Emit the operation (AstGen.zig:3776-3779).
   6198     uint32_t result = addPlNodeBin(gz, op_tag, infix_node, lhs, rhs);
   6199 
   6200     // Store result back (AstGen.zig:3780-3783).
   6201     addPlNodeBin(gz, ZIR_INST_STORE_NODE, infix_node, lhs_ptr, result);
   6202 }
   6203 
   6204 // --- builtinEvalToError (BuiltinFn.zig) ---
   6205 // Returns per-builtin eval_to_error. Default is .never; only a few are
   6206 // .maybe or .always. Mirrors BuiltinFn.list lookup in AstGen.zig:10539.
   6207 static int builtinEvalToError(const Ast* tree, uint32_t node) {
   6208     uint32_t main_tok = tree->nodes.main_tokens[node];
   6209     uint32_t tok_start = tree->tokens.starts[main_tok];
   6210     const char* source = tree->source;
   6211     uint32_t name_start = tok_start + 1; // skip '@'
   6212     uint32_t name_end = name_start;
   6213     while (name_end < tree->source_len
   6214         && ((source[name_end] >= 'a' && source[name_end] <= 'z')
   6215             || (source[name_end] >= 'A' && source[name_end] <= 'Z')
   6216             || source[name_end] == '_')) {
   6217         name_end++;
   6218     }
   6219     uint32_t name_len = name_end - name_start;
   6220     const char* name = source + name_start;
   6221     // clang-format off
   6222     // .always:
   6223     if (name_len == 12 && memcmp(name, "errorFromInt", 12) == 0)
   6224         return 1; // EVAL_TO_ERROR_ALWAYS
   6225     // .maybe:
   6226     if (name_len == 2 && memcmp(name, "as", 2) == 0) return 2;
   6227     if (name_len == 4 && memcmp(name, "call", 4) == 0) return 2;
   6228     if (name_len == 5 && memcmp(name, "field", 5) == 0) return 2;
   6229     if (name_len == 9 && memcmp(name, "errorCast", 9) == 0) return 2;
   6230     // clang-format on
   6231     // Default: .never
   6232     return 0;
   6233 }
   6234 
   6235 // --- nodeMayEvalToError (AstGen.zig:10340) ---
   6236 // Three-way result: 0=never, 1=always, 2=maybe.
   6237 #define EVAL_TO_ERROR_NEVER 0
   6238 #define EVAL_TO_ERROR_ALWAYS 1
   6239 #define EVAL_TO_ERROR_MAYBE 2
   6240 
   6241 static int nodeMayEvalToError(const Ast* tree, uint32_t node) {
   6242     uint32_t n = node;
   6243     while (true) {
   6244         AstNodeTag tag = tree->nodes.tags[n];
   6245         switch (tag) {
   6246         case AST_NODE_ERROR_VALUE:
   6247             return EVAL_TO_ERROR_ALWAYS;
   6248         // These may evaluate to errors.
   6249         case AST_NODE_IDENTIFIER:
   6250         case AST_NODE_FIELD_ACCESS:
   6251         case AST_NODE_DEREF:
   6252         case AST_NODE_ARRAY_ACCESS:
   6253         case AST_NODE_WHILE_SIMPLE:
   6254         case AST_NODE_WHILE_CONT:
   6255         case AST_NODE_WHILE:
   6256         case AST_NODE_FOR_SIMPLE:
   6257         case AST_NODE_FOR:
   6258         case AST_NODE_IF_SIMPLE:
   6259         case AST_NODE_IF:
   6260         case AST_NODE_SWITCH:
   6261         case AST_NODE_SWITCH_COMMA:
   6262         case AST_NODE_CALL_ONE:
   6263         case AST_NODE_CALL_ONE_COMMA:
   6264         case AST_NODE_CALL:
   6265         case AST_NODE_CALL_COMMA:
   6266         case AST_NODE_ASM_SIMPLE:
   6267         case AST_NODE_ASM_LEGACY:
   6268         case AST_NODE_ASM:
   6269         case AST_NODE_CATCH:
   6270         case AST_NODE_ORELSE:
   6271             return EVAL_TO_ERROR_MAYBE;
   6272         // Forward to sub-expression.
   6273         case AST_NODE_TRY:
   6274         case AST_NODE_COMPTIME:
   6275         case AST_NODE_NOSUSPEND:
   6276             n = tree->nodes.datas[n].lhs;
   6277             continue;
   6278         case AST_NODE_GROUPED_EXPRESSION:
   6279         case AST_NODE_UNWRAP_OPTIONAL:
   6280             n = tree->nodes.datas[n].lhs;
   6281             continue;
   6282         // Labeled blocks may need a memory location.
   6283         case AST_NODE_BLOCK_TWO:
   6284         case AST_NODE_BLOCK_TWO_SEMICOLON:
   6285         case AST_NODE_BLOCK:
   6286         case AST_NODE_BLOCK_SEMICOLON: {
   6287             uint32_t lbrace = tree->nodes.main_tokens[n];
   6288             if (lbrace > 0 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON)
   6289                 return EVAL_TO_ERROR_MAYBE;
   6290             return EVAL_TO_ERROR_NEVER;
   6291         }
   6292         // Builtins: look up per-builtin eval_to_error
   6293         // (AstGen.zig:10530-10541).
   6294         case AST_NODE_BUILTIN_CALL:
   6295         case AST_NODE_BUILTIN_CALL_COMMA:
   6296         case AST_NODE_BUILTIN_CALL_TWO:
   6297         case AST_NODE_BUILTIN_CALL_TWO_COMMA:
   6298             return builtinEvalToError(tree, n);
   6299         // Everything else: .never
   6300         default:
   6301             return EVAL_TO_ERROR_NEVER;
   6302         }
   6303     }
   6304 }
   6305 
   6306 // --- nodeMayAppendToErrorTrace (AstGen.zig:10315) ---
   6307 // Returns true if the expression may append to the error return trace.
   6308 static bool nodeMayAppendToErrorTrace(const Ast* tree, uint32_t node) {
   6309     uint32_t n = node;
   6310     while (true) {
   6311         AstNodeTag tag = tree->nodes.tags[n];
   6312         switch (tag) {
   6313         // These don't call runtime functions.
   6314         case AST_NODE_ERROR_VALUE:
   6315         case AST_NODE_IDENTIFIER:
   6316         case AST_NODE_COMPTIME:
   6317             return false;
   6318         // Forward to sub-expression.
   6319         case AST_NODE_TRY:
   6320         case AST_NODE_NOSUSPEND:
   6321             n = tree->nodes.datas[n].lhs;
   6322             continue;
   6323         case AST_NODE_GROUPED_EXPRESSION:
   6324         case AST_NODE_UNWRAP_OPTIONAL:
   6325             n = tree->nodes.datas[n].lhs;
   6326             continue;
   6327         // Anything else: check if it may eval to error.
   6328         default:
   6329             return nodeMayEvalToError(tree, n) != EVAL_TO_ERROR_NEVER;
   6330         }
   6331     }
   6332 }
   6333 
   6334 // --- addSaveErrRetIndex (AstGen.zig:12556) ---
   6335 // Emits SAVE_ERR_RET_INDEX instruction.
   6336 // operand is the init inst ref (or ZIR_REF_NONE for .always).
   6337 static void addSaveErrRetIndex(GenZir* gz, uint32_t operand) {
   6338     ZirInstData data;
   6339     data.save_err_ret_index.operand = operand;
   6340     data.save_err_ret_index._pad = 0;
   6341     addInstruction(gz, ZIR_INST_SAVE_ERR_RET_INDEX, data);
   6342 }
   6343 
   6344 // --- addRestoreErrRetIndexBlock (AstGen.zig:12607-12614) ---
   6345 // Emits extended RESTORE_ERR_RET_INDEX with block target (if_non_error
   6346 // condition). Payload: src_node, block_ref, operand.
   6347 static void addRestoreErrRetIndexBlock(
   6348     GenZir* gz, uint32_t block_inst, uint32_t operand, uint32_t node) {
   6349     AstGenCtx* ag = gz->astgen;
   6350     ensureExtraCapacity(ag, 3);
   6351     uint32_t payload_index = ag->extra_len;
   6352     ag->extra[ag->extra_len++]
   6353         = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index);
   6354     ag->extra[ag->extra_len++] = block_inst + ZIR_REF_START_INDEX;
   6355     ag->extra[ag->extra_len++] = operand;
   6356 
   6357     ZirInstData ext_data;
   6358     ext_data.extended.opcode = (uint16_t)ZIR_EXT_RESTORE_ERR_RET_INDEX;
   6359     ext_data.extended.small = 0;
   6360     ext_data.extended.operand = payload_index;
   6361     addInstruction(gz, ZIR_INST_EXTENDED, ext_data);
   6362 }
   6363 
   6364 // --- restoreErrRetIndex (AstGen.zig:2121-2148) ---
   6365 // Emits restore_err_ret_index for block target based on nodeMayEvalToError.
   6366 static void restoreErrRetIndex(GenZir* gz, uint32_t block_inst, ResultLoc rl,
   6367     uint32_t node, uint32_t result) {
   6368     const Ast* tree = gz->astgen->tree;
   6369     int eval = nodeMayEvalToError(tree, node);
   6370     if (eval == EVAL_TO_ERROR_ALWAYS)
   6371         return; // never restore/pop
   6372     uint32_t op;
   6373     if (eval == EVAL_TO_ERROR_NEVER) {
   6374         op = ZIR_REF_NONE; // always restore/pop
   6375     } else {
   6376         // EVAL_TO_ERROR_MAYBE
   6377         // Simplified: without ri.ctx, treat non-ptr RL as result
   6378         // (AstGen.zig:2131-2144).
   6379         if (rl.tag == RL_PTR) {
   6380             op = addUnNode(gz, ZIR_INST_LOAD, rl.data, node);
   6381         } else if (rl.tag == RL_INFERRED_PTR) {
   6382             op = ZIR_REF_NONE;
   6383         } else {
   6384             op = result;
   6385         }
   6386     }
   6387     addRestoreErrRetIndexBlock(gz, block_inst, op, node);
   6388 }
   6389 
   6390 // --- varDecl (AstGen.zig:3189) ---
   6391 // Handles local const/var declarations. Returns new scope with the variable.
   6392 // scope_out: set to new scope if variable is added; unchanged otherwise.
   6393 
   6394 static void varDecl(GenZir* gz, Scope* scope, uint32_t node,
   6395     ScopeLocalVal* val_out, ScopeLocalPtr* ptr_out, Scope** scope_out) {
   6396     AstGenCtx* ag = gz->astgen;
   6397     emitDbgNode(gz, node); // AstGen.zig:3196
   6398     const Ast* tree = ag->tree;
   6399     AstData nd = tree->nodes.datas[node];
   6400     AstNodeTag tag = tree->nodes.tags[node];
   6401 
   6402     uint32_t mut_token = tree->nodes.main_tokens[node];
   6403     uint32_t name_token = mut_token + 1;
   6404     bool is_const = (tree->source[tree->tokens.starts[mut_token]] == 'c');
   6405 
   6406     uint32_t ident_name = identAsString(ag, name_token);
   6407 
   6408     // Extract type_node and init_node based on variant.
   6409     uint32_t type_node = 0;
   6410     uint32_t init_node = 0;
   6411 
   6412     if (tag == AST_NODE_SIMPLE_VAR_DECL) {
   6413         // lhs = type (optional), rhs = init (optional).
   6414         type_node = nd.lhs;
   6415         init_node = nd.rhs;
   6416     } else if (tag == AST_NODE_LOCAL_VAR_DECL) {
   6417         // lhs = extra_data index, rhs = init.
   6418         // extra: {type_node, align_node, addrspace_node, section_node}
   6419         // Simplified: just extract type_node.
   6420         uint32_t extra_idx = nd.lhs;
   6421         type_node = tree->extra_data.arr[extra_idx]; // type_node
   6422         init_node = nd.rhs;
   6423     } else if (tag == AST_NODE_ALIGNED_VAR_DECL) {
   6424         // lhs = align expr, rhs = init.
   6425         // No type node in this variant.
   6426         init_node = nd.rhs;
   6427     } else {
   6428         // global_var_decl or unknown — bail.
   6429         SET_ERROR(ag);
   6430         return;
   6431     }
   6432 
   6433     if (init_node == 0) {
   6434         // Variables must be initialized (AstGen.zig:3228).
   6435         SET_ERROR(ag);
   6436         return;
   6437     }
   6438 
   6439     if (is_const) {
   6440         // --- CONST path (AstGen.zig:3232-3340) ---
   6441         if (!nodesNeedRlContains(ag, node)) {
   6442             // Rvalue path (AstGen.zig:3246-3271).
   6443             // Evaluate type annotation and build result_info
   6444             // (AstGen.zig:3247-3250).
   6445             ResultLoc result_info;
   6446             if (type_node != 0) {
   6447                 uint32_t type_ref = typeExpr(gz, scope, type_node);
   6448                 result_info = (ResultLoc) { .tag = RL_TY,
   6449                     .data = type_ref,
   6450                     .src_node = 0,
   6451                     .ctx = RI_CTX_CONST_INIT };
   6452             } else {
   6453                 result_info = (ResultLoc) { .tag = RL_NONE,
   6454                     .data = 0,
   6455                     .src_node = 0,
   6456                     .ctx = RI_CTX_CONST_INIT };
   6457             }
   6458 
   6459             // Evaluate init expression (AstGen.zig:3251-3252).
   6460             uint32_t init_ref = exprRl(gz, scope, result_info, init_node);
   6461 
   6462             if (ag->has_compile_errors)
   6463                 return;
   6464 
   6465             // validate_const (AstGen.zig:3266).
   6466             addUnNode(gz, ZIR_INST_VALIDATE_CONST, init_ref, init_node);
   6467 
   6468             // dbg_var_val (AstGen.zig:3269).
   6469             addDbgVar(gz, ZIR_INST_DBG_VAR_VAL, ident_name, init_ref);
   6470 
   6471             // save_err_ret_index (AstGen.zig:3259-3260).
   6472             if (nodeMayAppendToErrorTrace(tree, init_node))
   6473                 addSaveErrRetIndex(gz, init_ref);
   6474 
   6475             // Create ScopeLocalVal (AstGen.zig:3276-3284).
   6476             val_out->base.tag = SCOPE_LOCAL_VAL;
   6477             val_out->parent = *scope_out;
   6478             val_out->gen_zir = gz;
   6479             val_out->inst = init_ref;
   6480             val_out->token_src = name_token;
   6481             val_out->name = ident_name;
   6482             *scope_out = &val_out->base;
   6483         } else {
   6484             // Alloc path (AstGen.zig:3277-3340).
   6485             // The init expression needs a result pointer (nodes_need_rl).
   6486             bool is_comptime_init = gz->is_comptime
   6487                 || tree->nodes.tags[init_node] == AST_NODE_COMPTIME;
   6488 
   6489             uint32_t var_ptr;
   6490             bool resolve_inferred;
   6491 
   6492             if (type_node != 0) {
   6493                 // Typed const: alloc (AstGen.zig:3280).
   6494                 uint32_t type_ref = typeExpr(gz, scope, type_node);
   6495                 var_ptr = addUnNode(gz, ZIR_INST_ALLOC, type_ref, node);
   6496                 resolve_inferred = false;
   6497             } else {
   6498                 // Inferred type: alloc_inferred (AstGen.zig:3291-3296).
   6499                 ZirInstTag alloc_tag = is_comptime_init
   6500                     ? ZIR_INST_ALLOC_INFERRED_COMPTIME
   6501                     : ZIR_INST_ALLOC_INFERRED;
   6502                 ZirInstData adata;
   6503                 adata.node = (int32_t)node - (int32_t)gz->decl_node_index;
   6504                 var_ptr = addInstruction(gz, alloc_tag, adata);
   6505                 resolve_inferred = true;
   6506             }
   6507 
   6508             // Evaluate init with RL pointing to alloc (AstGen.zig:3313-3316).
   6509             ResultLoc init_rl;
   6510             if (type_node != 0) {
   6511                 init_rl.tag = RL_PTR;
   6512                 init_rl.data = var_ptr;
   6513                 init_rl.src_node = 0; // upstream: .none (PtrResultLoc.src_node
   6514                                       // defaults to null)
   6515             } else {
   6516                 init_rl.tag = RL_INFERRED_PTR;
   6517                 init_rl.data = var_ptr;
   6518                 init_rl.src_node = 0;
   6519             }
   6520             init_rl.ctx = RI_CTX_CONST_INIT;
   6521             uint32_t init_ref = exprRl(gz, scope, init_rl, init_node);
   6522 
   6523             if (ag->has_compile_errors)
   6524                 return;
   6525 
   6526             // save_err_ret_index (AstGen.zig:3320-3321).
   6527             if (nodeMayAppendToErrorTrace(tree, init_node))
   6528                 addSaveErrRetIndex(gz, init_ref);
   6529 
   6530             // resolve_inferred_alloc or make_ptr_const (AstGen.zig:3323-3326).
   6531             uint32_t const_ptr;
   6532             if (resolve_inferred)
   6533                 const_ptr = addUnNode(
   6534                     gz, ZIR_INST_RESOLVE_INFERRED_ALLOC, var_ptr, node);
   6535             else
   6536                 const_ptr
   6537                     = addUnNode(gz, ZIR_INST_MAKE_PTR_CONST, var_ptr, node);
   6538 
   6539             // dbg_var_ptr (AstGen.zig:3328).
   6540             addDbgVar(gz, ZIR_INST_DBG_VAR_PTR, ident_name, const_ptr);
   6541 
   6542             // Create ScopeLocalPtr (AstGen.zig:3330-3340).
   6543             ptr_out->base.tag = SCOPE_LOCAL_PTR;
   6544             ptr_out->parent = *scope_out;
   6545             ptr_out->gen_zir = gz;
   6546             ptr_out->ptr = const_ptr;
   6547             ptr_out->token_src = name_token;
   6548             ptr_out->name = ident_name;
   6549             ptr_out->maybe_comptime = true;
   6550             *scope_out = &ptr_out->base;
   6551         }
   6552     } else {
   6553         // --- VAR path (AstGen.zig:3342-3416) ---
   6554 
   6555         uint32_t alloc_ref;
   6556         bool resolve_inferred = false;
   6557 
   6558         if (type_node != 0) {
   6559             // Typed var: alloc_mut (AstGen.zig:3361-3375).
   6560             uint32_t type_ref = typeExpr(gz, scope, type_node);
   6561             ZirInstTag alloc_tag = gz->is_comptime
   6562                 ? ZIR_INST_ALLOC_COMPTIME_MUT
   6563                 : ZIR_INST_ALLOC_MUT;
   6564             alloc_ref = addUnNode(gz, alloc_tag, type_ref, node);
   6565         } else {
   6566             // Inferred type var: alloc_inferred_mut
   6567             // (AstGen.zig:3384-3392).
   6568             ZirInstTag alloc_tag = gz->is_comptime
   6569                 ? ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT
   6570                 : ZIR_INST_ALLOC_INFERRED_MUT;
   6571             ZirInstData adata;
   6572             adata.node = (int32_t)node - (int32_t)gz->decl_node_index;
   6573             alloc_ref = addInstruction(gz, alloc_tag, adata);
   6574             resolve_inferred = true;
   6575         }
   6576 
   6577         // Evaluate init with RL pointing to alloc (AstGen.zig:3395-3402).
   6578         ResultLoc var_init_rl;
   6579         if (type_node != 0) {
   6580             var_init_rl.tag = RL_PTR;
   6581             var_init_rl.data = alloc_ref;
   6582             var_init_rl.src_node = 0; // upstream: .none (PtrResultLoc.src_node
   6583                                       // defaults to null)
   6584         } else {
   6585             var_init_rl.tag = RL_INFERRED_PTR;
   6586             var_init_rl.data = alloc_ref;
   6587             var_init_rl.src_node = 0;
   6588         }
   6589         var_init_rl.ctx = RI_CTX_NONE;
   6590         uint32_t init_ref = exprRl(gz, scope, var_init_rl, init_node);
   6591         (void)init_ref;
   6592 
   6593         if (ag->has_compile_errors)
   6594             return;
   6595 
   6596         // resolve_inferred_alloc if type was inferred
   6597         // (AstGen.zig:3407-3408).
   6598         uint32_t final_ptr = alloc_ref;
   6599         if (resolve_inferred)
   6600             final_ptr = addUnNode(
   6601                 gz, ZIR_INST_RESOLVE_INFERRED_ALLOC, alloc_ref, node);
   6602 
   6603         // dbg_var_ptr (AstGen.zig:3411).
   6604         addDbgVar(gz, ZIR_INST_DBG_VAR_PTR, ident_name, final_ptr);
   6605 
   6606         // Create ScopeLocalPtr (AstGen.zig:3413-3422).
   6607         ptr_out->base.tag = SCOPE_LOCAL_PTR;
   6608         ptr_out->parent = *scope_out;
   6609         ptr_out->gen_zir = gz;
   6610         ptr_out->ptr = final_ptr;
   6611         ptr_out->token_src = name_token;
   6612         ptr_out->name = ident_name;
   6613         ptr_out->maybe_comptime = gz->is_comptime;
   6614         *scope_out = &ptr_out->base;
   6615     }
   6616 }
   6617 
   6618 // --- addEnsureResult (AstGen.zig:2649) ---
   6619 // After evaluating an expression as a statement, optionally emits
   6620 // ensure_result_used. For call/field_call, sets flag in extra data instead.
   6621 // Returns true if the result is noreturn (AstGen.zig:2909).
   6622 static bool addEnsureResult(
   6623     GenZir* gz, uint32_t maybe_unused_result, uint32_t statement) {
   6624     AstGenCtx* ag = gz->astgen;
   6625     bool elide_check;
   6626     bool is_noreturn = false;
   6627     if (maybe_unused_result >= ZIR_REF_START_INDEX) {
   6628         uint32_t inst = maybe_unused_result - ZIR_REF_START_INDEX;
   6629         ZirInstTag tag = ag->inst_tags[inst];
   6630         switch (tag) {
   6631         // For call/field_call: set ensure_result_used flag
   6632         // (bit 3 of flags at offset 0). Flags *must* be at offset 0
   6633         // (AstGen.zig:2658-2665, Zir.zig:3022).
   6634         case ZIR_INST_CALL:
   6635         case ZIR_INST_FIELD_CALL: {
   6636             uint32_t pi = ag->inst_datas[inst].pl_node.payload_index;
   6637             ag->extra[pi] |= (1u << 3); // ensure_result_used
   6638             elide_check = true;
   6639             break;
   6640         }
   6641         // For builtin_call: ensure_result_used is at bit 1, not bit 3.
   6642         case ZIR_INST_BUILTIN_CALL: {
   6643             uint32_t pi = ag->inst_datas[inst].pl_node.payload_index;
   6644             ag->extra[pi] |= (1u << 1); // ensure_result_used
   6645             elide_check = true;
   6646             break;
   6647         }
   6648         // Always noreturn → elide (AstGen.zig:2909).
   6649         case ZIR_INST_BREAK:
   6650         case ZIR_INST_BREAK_INLINE:
   6651         case ZIR_INST_CONDBR:
   6652         case ZIR_INST_CONDBR_INLINE:
   6653         case ZIR_INST_RET_NODE:
   6654         case ZIR_INST_RET_LOAD:
   6655         case ZIR_INST_RET_IMPLICIT:
   6656         case ZIR_INST_RET_ERR_VALUE:
   6657         case ZIR_INST_UNREACHABLE:
   6658         case ZIR_INST_REPEAT:
   6659         case ZIR_INST_REPEAT_INLINE:
   6660         case ZIR_INST_PANIC:
   6661         case ZIR_INST_TRAP:
   6662         case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW:
   6663         case ZIR_INST_SWITCH_CONTINUE:
   6664         case ZIR_INST_COMPILE_ERROR:
   6665             is_noreturn = true;
   6666             elide_check = true;
   6667             break;
   6668         // Always void → elide.
   6669         case ZIR_INST_DBG_STMT:
   6670         case ZIR_INST_DBG_VAR_PTR:
   6671         case ZIR_INST_DBG_VAR_VAL:
   6672         case ZIR_INST_ENSURE_RESULT_USED:
   6673         case ZIR_INST_ENSURE_RESULT_NON_ERROR:
   6674         case ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID:
   6675         case ZIR_INST_EXPORT:
   6676         case ZIR_INST_SET_EVAL_BRANCH_QUOTA:
   6677         case ZIR_INST_ATOMIC_STORE:
   6678         case ZIR_INST_STORE_NODE:
   6679         case ZIR_INST_STORE_TO_INFERRED_PTR:
   6680         case ZIR_INST_RESOLVE_INFERRED_ALLOC:
   6681         case ZIR_INST_SET_RUNTIME_SAFETY:
   6682         case ZIR_INST_MEMCPY:
   6683         case ZIR_INST_MEMSET:
   6684         case ZIR_INST_MEMMOVE:
   6685         case ZIR_INST_VALIDATE_DEREF:
   6686         case ZIR_INST_VALIDATE_DESTRUCTURE:
   6687         case ZIR_INST_SAVE_ERR_RET_INDEX:
   6688         case ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL:
   6689         case ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY:
   6690         case ZIR_INST_VALIDATE_STRUCT_INIT_TY:
   6691         case ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY:
   6692         case ZIR_INST_VALIDATE_PTR_STRUCT_INIT:
   6693         case ZIR_INST_VALIDATE_ARRAY_INIT_TY:
   6694         case ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY:
   6695         case ZIR_INST_VALIDATE_PTR_ARRAY_INIT:
   6696         case ZIR_INST_VALIDATE_REF_TY:
   6697         case ZIR_INST_VALIDATE_CONST:
   6698             elide_check = true;
   6699             break;
   6700         // Extended: check opcode.
   6701         case ZIR_INST_EXTENDED: {
   6702             uint32_t opcode = ag->inst_datas[inst].extended.opcode;
   6703             elide_check = (opcode == ZIR_EXT_BREAKPOINT
   6704                 || opcode == ZIR_EXT_BRANCH_HINT
   6705                 || opcode == ZIR_EXT_SET_FLOAT_MODE
   6706                 || opcode == ZIR_EXT_DISABLE_INSTRUMENTATION
   6707                 || opcode == ZIR_EXT_DISABLE_INTRINSICS);
   6708             break;
   6709         }
   6710         // Everything else: might produce non-void result → emit check.
   6711         default:
   6712             elide_check = false;
   6713             break;
   6714         }
   6715     } else {
   6716         // Named ref constant.
   6717         is_noreturn = (maybe_unused_result == ZIR_REF_UNREACHABLE_VALUE);
   6718         elide_check
   6719             = (is_noreturn || maybe_unused_result == ZIR_REF_VOID_VALUE);
   6720     }
   6721     if (!elide_check) {
   6722         addUnNode(
   6723             gz, ZIR_INST_ENSURE_RESULT_USED, maybe_unused_result, statement);
   6724     }
   6725     return is_noreturn;
   6726 }
   6727 
   6728 // --- countDefers (AstGen.zig:2966) ---
   6729 // Walk scope chain and count defer types.
   6730 
   6731 static DeferCounts countDefers(const Scope* outer_scope, Scope* inner_scope) {
   6732     DeferCounts c = { false, false, false, false };
   6733     Scope* s = inner_scope;
   6734     while (s != outer_scope) {
   6735         switch (s->tag) {
   6736         case SCOPE_GEN_ZIR:
   6737             s = ((GenZir*)s)->parent;
   6738             break;
   6739         case SCOPE_LOCAL_VAL:
   6740             s = ((ScopeLocalVal*)s)->parent;
   6741             break;
   6742         case SCOPE_LOCAL_PTR:
   6743             s = ((ScopeLocalPtr*)s)->parent;
   6744             break;
   6745         case SCOPE_DEFER_NORMAL: {
   6746             ScopeDefer* d = (ScopeDefer*)s;
   6747             s = d->parent;
   6748             c.have_normal = true;
   6749             break;
   6750         }
   6751         case SCOPE_DEFER_ERROR: {
   6752             ScopeDefer* d = (ScopeDefer*)s;
   6753             s = d->parent;
   6754             c.have_err = true;
   6755             // need_err_code if remapped_err_code exists (we don't
   6756             // implement err capture yet, so always false).
   6757             break;
   6758         }
   6759         default:
   6760             return c;
   6761         }
   6762     }
   6763     c.have_any = c.have_normal || c.have_err;
   6764     return c;
   6765 }
   6766 
   6767 // --- genDefers (AstGen.zig:3014) ---
   6768 // Walk scope chain from inner to outer, emitting .defer instructions.
   6769 // which: DEFER_NORMAL_ONLY or DEFER_BOTH_SANS_ERR.
   6770 
   6771 static void genDefers(
   6772     GenZir* gz, const Scope* outer_scope, Scope* inner_scope, int which) {
   6773     Scope* s = inner_scope;
   6774     while (s != outer_scope) {
   6775         switch (s->tag) {
   6776         case SCOPE_GEN_ZIR: {
   6777             GenZir* g = (GenZir*)s;
   6778             s = g->parent;
   6779             break;
   6780         }
   6781         case SCOPE_LOCAL_VAL: {
   6782             ScopeLocalVal* lv = (ScopeLocalVal*)s;
   6783             s = lv->parent;
   6784             break;
   6785         }
   6786         case SCOPE_LOCAL_PTR: {
   6787             ScopeLocalPtr* lp = (ScopeLocalPtr*)s;
   6788             s = lp->parent;
   6789             break;
   6790         }
   6791         case SCOPE_DEFER_NORMAL: {
   6792             ScopeDefer* d = (ScopeDefer*)s;
   6793             s = d->parent;
   6794             // Emit ZIR_INST_DEFER (AstGen.zig:3031).
   6795             ZirInstData data;
   6796             data.defer_data.index = d->index;
   6797             data.defer_data.len = d->len;
   6798             addInstruction(gz, ZIR_INST_DEFER, data);
   6799             break;
   6800         }
   6801         case SCOPE_DEFER_ERROR: {
   6802             ScopeDefer* d = (ScopeDefer*)s;
   6803             s = d->parent;
   6804             if (which == DEFER_BOTH_SANS_ERR) {
   6805                 // Emit regular DEFER for error defers too (AstGen.zig:3038).
   6806                 ZirInstData data;
   6807                 data.defer_data.index = d->index;
   6808                 data.defer_data.len = d->len;
   6809                 addInstruction(gz, ZIR_INST_DEFER, data);
   6810             }
   6811             // DEFER_NORMAL_ONLY: skip error defers (AstGen.zig:3063).
   6812             break;
   6813         }
   6814         case SCOPE_LABEL: {
   6815             // Labels store parent in the GenZir they're attached to.
   6816             // Just skip by going to the parent scope stored in parent.
   6817             // Actually labels don't have a separate parent pointer in our
   6818             // representation; they're part of GenZir. This case shouldn't
   6819             // appear when walking from blockExprStmts scope.
   6820             return;
   6821         }
   6822         case SCOPE_NAMESPACE:
   6823         case SCOPE_TOP:
   6824         default:
   6825             return;
   6826         }
   6827     }
   6828 }
   6829 
   6830 // --- blockExprStmts (AstGen.zig:2538) ---
   6831 // Processes block statements sequentially, threading scope.
   6832 
   6833 static void blockExprStmts(GenZir* gz, Scope* scope,
   6834     const uint32_t* statements, uint32_t stmt_count) {
   6835     AstGenCtx* ag = gz->astgen;
   6836     // Stack-allocated scope storage for local variables and defers.
   6837     // Max 64 local variable declarations and 64 defers per block.
   6838     ScopeLocalVal val_scopes[64];
   6839     ScopeLocalPtr ptr_scopes[64];
   6840     ScopeDefer defer_scopes[64];
   6841     uint32_t val_idx = 0;
   6842     uint32_t ptr_idx = 0;
   6843     uint32_t defer_idx = 0;
   6844     Scope* cur_scope = scope;
   6845     bool noreturn_stmt = false;
   6846 
   6847     for (uint32_t i = 0; i < stmt_count; i++) {
   6848         if (ag->has_compile_errors)
   6849             return;
   6850         uint32_t stmt = statements[i];
   6851         AstNodeTag tag = ag->tree->nodes.tags[stmt];
   6852         switch (tag) {
   6853         case AST_NODE_ASSIGN:
   6854             assignStmt(gz, cur_scope, stmt);
   6855             break;
   6856         // Compound assignment operators (AstGen.zig:2588-2607).
   6857         case AST_NODE_ASSIGN_ADD:
   6858             assignOp(gz, cur_scope, stmt, ZIR_INST_ADD);
   6859             break;
   6860         case AST_NODE_ASSIGN_SUB:
   6861             assignOp(gz, cur_scope, stmt, ZIR_INST_SUB);
   6862             break;
   6863         case AST_NODE_ASSIGN_MUL:
   6864             assignOp(gz, cur_scope, stmt, ZIR_INST_MUL);
   6865             break;
   6866         case AST_NODE_ASSIGN_DIV:
   6867             assignOp(gz, cur_scope, stmt, ZIR_INST_DIV);
   6868             break;
   6869         case AST_NODE_ASSIGN_MOD:
   6870             assignOp(gz, cur_scope, stmt, ZIR_INST_MOD_REM);
   6871             break;
   6872         case AST_NODE_ASSIGN_BIT_AND:
   6873             assignOp(gz, cur_scope, stmt, ZIR_INST_BIT_AND);
   6874             break;
   6875         case AST_NODE_ASSIGN_BIT_OR:
   6876             assignOp(gz, cur_scope, stmt, ZIR_INST_BIT_OR);
   6877             break;
   6878         case AST_NODE_ASSIGN_BIT_XOR:
   6879             assignOp(gz, cur_scope, stmt, ZIR_INST_XOR);
   6880             break;
   6881         case AST_NODE_ASSIGN_ADD_WRAP:
   6882             assignOp(gz, cur_scope, stmt, ZIR_INST_ADDWRAP);
   6883             break;
   6884         case AST_NODE_ASSIGN_SUB_WRAP:
   6885             assignOp(gz, cur_scope, stmt, ZIR_INST_SUBWRAP);
   6886             break;
   6887         case AST_NODE_ASSIGN_MUL_WRAP:
   6888             assignOp(gz, cur_scope, stmt, ZIR_INST_MULWRAP);
   6889             break;
   6890         case AST_NODE_ASSIGN_ADD_SAT:
   6891             assignOp(gz, cur_scope, stmt, ZIR_INST_ADD_SAT);
   6892             break;
   6893         case AST_NODE_ASSIGN_SUB_SAT:
   6894             assignOp(gz, cur_scope, stmt, ZIR_INST_SUB_SAT);
   6895             break;
   6896         case AST_NODE_ASSIGN_MUL_SAT:
   6897             assignOp(gz, cur_scope, stmt, ZIR_INST_MUL_SAT);
   6898             break;
   6899         case AST_NODE_SIMPLE_VAR_DECL:
   6900         case AST_NODE_LOCAL_VAR_DECL:
   6901         case AST_NODE_ALIGNED_VAR_DECL:
   6902             if (val_idx < 64 && ptr_idx < 64) {
   6903                 varDecl(gz, cur_scope, stmt, &val_scopes[val_idx],
   6904                     &ptr_scopes[ptr_idx], &cur_scope);
   6905                 // Check which one was used: if scope now points to
   6906                 // val_scopes[val_idx], advance val_idx; same for ptr.
   6907                 if (cur_scope == &val_scopes[val_idx].base)
   6908                     val_idx++;
   6909                 else if (cur_scope == &ptr_scopes[ptr_idx].base)
   6910                     ptr_idx++;
   6911             } else {
   6912                 SET_ERROR(ag);
   6913             }
   6914             break;
   6915         // defer/errdefer (AstGen.zig:2580-2581).
   6916         case AST_NODE_DEFER:
   6917         case AST_NODE_ERRDEFER: {
   6918             if (defer_idx >= 64) {
   6919                 SET_ERROR(ag);
   6920                 break;
   6921             }
   6922             ScopeTag scope_tag = (tag == AST_NODE_DEFER) ? SCOPE_DEFER_NORMAL
   6923                                                          : SCOPE_DEFER_ERROR;
   6924             // Create sub-block for defer body (AstGen.zig:3123-3126).
   6925             GenZir defer_gen = makeSubBlock(gz, cur_scope);
   6926 
   6927             // Evaluate deferred expression (AstGen.zig:3165).
   6928             // DEFER: lhs is the deferred expression, rhs = 0.
   6929             // ERRDEFER: lhs is optional error capture token, rhs is expr.
   6930             AstData dnd = ag->tree->nodes.datas[stmt];
   6931             uint32_t expr_node;
   6932             if (tag == AST_NODE_DEFER) {
   6933                 expr_node = dnd.lhs;
   6934             } else {
   6935                 expr_node = dnd.rhs;
   6936             }
   6937             // unusedResultExpr pattern (AstGen.zig:3165, 2641-2646).
   6938             emitDbgNode(&defer_gen, expr_node);
   6939             uint32_t defer_result
   6940                 = expr(&defer_gen, &defer_gen.base, expr_node);
   6941             addEnsureResult(&defer_gen, defer_result, expr_node);
   6942 
   6943             // Add break_inline at end (AstGen.zig:3167).
   6944             addBreak(&defer_gen, ZIR_INST_BREAK_INLINE, 0, ZIR_REF_VOID_VALUE,
   6945                 AST_NODE_OFFSET_NONE);
   6946 
   6947             // Write body to extra (AstGen.zig:3173-3175).
   6948             uint32_t raw_body_len = gzInstructionsLen(&defer_gen);
   6949             const uint32_t* body = gzInstructionsSlice(&defer_gen);
   6950             uint32_t extra_index = ag->extra_len;
   6951             uint32_t fixup_len
   6952                 = countBodyLenAfterFixups(ag, body, raw_body_len);
   6953             ensureExtraCapacity(ag, fixup_len);
   6954             for (uint32_t b = 0; b < raw_body_len; b++)
   6955                 appendPossiblyRefdBodyInst(ag, body[b]);
   6956             gzUnstack(&defer_gen);
   6957 
   6958             // Create scope (AstGen.zig:3179-3185).
   6959             defer_scopes[defer_idx] = (ScopeDefer) {
   6960                 .base = { .tag = scope_tag },
   6961                 .parent = cur_scope,
   6962                 .index = extra_index,
   6963                 .len = fixup_len,
   6964             };
   6965             cur_scope = &defer_scopes[defer_idx].base;
   6966             defer_idx++;
   6967             break;
   6968         }
   6969         // while/for as statements (AstGen.zig:2605-2610).
   6970         // These do NOT get emitDbgNode; they emit their own dbg_stmt.
   6971         case AST_NODE_WHILE_SIMPLE:
   6972         case AST_NODE_WHILE_CONT:
   6973         case AST_NODE_WHILE:
   6974             (void)whileExpr(gz, cur_scope, stmt, true);
   6975             break;
   6976         case AST_NODE_FOR_SIMPLE:
   6977         case AST_NODE_FOR:
   6978             (void)forExpr(gz, cur_scope, stmt, true);
   6979             break;
   6980         default: {
   6981             // Expression statement (AstGen.zig:2627 unusedResultExpr).
   6982             emitDbgNode(gz, stmt);
   6983             uint32_t result = expr(gz, cur_scope, stmt);
   6984             noreturn_stmt = addEnsureResult(gz, result, stmt);
   6985             break;
   6986         }
   6987         }
   6988     }
   6989     // Emit normal defers at block exit (AstGen.zig:2633-2634).
   6990     if (!noreturn_stmt) {
   6991         genDefers(gz, scope, cur_scope, DEFER_NORMAL_ONLY);
   6992     }
   6993 }
   6994 
   6995 // --- fullBodyExpr (AstGen.zig:2358) ---
   6996 // Processes a body expression. If it's an unlabeled block, processes
   6997 // statements inline without creating a BLOCK instruction (unlike blockExprExpr
   6998 // which wraps in BLOCK). Returns the result ref.
   6999 
   7000 static uint32_t fullBodyExpr(
   7001     GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
   7002     const Ast* tree = gz->astgen->tree;
   7003     AstNodeTag tag = tree->nodes.tags[node];
   7004 
   7005     // Extract block statements (AstGen.zig:2368).
   7006     AstData nd = tree->nodes.datas[node];
   7007     uint32_t stmt_buf[2];
   7008     const uint32_t* statements = NULL;
   7009     uint32_t stmt_count = 0;
   7010 
   7011     switch (tag) {
   7012     case AST_NODE_BLOCK_TWO:
   7013     case AST_NODE_BLOCK_TWO_SEMICOLON: {
   7014         uint32_t idx = 0;
   7015         if (nd.lhs != 0)
   7016             stmt_buf[idx++] = nd.lhs;
   7017         if (nd.rhs != 0)
   7018             stmt_buf[idx++] = nd.rhs;
   7019         statements = stmt_buf;
   7020         stmt_count = idx;
   7021         break;
   7022     }
   7023     case AST_NODE_BLOCK:
   7024     case AST_NODE_BLOCK_SEMICOLON: {
   7025         uint32_t start = nd.lhs;
   7026         uint32_t end = nd.rhs;
   7027         statements = tree->extra_data.arr + start;
   7028         stmt_count = end - start;
   7029         break;
   7030     }
   7031     default:
   7032         // Not a block — treat as single expression (AstGen.zig:2369).
   7033         return exprRl(gz, scope, rl, node);
   7034     }
   7035 
   7036     // Check if labeled (AstGen.zig:2373-2377).
   7037     uint32_t lbrace = tree->nodes.main_tokens[node];
   7038     bool is_labeled
   7039         = (lbrace >= 2 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON
   7040             && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER);
   7041     if (is_labeled) {
   7042         // Labeled blocks need a proper block instruction.
   7043         return blockExprExpr(gz, scope, rl, node);
   7044     }
   7045 
   7046     // Unlabeled block: process statements inline (AstGen.zig:2380-2383).
   7047     GenZir sub_gz = makeSubBlock(gz, scope);
   7048     blockExprStmts(&sub_gz, &sub_gz.base, statements, stmt_count);
   7049     return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node);
   7050 }
   7051 
   7052 // --- lastToken (Ast.zig:874) ---
   7053 // Mechanical port of Ast.lastToken. Uses iterative end_offset accumulation.
   7054 
   7055 static uint32_t lastToken(const Ast* tree, uint32_t node) {
   7056     uint32_t n = node;
   7057     uint32_t end_offset = 0;
   7058     while (1) {
   7059         AstNodeTag tag = tree->nodes.tags[n];
   7060         AstData nd = tree->nodes.datas[n];
   7061         switch (tag) {
   7062         case AST_NODE_ROOT:
   7063             return tree->tokens.len - 1;
   7064 
   7065         // Binary ops: recurse into RHS (Ast.zig:893-948).
   7066         case AST_NODE_ASSIGN:
   7067         case AST_NODE_ADD:
   7068         case AST_NODE_SUB:
   7069         case AST_NODE_MUL:
   7070         case AST_NODE_DIV:
   7071         case AST_NODE_MOD:
   7072         case AST_NODE_BIT_AND:
   7073         case AST_NODE_BIT_OR:
   7074         case AST_NODE_BIT_XOR:
   7075         case AST_NODE_SHL:
   7076         case AST_NODE_SHR:
   7077         case AST_NODE_ARRAY_CAT:
   7078         case AST_NODE_ARRAY_MULT:
   7079         case AST_NODE_ADD_WRAP:
   7080         case AST_NODE_SUB_WRAP:
   7081         case AST_NODE_ADD_SAT:
   7082         case AST_NODE_SUB_SAT:
   7083         case AST_NODE_MUL_WRAP:
   7084         case AST_NODE_MUL_SAT:
   7085         case AST_NODE_MERGE_ERROR_SETS:
   7086         case AST_NODE_EQUAL_EQUAL:
   7087         case AST_NODE_BANG_EQUAL:
   7088         case AST_NODE_LESS_THAN:
   7089         case AST_NODE_GREATER_THAN:
   7090         case AST_NODE_LESS_OR_EQUAL:
   7091         case AST_NODE_GREATER_OR_EQUAL:
   7092         case AST_NODE_BOOL_AND:
   7093         case AST_NODE_BOOL_OR:
   7094         case AST_NODE_ORELSE:
   7095         case AST_NODE_CATCH:
   7096         case AST_NODE_ERROR_UNION:
   7097         case AST_NODE_SHL_SAT:
   7098             n = nd.rhs;
   7099             continue;
   7100 
   7101         // field_access: return field token + end_offset (Ast.zig:979).
   7102         case AST_NODE_FIELD_ACCESS:
   7103             return nd.rhs + end_offset;
   7104 
   7105         // test_decl: recurse into body node (Ast.zig:950).
   7106         case AST_NODE_TEST_DECL:
   7107             n = nd.rhs;
   7108             continue;
   7109 
   7110         // defer: recurse into body (lhs) (Ast.zig:951).
   7111         case AST_NODE_DEFER:
   7112             n = nd.lhs;
   7113             continue;
   7114 
   7115         // errdefer: recurse into body (rhs) (Ast.zig:950).
   7116         case AST_NODE_ERRDEFER:
   7117             n = nd.rhs;
   7118             continue;
   7119 
   7120         // block (Ast.zig:1085): end_offset += 1 (rbrace), recurse into last.
   7121         case AST_NODE_BLOCK: {
   7122             uint32_t start = nd.lhs;
   7123             uint32_t end = nd.rhs;
   7124             assert(start != end);
   7125             end_offset += 1;
   7126             n = tree->extra_data.arr[end - 1];
   7127             continue;
   7128         }
   7129 
   7130         // block_semicolon (Ast.zig:1097): += 2 (semicolon + rbrace).
   7131         case AST_NODE_BLOCK_SEMICOLON: {
   7132             uint32_t start = nd.lhs;
   7133             uint32_t end = nd.rhs;
   7134             assert(start != end);
   7135             end_offset += 2;
   7136             n = tree->extra_data.arr[end - 1];
   7137             continue;
   7138         }
   7139 
   7140         // block_two (Ast.zig:1117): if rhs, recurse rhs +1; if lhs, +1; else
   7141         // +1. Note: C parser uses 0 for "none" (OptionalIndex), not
   7142         // UINT32_MAX.
   7143         case AST_NODE_BLOCK_TWO: {
   7144             if (nd.rhs != 0) {
   7145                 end_offset += 1;
   7146                 n = nd.rhs;
   7147             } else if (nd.lhs != 0) {
   7148                 end_offset += 1;
   7149                 n = nd.lhs;
   7150             } else {
   7151                 end_offset += 1;
   7152                 return tree->nodes.main_tokens[n] + end_offset;
   7153             }
   7154             continue;
   7155         }
   7156 
   7157         // block_two_semicolon (Ast.zig:1153).
   7158         case AST_NODE_BLOCK_TWO_SEMICOLON: {
   7159             if (nd.rhs != 0) {
   7160                 end_offset += 2;
   7161                 n = nd.rhs;
   7162             } else if (nd.lhs != 0) {
   7163                 end_offset += 2;
   7164                 n = nd.lhs;
   7165             } else {
   7166                 end_offset += 1;
   7167                 return tree->nodes.main_tokens[n] + end_offset;
   7168             }
   7169             continue;
   7170         }
   7171 
   7172         // builtin_call_two (Ast.zig:1118): recurse into args + rparen.
   7173         case AST_NODE_BUILTIN_CALL_TWO: {
   7174             if (nd.rhs != 0) {
   7175                 end_offset += 1;
   7176                 n = nd.rhs;
   7177             } else if (nd.lhs != 0) {
   7178                 end_offset += 1;
   7179                 n = nd.lhs;
   7180             } else {
   7181                 end_offset += 2; // lparen + rparen
   7182                 return tree->nodes.main_tokens[n] + end_offset;
   7183             }
   7184             continue;
   7185         }
   7186 
   7187         case AST_NODE_BUILTIN_CALL_TWO_COMMA: {
   7188             if (nd.rhs != 0) {
   7189                 end_offset += 2; // comma + rparen
   7190                 n = nd.rhs;
   7191             } else if (nd.lhs != 0) {
   7192                 end_offset += 2;
   7193                 n = nd.lhs;
   7194             } else {
   7195                 end_offset += 1;
   7196                 return tree->nodes.main_tokens[n] + end_offset;
   7197             }
   7198             continue;
   7199         }
   7200 
   7201         // Unary ops: recurse into lhs (Ast.zig:895-910).
   7202         case AST_NODE_BOOL_NOT:
   7203         case AST_NODE_BIT_NOT:
   7204         case AST_NODE_NEGATION:
   7205         case AST_NODE_NEGATION_WRAP:
   7206         case AST_NODE_ADDRESS_OF:
   7207         case AST_NODE_TRY:
   7208         case AST_NODE_AWAIT:
   7209         case AST_NODE_OPTIONAL_TYPE:
   7210         case AST_NODE_COMPTIME:
   7211         case AST_NODE_NOSUSPEND:
   7212         case AST_NODE_RESUME:
   7213             n = nd.lhs;
   7214             continue;
   7215 
   7216         // return: optional operand (Ast.zig:998-1002).
   7217         case AST_NODE_RETURN:
   7218             if (nd.lhs != 0) {
   7219                 n = nd.lhs;
   7220                 continue;
   7221             }
   7222             return tree->nodes.main_tokens[n] + end_offset;
   7223 
   7224         // deref: main_token is the dot, +1 for '*' (Ast.zig:974).
   7225         case AST_NODE_DEREF:
   7226             return tree->nodes.main_tokens[n] + 1 + end_offset;
   7227 
   7228         // unwrap_optional: +1 for '?' (Ast.zig:971).
   7229         case AST_NODE_UNWRAP_OPTIONAL:
   7230             return tree->nodes.main_tokens[n] + 1 + end_offset;
   7231 
   7232         // for_range: recurse into rhs if present, else lhs.
   7233         case AST_NODE_FOR_RANGE:
   7234             if (nd.rhs != 0) {
   7235                 n = nd.rhs;
   7236             } else {
   7237                 // Unbounded range: last token is the '..' operator.
   7238                 // main_token + 1 (the second dot of ..)
   7239                 return tree->nodes.main_tokens[n] + 1 + end_offset;
   7240             }
   7241             continue;
   7242 
   7243         // error_value: main_token is `error`, last token is name (+2)
   7244         // (Ast.zig:986).
   7245         case AST_NODE_ERROR_VALUE:
   7246             return tree->nodes.main_tokens[n] + 2 + end_offset;
   7247 
   7248         // Terminals: return main_token + end_offset (Ast.zig:988-996).
   7249         case AST_NODE_NUMBER_LITERAL:
   7250         case AST_NODE_STRING_LITERAL:
   7251         case AST_NODE_IDENTIFIER:
   7252         case AST_NODE_ENUM_LITERAL:
   7253         case AST_NODE_CHAR_LITERAL:
   7254         case AST_NODE_UNREACHABLE_LITERAL:
   7255         case AST_NODE_ANYFRAME_LITERAL:
   7256             return tree->nodes.main_tokens[n] + end_offset;
   7257 
   7258         // call_one: recurse into lhs, +1 for ')'.
   7259         case AST_NODE_CALL_ONE:
   7260             end_offset += 1; // rparen
   7261             if (nd.rhs != 0) {
   7262                 n = nd.rhs;
   7263             } else {
   7264                 n = nd.lhs;
   7265             }
   7266             continue;
   7267         case AST_NODE_CALL_ONE_COMMA:
   7268             end_offset += 2; // comma + rparen
   7269             if (nd.rhs != 0) {
   7270                 n = nd.rhs;
   7271             } else {
   7272                 n = nd.lhs;
   7273             }
   7274             continue;
   7275 
   7276         // array_access: end_offset += 1 (rbracket), recurse rhs.
   7277         case AST_NODE_ARRAY_ACCESS:
   7278             end_offset += 1;
   7279             n = nd.rhs;
   7280             continue;
   7281 
   7282         // simple_var_decl: recurse into init/type (Ast.zig:1169-1178).
   7283         case AST_NODE_SIMPLE_VAR_DECL:
   7284             if (nd.rhs != 0) {
   7285                 n = nd.rhs; // init expr
   7286             } else if (nd.lhs != 0) {
   7287                 n = nd.lhs; // type expr
   7288             } else {
   7289                 end_offset += 1; // from mut token to name
   7290                 return tree->nodes.main_tokens[n] + end_offset;
   7291             }
   7292             continue;
   7293 
   7294         // aligned_var_decl: recurse into init/align (Ast.zig:1180-1187).
   7295         case AST_NODE_ALIGNED_VAR_DECL:
   7296             if (nd.rhs != 0) {
   7297                 n = nd.rhs; // init expr
   7298             } else {
   7299                 end_offset += 1; // rparen
   7300                 n = nd.lhs; // align expr
   7301             }
   7302             continue;
   7303 
   7304         // local_var_decl (Ast.zig:1209-1217).
   7305         case AST_NODE_LOCAL_VAR_DECL:
   7306             if (nd.rhs != 0) {
   7307                 n = nd.rhs; // init expr
   7308             } else {
   7309                 // extra[lhs] has align_node
   7310                 end_offset += 1; // rparen
   7311                 n = tree->extra_data.arr[nd.lhs]; // align_node
   7312             }
   7313             continue;
   7314 
   7315         // global_var_decl (Ast.zig:1189-1207).
   7316         case AST_NODE_GLOBAL_VAR_DECL:
   7317             if (nd.rhs != 0) {
   7318                 n = nd.rhs; // init expr
   7319             } else {
   7320                 // extra[lhs] = {type_node, align_node, ...}
   7321                 // complex; approximate by using main_token
   7322                 end_offset += 1;
   7323                 return tree->nodes.main_tokens[n] + end_offset;
   7324             }
   7325             continue;
   7326 
   7327         // slice_open: end_offset += 2 (ellipsis2 + rbracket), recurse rhs
   7328         // (Ast.zig:1245-1248).
   7329         case AST_NODE_SLICE_OPEN:
   7330             end_offset += 2;
   7331             n = nd.rhs;
   7332             continue;
   7333 
   7334         // grouped_expression: end_offset += 1 (rparen), recurse lhs.
   7335         case AST_NODE_GROUPED_EXPRESSION:
   7336             end_offset += 1;
   7337             n = nd.lhs;
   7338             continue;
   7339 
   7340         // if_simple: recurse into body (rhs) (Ast.zig:942).
   7341         case AST_NODE_IF_SIMPLE:
   7342         case AST_NODE_WHILE_SIMPLE:
   7343         case AST_NODE_FOR_SIMPLE:
   7344         case AST_NODE_FN_DECL:
   7345         case AST_NODE_ARRAY_TYPE:
   7346             n = nd.rhs;
   7347             continue;
   7348 
   7349         // if: recurse into else_expr (Ast.zig:1295).
   7350         case AST_NODE_IF: {
   7351             // If[rhs]: { then_expr, else_expr }
   7352             n = tree->extra_data.arr[nd.rhs + 1]; // else_expr
   7353             continue;
   7354         }
   7355 
   7356         // while: recurse into else_expr (Ast.zig:1290).
   7357         case AST_NODE_WHILE: {
   7358             // While[rhs]: { cont_expr, then_expr, else_expr }
   7359             n = tree->extra_data.arr[nd.rhs + 2]; // else_expr
   7360             continue;
   7361         }
   7362 
   7363         // while_cont: recurse into then_expr (Ast.zig:943-like).
   7364         case AST_NODE_WHILE_CONT: {
   7365             // WhileCont[rhs]: { cont_expr, then_expr }
   7366             n = tree->extra_data.arr[nd.rhs + 1]; // then_expr
   7367             continue;
   7368         }
   7369 
   7370         // switch: recurse into last case (Ast.zig:1031-1041).
   7371         case AST_NODE_SWITCH: {
   7372             uint32_t ei = nd.rhs;
   7373             uint32_t cs = tree->extra_data.arr[ei];
   7374             uint32_t ce = tree->extra_data.arr[ei + 1];
   7375             if (cs == ce) {
   7376                 end_offset += 3; // rparen, lbrace, rbrace
   7377                 n = nd.lhs;
   7378             } else {
   7379                 end_offset += 1; // rbrace
   7380                 n = tree->extra_data.arr[ce - 1];
   7381             }
   7382             continue;
   7383         }
   7384         case AST_NODE_SWITCH_COMMA: {
   7385             uint32_t ei = nd.rhs;
   7386             uint32_t cs = tree->extra_data.arr[ei];
   7387             uint32_t ce = tree->extra_data.arr[ei + 1];
   7388             assert(cs != ce);
   7389             end_offset += 2; // comma + rbrace
   7390             n = tree->extra_data.arr[ce - 1];
   7391             continue;
   7392         }
   7393 
   7394         // switch_case_one: recurse into rhs (body) (Ast.zig:942).
   7395         case AST_NODE_SWITCH_CASE_ONE:
   7396         case AST_NODE_SWITCH_CASE_INLINE_ONE:
   7397         case AST_NODE_SWITCH_CASE:
   7398         case AST_NODE_SWITCH_CASE_INLINE:
   7399             n = nd.rhs;
   7400             continue;
   7401 
   7402         // switch_range: recurse into rhs (Ast.zig: binary op pattern).
   7403         case AST_NODE_SWITCH_RANGE:
   7404             n = nd.rhs;
   7405             continue;
   7406 
   7407         // struct_init_one: recurse into field if present, +1.
   7408         case AST_NODE_STRUCT_INIT_ONE:
   7409             end_offset += 1; // rbrace
   7410             if (nd.rhs != 0) {
   7411                 n = nd.rhs;
   7412             } else {
   7413                 return tree->nodes.main_tokens[n] + end_offset;
   7414             }
   7415             continue;
   7416         case AST_NODE_STRUCT_INIT_ONE_COMMA:
   7417             end_offset += 2; // comma + rbrace
   7418             n = nd.rhs;
   7419             continue;
   7420 
   7421         // struct_init_dot_two: similar to block_two.
   7422         case AST_NODE_STRUCT_INIT_DOT_TWO:
   7423             if (nd.rhs != 0) {
   7424                 end_offset += 1;
   7425                 n = nd.rhs;
   7426             } else if (nd.lhs != 0) {
   7427                 end_offset += 1;
   7428                 n = nd.lhs;
   7429             } else {
   7430                 end_offset += 1; // rbrace
   7431                 return tree->nodes.main_tokens[n] + end_offset;
   7432             }
   7433             continue;
   7434         case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA:
   7435             end_offset += 2;
   7436             if (nd.rhs != 0) {
   7437                 n = nd.rhs;
   7438             } else {
   7439                 n = nd.lhs;
   7440             }
   7441             continue;
   7442 
   7443         // struct_init_dot: SubRange pattern.
   7444         case AST_NODE_STRUCT_INIT_DOT:
   7445             assert(nd.lhs != nd.rhs);
   7446             end_offset += 1;
   7447             n = tree->extra_data.arr[nd.rhs - 1];
   7448             continue;
   7449 
   7450         // struct_init: node_and_extra SubRange pattern.
   7451         case AST_NODE_STRUCT_INIT: {
   7452             uint32_t si = tree->extra_data.arr[nd.rhs];
   7453             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7454             assert(si != se);
   7455             end_offset += 1;
   7456             n = tree->extra_data.arr[se - 1];
   7457             continue;
   7458         }
   7459 
   7460         // call: SubRange pattern.
   7461         case AST_NODE_CALL: {
   7462             uint32_t si = tree->extra_data.arr[nd.rhs];
   7463             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7464             assert(si != se);
   7465             end_offset += 1;
   7466             n = tree->extra_data.arr[se - 1];
   7467             continue;
   7468         }
   7469         case AST_NODE_CALL_COMMA: {
   7470             uint32_t si = tree->extra_data.arr[nd.rhs];
   7471             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7472             assert(si != se);
   7473             end_offset += 2;
   7474             n = tree->extra_data.arr[se - 1];
   7475             continue;
   7476         }
   7477 
   7478         // fn_proto_simple: recurse into rhs (return type).
   7479         case AST_NODE_FN_PROTO_SIMPLE:
   7480         case AST_NODE_FN_PROTO_ONE:
   7481         case AST_NODE_FN_PROTO_MULTI:
   7482         case AST_NODE_FN_PROTO:
   7483             n = nd.rhs;
   7484             continue;
   7485 
   7486         // error_set_decl: rhs is the closing rbrace token.
   7487         case AST_NODE_ERROR_SET_DECL:
   7488             return nd.rhs + end_offset;
   7489 
   7490         // ptr_type variants: recurse into rhs (child type).
   7491         case AST_NODE_PTR_TYPE_ALIGNED:
   7492         case AST_NODE_PTR_TYPE_SENTINEL:
   7493         case AST_NODE_PTR_TYPE:
   7494         case AST_NODE_PTR_TYPE_BIT_RANGE:
   7495             n = nd.rhs;
   7496             continue;
   7497 
   7498         // container_decl: extra_range pattern.
   7499         case AST_NODE_CONTAINER_DECL:
   7500         case AST_NODE_TAGGED_UNION:
   7501             assert(nd.lhs != nd.rhs);
   7502             end_offset += 1;
   7503             n = tree->extra_data.arr[nd.rhs - 1];
   7504             continue;
   7505         case AST_NODE_CONTAINER_DECL_TRAILING:
   7506         case AST_NODE_TAGGED_UNION_TRAILING:
   7507             assert(nd.lhs != nd.rhs);
   7508             end_offset += 2;
   7509             n = tree->extra_data.arr[nd.rhs - 1];
   7510             continue;
   7511 
   7512         // container_decl_two: like block_two.
   7513         case AST_NODE_CONTAINER_DECL_TWO:
   7514         case AST_NODE_TAGGED_UNION_TWO:
   7515             if (nd.rhs != 0) {
   7516                 end_offset += 1;
   7517                 n = nd.rhs;
   7518             } else if (nd.lhs != 0) {
   7519                 end_offset += 1;
   7520                 n = nd.lhs;
   7521             } else {
   7522                 end_offset += 2; // lbrace + rbrace
   7523                 return tree->nodes.main_tokens[n] + end_offset;
   7524             }
   7525             continue;
   7526         case AST_NODE_CONTAINER_DECL_TWO_TRAILING:
   7527         case AST_NODE_TAGGED_UNION_TWO_TRAILING:
   7528             end_offset += 2;
   7529             if (nd.rhs != 0) {
   7530                 n = nd.rhs;
   7531             } else {
   7532                 n = nd.lhs;
   7533             }
   7534             continue;
   7535 
   7536         // container_decl_arg: node_and_extra SubRange.
   7537         case AST_NODE_CONTAINER_DECL_ARG: {
   7538             uint32_t si = tree->extra_data.arr[nd.rhs];
   7539             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7540             if (si == se) {
   7541                 end_offset += 3; // rparen + lbrace + rbrace
   7542                 n = nd.lhs;
   7543             } else {
   7544                 end_offset += 1;
   7545                 n = tree->extra_data.arr[se - 1];
   7546             }
   7547             continue;
   7548         }
   7549         case AST_NODE_CONTAINER_DECL_ARG_TRAILING: {
   7550             uint32_t si = tree->extra_data.arr[nd.rhs];
   7551             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7552             assert(si != se);
   7553             end_offset += 2;
   7554             n = tree->extra_data.arr[se - 1];
   7555             continue;
   7556         }
   7557 
   7558         // slice: extra data pattern.
   7559         case AST_NODE_SLICE: {
   7560             // Slice[rhs]: { start, end }
   7561             end_offset += 1;
   7562             n = tree->extra_data.arr[nd.rhs + 1]; // end
   7563             continue;
   7564         }
   7565         case AST_NODE_SLICE_SENTINEL: {
   7566             // SliceSentinel[rhs]: { start, end, sentinel }
   7567             end_offset += 1;
   7568             n = tree->extra_data.arr[nd.rhs + 2]; // sentinel
   7569             continue;
   7570         }
   7571 
   7572         // array_type_sentinel: extra data.
   7573         case AST_NODE_ARRAY_TYPE_SENTINEL: {
   7574             // ArrayTypeSentinel[rhs]: { sentinel, elem_type }
   7575             n = tree->extra_data.arr[nd.rhs + 1]; // elem_type
   7576             continue;
   7577         }
   7578 
   7579         // multiline_string_literal: main_token + end_offset.
   7580         case AST_NODE_MULTILINE_STRING_LITERAL:
   7581             return nd.rhs + end_offset;
   7582 
   7583         // break/continue (Ast.zig:1275-1283).
   7584         // lhs is opt_token (null_token = UINT32_MAX), rhs is opt_node (0 =
   7585         // none).
   7586         case AST_NODE_BREAK:
   7587         case AST_NODE_CONTINUE:
   7588             if (nd.rhs != 0) {
   7589                 n = nd.rhs; // optional rhs expression
   7590             } else if (nd.lhs != UINT32_MAX) {
   7591                 return nd.lhs + end_offset; // label token
   7592             } else {
   7593                 return tree->nodes.main_tokens[n] + end_offset;
   7594             }
   7595             continue;
   7596 
   7597         // array_init_one: end_offset += 1 (rbrace), recurse rhs
   7598         // (Ast.zig:1224-1230).
   7599         case AST_NODE_ARRAY_INIT_ONE:
   7600             end_offset += 1;
   7601             n = nd.rhs;
   7602             continue;
   7603 
   7604         case AST_NODE_ARRAY_INIT_ONE_COMMA:
   7605             end_offset += 2; // comma + rbrace
   7606             n = nd.rhs;
   7607             continue;
   7608 
   7609         // struct_init_dot_comma: SubRange pattern.
   7610         case AST_NODE_STRUCT_INIT_DOT_COMMA:
   7611             assert(nd.lhs != nd.rhs);
   7612             end_offset += 2; // comma + rbrace
   7613             n = tree->extra_data.arr[nd.rhs - 1];
   7614             continue;
   7615 
   7616         // struct_init_comma: node_and_extra SubRange.
   7617         case AST_NODE_STRUCT_INIT_COMMA: {
   7618             uint32_t si = tree->extra_data.arr[nd.rhs];
   7619             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7620             assert(si != se);
   7621             end_offset += 2;
   7622             n = tree->extra_data.arr[se - 1];
   7623             continue;
   7624         }
   7625 
   7626         // array_init variants.
   7627         case AST_NODE_ARRAY_INIT: {
   7628             uint32_t si = tree->extra_data.arr[nd.rhs];
   7629             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7630             assert(si != se);
   7631             end_offset += 1;
   7632             n = tree->extra_data.arr[se - 1];
   7633             continue;
   7634         }
   7635         case AST_NODE_ARRAY_INIT_COMMA: {
   7636             uint32_t si = tree->extra_data.arr[nd.rhs];
   7637             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7638             assert(si != se);
   7639             end_offset += 2;
   7640             n = tree->extra_data.arr[se - 1];
   7641             continue;
   7642         }
   7643 
   7644         // array_init_dot variants.
   7645         case AST_NODE_ARRAY_INIT_DOT_TWO:
   7646             if (nd.rhs != 0) {
   7647                 end_offset += 1;
   7648                 n = nd.rhs;
   7649             } else if (nd.lhs != 0) {
   7650                 end_offset += 1;
   7651                 n = nd.lhs;
   7652             } else {
   7653                 end_offset += 1;
   7654                 return tree->nodes.main_tokens[n] + end_offset;
   7655             }
   7656             continue;
   7657         case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA:
   7658             end_offset += 2;
   7659             if (nd.rhs != 0) {
   7660                 n = nd.rhs;
   7661             } else {
   7662                 n = nd.lhs;
   7663             }
   7664             continue;
   7665         case AST_NODE_ARRAY_INIT_DOT:
   7666             assert(nd.lhs != nd.rhs);
   7667             end_offset += 1;
   7668             n = tree->extra_data.arr[nd.rhs - 1];
   7669             continue;
   7670         case AST_NODE_ARRAY_INIT_DOT_COMMA:
   7671             assert(nd.lhs != nd.rhs);
   7672             end_offset += 2;
   7673             n = tree->extra_data.arr[nd.rhs - 1];
   7674             continue;
   7675 
   7676         // builtin_call (Ast.zig:1083-1105).
   7677         case AST_NODE_BUILTIN_CALL: {
   7678             uint32_t si = tree->extra_data.arr[nd.rhs];
   7679             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7680             assert(si != se);
   7681             end_offset += 1;
   7682             n = tree->extra_data.arr[se - 1];
   7683             continue;
   7684         }
   7685         case AST_NODE_BUILTIN_CALL_COMMA: {
   7686             uint32_t si = tree->extra_data.arr[nd.rhs];
   7687             uint32_t se = tree->extra_data.arr[nd.rhs + 1];
   7688             assert(si != se);
   7689             end_offset += 2;
   7690             n = tree->extra_data.arr[se - 1];
   7691             continue;
   7692         }
   7693 
   7694         // for (Ast.zig:1300-1303): complex extra data.
   7695         case AST_NODE_FOR: {
   7696             // lhs = span.start (extra_data index),
   7697             // rhs = packed(inputs:u31, has_else:u1 at bit 31).
   7698             // extra[lhs..] = input nodes, then_body, [else_body].
   7699             uint32_t span_start = nd.lhs;
   7700             uint32_t for_packed = nd.rhs;
   7701             uint32_t inputs = for_packed & 0x7FFFFFFFu;
   7702             bool has_else = (for_packed >> 31) != 0;
   7703             uint32_t last_idx = span_start + inputs + (has_else ? 1 : 0);
   7704             n = tree->extra_data.arr[last_idx];
   7705             continue;
   7706         }
   7707 
   7708         default:
   7709             // Fallback: return main_token + end_offset.
   7710             return tree->nodes.main_tokens[n] + end_offset;
   7711         }
   7712     }
   7713 }
   7714 
   7715 // --- addParam (AstGen.zig:12390) ---
   7716 // Creates a param instruction with pl_tok data and type body in extra.
   7717 
   7718 static uint32_t addParam(GenZir* gz, GenZir* param_gz, ZirInstTag tag,
   7719     uint32_t abs_tok_index, uint32_t name) {
   7720     AstGenCtx* ag = gz->astgen;
   7721 
   7722     uint32_t body_len = gzInstructionsLen(param_gz);
   7723     const uint32_t* param_body = gzInstructionsSlice(param_gz);
   7724 
   7725     // Param payload: name, type{body_len:u31|is_generic:u1}
   7726     ensureExtraCapacity(ag, 2 + body_len);
   7727     uint32_t payload_index = ag->extra_len;
   7728     ag->extra[ag->extra_len++] = name;
   7729     ag->extra[ag->extra_len++] = body_len & 0x7FFFFFFFu; // is_generic = false
   7730     for (uint32_t i = 0; i < body_len; i++) {
   7731         ag->extra[ag->extra_len++] = param_body[i];
   7732     }
   7733     gzUnstack(param_gz);
   7734 
   7735     // Emit the param instruction.
   7736     ensureInstCapacity(ag, 1);
   7737     uint32_t idx = ag->inst_len;
   7738     ag->inst_tags[idx] = tag;
   7739     ZirInstData data;
   7740     data.pl_tok.src_tok = tokenIndexToRelative(gz, abs_tok_index);
   7741     data.pl_tok.payload_index = payload_index;
   7742     ag->inst_datas[idx] = data;
   7743     ag->inst_len++;
   7744     gzAppendInstruction(gz, idx);
   7745     return idx;
   7746 }
   7747 
   7748 // --- addDbgVar (AstGen.zig:13196) ---
   7749 
   7750 static void addDbgVar(
   7751     GenZir* gz, ZirInstTag tag, uint32_t name, uint32_t inst) {
   7752     if (gz->is_comptime)
   7753         return;
   7754     ZirInstData data;
   7755     data.str_op.str = name;
   7756     data.str_op.operand = inst;
   7757     addInstruction(gz, tag, data);
   7758 }
   7759 
   7760 // --- addFunc (AstGen.zig:12023) ---
   7761 // Handles non-fancy func/func_inferred instructions.
   7762 // ret_body/ret_body_len: instructions for the return type sub-block (may be
   7763 // 0). ret_ref: if ret_body_len==0, the return type as a simple Ref.
   7764 
   7765 static uint32_t addFunc(GenZir* gz, uint32_t src_node, uint32_t block_node,
   7766     uint32_t param_block, uint32_t ret_ref, const uint32_t* ret_body,
   7767     uint32_t ret_body_len, const uint32_t* body, uint32_t body_len,
   7768     const uint32_t* param_insts, uint32_t param_insts_len,
   7769     uint32_t lbrace_line, uint32_t lbrace_column, bool is_inferred_error) {
   7770     AstGenCtx* ag = gz->astgen;
   7771     const Ast* tree = ag->tree;
   7772     uint32_t rbrace_tok = lastToken(tree, block_node);
   7773     uint32_t rbrace_start = tree->tokens.starts[rbrace_tok];
   7774     advanceSourceCursor(ag, rbrace_start);
   7775     uint32_t rbrace_line = ag->source_line - gz->decl_line;
   7776     uint32_t rbrace_column = ag->source_column;
   7777 
   7778     // Build Func payload (Zir.Inst.Func: ret_ty, param_block, body_len).
   7779     // (AstGen.zig:12187-12194)
   7780     uint32_t ret_ty_packed_len;
   7781     if (ret_body_len > 0) {
   7782         ret_ty_packed_len = ret_body_len; // body-based return type
   7783     } else if (ret_ref != ZIR_REF_NONE) {
   7784         ret_ty_packed_len = 1; // simple Ref
   7785     } else {
   7786         ret_ty_packed_len = 0; // void return
   7787     }
   7788     // Pack RetTy: body_len:u31 | is_generic:bool(u1) = just body_len.
   7789     uint32_t ret_ty_packed
   7790         = ret_ty_packed_len & 0x7FFFFFFFu; // is_generic=false
   7791 
   7792     uint32_t fixup_body_len = countBodyLenAfterFixupsExtraRefs(
   7793         ag, body, body_len, param_insts, param_insts_len);
   7794     ensureExtraCapacity(ag, 3 + ret_ty_packed_len + fixup_body_len + 7);
   7795     uint32_t payload_index = ag->extra_len;
   7796     ag->extra[ag->extra_len++] = ret_ty_packed; // Func.ret_ty
   7797     ag->extra[ag->extra_len++] = param_block; // Func.param_block
   7798     ag->extra[ag->extra_len++] = fixup_body_len; // Func.body_len
   7799 
   7800     // Trailing ret_ty: either body instructions or a single ref.
   7801     if (ret_body_len > 0) {
   7802         for (uint32_t i = 0; i < ret_body_len; i++)
   7803             ag->extra[ag->extra_len++] = ret_body[i];
   7804     } else if (ret_ref != ZIR_REF_NONE) {
   7805         ag->extra[ag->extra_len++] = ret_ref;
   7806     }
   7807 
   7808     // Body instructions with extra_refs for param_insts
   7809     // (AstGen.zig:12206).
   7810     appendBodyWithFixupsExtraRefs(
   7811         ag, body, body_len, param_insts, param_insts_len);
   7812 
   7813     // SrcLocs (AstGen.zig:12098-12106).
   7814     uint32_t columns = (lbrace_column & 0xFFFFu) | (rbrace_column << 16);
   7815     ag->extra[ag->extra_len++] = lbrace_line;
   7816     ag->extra[ag->extra_len++] = rbrace_line;
   7817     ag->extra[ag->extra_len++] = columns;
   7818     // proto_hash (4 words): zero for now.
   7819     ag->extra[ag->extra_len++] = 0;
   7820     ag->extra[ag->extra_len++] = 0;
   7821     ag->extra[ag->extra_len++] = 0;
   7822     ag->extra[ag->extra_len++] = 0;
   7823 
   7824     // Emit the func instruction (AstGen.zig:12220-12226).
   7825     ZirInstTag tag
   7826         = is_inferred_error ? ZIR_INST_FUNC_INFERRED : ZIR_INST_FUNC;
   7827     ZirInstData data;
   7828     data.pl_node.src_node = (int32_t)src_node - (int32_t)gz->decl_node_index;
   7829     data.pl_node.payload_index = payload_index;
   7830     return addInstruction(gz, tag, data);
   7831 }
   7832 
   7833 // --- testDecl (AstGen.zig:4708) ---
   7834 
   7835 static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
   7836     uint32_t* decl_idx, uint32_t node) {
   7837     const Ast* tree = ag->tree;
   7838     AstData nd = tree->nodes.datas[node];
   7839     uint32_t body_node = nd.rhs;
   7840 
   7841     // makeDeclaration before advanceSourceCursorToNode (AstGen.zig:4726-4729).
   7842     uint32_t decl_inst = makeDeclaration(ag, node);
   7843     wip_decl_insts[*decl_idx] = decl_inst;
   7844     (*decl_idx)++;
   7845     advanceSourceCursorToNode(ag, node);
   7846 
   7847     uint32_t decl_line = ag->source_line;
   7848     uint32_t decl_column = ag->source_column;
   7849 
   7850     // Extract test name (AstGen.zig:4748-4835).
   7851     uint32_t test_token = tree->nodes.main_tokens[node];
   7852     uint32_t test_name_token = test_token + 1;
   7853     uint32_t test_name = 0; // NullTerminatedString.empty
   7854     DeclFlagsId decl_id = DECL_ID_UNNAMED_TEST;
   7855 
   7856     // Check if the token after 'test' is a string literal.
   7857     // We identify string literals by checking the source character.
   7858     uint32_t name_tok_start = tree->tokens.starts[test_name_token];
   7859     if (name_tok_start < tree->source_len
   7860         && tree->source[name_tok_start] == '"') {
   7861         // String literal name.
   7862         uint32_t name_len;
   7863         strLitAsString(ag, test_name_token, &test_name, &name_len);
   7864         decl_id = DECL_ID_TEST;
   7865     }
   7866     // TODO: handle identifier test names (decltest).
   7867 
   7868     // Set up decl_block GenZir (AstGen.zig:4735-4743).
   7869     GenZir decl_block;
   7870     memset(&decl_block, 0, sizeof(decl_block));
   7871     decl_block.base.tag = SCOPE_GEN_ZIR;
   7872     decl_block.parent = NULL;
   7873     decl_block.astgen = ag;
   7874     decl_block.decl_node_index = node;
   7875     decl_block.decl_line = decl_line;
   7876     decl_block.is_comptime = true;
   7877     decl_block.instructions_top = ag->scratch_inst_len;
   7878     decl_block.break_block = UINT32_MAX;
   7879 
   7880     // Set up fn_block GenZir (AstGen.zig:4837-4845).
   7881     GenZir fn_block;
   7882     memset(&fn_block, 0, sizeof(fn_block));
   7883     fn_block.base.tag = SCOPE_GEN_ZIR;
   7884     fn_block.parent = &decl_block.base;
   7885     fn_block.astgen = ag;
   7886     fn_block.decl_node_index = node;
   7887     fn_block.decl_line = decl_line;
   7888     fn_block.is_comptime = false;
   7889     fn_block.instructions_top = ag->scratch_inst_len;
   7890     fn_block.break_block = UINT32_MAX;
   7891 
   7892     // Set fn_block and fn_ret_ty for the body (AstGen.zig:4849-4853).
   7893     void* prev_fn_block = ag->fn_block;
   7894     uint32_t prev_fn_ret_ty = ag->fn_ret_ty;
   7895     setFnBlock(ag, &fn_block);
   7896     ag->fn_ret_ty = ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE;
   7897 
   7898     // Compute lbrace source location (AstGen.zig:4860-4862).
   7899     advanceSourceCursorToNode(ag, body_node);
   7900     uint32_t lbrace_line = ag->source_line - decl_line;
   7901     uint32_t lbrace_column = ag->source_column;
   7902 
   7903     // Process test body (AstGen.zig:4864).
   7904     uint32_t block_result
   7905         = fullBodyExpr(&fn_block, &fn_block.base, RL_NONE_VAL, body_node);
   7906 
   7907     ag->fn_block = prev_fn_block;
   7908     ag->fn_ret_ty = prev_fn_ret_ty;
   7909 
   7910     // If we hit unimplemented features, bail out.
   7911     if (ag->has_compile_errors)
   7912         return;
   7913 
   7914     // Add restore_err_ret_index + ret_implicit (AstGen.zig:4865-4871).
   7915     if (gzInstructionsLen(&fn_block) == 0
   7916         || !refIsNoReturn(&fn_block, block_result)) {
   7917         ZirInstData rdata;
   7918         rdata.un_node.operand = ZIR_REF_NONE; // .none for .ret
   7919         rdata.un_node.src_node
   7920             = (int32_t)node - (int32_t)fn_block.decl_node_index;
   7921         addInstruction(
   7922             &fn_block, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata);
   7923 
   7924         uint32_t body_last_tok = lastToken(tree, body_node);
   7925         ZirInstData rdata2;
   7926         rdata2.un_tok.operand = ZIR_REF_VOID_VALUE;
   7927         rdata2.un_tok.src_tok = tokenIndexToRelative(&fn_block, body_last_tok);
   7928         addInstruction(&fn_block, ZIR_INST_RET_IMPLICIT, rdata2);
   7929     }
   7930 
   7931     // Read fn_block body before unstacking (AstGen.zig:4874).
   7932     // Upstream unstacks fn_block inside addFunc before appending the func
   7933     // instruction to decl_block. We must unstack fn_block first so that
   7934     // addFunc's addInstruction goes into decl_block's range.
   7935     const uint32_t* fn_body = gzInstructionsSlice(&fn_block);
   7936     uint32_t fn_body_len = gzInstructionsLen(&fn_block);
   7937     gzUnstack(&fn_block);
   7938 
   7939     // Create func instruction (AstGen.zig:4874-4897).
   7940     uint32_t func_ref = addFunc(&decl_block, node, body_node, decl_inst,
   7941         ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE, NULL, 0, fn_body, fn_body_len,
   7942         NULL, 0, lbrace_line, lbrace_column, false);
   7943 
   7944     // break_inline returning func to declaration (AstGen.zig:4899).
   7945     makeBreakInline(&decl_block, decl_inst, func_ref, AST_NODE_OFFSET_NONE);
   7946 
   7947     // setDeclaration (AstGen.zig:4903-4923).
   7948     setDeclaration(ag, decl_inst,
   7949         (SetDeclArgs) { .src_line = decl_line,
   7950             .src_column = decl_column,
   7951             .id = decl_id,
   7952             .name = test_name,
   7953             .lib_name = UINT32_MAX,
   7954             .value_body = gzInstructionsSlice(&decl_block),
   7955             .value_body_len = gzInstructionsLen(&decl_block) });
   7956     gzUnstack(&decl_block);
   7957 
   7958     (void)gz;
   7959 }
   7960 
   7961 // --- fnDecl (AstGen.zig:4067) / fnDeclInner (AstGen.zig:4228) ---
   7962 // Handles non-extern function declarations with bodies, including params.
   7963 
   7964 static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
   7965     uint32_t* decl_idx, uint32_t node) {
   7966     const Ast* tree = ag->tree;
   7967     AstData nd = tree->nodes.datas[node];
   7968 
   7969     // For fn_decl: data.lhs = fn_proto node, data.rhs = body node.
   7970     uint32_t proto_node = nd.lhs;
   7971     uint32_t body_node = nd.rhs;
   7972 
   7973     // Get function name token (main_token of proto + 1 = fn name).
   7974     uint32_t fn_token = tree->nodes.main_tokens[proto_node];
   7975     uint32_t fn_name_token = fn_token + 1;
   7976 
   7977     // Check for 'pub' modifier (Ast.zig:2003-2025).
   7978     bool is_pub = (fn_token > 0
   7979         && tree->tokens.tags[fn_token - 1] == TOKEN_KEYWORD_PUB);
   7980 
   7981     // makeDeclaration on fn_proto node (AstGen.zig:4090).
   7982     uint32_t decl_inst = makeDeclaration(ag, proto_node);
   7983     wip_decl_insts[*decl_idx] = decl_inst;
   7984     (*decl_idx)++;
   7985 
   7986     advanceSourceCursorToNode(ag, node);
   7987     uint32_t decl_line = ag->source_line;
   7988     uint32_t decl_column = ag->source_column;
   7989 
   7990     // Save source cursor for restoring after ret_gz (AstGen.zig:4387-4388).
   7991     uint32_t saved_source_offset = ag->source_offset;
   7992     uint32_t saved_source_line = ag->source_line;
   7993     uint32_t saved_source_column = ag->source_column;
   7994 
   7995     AstNodeTag proto_tag = tree->nodes.tags[proto_node];
   7996     AstData proto_data = tree->nodes.datas[proto_node];
   7997 
   7998     // Extract return type node (rhs for all fn_proto variants).
   7999     uint32_t return_type_node = proto_data.rhs;
   8000 
   8001     // Detect inferred error set: token before return type is '!'
   8002     // (AstGen.zig:4249-4251).
   8003     bool is_inferred_error = false;
   8004     if (return_type_node != 0) {
   8005         uint32_t ret_first_tok = firstToken(tree, return_type_node);
   8006         if (ret_first_tok > 0) {
   8007             uint32_t maybe_bang = ret_first_tok - 1;
   8008             uint32_t bang_start = tree->tokens.starts[maybe_bang];
   8009             if (tree->source[bang_start] == '!')
   8010                 is_inferred_error = true;
   8011         }
   8012     }
   8013 
   8014     // Extract param type nodes from proto variant (AstGen.zig:4253-4254).
   8015     uint32_t param_nodes_buf[1]; // buffer for fn_proto_simple/fn_proto_one
   8016     const uint32_t* param_nodes = NULL;
   8017     uint32_t params_len = 0;
   8018 
   8019     if (proto_tag == AST_NODE_FN_PROTO_SIMPLE) {
   8020         // data.lhs = optional param node, data.rhs = return type.
   8021         if (proto_data.lhs != 0) {
   8022             param_nodes_buf[0] = proto_data.lhs;
   8023             param_nodes = param_nodes_buf;
   8024             params_len = 1;
   8025         }
   8026     } else if (proto_tag == AST_NODE_FN_PROTO_ONE) {
   8027         // data.lhs = extra_data index → AstFnProtoOne.
   8028         uint32_t extra_idx = proto_data.lhs;
   8029         uint32_t param
   8030             = tree->extra_data.arr[extra_idx]; // AstFnProtoOne.param
   8031         if (param != 0) {
   8032             param_nodes_buf[0] = param;
   8033             param_nodes = param_nodes_buf;
   8034             params_len = 1;
   8035         }
   8036     } else if (proto_tag == AST_NODE_FN_PROTO_MULTI) {
   8037         // data.lhs = extra_data index → SubRange{start, end}.
   8038         uint32_t extra_idx = proto_data.lhs;
   8039         uint32_t range_start = tree->extra_data.arr[extra_idx];
   8040         uint32_t range_end = tree->extra_data.arr[extra_idx + 1];
   8041         param_nodes = tree->extra_data.arr + range_start;
   8042         params_len = range_end - range_start;
   8043     } else if (proto_tag == AST_NODE_FN_PROTO) {
   8044         // data.lhs = extra_data index → AstFnProto{params_start, params_end,
   8045         // ...}.
   8046         uint32_t extra_idx = proto_data.lhs;
   8047         uint32_t pstart = tree->extra_data.arr[extra_idx]; // params_start
   8048         uint32_t pend = tree->extra_data.arr[extra_idx + 1]; // params_end
   8049         param_nodes = tree->extra_data.arr + pstart;
   8050         params_len = pend - pstart;
   8051     }
   8052 
   8053     // decl_gz (called value_gz in caller, decl_gz in fnDeclInner)
   8054     // (AstGen.zig:4194-4201).
   8055     GenZir decl_gz;
   8056     memset(&decl_gz, 0, sizeof(decl_gz));
   8057     decl_gz.base.tag = SCOPE_GEN_ZIR;
   8058     decl_gz.parent = NULL;
   8059     decl_gz.astgen = ag;
   8060     decl_gz.decl_node_index = proto_node;
   8061     decl_gz.decl_line = decl_line;
   8062     decl_gz.is_comptime = true;
   8063     decl_gz.instructions_top = ag->scratch_inst_len;
   8064     decl_gz.break_block = UINT32_MAX;
   8065 
   8066     // --- Parameter iteration (AstGen.zig:4260-4363) ---
   8067     // Walk params, creating param instructions and ScopeLocalVal entries.
   8068     // We keep param scopes on the C stack (max 32 params like upstream).
   8069     Scope* params_scope = &decl_gz.base;
   8070     ScopeLocalVal param_scopes[32];
   8071     uint32_t param_scope_count = 0;
   8072     // Collect param instruction indices (AstGen.zig:4254, 4360).
   8073     uint32_t param_insts[32];
   8074     uint32_t param_insts_len = 0;
   8075 
   8076     for (uint32_t param_i = 0; param_i < params_len; param_i++) {
   8077         uint32_t param_type_node = param_nodes[param_i];
   8078 
   8079         // Find param name token by scanning backwards from firstToken of
   8080         // type expression (mirrors FnProto.Iterator.next, Ast.zig:2687).
   8081         // Layout: [comptime] [name] [:] type_expr
   8082         // So: type_first_tok - 1 is ':', type_first_tok - 2 is name.
   8083         uint32_t type_first_tok = firstToken(tree, param_type_node);
   8084         uint32_t name_token = 0; // 0 = no name found
   8085         bool is_comptime_param = false;
   8086         if (type_first_tok >= 2
   8087             && tree->tokens.tags[type_first_tok - 1] == TOKEN_COLON) {
   8088             // Named parameter: name is at type_first_tok - 2.
   8089             uint32_t maybe_name = type_first_tok - 2;
   8090             uint32_t name_start = tree->tokens.starts[maybe_name];
   8091             char ch = tree->source[name_start];
   8092             if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
   8093                 || ch == '_' || ch == '@') {
   8094                 // Could be name or comptime/noalias keyword.
   8095                 if (name_start + 8 <= tree->source_len
   8096                     && memcmp(tree->source + name_start, "comptime", 8) == 0) {
   8097                     is_comptime_param = true;
   8098                 } else if (name_start + 7 <= tree->source_len
   8099                     && memcmp(tree->source + name_start, "noalias", 7) == 0) {
   8100                     // noalias keyword, not a name.
   8101                 } else {
   8102                     name_token = maybe_name;
   8103                     // Check for preceding comptime keyword.
   8104                     if (maybe_name > 0) {
   8105                         uint32_t prev = maybe_name - 1;
   8106                         uint32_t prev_start = tree->tokens.starts[prev];
   8107                         if (prev_start + 8 <= tree->source_len
   8108                             && memcmp(tree->source + prev_start, "comptime", 8)
   8109                                 == 0)
   8110                             is_comptime_param = true;
   8111                     }
   8112                 }
   8113             }
   8114         }
   8115 
   8116         // Determine param name string (AstGen.zig:4283-4321).
   8117         // Must be resolved BEFORE type expression to match upstream string
   8118         // table ordering.
   8119         uint32_t param_name_str = 0; // NullTerminatedString.empty
   8120         if (name_token != 0) {
   8121             uint32_t name_start = tree->tokens.starts[name_token];
   8122             char nch = tree->source[name_start];
   8123             // Skip "_" params (AstGen.zig:4285-4286).
   8124             if (nch == '_') {
   8125                 uint32_t next_start = tree->tokens.starts[name_token + 1];
   8126                 if (next_start == name_start + 1) {
   8127                     // Single underscore: empty name.
   8128                     param_name_str = 0;
   8129                 } else {
   8130                     param_name_str = identAsString(ag, name_token);
   8131                 }
   8132             } else {
   8133                 param_name_str = identAsString(ag, name_token);
   8134             }
   8135         }
   8136 
   8137         // Evaluate param type expression in a sub-block
   8138         // (AstGen.zig:4333-4337).
   8139         GenZir param_gz = makeSubBlock(&decl_gz, params_scope);
   8140         uint32_t param_type_ref
   8141             = expr(&param_gz, params_scope, param_type_node);
   8142 
   8143         if (ag->has_compile_errors)
   8144             return;
   8145 
   8146         // The break_inline target is the param instruction we're about to
   8147         // create (AstGen.zig:4336-4337).
   8148         uint32_t param_inst_expected = ag->inst_len + 1;
   8149         // +1 because: the break_inline is emitted first (uses inst_len),
   8150         // then addParam emits the param instruction at inst_len.
   8151         // Actually, addParam emits the param after break_inline. The
   8152         // break_inline's block_inst field should point to the param inst.
   8153         // We know it will be at ag->inst_len after the break_inline.
   8154         makeBreakInline(&param_gz, param_inst_expected, param_type_ref,
   8155             (int32_t)param_type_node - (int32_t)param_gz.decl_node_index);
   8156 
   8157         // Create param instruction (AstGen.zig:4341-4343).
   8158         ZirInstTag param_tag
   8159             = is_comptime_param ? ZIR_INST_PARAM_COMPTIME : ZIR_INST_PARAM;
   8160         uint32_t name_tok_for_src = name_token != 0
   8161             ? name_token
   8162             : tree->nodes.main_tokens[param_type_node];
   8163         uint32_t param_inst = addParam(
   8164             &decl_gz, &param_gz, param_tag, name_tok_for_src, param_name_str);
   8165         (void)param_inst_expected;
   8166         // Record param instruction index (AstGen.zig:4360).
   8167         if (param_insts_len < 32)
   8168             param_insts[param_insts_len++] = param_inst;
   8169 
   8170         // Create ScopeLocalVal for this param (AstGen.zig:4349-4359).
   8171         if (param_name_str != 0 && param_scope_count < 32) {
   8172             ScopeLocalVal* lv = &param_scopes[param_scope_count++];
   8173             lv->base.tag = SCOPE_LOCAL_VAL;
   8174             lv->parent = params_scope;
   8175             lv->gen_zir = &decl_gz;
   8176             lv->inst = param_inst + ZIR_REF_START_INDEX; // toRef()
   8177             lv->token_src = name_token;
   8178             lv->name = param_name_str;
   8179             params_scope = &lv->base;
   8180         }
   8181     }
   8182 
   8183     // --- Return type (AstGen.zig:4369-4383) ---
   8184     GenZir ret_gz = makeSubBlock(&decl_gz, params_scope);
   8185     uint32_t ret_ref = ZIR_REF_NONE;
   8186     if (return_type_node != 0) {
   8187         ret_ref = expr(&ret_gz, params_scope, return_type_node);
   8188         if (ag->has_compile_errors)
   8189             return;
   8190         // If ret_gz produced instructions, add break_inline
   8191         // (AstGen.zig:4377-4381).
   8192         if (gzInstructionsLen(&ret_gz) > 0) {
   8193             // break_inline targets the func instruction (which doesn't
   8194             // exist yet). We use 0 as placeholder and patch later.
   8195             makeBreakInline(&ret_gz, 0, ret_ref, AST_NODE_OFFSET_NONE);
   8196         }
   8197     }
   8198     // Map void_type → .none (AstGen.zig:12054).
   8199     if (ret_ref == ZIR_REF_VOID_TYPE)
   8200         ret_ref = ZIR_REF_NONE;
   8201 
   8202     uint32_t ret_body_len = gzInstructionsLen(&ret_gz);
   8203     // Copy ret_body before unstacking: body_gz reuses the same scratch area.
   8204     uint32_t* ret_body = NULL;
   8205     if (ret_body_len > 0) {
   8206         ret_body = malloc(ret_body_len * sizeof(uint32_t));
   8207         if (!ret_body)
   8208             abort();
   8209         memcpy(ret_body, gzInstructionsSlice(&ret_gz),
   8210             ret_body_len * sizeof(uint32_t));
   8211     }
   8212     gzUnstack(&ret_gz);
   8213 
   8214     // Restore source cursor (AstGen.zig:4387-4388).
   8215     ag->source_offset = saved_source_offset;
   8216     ag->source_line = saved_source_line;
   8217     ag->source_column = saved_source_column;
   8218 
   8219     // --- Body (AstGen.zig:4415-4424) ---
   8220     GenZir body_gz;
   8221     memset(&body_gz, 0, sizeof(body_gz));
   8222     body_gz.base.tag = SCOPE_GEN_ZIR;
   8223     body_gz.parent = params_scope;
   8224     body_gz.astgen = ag;
   8225     body_gz.decl_node_index = proto_node;
   8226     body_gz.decl_line = decl_line;
   8227     body_gz.is_comptime = false;
   8228     body_gz.instructions_top = ag->scratch_inst_len;
   8229 
   8230     // Set fn_block and fn_ret_ty for the body (AstGen.zig:4442-4455).
   8231     void* prev_fn_block = ag->fn_block;
   8232     setFnBlock(ag, &body_gz);
   8233     uint32_t prev_fn_ret_ty = ag->fn_ret_ty;
   8234     if (is_inferred_error || ret_ref == ZIR_REF_NONE) {
   8235         // Non-void non-trivial return type: emit ret_type instruction.
   8236         if (ret_body_len > 0 || is_inferred_error) {
   8237             ZirInstData rtdata;
   8238             memset(&rtdata, 0, sizeof(rtdata));
   8239             rtdata.node = (int32_t)node - (int32_t)body_gz.decl_node_index;
   8240             ag->fn_ret_ty
   8241                 = addInstruction(&body_gz, ZIR_INST_RET_TYPE, rtdata);
   8242         } else {
   8243             ag->fn_ret_ty = ret_ref; // void
   8244         }
   8245     } else {
   8246         // ret_ref is a simple ref (not void, not inferred error).
   8247         // Still need ret_type instruction if it resolved to an inst.
   8248         if (ret_ref >= ZIR_REF_START_INDEX) {
   8249             ZirInstData rtdata;
   8250             memset(&rtdata, 0, sizeof(rtdata));
   8251             rtdata.node = (int32_t)node - (int32_t)body_gz.decl_node_index;
   8252             ag->fn_ret_ty
   8253                 = addInstruction(&body_gz, ZIR_INST_RET_TYPE, rtdata);
   8254         } else {
   8255             ag->fn_ret_ty = ret_ref;
   8256         }
   8257     }
   8258 
   8259     // Process function body (AstGen.zig:4461-4465).
   8260     advanceSourceCursorToNode(ag, body_node);
   8261     uint32_t lbrace_line = ag->source_line - decl_line;
   8262     uint32_t lbrace_column = ag->source_column;
   8263 
   8264     fullBodyExpr(&body_gz, &body_gz.base, RL_NONE_VAL, body_node);
   8265 
   8266     ag->fn_block = prev_fn_block;
   8267     ag->fn_ret_ty = prev_fn_ret_ty;
   8268 
   8269     if (ag->has_compile_errors) {
   8270         free(ret_body);
   8271         return;
   8272     }
   8273 
   8274     // Add implicit return at end of function body
   8275     // (AstGen.zig:4465-4871).
   8276     if (!endsWithNoReturn(&body_gz)) {
   8277         ZirInstData rdata;
   8278         rdata.un_node.operand = ZIR_REF_NONE;
   8279         rdata.un_node.src_node
   8280             = (int32_t)node - (int32_t)body_gz.decl_node_index;
   8281         addInstruction(
   8282             &body_gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata);
   8283 
   8284         uint32_t body_last_tok = lastToken(tree, body_node);
   8285         ZirInstData rdata2;
   8286         rdata2.un_tok.operand = ZIR_REF_VOID_VALUE;
   8287         rdata2.un_tok.src_tok = tokenIndexToRelative(&body_gz, body_last_tok);
   8288         addInstruction(&body_gz, ZIR_INST_RET_IMPLICIT, rdata2);
   8289     }
   8290 
   8291     // Read body before unstacking (AstGen.zig:12215-12218).
   8292     const uint32_t* fn_body = gzInstructionsSlice(&body_gz);
   8293     uint32_t fn_body_len = gzInstructionsLen(&body_gz);
   8294     gzUnstack(&body_gz);
   8295 
   8296     // Create func instruction (AstGen.zig:4476-4494).
   8297     uint32_t func_ref = addFunc(&decl_gz, node, body_node, decl_inst, ret_ref,
   8298         ret_body, ret_body_len, fn_body, fn_body_len, param_insts,
   8299         param_insts_len, lbrace_line, lbrace_column, is_inferred_error);
   8300 
   8301     // Patch ret_body break_inline to point to func instruction
   8302     // (AstGen.zig:12199-12202).
   8303     if (ret_body_len > 0) {
   8304         uint32_t break_inst = ret_body[ret_body_len - 1];
   8305         // The break_inline payload is at payload_index; block_inst is at
   8306         // offset 1 in the Break struct.
   8307         uint32_t break_payload
   8308             = ag->inst_datas[break_inst].break_data.payload_index;
   8309         ag->extra[break_payload + 1] = func_ref - ZIR_REF_START_INDEX;
   8310     }
   8311     free(ret_body);
   8312 
   8313     // break_inline returning func to declaration (AstGen.zig:4495).
   8314     // nodeIndexToRelative(decl_node) = node - decl_gz.decl_node_index.
   8315     makeBreakInline(
   8316         &decl_gz, decl_inst, func_ref, (int32_t)node - (int32_t)proto_node);
   8317 
   8318     // setDeclaration (AstGen.zig:4208-4225).
   8319     DeclFlagsId decl_id
   8320         = is_pub ? DECL_ID_PUB_CONST_SIMPLE : DECL_ID_CONST_SIMPLE;
   8321     uint32_t name_str = identAsString(ag, fn_name_token);
   8322     setDeclaration(ag, decl_inst,
   8323         (SetDeclArgs) { .src_line = decl_line,
   8324             .src_column = decl_column,
   8325             .id = decl_id,
   8326             .name = name_str,
   8327             .lib_name = UINT32_MAX,
   8328             .value_body = gzInstructionsSlice(&decl_gz),
   8329             .value_body_len = gzInstructionsLen(&decl_gz) });
   8330     gzUnstack(&decl_gz);
   8331 
   8332     (void)gz;
   8333 }
   8334 
   8335 // --- comptimeDecl (AstGen.zig:4645) ---
   8336 
   8337 static void comptimeDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
   8338     uint32_t* decl_idx, uint32_t node) {
   8339     // makeDeclaration before advanceSourceCursorToNode (AstGen.zig:4663-4665).
   8340     uint32_t decl_inst = makeDeclaration(ag, node);
   8341     wip_decl_insts[*decl_idx] = decl_inst;
   8342     (*decl_idx)++;
   8343 
   8344     advanceSourceCursorToNode(ag, node);
   8345 
   8346     uint32_t decl_line = ag->source_line;
   8347     uint32_t decl_column = ag->source_column;
   8348 
   8349     // Value sub-block (AstGen.zig:4675-4686).
   8350     GenZir value_gz;
   8351     memset(&value_gz, 0, sizeof(value_gz));
   8352     value_gz.base.tag = SCOPE_GEN_ZIR;
   8353     value_gz.parent = NULL;
   8354     value_gz.astgen = ag;
   8355     value_gz.decl_node_index = node;
   8356     value_gz.decl_line = decl_line;
   8357     value_gz.is_comptime = true;
   8358     value_gz.instructions_top = ag->scratch_inst_len;
   8359 
   8360     // For comptime {}: body is empty block → no instructions generated.
   8361     // comptime_gz.isEmpty() == true → addBreak(.break_inline, decl_inst,
   8362     // .void_value) (AstGen.zig:4685-4686)
   8363     makeBreakInline(
   8364         &value_gz, decl_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE);
   8365 
   8366     setDeclaration(ag, decl_inst,
   8367         (SetDeclArgs) { .src_line = decl_line,
   8368             .src_column = decl_column,
   8369             .id = DECL_ID_COMPTIME,
   8370             .name = 0,
   8371             .lib_name = UINT32_MAX,
   8372             .value_body = gzInstructionsSlice(&value_gz),
   8373             .value_body_len = gzInstructionsLen(&value_gz) });
   8374     gzUnstack(&value_gz);
   8375 
   8376     (void)gz;
   8377 }
   8378 
   8379 // --- globalVarDecl (AstGen.zig:4498) ---
   8380 
   8381 // Extract VarDecl fields from an AST node (Ast.zig:1326-1380).
   8382 typedef struct {
   8383     uint32_t mut_token;
   8384     uint32_t type_node; // 0 = none
   8385     uint32_t align_node; // 0 = none
   8386     uint32_t addrspace_node; // 0 = none
   8387     uint32_t section_node; // 0 = none
   8388     uint32_t init_node; // UINT32_MAX = none
   8389     bool is_pub;
   8390     bool is_extern;
   8391     bool is_export;
   8392     bool is_mutable;
   8393     bool is_threadlocal;
   8394     uint32_t lib_name_token; // UINT32_MAX = none
   8395 } VarDeclInfo;
   8396 
   8397 static VarDeclInfo extractVarDecl(const Ast* tree, uint32_t node) {
   8398     AstNodeTag tag = tree->nodes.tags[node];
   8399     AstData nd = tree->nodes.datas[node];
   8400     uint32_t mut_token = tree->nodes.main_tokens[node];
   8401     VarDeclInfo info;
   8402     memset(&info, 0, sizeof(info));
   8403     info.mut_token = mut_token;
   8404     info.init_node = UINT32_MAX;
   8405     info.lib_name_token = UINT32_MAX;
   8406 
   8407     switch (tag) {
   8408     case AST_NODE_SIMPLE_VAR_DECL:
   8409         // lhs = type_node (optional), rhs = init_node (optional)
   8410         info.type_node = nd.lhs;
   8411         info.init_node = nd.rhs;
   8412         break;
   8413     case AST_NODE_ALIGNED_VAR_DECL:
   8414         // lhs = align_node, rhs = init_node (optional)
   8415         info.align_node = nd.lhs;
   8416         info.init_node = nd.rhs;
   8417         break;
   8418     case AST_NODE_GLOBAL_VAR_DECL: {
   8419         // lhs = extra_data index, rhs = init_node (optional)
   8420         uint32_t ei = nd.lhs;
   8421         info.type_node = tree->extra_data.arr[ei + 0];
   8422         info.align_node = tree->extra_data.arr[ei + 1];
   8423         info.addrspace_node = tree->extra_data.arr[ei + 2];
   8424         info.section_node = tree->extra_data.arr[ei + 3];
   8425         info.init_node = nd.rhs;
   8426         break;
   8427     }
   8428     case AST_NODE_LOCAL_VAR_DECL: {
   8429         // lhs = extra_data index, rhs = init_node (optional)
   8430         uint32_t ei = nd.lhs;
   8431         info.type_node = tree->extra_data.arr[ei + 0];
   8432         info.align_node = tree->extra_data.arr[ei + 1];
   8433         info.init_node = nd.rhs;
   8434         break;
   8435     }
   8436     default:
   8437         break;
   8438     }
   8439 
   8440     // Scan backwards from mut_token to find modifiers (Ast.zig:2003-2025).
   8441     info.is_mutable = (tree->tokens.tags[mut_token] == TOKEN_KEYWORD_VAR);
   8442     for (uint32_t i = mut_token; i > 0;) {
   8443         i--;
   8444         TokenizerTag ttag = tree->tokens.tags[i];
   8445         if (ttag == TOKEN_KEYWORD_EXTERN)
   8446             info.is_extern = true;
   8447         else if (ttag == TOKEN_KEYWORD_EXPORT)
   8448             info.is_export = true;
   8449         else if (ttag == TOKEN_KEYWORD_PUB)
   8450             info.is_pub = true;
   8451         else if (ttag == TOKEN_KEYWORD_THREADLOCAL)
   8452             info.is_threadlocal = true;
   8453         else if (ttag == TOKEN_STRING_LITERAL)
   8454             info.lib_name_token = i;
   8455         else
   8456             break;
   8457     }
   8458     return info;
   8459 }
   8460 
   8461 // Compute DeclFlagsId from VarDecl properties (AstGen.zig:13916-13972).
   8462 static DeclFlagsId computeVarDeclId(bool is_mutable, bool is_pub,
   8463     bool is_extern, bool is_export, bool is_threadlocal, bool has_type_body,
   8464     bool has_special_body, bool has_lib_name) {
   8465     if (!is_mutable) {
   8466         // const
   8467         if (is_extern) {
   8468             if (is_pub) {
   8469                 if (has_lib_name || has_special_body)
   8470                     return DECL_ID_PUB_EXTERN_CONST;
   8471                 return DECL_ID_PUB_EXTERN_CONST_SIMPLE;
   8472             }
   8473             if (has_lib_name || has_special_body)
   8474                 return DECL_ID_EXTERN_CONST;
   8475             return DECL_ID_EXTERN_CONST_SIMPLE;
   8476         }
   8477         if (is_export)
   8478             return is_pub ? DECL_ID_PUB_EXPORT_CONST : DECL_ID_EXPORT_CONST;
   8479         if (is_pub) {
   8480             if (has_special_body)
   8481                 return DECL_ID_PUB_CONST;
   8482             if (has_type_body)
   8483                 return DECL_ID_PUB_CONST_TYPED;
   8484             return DECL_ID_PUB_CONST_SIMPLE;
   8485         }
   8486         if (has_special_body)
   8487             return DECL_ID_CONST;
   8488         if (has_type_body)
   8489             return DECL_ID_CONST_TYPED;
   8490         return DECL_ID_CONST_SIMPLE;
   8491     }
   8492     // var
   8493     if (is_extern) {
   8494         if (is_pub) {
   8495             if (is_threadlocal)
   8496                 return DECL_ID_PUB_EXTERN_VAR_THREADLOCAL;
   8497             return DECL_ID_PUB_EXTERN_VAR;
   8498         }
   8499         if (is_threadlocal)
   8500             return DECL_ID_EXTERN_VAR_THREADLOCAL;
   8501         return DECL_ID_EXTERN_VAR;
   8502     }
   8503     if (is_export) {
   8504         if (is_pub) {
   8505             if (is_threadlocal)
   8506                 return DECL_ID_PUB_EXPORT_VAR_THREADLOCAL;
   8507             return DECL_ID_PUB_EXPORT_VAR;
   8508         }
   8509         if (is_threadlocal)
   8510             return DECL_ID_EXPORT_VAR_THREADLOCAL;
   8511         return DECL_ID_EXPORT_VAR;
   8512     }
   8513     if (is_pub) {
   8514         if (is_threadlocal)
   8515             return DECL_ID_PUB_VAR_THREADLOCAL;
   8516         if (has_special_body || has_type_body)
   8517             return DECL_ID_PUB_VAR;
   8518         return DECL_ID_PUB_VAR_SIMPLE;
   8519     }
   8520     if (is_threadlocal)
   8521         return DECL_ID_VAR_THREADLOCAL;
   8522     if (has_special_body || has_type_body)
   8523         return DECL_ID_VAR;
   8524     return DECL_ID_VAR_SIMPLE;
   8525 }
   8526 
   8527 static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts,
   8528     uint32_t* decl_idx, uint32_t node) {
   8529     const Ast* tree = ag->tree;
   8530     VarDeclInfo vd = extractVarDecl(tree, node);
   8531     uint32_t name_token = vd.mut_token + 1;
   8532 
   8533     // advanceSourceCursorToNode before makeDeclaration (AstGen.zig:4542-4546).
   8534     advanceSourceCursorToNode(ag, node);
   8535     uint32_t decl_column = ag->source_column;
   8536 
   8537     uint32_t decl_inst = makeDeclaration(ag, node);
   8538     wip_decl_insts[*decl_idx] = decl_inst;
   8539     (*decl_idx)++;
   8540 
   8541     // Set up type sub-block (AstGen.zig:4574-4582).
   8542     GenZir type_gz;
   8543     memset(&type_gz, 0, sizeof(type_gz));
   8544     type_gz.base.tag = SCOPE_GEN_ZIR;
   8545     type_gz.astgen = ag;
   8546     type_gz.decl_node_index = node;
   8547     type_gz.instructions_top = ag->scratch_inst_len;
   8548     type_gz.decl_line = ag->source_line;
   8549     type_gz.is_comptime = true;
   8550 
   8551     if (vd.type_node != 0) {
   8552         uint32_t type_inst = typeExpr(&type_gz, &type_gz.base, vd.type_node);
   8553         makeBreakInline(&type_gz, decl_inst, type_inst, 0);
   8554     }
   8555 
   8556     // Record type_gz boundary for slicing.
   8557     uint32_t type_top = ag->scratch_inst_len;
   8558 
   8559     // Align sub-block (AstGen.zig:4592-4596).
   8560     GenZir align_gz;
   8561     memset(&align_gz, 0, sizeof(align_gz));
   8562     align_gz.base.tag = SCOPE_GEN_ZIR;
   8563     align_gz.astgen = ag;
   8564     align_gz.decl_node_index = node;
   8565     align_gz.instructions_top = type_top;
   8566     align_gz.decl_line = ag->source_line;
   8567     align_gz.is_comptime = true;
   8568 
   8569     if (vd.align_node != 0) {
   8570         uint32_t align_inst = expr(&align_gz, &align_gz.base, vd.align_node);
   8571         makeBreakInline(&align_gz, decl_inst, align_inst, 0);
   8572     }
   8573 
   8574     uint32_t align_top = ag->scratch_inst_len;
   8575 
   8576     // Linksection sub-block (AstGen.zig:4598-4602).
   8577     GenZir linksection_gz;
   8578     memset(&linksection_gz, 0, sizeof(linksection_gz));
   8579     linksection_gz.base.tag = SCOPE_GEN_ZIR;
   8580     linksection_gz.astgen = ag;
   8581     linksection_gz.decl_node_index = node;
   8582     linksection_gz.instructions_top = align_top;
   8583     linksection_gz.decl_line = ag->source_line;
   8584     linksection_gz.is_comptime = true;
   8585 
   8586     if (vd.section_node != 0) {
   8587         uint32_t ls_inst
   8588             = expr(&linksection_gz, &linksection_gz.base, vd.section_node);
   8589         makeBreakInline(&linksection_gz, decl_inst, ls_inst, 0);
   8590     }
   8591 
   8592     uint32_t linksection_top = ag->scratch_inst_len;
   8593 
   8594     // Addrspace sub-block (AstGen.zig:4604-4608).
   8595     GenZir addrspace_gz;
   8596     memset(&addrspace_gz, 0, sizeof(addrspace_gz));
   8597     addrspace_gz.base.tag = SCOPE_GEN_ZIR;
   8598     addrspace_gz.astgen = ag;
   8599     addrspace_gz.decl_node_index = node;
   8600     addrspace_gz.instructions_top = linksection_top;
   8601     addrspace_gz.decl_line = ag->source_line;
   8602     addrspace_gz.is_comptime = true;
   8603 
   8604     if (vd.addrspace_node != 0) {
   8605         uint32_t as_inst
   8606             = expr(&addrspace_gz, &addrspace_gz.base, vd.addrspace_node);
   8607         makeBreakInline(&addrspace_gz, decl_inst, as_inst, 0);
   8608     }
   8609 
   8610     uint32_t addrspace_top = ag->scratch_inst_len;
   8611 
   8612     // Value sub-block (AstGen.zig:4610-4620).
   8613     GenZir value_gz;
   8614     memset(&value_gz, 0, sizeof(value_gz));
   8615     value_gz.base.tag = SCOPE_GEN_ZIR;
   8616     value_gz.astgen = ag;
   8617     value_gz.decl_node_index = node;
   8618     value_gz.instructions_top = addrspace_top;
   8619     value_gz.decl_line = ag->source_line;
   8620     value_gz.is_comptime = true;
   8621 
   8622     if (vd.init_node != UINT32_MAX && vd.init_node != 0) {
   8623         uint32_t init_ref = expr(&value_gz, &value_gz.base, vd.init_node);
   8624         makeBreakInline(&value_gz, decl_inst, init_ref, 0);
   8625     }
   8626 
   8627     // Compute body slices (instructionsSliceUpto).
   8628     const uint32_t* type_body
   8629         = ag->scratch_instructions + type_gz.instructions_top;
   8630     uint32_t type_body_len = type_top - type_gz.instructions_top;
   8631     const uint32_t* align_body
   8632         = ag->scratch_instructions + align_gz.instructions_top;
   8633     uint32_t align_body_len = align_top - align_gz.instructions_top;
   8634     const uint32_t* ls_body
   8635         = ag->scratch_instructions + linksection_gz.instructions_top;
   8636     uint32_t ls_body_len = linksection_top - linksection_gz.instructions_top;
   8637     const uint32_t* as_body
   8638         = ag->scratch_instructions + addrspace_gz.instructions_top;
   8639     uint32_t as_body_len = addrspace_top - addrspace_gz.instructions_top;
   8640     const uint32_t* val_body = gzInstructionsSlice(&value_gz);
   8641     uint32_t val_body_len = gzInstructionsLen(&value_gz);
   8642 
   8643     bool has_type_body = (type_body_len > 0);
   8644     bool has_special_body
   8645         = (align_body_len > 0 || ls_body_len > 0 || as_body_len > 0);
   8646     bool has_lib_name = (vd.lib_name_token != UINT32_MAX);
   8647 
   8648     uint32_t name_str = identAsString(ag, name_token);
   8649 
   8650     DeclFlagsId decl_id = computeVarDeclId(vd.is_mutable, vd.is_pub,
   8651         vd.is_extern, vd.is_export, vd.is_threadlocal, has_type_body,
   8652         has_special_body, has_lib_name);
   8653 
   8654     // Compute lib_name string index.
   8655     uint32_t lib_name = UINT32_MAX;
   8656     if (has_lib_name) {
   8657         uint32_t li, ll;
   8658         strLitAsString(ag, vd.lib_name_token, &li, &ll);
   8659         lib_name = li;
   8660     }
   8661 
   8662     setDeclaration(ag, decl_inst,
   8663         (SetDeclArgs) { .src_line = ag->source_line,
   8664             .src_column = decl_column,
   8665             .id = decl_id,
   8666             .name = name_str,
   8667             .lib_name = lib_name,
   8668             .type_body = type_body,
   8669             .type_body_len = type_body_len,
   8670             .align_body = align_body,
   8671             .align_body_len = align_body_len,
   8672             .linksection_body = ls_body,
   8673             .linksection_body_len = ls_body_len,
   8674             .addrspace_body = as_body,
   8675             .addrspace_body_len = as_body_len,
   8676             .value_body = val_body,
   8677             .value_body_len = val_body_len });
   8678 
   8679     gzUnstack(&value_gz);
   8680 
   8681     (void)gz;
   8682 }
   8683 
   8684 // --- nodeImpliesMoreThanOnePossibleValue (AstGen.zig:10548) ---
   8685 // Check if an identifier is a primitive type with more than one value.
   8686 static bool identImpliesMoreThanOnePossibleValue(
   8687     const Ast* tree, uint32_t main_token) {
   8688     uint32_t start = tree->tokens.starts[main_token];
   8689     const char* src = tree->source + start;
   8690     // Match known primitive types that have more than one possible value.
   8691     // (AstGen.zig:10729-10766)
   8692     if (src[0] == 'u' || src[0] == 'i') {
   8693         // u8, u16, u32, u64, u128, u1, u29, usize, i8, i16, i32, i64, i128,
   8694         // isize
   8695         char c1 = src[1];
   8696         if (c1 >= '0' && c1 <= '9')
   8697             return true;
   8698         if (c1 == 's') // usize, isize
   8699             return (src[2] == 'i' && src[3] == 'z' && src[4] == 'e');
   8700     }
   8701     if (src[0] == 'f') {
   8702         // f16, f32, f64, f80, f128
   8703         char c1 = src[1];
   8704         if (c1 >= '0' && c1 <= '9')
   8705             return true;
   8706     }
   8707     if (src[0] == 'b' && src[1] == 'o' && src[2] == 'o' && src[3] == 'l'
   8708         && !(src[4] >= 'a' && src[4] <= 'z')
   8709         && !(src[4] >= 'A' && src[4] <= 'Z')
   8710         && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_')
   8711         return true;
   8712     if (src[0] == 'c' && src[1] == '_')
   8713         return true; // c_int, c_long, etc.
   8714     if (src[0] == 'a' && src[1] == 'n' && src[2] == 'y') {
   8715         // anyerror, anyframe, anyopaque
   8716         return true;
   8717     }
   8718     if (src[0] == 'c' && src[1] == 'o' && src[2] == 'm' && src[3] == 'p'
   8719         && src[4] == 't' && src[5] == 'i' && src[6] == 'm' && src[7] == 'e')
   8720         return true; // comptime_float, comptime_int
   8721     if (src[0] == 't' && src[1] == 'y' && src[2] == 'p' && src[3] == 'e'
   8722         && !(src[4] >= 'a' && src[4] <= 'z')
   8723         && !(src[4] >= 'A' && src[4] <= 'Z')
   8724         && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_')
   8725         return true;
   8726     return false;
   8727 }
   8728 
   8729 static bool nodeImpliesMoreThanOnePossibleValue(
   8730     const Ast* tree, uint32_t node) {
   8731     uint32_t cur = node;
   8732     while (1) {
   8733         AstNodeTag tag = tree->nodes.tags[cur];
   8734         switch (tag) {
   8735         // Pointer/optional/array/anyframe types → true
   8736         // (AstGen.zig:10718-10725)
   8737         case AST_NODE_PTR_TYPE_ALIGNED:
   8738         case AST_NODE_PTR_TYPE_SENTINEL:
   8739         case AST_NODE_PTR_TYPE:
   8740         case AST_NODE_PTR_TYPE_BIT_RANGE:
   8741         case AST_NODE_OPTIONAL_TYPE:
   8742         case AST_NODE_ANYFRAME_TYPE:
   8743         case AST_NODE_ARRAY_TYPE_SENTINEL:
   8744             return true;
   8745         // Forward to LHS: try, comptime, nosuspend
   8746         // (AstGen.zig:10710-10713)
   8747         case AST_NODE_TRY:
   8748         case AST_NODE_COMPTIME:
   8749         case AST_NODE_NOSUSPEND:
   8750             cur = tree->nodes.datas[cur].lhs;
   8751             continue;
   8752         // Forward to LHS: grouped_expression, unwrap_optional
   8753         // (AstGen.zig:10714-10716)
   8754         case AST_NODE_GROUPED_EXPRESSION:
   8755         case AST_NODE_UNWRAP_OPTIONAL:
   8756             cur = tree->nodes.datas[cur].lhs;
   8757             continue;
   8758         // Identifier: check primitives (AstGen.zig:10727-10780)
   8759         case AST_NODE_IDENTIFIER:
   8760             return identImpliesMoreThanOnePossibleValue(
   8761                 tree, tree->nodes.main_tokens[cur]);
   8762         default:
   8763             return false;
   8764         }
   8765     }
   8766 }
   8767 
   8768 // --- nodeImpliesComptimeOnly (AstGen.zig:10787) ---
   8769 
   8770 static bool identImpliesComptimeOnly(const Ast* tree, uint32_t main_token) {
   8771     uint32_t start = tree->tokens.starts[main_token];
   8772     const char* src = tree->source + start;
   8773     // Only comptime_float, comptime_int, type → true
   8774     // (AstGen.zig:11010-11013)
   8775     if (src[0] == 'c' && src[1] == 'o' && src[2] == 'm' && src[3] == 'p'
   8776         && src[4] == 't' && src[5] == 'i' && src[6] == 'm' && src[7] == 'e')
   8777         return true; // comptime_float, comptime_int
   8778     if (src[0] == 't' && src[1] == 'y' && src[2] == 'p' && src[3] == 'e'
   8779         && !(src[4] >= 'a' && src[4] <= 'z')
   8780         && !(src[4] >= 'A' && src[4] <= 'Z')
   8781         && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_')
   8782         return true;
   8783     return false;
   8784 }
   8785 
   8786 static bool nodeImpliesComptimeOnly(const Ast* tree, uint32_t node) {
   8787     uint32_t cur = node;
   8788     while (1) {
   8789         AstNodeTag tag = tree->nodes.tags[cur];
   8790         switch (tag) {
   8791         // Function prototypes → true (AstGen.zig:10950-10955)
   8792         case AST_NODE_FN_PROTO_SIMPLE:
   8793         case AST_NODE_FN_PROTO_MULTI:
   8794         case AST_NODE_FN_PROTO_ONE:
   8795         case AST_NODE_FN_PROTO:
   8796             return true;
   8797         // Forward to LHS: try, comptime, nosuspend
   8798         case AST_NODE_TRY:
   8799         case AST_NODE_COMPTIME:
   8800         case AST_NODE_NOSUSPEND:
   8801             cur = tree->nodes.datas[cur].lhs;
   8802             continue;
   8803         case AST_NODE_GROUPED_EXPRESSION:
   8804         case AST_NODE_UNWRAP_OPTIONAL:
   8805             cur = tree->nodes.datas[cur].lhs;
   8806             continue;
   8807         // Identifier: check primitives
   8808         case AST_NODE_IDENTIFIER:
   8809             return identImpliesComptimeOnly(
   8810                 tree, tree->nodes.main_tokens[cur]);
   8811         default:
   8812             return false;
   8813         }
   8814     }
   8815 }
   8816 
   8817 // --- WipMembers (AstGen.zig:3989) ---
   8818 // Tracks decl indices, field bit-flags, and per-field data during container
   8819 // processing. All data lives in a single malloc'd array laid out as:
   8820 //   [decls (decl_count)] [field_bits (ceil)] [fields (up to field_count*max)]
   8821 // Bodies are tracked separately in a dynamic array.
   8822 
   8823 typedef struct {
   8824     uint32_t* payload; // malloc'd array
   8825     uint32_t payload_top; // always 0 (start of decls region)
   8826     uint32_t field_bits_start;
   8827     uint32_t fields_start;
   8828     uint32_t fields_end;
   8829     uint32_t decl_index;
   8830     uint32_t field_index;
   8831     // Bodies scratch: dynamically grown array for field type/align/init
   8832     // bodies.
   8833     uint32_t* bodies;
   8834     uint32_t bodies_len;
   8835     uint32_t bodies_cap;
   8836 } WipMembers;
   8837 
   8838 static WipMembers wipMembersInit(uint32_t decl_count, uint32_t field_count) {
   8839     // bits_per_field = 4, max_field_size = 5
   8840     uint32_t fields_per_u32 = 8; // 32 / 4
   8841     uint32_t field_bits_start = decl_count;
   8842     uint32_t bit_words = field_count > 0
   8843         ? (field_count + fields_per_u32 - 1) / fields_per_u32
   8844         : 0;
   8845     uint32_t fields_start = field_bits_start + bit_words;
   8846     uint32_t payload_end = fields_start + field_count * 5;
   8847     uint32_t alloc_size = payload_end > 0 ? payload_end : 1;
   8848     uint32_t* payload = calloc(alloc_size, sizeof(uint32_t));
   8849     if (!payload)
   8850         exit(1);
   8851     WipMembers wm;
   8852     memset(&wm, 0, sizeof(wm));
   8853     wm.payload = payload;
   8854     wm.payload_top = 0;
   8855     wm.field_bits_start = field_bits_start;
   8856     wm.fields_start = fields_start;
   8857     wm.fields_end = fields_start;
   8858     wm.decl_index = 0;
   8859     wm.field_index = 0;
   8860     wm.bodies = NULL;
   8861     wm.bodies_len = 0;
   8862     wm.bodies_cap = 0;
   8863     return wm;
   8864 }
   8865 
   8866 static void wipMembersDeinit(WipMembers* wm) {
   8867     free(wm->payload);
   8868     free(wm->bodies);
   8869 }
   8870 
   8871 static void wipMembersNextDecl(WipMembers* wm, uint32_t decl_inst) {
   8872     wm->payload[wm->payload_top + wm->decl_index] = decl_inst;
   8873     wm->decl_index++;
   8874 }
   8875 
   8876 // bits_per_field = 4: bits[0]=have_align, bits[1]=have_value,
   8877 // bits[2]=is_comptime, bits[3]=have_type_body
   8878 static void wipMembersNextField(WipMembers* wm, bool bits[4]) {
   8879     uint32_t fields_per_u32 = 8; // 32 / 4
   8880     uint32_t index = wm->field_bits_start + wm->field_index / fields_per_u32;
   8881     uint32_t bit_bag
   8882         = (wm->field_index % fields_per_u32 == 0) ? 0 : wm->payload[index];
   8883     bit_bag >>= 4;
   8884     for (int i = 0; i < 4; i++) {
   8885         bit_bag |= ((uint32_t)(bits[i] ? 1 : 0)) << (32 - 4 + i);
   8886     }
   8887     wm->payload[index] = bit_bag;
   8888     wm->field_index++;
   8889 }
   8890 
   8891 static void wipMembersAppendToField(WipMembers* wm, uint32_t data) {
   8892     wm->payload[wm->fields_end] = data;
   8893     wm->fields_end++;
   8894 }
   8895 
   8896 static void wipMembersFinishBits(WipMembers* wm) {
   8897     uint32_t fields_per_u32 = 8; // 32 / 4
   8898     uint32_t empty_field_slots
   8899         = fields_per_u32 - (wm->field_index % fields_per_u32);
   8900     if (wm->field_index > 0 && empty_field_slots < fields_per_u32) {
   8901         uint32_t index
   8902             = wm->field_bits_start + wm->field_index / fields_per_u32;
   8903         wm->payload[index] >>= (empty_field_slots * 4);
   8904     }
   8905 }
   8906 
   8907 // Returns pointer to decls region and its length.
   8908 static const uint32_t* wipMembersDeclsSlice(
   8909     const WipMembers* wm, uint32_t* out_len) {
   8910     *out_len = wm->decl_index;
   8911     return wm->payload + wm->payload_top;
   8912 }
   8913 
   8914 // Returns pointer to fields region (field_bits + field_data) and its length.
   8915 static const uint32_t* wipMembersFieldsSlice(
   8916     const WipMembers* wm, uint32_t* out_len) {
   8917     *out_len = wm->fields_end - wm->field_bits_start;
   8918     return wm->payload + wm->field_bits_start;
   8919 }
   8920 
   8921 // Append body instructions to the WipMembers bodies scratch.
   8922 static void wipMembersBodiesAppend(
   8923     WipMembers* wm, const uint32_t* data, uint32_t len) {
   8924     if (wm->bodies_len + len > wm->bodies_cap) {
   8925         uint32_t new_cap = wm->bodies_cap == 0 ? 64 : wm->bodies_cap * 2;
   8926         while (new_cap < wm->bodies_len + len)
   8927             new_cap *= 2;
   8928         wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t));
   8929         if (!wm->bodies)
   8930             exit(1);
   8931         wm->bodies_cap = new_cap;
   8932     }
   8933     memcpy(wm->bodies + wm->bodies_len, data, len * sizeof(uint32_t));
   8934     wm->bodies_len += len;
   8935 }
   8936 
   8937 // Append body instructions with ref_table fixups to wm->bodies.
   8938 static void wipMembersBodiesAppendWithFixups(
   8939     WipMembers* wm, AstGenCtx* ag, const uint32_t* body, uint32_t body_len) {
   8940     for (uint32_t i = 0; i < body_len; i++) {
   8941         uint32_t inst = body[i];
   8942         // Grow if needed.
   8943         if (wm->bodies_len + 1 > wm->bodies_cap) {
   8944             uint32_t new_cap = wm->bodies_cap == 0 ? 64 : wm->bodies_cap * 2;
   8945             wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t));
   8946             if (!wm->bodies)
   8947                 exit(1);
   8948             wm->bodies_cap = new_cap;
   8949         }
   8950         wm->bodies[wm->bodies_len++] = inst;
   8951         // Check for ref fixup.
   8952         uint32_t ref_inst;
   8953         while (refTableFetchRemove(ag, inst, &ref_inst)) {
   8954             if (wm->bodies_len + 1 > wm->bodies_cap) {
   8955                 uint32_t new_cap = wm->bodies_cap * 2;
   8956                 wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t));
   8957                 if (!wm->bodies)
   8958                     exit(1);
   8959                 wm->bodies_cap = new_cap;
   8960             }
   8961             wm->bodies[wm->bodies_len++] = ref_inst;
   8962             inst = ref_inst;
   8963         }
   8964     }
   8965 }
   8966 
   8967 // --- containerDecl (AstGen.zig:5468) ---
   8968 // Handles container declarations as expressions (struct{}, enum{}, etc.).
   8969 
   8970 static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node) {
   8971     AstGenCtx* ag = gz->astgen;
   8972     const Ast* tree = ag->tree;
   8973     AstNodeTag tag = tree->nodes.tags[node];
   8974     AstData nd = tree->nodes.datas[node];
   8975 
   8976     // Extract members based on node type (Ast.zig:2459-2470).
   8977     uint32_t members_buf[2];
   8978     const uint32_t* members;
   8979     uint32_t members_len;
   8980 
   8981     switch (tag) {
   8982     case AST_NODE_CONTAINER_DECL_TWO:
   8983     case AST_NODE_CONTAINER_DECL_TWO_TRAILING:
   8984     case AST_NODE_TAGGED_UNION_TWO:
   8985     case AST_NODE_TAGGED_UNION_TWO_TRAILING: {
   8986         // lhs and rhs are optional member nodes (0 = none).
   8987         members_len = 0;
   8988         if (nd.lhs != 0)
   8989             members_buf[members_len++] = nd.lhs;
   8990         if (nd.rhs != 0)
   8991             members_buf[members_len++] = nd.rhs;
   8992         members = members_buf;
   8993         break;
   8994     }
   8995     case AST_NODE_CONTAINER_DECL:
   8996     case AST_NODE_CONTAINER_DECL_TRAILING:
   8997     case AST_NODE_TAGGED_UNION:
   8998     case AST_NODE_TAGGED_UNION_TRAILING: {
   8999         // extra_data[lhs..rhs] contains members.
   9000         members = tree->extra_data.arr + nd.lhs;
   9001         members_len = nd.rhs - nd.lhs;
   9002         break;
   9003     }
   9004     case AST_NODE_CONTAINER_DECL_ARG:
   9005     case AST_NODE_CONTAINER_DECL_ARG_TRAILING:
   9006     case AST_NODE_TAGGED_UNION_ENUM_TAG:
   9007     case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: {
   9008         // lhs is arg node, rhs is extra index → SubRange(start, end).
   9009         uint32_t start = tree->extra_data.arr[nd.rhs];
   9010         uint32_t end = tree->extra_data.arr[nd.rhs + 1];
   9011         members = tree->extra_data.arr + start;
   9012         members_len = end - start;
   9013         break;
   9014     }
   9015     default:
   9016         SET_ERROR(ag);
   9017         return ZIR_REF_VOID_VALUE;
   9018     }
   9019 
   9020     // Save/clear fn_block for nested containers (AstGen.zig:5480-5482).
   9021     void* prev_fn_block = ag->fn_block;
   9022     ag->fn_block = NULL;
   9023 
   9024     // Dispatch based on container keyword (AstGen.zig:5485-5536).
   9025     uint32_t main_token = tree->nodes.main_tokens[node];
   9026     TokenizerTag kw_tag = tree->tokens.tags[main_token];
   9027     uint32_t decl_inst;
   9028     switch (kw_tag) {
   9029     case TOKEN_KEYWORD_STRUCT:
   9030         decl_inst = structDeclInner(ag, gz, node, members, members_len);
   9031         break;
   9032     case TOKEN_KEYWORD_ENUM:
   9033         decl_inst = enumDeclInner(ag, gz, node, members, members_len);
   9034         break;
   9035     default:
   9036         // union/opaque: fall back to struct for now.
   9037         decl_inst = structDeclInner(ag, gz, node, members, members_len);
   9038         break;
   9039     }
   9040     (void)scope;
   9041 
   9042     ag->fn_block = prev_fn_block;
   9043     return decl_inst + ZIR_REF_START_INDEX;
   9044 }
   9045 
   9046 // --- EnumDecl.Small packing (Zir.zig EnumDecl.Small) ---
   9047 
   9048 typedef struct {
   9049     bool has_tag_type;
   9050     bool has_captures_len;
   9051     bool has_body_len;
   9052     bool has_fields_len;
   9053     bool has_decls_len;
   9054     uint8_t name_strategy; // 2 bits
   9055     bool nonexhaustive;
   9056 } EnumDeclSmall;
   9057 
   9058 static uint16_t packEnumDeclSmall(EnumDeclSmall s) {
   9059     uint16_t r = 0;
   9060     if (s.has_tag_type)
   9061         r |= (1u << 0);
   9062     if (s.has_captures_len)
   9063         r |= (1u << 1);
   9064     if (s.has_body_len)
   9065         r |= (1u << 2);
   9066     if (s.has_fields_len)
   9067         r |= (1u << 3);
   9068     if (s.has_decls_len)
   9069         r |= (1u << 4);
   9070     r |= (uint16_t)(s.name_strategy & 0x3u) << 5;
   9071     if (s.nonexhaustive)
   9072         r |= (1u << 7);
   9073     return r;
   9074 }
   9075 
   9076 // Mirrors GenZir.setEnum (AstGen.zig:13080).
   9077 static void setEnum(AstGenCtx* ag, uint32_t inst, uint32_t src_node,
   9078     EnumDeclSmall small, uint32_t fields_len, uint32_t decls_len) {
   9079     ensureExtraCapacity(ag, 6 + 3);
   9080 
   9081     uint32_t payload_index = ag->extra_len;
   9082 
   9083     // fields_hash (4 words): zero-filled.
   9084     ag->extra[ag->extra_len++] = 0;
   9085     ag->extra[ag->extra_len++] = 0;
   9086     ag->extra[ag->extra_len++] = 0;
   9087     ag->extra[ag->extra_len++] = 0;
   9088 
   9089     ag->extra[ag->extra_len++] = ag->source_line;
   9090     ag->extra[ag->extra_len++] = src_node;
   9091 
   9092     if (small.has_fields_len)
   9093         ag->extra[ag->extra_len++] = fields_len;
   9094     if (small.has_decls_len)
   9095         ag->extra[ag->extra_len++] = decls_len;
   9096 
   9097     ag->inst_tags[inst] = ZIR_INST_EXTENDED;
   9098     ZirInstData data;
   9099     memset(&data, 0, sizeof(data));
   9100     data.extended.opcode = (uint16_t)ZIR_EXT_ENUM_DECL;
   9101     data.extended.small = packEnumDeclSmall(small);
   9102     data.extended.operand = payload_index;
   9103     ag->inst_datas[inst] = data;
   9104 }
   9105 
   9106 // --- enumDeclInner (AstGen.zig:5508) ---
   9107 
   9108 static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node,
   9109     const uint32_t* members, uint32_t members_len) {
   9110     const Ast* tree = ag->tree;
   9111     uint32_t decl_inst = reserveInstructionIndex(ag);
   9112     gzAppendInstruction(gz, decl_inst);
   9113 
   9114     if (members_len == 0) {
   9115         EnumDeclSmall small;
   9116         memset(&small, 0, sizeof(small));
   9117         setEnum(ag, decl_inst, node, small, 0, 0);
   9118         return decl_inst;
   9119     }
   9120 
   9121     advanceSourceCursorToNode(ag, node);
   9122 
   9123     uint32_t decl_count = scanContainer(ag, members, members_len);
   9124     uint32_t field_count = members_len - decl_count;
   9125 
   9126     // Use WipMembers for decls and field data.
   9127     // Enum fields: 1 bit per field (has_value), max 2 words per field
   9128     // (name + value).
   9129     WipMembers wm = wipMembersInit(decl_count, field_count);
   9130 
   9131     // Enum fields use 1 bit per field: has_value.
   9132     // We use the same WipMembers but with 1-bit fields.
   9133     // Actually, upstream uses bits_per_field=1, max_field_size=2.
   9134     // Re-init with correct params would be better but let's reuse.
   9135     // For simplicity: track field data manually.
   9136     uint32_t* field_names = NULL;
   9137     uint32_t field_names_len = 0;
   9138     uint32_t field_names_cap = 0;
   9139 
   9140     for (uint32_t i = 0; i < members_len; i++) {
   9141         uint32_t member_node = members[i];
   9142         AstNodeTag mtag = tree->nodes.tags[member_node];
   9143         switch (mtag) {
   9144         case AST_NODE_COMPTIME:
   9145             comptimeDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9146             break;
   9147         case AST_NODE_SIMPLE_VAR_DECL:
   9148         case AST_NODE_GLOBAL_VAR_DECL:
   9149         case AST_NODE_LOCAL_VAR_DECL:
   9150         case AST_NODE_ALIGNED_VAR_DECL:
   9151             globalVarDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9152             break;
   9153         case AST_NODE_FN_DECL:
   9154             fnDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9155             break;
   9156         case AST_NODE_TEST_DECL:
   9157             testDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9158             break;
   9159         case AST_NODE_CONTAINER_FIELD_INIT:
   9160         case AST_NODE_CONTAINER_FIELD_ALIGN:
   9161         case AST_NODE_CONTAINER_FIELD: {
   9162             // Enum field: just a name (AstGen.zig:5617-5670).
   9163             uint32_t main_token = tree->nodes.main_tokens[member_node];
   9164             uint32_t field_name = identAsString(ag, main_token);
   9165             // Grow field_names array.
   9166             if (field_names_len >= field_names_cap) {
   9167                 uint32_t new_cap
   9168                     = field_names_cap == 0 ? 8 : field_names_cap * 2;
   9169                 field_names = realloc(field_names, new_cap * sizeof(uint32_t));
   9170                 if (!field_names)
   9171                     exit(1);
   9172                 field_names_cap = new_cap;
   9173             }
   9174             field_names[field_names_len++] = field_name;
   9175             break;
   9176         }
   9177         default:
   9178             SET_ERROR(ag);
   9179             break;
   9180         }
   9181     }
   9182 
   9183     EnumDeclSmall small;
   9184     memset(&small, 0, sizeof(small));
   9185     small.has_fields_len = (field_count > 0);
   9186     small.has_decls_len = (decl_count > 0);
   9187     setEnum(ag, decl_inst, node, small, field_count, decl_count);
   9188 
   9189     // Append: decls, field_bits, field_names (AstGen.zig:5724-5729).
   9190     uint32_t decls_len_out;
   9191     const uint32_t* decls_slice = wipMembersDeclsSlice(&wm, &decls_len_out);
   9192 
   9193     // Field bits: 1 bit per field (has_value = false for simple enums).
   9194     uint32_t fields_per_u32 = 32;
   9195     uint32_t bit_words = field_count > 0
   9196         ? (field_count + fields_per_u32 - 1) / fields_per_u32
   9197         : 0;
   9198 
   9199     ensureExtraCapacity(ag, decls_len_out + bit_words + field_names_len);
   9200     for (uint32_t i = 0; i < decls_len_out; i++)
   9201         ag->extra[ag->extra_len++] = decls_slice[i];
   9202     // Field bits: all zero (no values).
   9203     for (uint32_t i = 0; i < bit_words; i++)
   9204         ag->extra[ag->extra_len++] = 0;
   9205     // Field names.
   9206     for (uint32_t i = 0; i < field_names_len; i++)
   9207         ag->extra[ag->extra_len++] = field_names[i];
   9208 
   9209     free(field_names);
   9210     wipMembersDeinit(&wm);
   9211     return decl_inst;
   9212 }
   9213 
   9214 // --- structDeclInner (AstGen.zig:4926) ---
   9215 
   9216 static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node,
   9217     const uint32_t* members, uint32_t members_len) {
   9218     const Ast* tree = ag->tree;
   9219     uint32_t decl_inst = reserveInstructionIndex(ag);
   9220     gzAppendInstruction(gz, decl_inst);
   9221 
   9222     // Fast path: no members, no backing int (AstGen.zig:4954-4970).
   9223     if (members_len == 0) {
   9224         StructDeclSmall small;
   9225         memset(&small, 0, sizeof(small));
   9226         setStruct(ag, decl_inst, node, small, 0, 0, 0);
   9227         return decl_inst;
   9228     }
   9229 
   9230     // Non-empty container (AstGen.zig:4973-5189).
   9231     advanceSourceCursorToNode(ag, node);
   9232 
   9233     uint32_t decl_count = scanContainer(ag, members, members_len);
   9234     uint32_t field_count = members_len - decl_count;
   9235 
   9236     WipMembers wm = wipMembersInit(decl_count, field_count);
   9237 
   9238     // Set up block_scope for field type/align/init expressions.
   9239     // (AstGen.zig:4983-4992)
   9240     GenZir block_scope;
   9241     memset(&block_scope, 0, sizeof(block_scope));
   9242     block_scope.base.tag = SCOPE_GEN_ZIR;
   9243     block_scope.parent = NULL;
   9244     block_scope.astgen = ag;
   9245     block_scope.decl_node_index = node;
   9246     block_scope.decl_line = ag->source_line;
   9247     block_scope.is_comptime = true;
   9248     block_scope.instructions_top = ag->scratch_inst_len;
   9249 
   9250     bool known_non_opv = false;
   9251     bool known_comptime_only = false;
   9252     bool any_comptime_fields = false;
   9253     bool any_aligned_fields = false;
   9254     bool any_default_inits = false;
   9255 
   9256     // Process each member (AstGen.zig:5060-5147).
   9257     for (uint32_t i = 0; i < members_len; i++) {
   9258         uint32_t member_node = members[i];
   9259         AstNodeTag mtag = tree->nodes.tags[member_node];
   9260         switch (mtag) {
   9261         case AST_NODE_COMPTIME:
   9262             comptimeDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9263             break;
   9264         case AST_NODE_SIMPLE_VAR_DECL:
   9265             globalVarDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9266             break;
   9267         case AST_NODE_TEST_DECL:
   9268             testDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9269             break;
   9270         case AST_NODE_FN_DECL:
   9271             fnDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9272             break;
   9273         case AST_NODE_USINGNAMESPACE:
   9274         case AST_NODE_GLOBAL_VAR_DECL:
   9275         case AST_NODE_LOCAL_VAR_DECL:
   9276         case AST_NODE_ALIGNED_VAR_DECL:
   9277             globalVarDecl(ag, gz, wm.payload, &wm.decl_index, member_node);
   9278             break;
   9279         case AST_NODE_CONTAINER_FIELD_INIT:
   9280         case AST_NODE_CONTAINER_FIELD_ALIGN:
   9281         case AST_NODE_CONTAINER_FIELD: {
   9282             // Extract field info from AST node (Ast.zig:1413-1454).
   9283             uint32_t main_token = tree->nodes.main_tokens[member_node];
   9284             AstData nd = tree->nodes.datas[member_node];
   9285             uint32_t type_node = nd.lhs;
   9286             uint32_t align_node = 0;
   9287             uint32_t value_node = 0;
   9288             bool has_comptime_token = false;
   9289 
   9290             switch (mtag) {
   9291             case AST_NODE_CONTAINER_FIELD_INIT:
   9292                 // lhs = type_expr, rhs = value_expr (optional, 0=none)
   9293                 value_node = nd.rhs;
   9294                 break;
   9295             case AST_NODE_CONTAINER_FIELD_ALIGN:
   9296                 // lhs = type_expr, rhs = align_expr
   9297                 align_node = nd.rhs;
   9298                 break;
   9299             case AST_NODE_CONTAINER_FIELD:
   9300                 // lhs = type_expr, rhs = extra index to {align, value}
   9301                 if (nd.rhs != 0) {
   9302                     align_node = tree->extra_data.arr[nd.rhs];
   9303                     value_node = tree->extra_data.arr[nd.rhs + 1];
   9304                 }
   9305                 break;
   9306             default:
   9307                 break;
   9308             }
   9309 
   9310             // Check for comptime token preceding main_token
   9311             // (Ast.zig:2071-2082).
   9312             if (main_token > 0
   9313                 && tree->tokens.tags[main_token - 1]
   9314                     == TOKEN_KEYWORD_COMPTIME) {
   9315                 has_comptime_token = true;
   9316             }
   9317 
   9318             // Field name (AstGen.zig:5080).
   9319             uint32_t field_name = identAsString(ag, main_token);
   9320             wipMembersAppendToField(&wm, field_name);
   9321 
   9322             // Type expression (AstGen.zig:5089-5109).
   9323             bool have_type_body = false;
   9324             uint32_t field_type = 0;
   9325             if (type_node != 0) {
   9326                 field_type
   9327                     = typeExpr(&block_scope, &block_scope.base, type_node);
   9328                 have_type_body = (gzInstructionsLen(&block_scope) > 0);
   9329             }
   9330 
   9331             bool have_align = (align_node != 0);
   9332             bool have_value = (value_node != 0);
   9333             bool is_comptime = has_comptime_token;
   9334 
   9335             if (is_comptime) {
   9336                 any_comptime_fields = true;
   9337             } else {
   9338                 // (AstGen.zig:5106-5109)
   9339                 if (type_node != 0) {
   9340                     known_non_opv = known_non_opv
   9341                         || nodeImpliesMoreThanOnePossibleValue(
   9342                             tree, type_node);
   9343                     known_comptime_only = known_comptime_only
   9344                         || nodeImpliesComptimeOnly(tree, type_node);
   9345                 }
   9346             }
   9347 
   9348             bool field_bits[4]
   9349                 = { have_align, have_value, is_comptime, have_type_body };
   9350             wipMembersNextField(&wm, field_bits);
   9351 
   9352             if (have_type_body) {
   9353                 // Emit break_inline to carry the type value
   9354                 // (AstGen.zig:5097-5099).
   9355                 if (!endsWithNoReturn(&block_scope)) {
   9356                     makeBreakInline(&block_scope, decl_inst, field_type,
   9357                         AST_NODE_OFFSET_NONE);
   9358                 }
   9359                 uint32_t raw_len = gzInstructionsLen(&block_scope);
   9360                 const uint32_t* body = gzInstructionsSlice(&block_scope);
   9361                 uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_len);
   9362                 uint32_t bodies_before = wm.bodies_len;
   9363                 wipMembersBodiesAppendWithFixups(&wm, ag, body, raw_len);
   9364                 (void)bodies_before;
   9365                 wipMembersAppendToField(&wm, body_len);
   9366                 // Reset block_scope.
   9367                 ag->scratch_inst_len = block_scope.instructions_top;
   9368             } else {
   9369                 wipMembersAppendToField(&wm, field_type);
   9370             }
   9371 
   9372             if (have_align) {
   9373                 any_aligned_fields = true;
   9374                 uint32_t align_ref
   9375                     = expr(&block_scope, &block_scope.base, align_node);
   9376                 if (!endsWithNoReturn(&block_scope)) {
   9377                     makeBreakInline(&block_scope, decl_inst, align_ref,
   9378                         AST_NODE_OFFSET_NONE);
   9379                 }
   9380                 uint32_t raw_len = gzInstructionsLen(&block_scope);
   9381                 const uint32_t* body = gzInstructionsSlice(&block_scope);
   9382                 uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_len);
   9383                 wipMembersBodiesAppendWithFixups(&wm, ag, body, raw_len);
   9384                 wipMembersAppendToField(&wm, body_len);
   9385                 ag->scratch_inst_len = block_scope.instructions_top;
   9386             }
   9387 
   9388             if (have_value) {
   9389                 any_default_inits = true;
   9390                 uint32_t default_ref
   9391                     = expr(&block_scope, &block_scope.base, value_node);
   9392                 if (!endsWithNoReturn(&block_scope)) {
   9393                     makeBreakInline(&block_scope, decl_inst, default_ref,
   9394                         AST_NODE_OFFSET_NONE);
   9395                 }
   9396                 uint32_t raw_len = gzInstructionsLen(&block_scope);
   9397                 const uint32_t* body = gzInstructionsSlice(&block_scope);
   9398                 uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_len);
   9399                 wipMembersBodiesAppendWithFixups(&wm, ag, body, raw_len);
   9400                 wipMembersAppendToField(&wm, body_len);
   9401                 ag->scratch_inst_len = block_scope.instructions_top;
   9402             }
   9403             break;
   9404         }
   9405         default:
   9406             SET_ERROR(ag);
   9407             break;
   9408         }
   9409     }
   9410 
   9411     wipMembersFinishBits(&wm);
   9412 
   9413     // setStruct (AstGen.zig:5152-5166).
   9414     StructDeclSmall small;
   9415     memset(&small, 0, sizeof(small));
   9416     small.has_decls_len = (decl_count > 0);
   9417     small.has_fields_len = (field_count > 0);
   9418     small.known_non_opv = known_non_opv;
   9419     small.known_comptime_only = known_comptime_only;
   9420     small.any_comptime_fields = any_comptime_fields;
   9421     small.any_default_inits = any_default_inits;
   9422     small.any_aligned_fields = any_aligned_fields;
   9423     setStruct(ag, decl_inst, node, small, 0, field_count, decl_count);
   9424 
   9425     // Append: captures (none), backing_int (none), decls, fields, bodies
   9426     // (AstGen.zig:5176-5189).
   9427     uint32_t decls_len;
   9428     const uint32_t* decls_slice = wipMembersDeclsSlice(&wm, &decls_len);
   9429     uint32_t fields_len;
   9430     const uint32_t* fields_slice = wipMembersFieldsSlice(&wm, &fields_len);
   9431 
   9432     ensureExtraCapacity(ag, decls_len + fields_len + wm.bodies_len);
   9433     for (uint32_t i = 0; i < decls_len; i++)
   9434         ag->extra[ag->extra_len++] = decls_slice[i];
   9435     for (uint32_t i = 0; i < fields_len; i++)
   9436         ag->extra[ag->extra_len++] = fields_slice[i];
   9437     for (uint32_t i = 0; i < wm.bodies_len; i++)
   9438         ag->extra[ag->extra_len++] = wm.bodies[i];
   9439 
   9440     gzUnstack(&block_scope);
   9441     wipMembersDeinit(&wm);
   9442     return decl_inst;
   9443 }
   9444 
   9445 // --- AstRlAnnotate (AstRlAnnotate.zig) ---
   9446 // Pre-pass to determine which AST nodes need result locations.
   9447 
   9448 typedef struct {
   9449     bool have_type;
   9450     bool have_ptr;
   9451 } RlResultInfo;
   9452 
   9453 #define RL_RI_NONE ((RlResultInfo) { false, false })
   9454 #define RL_RI_TYPED_PTR ((RlResultInfo) { true, true })
   9455 #define RL_RI_INFERRED_PTR ((RlResultInfo) { false, true })
   9456 #define RL_RI_TYPE_ONLY ((RlResultInfo) { true, false })
   9457 
   9458 // Block for label tracking (AstRlAnnotate.zig:56-62).
   9459 typedef struct RlBlock {
   9460     struct RlBlock* parent;
   9461     uint32_t label_token; // UINT32_MAX = no label
   9462     bool is_loop;
   9463     RlResultInfo ri;
   9464     bool consumes_res_ptr;
   9465 } RlBlock;
   9466 
   9467 static void nodesNeedRlAdd(AstGenCtx* ag, uint32_t node) {
   9468     if (ag->nodes_need_rl_len >= ag->nodes_need_rl_cap) {
   9469         uint32_t new_cap
   9470             = ag->nodes_need_rl_cap == 0 ? 16 : ag->nodes_need_rl_cap * 2;
   9471         ag->nodes_need_rl
   9472             = realloc(ag->nodes_need_rl, new_cap * sizeof(uint32_t));
   9473         ag->nodes_need_rl_cap = new_cap;
   9474     }
   9475     ag->nodes_need_rl[ag->nodes_need_rl_len++] = node;
   9476 }
   9477 
   9478 static bool nodesNeedRlContains(const AstGenCtx* ag, uint32_t node) {
   9479     for (uint32_t i = 0; i < ag->nodes_need_rl_len; i++) {
   9480         if (ag->nodes_need_rl[i] == node)
   9481             return true;
   9482     }
   9483     return false;
   9484 }
   9485 
   9486 // Compare two identifier tokens by their source text.
   9487 static bool rlTokenIdentEqual(
   9488     const Ast* tree, uint32_t tok_a, uint32_t tok_b) {
   9489     const char* src = tree->source;
   9490     uint32_t a_start = tree->tokens.starts[tok_a];
   9491     uint32_t b_start = tree->tokens.starts[tok_b];
   9492     for (uint32_t i = 0;; i++) {
   9493         char ca = src[a_start + i];
   9494         char cb = src[b_start + i];
   9495         bool a_id = (ca >= 'a' && ca <= 'z') || (ca >= 'A' && ca <= 'Z')
   9496             || (ca >= '0' && ca <= '9') || ca == '_';
   9497         bool b_id = (cb >= 'a' && cb <= 'z') || (cb >= 'A' && cb <= 'Z')
   9498             || (cb >= '0' && cb <= '9') || cb == '_';
   9499         if (!a_id && !b_id)
   9500             return true;
   9501         if (!a_id || !b_id)
   9502             return false;
   9503         if (ca != cb)
   9504             return false;
   9505     }
   9506 }
   9507 
   9508 // Forward declarations.
   9509 static bool rlExpr(
   9510     AstGenCtx* ag, uint32_t node, RlBlock* block, RlResultInfo ri);
   9511 static void rlContainerDecl(AstGenCtx* ag, RlBlock* block, uint32_t node);
   9512 static bool rlBlockExpr(AstGenCtx* ag, RlBlock* parent_block, RlResultInfo ri,
   9513     uint32_t node, const uint32_t* stmts, uint32_t count);
   9514 static bool rlBuiltinCall(AstGenCtx* ag, RlBlock* block, uint32_t node,
   9515     const uint32_t* args, uint32_t nargs);
   9516 
   9517 // containerDecl (AstRlAnnotate.zig:89-127).
   9518 static void rlContainerDecl(AstGenCtx* ag, RlBlock* block, uint32_t node) {
   9519     const Ast* tree = ag->tree;
   9520     AstNodeTag tag = tree->nodes.tags[node];
   9521     AstData nd = tree->nodes.datas[node];
   9522 
   9523     // Extract arg and members depending on variant.
   9524     // All container decls: recurse arg with type_only, members with none.
   9525     // (The keyword type — struct/union/enum/opaque — doesn't matter for RL.)
   9526     uint32_t member_buf[2];
   9527     const uint32_t* members = NULL;
   9528     uint32_t members_len = 0;
   9529     uint32_t arg_node = 0; // 0 = no arg
   9530 
   9531     switch (tag) {
   9532     case AST_NODE_CONTAINER_DECL_TWO:
   9533     case AST_NODE_CONTAINER_DECL_TWO_TRAILING:
   9534     case AST_NODE_TAGGED_UNION_TWO:
   9535     case AST_NODE_TAGGED_UNION_TWO_TRAILING: {
   9536         uint32_t idx = 0;
   9537         if (nd.lhs != 0)
   9538             member_buf[idx++] = nd.lhs;
   9539         if (nd.rhs != 0)
   9540             member_buf[idx++] = nd.rhs;
   9541         members = member_buf;
   9542         members_len = idx;
   9543         break;
   9544     }
   9545     case AST_NODE_CONTAINER_DECL:
   9546     case AST_NODE_CONTAINER_DECL_TRAILING:
   9547     case AST_NODE_TAGGED_UNION:
   9548     case AST_NODE_TAGGED_UNION_TRAILING:
   9549         members = tree->extra_data.arr + nd.lhs;
   9550         members_len = nd.rhs - nd.lhs;
   9551         break;
   9552     case AST_NODE_CONTAINER_DECL_ARG:
   9553     case AST_NODE_CONTAINER_DECL_ARG_TRAILING:
   9554     case AST_NODE_TAGGED_UNION_ENUM_TAG:
   9555     case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: {
   9556         arg_node = nd.lhs;
   9557         uint32_t extra_idx = nd.rhs;
   9558         uint32_t start = tree->extra_data.arr[extra_idx];
   9559         uint32_t end = tree->extra_data.arr[extra_idx + 1];
   9560         members = tree->extra_data.arr + start;
   9561         members_len = end - start;
   9562         break;
   9563     }
   9564     default:
   9565         return;
   9566     }
   9567 
   9568     if (arg_node != 0)
   9569         (void)rlExpr(ag, arg_node, block, RL_RI_TYPE_ONLY);
   9570     for (uint32_t i = 0; i < members_len; i++)
   9571         (void)rlExpr(ag, members[i], block, RL_RI_NONE);
   9572 }
   9573 
   9574 // blockExpr (AstRlAnnotate.zig:787-814).
   9575 static bool rlBlockExpr(AstGenCtx* ag, RlBlock* parent_block, RlResultInfo ri,
   9576     uint32_t node, const uint32_t* stmts, uint32_t count) {
   9577     const Ast* tree = ag->tree;
   9578     uint32_t lbrace = tree->nodes.main_tokens[node];
   9579     bool is_labeled
   9580         = (lbrace >= 2 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON
   9581             && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER);
   9582 
   9583     if (is_labeled) {
   9584         RlBlock new_block;
   9585         new_block.parent = parent_block;
   9586         new_block.label_token = lbrace - 2;
   9587         new_block.is_loop = false;
   9588         new_block.ri = ri;
   9589         new_block.consumes_res_ptr = false;
   9590         for (uint32_t i = 0; i < count; i++)
   9591             (void)rlExpr(ag, stmts[i], &new_block, RL_RI_NONE);
   9592         if (new_block.consumes_res_ptr)
   9593             nodesNeedRlAdd(ag, node);
   9594         return new_block.consumes_res_ptr;
   9595     } else {
   9596         for (uint32_t i = 0; i < count; i++)
   9597             (void)rlExpr(ag, stmts[i], parent_block, RL_RI_NONE);
   9598         return false;
   9599     }
   9600 }
   9601 
   9602 // builtinCall (AstRlAnnotate.zig:816-1100).
   9603 // Simplified: no builtin currently consumes its result location,
   9604 // so we just recurse into all args with RL_RI_NONE.
   9605 static bool rlBuiltinCall(AstGenCtx* ag, RlBlock* block, uint32_t node,
   9606     const uint32_t* args, uint32_t nargs) {
   9607     (void)node;
   9608     for (uint32_t i = 0; i < nargs; i++)
   9609         (void)rlExpr(ag, args[i], block, RL_RI_NONE);
   9610     return false;
   9611 }
   9612 
   9613 // expr (AstRlAnnotate.zig:130-771).
   9614 static bool rlExpr(
   9615     AstGenCtx* ag, uint32_t node, RlBlock* block, RlResultInfo ri) {
   9616     const Ast* tree = ag->tree;
   9617     AstNodeTag tag = tree->nodes.tags[node];
   9618     AstData nd = tree->nodes.datas[node];
   9619 
   9620     switch (tag) {
   9621     // Unreachable nodes (AstRlAnnotate.zig:133-142).
   9622     case AST_NODE_ROOT:
   9623     case AST_NODE_SWITCH_CASE_ONE:
   9624     case AST_NODE_SWITCH_CASE_INLINE_ONE:
   9625     case AST_NODE_SWITCH_CASE:
   9626     case AST_NODE_SWITCH_CASE_INLINE:
   9627     case AST_NODE_SWITCH_RANGE:
   9628     case AST_NODE_FOR_RANGE:
   9629     case AST_NODE_ASM_OUTPUT:
   9630     case AST_NODE_ASM_INPUT:
   9631         return false; // unreachable in upstream
   9632 
   9633     // errdefer (AstRlAnnotate.zig:144-147).
   9634     case AST_NODE_ERRDEFER:
   9635         (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE);
   9636         return false;
   9637 
   9638     // defer (AstRlAnnotate.zig:148-151).
   9639     case AST_NODE_DEFER:
   9640         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9641         return false;
   9642 
   9643     // container_field (AstRlAnnotate.zig:153-167).
   9644     case AST_NODE_CONTAINER_FIELD_INIT: {
   9645         // lhs = type_expr, rhs = value_expr
   9646         if (nd.lhs != 0)
   9647             (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
   9648         if (nd.rhs != 0)
   9649             (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
   9650         return false;
   9651     }
   9652     case AST_NODE_CONTAINER_FIELD_ALIGN: {
   9653         // lhs = type_expr, rhs = align_expr
   9654         if (nd.lhs != 0)
   9655             (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
   9656         if (nd.rhs != 0)
   9657             (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
   9658         return false;
   9659     }
   9660     case AST_NODE_CONTAINER_FIELD: {
   9661         // lhs = type_expr, rhs = extra index to {align_expr, value_expr}
   9662         if (nd.lhs != 0)
   9663             (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
   9664         if (nd.rhs != 0) {
   9665             uint32_t align_node = tree->extra_data.arr[nd.rhs];
   9666             uint32_t value_node = tree->extra_data.arr[nd.rhs + 1];
   9667             if (align_node != 0)
   9668                 (void)rlExpr(ag, align_node, block, RL_RI_TYPE_ONLY);
   9669             if (value_node != 0)
   9670                 (void)rlExpr(ag, value_node, block, RL_RI_TYPE_ONLY);
   9671         }
   9672         return false;
   9673     }
   9674 
   9675     // test_decl (AstRlAnnotate.zig:168-171).
   9676     case AST_NODE_TEST_DECL:
   9677         (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE);
   9678         return false;
   9679 
   9680     // var_decl (AstRlAnnotate.zig:172-202).
   9681     case AST_NODE_GLOBAL_VAR_DECL:
   9682     case AST_NODE_LOCAL_VAR_DECL:
   9683     case AST_NODE_SIMPLE_VAR_DECL:
   9684     case AST_NODE_ALIGNED_VAR_DECL: {
   9685         uint32_t type_node = 0;
   9686         uint32_t init_node = 0;
   9687         uint32_t mut_token = tree->nodes.main_tokens[node];
   9688         if (tag == AST_NODE_SIMPLE_VAR_DECL) {
   9689             type_node = nd.lhs;
   9690             init_node = nd.rhs;
   9691         } else if (tag == AST_NODE_LOCAL_VAR_DECL
   9692             || tag == AST_NODE_GLOBAL_VAR_DECL) {
   9693             type_node = tree->extra_data.arr[nd.lhs];
   9694             init_node = nd.rhs;
   9695         } else { // ALIGNED_VAR_DECL
   9696             init_node = nd.rhs;
   9697         }
   9698         RlResultInfo init_ri;
   9699         if (type_node != 0) {
   9700             (void)rlExpr(ag, type_node, block, RL_RI_TYPE_ONLY);
   9701             init_ri = RL_RI_TYPED_PTR;
   9702         } else {
   9703             init_ri = RL_RI_INFERRED_PTR;
   9704         }
   9705         if (init_node == 0)
   9706             return false;
   9707         bool is_const = (tree->source[tree->tokens.starts[mut_token]] == 'c');
   9708         if (is_const) {
   9709             bool init_consumes_rl = rlExpr(ag, init_node, block, init_ri);
   9710             if (init_consumes_rl)
   9711                 nodesNeedRlAdd(ag, node);
   9712             return false;
   9713         } else {
   9714             (void)rlExpr(ag, init_node, block, init_ri);
   9715             return false;
   9716         }
   9717     }
   9718 
   9719     // assign (AstRlAnnotate.zig:212-217).
   9720     case AST_NODE_ASSIGN:
   9721         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9722         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPED_PTR);
   9723         return false;
   9724 
   9725     // compound assign (AstRlAnnotate.zig:218-240).
   9726     case AST_NODE_ASSIGN_SHL:
   9727     case AST_NODE_ASSIGN_SHL_SAT:
   9728     case AST_NODE_ASSIGN_SHR:
   9729     case AST_NODE_ASSIGN_BIT_AND:
   9730     case AST_NODE_ASSIGN_BIT_OR:
   9731     case AST_NODE_ASSIGN_BIT_XOR:
   9732     case AST_NODE_ASSIGN_DIV:
   9733     case AST_NODE_ASSIGN_SUB:
   9734     case AST_NODE_ASSIGN_SUB_WRAP:
   9735     case AST_NODE_ASSIGN_SUB_SAT:
   9736     case AST_NODE_ASSIGN_MOD:
   9737     case AST_NODE_ASSIGN_ADD:
   9738     case AST_NODE_ASSIGN_ADD_WRAP:
   9739     case AST_NODE_ASSIGN_ADD_SAT:
   9740     case AST_NODE_ASSIGN_MUL:
   9741     case AST_NODE_ASSIGN_MUL_WRAP:
   9742     case AST_NODE_ASSIGN_MUL_SAT:
   9743         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9744         (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE);
   9745         return false;
   9746 
   9747     // shl/shr (AstRlAnnotate.zig:241-246).
   9748     case AST_NODE_SHL:
   9749     case AST_NODE_SHR:
   9750         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9751         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
   9752         return false;
   9753 
   9754     // binary arithmetic/comparison (AstRlAnnotate.zig:247-274).
   9755     case AST_NODE_ADD:
   9756     case AST_NODE_ADD_WRAP:
   9757     case AST_NODE_ADD_SAT:
   9758     case AST_NODE_SUB:
   9759     case AST_NODE_SUB_WRAP:
   9760     case AST_NODE_SUB_SAT:
   9761     case AST_NODE_MUL:
   9762     case AST_NODE_MUL_WRAP:
   9763     case AST_NODE_MUL_SAT:
   9764     case AST_NODE_DIV:
   9765     case AST_NODE_MOD:
   9766     case AST_NODE_SHL_SAT:
   9767     case AST_NODE_BIT_AND:
   9768     case AST_NODE_BIT_OR:
   9769     case AST_NODE_BIT_XOR:
   9770     case AST_NODE_BANG_EQUAL:
   9771     case AST_NODE_EQUAL_EQUAL:
   9772     case AST_NODE_GREATER_THAN:
   9773     case AST_NODE_GREATER_OR_EQUAL:
   9774     case AST_NODE_LESS_THAN:
   9775     case AST_NODE_LESS_OR_EQUAL:
   9776     case AST_NODE_ARRAY_CAT:
   9777         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9778         (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE);
   9779         return false;
   9780 
   9781     // array_mult (AstRlAnnotate.zig:276-281).
   9782     case AST_NODE_ARRAY_MULT:
   9783         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9784         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
   9785         return false;
   9786 
   9787     // error_union, merge_error_sets (AstRlAnnotate.zig:282-287).
   9788     case AST_NODE_ERROR_UNION:
   9789     case AST_NODE_MERGE_ERROR_SETS:
   9790         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9791         (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE);
   9792         return false;
   9793 
   9794     // bool_and, bool_or (AstRlAnnotate.zig:288-295).
   9795     case AST_NODE_BOOL_AND:
   9796     case AST_NODE_BOOL_OR:
   9797         (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
   9798         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
   9799         return false;
   9800 
   9801     // bool_not (AstRlAnnotate.zig:296-299).
   9802     case AST_NODE_BOOL_NOT:
   9803         (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
   9804         return false;
   9805 
   9806     // bit_not, negation, negation_wrap (AstRlAnnotate.zig:300-303).
   9807     case AST_NODE_BIT_NOT:
   9808     case AST_NODE_NEGATION:
   9809     case AST_NODE_NEGATION_WRAP:
   9810         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9811         return false;
   9812 
   9813     // Leaves (AstRlAnnotate.zig:305-320).
   9814     case AST_NODE_IDENTIFIER:
   9815     case AST_NODE_STRING_LITERAL:
   9816     case AST_NODE_MULTILINE_STRING_LITERAL:
   9817     case AST_NODE_NUMBER_LITERAL:
   9818     case AST_NODE_UNREACHABLE_LITERAL:
   9819     case AST_NODE_ASM_SIMPLE:
   9820     case AST_NODE_ASM:
   9821     case AST_NODE_ASM_LEGACY:
   9822     case AST_NODE_ENUM_LITERAL:
   9823     case AST_NODE_ERROR_VALUE:
   9824     case AST_NODE_ANYFRAME_LITERAL:
   9825     case AST_NODE_CONTINUE:
   9826     case AST_NODE_CHAR_LITERAL:
   9827     case AST_NODE_ERROR_SET_DECL:
   9828         return false;
   9829 
   9830     // builtin_call (AstRlAnnotate.zig:322-330).
   9831     case AST_NODE_BUILTIN_CALL_TWO:
   9832     case AST_NODE_BUILTIN_CALL_TWO_COMMA: {
   9833         uint32_t args[2];
   9834         uint32_t nargs = 0;
   9835         if (nd.lhs != 0)
   9836             args[nargs++] = nd.lhs;
   9837         if (nd.rhs != 0)
   9838             args[nargs++] = nd.rhs;
   9839         return rlBuiltinCall(ag, block, node, args, nargs);
   9840     }
   9841     case AST_NODE_BUILTIN_CALL:
   9842     case AST_NODE_BUILTIN_CALL_COMMA: {
   9843         uint32_t start = nd.lhs;
   9844         uint32_t end = nd.rhs;
   9845         return rlBuiltinCall(
   9846             ag, block, node, tree->extra_data.arr + start, end - start);
   9847     }
   9848 
   9849     // call (AstRlAnnotate.zig:332-351).
   9850     case AST_NODE_CALL_ONE:
   9851     case AST_NODE_CALL_ONE_COMMA: {
   9852         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9853         if (nd.rhs != 0)
   9854             (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
   9855         return false;
   9856     }
   9857     case AST_NODE_CALL:
   9858     case AST_NODE_CALL_COMMA: {
   9859         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9860         uint32_t start = tree->extra_data.arr[nd.rhs];
   9861         uint32_t end = tree->extra_data.arr[nd.rhs + 1];
   9862         for (uint32_t i = start; i < end; i++)
   9863             (void)rlExpr(ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY);
   9864         return false;
   9865     }
   9866 
   9867     // return (AstRlAnnotate.zig:353-361).
   9868     case AST_NODE_RETURN:
   9869         if (nd.lhs != 0) {
   9870             bool ret_consumes_rl = rlExpr(ag, nd.lhs, block, RL_RI_TYPED_PTR);
   9871             if (ret_consumes_rl)
   9872                 nodesNeedRlAdd(ag, node);
   9873         }
   9874         return false;
   9875 
   9876     // field_access (AstRlAnnotate.zig:363-367).
   9877     case AST_NODE_FIELD_ACCESS:
   9878         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
   9879         return false;
   9880 
   9881     // if_simple, if (AstRlAnnotate.zig:369-387).
   9882     case AST_NODE_IF_SIMPLE:
   9883     case AST_NODE_IF: {
   9884         uint32_t cond_node = nd.lhs;
   9885         uint32_t then_node, else_node = 0;
   9886         if (tag == AST_NODE_IF_SIMPLE) {
   9887             then_node = nd.rhs;
   9888         } else {
   9889             then_node = tree->extra_data.arr[nd.rhs];
   9890             else_node = tree->extra_data.arr[nd.rhs + 1];
   9891         }
   9892         // Detect payload/error token.
   9893         uint32_t last_cond_tok = lastToken(tree, cond_node);
   9894         uint32_t pipe_tok = last_cond_tok + 2;
   9895         bool has_payload = (pipe_tok < tree->tokens.len
   9896             && tree->tokens.tags[pipe_tok] == TOKEN_PIPE);
   9897         bool has_error = false;
   9898         if (else_node != 0) {
   9899             uint32_t else_tok = lastToken(tree, then_node) + 1;
   9900             has_error = (else_tok + 1 < tree->tokens.len
   9901                 && tree->tokens.tags[else_tok + 1] == TOKEN_PIPE);
   9902         }
   9903         if (has_error || has_payload)
   9904             (void)rlExpr(ag, cond_node, block, RL_RI_NONE);
   9905         else
   9906             (void)rlExpr(ag, cond_node, block, RL_RI_TYPE_ONLY);
   9907 
   9908         if (else_node != 0) {
   9909             bool then_uses = rlExpr(ag, then_node, block, ri);
   9910             bool else_uses = rlExpr(ag, else_node, block, ri);
   9911             bool uses_rl = then_uses || else_uses;
   9912             if (uses_rl)
   9913                 nodesNeedRlAdd(ag, node);
   9914             return uses_rl;
   9915         } else {
   9916             (void)rlExpr(ag, then_node, block, RL_RI_NONE);
   9917             return false;
   9918         }
   9919     }
   9920 
   9921     // while (AstRlAnnotate.zig:389-419).
   9922     case AST_NODE_WHILE_SIMPLE:
   9923     case AST_NODE_WHILE_CONT:
   9924     case AST_NODE_WHILE: {
   9925         uint32_t cond_node = nd.lhs;
   9926         uint32_t body_node, cont_node = 0, else_node = 0;
   9927         if (tag == AST_NODE_WHILE_SIMPLE) {
   9928             body_node = nd.rhs;
   9929         } else if (tag == AST_NODE_WHILE_CONT) {
   9930             cont_node = tree->extra_data.arr[nd.rhs];
   9931             body_node = tree->extra_data.arr[nd.rhs + 1];
   9932         } else {
   9933             cont_node = tree->extra_data.arr[nd.rhs];
   9934             body_node = tree->extra_data.arr[nd.rhs + 1];
   9935             else_node = tree->extra_data.arr[nd.rhs + 2];
   9936         }
   9937         uint32_t main_tok = tree->nodes.main_tokens[node];
   9938         bool is_labeled
   9939             = (main_tok >= 2 && tree->tokens.tags[main_tok - 1] == TOKEN_COLON
   9940                 && tree->tokens.tags[main_tok - 2] == TOKEN_IDENTIFIER);
   9941         uint32_t label_token = is_labeled ? main_tok - 2 : UINT32_MAX;
   9942 
   9943         // Detect payload/error.
   9944         uint32_t last_cond_tok = lastToken(tree, cond_node);
   9945         uint32_t pipe_tok = last_cond_tok + 2;
   9946         bool has_payload = (pipe_tok < tree->tokens.len
   9947             && tree->tokens.tags[pipe_tok] == TOKEN_PIPE);
   9948         // Error token detection for while: check for else |err|.
   9949         bool has_error = false;
   9950         if (else_node != 0) {
   9951             uint32_t else_tok = lastToken(tree, body_node) + 1;
   9952             has_error = (else_tok + 1 < tree->tokens.len
   9953                 && tree->tokens.tags[else_tok + 1] == TOKEN_PIPE);
   9954         }
   9955         if (has_error || has_payload)
   9956             (void)rlExpr(ag, cond_node, block, RL_RI_NONE);
   9957         else
   9958             (void)rlExpr(ag, cond_node, block, RL_RI_TYPE_ONLY);
   9959 
   9960         RlBlock new_block;
   9961         new_block.parent = block;
   9962         new_block.label_token = label_token;
   9963         new_block.is_loop = true;
   9964         new_block.ri = ri;
   9965         new_block.consumes_res_ptr = false;
   9966 
   9967         if (cont_node != 0)
   9968             (void)rlExpr(ag, cont_node, &new_block, RL_RI_NONE);
   9969         (void)rlExpr(ag, body_node, &new_block, RL_RI_NONE);
   9970         bool else_consumes = false;
   9971         if (else_node != 0)
   9972             else_consumes = rlExpr(ag, else_node, block, ri);
   9973         if (new_block.consumes_res_ptr || else_consumes) {
   9974             nodesNeedRlAdd(ag, node);
   9975             return true;
   9976         }
   9977         return false;
   9978     }
   9979 
   9980     // for (AstRlAnnotate.zig:421-454).
   9981     case AST_NODE_FOR_SIMPLE:
   9982     case AST_NODE_FOR: {
   9983         uint32_t input_buf[16];
   9984         const uint32_t* inputs = NULL;
   9985         uint32_t num_inputs = 0;
   9986         uint32_t body_node = 0;
   9987         uint32_t else_node = 0;
   9988 
   9989         if (tag == AST_NODE_FOR_SIMPLE) {
   9990             input_buf[0] = nd.lhs;
   9991             inputs = input_buf;
   9992             num_inputs = 1;
   9993             body_node = nd.rhs;
   9994         } else {
   9995             AstFor for_data;
   9996             memcpy(&for_data, &nd.rhs, sizeof(AstFor));
   9997             num_inputs = for_data.inputs;
   9998             if (num_inputs > 16)
   9999                 num_inputs = 16;
  10000             for (uint32_t i = 0; i < num_inputs; i++)
  10001                 input_buf[i] = tree->extra_data.arr[nd.lhs + i];
  10002             inputs = input_buf;
  10003             body_node = tree->extra_data.arr[nd.lhs + num_inputs];
  10004             if (for_data.has_else)
  10005                 else_node = tree->extra_data.arr[nd.lhs + num_inputs + 1];
  10006         }
  10007 
  10008         uint32_t main_tok = tree->nodes.main_tokens[node];
  10009         bool is_labeled
  10010             = (main_tok >= 2 && tree->tokens.tags[main_tok - 1] == TOKEN_COLON
  10011                 && tree->tokens.tags[main_tok - 2] == TOKEN_IDENTIFIER);
  10012         uint32_t label_token = is_labeled ? main_tok - 2 : UINT32_MAX;
  10013 
  10014         for (uint32_t i = 0; i < num_inputs; i++) {
  10015             uint32_t input = inputs[i];
  10016             if (tree->nodes.tags[input] == AST_NODE_FOR_RANGE) {
  10017                 AstData range_nd = tree->nodes.datas[input];
  10018                 (void)rlExpr(ag, range_nd.lhs, block, RL_RI_TYPE_ONLY);
  10019                 if (range_nd.rhs != 0)
  10020                     (void)rlExpr(ag, range_nd.rhs, block, RL_RI_TYPE_ONLY);
  10021             } else {
  10022                 (void)rlExpr(ag, input, block, RL_RI_NONE);
  10023             }
  10024         }
  10025 
  10026         RlBlock new_block;
  10027         new_block.parent = block;
  10028         new_block.label_token = label_token;
  10029         new_block.is_loop = true;
  10030         new_block.ri = ri;
  10031         new_block.consumes_res_ptr = false;
  10032 
  10033         (void)rlExpr(ag, body_node, &new_block, RL_RI_NONE);
  10034         bool else_consumes = false;
  10035         if (else_node != 0)
  10036             else_consumes = rlExpr(ag, else_node, block, ri);
  10037         if (new_block.consumes_res_ptr || else_consumes) {
  10038             nodesNeedRlAdd(ag, node);
  10039             return true;
  10040         }
  10041         return false;
  10042     }
  10043 
  10044     // slice (AstRlAnnotate.zig:456-480).
  10045     case AST_NODE_SLICE_OPEN:
  10046         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10047         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
  10048         return false;
  10049     case AST_NODE_SLICE: {
  10050         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10051         uint32_t start = tree->extra_data.arr[nd.rhs];
  10052         uint32_t end = tree->extra_data.arr[nd.rhs + 1];
  10053         (void)rlExpr(ag, start, block, RL_RI_TYPE_ONLY);
  10054         (void)rlExpr(ag, end, block, RL_RI_TYPE_ONLY);
  10055         return false;
  10056     }
  10057     case AST_NODE_SLICE_SENTINEL: {
  10058         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10059         AstSliceSentinel ss;
  10060         ss.start = tree->extra_data.arr[nd.rhs];
  10061         ss.end = tree->extra_data.arr[nd.rhs + 1];
  10062         ss.sentinel = tree->extra_data.arr[nd.rhs + 2];
  10063         (void)rlExpr(ag, ss.start, block, RL_RI_TYPE_ONLY);
  10064         if (ss.end != 0)
  10065             (void)rlExpr(ag, ss.end, block, RL_RI_TYPE_ONLY);
  10066         (void)rlExpr(ag, ss.sentinel, block, RL_RI_NONE);
  10067         return false;
  10068     }
  10069 
  10070     // deref (AstRlAnnotate.zig:481-484).
  10071     case AST_NODE_DEREF:
  10072         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10073         return false;
  10074 
  10075     // address_of (AstRlAnnotate.zig:485-488).
  10076     case AST_NODE_ADDRESS_OF:
  10077         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10078         return false;
  10079 
  10080     // optional_type (AstRlAnnotate.zig:489-492).
  10081     case AST_NODE_OPTIONAL_TYPE:
  10082         (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
  10083         return false;
  10084 
  10085     // try, nosuspend (AstRlAnnotate.zig:493-495).
  10086     case AST_NODE_TRY:
  10087     case AST_NODE_NOSUSPEND:
  10088         return rlExpr(ag, nd.lhs, block, ri);
  10089 
  10090     // grouped_expression, unwrap_optional (AstRlAnnotate.zig:496-498).
  10091     case AST_NODE_GROUPED_EXPRESSION:
  10092     case AST_NODE_UNWRAP_OPTIONAL:
  10093         return rlExpr(ag, nd.lhs, block, ri);
  10094 
  10095     // block (AstRlAnnotate.zig:500-508).
  10096     case AST_NODE_BLOCK_TWO:
  10097     case AST_NODE_BLOCK_TWO_SEMICOLON: {
  10098         uint32_t stmts[2];
  10099         uint32_t count = 0;
  10100         if (nd.lhs != 0)
  10101             stmts[count++] = nd.lhs;
  10102         if (nd.rhs != 0)
  10103             stmts[count++] = nd.rhs;
  10104         return rlBlockExpr(ag, block, ri, node, stmts, count);
  10105     }
  10106     case AST_NODE_BLOCK:
  10107     case AST_NODE_BLOCK_SEMICOLON:
  10108         return rlBlockExpr(ag, block, ri, node, tree->extra_data.arr + nd.lhs,
  10109             nd.rhs - nd.lhs);
  10110 
  10111     // anyframe_type (AstRlAnnotate.zig:509-513).
  10112     case AST_NODE_ANYFRAME_TYPE:
  10113         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
  10114         return false;
  10115 
  10116     // catch/orelse (AstRlAnnotate.zig:514-522).
  10117     case AST_NODE_CATCH:
  10118     case AST_NODE_ORELSE: {
  10119         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10120         bool rhs_consumes = rlExpr(ag, nd.rhs, block, ri);
  10121         if (rhs_consumes)
  10122             nodesNeedRlAdd(ag, node);
  10123         return rhs_consumes;
  10124     }
  10125 
  10126     // ptr_type (AstRlAnnotate.zig:524-546).
  10127     case AST_NODE_PTR_TYPE_ALIGNED:
  10128         if (nd.lhs != 0)
  10129             (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
  10130         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
  10131         return false;
  10132     case AST_NODE_PTR_TYPE_SENTINEL:
  10133         if (nd.lhs != 0)
  10134             (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
  10135         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
  10136         return false;
  10137     case AST_NODE_PTR_TYPE: {
  10138         AstPtrType pt;
  10139         pt.sentinel = tree->extra_data.arr[nd.lhs];
  10140         pt.align_node = tree->extra_data.arr[nd.lhs + 1];
  10141         pt.addrspace_node = tree->extra_data.arr[nd.lhs + 2];
  10142         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
  10143         if (pt.sentinel != 0)
  10144             (void)rlExpr(ag, pt.sentinel, block, RL_RI_TYPE_ONLY);
  10145         if (pt.align_node != 0)
  10146             (void)rlExpr(ag, pt.align_node, block, RL_RI_TYPE_ONLY);
  10147         if (pt.addrspace_node != 0)
  10148             (void)rlExpr(ag, pt.addrspace_node, block, RL_RI_TYPE_ONLY);
  10149         return false;
  10150     }
  10151     case AST_NODE_PTR_TYPE_BIT_RANGE: {
  10152         AstPtrTypeBitRange pt;
  10153         pt.sentinel = tree->extra_data.arr[nd.lhs];
  10154         pt.align_node = tree->extra_data.arr[nd.lhs + 1];
  10155         pt.addrspace_node = tree->extra_data.arr[nd.lhs + 2];
  10156         pt.bit_range_start = tree->extra_data.arr[nd.lhs + 3];
  10157         pt.bit_range_end = tree->extra_data.arr[nd.lhs + 4];
  10158         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
  10159         if (pt.sentinel != 0)
  10160             (void)rlExpr(ag, pt.sentinel, block, RL_RI_TYPE_ONLY);
  10161         if (pt.align_node != 0)
  10162             (void)rlExpr(ag, pt.align_node, block, RL_RI_TYPE_ONLY);
  10163         if (pt.addrspace_node != 0)
  10164             (void)rlExpr(ag, pt.addrspace_node, block, RL_RI_TYPE_ONLY);
  10165         if (pt.bit_range_start != 0) {
  10166             (void)rlExpr(ag, pt.bit_range_start, block, RL_RI_TYPE_ONLY);
  10167             (void)rlExpr(ag, pt.bit_range_end, block, RL_RI_TYPE_ONLY);
  10168         }
  10169         return false;
  10170     }
  10171 
  10172     // container_decl (AstRlAnnotate.zig:548-564).
  10173     case AST_NODE_CONTAINER_DECL:
  10174     case AST_NODE_CONTAINER_DECL_TRAILING:
  10175     case AST_NODE_CONTAINER_DECL_ARG:
  10176     case AST_NODE_CONTAINER_DECL_ARG_TRAILING:
  10177     case AST_NODE_CONTAINER_DECL_TWO:
  10178     case AST_NODE_CONTAINER_DECL_TWO_TRAILING:
  10179     case AST_NODE_TAGGED_UNION:
  10180     case AST_NODE_TAGGED_UNION_TRAILING:
  10181     case AST_NODE_TAGGED_UNION_ENUM_TAG:
  10182     case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING:
  10183     case AST_NODE_TAGGED_UNION_TWO:
  10184     case AST_NODE_TAGGED_UNION_TWO_TRAILING:
  10185         rlContainerDecl(ag, block, node);
  10186         return false;
  10187 
  10188     // break (AstRlAnnotate.zig:566-596).
  10189     case AST_NODE_BREAK: {
  10190         uint32_t opt_label_tok = nd.lhs; // 0 = no label
  10191         uint32_t rhs_node = nd.rhs; // 0 = void break
  10192         if (rhs_node == 0)
  10193             return false;
  10194 
  10195         RlBlock* opt_cur_block = block;
  10196         if (opt_label_tok != 0) {
  10197             // Labeled break: find matching block.
  10198             while (opt_cur_block != NULL) {
  10199                 if (opt_cur_block->label_token != UINT32_MAX
  10200                     && rlTokenIdentEqual(
  10201                         tree, opt_cur_block->label_token, opt_label_tok))
  10202                     break;
  10203                 opt_cur_block = opt_cur_block->parent;
  10204             }
  10205         } else {
  10206             // No label: breaking from innermost loop.
  10207             while (opt_cur_block != NULL) {
  10208                 if (opt_cur_block->is_loop)
  10209                     break;
  10210                 opt_cur_block = opt_cur_block->parent;
  10211             }
  10212         }
  10213 
  10214         if (opt_cur_block != NULL) {
  10215             bool consumes = rlExpr(ag, rhs_node, block, opt_cur_block->ri);
  10216             if (consumes)
  10217                 opt_cur_block->consumes_res_ptr = true;
  10218         } else {
  10219             (void)rlExpr(ag, rhs_node, block, RL_RI_NONE);
  10220         }
  10221         return false;
  10222     }
  10223 
  10224     // array_type (AstRlAnnotate.zig:598-611).
  10225     case AST_NODE_ARRAY_TYPE:
  10226         (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
  10227         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
  10228         return false;
  10229     case AST_NODE_ARRAY_TYPE_SENTINEL: {
  10230         (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
  10231         uint32_t elem_type = tree->extra_data.arr[nd.rhs + 1];
  10232         uint32_t sentinel = tree->extra_data.arr[nd.rhs];
  10233         (void)rlExpr(ag, elem_type, block, RL_RI_TYPE_ONLY);
  10234         (void)rlExpr(ag, sentinel, block, RL_RI_TYPE_ONLY);
  10235         return false;
  10236     }
  10237 
  10238     // array_access (AstRlAnnotate.zig:612-617).
  10239     case AST_NODE_ARRAY_ACCESS:
  10240         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10241         (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY);
  10242         return false;
  10243 
  10244     // comptime (AstRlAnnotate.zig:618-623).
  10245     case AST_NODE_COMPTIME:
  10246         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10247         return false;
  10248 
  10249     // switch (AstRlAnnotate.zig:624-650).
  10250     case AST_NODE_SWITCH:
  10251     case AST_NODE_SWITCH_COMMA: {
  10252         uint32_t cond_node = nd.lhs;
  10253         uint32_t extra_idx = nd.rhs;
  10254         uint32_t cases_start = tree->extra_data.arr[extra_idx];
  10255         uint32_t cases_end = tree->extra_data.arr[extra_idx + 1];
  10256 
  10257         (void)rlExpr(ag, cond_node, block, RL_RI_NONE);
  10258 
  10259         bool any_consumed = false;
  10260         for (uint32_t ci = cases_start; ci < cases_end; ci++) {
  10261             uint32_t case_node = tree->extra_data.arr[ci];
  10262             AstNodeTag ct = tree->nodes.tags[case_node];
  10263             AstData cd = tree->nodes.datas[case_node];
  10264 
  10265             // Process case values.
  10266             if (ct == AST_NODE_SWITCH_CASE_ONE
  10267                 || ct == AST_NODE_SWITCH_CASE_INLINE_ONE) {
  10268                 if (cd.lhs != 0) {
  10269                     if (tree->nodes.tags[cd.lhs] == AST_NODE_SWITCH_RANGE) {
  10270                         AstData rd = tree->nodes.datas[cd.lhs];
  10271                         (void)rlExpr(ag, rd.lhs, block, RL_RI_NONE);
  10272                         (void)rlExpr(ag, rd.rhs, block, RL_RI_NONE);
  10273                     } else {
  10274                         (void)rlExpr(ag, cd.lhs, block, RL_RI_NONE);
  10275                     }
  10276                 }
  10277             } else {
  10278                 // SWITCH_CASE / SWITCH_CASE_INLINE: SubRange[lhs]
  10279                 uint32_t items_start = tree->extra_data.arr[cd.lhs];
  10280                 uint32_t items_end = tree->extra_data.arr[cd.lhs + 1];
  10281                 for (uint32_t ii = items_start; ii < items_end; ii++) {
  10282                     uint32_t item = tree->extra_data.arr[ii];
  10283                     if (tree->nodes.tags[item] == AST_NODE_SWITCH_RANGE) {
  10284                         AstData rd = tree->nodes.datas[item];
  10285                         (void)rlExpr(ag, rd.lhs, block, RL_RI_NONE);
  10286                         (void)rlExpr(ag, rd.rhs, block, RL_RI_NONE);
  10287                     } else {
  10288                         (void)rlExpr(ag, item, block, RL_RI_NONE);
  10289                     }
  10290                 }
  10291             }
  10292             // Process case target expr.
  10293             if (rlExpr(ag, cd.rhs, block, ri))
  10294                 any_consumed = true;
  10295         }
  10296         if (any_consumed)
  10297             nodesNeedRlAdd(ag, node);
  10298         return any_consumed;
  10299     }
  10300 
  10301     // suspend (AstRlAnnotate.zig:651-654).
  10302     case AST_NODE_SUSPEND:
  10303         if (nd.lhs != 0)
  10304             (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10305         return false;
  10306 
  10307     // resume (AstRlAnnotate.zig:655-658).
  10308     case AST_NODE_RESUME:
  10309         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10310         return false;
  10311 
  10312     // array_init (AstRlAnnotate.zig:660-695).
  10313     case AST_NODE_ARRAY_INIT_ONE:
  10314     case AST_NODE_ARRAY_INIT_ONE_COMMA:
  10315     case AST_NODE_ARRAY_INIT_DOT_TWO:
  10316     case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA:
  10317     case AST_NODE_ARRAY_INIT_DOT:
  10318     case AST_NODE_ARRAY_INIT_DOT_COMMA:
  10319     case AST_NODE_ARRAY_INIT:
  10320     case AST_NODE_ARRAY_INIT_COMMA: {
  10321         // Extract type_expr and elements.
  10322         uint32_t type_expr = 0;
  10323         uint32_t elem_buf[2];
  10324         const uint32_t* elems = NULL;
  10325         uint32_t nelem = 0;
  10326         switch (tag) {
  10327         case AST_NODE_ARRAY_INIT_ONE:
  10328         case AST_NODE_ARRAY_INIT_ONE_COMMA:
  10329             type_expr = nd.lhs;
  10330             if (nd.rhs != 0) {
  10331                 elem_buf[0] = nd.rhs;
  10332                 elems = elem_buf;
  10333                 nelem = 1;
  10334             }
  10335             break;
  10336         case AST_NODE_ARRAY_INIT_DOT_TWO:
  10337         case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: {
  10338             uint32_t idx = 0;
  10339             if (nd.lhs != 0)
  10340                 elem_buf[idx++] = nd.lhs;
  10341             if (nd.rhs != 0)
  10342                 elem_buf[idx++] = nd.rhs;
  10343             elems = elem_buf;
  10344             nelem = idx;
  10345             break;
  10346         }
  10347         case AST_NODE_ARRAY_INIT_DOT:
  10348         case AST_NODE_ARRAY_INIT_DOT_COMMA:
  10349             elems = tree->extra_data.arr + nd.lhs;
  10350             nelem = nd.rhs - nd.lhs;
  10351             break;
  10352         case AST_NODE_ARRAY_INIT:
  10353         case AST_NODE_ARRAY_INIT_COMMA: {
  10354             type_expr = nd.lhs;
  10355             uint32_t start = tree->extra_data.arr[nd.rhs];
  10356             uint32_t end = tree->extra_data.arr[nd.rhs + 1];
  10357             elems = tree->extra_data.arr + start;
  10358             nelem = end - start;
  10359             break;
  10360         }
  10361         default:
  10362             break;
  10363         }
  10364         if (type_expr != 0) {
  10365             (void)rlExpr(ag, type_expr, block, RL_RI_NONE);
  10366             for (uint32_t i = 0; i < nelem; i++)
  10367                 (void)rlExpr(ag, elems[i], block, RL_RI_TYPE_ONLY);
  10368             return false;
  10369         }
  10370         if (ri.have_type) {
  10371             for (uint32_t i = 0; i < nelem; i++)
  10372                 (void)rlExpr(ag, elems[i], block, ri);
  10373             return ri.have_ptr;
  10374         } else {
  10375             for (uint32_t i = 0; i < nelem; i++)
  10376                 (void)rlExpr(ag, elems[i], block, RL_RI_NONE);
  10377             return false;
  10378         }
  10379     }
  10380 
  10381     // struct_init (AstRlAnnotate.zig:697-732).
  10382     case AST_NODE_STRUCT_INIT_ONE:
  10383     case AST_NODE_STRUCT_INIT_ONE_COMMA:
  10384     case AST_NODE_STRUCT_INIT_DOT_TWO:
  10385     case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA:
  10386     case AST_NODE_STRUCT_INIT_DOT:
  10387     case AST_NODE_STRUCT_INIT_DOT_COMMA:
  10388     case AST_NODE_STRUCT_INIT:
  10389     case AST_NODE_STRUCT_INIT_COMMA: {
  10390         uint32_t type_expr = 0;
  10391         uint32_t field_buf[2];
  10392         const uint32_t* fields = NULL;
  10393         uint32_t nfields = 0;
  10394         switch (tag) {
  10395         case AST_NODE_STRUCT_INIT_ONE:
  10396         case AST_NODE_STRUCT_INIT_ONE_COMMA:
  10397             type_expr = nd.lhs;
  10398             if (nd.rhs != 0) {
  10399                 field_buf[0] = nd.rhs;
  10400                 fields = field_buf;
  10401                 nfields = 1;
  10402             }
  10403             break;
  10404         case AST_NODE_STRUCT_INIT_DOT_TWO:
  10405         case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: {
  10406             uint32_t idx = 0;
  10407             if (nd.lhs != 0)
  10408                 field_buf[idx++] = nd.lhs;
  10409             if (nd.rhs != 0)
  10410                 field_buf[idx++] = nd.rhs;
  10411             fields = field_buf;
  10412             nfields = idx;
  10413             break;
  10414         }
  10415         case AST_NODE_STRUCT_INIT_DOT:
  10416         case AST_NODE_STRUCT_INIT_DOT_COMMA:
  10417             fields = tree->extra_data.arr + nd.lhs;
  10418             nfields = nd.rhs - nd.lhs;
  10419             break;
  10420         case AST_NODE_STRUCT_INIT:
  10421         case AST_NODE_STRUCT_INIT_COMMA: {
  10422             type_expr = nd.lhs;
  10423             uint32_t start = tree->extra_data.arr[nd.rhs];
  10424             uint32_t end = tree->extra_data.arr[nd.rhs + 1];
  10425             fields = tree->extra_data.arr + start;
  10426             nfields = end - start;
  10427             break;
  10428         }
  10429         default:
  10430             break;
  10431         }
  10432         if (type_expr != 0) {
  10433             (void)rlExpr(ag, type_expr, block, RL_RI_NONE);
  10434             for (uint32_t i = 0; i < nfields; i++)
  10435                 (void)rlExpr(ag, fields[i], block, RL_RI_TYPE_ONLY);
  10436             return false;
  10437         }
  10438         if (ri.have_type) {
  10439             for (uint32_t i = 0; i < nfields; i++)
  10440                 (void)rlExpr(ag, fields[i], block, ri);
  10441             return ri.have_ptr;
  10442         } else {
  10443             for (uint32_t i = 0; i < nfields; i++)
  10444                 (void)rlExpr(ag, fields[i], block, RL_RI_NONE);
  10445             return false;
  10446         }
  10447     }
  10448 
  10449     // fn_proto, fn_decl (AstRlAnnotate.zig:734-770).
  10450     case AST_NODE_FN_PROTO_SIMPLE:
  10451     case AST_NODE_FN_PROTO_MULTI:
  10452     case AST_NODE_FN_PROTO_ONE:
  10453     case AST_NODE_FN_PROTO:
  10454     case AST_NODE_FN_DECL: {
  10455         // Extract return type and body.
  10456         uint32_t return_type = 0;
  10457         uint32_t body_node = 0;
  10458 
  10459         if (tag == AST_NODE_FN_DECL) {
  10460             body_node = nd.rhs;
  10461             // fn_proto is nd.lhs
  10462             uint32_t proto = nd.lhs;
  10463             AstNodeTag ptag = tree->nodes.tags[proto];
  10464             AstData pnd = tree->nodes.datas[proto];
  10465             if (ptag == AST_NODE_FN_PROTO_SIMPLE) {
  10466                 return_type = pnd.rhs;
  10467                 if (pnd.lhs != 0)
  10468                     (void)rlExpr(ag, pnd.lhs, block, RL_RI_TYPE_ONLY);
  10469             } else if (ptag == AST_NODE_FN_PROTO_MULTI) {
  10470                 return_type = pnd.rhs;
  10471                 uint32_t ps = tree->extra_data.arr[pnd.lhs];
  10472                 uint32_t pe = tree->extra_data.arr[pnd.lhs + 1];
  10473                 for (uint32_t i = ps; i < pe; i++)
  10474                     (void)rlExpr(
  10475                         ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY);
  10476             } else if (ptag == AST_NODE_FN_PROTO_ONE) {
  10477                 return_type = pnd.rhs;
  10478                 AstFnProtoOne fp;
  10479                 fp.param = tree->extra_data.arr[pnd.lhs];
  10480                 fp.align_expr = tree->extra_data.arr[pnd.lhs + 1];
  10481                 fp.addrspace_expr = tree->extra_data.arr[pnd.lhs + 2];
  10482                 fp.section_expr = tree->extra_data.arr[pnd.lhs + 3];
  10483                 fp.callconv_expr = tree->extra_data.arr[pnd.lhs + 4];
  10484                 if (fp.param != 0)
  10485                     (void)rlExpr(ag, fp.param, block, RL_RI_TYPE_ONLY);
  10486                 if (fp.align_expr != 0)
  10487                     (void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY);
  10488                 if (fp.addrspace_expr != 0)
  10489                     (void)rlExpr(
  10490                         ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY);
  10491                 if (fp.section_expr != 0)
  10492                     (void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY);
  10493                 if (fp.callconv_expr != 0)
  10494                     (void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY);
  10495             } else if (ptag == AST_NODE_FN_PROTO) {
  10496                 return_type = pnd.rhs;
  10497                 AstFnProto fp;
  10498                 fp.params_start = tree->extra_data.arr[pnd.lhs];
  10499                 fp.params_end = tree->extra_data.arr[pnd.lhs + 1];
  10500                 fp.align_expr = tree->extra_data.arr[pnd.lhs + 2];
  10501                 fp.addrspace_expr = tree->extra_data.arr[pnd.lhs + 3];
  10502                 fp.section_expr = tree->extra_data.arr[pnd.lhs + 4];
  10503                 fp.callconv_expr = tree->extra_data.arr[pnd.lhs + 5];
  10504                 for (uint32_t i = fp.params_start; i < fp.params_end; i++)
  10505                     (void)rlExpr(
  10506                         ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY);
  10507                 if (fp.align_expr != 0)
  10508                     (void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY);
  10509                 if (fp.addrspace_expr != 0)
  10510                     (void)rlExpr(
  10511                         ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY);
  10512                 if (fp.section_expr != 0)
  10513                     (void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY);
  10514                 if (fp.callconv_expr != 0)
  10515                     (void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY);
  10516             }
  10517         } else {
  10518             // Standalone fn_proto (no body).
  10519             if (tag == AST_NODE_FN_PROTO_SIMPLE) {
  10520                 return_type = nd.rhs;
  10521                 if (nd.lhs != 0)
  10522                     (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY);
  10523             } else if (tag == AST_NODE_FN_PROTO_MULTI) {
  10524                 return_type = nd.rhs;
  10525                 uint32_t ps = tree->extra_data.arr[nd.lhs];
  10526                 uint32_t pe = tree->extra_data.arr[nd.lhs + 1];
  10527                 for (uint32_t i = ps; i < pe; i++)
  10528                     (void)rlExpr(
  10529                         ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY);
  10530             } else if (tag == AST_NODE_FN_PROTO_ONE) {
  10531                 return_type = nd.rhs;
  10532                 AstFnProtoOne fp;
  10533                 fp.param = tree->extra_data.arr[nd.lhs];
  10534                 fp.align_expr = tree->extra_data.arr[nd.lhs + 1];
  10535                 fp.addrspace_expr = tree->extra_data.arr[nd.lhs + 2];
  10536                 fp.section_expr = tree->extra_data.arr[nd.lhs + 3];
  10537                 fp.callconv_expr = tree->extra_data.arr[nd.lhs + 4];
  10538                 if (fp.param != 0)
  10539                     (void)rlExpr(ag, fp.param, block, RL_RI_TYPE_ONLY);
  10540                 if (fp.align_expr != 0)
  10541                     (void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY);
  10542                 if (fp.addrspace_expr != 0)
  10543                     (void)rlExpr(
  10544                         ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY);
  10545                 if (fp.section_expr != 0)
  10546                     (void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY);
  10547                 if (fp.callconv_expr != 0)
  10548                     (void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY);
  10549             } else if (tag == AST_NODE_FN_PROTO) {
  10550                 return_type = nd.rhs;
  10551                 AstFnProto fp;
  10552                 fp.params_start = tree->extra_data.arr[nd.lhs];
  10553                 fp.params_end = tree->extra_data.arr[nd.lhs + 1];
  10554                 fp.align_expr = tree->extra_data.arr[nd.lhs + 2];
  10555                 fp.addrspace_expr = tree->extra_data.arr[nd.lhs + 3];
  10556                 fp.section_expr = tree->extra_data.arr[nd.lhs + 4];
  10557                 fp.callconv_expr = tree->extra_data.arr[nd.lhs + 5];
  10558                 for (uint32_t i = fp.params_start; i < fp.params_end; i++)
  10559                     (void)rlExpr(
  10560                         ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY);
  10561                 if (fp.align_expr != 0)
  10562                     (void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY);
  10563                 if (fp.addrspace_expr != 0)
  10564                     (void)rlExpr(
  10565                         ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY);
  10566                 if (fp.section_expr != 0)
  10567                     (void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY);
  10568                 if (fp.callconv_expr != 0)
  10569                     (void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY);
  10570             }
  10571         }
  10572 
  10573         if (return_type != 0)
  10574             (void)rlExpr(ag, return_type, block, RL_RI_TYPE_ONLY);
  10575         if (body_node != 0)
  10576             (void)rlExpr(ag, body_node, block, RL_RI_NONE);
  10577         return false;
  10578     }
  10579 
  10580     // Remaining: usingnamespace, await, assign_destructure, async calls.
  10581     case AST_NODE_USINGNAMESPACE:
  10582         return false;
  10583     case AST_NODE_AWAIT:
  10584         (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE);
  10585         return false;
  10586     case AST_NODE_ASSIGN_DESTRUCTURE:
  10587         return false; // TODO if needed
  10588     case AST_NODE_ASYNC_CALL_ONE:
  10589     case AST_NODE_ASYNC_CALL_ONE_COMMA:
  10590     case AST_NODE_ASYNC_CALL:
  10591     case AST_NODE_ASYNC_CALL_COMMA:
  10592         return false; // async not relevant
  10593 
  10594     default:
  10595         return false;
  10596     }
  10597 }
  10598 
  10599 // astRlAnnotate (AstRlAnnotate.zig:64-83).
  10600 // Entry point: run the RL annotation pre-pass.
  10601 static void astRlAnnotate(AstGenCtx* ag) {
  10602     const Ast* tree = ag->tree;
  10603     if (tree->has_error)
  10604         return;
  10605 
  10606     // Get root container members (same as in astGen).
  10607     AstData root_data = tree->nodes.datas[0];
  10608     uint32_t members_start = root_data.lhs;
  10609     uint32_t members_end = root_data.rhs;
  10610     const uint32_t* members = tree->extra_data.arr + members_start;
  10611     uint32_t members_len = members_end - members_start;
  10612 
  10613     for (uint32_t i = 0; i < members_len; i++)
  10614         (void)rlExpr(ag, members[i], NULL, RL_RI_NONE);
  10615 }
  10616 
  10617 // --- Public API: astGen (AstGen.zig:144) ---
  10618 
  10619 Zir astGen(const Ast* ast) {
  10620     AstGenCtx ag;
  10621     memset(&ag, 0, sizeof(ag));
  10622     ag.tree = ast;
  10623 
  10624     // Initial allocations (AstGen.zig:162-172).
  10625     uint32_t nodes_len = ast->nodes.len;
  10626     uint32_t init_cap = nodes_len > 8 ? nodes_len : 8;
  10627 
  10628     ag.inst_cap = init_cap;
  10629     ag.inst_tags = ARR_INIT(ZirInstTag, ag.inst_cap);
  10630     ag.inst_datas = ARR_INIT(ZirInstData, ag.inst_cap);
  10631 
  10632     ag.extra_cap = init_cap + ZIR_EXTRA_RESERVED_COUNT;
  10633     ag.extra = ARR_INIT(uint32_t, ag.extra_cap);
  10634 
  10635     ag.string_bytes_cap = 16;
  10636     ag.string_bytes = ARR_INIT(uint8_t, ag.string_bytes_cap);
  10637 
  10638     // String table index 0 is reserved for NullTerminatedString.empty
  10639     // (AstGen.zig:163).
  10640     ag.string_bytes[0] = 0;
  10641     ag.string_bytes_len = 1;
  10642 
  10643     // Reserve extra[0..1] (AstGen.zig:170-172).
  10644     ag.extra[ZIR_EXTRA_COMPILE_ERRORS] = 0;
  10645     ag.extra[ZIR_EXTRA_IMPORTS] = 0;
  10646     ag.extra_len = ZIR_EXTRA_RESERVED_COUNT;
  10647 
  10648     // Run AstRlAnnotate pre-pass (AstGen.zig:150-151).
  10649     astRlAnnotate(&ag);
  10650 
  10651     // Set up root GenZir scope (AstGen.zig:176-185).
  10652     GenZir gen_scope;
  10653     memset(&gen_scope, 0, sizeof(gen_scope));
  10654     gen_scope.base.tag = SCOPE_GEN_ZIR;
  10655     gen_scope.parent = NULL;
  10656     gen_scope.astgen = &ag;
  10657     gen_scope.is_comptime = true;
  10658     gen_scope.decl_node_index = 0; // root
  10659     gen_scope.decl_line = 0;
  10660     gen_scope.break_block = UINT32_MAX;
  10661 
  10662     // Get root container members: containerDeclRoot (AstGen.zig:191-195).
  10663     AstData root_data = ast->nodes.datas[0];
  10664     uint32_t members_start = root_data.lhs;
  10665     uint32_t members_end = root_data.rhs;
  10666     const uint32_t* members = ast->extra_data.arr + members_start;
  10667     uint32_t members_len = members_end - members_start;
  10668 
  10669     structDeclInner(&ag, &gen_scope, 0, members, members_len);
  10670 
  10671     // Write imports list (AstGen.zig:227-244).
  10672     writeImports(&ag);
  10673 
  10674     // Build output Zir (AstGen.zig:211-239).
  10675     Zir zir;
  10676     zir.inst_len = ag.inst_len;
  10677     zir.inst_cap = ag.inst_cap;
  10678     zir.inst_tags = ag.inst_tags;
  10679     zir.inst_datas = ag.inst_datas;
  10680     zir.extra_len = ag.extra_len;
  10681     zir.extra_cap = ag.extra_cap;
  10682     zir.extra = ag.extra;
  10683     zir.string_bytes_len = ag.string_bytes_len;
  10684     zir.string_bytes_cap = ag.string_bytes_cap;
  10685     zir.string_bytes = ag.string_bytes;
  10686     zir.has_compile_errors = ag.has_compile_errors;
  10687 
  10688     free(ag.imports);
  10689     free(ag.decl_names);
  10690     free(ag.decl_nodes);
  10691     free(ag.scratch_instructions);
  10692     free(ag.scratch_extra);
  10693     free(ag.ref_table_keys);
  10694     free(ag.ref_table_vals);
  10695     free(ag.nodes_need_rl);
  10696     free(ag.string_table);
  10697 
  10698     return zir;
  10699 }