// astgen.c — AST to ZIR conversion, ported from lib/std/zig/AstGen.zig. // // Structural translation of AstGen.zig into C. // Each function corresponds to a Zig function with the same name, // with line references to Zig 0.15.1 AstGen.zig. #include "astgen.h" #include "common.h" #include #include #include // --- Declaration.Flags.Id enum (Zir.zig:2724) --- typedef enum { DECL_ID_UNNAMED_TEST, DECL_ID_TEST, DECL_ID_DECLTEST, DECL_ID_COMPTIME, DECL_ID_CONST_SIMPLE, DECL_ID_CONST_TYPED, DECL_ID_CONST, DECL_ID_PUB_CONST_SIMPLE, DECL_ID_PUB_CONST_TYPED, DECL_ID_PUB_CONST, DECL_ID_EXTERN_CONST_SIMPLE, DECL_ID_EXTERN_CONST, DECL_ID_PUB_EXTERN_CONST_SIMPLE, DECL_ID_PUB_EXTERN_CONST, DECL_ID_EXPORT_CONST, DECL_ID_PUB_EXPORT_CONST, DECL_ID_VAR_SIMPLE, DECL_ID_VAR, DECL_ID_VAR_THREADLOCAL, DECL_ID_PUB_VAR_SIMPLE, DECL_ID_PUB_VAR, DECL_ID_PUB_VAR_THREADLOCAL, DECL_ID_EXTERN_VAR, DECL_ID_EXTERN_VAR_THREADLOCAL, DECL_ID_PUB_EXTERN_VAR, DECL_ID_PUB_EXTERN_VAR_THREADLOCAL, DECL_ID_EXPORT_VAR, DECL_ID_EXPORT_VAR_THREADLOCAL, DECL_ID_PUB_EXPORT_VAR, DECL_ID_PUB_EXPORT_VAR_THREADLOCAL, } DeclFlagsId; // --- Import tracking (AstGen.zig:265) --- typedef struct { uint32_t name; // NullTerminatedString index uint32_t token; // Ast.TokenIndex } ImportEntry; // --- AstGen internal context (mirrors AstGen struct, AstGen.zig:153) --- typedef struct { const Ast* tree; ZirInstTag* inst_tags; ZirInstData* inst_datas; uint32_t inst_len; uint32_t inst_cap; uint32_t* extra; uint32_t extra_len; uint32_t extra_cap; uint8_t* string_bytes; uint32_t string_bytes_len; uint32_t string_bytes_cap; // String dedup table: stores positions in string_bytes that are // registered for deduplication (mirrors AstGen.string_table). // Only strings added via identAsString/strLitAsString (non-embedded-null) // are registered. Multiline strings are NOT registered. uint32_t* string_table; uint32_t string_table_len; uint32_t string_table_cap; uint32_t source_offset; uint32_t source_line; uint32_t source_column; ImportEntry* imports; uint32_t imports_len; uint32_t imports_cap; // Namespace decl table: maps string indices to node indices. // Populated by scanContainer, used by identifier resolution. uint32_t* decl_names; // string indices uint32_t* decl_nodes; // node indices uint32_t decl_table_len; uint32_t decl_table_cap; // Shared dynamic array for GenZir instructions (AstGen.zig:11796). // Sub-blocks share this array and track their slice via // instructions_top. uint32_t* scratch_instructions; uint32_t scratch_inst_len; uint32_t scratch_inst_cap; // Scratch extra array for call arguments (mirrors AstGen.scratch in Zig). // Used to collect body lengths + body instructions before copying to // extra. uint32_t* scratch_extra; uint32_t scratch_extra_len; uint32_t scratch_extra_cap; // Return type ref for the current function (set during fnDecl/testDecl). uint32_t fn_ret_ty; // ZirInstRef // Pointer to the fn_block GenZir for the current function (AstGen.zig:45). void* fn_block; // GenZir* // ref_table: deferred REF instructions (AstGen.zig:58-68). // Key = operand inst index, Value = ref inst index. uint32_t* ref_table_keys; uint32_t* ref_table_vals; uint32_t ref_table_len; uint32_t ref_table_cap; // nodes_need_rl: set of AST node indices that need result locations. // Populated by astRlAnnotate() pre-pass (AstRlAnnotate.zig). uint32_t* nodes_need_rl; uint32_t nodes_need_rl_len; uint32_t nodes_need_rl_cap; bool has_compile_errors; bool within_fn; // AstGen.zig:49 bool fn_var_args; // AstGen.zig:46 } AstGenCtx; static void setCompileError(AstGenCtx* ag, const char* where, int line) { (void)where; (void)line; ag->has_compile_errors = true; } #define SET_ERROR(ag) setCompileError(ag, __func__, __LINE__) // Set fn_block pointer on AstGenCtx. The caller is responsible for saving // and restoring the previous value before the pointed-to GenZir goes out // of scope (AstGen.zig:45). static void setFnBlock(AstGenCtx* ag, void* block) { ag->fn_block = block; } // --- ref_table operations (AstGen.zig:58-68) --- // Simple linear-scan hash table for deferred REF instructions. // Returns pointer to existing value if key found, NULL if not found. static uint32_t* refTableGet(AstGenCtx* ag, uint32_t key) { for (uint32_t i = 0; i < ag->ref_table_len; i++) { if (ag->ref_table_keys[i] == key) return &ag->ref_table_vals[i]; } return NULL; } // getOrPut: returns pointer to value slot; sets *found to true if existed. static uint32_t* refTableGetOrPut(AstGenCtx* ag, uint32_t key, bool* found) { for (uint32_t i = 0; i < ag->ref_table_len; i++) { if (ag->ref_table_keys[i] == key) { *found = true; return &ag->ref_table_vals[i]; } } *found = false; if (ag->ref_table_len >= ag->ref_table_cap) { uint32_t new_cap = ag->ref_table_cap == 0 ? 16 : ag->ref_table_cap * 2; ag->ref_table_keys = realloc(ag->ref_table_keys, new_cap * sizeof(uint32_t)); ag->ref_table_vals = realloc(ag->ref_table_vals, new_cap * sizeof(uint32_t)); ag->ref_table_cap = new_cap; } uint32_t idx = ag->ref_table_len++; ag->ref_table_keys[idx] = key; return &ag->ref_table_vals[idx]; } // fetchRemove: if key exists, remove it and return true with *val set. static bool refTableFetchRemove(AstGenCtx* ag, uint32_t key, uint32_t* val) { for (uint32_t i = 0; i < ag->ref_table_len; i++) { if (ag->ref_table_keys[i] == key) { *val = ag->ref_table_vals[i]; // Swap with last element. ag->ref_table_len--; if (i < ag->ref_table_len) { ag->ref_table_keys[i] = ag->ref_table_keys[ag->ref_table_len]; ag->ref_table_vals[i] = ag->ref_table_vals[ag->ref_table_len]; } return true; } } return false; } // --- Result location (AstGen.zig:11808) --- // Simplified version of ResultInfo.Loc. // Defined here (before GenZir) because GenZir.break_result_info uses it. // ResultInfo.Context (AstGen.zig:371-386). typedef enum { RI_CTX_NONE, RI_CTX_RETURN, RI_CTX_ERROR_HANDLING_EXPR, RI_CTX_SHIFT_OP, RI_CTX_FN_ARG, RI_CTX_CONST_INIT, RI_CTX_ASSIGNMENT, } ResultCtx; typedef enum { RL_NONE, // Just compute the value. RL_REF, // Compute a pointer to the value. RL_DISCARD, // Compute but discard (emit ensure_result_non_error). RL_TY, // Coerce to specific type. RL_COERCED_TY, // Coerce to specific type, result is the coercion. RL_PTR, // Store result to typed pointer. data=alloc inst, src_node=node. RL_INFERRED_PTR, // Store result to inferred pointer. data=alloc inst. RL_REF_COERCED_TY, // Ref with pointer type. data=ptr_ty_inst. } ResultLocTag; typedef struct { ResultLocTag tag; uint32_t data; // ZirInstRef: ty_inst for TY/COERCED_TY, alloc inst for // PTR/INFERRED_PTR. uint32_t src_node; // Only used for RL_PTR. ResultCtx ctx; // ResultInfo.Context (AstGen.zig:371). } ResultLoc; #define RL_NONE_VAL \ ((ResultLoc) { \ .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE }) #define RL_REF_VAL \ ((ResultLoc) { \ .tag = RL_REF, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE }) #define RL_DISCARD_VAL \ ((ResultLoc) { \ .tag = RL_DISCARD, .data = 0, .src_node = 0, .ctx = RI_CTX_NONE }) #define RL_IS_REF(rl) ((rl).tag == RL_REF || (rl).tag == RL_REF_COERCED_TY) // --- Scope types (AstGen.zig:11621-11768) --- typedef enum { SCOPE_GEN_ZIR, SCOPE_LOCAL_VAL, SCOPE_LOCAL_PTR, SCOPE_DEFER_NORMAL, SCOPE_DEFER_ERROR, SCOPE_NAMESPACE, SCOPE_TOP, SCOPE_LABEL, } ScopeTag; typedef struct Scope { ScopeTag tag; } Scope; // --- GenZir scope (mirrors GenZir struct, AstGen.zig:11772) --- // // Sub-blocks share the parent AstGenCtx's scratch_instructions array and // record their starting offset (instructions_top). This mirrors the upstream // GenZir.instructions / instructions_top design (AstGen.zig:11796-11850). typedef struct { Scope base; // tag = SCOPE_GEN_ZIR Scope* parent; AstGenCtx* astgen; uint32_t decl_node_index; uint32_t decl_line; bool is_comptime; bool is_inline; // true for inline for/while, labeled blocks in comptime bool c_import; // true inside @cImport block uint32_t instructions_top; // start index in shared array uint32_t break_block; // UINT32_MAX = none (AstGen.zig:11780) uint32_t continue_block; // UINT32_MAX = none (AstGen.zig:11784) // Label for labeled blocks (AstGen.zig:11800, 11869-11874). uint32_t label_token; // UINT32_MAX = no label uint32_t label_block_inst; // the BLOCK instruction index ResultLoc break_result_info; // RL for break values uint32_t any_defer_node; // UINT32_MAX = none (AstGen.zig:11812) } GenZir; // Scope.LocalVal (AstGen.zig:11682). // This is always a `const` local and the `inst` is a value type, not a // pointer. typedef struct { Scope base; // tag = SCOPE_LOCAL_VAL Scope* parent; GenZir* gen_zir; uint32_t inst; // ZirInstRef uint32_t token_src; // Ast.TokenIndex uint32_t name; // NullTerminatedString (string table index) } ScopeLocalVal; // Scope.LocalPtr (AstGen.zig:11704). // This could be a `const` or `var` local. It has a pointer instead of a value. typedef struct { Scope base; // tag = SCOPE_LOCAL_PTR Scope* parent; GenZir* gen_zir; uint32_t ptr; // ZirInstRef uint32_t token_src; // Ast.TokenIndex uint32_t name; // NullTerminatedString (string table index) bool maybe_comptime; } ScopeLocalPtr; // Scope.Defer (AstGen.zig:11741). typedef struct { Scope base; // tag = SCOPE_DEFER_NORMAL or SCOPE_DEFER_ERROR Scope* parent; uint32_t index; uint32_t len; } ScopeDefer; // Scope.Label — for labeled blocks and loops. typedef struct { Scope base; // tag = SCOPE_LABEL Scope* parent; uint32_t label_name; // NullTerminatedString uint32_t block_inst; // instruction index (not ref) } ScopeLabel; // --- GenZir instruction helpers (AstGen.zig:11830-11850) --- // Returns the number of instructions in this scope. static uint32_t gzInstructionsLen(const GenZir* gz) { return gz->astgen->scratch_inst_len - gz->instructions_top; } // Returns pointer to start of this scope's instructions in the shared array. static const uint32_t* gzInstructionsSlice(const GenZir* gz) { return gz->astgen->scratch_instructions + gz->instructions_top; } // Mirrors GenZir.instructionsSliceUpto (AstGen.zig:11835). // Returns instructions from gz up to (but not including) stacked_gz's start. static uint32_t gzInstructionsLenUpto( const GenZir* gz, const GenZir* stacked_gz) { return stacked_gz->instructions_top - gz->instructions_top; } static const uint32_t* gzInstructionsSliceUpto( const GenZir* gz, const GenZir* stacked_gz) { (void)stacked_gz; // used only for length computation return gz->astgen->scratch_instructions + gz->instructions_top; } // Mirrors GenZir.unstack (AstGen.zig:11822). // Restores the shared array length to this scope's start. static void gzUnstack(GenZir* gz) { gz->astgen->scratch_inst_len = gz->instructions_top; } // Append an instruction index to this scope's portion of the shared array. static void gzAppendInstruction(GenZir* gz, uint32_t inst_idx) { AstGenCtx* ag = gz->astgen; if (ag->scratch_inst_len >= ag->scratch_inst_cap) { uint32_t new_cap = ag->scratch_inst_cap > 0 ? ag->scratch_inst_cap * 2 : 64; uint32_t* p = realloc(ag->scratch_instructions, new_cap * sizeof(uint32_t)); if (!p) exit(1); ag->scratch_instructions = p; ag->scratch_inst_cap = new_cap; } ag->scratch_instructions[ag->scratch_inst_len++] = inst_idx; } // Mirrors GenZir.makeSubBlock (AstGen.zig:11852). static GenZir makeSubBlock(GenZir* parent, Scope* scope) { GenZir sub; memset(&sub, 0, sizeof(sub)); sub.base.tag = SCOPE_GEN_ZIR; sub.parent = scope; sub.astgen = parent->astgen; sub.decl_node_index = parent->decl_node_index; sub.decl_line = parent->decl_line; sub.is_comptime = parent->is_comptime; sub.c_import = parent->c_import; sub.instructions_top = parent->astgen->scratch_inst_len; sub.break_block = UINT32_MAX; sub.continue_block = UINT32_MAX; sub.label_token = UINT32_MAX; sub.any_defer_node = parent->any_defer_node; return sub; } // --- Capacity helpers --- static void ensureExtraCapacity(AstGenCtx* ag, uint32_t additional) { uint32_t needed = ag->extra_len + additional; if (needed > ag->extra_cap) { uint32_t new_cap = ag->extra_cap * 2; if (new_cap < needed) new_cap = needed; uint32_t* p = realloc(ag->extra, new_cap * sizeof(uint32_t)); if (!p) exit(1); ag->extra = p; ag->extra_cap = new_cap; } } static void ensureInstCapacity(AstGenCtx* ag, uint32_t additional) { uint32_t needed = ag->inst_len + additional; if (needed > ag->inst_cap) { uint32_t new_cap = ag->inst_cap * 2; if (new_cap < needed) new_cap = needed; ZirInstTag* t = realloc(ag->inst_tags, new_cap * sizeof(ZirInstTag)); ZirInstData* d = realloc(ag->inst_datas, new_cap * sizeof(ZirInstData)); if (!t || !d) exit(1); ag->inst_tags = t; ag->inst_datas = d; ag->inst_cap = new_cap; } } static void ensureStringBytesCapacity(AstGenCtx* ag, uint32_t additional) { uint32_t needed = ag->string_bytes_len + additional; if (needed > ag->string_bytes_cap) { uint32_t new_cap = ag->string_bytes_cap * 2; if (new_cap < needed) new_cap = needed; uint8_t* p = realloc(ag->string_bytes, new_cap * sizeof(uint8_t)); if (!p) exit(1); ag->string_bytes = p; ag->string_bytes_cap = new_cap; } } // --- Extra data helpers --- static uint32_t addExtraU32(AstGenCtx* ag, uint32_t value) { ensureExtraCapacity(ag, 1); uint32_t idx = ag->extra_len; ag->extra[ag->extra_len++] = value; return idx; } // --- Instruction helpers --- // Mirrors AstGen.reserveInstructionIndex (AstGen.zig:12902). static uint32_t reserveInstructionIndex(AstGenCtx* ag) { ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; memset(&ag->inst_datas[idx], 0, sizeof(ZirInstData)); ag->inst_tags[idx] = (ZirInstTag)0; ag->inst_len++; return idx; } // Forward declarations. static int32_t tokenIndexToRelative(const GenZir* gz, uint32_t token); static uint32_t firstToken(const Ast* tree, uint32_t node); static bool nodesNeedRlContains(const AstGenCtx* ag, uint32_t node); // Mirrors GenZir.makeUnTok (AstGen.zig:12520). // Allocates an instruction but does NOT add to GenZir body. // Returns the raw instruction INDEX (not a Ref). static uint32_t makeUnTok( GenZir* gz, ZirInstTag tag, uint32_t operand, uint32_t abs_tok_index) { AstGenCtx* ag = gz->astgen; ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; ZirInstData data; data.un_tok.src_tok = tokenIndexToRelative(gz, abs_tok_index); data.un_tok.operand = operand; ag->inst_tags[idx] = tag; ag->inst_datas[idx] = data; ag->inst_len++; return idx; // Raw index, NOT a Ref. } // Mirrors GenZir.add (AstGen.zig:13162). // Appends an instruction and records it in the GenZir body. // Returns the instruction index as a Ref (index + ZIR_INST_REF_START_INDEX). static uint32_t addInstruction(GenZir* gz, ZirInstTag tag, ZirInstData data) { AstGenCtx* ag = gz->astgen; ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; ag->inst_tags[idx] = tag; ag->inst_datas[idx] = data; ag->inst_len++; // Record in sub-block body. gzAppendInstruction(gz, idx); return idx + ZIR_REF_START_INDEX; // toRef() } // Mirrors GenZir.addInt (AstGen.zig:12238). static uint32_t addInt(GenZir* gz, uint64_t integer) { ZirInstData data; data.int_val = integer; return addInstruction(gz, ZIR_INST_INT, data); } // Mirrors GenZir.add for bin data (Zir.zig:1877). // Creates an instruction with bin data (lhs + rhs stored in inst_datas). static uint32_t addBin( GenZir* gz, ZirInstTag tag, uint32_t lhs, uint32_t rhs) { ZirInstData data; data.bin.lhs = lhs; data.bin.rhs = rhs; return addInstruction(gz, tag, data); } // Mirrors GenZir.addPlNode (AstGen.zig:12308). // Creates an instruction with pl_node data and 2-word payload. static uint32_t addPlNodeBin( GenZir* gz, ZirInstTag tag, uint32_t node, uint32_t lhs, uint32_t rhs) { AstGenCtx* ag = gz->astgen; ensureExtraCapacity(ag, 2); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = lhs; ag->extra[ag->extra_len++] = rhs; ZirInstData data; data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; return addInstruction(gz, tag, data); } // Mirrors addPlNode for 3-operand payloads (e.g. ArrayTypeSentinel). static uint32_t addPlNodeTriple(GenZir* gz, ZirInstTag tag, uint32_t node, uint32_t a, uint32_t b, uint32_t c) { AstGenCtx* ag = gz->astgen; ensureExtraCapacity(ag, 3); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = a; ag->extra[ag->extra_len++] = b; ag->extra[ag->extra_len++] = c; ZirInstData data; data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; return addInstruction(gz, tag, data); } // Checks if an AST identifier node is the single underscore `_`. // Used for inferred array length detection in [_]T patterns. // Intentionally does NOT support @"_" syntax (matches upstream). static bool isUnderscoreIdent(const Ast* tree, uint32_t ident_node) { uint32_t id_tok = tree->nodes.main_tokens[ident_node]; uint32_t id_start = tree->tokens.starts[id_tok]; if (tree->source[id_start] != '_') return false; if (id_start + 1 >= tree->source_len) return true; char next = tree->source[id_start + 1]; return !((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') || next == '_' || (next >= '0' && next <= '9')); } // Mirrors GenZir.addUnNode (AstGen.zig:12406). static uint32_t addUnNode( GenZir* gz, ZirInstTag tag, uint32_t operand, uint32_t node) { ZirInstData data; data.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.un_node.operand = operand; return addInstruction(gz, tag, data); } // Mirrors GenZir.addUnTok (AstGen.zig:12497). static uint32_t addUnTok( GenZir* gz, ZirInstTag tag, uint32_t operand, uint32_t abs_tok_index) { ZirInstData data; data.un_tok.src_tok = tokenIndexToRelative(gz, abs_tok_index); data.un_tok.operand = operand; return addInstruction(gz, tag, data); } // Mirrors GenZir.addStrTok (AstGen.zig:12349). static uint32_t addStrTok( GenZir* gz, ZirInstTag tag, uint32_t str_index, uint32_t token) { ZirInstData data; data.str_tok.start = str_index; data.str_tok.src_tok = tokenIndexToRelative(gz, token); return addInstruction(gz, tag, data); } // Mirrors GenZir.addPlNodePayloadIndex (AstGen.zig:12332). static uint32_t addPlNodePayloadIndex( GenZir* gz, ZirInstTag tag, uint32_t node, uint32_t payload_index) { ZirInstData data; data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; return addInstruction(gz, tag, data); } // Mirrors GenZir.addBuiltinValue (AstGen.zig:12389-12391). // Emits extended instruction with builtin_value opcode. static uint32_t addBuiltinValue( GenZir* gz, uint32_t src_node, uint16_t builtin_val) { AstGenCtx* ag = gz->astgen; ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; ag->inst_tags[idx] = ZIR_INST_EXTENDED; ZirInstData data; data.extended.opcode = (uint16_t)ZIR_EXT_BUILTIN_VALUE; data.extended.small = builtin_val; data.extended.operand = (uint32_t)((int32_t)src_node - (int32_t)gz->decl_node_index); ag->inst_datas[idx] = data; ag->inst_len++; gzAppendInstruction(gz, idx); return idx + ZIR_REF_START_INDEX; } // --- Source cursor (AstGen.zig:13335-13359) --- // Mirrors AstGen.advanceSourceCursor (AstGen.zig:13342). static void advanceSourceCursor(AstGenCtx* ag, uint32_t end) { const char* source = ag->tree->source; uint32_t i = ag->source_offset; uint32_t line = ag->source_line; uint32_t column = ag->source_column; assert(i <= end); while (i < end) { if (source[i] == '\n') { line++; column = 0; } else { column++; } i++; } ag->source_offset = i; ag->source_line = line; ag->source_column = column; } // Mirrors tree.firstToken (Ast.zig:596). // Recurse through nodes to find the first token. static uint32_t firstToken(const Ast* tree, uint32_t node) { uint32_t end_offset = 0; uint32_t n = node; while (1) { AstNodeTag tag = tree->nodes.tags[n]; switch (tag) { case AST_NODE_ROOT: return 0; // Return main_token directly (Ast.zig:602-643). case AST_NODE_TEST_DECL: case AST_NODE_ERRDEFER: case AST_NODE_DEFER: case AST_NODE_BOOL_NOT: case AST_NODE_NEGATION: case AST_NODE_BIT_NOT: case AST_NODE_NEGATION_WRAP: case AST_NODE_ADDRESS_OF: case AST_NODE_TRY: case AST_NODE_AWAIT: case AST_NODE_OPTIONAL_TYPE: case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: case AST_NODE_IF_SIMPLE: case AST_NODE_IF: case AST_NODE_SUSPEND: case AST_NODE_RESUME: case AST_NODE_CONTINUE: case AST_NODE_BREAK: case AST_NODE_RETURN: case AST_NODE_ANYFRAME_TYPE: case AST_NODE_IDENTIFIER: case AST_NODE_ANYFRAME_LITERAL: case AST_NODE_CHAR_LITERAL: case AST_NODE_NUMBER_LITERAL: case AST_NODE_UNREACHABLE_LITERAL: case AST_NODE_STRING_LITERAL: case AST_NODE_MULTILINE_STRING_LITERAL: case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: case AST_NODE_ERROR_SET_DECL: case AST_NODE_COMPTIME: case AST_NODE_NOSUSPEND: case AST_NODE_ASM_SIMPLE: case AST_NODE_ASM: case AST_NODE_ARRAY_TYPE: case AST_NODE_ARRAY_TYPE_SENTINEL: case AST_NODE_ERROR_VALUE: case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: return tree->nodes.main_tokens[n] - end_offset; // Return main_token - 1: dot-prefixed inits and enum_literal // (Ast.zig:645-654). case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_ENUM_LITERAL: return tree->nodes.main_tokens[n] - 1 - end_offset; // Recurse into LHS: all binary ops and compound expressions // (Ast.zig:656-733). case AST_NODE_CATCH: case AST_NODE_EQUAL_EQUAL: case AST_NODE_BANG_EQUAL: case AST_NODE_LESS_THAN: case AST_NODE_GREATER_THAN: case AST_NODE_LESS_OR_EQUAL: case AST_NODE_GREATER_OR_EQUAL: case AST_NODE_ASSIGN_MUL: case AST_NODE_ASSIGN_DIV: case AST_NODE_ASSIGN_MOD: case AST_NODE_ASSIGN_ADD: case AST_NODE_ASSIGN_SUB: case AST_NODE_ASSIGN_SHL: case AST_NODE_ASSIGN_SHL_SAT: case AST_NODE_ASSIGN_SHR: case AST_NODE_ASSIGN_BIT_AND: case AST_NODE_ASSIGN_BIT_XOR: case AST_NODE_ASSIGN_BIT_OR: case AST_NODE_ASSIGN_MUL_WRAP: case AST_NODE_ASSIGN_ADD_WRAP: case AST_NODE_ASSIGN_SUB_WRAP: case AST_NODE_ASSIGN_MUL_SAT: case AST_NODE_ASSIGN_ADD_SAT: case AST_NODE_ASSIGN_SUB_SAT: case AST_NODE_ASSIGN: case AST_NODE_MERGE_ERROR_SETS: case AST_NODE_MUL: case AST_NODE_DIV: case AST_NODE_MOD: case AST_NODE_ARRAY_MULT: case AST_NODE_MUL_WRAP: case AST_NODE_MUL_SAT: case AST_NODE_ADD: case AST_NODE_SUB: case AST_NODE_ARRAY_CAT: case AST_NODE_ADD_WRAP: case AST_NODE_SUB_WRAP: case AST_NODE_ADD_SAT: case AST_NODE_SUB_SAT: case AST_NODE_SHL: case AST_NODE_SHL_SAT: case AST_NODE_SHR: case AST_NODE_BIT_AND: case AST_NODE_BIT_XOR: case AST_NODE_BIT_OR: case AST_NODE_ORELSE: case AST_NODE_BOOL_AND: case AST_NODE_BOOL_OR: case AST_NODE_SLICE_OPEN: case AST_NODE_ARRAY_ACCESS: case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: case AST_NODE_SWITCH_RANGE: case AST_NODE_ERROR_UNION: case AST_NODE_FOR_RANGE: case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: case AST_NODE_SLICE: case AST_NODE_SLICE_SENTINEL: case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: case AST_NODE_FIELD_ACCESS: case AST_NODE_UNWRAP_OPTIONAL: case AST_NODE_DEREF: case AST_NODE_ASYNC_CALL_ONE: case AST_NODE_ASYNC_CALL_ONE_COMMA: case AST_NODE_ASYNC_CALL: case AST_NODE_ASYNC_CALL_COMMA: n = tree->nodes.datas[n].lhs; continue; // Var decls: scan backwards for modifiers (Ast.zig:771-792). case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: { uint32_t mt = tree->nodes.main_tokens[n]; uint32_t i = mt; while (i > 0) { TokenizerTag tt = tree->tokens.tags[i - 1]; if (tt == TOKEN_KEYWORD_EXTERN || tt == TOKEN_KEYWORD_EXPORT || tt == TOKEN_KEYWORD_PUB || tt == TOKEN_KEYWORD_THREADLOCAL || tt == TOKEN_KEYWORD_COMPTIME || tt == TOKEN_STRING_LITERAL) { i--; } else { break; } } return i - end_offset; } // Fn decls: scan backwards for modifiers (Ast.zig:737-759). case AST_NODE_FN_DECL: case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: { uint32_t mt = tree->nodes.main_tokens[n]; uint32_t i = mt; while (i > 0) { TokenizerTag tt = tree->tokens.tags[i - 1]; if (tt == TOKEN_KEYWORD_EXTERN || tt == TOKEN_KEYWORD_EXPORT || tt == TOKEN_KEYWORD_PUB || tt == TOKEN_KEYWORD_INLINE || tt == TOKEN_KEYWORD_NOINLINE || tt == TOKEN_STRING_LITERAL) { i--; } else { break; } } return i - end_offset; } // Container fields: check for preceding comptime (Ast.zig:761-769). case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { uint32_t mt = tree->nodes.main_tokens[n]; if (mt > 0 && tree->tokens.tags[mt - 1] == TOKEN_KEYWORD_COMPTIME) end_offset++; return mt - end_offset; } // Blocks: check for label (Ast.zig:794-805). case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: { uint32_t lbrace = tree->nodes.main_tokens[n]; if (lbrace >= 2 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER) end_offset += 2; return lbrace - end_offset; } // Container decls: check for packed/extern (Ast.zig:807-826). case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: { uint32_t mt = tree->nodes.main_tokens[n]; if (mt > 0) { TokenizerTag prev = tree->tokens.tags[mt - 1]; if (prev == TOKEN_KEYWORD_PACKED || prev == TOKEN_KEYWORD_EXTERN) end_offset++; } return mt - end_offset; } // Switch cases: check for inline/else/values (Ast.zig:834-847). case AST_NODE_SWITCH_CASE_ONE: if (tree->nodes.datas[n].lhs == 0) return tree->nodes.main_tokens[n] - 1 - end_offset; n = tree->nodes.datas[n].lhs; continue; case AST_NODE_SWITCH_CASE_INLINE_ONE: if (tree->nodes.datas[n].lhs == 0) return tree->nodes.main_tokens[n] - 2; end_offset += 1; n = tree->nodes.datas[n].lhs; continue; case AST_NODE_SWITCH_CASE: { uint32_t extra_idx = tree->nodes.datas[n].lhs; uint32_t items_start = tree->extra_data.arr[extra_idx]; uint32_t items_end = tree->extra_data.arr[extra_idx + 1]; if (items_start == items_end) return tree->nodes.main_tokens[n] - 1 - end_offset; n = tree->extra_data.arr[items_start]; continue; } case AST_NODE_SWITCH_CASE_INLINE: { uint32_t extra_idx = tree->nodes.datas[n].lhs; uint32_t items_start = tree->extra_data.arr[extra_idx]; uint32_t items_end = tree->extra_data.arr[extra_idx + 1]; if (items_start == items_end) return tree->nodes.main_tokens[n] - 2; end_offset += 1; n = tree->extra_data.arr[items_start]; continue; } // Asm output/input: first token is '[' before main_token // (Ast.zig:849-852). case AST_NODE_ASM_OUTPUT: case AST_NODE_ASM_INPUT: return tree->nodes.main_tokens[n] - 1 - end_offset; // While/for: check for inline and label (Ast.zig:854-870). case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: { uint32_t result = tree->nodes.main_tokens[n]; if (result > 0 && tree->tokens.tags[result - 1] == TOKEN_KEYWORD_INLINE) result--; if (result >= 2 && tree->tokens.tags[result - 1] == TOKEN_COLON && tree->tokens.tags[result - 2] == TOKEN_IDENTIFIER) result -= 2; return result - end_offset; } // Assign destructure: recurse into first variable // (Ast.zig:735). case AST_NODE_ASSIGN_DESTRUCTURE: { uint32_t extra_start = tree->nodes.datas[n].lhs; // extra_data[extra_start] = variable_count // extra_data[extra_start + 1 .. +1+count] = variables n = tree->extra_data.arr[extra_start + 1]; continue; } // Fallback for any remaining node types. default: return tree->nodes.main_tokens[n] - end_offset; } } } // Mirrors AstGen.advanceSourceCursorToNode (AstGen.zig:13335). static void advanceSourceCursorToNode(AstGenCtx* ag, uint32_t node) { uint32_t ft = firstToken(ag->tree, node); uint32_t token_start = ag->tree->tokens.starts[ft]; (void)0; // cursor backward check disabled temporarily advanceSourceCursor(ag, token_start); } // Mirrors maybeAdvanceSourceCursorToMainToken (AstGen.zig:13324). // Skips advancing when in comptime scope (matching upstream behavior). static void advanceSourceCursorToMainToken( AstGenCtx* ag, const GenZir* gz, uint32_t node) { if (gz->is_comptime) return; uint32_t main_tok = ag->tree->nodes.main_tokens[node]; uint32_t token_start = ag->tree->tokens.starts[main_tok]; advanceSourceCursor(ag, token_start); } // --- Token helpers --- // Mirrors GenZir.tokenIndexToRelative (AstGen.zig:11897). // Returns destination - base as i32. static int32_t tokenIndexToRelative(const GenZir* gz, uint32_t token) { uint32_t base = firstToken(gz->astgen->tree, gz->decl_node_index); return (int32_t)token - (int32_t)base; } // --- String bytes helpers --- // Search for an existing null-terminated string in string_bytes. // Returns the index if found, or UINT32_MAX if not found. // Mirrors string_table dedup (AstGen.zig:11564). // Find a string in string_table (registered strings only). // Mirrors AstGen.string_table hash table lookup. static uint32_t findExistingString( const AstGenCtx* ag, const char* str, uint32_t len) { for (uint32_t k = 0; k < ag->string_table_len; k++) { uint32_t pos = ag->string_table[k]; // Compare: string at pos is null-terminated in string_bytes. const char* existing = (const char*)ag->string_bytes + pos; uint32_t existing_len = (uint32_t)strlen(existing); if (existing_len == len && memcmp(existing, str, len) == 0) { return pos; } } return UINT32_MAX; } // Register a string position in the string table for deduplication. static void registerString(AstGenCtx* ag, uint32_t pos) { if (ag->string_table_len >= ag->string_table_cap) { uint32_t new_cap = ag->string_table_cap * 2; if (new_cap < 64) new_cap = 64; uint32_t* p = realloc(ag->string_table, new_cap * sizeof(uint32_t)); if (!p) exit(1); ag->string_table = p; ag->string_table_cap = new_cap; } ag->string_table[ag->string_table_len++] = pos; } // Mirrors AstGen.tokenIdentEql (AstGen.zig:6148-6152). // Compares two identifier tokens by source text without touching string_bytes. static bool tokenIdentEql(const Ast* tree, uint32_t tok1, uint32_t tok2) { uint32_t s1 = tree->tokens.starts[tok1]; uint32_t s2 = tree->tokens.starts[tok2]; uint32_t e1 = tree->tokens.starts[tok1 + 1]; uint32_t e2 = tree->tokens.starts[tok2 + 1]; // Token length includes trailing whitespace in starts delta, but for // identifiers the actual content is a contiguous alphanumeric/underscore // run. Compute actual identifier lengths. uint32_t len1 = 0; while (s1 + len1 < e1) { char c = tree->source[s1 + len1]; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) break; len1++; } uint32_t len2 = 0; while (s2 + len2 < e2) { char c = tree->source[s2 + len2]; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) break; len2++; } return len1 == len2 && memcmp(tree->source + s1, tree->source + s2, len1) == 0; } // Forward declaration for strLitAsString (used by identAsString for @"..." // quoted identifiers with escapes). static void strLitAsString(AstGenCtx* ag, uint32_t str_lit_token, uint32_t* out_index, uint32_t* out_len); // Mirrors AstGen.identAsString (AstGen.zig:11530). // Handles both bare identifiers and @"..." quoted identifiers. static uint32_t identAsString(AstGenCtx* ag, uint32_t ident_token) { uint32_t start = ag->tree->tokens.starts[ident_token]; const char* source = ag->tree->source; if (source[start] == '@' && start + 1 < ag->tree->source_len && source[start + 1] == '"') { // Quoted identifier: @"name" (AstGen.zig:11297-11308). // Extract content between quotes, handling escapes. uint32_t si, sl; // str_lit_token refers to the same token, content starts after @" // We reuse strLitAsString but offset by 1 to skip '@'. // Actually, strLitAsString expects a token whose source starts // with '"'. The @"..." token starts with '@'. We need to handle // the offset manually. uint32_t content_start = start + 2; // skip @" uint32_t content_end = content_start; while ( content_end < ag->tree->source_len && source[content_end] != '"') content_end++; // Check for escapes. bool has_escapes = false; for (uint32_t j = content_start; j < content_end; j++) { if (source[j] == '\\') { has_escapes = true; break; } } if (!has_escapes) { uint32_t content_len = content_end - content_start; uint32_t existing = findExistingString(ag, source + content_start, content_len); if (existing != UINT32_MAX) return existing; uint32_t str_index = ag->string_bytes_len; ensureStringBytesCapacity(ag, content_len + 1); memcpy(ag->string_bytes + ag->string_bytes_len, source + content_start, content_len); ag->string_bytes_len += content_len; ag->string_bytes[ag->string_bytes_len++] = 0; registerString(ag, str_index); return str_index; } // With escapes: use strLitAsString-like decoding. strLitAsString(ag, ident_token, &si, &sl); return si; } // Bare identifier: scan alphanumeric + underscore. uint32_t end = start; while (end < ag->tree->source_len) { char ch = source[end]; if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') { end++; } else { break; } } uint32_t ident_len = end - start; // Check for existing string (dedup). uint32_t existing = findExistingString(ag, source + start, ident_len); if (existing != UINT32_MAX) return existing; uint32_t str_index = ag->string_bytes_len; ensureStringBytesCapacity(ag, ident_len + 1); memcpy(ag->string_bytes + ag->string_bytes_len, source + start, ident_len); ag->string_bytes_len += ident_len; ag->string_bytes[ag->string_bytes_len++] = 0; registerString(ag, str_index); return str_index; } // Mirrors AstGen.strLitAsString (AstGen.zig:11553). // Decodes string literal, checks for embedded nulls. // If embedded null found: store raw bytes without trailing null, no dedup. // Otherwise: dedup via string_table, add trailing null. static void strLitAsString(AstGenCtx* ag, uint32_t str_lit_token, uint32_t* out_index, uint32_t* out_len) { uint32_t tok_start = ag->tree->tokens.starts[str_lit_token]; const char* source = ag->tree->source; // Skip opening quote. uint32_t i = tok_start + 1; // Find closing quote, skipping escaped characters. uint32_t raw_end = i; while (raw_end < ag->tree->source_len) { if (source[raw_end] == '\\') { raw_end += 2; // skip escape + escaped char } else if (source[raw_end] == '"') { break; } else { raw_end++; } } // Check if there are any escape sequences. bool has_escapes = false; for (uint32_t j = i; j < raw_end; j++) { if (source[j] == '\\') { has_escapes = true; break; } } if (!has_escapes) { // Fast path: no escapes, no embedded nulls possible. uint32_t content_len = raw_end - i; uint32_t existing = findExistingString(ag, source + i, content_len); if (existing != UINT32_MAX) { *out_index = existing; *out_len = content_len; return; } uint32_t str_index = ag->string_bytes_len; ensureStringBytesCapacity(ag, content_len + 1); memcpy( ag->string_bytes + ag->string_bytes_len, source + i, content_len); ag->string_bytes_len += content_len; ag->string_bytes[ag->string_bytes_len++] = 0; registerString(ag, str_index); *out_index = str_index; *out_len = content_len; return; } // Slow path: process escape sequences (AstGen.zig:11558). // Decode directly into string_bytes (like upstream). uint32_t str_index = ag->string_bytes_len; uint32_t max_len = raw_end - i; ensureStringBytesCapacity(ag, max_len + 1); while (i < raw_end) { if (source[i] == '\\') { i++; if (i >= raw_end) break; switch (source[i]) { case 'n': ag->string_bytes[ag->string_bytes_len++] = '\n'; break; case 'r': ag->string_bytes[ag->string_bytes_len++] = '\r'; break; case 't': ag->string_bytes[ag->string_bytes_len++] = '\t'; break; case '\\': ag->string_bytes[ag->string_bytes_len++] = '\\'; break; case '\'': ag->string_bytes[ag->string_bytes_len++] = '\''; break; case '"': ag->string_bytes[ag->string_bytes_len++] = '"'; break; case 'x': { // \xNN hex escape. uint8_t val = 0; for (int k = 0; k < 2 && i + 1 < raw_end; k++) { i++; char c = source[i]; if (c >= '0' && c <= '9') val = (uint8_t)(val * 16 + (uint8_t)(c - '0')); else if (c >= 'a' && c <= 'f') val = (uint8_t)(val * 16 + 10 + (uint8_t)(c - 'a')); else if (c >= 'A' && c <= 'F') val = (uint8_t)(val * 16 + 10 + (uint8_t)(c - 'A')); } ag->string_bytes[ag->string_bytes_len++] = val; break; } case 'u': { // \u{NNNNNN} unicode escape (string_literal.zig:194-231). // Skip past '{'. i++; // Parse hex digits until '}'. uint32_t codepoint = 0; while (i + 1 < raw_end) { i++; char c = source[i]; if (c >= '0' && c <= '9') { codepoint = codepoint * 16 + (uint32_t)(c - '0'); } else if (c >= 'a' && c <= 'f') { codepoint = codepoint * 16 + 10 + (uint32_t)(c - 'a'); } else if (c >= 'A' && c <= 'F') { codepoint = codepoint * 16 + 10 + (uint32_t)(c - 'A'); } else { // Must be '}', done. break; } } // Encode codepoint as UTF-8 (unicode.zig:53-82). if (codepoint <= 0x7F) { ag->string_bytes[ag->string_bytes_len++] = (uint8_t)codepoint; } else if (codepoint <= 0x7FF) { ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0xC0 | (codepoint >> 6)); ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0x80 | (codepoint & 0x3F)); } else if (codepoint <= 0xFFFF) { ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0xE0 | (codepoint >> 12)); ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F)); ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0x80 | (codepoint & 0x3F)); } else { ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0xF0 | (codepoint >> 18)); ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0x80 | ((codepoint >> 12) & 0x3F)); ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F)); ag->string_bytes[ag->string_bytes_len++] = (uint8_t)(0x80 | (codepoint & 0x3F)); } break; } default: ag->string_bytes[ag->string_bytes_len++] = (uint8_t)source[i]; break; } } else { ag->string_bytes[ag->string_bytes_len++] = (uint8_t)source[i]; } i++; } uint32_t decoded_len = ag->string_bytes_len - str_index; uint8_t* key = ag->string_bytes + str_index; // Check for embedded null bytes (AstGen.zig:11560). // If found, skip dedup and don't add trailing null. bool has_embedded_null = false; for (uint32_t j = 0; j < decoded_len; j++) { if (key[j] == 0) { has_embedded_null = true; break; } } if (has_embedded_null) { *out_index = str_index; *out_len = decoded_len; return; } // Dedup against string_table (AstGen.zig:11564-11585). uint32_t existing = findExistingString(ag, (const char*)key, decoded_len); if (existing != UINT32_MAX) { // Shrink back (AstGen.zig:11570). ag->string_bytes_len = str_index; *out_index = existing; *out_len = decoded_len; return; } // New entry: add trailing null and register. ensureStringBytesCapacity(ag, 1); ag->string_bytes[ag->string_bytes_len++] = 0; registerString(ag, str_index); *out_index = str_index; *out_len = decoded_len; } // --- Declaration helpers --- // Mirrors GenZir.makeDeclaration (AstGen.zig:12906). static uint32_t makeDeclaration(AstGenCtx* ag, uint32_t node) { ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; ag->inst_tags[idx] = ZIR_INST_DECLARATION; ZirInstData data; memset(&data, 0, sizeof(data)); data.declaration.src_node = node; // payload_index is set later by setDeclaration. ag->inst_datas[idx] = data; ag->inst_len++; return idx; } // Mirrors GenZir.makeBreakCommon (AstGen.zig:12667). // Creates a break_inline instruction with a Break payload in extra. // Records the instruction in the GenZir body. static uint32_t makeBreakInline(GenZir* gz, uint32_t block_inst, uint32_t operand, int32_t operand_src_node) { AstGenCtx* ag = gz->astgen; ensureInstCapacity(ag, 1); ensureExtraCapacity(ag, 2); // Write Zir.Inst.Break payload to extra (Zir.zig:2489). uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = (uint32_t)operand_src_node; ag->extra[ag->extra_len++] = block_inst; uint32_t idx = ag->inst_len; ag->inst_tags[idx] = ZIR_INST_BREAK_INLINE; ZirInstData data; data.break_data.operand = operand; data.break_data.payload_index = payload_index; ag->inst_datas[idx] = data; ag->inst_len++; // Record in sub-block body. gzAppendInstruction(gz, idx); return idx; } // Mirrors GenZir.makeBlockInst (AstGen.zig:12890). // Creates a pl_node instruction with payload_index left as 0 (set later). // Does NOT append to gz's instruction list. // Returns instruction index (not a ref). static uint32_t makeBlockInst( AstGenCtx* ag, ZirInstTag tag, const GenZir* gz, uint32_t node) { ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; ag->inst_tags[idx] = tag; ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = 0; // set later ag->inst_datas[idx] = data; ag->inst_len++; return idx; } // Mirrors appendPossiblyRefdBodyInst (AstGen.zig:13675-13683). // Appends body_inst first, then recursively appends ref_table entry. static void appendPossiblyRefdBodyInst(AstGenCtx* ag, uint32_t body_inst) { ag->extra[ag->extra_len++] = body_inst; uint32_t ref_inst; if (refTableFetchRemove(ag, body_inst, &ref_inst)) { appendPossiblyRefdBodyInst(ag, ref_inst); } } // Mirrors appendBodyWithFixupsExtraRefsArrayList (AstGen.zig:13659-13673). // First processes extra_refs (e.g. param_insts), prepending their ref_table // entries. Then writes body instructions with ref_table fixups. static void appendBodyWithFixupsExtraRefs(AstGenCtx* ag, const uint32_t* body, uint32_t body_len, const uint32_t* extra_refs, uint32_t extra_refs_len) { for (uint32_t i = 0; i < extra_refs_len; i++) { uint32_t ref_inst; if (refTableFetchRemove(ag, extra_refs[i], &ref_inst)) { appendPossiblyRefdBodyInst(ag, ref_inst); } } for (uint32_t i = 0; i < body_len; i++) { appendPossiblyRefdBodyInst(ag, body[i]); } } // Scratch extra capacity helper (for call arg bodies). static void ensureScratchExtraCapacity(AstGenCtx* ag, uint32_t additional) { uint32_t needed = ag->scratch_extra_len + additional; if (needed > ag->scratch_extra_cap) { uint32_t new_cap = ag->scratch_extra_cap * 2; if (new_cap < needed) new_cap = needed; if (new_cap < 64) new_cap = 64; uint32_t* p = realloc(ag->scratch_extra, new_cap * sizeof(uint32_t)); if (!p) exit(1); ag->scratch_extra = p; ag->scratch_extra_cap = new_cap; } } // Like appendPossiblyRefdBodyInst but appends to scratch_extra instead of // extra. static void appendPossiblyRefdBodyInstScratch( AstGenCtx* ag, uint32_t body_inst) { ag->scratch_extra[ag->scratch_extra_len++] = body_inst; uint32_t ref_inst; if (refTableFetchRemove(ag, body_inst, &ref_inst)) { ensureScratchExtraCapacity(ag, 1); appendPossiblyRefdBodyInstScratch(ag, ref_inst); } } // Mirrors countBodyLenAfterFixupsExtraRefs (AstGen.zig:13694-13711). static uint32_t countBodyLenAfterFixupsExtraRefs(AstGenCtx* ag, const uint32_t* body, uint32_t body_len, const uint32_t* extra_refs, uint32_t extra_refs_len) { uint32_t count = body_len; for (uint32_t i = 0; i < body_len; i++) { uint32_t check_inst = body[i]; const uint32_t* ref; while ((ref = refTableGet(ag, check_inst)) != NULL) { count++; check_inst = *ref; } } for (uint32_t i = 0; i < extra_refs_len; i++) { uint32_t check_inst = extra_refs[i]; const uint32_t* ref; while ((ref = refTableGet(ag, check_inst)) != NULL) { count++; check_inst = *ref; } } return count; } // Mirrors countBodyLenAfterFixups (AstGen.zig:13686-13688). static uint32_t countBodyLenAfterFixups( AstGenCtx* ag, const uint32_t* body, uint32_t body_len) { return countBodyLenAfterFixupsExtraRefs(ag, body, body_len, NULL, 0); } // Mirrors GenZir.setBlockBody (AstGen.zig:11949). // Writes Block payload (body_len + instruction indices) to extra. // Sets the instruction's payload_index. Unstacks gz. static void setBlockBody(AstGenCtx* ag, GenZir* gz, uint32_t inst) { uint32_t raw_body_len = gzInstructionsLen(gz); const uint32_t* body = gzInstructionsSlice(gz); uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len); ensureExtraCapacity(ag, 1 + body_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = body_len; for (uint32_t i = 0; i < raw_body_len; i++) { appendPossiblyRefdBodyInst(ag, body[i]); } ag->inst_datas[inst].pl_node.payload_index = payload_index; gzUnstack(gz); } // Mirrors GenZir.setTryBody (AstGen.zig:11997). // Writes Try payload (operand + body_len + instruction indices) to extra. // Sets the instruction's payload_index. Unstacks gz. static void setTryBody( AstGenCtx* ag, GenZir* gz, uint32_t inst, uint32_t operand) { uint32_t raw_body_len = gzInstructionsLen(gz); const uint32_t* body = gzInstructionsSlice(gz); uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len); ensureExtraCapacity(ag, 2 + body_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = operand; // Try.operand ag->extra[ag->extra_len++] = body_len; // Try.body_len for (uint32_t i = 0; i < raw_body_len; i++) { appendPossiblyRefdBodyInst(ag, body[i]); } ag->inst_datas[inst].pl_node.payload_index = payload_index; gzUnstack(gz); } // Mirrors GenZir.setBlockComptimeBody (AstGen.zig:11972). // Like setBlockBody but prepends comptime_reason before body_len. // Asserts inst is a BLOCK_COMPTIME. static void setBlockComptimeBody( AstGenCtx* ag, GenZir* gz, uint32_t inst, uint32_t comptime_reason) { uint32_t raw_body_len = gzInstructionsLen(gz); const uint32_t* body = gzInstructionsSlice(gz); uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len); ensureExtraCapacity(ag, 2 + body_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = comptime_reason; ag->extra[ag->extra_len++] = body_len; for (uint32_t i = 0; i < raw_body_len; i++) { appendPossiblyRefdBodyInst(ag, body[i]); } ag->inst_datas[inst].pl_node.payload_index = payload_index; gzUnstack(gz); } // Mirrors GenZir.addBreak (AstGen.zig:12623). // Creates a ZIR_INST_BREAK instruction. static uint32_t addBreak(GenZir* gz, ZirInstTag tag, uint32_t block_inst, uint32_t operand, int32_t operand_src_node) { AstGenCtx* ag = gz->astgen; ensureInstCapacity(ag, 1); ensureExtraCapacity(ag, 2); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = (uint32_t)operand_src_node; ag->extra[ag->extra_len++] = block_inst; uint32_t idx = ag->inst_len; ag->inst_tags[idx] = tag; ZirInstData data; data.break_data.operand = operand; data.break_data.payload_index = payload_index; ag->inst_datas[idx] = data; ag->inst_len++; gzAppendInstruction(gz, idx); return idx; } // Mirrors GenZir.addCondBr (AstGen.zig:12834). // Creates condbr instruction placeholder with src_node set. // Payload is filled later by setCondBrPayload. static uint32_t addCondBr(GenZir* gz, ZirInstTag tag, uint32_t node) { AstGenCtx* ag = gz->astgen; ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; ag->inst_tags[idx] = tag; ZirInstData data; memset(&data, 0, sizeof(data)); data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = 0; // set later ag->inst_datas[idx] = data; ag->inst_len++; gzAppendInstruction(gz, idx); return idx; } // Mirrors setCondBrPayload (AstGen.zig:6501). // Writes CondBr payload: {condition, then_body_len, else_body_len} then // then_body instructions, then else_body instructions. Unstacks both scopes. // IMPORTANT: then_gz and else_gz are stacked (else on top of then), so // then's instructions must use instructionsSliceUpto(else_gz) to avoid // including else_gz's instructions in then's body. static void setCondBrPayload(AstGenCtx* ag, uint32_t condbr_inst, uint32_t condition, GenZir* then_gz, GenZir* else_gz) { uint32_t raw_then_len = gzInstructionsLenUpto(then_gz, else_gz); const uint32_t* then_body = gzInstructionsSliceUpto(then_gz, else_gz); uint32_t raw_else_len = gzInstructionsLen(else_gz); const uint32_t* else_body = gzInstructionsSlice(else_gz); uint32_t then_len = countBodyLenAfterFixups(ag, then_body, raw_then_len); uint32_t else_len = countBodyLenAfterFixups(ag, else_body, raw_else_len); ensureExtraCapacity(ag, 3 + then_len + else_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = condition; // CondBr.condition ag->extra[ag->extra_len++] = then_len; // CondBr.then_body_len ag->extra[ag->extra_len++] = else_len; // CondBr.else_body_len for (uint32_t i = 0; i < raw_then_len; i++) appendPossiblyRefdBodyInst(ag, then_body[i]); for (uint32_t i = 0; i < raw_else_len; i++) appendPossiblyRefdBodyInst(ag, else_body[i]); ag->inst_datas[condbr_inst].pl_node.payload_index = payload_index; gzUnstack(else_gz); gzUnstack(then_gz); } // Does this Declaration.Flags.Id have a name? (Zir.zig:2762) static bool declIdHasName(DeclFlagsId id) { return id != DECL_ID_UNNAMED_TEST && id != DECL_ID_COMPTIME; } // Does this Declaration.Flags.Id have a lib name? (Zir.zig:2771) static bool declIdHasLibName(DeclFlagsId id) { switch (id) { case DECL_ID_EXTERN_CONST: case DECL_ID_PUB_EXTERN_CONST: case DECL_ID_EXTERN_VAR: case DECL_ID_EXTERN_VAR_THREADLOCAL: case DECL_ID_PUB_EXTERN_VAR: case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL: return true; default: return false; } } // Does this Declaration.Flags.Id have a type body? (Zir.zig:2783) static bool declIdHasTypeBody(DeclFlagsId id) { switch (id) { case DECL_ID_UNNAMED_TEST: case DECL_ID_TEST: case DECL_ID_DECLTEST: case DECL_ID_COMPTIME: case DECL_ID_CONST_SIMPLE: case DECL_ID_PUB_CONST_SIMPLE: case DECL_ID_VAR_SIMPLE: case DECL_ID_PUB_VAR_SIMPLE: return false; default: return true; } } // Does this Declaration.Flags.Id have a value body? (Zir.zig:2800) static bool declIdHasValueBody(DeclFlagsId id) { switch (id) { case DECL_ID_EXTERN_CONST_SIMPLE: case DECL_ID_EXTERN_CONST: case DECL_ID_PUB_EXTERN_CONST_SIMPLE: case DECL_ID_PUB_EXTERN_CONST: case DECL_ID_EXTERN_VAR: case DECL_ID_EXTERN_VAR_THREADLOCAL: case DECL_ID_PUB_EXTERN_VAR: case DECL_ID_PUB_EXTERN_VAR_THREADLOCAL: return false; default: return true; } } // Does this Declaration.Flags.Id have special bodies? (Zir.zig:2815) static bool declIdHasSpecialBodies(DeclFlagsId id) { switch (id) { case DECL_ID_UNNAMED_TEST: case DECL_ID_TEST: case DECL_ID_DECLTEST: case DECL_ID_COMPTIME: case DECL_ID_CONST_SIMPLE: case DECL_ID_CONST_TYPED: case DECL_ID_PUB_CONST_SIMPLE: case DECL_ID_PUB_CONST_TYPED: case DECL_ID_EXTERN_CONST_SIMPLE: case DECL_ID_PUB_EXTERN_CONST_SIMPLE: case DECL_ID_VAR_SIMPLE: case DECL_ID_PUB_VAR_SIMPLE: return false; default: return true; } } // Mirrors setDeclaration (AstGen.zig:13883). // Full version with type/align/linksection/addrspace/value bodies. typedef struct { uint32_t src_line; uint32_t src_column; DeclFlagsId id; uint32_t name; // NullTerminatedString index uint32_t lib_name; // NullTerminatedString index (UINT32_MAX=none) const uint32_t* type_body; uint32_t type_body_len; const uint32_t* align_body; uint32_t align_body_len; const uint32_t* linksection_body; uint32_t linksection_body_len; const uint32_t* addrspace_body; uint32_t addrspace_body_len; const uint32_t* value_body; uint32_t value_body_len; } SetDeclArgs; static void setDeclaration( AstGenCtx* ag, uint32_t decl_inst, SetDeclArgs args) { DeclFlagsId id = args.id; bool has_name = declIdHasName(id); bool has_lib_name = declIdHasLibName(id); bool has_type_body_field = declIdHasTypeBody(id); bool has_special_bodies = declIdHasSpecialBodies(id); bool has_value_body_field = declIdHasValueBody(id); uint32_t type_len = countBodyLenAfterFixups(ag, args.type_body, args.type_body_len); uint32_t align_len = countBodyLenAfterFixups(ag, args.align_body, args.align_body_len); uint32_t linksection_len = countBodyLenAfterFixups( ag, args.linksection_body, args.linksection_body_len); uint32_t addrspace_len = countBodyLenAfterFixups( ag, args.addrspace_body, args.addrspace_body_len); uint32_t value_len = countBodyLenAfterFixups(ag, args.value_body, args.value_body_len); uint32_t need = 6; // src_hash[4] + flags[2] if (has_name) need++; if (has_lib_name) need++; if (has_type_body_field) need++; if (has_special_bodies) need += 3; if (has_value_body_field) need++; need += type_len + align_len + linksection_len + addrspace_len + value_len; ensureExtraCapacity(ag, need); uint32_t payload_start = ag->extra_len; // src_hash (4 words): zero-filled; hash comparison skipped in tests. ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; // Declaration.Flags: packed struct(u64) { src_line: u30, src_column: u29, // id: u5 } (Zir.zig:2719) uint64_t flags = 0; flags |= (uint64_t)(args.src_line & 0x3FFFFFFFu); flags |= (uint64_t)(args.src_column & 0x1FFFFFFFu) << 30; flags |= (uint64_t)((uint32_t)id & 0x1Fu) << 59; ag->extra[ag->extra_len++] = (uint32_t)(flags & 0xFFFFFFFFu); ag->extra[ag->extra_len++] = (uint32_t)(flags >> 32); if (has_name) ag->extra[ag->extra_len++] = args.name; if (has_lib_name) { ag->extra[ag->extra_len++] = (args.lib_name != UINT32_MAX) ? args.lib_name : 0; } if (has_type_body_field) ag->extra[ag->extra_len++] = type_len; if (has_special_bodies) { ag->extra[ag->extra_len++] = align_len; ag->extra[ag->extra_len++] = linksection_len; ag->extra[ag->extra_len++] = addrspace_len; } if (has_value_body_field) ag->extra[ag->extra_len++] = value_len; for (uint32_t i = 0; i < args.type_body_len; i++) appendPossiblyRefdBodyInst(ag, args.type_body[i]); for (uint32_t i = 0; i < args.align_body_len; i++) appendPossiblyRefdBodyInst(ag, args.align_body[i]); for (uint32_t i = 0; i < args.linksection_body_len; i++) appendPossiblyRefdBodyInst(ag, args.linksection_body[i]); for (uint32_t i = 0; i < args.addrspace_body_len; i++) appendPossiblyRefdBodyInst(ag, args.addrspace_body[i]); for (uint32_t i = 0; i < args.value_body_len; i++) appendPossiblyRefdBodyInst(ag, args.value_body[i]); ag->inst_datas[decl_inst].declaration.payload_index = payload_start; } // --- StructDecl.Small packing (Zir.zig StructDecl.Small) --- typedef struct { bool has_captures_len; bool has_fields_len; bool has_decls_len; bool has_backing_int; bool known_non_opv; bool known_comptime_only; uint8_t name_strategy; // 2 bits uint8_t layout; // 2 bits bool any_default_inits; bool any_comptime_fields; bool any_aligned_fields; } StructDeclSmall; static uint16_t packStructDeclSmall(StructDeclSmall s) { uint16_t r = 0; if (s.has_captures_len) r |= (1u << 0); if (s.has_fields_len) r |= (1u << 1); if (s.has_decls_len) r |= (1u << 2); if (s.has_backing_int) r |= (1u << 3); if (s.known_non_opv) r |= (1u << 4); if (s.known_comptime_only) r |= (1u << 5); r |= (uint16_t)(s.name_strategy & 0x3u) << 6; r |= (uint16_t)(s.layout & 0x3u) << 8; if (s.any_default_inits) r |= (1u << 10); if (s.any_comptime_fields) r |= (1u << 11); if (s.any_aligned_fields) r |= (1u << 12); return r; } // Mirrors GenZir.setStruct (AstGen.zig:12935). // Writes StructDecl payload and optional length fields. // The caller appends captures, backing_int, decls, fields, bodies after. static void setStruct(AstGenCtx* ag, uint32_t inst, uint32_t src_node, StructDeclSmall small, uint32_t captures_len, uint32_t fields_len, uint32_t decls_len) { ensureExtraCapacity(ag, 6 + 3); uint32_t payload_index = ag->extra_len; // fields_hash (4 words): zero-filled; hash comparison skipped in tests. ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = ag->source_line; ag->extra[ag->extra_len++] = src_node; if (small.has_captures_len) ag->extra[ag->extra_len++] = captures_len; if (small.has_fields_len) ag->extra[ag->extra_len++] = fields_len; if (small.has_decls_len) ag->extra[ag->extra_len++] = decls_len; ag->inst_tags[inst] = ZIR_INST_EXTENDED; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_STRUCT_DECL; data.extended.small = packStructDeclSmall(small); data.extended.operand = payload_index; ag->inst_datas[inst] = data; } // --- scanContainer (AstGen.zig:13384) --- // Add a name→node entry to the decl table. static void addDeclToTable( AstGenCtx* ag, uint32_t name_str_index, uint32_t node) { if (ag->decl_table_len >= ag->decl_table_cap) { uint32_t new_cap = ag->decl_table_cap > 0 ? ag->decl_table_cap * 2 : 8; uint32_t* n = realloc(ag->decl_names, new_cap * sizeof(uint32_t)); uint32_t* d = realloc(ag->decl_nodes, new_cap * sizeof(uint32_t)); if (!n || !d) exit(1); ag->decl_names = n; ag->decl_nodes = d; ag->decl_table_cap = new_cap; } ag->decl_names[ag->decl_table_len] = name_str_index; ag->decl_nodes[ag->decl_table_len] = node; ag->decl_table_len++; } // Mirrors scanContainer (AstGen.zig:13384). // Also populates the decl table (namespace.decls) for identifier resolution. static uint32_t scanContainer( AstGenCtx* ag, const uint32_t* members, uint32_t member_count) { const Ast* tree = ag->tree; uint32_t decl_count = 0; for (uint32_t i = 0; i < member_count; i++) { uint32_t member = members[i]; AstNodeTag tag = tree->nodes.tags[member]; switch (tag) { case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: { decl_count++; uint32_t name_token = tree->nodes.main_tokens[member] + 1; uint32_t name_str = identAsString(ag, name_token); addDeclToTable(ag, name_str, member); break; } case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: case AST_NODE_FN_DECL: { decl_count++; uint32_t name_token = tree->nodes.main_tokens[member] + 1; uint32_t name_str = identAsString(ag, name_token); addDeclToTable(ag, name_str, member); break; } // Container fields: add field name to string table for ordering // (AstGen.zig:13509). case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { uint32_t main_token = tree->nodes.main_tokens[member]; identAsString(ag, main_token); break; } case AST_NODE_COMPTIME: decl_count++; break; case AST_NODE_TEST_DECL: { decl_count++; // Process test name string to match upstream string table // ordering (AstGen.zig:13465-13500). uint32_t test_name_token = tree->nodes.main_tokens[member] + 1; TokenizerTag tt = tree->tokens.tags[test_name_token]; if (tt == TOKEN_STRING_LITERAL) { uint32_t si, sl; strLitAsString(ag, test_name_token, &si, &sl); } else if (tt == TOKEN_IDENTIFIER) { identAsString(ag, test_name_token); } break; } default: break; } } return decl_count; } // --- Import tracking --- static void addImport(AstGenCtx* ag, uint32_t name_index, uint32_t token) { // Check for duplicates. for (uint32_t i = 0; i < ag->imports_len; i++) { if (ag->imports[i].name == name_index) return; } if (ag->imports_len >= ag->imports_cap) { uint32_t new_cap = ag->imports_cap > 0 ? ag->imports_cap * 2 : 4; ImportEntry* p = realloc(ag->imports, new_cap * sizeof(ImportEntry)); if (!p) exit(1); ag->imports = p; ag->imports_cap = new_cap; } ag->imports[ag->imports_len].name = name_index; ag->imports[ag->imports_len].token = token; ag->imports_len++; } // Write imports list to extra (AstGen.zig:227-244). static void writeImports(AstGenCtx* ag) { if (ag->imports_len == 0) { ag->extra[ZIR_EXTRA_IMPORTS] = 0; return; } uint32_t need = 1 + ag->imports_len * 2; ensureExtraCapacity(ag, need); uint32_t imports_index = ag->extra_len; ag->extra[ag->extra_len++] = ag->imports_len; for (uint32_t i = 0; i < ag->imports_len; i++) { ag->extra[ag->extra_len++] = ag->imports[i].name; ag->extra[ag->extra_len++] = ag->imports[i].token; } ag->extra[ZIR_EXTRA_IMPORTS] = imports_index; } // ri.br() (AstGen.zig:274-282): convert coerced_ty to ty for branching. static inline ResultLoc rlBr(ResultLoc rl) { if (rl.tag == RL_COERCED_TY) { return (ResultLoc) { .tag = RL_TY, .data = rl.data, .src_node = 0, .ctx = rl.ctx }; } return rl; } // setBreakResultInfo (AstGen.zig:11905-11926): compute break result info // from parent RL. Converts coerced_ty → ty, discard → discard, else passes // through. For ptr/inferred_ptr, converts to ty/none respectively. static ResultLoc breakResultInfo( GenZir* gz, ResultLoc parent_rl, uint32_t node, bool need_rl) { // First: compute block_ri (AstGen.zig:7639-7646). // When need_rl is true, forward the rl as-is (don't convert ptr→ty). ResultLoc block_ri; if (need_rl) { block_ri = parent_rl; } else { switch (parent_rl.tag) { case RL_PTR: { uint32_t ptr_ty = addUnNode(gz, ZIR_INST_TYPEOF, parent_rl.data, node); uint32_t ty = addUnNode(gz, ZIR_INST_ELEM_TYPE, ptr_ty, node); block_ri = (ResultLoc) { .tag = RL_TY, .data = ty, .src_node = 0, .ctx = parent_rl.ctx }; break; } case RL_INFERRED_PTR: block_ri = (ResultLoc) { .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = parent_rl.ctx }; break; default: block_ri = parent_rl; break; } } // Then: setBreakResultInfo (AstGen.zig:11910-11925). switch (block_ri.tag) { case RL_COERCED_TY: return (ResultLoc) { .tag = RL_TY, .data = block_ri.data, .src_node = 0, .ctx = block_ri.ctx }; case RL_DISCARD: // Don't forward ctx (AstGen.zig:11916-11920). return RL_DISCARD_VAL; default: return block_ri; } } // resultType (AstGen.zig:341-351): extract result type from RL. // Returns 0 if no result type available. static uint32_t rlResultType(GenZir* gz, ResultLoc rl, uint32_t node) { switch (rl.tag) { case RL_TY: case RL_COERCED_TY: return rl.data; case RL_REF_COERCED_TY: // AstGen.zig:345: .ref_coerced_ty => |ptr_ty| gz.addUnNode(.elem_type, // ptr_ty, node) return addUnNode(gz, ZIR_INST_ELEM_TYPE, rl.data, node); case RL_PTR: { // typeof(ptr) -> elem_type (AstGen.zig:346-349). uint32_t ptr_ty = addUnNode(gz, ZIR_INST_TYPEOF, rl.data, node); return addUnNode(gz, ZIR_INST_ELEM_TYPE, ptr_ty, node); } default: return 0; } } // Mirrors ResultLoc.resultTypeForCast (AstGen.zig:356-368). // Like rlResultType but errors if no result type is available. static uint32_t rlResultTypeForCast(GenZir* gz, ResultLoc rl, uint32_t node) { uint32_t ty = rlResultType(gz, rl, node); if (ty != 0) return ty; SET_ERROR(gz->astgen); return 0; } static bool endsWithNoReturn(GenZir* gz); // Mirrors Zir.Inst.Tag.isAlwaysVoid (Zir.zig:1343-1608). static bool isAlwaysVoid(ZirInstTag tag, ZirInstData data) { switch (tag) { case ZIR_INST_DBG_STMT: case ZIR_INST_DBG_VAR_PTR: case ZIR_INST_DBG_VAR_VAL: case ZIR_INST_ENSURE_RESULT_USED: case ZIR_INST_ENSURE_RESULT_NON_ERROR: case ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID: case ZIR_INST_SET_EVAL_BRANCH_QUOTA: case ZIR_INST_ATOMIC_STORE: case ZIR_INST_STORE_NODE: case ZIR_INST_STORE_TO_INFERRED_PTR: case ZIR_INST_VALIDATE_DEREF: case ZIR_INST_VALIDATE_DESTRUCTURE: case ZIR_INST_EXPORT: case ZIR_INST_SET_RUNTIME_SAFETY: case ZIR_INST_MEMCPY: case ZIR_INST_MEMSET: case ZIR_INST_MEMMOVE: case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW: case ZIR_INST_DEFER: case ZIR_INST_DEFER_ERR_CODE: case ZIR_INST_SAVE_ERR_RET_INDEX: case ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL: case ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY: case ZIR_INST_VALIDATE_STRUCT_INIT_TY: case ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY: case ZIR_INST_VALIDATE_PTR_STRUCT_INIT: case ZIR_INST_VALIDATE_ARRAY_INIT_TY: case ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY: case ZIR_INST_VALIDATE_PTR_ARRAY_INIT: case ZIR_INST_VALIDATE_REF_TY: case ZIR_INST_VALIDATE_CONST: return true; case ZIR_INST_EXTENDED: switch (data.extended.opcode) { case ZIR_EXT_BRANCH_HINT: case ZIR_EXT_BREAKPOINT: case ZIR_EXT_DISABLE_INSTRUMENTATION: case ZIR_EXT_DISABLE_INTRINSICS: return true; default: return false; } default: return false; } } // rvalue (AstGen.zig:11051-11224): apply result location wrapping. static uint32_t rvalue( GenZir* gz, ResultLoc rl, uint32_t result, uint32_t node) { // isAlwaysVoid check (AstGen.zig:11058-11067). if (result >= ZIR_REF_START_INDEX) { uint32_t result_index = result - ZIR_REF_START_INDEX; if (isAlwaysVoid(gz->astgen->inst_tags[result_index], gz->astgen->inst_datas[result_index])) { result = ZIR_REF_VOID_VALUE; } } // endsWithNoReturn check (AstGen.zig:11068). if (endsWithNoReturn(gz)) return result; switch (rl.tag) { case RL_NONE: case RL_COERCED_TY: return result; case RL_DISCARD: // ensure_result_non_error (AstGen.zig:11071-11074). addUnNode(gz, ZIR_INST_ENSURE_RESULT_NON_ERROR, result, node); return ZIR_REF_VOID_VALUE; case RL_REF: case RL_REF_COERCED_TY: { // coerce_ptr_elem_ty for ref_coerced_ty (AstGen.zig:11077-11083). uint32_t coerced_result = result; if (rl.tag == RL_REF_COERCED_TY) { coerced_result = addPlNodeBin( gz, ZIR_INST_COERCE_PTR_ELEM_TY, node, rl.data, result); } AstGenCtx* ag = gz->astgen; uint32_t src_token = firstToken(ag->tree, node); // If result is not an instruction index (e.g. a well-known ref), // emit ref directly (AstGen.zig:11091-11092). if (coerced_result < ZIR_REF_START_INDEX) { return addUnTok(gz, ZIR_INST_REF, coerced_result, src_token); } // Deduplication via ref_table (AstGen.zig:11093-11097). uint32_t result_index = coerced_result - ZIR_REF_START_INDEX; bool found; uint32_t* val_ptr = refTableGetOrPut(ag, result_index, &found); if (!found) { *val_ptr = makeUnTok(gz, ZIR_INST_REF, coerced_result, src_token); } return *val_ptr + ZIR_REF_START_INDEX; } case RL_TY: { // Quick elimination of common, unnecessary type coercions // (AstGen.zig:11099-11209). #define RC(t, v) (((uint64_t)(t) << 32) | (uint64_t)(v)) uint64_t combined = RC(rl.data, result); switch (combined) { // Identity: type of result is already correct // (AstGen.zig:11109-11176). case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U1_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U8_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I8_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U16_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U29_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I16_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U32_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I32_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U64_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I64_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_U128_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_I128_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_USIZE_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ISIZE_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_CHAR_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_SHORT_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_USHORT_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_INT_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_UINT_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_LONG_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_ULONG_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_LONGLONG_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_ULONGLONG_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_C_LONGDOUBLE_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F16_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F32_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F64_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F80_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_F128_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ANYOPAQUE_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_BOOL_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_VOID_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_TYPE_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ANYERROR_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_COMPTIME_INT_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_COMPTIME_FLOAT_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_NORETURN_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ANYFRAME_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_NULL_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_UNDEFINED_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ENUM_LITERAL_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_PTR_USIZE_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_PTR_CONST_COMPTIME_INT_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_MANYPTR_U8_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_MANYPTR_CONST_U8_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_MANYPTR_CONST_U8_SENTINEL_0_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_SLICE_CONST_U8_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_SLICE_CONST_U8_SENTINEL_0_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_GENERIC_POISON_TYPE): case RC(ZIR_REF_TYPE_TYPE, ZIR_REF_EMPTY_TUPLE_TYPE): case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ZERO): case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ONE): case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_NEGATIVE_ONE): case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_UNDEF_USIZE): case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ZERO_USIZE): case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ONE_USIZE): case RC(ZIR_REF_U1_TYPE, ZIR_REF_UNDEF_U1): case RC(ZIR_REF_U1_TYPE, ZIR_REF_ZERO_U1): case RC(ZIR_REF_U1_TYPE, ZIR_REF_ONE_U1): case RC(ZIR_REF_U8_TYPE, ZIR_REF_ZERO_U8): case RC(ZIR_REF_U8_TYPE, ZIR_REF_ONE_U8): case RC(ZIR_REF_U8_TYPE, ZIR_REF_FOUR_U8): case RC(ZIR_REF_BOOL_TYPE, ZIR_REF_UNDEF_BOOL): case RC(ZIR_REF_BOOL_TYPE, ZIR_REF_BOOL_TRUE): case RC(ZIR_REF_BOOL_TYPE, ZIR_REF_BOOL_FALSE): case RC(ZIR_REF_VOID_TYPE, ZIR_REF_VOID_VALUE): return result; // Conversions (AstGen.zig:11178-11202). case RC(ZIR_REF_BOOL_TYPE, ZIR_REF_UNDEF): return ZIR_REF_UNDEF_BOOL; case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_UNDEF): return ZIR_REF_UNDEF_USIZE; case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_UNDEF_U1): return ZIR_REF_UNDEF_USIZE; case RC(ZIR_REF_U1_TYPE, ZIR_REF_UNDEF): return ZIR_REF_UNDEF_U1; case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ZERO): return ZIR_REF_ZERO_USIZE; case RC(ZIR_REF_U1_TYPE, ZIR_REF_ZERO): return ZIR_REF_ZERO_U1; case RC(ZIR_REF_U8_TYPE, ZIR_REF_ZERO): return ZIR_REF_ZERO_U8; case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ONE): return ZIR_REF_ONE_USIZE; case RC(ZIR_REF_U1_TYPE, ZIR_REF_ONE): return ZIR_REF_ONE_U1; case RC(ZIR_REF_U8_TYPE, ZIR_REF_ONE): return ZIR_REF_ONE_U8; case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ZERO_USIZE): return ZIR_REF_ZERO; case RC(ZIR_REF_U1_TYPE, ZIR_REF_ZERO_USIZE): return ZIR_REF_ZERO_U1; case RC(ZIR_REF_U8_TYPE, ZIR_REF_ZERO_USIZE): return ZIR_REF_ZERO_U8; case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ONE_USIZE): return ZIR_REF_ONE; case RC(ZIR_REF_U1_TYPE, ZIR_REF_ONE_USIZE): return ZIR_REF_ONE_U1; case RC(ZIR_REF_U8_TYPE, ZIR_REF_ONE_USIZE): return ZIR_REF_ONE_U8; case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ZERO_U1): return ZIR_REF_ZERO; case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ZERO_U8): return ZIR_REF_ZERO; case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ZERO_U1): return ZIR_REF_ZERO_USIZE; case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ZERO_U8): return ZIR_REF_ZERO_USIZE; case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ONE_U1): return ZIR_REF_ONE; case RC(ZIR_REF_COMPTIME_INT_TYPE, ZIR_REF_ONE_U8): return ZIR_REF_ONE; case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ONE_U1): return ZIR_REF_ONE_USIZE; case RC(ZIR_REF_USIZE_TYPE, ZIR_REF_ONE_U8): return ZIR_REF_ONE_USIZE; default: { ZirInstTag as_tag = (rl.ctx == RI_CTX_SHIFT_OP) ? ZIR_INST_AS_SHIFT_OPERAND : ZIR_INST_AS_NODE; return addPlNodeBin(gz, as_tag, node, rl.data, result); } } #undef RC } case RL_PTR: // store_node (AstGen.zig:11211-11216). addPlNodeBin(gz, ZIR_INST_STORE_NODE, rl.src_node != 0 ? rl.src_node : node, rl.data, result); return ZIR_REF_VOID_VALUE; case RL_INFERRED_PTR: // store_to_inferred_ptr (AstGen.zig:11218-11223). addPlNodeBin( gz, ZIR_INST_STORE_TO_INFERRED_PTR, node, rl.data, result); return ZIR_REF_VOID_VALUE; } return result; } // rvalueNoCoercePreRef (AstGen.zig:11042-11049): like rvalue but does NOT // emit coerce_ptr_elem_ty for RL_REF_COERCED_TY. Used for local var refs. static uint32_t rvalueNoCoercePreRef( GenZir* gz, ResultLoc rl, uint32_t result, uint32_t node) { if (rl.tag == RL_REF_COERCED_TY) { ResultLoc ref_rl = rl; ref_rl.tag = RL_REF; return rvalue(gz, ref_rl, result, node); } return rvalue(gz, rl, result, node); } // --- Expression evaluation (AstGen.zig:634) --- // Forward declarations. static uint32_t expr(GenZir* gz, Scope* scope, uint32_t node); // --- DefersToEmit (AstGen.zig:3008) --- #define DEFER_NORMAL_ONLY 0 #define DEFER_BOTH_SANS_ERR 1 // --- DeferCounts (AstGen.zig:2966) --- typedef struct { bool have_any; bool have_normal; bool have_err; bool need_err_code; } DeferCounts; static DeferCounts countDefers(const Scope* outer_scope, Scope* inner_scope); static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); static void assignStmt(GenZir* gz, Scope* scope, uint32_t infix_node); static void assignOp( GenZir* gz, Scope* scope, uint32_t infix_node, ZirInstTag op_tag); static void assignShift( GenZir* gz, Scope* scope, uint32_t infix_node, ZirInstTag op_tag); static void assignShiftSat(GenZir* gz, Scope* scope, uint32_t infix_node); static uint32_t shiftOp( GenZir* gz, Scope* scope, uint32_t node, ZirInstTag tag); static void emitDbgStmt(GenZir* gz, uint32_t line, uint32_t column); static void genDefers( GenZir* gz, const Scope* outer_scope, Scope* inner_scope, int which); static void emitDbgStmtForceCurrentIndex( GenZir* gz, uint32_t line, uint32_t column); static void emitDbgNode(GenZir* gz, uint32_t node); static void addDbgVar( GenZir* gz, ZirInstTag tag, uint32_t name, uint32_t inst); static bool addEnsureResult( GenZir* gz, uint32_t maybe_unused_result, uint32_t statement); static void blockExprStmts( GenZir* gz, Scope* scope, const uint32_t* statements, uint32_t stmt_count); static uint32_t fullBodyExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); static bool nameStratExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, uint8_t name_strategy, uint32_t* out_ref); static bool tokenIsUnderscore(const Ast* tree, uint32_t ident_token); static uint32_t containerDecl( GenZir* gz, Scope* scope, uint32_t node, uint8_t name_strategy); static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len, uint8_t layout, uint32_t backing_int_node, uint8_t name_strategy); static uint32_t tupleDecl(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len, uint8_t layout, uint32_t backing_int_node); static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len, uint32_t arg_node, uint8_t name_strategy); static uint32_t blockExprExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); static uint32_t forExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_statement); static uint32_t orelseCatchExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, ZirInstTag cond_op, ZirInstTag unwrap_op, ZirInstTag unwrap_code_op); static uint32_t arrayInitDotExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); static uint32_t switchExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); static uint32_t whileExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_statement); #define EVAL_TO_ERROR_NEVER 0 #define EVAL_TO_ERROR_ALWAYS 1 #define EVAL_TO_ERROR_MAYBE 2 static int nodeMayEvalToError(const Ast* tree, uint32_t node); static bool nodeMayAppendToErrorTrace(const Ast* tree, uint32_t node); static void addSaveErrRetIndex(GenZir* gz, uint32_t operand); static void addRestoreErrRetIndexBlock( GenZir* gz, uint32_t block_inst, uint32_t operand, uint32_t node); static void restoreErrRetIndex(GenZir* gz, uint32_t block_inst, ResultLoc rl, uint32_t node, uint32_t result); static uint32_t identAsString(AstGenCtx* ag, uint32_t token); static uint32_t lastToken(const Ast* tree, uint32_t node); static uint32_t simpleBinOp( GenZir* gz, Scope* scope, uint32_t node, ZirInstTag tag); // Mirrors GenZir.endsWithNoReturn (AstGen.zig:11770). static bool endsWithNoReturn(GenZir* gz) { uint32_t len = gzInstructionsLen(gz); if (len == 0) return false; uint32_t last = gzInstructionsSlice(gz)[len - 1]; ZirInstTag tag = gz->astgen->inst_tags[last]; switch (tag) { case ZIR_INST_BREAK: case ZIR_INST_BREAK_INLINE: case ZIR_INST_CONDBR: case ZIR_INST_CONDBR_INLINE: case ZIR_INST_COMPILE_ERROR: case ZIR_INST_RET_NODE: case ZIR_INST_RET_LOAD: case ZIR_INST_RET_IMPLICIT: case ZIR_INST_RET_ERR_VALUE: case ZIR_INST_UNREACHABLE: case ZIR_INST_REPEAT: case ZIR_INST_REPEAT_INLINE: case ZIR_INST_PANIC: case ZIR_INST_TRAP: case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW: case ZIR_INST_SWITCH_CONTINUE: return true; default: return false; } } // Mirrors GenZir.refIsNoReturn (AstGen.zig:11885). static bool refIsNoReturn(GenZir* gz, uint32_t inst_ref) { if (inst_ref == ZIR_REF_UNREACHABLE_VALUE) return true; if (inst_ref >= ZIR_REF_START_INDEX) { uint32_t inst_index = inst_ref - ZIR_REF_START_INDEX; ZirInstTag tag = gz->astgen->inst_tags[inst_index]; switch (tag) { case ZIR_INST_BREAK: case ZIR_INST_BREAK_INLINE: case ZIR_INST_CONDBR: case ZIR_INST_CONDBR_INLINE: case ZIR_INST_COMPILE_ERROR: case ZIR_INST_RET_NODE: case ZIR_INST_RET_LOAD: case ZIR_INST_RET_IMPLICIT: case ZIR_INST_RET_ERR_VALUE: case ZIR_INST_UNREACHABLE: case ZIR_INST_REPEAT: case ZIR_INST_REPEAT_INLINE: case ZIR_INST_PANIC: case ZIR_INST_TRAP: case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW: case ZIR_INST_SWITCH_CONTINUE: return true; default: return false; } } return false; } // Mirrors reachableExpr (AstGen.zig:408-416). // Wraps exprRl and emits an error if the result is noreturn. static uint32_t reachableExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, uint32_t reachable_node) { uint32_t result = exprRl(gz, scope, rl, node); if (refIsNoReturn(gz, result)) { (void)reachable_node; SET_ERROR(gz->astgen); } return result; } // Forward declaration needed by reachableExprComptime. static uint32_t comptimeExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, uint32_t reason); // Mirrors reachableExprComptime (AstGen.zig:418-438). // Like reachableExpr but optionally wraps in comptimeExpr when // comptime_reason is non-zero (i.e. force_comptime is set). static uint32_t reachableExprComptime(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, uint32_t reachable_node, uint32_t comptime_reason) { uint32_t result; if (comptime_reason != 0) result = comptimeExpr(gz, scope, rl, node, comptime_reason); else result = exprRl(gz, scope, rl, node); if (refIsNoReturn(gz, result)) { (void)reachable_node; SET_ERROR(gz->astgen); } return result; } static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node); // SimpleComptimeReason (std.zig:727) — values used in block_comptime payload. #define COMPTIME_REASON_C_INCLUDE 9 #define COMPTIME_REASON_C_UNDEF 10 #define COMPTIME_REASON_TYPE 29 #define COMPTIME_REASON_ARRAY_SENTINEL 30 #define COMPTIME_REASON_POINTER_SENTINEL 31 #define COMPTIME_REASON_SLICE_SENTINEL 32 #define COMPTIME_REASON_ARRAY_LENGTH 33 #define COMPTIME_REASON_ALIGN 50 #define COMPTIME_REASON_ADDRSPACE 51 #define COMPTIME_REASON_COMPTIME_KEYWORD 53 #define COMPTIME_REASON_SWITCH_ITEM 56 #define COMPTIME_REASON_TUPLE_FIELD_DEFAULT_VALUE 57 // Mirrors comptimeExpr2 (AstGen.zig:1982). // Evaluates a node in a comptime block_comptime scope. static uint32_t comptimeExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, uint32_t reason) { // Skip wrapping when already in comptime context (AstGen.zig:1990). if (gz->is_comptime) return exprRl(gz, scope, rl, node); // Optimization: certain node types are trivially comptime and don't need // a block_comptime wrapper (AstGen.zig:1997-2046). AstGenCtx* ag = gz->astgen; AstNodeTag tag = ag->tree->nodes.tags[node]; switch (tag) { // Identifier handling (AstGen.zig:2000-2003): // Upstream calls identifier() with force_comptime which resolves // primitives/int types directly and only wraps others in block_comptime. // We mirror this by resolving primitives here and falling through for // non-primitives. case AST_NODE_IDENTIFIER: { uint32_t prim = tryResolvePrimitiveIdent(gz, node); if (prim != ZIR_REF_NONE) return prim; break; // non-primitive: fall through to block_comptime wrapping } case AST_NODE_NUMBER_LITERAL: case AST_NODE_CHAR_LITERAL: case AST_NODE_STRING_LITERAL: case AST_NODE_MULTILINE_STRING_LITERAL: case AST_NODE_ENUM_LITERAL: case AST_NODE_ERROR_VALUE: // Type expressions that force comptime eval of sub-expressions // (AstGen.zig:2017-2042). case AST_NODE_ERROR_UNION: case AST_NODE_MERGE_ERROR_SETS: case AST_NODE_OPTIONAL_TYPE: case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: case AST_NODE_ARRAY_TYPE: case AST_NODE_ARRAY_TYPE_SENTINEL: case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: return exprRl(gz, scope, rl, node); default: break; } // General case: wrap in block_comptime (AstGen.zig:2078-2096). uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK_COMPTIME, gz, node); GenZir block_scope = makeSubBlock(gz, scope); block_scope.is_comptime = true; // Transform RL to type-only (AstGen.zig:2084-2090). ResultLoc ty_only_rl; uint32_t res_ty = rlResultType(gz, rl, node); if (res_ty != 0) ty_only_rl = (ResultLoc) { .tag = RL_COERCED_TY, .data = res_ty, .src_node = 0, .ctx = rl.ctx }; else ty_only_rl = (ResultLoc) { .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = rl.ctx }; uint32_t result = exprRl(&block_scope, scope, ty_only_rl, node); addBreak(&block_scope, ZIR_INST_BREAK_INLINE, block_inst, result, AST_NODE_OFFSET_NONE); setBlockComptimeBody(ag, &block_scope, block_inst, reason); gzAppendInstruction(gz, block_inst); return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node); } // Mirrors typeExpr (AstGen.zig:394). static uint32_t typeExpr(GenZir* gz, Scope* scope, uint32_t node) { ResultLoc rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_TYPE_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; return comptimeExpr(gz, scope, rl, node, COMPTIME_REASON_TYPE); } // Sign parameter for numberLiteral (AstGen.zig:8674). enum NumSign { NUM_SIGN_POSITIVE, NUM_SIGN_NEGATIVE }; // Mirrors numberLiteral (AstGen.zig:8679). // Parses integer and float literals, returns appropriate ZIR ref. // source_node is the node used for rvalue/error reporting (may differ from // node when called from negation). static uint32_t numberLiteral( GenZir* gz, uint32_t node, uint32_t source_node, enum NumSign sign) { AstGenCtx* ag = gz->astgen; uint32_t num_token = ag->tree->nodes.main_tokens[node]; uint32_t tok_start = ag->tree->tokens.starts[num_token]; const char* source = ag->tree->source; // Determine token length by scanning to next non-number character. uint32_t tok_end = tok_start; while (tok_end < ag->tree->source_len && ((source[tok_end] >= '0' && source[tok_end] <= '9') || source[tok_end] == '_' || source[tok_end] == '.' || source[tok_end] == 'x' || source[tok_end] == 'o' || source[tok_end] == 'b' || (source[tok_end] >= 'a' && source[tok_end] <= 'f') || (source[tok_end] >= 'A' && source[tok_end] <= 'F'))) { tok_end++; } // Parse the integer value (simplified: decimal and hex). uint64_t value = 0; bool is_hex = false; uint32_t pos = tok_start; if (tok_end - tok_start >= 2 && source[tok_start] == '0' && source[tok_start + 1] == 'x') { is_hex = true; pos = tok_start + 2; } if (is_hex) { for (; pos < tok_end; pos++) { if (source[pos] == '_') continue; if (source[pos] >= '0' && source[pos] <= '9') value = value * 16 + (uint64_t)(source[pos] - '0'); else if (source[pos] >= 'a' && source[pos] <= 'f') value = value * 16 + 10 + (uint64_t)(source[pos] - 'a'); else if (source[pos] >= 'A' && source[pos] <= 'F') value = value * 16 + 10 + (uint64_t)(source[pos] - 'A'); } } else { for (; pos < tok_end; pos++) { if (source[pos] == '_') continue; if (source[pos] == '.') break; // float — not handled yet if (source[pos] >= '0' && source[pos] <= '9') value = value * 10 + (uint64_t)(source[pos] - '0'); } } // Special cases for 0 and 1 (AstGen.zig:8687-8703). // Note: upstream errors on negative zero for integers; we return zero // regardless of sign to match the same codegen path (AstGen.zig:8687). if (value == 0) return ZIR_REF_ZERO; if (value == 1) { return (sign == NUM_SIGN_POSITIVE) ? ZIR_REF_ONE : ZIR_REF_NEGATIVE_ONE; } // For other integers, emit the positive value and negate if needed // (AstGen.zig:8751-8756). uint32_t result = addInt(gz, value); if (sign == NUM_SIGN_NEGATIVE) { return addUnNode(gz, ZIR_INST_NEGATE, result, source_node); } return result; } // Mirrors builtinCall (AstGen.zig:9191), @import case (AstGen.zig:9242). static uint32_t builtinCallImport( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { (void)scope; AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; // For builtin_call_two: data.lhs = first arg node. AstData node_data = tree->nodes.datas[node]; uint32_t operand_node = node_data.lhs; assert(tree->nodes.tags[operand_node] == AST_NODE_STRING_LITERAL); uint32_t str_lit_token = tree->nodes.main_tokens[operand_node]; uint32_t str_index, str_len; strLitAsString(ag, str_lit_token, &str_index, &str_len); // Compute res_ty from result location (AstGen.zig:9257). uint32_t res_ty_raw = rlResultType(gz, rl, node); uint32_t res_ty = (res_ty_raw != 0) ? res_ty_raw : ZIR_REF_NONE; // Write Import payload to extra (Zir.Inst.Import: res_ty, path). ensureExtraCapacity(ag, 2); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = res_ty; ag->extra[ag->extra_len++] = str_index; // path // Create .import instruction with pl_tok data. ZirInstData data; data.pl_tok.src_tok = tokenIndexToRelative(gz, str_lit_token); data.pl_tok.payload_index = payload_index; uint32_t result_ref = addInstruction(gz, ZIR_INST_IMPORT, data); // Track import (AstGen.zig:9269). addImport(ag, str_index, str_lit_token); return rvalue(gz, rl, result_ref, node); } // Mirrors cImport (AstGen.zig:10011). static uint32_t cImportExpr(GenZir* gz, Scope* scope, uint32_t node) { AstGenCtx* ag = gz->astgen; AstData nd = ag->tree->nodes.datas[node]; uint32_t body_node = nd.lhs; // first arg = body uint32_t block_inst = makeBlockInst(ag, ZIR_INST_C_IMPORT, gz, node); GenZir block_scope = makeSubBlock(gz, scope); block_scope.is_comptime = true; block_scope.c_import = true; // Use fullBodyExpr to inline unlabeled block body (AstGen.zig:10028). uint32_t block_result = fullBodyExpr( &block_scope, &block_scope.base, RL_NONE_VAL, body_node); // ensure_result_used on gz (parent), not block_scope (AstGen.zig:10029). addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, block_result, node); // break_inline (AstGen.zig:10030-10032). makeBreakInline( &block_scope, block_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); setBlockBody(ag, &block_scope, block_inst); // block_scope unstacked now, can add to gz. gzAppendInstruction(gz, block_inst); return block_inst + ZIR_REF_START_INDEX; // toRef() } // Mirrors simpleCBuiltin (AstGen.zig:9938). static uint32_t simpleCBuiltin(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, uint32_t operand_node, uint16_t ext_tag) { AstGenCtx* ag = gz->astgen; // Check c_import scope (AstGen.zig:9947). if (!gz->c_import) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Evaluate operand as comptimeExpr with coerced_ty=slice_const_u8_type // (AstGen.zig:9948-9954). uint32_t comptime_reason = (ext_tag == (uint16_t)ZIR_EXT_C_UNDEF) ? COMPTIME_REASON_C_UNDEF : COMPTIME_REASON_C_INCLUDE; ResultLoc operand_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_SLICE_CONST_U8_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t operand = comptimeExpr(gz, scope, operand_rl, operand_node, comptime_reason); // Emit extended instruction with UnNode payload (AstGen.zig:9955). ensureExtraCapacity(ag, 2); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index); ag->extra[ag->extra_len++] = operand; ZirInstData data; data.extended.opcode = ext_tag; data.extended.small = 0xAAAAu; // undefined (addExtendedPayload passes // undefined for small) data.extended.operand = payload_index; addInstruction(gz, ZIR_INST_EXTENDED, data); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); } // Mirrors builtinCall (AstGen.zig:9191) dispatch. static uint32_t builtinCall( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; uint32_t builtin_token = tree->nodes.main_tokens[node]; uint32_t tok_start = tree->tokens.starts[builtin_token]; const char* source = tree->source; // Identify builtin name from source. // Skip '@' prefix and scan identifier. uint32_t name_start = tok_start + 1; // skip '@' uint32_t name_end = name_start; while (name_end < tree->source_len && ((source[name_end] >= 'a' && source[name_end] <= 'z') || (source[name_end] >= 'A' && source[name_end] <= 'Z') || source[name_end] == '_')) { name_end++; } uint32_t name_len = name_end - name_start; // clang-format off if (name_len == 6 && memcmp(source + name_start, "import", 6) == 0) return builtinCallImport(gz, scope, rl, node); if (name_len == 7 && memcmp(source + name_start, "cImport", 7) == 0) return cImportExpr(gz, scope, node); if (name_len == 8 && memcmp(source + name_start, "cInclude", 8) == 0) { AstData nd = tree->nodes.datas[node]; return simpleCBuiltin(gz, scope, rl, node, nd.lhs, (uint16_t)ZIR_EXT_C_INCLUDE); } // @intCast — typeCast pattern (AstGen.zig:9416, 9807-9826). if (name_len == 7 && memcmp(source + name_start, "intCast", 7) == 0) { advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t result_type = rlResultTypeForCast(gz, rl, node); AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); emitDbgStmt(gz, saved_line, saved_col); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_INT_CAST, node, result_type, operand), node); } // @embedFile — simpleUnOp with coerced_ty (AstGen.zig:9390). if (name_len == 9 && memcmp(source + name_start, "embedFile", 9) == 0) { advanceSourceCursorToMainToken(ag, gz, node); AstData nd = tree->nodes.datas[node]; ResultLoc operand_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_SLICE_CONST_U8_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t operand = exprRl(gz, scope, operand_rl, nd.lhs); uint32_t result = addUnNode(gz, ZIR_INST_EMBED_FILE, operand, node); return rvalue(gz, rl, result, node); } // @intFromEnum — simpleUnOp (AstGen.zig:9388). if (name_len == 11 && memcmp(source + name_start, "intFromEnum", 11) == 0) { advanceSourceCursorToMainToken(ag, gz, node); AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); uint32_t result = addUnNode(gz, ZIR_INST_INT_FROM_ENUM, operand, node); return rvalue(gz, rl, result, node); } // @tagName — simpleUnOp with dbg_stmt (AstGen.zig:9407). if (name_len == 7 && memcmp(source + name_start, "tagName", 7) == 0) { advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); emitDbgStmt(gz, saved_line, saved_col); uint32_t result = addUnNode(gz, ZIR_INST_TAG_NAME, operand, node); return rvalue(gz, rl, result, node); } // @as (AstGen.zig:8909-8920). if (name_len == 2 && memcmp(source + name_start, "as", 2) == 0) { AstData nd = tree->nodes.datas[node]; uint32_t dest_type = typeExpr(gz, scope, nd.lhs); ResultLoc as_rl = { .tag = RL_TY, .data = dest_type, .src_node = 0, .ctx = rl.ctx }; uint32_t operand = exprRl(gz, scope, as_rl, nd.rhs); return rvalue(gz, rl, operand, node); } // @truncate — typeCast pattern (AstGen.zig:9417, 9807-9826). if (name_len == 8 && memcmp(source + name_start, "truncate", 8) == 0) { advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t result_type = rlResultTypeForCast(gz, rl, node); AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); emitDbgStmt(gz, saved_line, saved_col); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_TRUNCATE, node, result_type, operand), node); } // @ptrCast — typeCast pattern (AstGen.zig:9056, 9807-9826). // TODO: Issue 14 — upstream routes through ptrCast() for nested // pointer cast collapsing. Currently uses simple typeCast path. if (name_len == 7 && memcmp(source + name_start, "ptrCast", 7) == 0) { advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t result_type = rlResultTypeForCast(gz, rl, node); AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); emitDbgStmt(gz, saved_line, saved_col); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_PTR_CAST, node, result_type, operand), node); } // @enumFromInt — typeCast pattern (AstGen.zig:9414, 9807-9826). if (name_len == 11 && memcmp(source + name_start, "enumFromInt", 11) == 0) { advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t result_type = rlResultTypeForCast(gz, rl, node); AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); emitDbgStmt(gz, saved_line, saved_col); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_ENUM_FROM_INT, node, result_type, operand), node); } // @bitCast (AstGen.zig:8944-8958, dispatched at 9313). if (name_len == 7 && memcmp(source + name_start, "bitCast", 7) == 0) { uint32_t result_type = rlResultTypeForCast(gz, rl, node); AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_BITCAST, node, result_type, operand), node); } // @memcpy (AstGen.zig:9631-9637). if (name_len == 6 && memcmp(source + name_start, "memcpy", 6) == 0) { AstData nd = tree->nodes.datas[node]; uint32_t dst = expr(gz, scope, nd.lhs); uint32_t src = expr(gz, scope, nd.rhs); addPlNodeBin(gz, ZIR_INST_MEMCPY, node, dst, src); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); } // @memset (AstGen.zig:9638-9647). if (name_len == 6 && memcmp(source + name_start, "memset", 6) == 0) { AstData nd = tree->nodes.datas[node]; uint32_t lhs = expr(gz, scope, nd.lhs); uint32_t lhs_ty = addUnNode(gz, ZIR_INST_TYPEOF, lhs, nd.lhs); uint32_t elem_ty = addUnNode(gz, ZIR_INST_INDEXABLE_PTR_ELEM_TYPE, lhs_ty, nd.lhs); ResultLoc val_rl = { .tag = RL_COERCED_TY, .data = elem_ty, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t val = exprRl(gz, scope, val_rl, nd.rhs); addPlNodeBin(gz, ZIR_INST_MEMSET, node, lhs, val); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); } // @min (AstGen.zig:9149-9172). if (name_len == 3 && memcmp(source + name_start, "min", 3) == 0) { AstData nd = tree->nodes.datas[node]; uint32_t a = expr(gz, scope, nd.lhs); uint32_t b = expr(gz, scope, nd.rhs); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_MIN, node, a, b), node); } // @max (AstGen.zig:9149-9172). if (name_len == 3 && memcmp(source + name_start, "max", 3) == 0) { AstData nd = tree->nodes.datas[node]; uint32_t a = expr(gz, scope, nd.lhs); uint32_t b = expr(gz, scope, nd.rhs); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_MAX, node, a, b), node); } // clang-format on // TODO: handle other builtins. SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // --- identifier (AstGen.zig:8282) --- // Simplified: handles decl_val resolution for container-level declarations. // Tries to resolve an identifier as a primitive type or integer type. // Returns the ZIR ref if it's a primitive/int type, or ZIR_REF_NONE. // Mirrors primitive_instrs + integer type checks in identifier() // (AstGen.zig:8298-8337). static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node) { AstGenCtx* ag = gz->astgen; uint32_t ident_token = ag->tree->nodes.main_tokens[node]; uint32_t tok_start = ag->tree->tokens.starts[ident_token]; const char* source = ag->tree->source; uint32_t tok_end = tok_start; while (tok_end < ag->tree->source_len && ((source[tok_end] >= 'a' && source[tok_end] <= 'z') || (source[tok_end] >= 'A' && source[tok_end] <= 'Z') || (source[tok_end] >= '0' && source[tok_end] <= '9') || source[tok_end] == '_')) tok_end++; uint32_t tok_len = tok_end - tok_start; // Check well-known primitive refs (primitive_instrs map, // AstGen.zig:10236-10281). // clang-format off if (tok_len == 2 && memcmp(source+tok_start, "u1", 2) == 0) return ZIR_REF_U1_TYPE; if (tok_len == 2 && memcmp(source+tok_start, "u8", 2) == 0) return ZIR_REF_U8_TYPE; if (tok_len == 2 && memcmp(source+tok_start, "i8", 2) == 0) return ZIR_REF_I8_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "u16", 3) == 0) return ZIR_REF_U16_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "i16", 3) == 0) return ZIR_REF_I16_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "u29", 3) == 0) return ZIR_REF_U29_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "u32", 3) == 0) return ZIR_REF_U32_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "i32", 3) == 0) return ZIR_REF_I32_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "u64", 3) == 0) return ZIR_REF_U64_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "i64", 3) == 0) return ZIR_REF_I64_TYPE; if (tok_len == 4 && memcmp(source+tok_start, "u128", 4) == 0) return ZIR_REF_U128_TYPE; if (tok_len == 4 && memcmp(source+tok_start, "i128", 4) == 0) return ZIR_REF_I128_TYPE; if (tok_len == 5 && memcmp(source+tok_start, "usize", 5) == 0) return ZIR_REF_USIZE_TYPE; if (tok_len == 5 && memcmp(source+tok_start, "isize", 5) == 0) return ZIR_REF_ISIZE_TYPE; if (tok_len == 6 && memcmp(source+tok_start, "c_char", 6) == 0) return ZIR_REF_C_CHAR_TYPE; if (tok_len == 7 && memcmp(source+tok_start, "c_short", 7) == 0) return ZIR_REF_C_SHORT_TYPE; if (tok_len == 8 && memcmp(source+tok_start, "c_ushort", 8) == 0) return ZIR_REF_C_USHORT_TYPE; if (tok_len == 5 && memcmp(source+tok_start, "c_int", 5) == 0) return ZIR_REF_C_INT_TYPE; if (tok_len == 6 && memcmp(source+tok_start, "c_uint", 6) == 0) return ZIR_REF_C_UINT_TYPE; if (tok_len == 6 && memcmp(source+tok_start, "c_long", 6) == 0) return ZIR_REF_C_LONG_TYPE; if (tok_len == 7 && memcmp(source+tok_start, "c_ulong", 7) == 0) return ZIR_REF_C_ULONG_TYPE; if (tok_len == 10 && memcmp(source+tok_start, "c_longlong", 10) == 0) return ZIR_REF_C_LONGLONG_TYPE; if (tok_len == 11 && memcmp(source+tok_start, "c_ulonglong", 11) == 0) return ZIR_REF_C_ULONGLONG_TYPE; if (tok_len == 14 && memcmp(source+tok_start, "comptime_float", 14) == 0) return ZIR_REF_COMPTIME_FLOAT_TYPE; if (tok_len == 12 && memcmp(source+tok_start, "comptime_int", 12) == 0) return ZIR_REF_COMPTIME_INT_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "f16", 3) == 0) return ZIR_REF_F16_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "f32", 3) == 0) return ZIR_REF_F32_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "f64", 3) == 0) return ZIR_REF_F64_TYPE; if (tok_len == 3 && memcmp(source+tok_start, "f80", 3) == 0) return ZIR_REF_F80_TYPE; if (tok_len == 4 && memcmp(source+tok_start, "f128", 4) == 0) return ZIR_REF_F128_TYPE; if (tok_len == 9 && memcmp(source+tok_start, "anyopaque", 9) == 0) return ZIR_REF_ANYOPAQUE_TYPE; if (tok_len == 4 && memcmp(source+tok_start, "bool", 4) == 0) return ZIR_REF_BOOL_TYPE; if (tok_len == 4 && memcmp(source+tok_start, "void", 4) == 0) return ZIR_REF_VOID_TYPE; if (tok_len == 4 && memcmp(source+tok_start, "type", 4) == 0) return ZIR_REF_TYPE_TYPE; if (tok_len == 8 && memcmp(source+tok_start, "anyerror", 8) == 0) return ZIR_REF_ANYERROR_TYPE; if (tok_len == 8 && memcmp(source+tok_start, "noreturn", 8) == 0) return ZIR_REF_NORETURN_TYPE; if (tok_len == 4 && memcmp(source+tok_start, "true", 4) == 0) return ZIR_REF_BOOL_TRUE; if (tok_len == 5 && memcmp(source+tok_start, "false", 5) == 0) return ZIR_REF_BOOL_FALSE; if (tok_len == 4 && memcmp(source+tok_start, "null", 4) == 0) return ZIR_REF_NULL_VALUE; if (tok_len == 9 && memcmp(source+tok_start, "undefined", 9) == 0) return ZIR_REF_UNDEF; // clang-format on // Integer type detection: u29, i13, etc. (AstGen.zig:8304-8336). if (tok_len >= 2 && (source[tok_start] == 'u' || source[tok_start] == 'i')) { // Zig Signedness enum: unsigned=1, signed=0 uint8_t signedness = (source[tok_start] == 'u') ? 1 : 0; uint16_t bit_count = 0; bool valid = true; for (uint32_t k = tok_start + 1; k < tok_end; k++) { if (source[k] >= '0' && source[k] <= '9') { bit_count = (uint16_t)(bit_count * 10 + (uint16_t)(source[k] - '0')); } else { valid = false; break; } } if (valid && bit_count > 0) { ZirInstData data; data.int_type.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.int_type.signedness = signedness; data.int_type._pad = 0; data.int_type.bit_count = bit_count; return addInstruction(gz, ZIR_INST_INT_TYPE, data); } } return ZIR_REF_NONE; } static uint32_t identifierExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; uint32_t ident_token = ag->tree->nodes.main_tokens[node]; // Check for primitive types FIRST (AstGen.zig:8298-8338). uint32_t prim = tryResolvePrimitiveIdent(gz, node); if (prim != ZIR_REF_NONE) return rvalue(gz, rl, prim, node); // Scope chain walk (AstGen.zig:8340-8461). uint32_t name_str = identAsString(ag, ident_token); for (Scope* s = scope; s != NULL;) { switch (s->tag) { case SCOPE_LOCAL_VAL: { ScopeLocalVal* lv = (ScopeLocalVal*)s; if (lv->name == name_str) return rvalueNoCoercePreRef(gz, rl, lv->inst, node); s = lv->parent; continue; } case SCOPE_LOCAL_PTR: { ScopeLocalPtr* lp = (ScopeLocalPtr*)s; if (lp->name == name_str) { if (RL_IS_REF(rl)) return lp->ptr; return addUnNode(gz, ZIR_INST_LOAD, lp->ptr, node); } s = lp->parent; continue; } case SCOPE_GEN_ZIR: { GenZir* gzs = (GenZir*)s; s = gzs->parent; continue; } case SCOPE_DEFER_NORMAL: case SCOPE_DEFER_ERROR: { ScopeDefer* sd = (ScopeDefer*)s; s = sd->parent; continue; } case SCOPE_LABEL: { ScopeLabel* sl = (ScopeLabel*)s; s = sl->parent; continue; } case SCOPE_NAMESPACE: case SCOPE_TOP: goto decl_table; } } decl_table: // Decl table lookup (AstGen.zig:8462-8520). for (uint32_t i = 0; i < ag->decl_table_len; i++) { if (ag->decl_names[i] == name_str) { ZirInstTag itag = (RL_IS_REF(rl)) ? ZIR_INST_DECL_REF : ZIR_INST_DECL_VAL; ZirInstData data; data.str_tok.start = name_str; data.str_tok.src_tok = tokenIndexToRelative(gz, ident_token); return addInstruction(gz, itag, data); } } SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // --- fieldAccess (AstGen.zig:6154) --- // Simplified: emits field_val instruction with Field payload. static uint32_t fieldAccessExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[node]; // data.lhs = object node, data.rhs = field identifier token. uint32_t object_node = nd.lhs; uint32_t field_ident = nd.rhs; // Get field name as string (AstGen.zig:6180). uint32_t str_index = identAsString(ag, field_ident); // Evaluate the LHS object expression (AstGen.zig:6181). // For .ref rl, LHS is also evaluated with .ref (AstGen.zig:6161). ResultLoc lhs_rl = (RL_IS_REF(rl)) ? RL_REF_VAL : RL_NONE_VAL; uint32_t lhs = exprRl(gz, scope, lhs_rl, object_node); // Emit dbg_stmt for the dot token (AstGen.zig:6183-6184). advanceSourceCursorToMainToken(ag, gz, node); { uint32_t line = ag->source_line - gz->decl_line; uint32_t column = ag->source_column; emitDbgStmt(gz, line, column); } // Emit field_val instruction with Field payload (AstGen.zig:6186-6189). ensureExtraCapacity(ag, 2); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = lhs; // Field.lhs ag->extra[ag->extra_len++] = str_index; // Field.field_name_start // .ref → field_ptr, else → field_val (AstGen.zig:6160-6164). ZirInstTag ftag = (RL_IS_REF(rl)) ? ZIR_INST_FIELD_PTR : ZIR_INST_FIELD_VAL; ZirInstData data; data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; uint32_t access = addInstruction(gz, ftag, data); // For ref, return directly; otherwise apply rvalue (AstGen.zig:6161-6164). if (RL_IS_REF(rl)) return access; return rvalue(gz, rl, access, node); } // --- ptrType (AstGen.zig:3833) --- static uint32_t ptrTypeExpr(GenZir* gz, Scope* scope, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; uint32_t main_tok = tree->nodes.main_tokens[node]; // child_type is always in rhs for all ptr_type variants. uint32_t child_type_node = nd.rhs; // Determine size from main_token (Ast.zig:2122-2131). // Pointer.Size: one=0, many=1, slice=2, c=3. uint8_t size; TokenizerTag main_tok_tag = tree->tokens.tags[main_tok]; if (main_tok_tag == TOKEN_ASTERISK || main_tok_tag == TOKEN_ASTERISK_ASTERISK) { size = 0; // one } else { assert(main_tok_tag == TOKEN_L_BRACKET); TokenizerTag next_tag = tree->tokens.tags[main_tok + 1]; if (next_tag == TOKEN_ASTERISK) { // [*c]T vs [*]T: c-pointer if next-next is identifier. if (tree->tokens.tags[main_tok + 2] == TOKEN_IDENTIFIER) size = 3; // c else size = 1; // many } else { size = 2; // slice } } // Determine sentinel, align, addrspace, bit_range nodes from AST variant // (Ast.zig:1656-1696). uint32_t sentinel_node = UINT32_MAX; uint32_t align_node = UINT32_MAX; uint32_t addrspace_node = UINT32_MAX; uint32_t bit_range_start = UINT32_MAX; uint32_t bit_range_end = UINT32_MAX; if (tag == AST_NODE_PTR_TYPE_ALIGNED) { // opt_node_and_node: lhs = optional align_node (0=none), rhs = child. if (nd.lhs != 0) align_node = nd.lhs; } else if (tag == AST_NODE_PTR_TYPE_SENTINEL) { // opt_node_and_node: lhs = optional sentinel (0=none), rhs = child. if (nd.lhs != 0) sentinel_node = nd.lhs; } else if (tag == AST_NODE_PTR_TYPE) { // extra_and_node: lhs = extra index to AstPtrType, rhs = child_type. const AstPtrType* pt = (const AstPtrType*)(tree->extra_data.arr + nd.lhs); if (pt->sentinel != UINT32_MAX) sentinel_node = pt->sentinel; if (pt->align_node != UINT32_MAX) align_node = pt->align_node; if (pt->addrspace_node != UINT32_MAX) addrspace_node = pt->addrspace_node; } else if (tag == AST_NODE_PTR_TYPE_BIT_RANGE) { // extra_and_node: lhs = extra index to AstPtrTypeBitRange. const AstPtrTypeBitRange* pt = (const AstPtrTypeBitRange*)(tree->extra_data.arr + nd.lhs); if (pt->sentinel != UINT32_MAX) sentinel_node = pt->sentinel; align_node = pt->align_node; if (pt->addrspace_node != UINT32_MAX) addrspace_node = pt->addrspace_node; bit_range_start = pt->bit_range_start; bit_range_end = pt->bit_range_end; } // Scan tokens between main_token and child_type to find const/volatile/ // allowzero (Ast.zig:2139-2164). bool has_const = false; bool has_volatile = false; bool has_allowzero = false; { uint32_t i; if (sentinel_node != UINT32_MAX) { i = lastToken(tree, sentinel_node) + 1; } else if (size == 1 || size == 3) { // many or c: start after main_token. i = main_tok + 1; } else { i = main_tok; } uint32_t end = firstToken(tree, child_type_node); while (i < end) { TokenizerTag tt = tree->tokens.tags[i]; if (tt == TOKEN_KEYWORD_ALLOWZERO) { has_allowzero = true; } else if (tt == TOKEN_KEYWORD_CONST) { has_const = true; } else if (tt == TOKEN_KEYWORD_VOLATILE) { has_volatile = true; } else if (tt == TOKEN_KEYWORD_ALIGN) { // Skip over align expression. if (bit_range_end != UINT32_MAX) i = lastToken(tree, bit_range_end) + 1; else if (align_node != UINT32_MAX) i = lastToken(tree, align_node) + 1; } i++; } } // C pointers always allow address zero (AstGen.zig:3840-3842). if (size == 3 && has_allowzero) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Save source cursor before typeExpr so we can restore it before each // trailing expression (AstGen.zig:3844-3846). uint32_t saved_source_offset = ag->source_offset; uint32_t saved_source_line = ag->source_line; uint32_t saved_source_column = ag->source_column; // Evaluate element type (AstGen.zig:3847). uint32_t elem_type = typeExpr(gz, scope, child_type_node); // Evaluate trailing expressions (AstGen.zig:3856-3897). uint32_t sentinel_ref = ZIR_REF_NONE; uint32_t align_ref = ZIR_REF_NONE; uint32_t addrspace_ref = ZIR_REF_NONE; uint32_t bit_start_ref = ZIR_REF_NONE; uint32_t bit_end_ref = ZIR_REF_NONE; uint32_t trailing_count = 0; if (sentinel_node != UINT32_MAX) { // Restore source cursor (AstGen.zig:3859-3861). ag->source_offset = saved_source_offset; ag->source_line = saved_source_line; ag->source_column = saved_source_column; uint32_t reason = (size == 2) ? COMPTIME_REASON_SLICE_SENTINEL : COMPTIME_REASON_POINTER_SENTINEL; ResultLoc srl = { .tag = RL_TY, .data = elem_type, .src_node = 0, .ctx = RI_CTX_NONE }; sentinel_ref = comptimeExpr(gz, scope, srl, sentinel_node, reason); trailing_count++; } if (addrspace_node != UINT32_MAX) { // Restore source cursor (AstGen.zig:3876-3878). ag->source_offset = saved_source_offset; ag->source_line = saved_source_line; ag->source_column = saved_source_column; // Upstream creates addrspace_ty via addBuiltinValue, we don't have // that yet, so pass RL_NONE (matching previous behavior). addrspace_ref = comptimeExpr( gz, scope, RL_NONE_VAL, addrspace_node, COMPTIME_REASON_ADDRSPACE); trailing_count++; } if (align_node != UINT32_MAX) { // Restore source cursor (AstGen.zig:3885-3887). ag->source_offset = saved_source_offset; ag->source_line = saved_source_line; ag->source_column = saved_source_column; ResultLoc arl = { .tag = RL_COERCED_TY, .data = ZIR_REF_U29_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; align_ref = comptimeExpr(gz, scope, arl, align_node, COMPTIME_REASON_ALIGN); trailing_count++; } if (bit_range_start != UINT32_MAX) { ResultLoc brl = { .tag = RL_COERCED_TY, .data = ZIR_REF_U16_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; bit_start_ref = comptimeExpr( gz, scope, brl, bit_range_start, COMPTIME_REASON_TYPE); bit_end_ref = comptimeExpr( gz, scope, brl, bit_range_end, COMPTIME_REASON_TYPE); trailing_count += 2; } // Build PtrType payload: { elem_type, src_node } + trailing // (AstGen.zig:3905-3921). ensureExtraCapacity(ag, 2 + trailing_count); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = elem_type; ag->extra[ag->extra_len++] = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index); if (sentinel_ref != ZIR_REF_NONE) ag->extra[ag->extra_len++] = sentinel_ref; if (align_ref != ZIR_REF_NONE) ag->extra[ag->extra_len++] = align_ref; if (addrspace_ref != ZIR_REF_NONE) ag->extra[ag->extra_len++] = addrspace_ref; if (bit_start_ref != ZIR_REF_NONE) { ag->extra[ag->extra_len++] = bit_start_ref; ag->extra[ag->extra_len++] = bit_end_ref; } // Build flags packed byte (AstGen.zig:3927-3934). uint8_t flags = 0; if (has_allowzero) flags |= (1 << 0); // is_allowzero if (!has_const) flags |= (1 << 1); // is_mutable if (has_volatile) flags |= (1 << 2); // is_volatile if (sentinel_ref != ZIR_REF_NONE) flags |= (1 << 3); // has_sentinel if (align_ref != ZIR_REF_NONE) flags |= (1 << 4); // has_align if (addrspace_ref != ZIR_REF_NONE) flags |= (1 << 5); // has_addrspace if (bit_start_ref != ZIR_REF_NONE) flags |= (1 << 6); // has_bit_range ZirInstData data; data.ptr_type.flags = flags; data.ptr_type.size = size; data.ptr_type._pad = 0; data.ptr_type.payload_index = payload_index; return addInstruction(gz, ZIR_INST_PTR_TYPE, data); } // --- arrayType (AstGen.zig:940) --- static uint32_t arrayTypeExpr(GenZir* gz, Scope* scope, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[node]; // data.lhs = length expr node, data.rhs = element type node. // Check for `_` identifier → compile error (AstGen.zig:3950-3953). if (tree->nodes.tags[nd.lhs] == AST_NODE_IDENTIFIER && isUnderscoreIdent(tree, nd.lhs)) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } ResultLoc len_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_USIZE_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t len = comptimeExpr(gz, scope, len_rl, nd.lhs, COMPTIME_REASON_TYPE); uint32_t elem_type = typeExpr(gz, scope, nd.rhs); return addPlNodeBin(gz, ZIR_INST_ARRAY_TYPE, node, len, elem_type); } // --- arrayInitExpr (AstGen.zig:1431) --- // Handles typed array init: [_]T{...}, [_:s]T{...}, and [N]T{...}. static uint32_t arrayInitExprTyped(GenZir* gz, Scope* scope, uint32_t node, const uint32_t* elements, uint32_t elem_count, uint32_t ty_inst, uint32_t elem_ty, bool is_ref); static uint32_t arrayInitExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // Get elements and type expression based on the variant. uint32_t type_expr_node = 0; uint32_t elem_buf[2]; const uint32_t* elements = NULL; uint32_t elem_count = 0; switch (tag) { case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: { type_expr_node = nd.lhs; if (nd.rhs != 0) { elem_buf[0] = nd.rhs; elements = elem_buf; elem_count = 1; } break; } case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: { // data = node_and_extra: lhs = type_expr, rhs = extra_index. // extra[rhs] = SubRange.start, extra[rhs+1] = SubRange.end. // Elements are extra_data[start..end]. type_expr_node = nd.lhs; uint32_t extra_idx = nd.rhs; uint32_t range_start = tree->extra_data.arr[extra_idx]; uint32_t range_end = tree->extra_data.arr[extra_idx + 1]; elements = tree->extra_data.arr + range_start; elem_count = range_end - range_start; break; } default: SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } if (type_expr_node == 0 || elem_count == 0) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Determine array_ty and elem_ty (AstGen.zig:1443-1482). uint32_t array_ty = ZIR_REF_NONE; uint32_t elem_ty = ZIR_REF_NONE; // Check if the type is [_]T or [_:s]T (inferred length) // (AstGen.zig:1446-1474, fullArrayType handles both array_type and // array_type_sentinel). AstNodeTag type_tag = tree->nodes.tags[type_expr_node]; if (type_tag == AST_NODE_ARRAY_TYPE || type_tag == AST_NODE_ARRAY_TYPE_SENTINEL) { AstData type_nd = tree->nodes.datas[type_expr_node]; uint32_t elem_count_node = type_nd.lhs; // This intentionally does not support `@"_"` syntax. if (tree->nodes.tags[elem_count_node] == AST_NODE_IDENTIFIER && isUnderscoreIdent(tree, elem_count_node)) { // Inferred length: addInt(elem_count) (AstGen.zig:1452). uint32_t len_inst = addInt(gz, elem_count); if (type_tag == AST_NODE_ARRAY_TYPE) { // [_]T: elem_type_node is rhs (AstGen.zig:1454-1459). uint32_t elem_type_node = type_nd.rhs; elem_ty = typeExpr(gz, scope, elem_type_node); array_ty = addPlNodeBin(gz, ZIR_INST_ARRAY_TYPE, type_expr_node, len_inst, elem_ty); } else { // [_:s]T: sentinel and elem_type from extra data // (AstGen.zig:1460-1473). uint32_t sentinel_node = tree->extra_data.arr[type_nd.rhs]; uint32_t elem_type_node = tree->extra_data.arr[type_nd.rhs + 1]; elem_ty = typeExpr(gz, scope, elem_type_node); ResultLoc sent_rl = { .tag = RL_TY, .data = elem_ty, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t sentinel = comptimeExpr(gz, scope, sent_rl, sentinel_node, COMPTIME_REASON_ARRAY_SENTINEL); array_ty = addPlNodeTriple(gz, ZIR_INST_ARRAY_TYPE_SENTINEL, type_expr_node, len_inst, elem_ty, sentinel); } goto typed_init; } } // Non-inferred length: evaluate type normally (AstGen.zig:1476-1481). array_ty = typeExpr(gz, scope, type_expr_node); // validate_array_init_ty: ArrayInit{ty, init_count} addPlNodeBin( gz, ZIR_INST_VALIDATE_ARRAY_INIT_TY, node, array_ty, elem_count); elem_ty = ZIR_REF_NONE; typed_init: // Typed inits do not use RLS for language simplicity // (AstGen.zig:1484-1513). if (rl.tag == RL_DISCARD) { // discard RL: evaluate elements but don't emit array_init // (AstGen.zig:1487-1506). if (elem_ty != ZIR_REF_NONE) { ResultLoc elem_rl = { .tag = RL_TY, .data = elem_ty, .src_node = 0 }; for (uint32_t i = 0; i < elem_count; i++) { exprRl(gz, scope, elem_rl, elements[i]); } } else { for (uint32_t i = 0; i < elem_count; i++) { uint32_t this_elem_ty = addBin(gz, ZIR_INST_ARRAY_INIT_ELEM_TYPE, array_ty, i); ResultLoc elem_rl = { .tag = RL_TY, .data = this_elem_ty, .src_node = 0 }; exprRl(gz, scope, elem_rl, elements[i]); } } return ZIR_REF_VOID_VALUE; } if (rl.tag == RL_REF) { // ref RL: arrayInitExprTyped with is_ref=true // (AstGen.zig:1507). return arrayInitExprTyped( gz, scope, node, elements, elem_count, array_ty, elem_ty, true); } // All other RLs: arrayInitExprTyped + rvalue (AstGen.zig:1508-1511). uint32_t array_inst = arrayInitExprTyped( gz, scope, node, elements, elem_count, array_ty, elem_ty, false); return rvalue(gz, rl, array_inst, node); } // arrayInitExprTyped (AstGen.zig:1598-1642). // Emits array_init or array_init_ref instruction. static uint32_t arrayInitExprTyped(GenZir* gz, Scope* scope, uint32_t node, const uint32_t* elements, uint32_t elem_count, uint32_t ty_inst, uint32_t elem_ty, bool is_ref) { AstGenCtx* ag = gz->astgen; uint32_t operands_len = elem_count + 1; // +1 for type ensureExtraCapacity(ag, 1 + operands_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = operands_len; ag->extra[ag->extra_len++] = ty_inst; uint32_t extra_start = ag->extra_len; ag->extra_len += elem_count; if (elem_ty != ZIR_REF_NONE) { // Known elem type: use coerced_ty RL (AstGen.zig:1617-1623). ResultLoc elem_rl = { .tag = RL_COERCED_TY, .data = elem_ty, .src_node = 0 }; for (uint32_t i = 0; i < elem_count; i++) { uint32_t elem_ref = exprRl(gz, scope, elem_rl, elements[i]); ag->extra[extra_start + i] = elem_ref; } } else { // Unknown elem type: use array_init_elem_type per element // (AstGen.zig:1625-1637). for (uint32_t i = 0; i < elem_count; i++) { uint32_t this_elem_ty = addBin(gz, ZIR_INST_ARRAY_INIT_ELEM_TYPE, ty_inst, i); ResultLoc elem_rl = { .tag = RL_COERCED_TY, .data = this_elem_ty, .src_node = 0 }; uint32_t elem_ref = exprRl(gz, scope, elem_rl, elements[i]); ag->extra[extra_start + i] = elem_ref; } } ZirInstTag init_tag = is_ref ? ZIR_INST_ARRAY_INIT_REF : ZIR_INST_ARRAY_INIT; return addPlNodePayloadIndex(gz, init_tag, node, payload_index); } // --- simpleBinOp (AstGen.zig:2204) --- static uint32_t simpleBinOp( GenZir* gz, Scope* scope, uint32_t node, ZirInstTag op_tag) { AstGenCtx* ag = gz->astgen; AstData nd = ag->tree->nodes.datas[node]; uint32_t lhs = exprRl(gz, scope, RL_NONE_VAL, nd.lhs); // For arithmetic ops, advance cursor before RHS (AstGen.zig:6245-6256). uint32_t saved_line = 0, saved_col = 0; bool need_dbg = false; if (op_tag == ZIR_INST_ADD || op_tag == ZIR_INST_SUB || op_tag == ZIR_INST_MUL || op_tag == ZIR_INST_DIV || op_tag == ZIR_INST_MOD_REM) { if (!gz->is_comptime) { advanceSourceCursorToMainToken(ag, gz, node); } saved_line = ag->source_line - gz->decl_line; saved_col = ag->source_column; need_dbg = true; } uint32_t rhs = exprRl(gz, scope, RL_NONE_VAL, nd.rhs); if (need_dbg) { emitDbgStmt(gz, saved_line, saved_col); } return addPlNodeBin(gz, op_tag, node, lhs, rhs); } // --- shiftOp (AstGen.zig:9978) --- static uint32_t shiftOp( GenZir* gz, Scope* scope, uint32_t node, ZirInstTag tag) { AstGenCtx* ag = gz->astgen; AstData nd = ag->tree->nodes.datas[node]; uint32_t lhs = exprRl(gz, scope, RL_NONE_VAL, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t log2_int_type = addUnNode(gz, ZIR_INST_TYPEOF_LOG2_INT_TYPE, lhs, nd.lhs); ResultLoc rhs_rl = { .tag = RL_TY, .data = log2_int_type, .src_node = 0, .ctx = RI_CTX_SHIFT_OP }; uint32_t rhs = exprRl(gz, scope, rhs_rl, nd.rhs); emitDbgStmt(gz, saved_line, saved_col); return addPlNodeBin(gz, tag, node, lhs, rhs); } // --- multilineStringLiteral (AstGen.zig:8645) --- // Port of strLitNodeAsString for multiline strings. static uint32_t multilineStringLiteral( GenZir* gz, Scope* scope, uint32_t node) { (void)scope; AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[node]; uint32_t start_tok = nd.lhs; uint32_t end_tok = nd.rhs; uint32_t str_index = ag->string_bytes_len; // First line: no preceding newline. for (uint32_t tok_i = start_tok; tok_i <= end_tok; tok_i++) { uint32_t tok_start = tree->tokens.starts[tok_i]; const char* source = tree->source; // Skip leading `\\` (2 chars). uint32_t content_start = tok_start + 2; // Find end of line. uint32_t content_end = content_start; while (content_end < tree->source_len && source[content_end] != '\n') content_end++; uint32_t line_len = content_end - content_start; if (tok_i > start_tok) { // Prepend newline for lines after the first. ensureStringBytesCapacity(ag, line_len + 1); ag->string_bytes[ag->string_bytes_len++] = '\n'; } else { ensureStringBytesCapacity(ag, line_len); } memcpy(ag->string_bytes + ag->string_bytes_len, source + content_start, line_len); ag->string_bytes_len += line_len; } uint32_t len = ag->string_bytes_len - str_index; ensureStringBytesCapacity(ag, 1); ag->string_bytes[ag->string_bytes_len++] = 0; // null terminator ZirInstData data; data.str.start = str_index; data.str.len = len; return addInstruction(gz, ZIR_INST_STR, data); } // --- ret (AstGen.zig:8119) --- static uint32_t retExpr(GenZir* gz, Scope* scope, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; // AstGen.zig:8123: return outside function is an error. if (ag->fn_block == NULL) { SET_ERROR(ag); return ZIR_REF_UNREACHABLE_VALUE; } // AstGen.zig:8127-8135: cannot return from defer expression. if (gz->any_defer_node != UINT32_MAX) { SET_ERROR(ag); return ZIR_REF_UNREACHABLE_VALUE; } // Ensure debug line/column information is emitted for this return // expression (AstGen.zig:8141-8144). if (!gz->is_comptime) { emitDbgNode(gz, node); } uint32_t ret_lc_line = ag->source_line - gz->decl_line; uint32_t ret_lc_column = ag->source_column; const Scope* defer_outer = &((GenZir*)ag->fn_block)->base; AstData nd = tree->nodes.datas[node]; uint32_t operand_node = nd.lhs; // optional if (operand_node == 0) { // Void return (AstGen.zig:8148-8156). genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY); // Restore error trace unconditionally (AstGen.zig:8153). ZirInstData rdata; rdata.un_node.operand = ZIR_REF_NONE; rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; addInstruction( gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); addUnNode(gz, ZIR_INST_RET_NODE, ZIR_REF_VOID_VALUE, node); return ZIR_REF_UNREACHABLE_VALUE; } // Fast path: return error.Foo (AstGen.zig:8159-8175). if (tree->nodes.tags[operand_node] == AST_NODE_ERROR_VALUE) { uint32_t error_token = tree->nodes.main_tokens[operand_node] + 2; uint32_t err_name_str = identAsString(ag, error_token); DeferCounts dc = countDefers(defer_outer, scope); if (!dc.need_err_code) { genDefers(gz, defer_outer, scope, DEFER_BOTH_SANS_ERR); emitDbgStmt(gz, ret_lc_line, ret_lc_column); addStrTok(gz, ZIR_INST_RET_ERR_VALUE, err_name_str, error_token); return ZIR_REF_UNREACHABLE_VALUE; } // need_err_code path: not implemented yet, fall through to general. } // Evaluate operand with result location (AstGen.zig:8178-8186). // If nodes_need_rl contains this return node, use ptr-based RL; // otherwise use coerced_ty. ResultLoc ret_rl = RL_NONE_VAL; bool use_ptr = nodesNeedRlContains(ag, node); uint32_t ret_ptr_inst = 0; if (use_ptr) { // Create ret_ptr instruction (AstGen.zig:8179). ZirInstData rpdata; rpdata.node = (int32_t)node - (int32_t)gz->decl_node_index; ret_ptr_inst = addInstruction(gz, ZIR_INST_RET_PTR, rpdata); ret_rl.tag = RL_PTR; ret_rl.data = ret_ptr_inst; } else if (ag->fn_ret_ty != 0) { ret_rl.tag = RL_COERCED_TY; ret_rl.data = ag->fn_ret_ty; } ret_rl.ctx = RI_CTX_RETURN; // nameStratExpr with .func name strategy (AstGen.zig:8185). uint32_t operand; if (!nameStratExpr( gz, scope, ret_rl, operand_node, 1 /* func */, &operand)) { operand = reachableExpr(gz, scope, ret_rl, operand_node, node); } // Emit RESTORE_ERR_RET_INDEX based on nodeMayEvalToError // (AstGen.zig:8188-8253). int eval_to_err = nodeMayEvalToError(tree, operand_node); if (eval_to_err == EVAL_TO_ERROR_NEVER) { // Returning non-error: pop error trace unconditionally // (AstGen.zig:8190-8198). genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY); ZirInstData rdata; rdata.un_node.operand = ZIR_REF_NONE; rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; addInstruction( gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); emitDbgStmt(gz, ret_lc_line, ret_lc_column); // addRet (AstGen.zig:13188-13194). if (use_ptr) { addUnNode(gz, ZIR_INST_RET_LOAD, ret_ptr_inst, node); } else { addUnNode(gz, ZIR_INST_RET_NODE, operand, node); } return ZIR_REF_UNREACHABLE_VALUE; } else if (eval_to_err == EVAL_TO_ERROR_ALWAYS) { // .always: emit both error defers and regular defers // (AstGen.zig:8200-8206). uint32_t err_code = use_ptr ? addUnNode(gz, ZIR_INST_LOAD, ret_ptr_inst, node) : operand; (void)err_code; // TODO: genDefers with .both = err_code when errdefer is implemented. genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY); emitDbgStmt(gz, ret_lc_line, ret_lc_column); if (use_ptr) { addUnNode(gz, ZIR_INST_RET_LOAD, ret_ptr_inst, node); } else { addUnNode(gz, ZIR_INST_RET_NODE, operand, node); } return ZIR_REF_UNREACHABLE_VALUE; } else { // .maybe (AstGen.zig:8208-8252). DeferCounts dc = countDefers(defer_outer, scope); if (!dc.have_err) { // Only regular defers; no branch needed (AstGen.zig:8210-8220). genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY); emitDbgStmt(gz, ret_lc_line, ret_lc_column); uint32_t result = use_ptr ? addUnNode(gz, ZIR_INST_LOAD, ret_ptr_inst, node) : operand; ZirInstData rdata; rdata.un_node.operand = result; rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; addInstruction(gz, ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY, rdata); if (use_ptr) { addUnNode(gz, ZIR_INST_RET_LOAD, ret_ptr_inst, node); } else { addUnNode(gz, ZIR_INST_RET_NODE, operand, node); } return ZIR_REF_UNREACHABLE_VALUE; } // have_err path: emit conditional branch (not yet implemented). // Fall through to simplified path. genDefers(gz, defer_outer, scope, DEFER_NORMAL_ONLY); emitDbgStmt(gz, ret_lc_line, ret_lc_column); if (use_ptr) { addUnNode(gz, ZIR_INST_RET_LOAD, ret_ptr_inst, node); } else { addUnNode(gz, ZIR_INST_RET_NODE, operand, node); } return ZIR_REF_UNREACHABLE_VALUE; } } // --- calleeExpr (AstGen.zig:10183) --- // Returns: 0 = direct call, 1 = field call. typedef struct { bool is_field; uint32_t obj_ptr; // for field calls: ref to object uint32_t field_name_start; // for field calls: string index uint32_t direct; // for direct calls: ref to callee } Callee; static Callee calleeExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t fn_expr_node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[fn_expr_node]; if (tag == AST_NODE_FIELD_ACCESS) { AstData nd = tree->nodes.datas[fn_expr_node]; uint32_t object_node = nd.lhs; uint32_t field_ident = nd.rhs; uint32_t str_index = identAsString(ag, field_ident); // Evaluate object with .ref rl (AstGen.zig:10207). uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, object_node); // Advance to main token (the `.` dot) — not first token // (AstGen.zig:10209). advanceSourceCursorToMainToken(ag, gz, fn_expr_node); { uint32_t line = ag->source_line - gz->decl_line; uint32_t column = ag->source_column; emitDbgStmt(gz, line, column); } Callee c; c.is_field = true; c.obj_ptr = lhs; c.field_name_start = str_index; c.direct = 0; return c; } // enum_literal callee: decl literal call syntax (AstGen.zig:10217-10233). if (tag == AST_NODE_ENUM_LITERAL) { uint32_t res_ty = rlResultType(gz, rl, fn_expr_node); if (res_ty != 0) { uint32_t str_index = identAsString(ag, tree->nodes.main_tokens[fn_expr_node]); uint32_t callee = addPlNodeBin(gz, ZIR_INST_DECL_LITERAL_NO_COERCE, fn_expr_node, res_ty, str_index); Callee c; c.is_field = false; c.direct = callee; c.obj_ptr = 0; c.field_name_start = 0; return c; } // No result type: fall through to expr with rl=none. } // Default: direct call (AstGen.zig:10235). Callee c; c.is_field = false; c.direct = expr(gz, scope, fn_expr_node); c.obj_ptr = 0; c.field_name_start = 0; return c; } // --- callExpr (AstGen.zig:10058) --- static uint32_t callExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // Extract callee and args from AST. uint32_t fn_expr_node; uint32_t arg_buf[2]; const uint32_t* args = NULL; uint32_t args_len = 0; uint32_t lparen_tok; switch (tag) { case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: { fn_expr_node = nd.lhs; lparen_tok = tree->nodes.main_tokens[node]; if (nd.rhs != 0) { arg_buf[0] = nd.rhs; args = arg_buf; args_len = 1; } break; } case AST_NODE_CALL: case AST_NODE_CALL_COMMA: { fn_expr_node = nd.lhs; lparen_tok = tree->nodes.main_tokens[node]; uint32_t extra_idx = nd.rhs; uint32_t range_start = tree->extra_data.arr[extra_idx]; uint32_t range_end = tree->extra_data.arr[extra_idx + 1]; args = tree->extra_data.arr + range_start; args_len = range_end - range_start; break; } default: SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } Callee callee = calleeExpr(gz, scope, rl, fn_expr_node); // dbg_stmt before call (AstGen.zig:10078-10083). { advanceSourceCursor(ag, tree->tokens.starts[lparen_tok]); uint32_t line = ag->source_line - gz->decl_line; uint32_t column = ag->source_column; emitDbgStmtForceCurrentIndex(gz, line, column); } // Reserve instruction slot for call (AstGen.zig:10093). uint32_t call_index = ag->inst_len; ensureInstCapacity(ag, 1); memset(&ag->inst_datas[call_index], 0, sizeof(ZirInstData)); ag->inst_tags[call_index] = (ZirInstTag)0; ag->inst_len++; gzAppendInstruction(gz, call_index); // Process arguments in sub-blocks (AstGen.zig:10096-10116). // Upstream uses a separate scratch array; we use a local buffer for body // lengths and append body instructions to scratch_extra, then copy all // to extra after the call payload. uint32_t call_inst = call_index + ZIR_REF_START_INDEX; ResultLoc arg_rl = { .tag = RL_COERCED_TY, .data = call_inst, .src_node = 0, .ctx = RI_CTX_FN_ARG }; // Use scratch_extra to collect body lengths + body instructions, // mirroring upstream's scratch array (AstGen.zig:10096-10116). uint32_t scratch_top = ag->scratch_extra_len; // Reserve space for cumulative body lengths (one per arg). ensureScratchExtraCapacity(ag, args_len); ag->scratch_extra_len += args_len; for (uint32_t i = 0; i < args_len; i++) { GenZir arg_block = makeSubBlock(gz, scope); uint32_t arg_ref = exprRl(&arg_block, &arg_block.base, arg_rl, args[i]); // break_inline with param_node src (AstGen.zig:10108). int32_t param_src = (int32_t)args[i] - (int32_t)arg_block.decl_node_index; makeBreakInline(&arg_block, call_index, arg_ref, param_src); // Append arg_block body to scratch_extra (with ref_table fixups). uint32_t raw_body_len = gzInstructionsLen(&arg_block); const uint32_t* body = gzInstructionsSlice(&arg_block); uint32_t fixup_len = countBodyLenAfterFixups(ag, body, raw_body_len); ensureScratchExtraCapacity(ag, fixup_len); for (uint32_t j = 0; j < raw_body_len; j++) { appendPossiblyRefdBodyInstScratch(ag, body[j]); } // Record cumulative body length (AstGen.zig:10114). ag->scratch_extra[scratch_top + i] = ag->scratch_extra_len - scratch_top; gzUnstack(&arg_block); } // Build call payload (AstGen.zig:10118-10168). // Upstream layout: [flags, callee/obj_ptr, field_name_start], then // body_lengths + body_instructions from scratch. // Flags layout (packed): modifier:u3, ensure_result_used:bool, // pop_error_return_trace:bool, args_len:u27. // pop_error_return_trace = !propagate_error_trace // (AstGen.zig:10121-10124). bool propagate_error_trace = (rl.ctx == RI_CTX_ERROR_HANDLING_EXPR || rl.ctx == RI_CTX_RETURN || rl.ctx == RI_CTX_FN_ARG || rl.ctx == RI_CTX_CONST_INIT); uint32_t flags = (propagate_error_trace ? 0u : (1u << 4)) | ((args_len & 0x7FFFFFFu) << 5); // args_len if (callee.is_field) { // FieldCall: {flags, obj_ptr, field_name_start} (AstGen.zig:10148). ensureExtraCapacity(ag, 3 + (ag->scratch_extra_len - scratch_top)); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = flags; ag->extra[ag->extra_len++] = callee.obj_ptr; ag->extra[ag->extra_len++] = callee.field_name_start; // Append scratch data (body lengths + body instructions). if (args_len != 0) { memcpy(ag->extra + ag->extra_len, ag->scratch_extra + scratch_top, (ag->scratch_extra_len - scratch_top) * sizeof(uint32_t)); ag->extra_len += ag->scratch_extra_len - scratch_top; } ag->inst_tags[call_index] = ZIR_INST_FIELD_CALL; ag->inst_datas[call_index].pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; ag->inst_datas[call_index].pl_node.payload_index = payload_index; } else { // Call: {flags, callee} (AstGen.zig:10128). ensureExtraCapacity(ag, 2 + (ag->scratch_extra_len - scratch_top)); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = flags; ag->extra[ag->extra_len++] = callee.direct; // Append scratch data (body lengths + body instructions). if (args_len != 0) { memcpy(ag->extra + ag->extra_len, ag->scratch_extra + scratch_top, (ag->scratch_extra_len - scratch_top) * sizeof(uint32_t)); ag->extra_len += ag->scratch_extra_len - scratch_top; } ag->inst_tags[call_index] = ZIR_INST_CALL; ag->inst_datas[call_index].pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; ag->inst_datas[call_index].pl_node.payload_index = payload_index; } // Restore scratch (AstGen.zig:10097 defer). ag->scratch_extra_len = scratch_top; return call_index + ZIR_REF_START_INDEX; } // structInitExprAnon (AstGen.zig:1865-1893). // Anonymous struct init using struct_init_anon instruction. static uint32_t structInitExprAnon(GenZir* gz, Scope* scope, uint32_t node, const uint32_t* fields, uint32_t fields_len) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; ensureExtraCapacity(ag, 3 + fields_len * 2); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = node; // abs_node ag->extra[ag->extra_len++] = ag->source_line; // abs_line ag->extra[ag->extra_len++] = fields_len; uint32_t items_start = ag->extra_len; ag->extra_len += fields_len * 2; for (uint32_t i = 0; i < fields_len; i++) { uint32_t field_init = fields[i]; uint32_t name_token = firstToken(tree, field_init) - 2; uint32_t str_index = identAsString(ag, name_token); uint32_t init_ref = expr(gz, scope, field_init); ag->extra[items_start + i * 2] = str_index; ag->extra[items_start + i * 2 + 1] = init_ref; } return addPlNodePayloadIndex( gz, ZIR_INST_STRUCT_INIT_ANON, node, payload_index); } // structInitExprTyped (AstGen.zig:1896-1931). // Typed struct init using struct_init or struct_init_ref instruction. static uint32_t structInitExprTyped(GenZir* gz, Scope* scope, uint32_t node, const uint32_t* fields, uint32_t fields_len, uint32_t ty_inst, bool is_ref) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; ensureExtraCapacity(ag, 3 + fields_len * 2); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = node; // abs_node ag->extra[ag->extra_len++] = ag->source_line; // abs_line ag->extra[ag->extra_len++] = fields_len; uint32_t items_start = ag->extra_len; ag->extra_len += fields_len * 2; for (uint32_t i = 0; i < fields_len; i++) { uint32_t field_init = fields[i]; uint32_t name_token = firstToken(tree, field_init) - 2; uint32_t str_index = identAsString(ag, name_token); uint32_t field_ty_inst = addPlNodeBin(gz, ZIR_INST_STRUCT_INIT_FIELD_TYPE, field_init, ty_inst, str_index); ResultLoc elem_rl = { .tag = RL_COERCED_TY, .data = field_ty_inst, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t init_ref = exprRl(gz, scope, elem_rl, field_init); ag->extra[items_start + i * 2] = field_ty_inst - ZIR_REF_START_INDEX; // .toIndex() ag->extra[items_start + i * 2 + 1] = init_ref; } ZirInstTag init_tag = is_ref ? ZIR_INST_STRUCT_INIT_REF : ZIR_INST_STRUCT_INIT; return addPlNodePayloadIndex(gz, init_tag, node, payload_index); } // --- structInitExpr (AstGen.zig:1674) --- static uint32_t structInitExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // Extract type_expr and fields. uint32_t type_expr_node = 0; // 0 = anonymous (.{...}) uint32_t field_buf[2]; const uint32_t* fields = NULL; uint32_t fields_len = 0; switch (tag) { case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: { // .{.a = lhs, .b = rhs} uint32_t idx = 0; if (nd.lhs != 0) field_buf[idx++] = nd.lhs; if (nd.rhs != 0) field_buf[idx++] = nd.rhs; fields = field_buf; fields_len = idx; break; } case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: { uint32_t start = nd.lhs; uint32_t end = nd.rhs; fields = tree->extra_data.arr + start; fields_len = end - start; break; } case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: { type_expr_node = nd.lhs; if (nd.rhs != 0) { field_buf[0] = nd.rhs; fields = field_buf; fields_len = 1; } break; } case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: { type_expr_node = nd.lhs; uint32_t extra_idx = nd.rhs; uint32_t range_start = tree->extra_data.arr[extra_idx]; uint32_t range_end = tree->extra_data.arr[extra_idx + 1]; fields = tree->extra_data.arr + range_start; fields_len = range_end - range_start; break; } default: SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } if (type_expr_node == 0 && fields_len == 0) { // .{} — depends on result location (AstGen.zig:1687-1698). if (rl.tag == RL_REF_COERCED_TY) { return addUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY_REF_RESULT, rl.data, node); } if (rl.tag == RL_TY || rl.tag == RL_COERCED_TY) { return addUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY_RESULT, rl.data, node); } if (rl.tag == RL_DISCARD) { return ZIR_REF_VOID_VALUE; } if (rl.tag == RL_PTR) { // AstGen.zig:1691-1696. uint32_t ty_inst = rlResultType(gz, rl, node); uint32_t val = addUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY_RESULT, ty_inst, node); return rvalue(gz, rl, val, node); } // RL_NONE, RL_REF, RL_INFERRED_PTR (AstGen.zig:1697-1699). return rvalue(gz, rl, ZIR_REF_EMPTY_TUPLE, node); } // Pre-register all field names to match upstream string ordering. // Upstream has a duplicate name check (AstGen.zig:1756-1806) that // adds all field names to string_bytes before evaluating values. for (uint32_t i = 0; i < fields_len; i++) { uint32_t name_token = firstToken(tree, fields[i]) - 2; identAsString(ag, name_token); } if (type_expr_node == 0 && fields_len > 0) { // Anonymous struct init with fields (AstGen.zig:1821-1861). switch (rl.tag) { case RL_NONE: // structInitExprAnon (AstGen.zig:1822, 1865-1893). return structInitExprAnon(gz, scope, node, fields, fields_len); case RL_DISCARD: { // Even if discarding we must perform side-effects // (AstGen.zig:1823-1828). for (uint32_t i = 0; i < fields_len; i++) exprRl(gz, scope, RL_DISCARD_VAL, fields[i]); return ZIR_REF_VOID_VALUE; } case RL_REF: { // structInitExprAnon + ref (AstGen.zig:1830-1833). uint32_t result = structInitExprAnon(gz, scope, node, fields, fields_len); return addUnTok(gz, ZIR_INST_REF, result, firstToken(tree, node)); } case RL_REF_COERCED_TY: { // Get elem type, validate, structInitExprTyped(is_ref=true) // (AstGen.zig:1834-1837). uint32_t result_ty_inst = addUnNode(gz, ZIR_INST_ELEM_TYPE, rl.data, node); addUnNode(gz, ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY, result_ty_inst, node); return structInitExprTyped( gz, scope, node, fields, fields_len, result_ty_inst, true); } case RL_TY: case RL_COERCED_TY: { // validate_struct_init_result_ty + // structInitExprTyped(is_ref=false) (AstGen.zig:1839-1841). uint32_t ty_inst = rl.data; addUnNode( gz, ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY, ty_inst, node); return structInitExprTyped( gz, scope, node, fields, fields_len, ty_inst, false); } case RL_PTR: { // structInitExprPtr (AstGen.zig:1843-1846, 1934-1964). uint32_t struct_ptr_inst = addUnNode(gz, ZIR_INST_OPT_EU_BASE_PTR_INIT, rl.data, node); // Block payload: body_len = fields_len. ensureExtraCapacity(ag, 1 + fields_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = fields_len; uint32_t items_start = ag->extra_len; ag->extra_len += fields_len; for (uint32_t i = 0; i < fields_len; i++) { uint32_t field_init = fields[i]; uint32_t name_token = firstToken(tree, field_init) - 2; uint32_t str_index = identAsString(ag, name_token); // struct_init_field_ptr (AstGen.zig:1954-1957). uint32_t field_ptr = addPlNodeBin(gz, ZIR_INST_STRUCT_INIT_FIELD_PTR, field_init, struct_ptr_inst, str_index); ag->extra[items_start + i] = field_ptr - ZIR_REF_START_INDEX; // .toIndex() // Evaluate init with ptr RL (AstGen.zig:1960). ResultLoc ptr_rl = { .tag = RL_PTR, .data = field_ptr, .src_node = 0, .ctx = RI_CTX_NONE }; exprRl(gz, scope, ptr_rl, field_init); } addPlNodePayloadIndex( gz, ZIR_INST_VALIDATE_PTR_STRUCT_INIT, node, payload_index); return ZIR_REF_VOID_VALUE; } case RL_INFERRED_PTR: { // Standard anon init + rvalue store (AstGen.zig:1847-1852). uint32_t struct_inst = structInitExprAnon(gz, scope, node, fields, fields_len); return rvalue(gz, rl, struct_inst, node); } } SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Typed init: evaluate type, emit struct_init_empty or struct_init. if (type_expr_node != 0 && fields_len == 0) { // Check for [_]T{} pattern (AstGen.zig:1707-1753). AstNodeTag type_tag = tree->nodes.tags[type_expr_node]; if (type_tag == AST_NODE_ARRAY_TYPE || type_tag == AST_NODE_ARRAY_TYPE_SENTINEL) { AstData type_nd = tree->nodes.datas[type_expr_node]; uint32_t elem_count_node = type_nd.lhs; if (tree->nodes.tags[elem_count_node] == AST_NODE_IDENTIFIER && isUnderscoreIdent(tree, elem_count_node)) { // Inferred length with 0 fields → length 0. if (type_tag == AST_NODE_ARRAY_TYPE) { uint32_t elem_type = typeExpr(gz, scope, type_nd.rhs); uint32_t array_type_inst = addPlNodeBin(gz, ZIR_INST_ARRAY_TYPE, type_expr_node, ZIR_REF_ZERO_USIZE, elem_type); return rvalue(gz, rl, addUnNode(gz, ZIR_INST_STRUCT_INIT_EMPTY, array_type_inst, node), node); } // ARRAY_TYPE_SENTINEL: extra[rhs] = sentinel, extra[rhs+1] // = elem_type uint32_t sentinel_node = tree->extra_data.arr[type_nd.rhs]; uint32_t elem_type_node = tree->extra_data.arr[type_nd.rhs + 1]; uint32_t elem_type = typeExpr(gz, scope, elem_type_node); ResultLoc sent_rl = { .tag = RL_TY, .data = elem_type, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t sentinel = comptimeExpr(gz, scope, sent_rl, sentinel_node, COMPTIME_REASON_ARRAY_SENTINEL); uint32_t array_type_inst = addPlNodeTriple(gz, ZIR_INST_ARRAY_TYPE_SENTINEL, type_expr_node, ZIR_REF_ZERO_USIZE, elem_type, sentinel); return rvalue(gz, rl, addUnNode( gz, ZIR_INST_STRUCT_INIT_EMPTY, array_type_inst, node), node); } } uint32_t ty_inst = typeExpr(gz, scope, type_expr_node); return rvalue(gz, rl, addUnNode(gz, ZIR_INST_STRUCT_INIT_EMPTY, ty_inst, node), node); } // Typed struct init with fields (AstGen.zig:1808-1818). if (type_expr_node != 0 && fields_len > 0) { uint32_t ty_inst = typeExpr(gz, scope, type_expr_node); addUnNode(gz, ZIR_INST_VALIDATE_STRUCT_INIT_TY, ty_inst, node); // Upstream: .ref => structInitExprTyped(is_ref=true) // else => rvalue(structInitExprTyped(is_ref=false)) if (rl.tag == RL_REF) { return structInitExprTyped( gz, scope, node, fields, fields_len, ty_inst, true); } uint32_t struct_inst = structInitExprTyped( gz, scope, node, fields, fields_len, ty_inst, false); return rvalue(gz, rl, struct_inst, node); } SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // --- tryExpr (AstGen.zig:5957) --- static uint32_t tryExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; AstData nd = ag->tree->nodes.datas[node]; uint32_t operand_node = nd.lhs; if (!gz->is_comptime) { emitDbgNode(gz, node); } uint32_t try_lc_line = ag->source_line - gz->decl_line; uint32_t try_lc_column = ag->source_column; // Determine operand rl and block tag based on result location // (AstGen.zig:5989-5992). ResultLoc operand_rl; ZirInstTag block_tag; ZirInstTag err_tag; if (RL_IS_REF(rl)) { operand_rl = RL_REF_VAL; block_tag = ZIR_INST_TRY_PTR; err_tag = ZIR_INST_ERR_UNION_CODE_PTR; } else { operand_rl = RL_NONE_VAL; block_tag = ZIR_INST_TRY; err_tag = ZIR_INST_ERR_UNION_CODE; } operand_rl.ctx = RI_CTX_ERROR_HANDLING_EXPR; // Evaluate operand (AstGen.zig:5993-6006). uint32_t operand = exprRl(gz, scope, operand_rl, operand_node); // Create try block instruction (AstGen.zig:6008). uint32_t try_inst = makeBlockInst(ag, block_tag, gz, node); gzAppendInstruction(gz, try_inst); // Else scope: extract error code, return it (AstGen.zig:6011-6025). GenZir else_scope = makeSubBlock(gz, scope); uint32_t err_code = addUnNode(&else_scope, err_tag, operand, node); // Emit defers for error path (AstGen.zig:6019). if (ag->fn_block != NULL) { const Scope* fn_block_scope = &((GenZir*)ag->fn_block)->base; genDefers(&else_scope, fn_block_scope, scope, DEFER_BOTH_SANS_ERR); } // Emit dbg_stmt at try keyword for error return tracing (AstGen.zig:6020). emitDbgStmt(&else_scope, try_lc_line, try_lc_column); // ret_node with error code (AstGen.zig:6021). addUnNode(&else_scope, ZIR_INST_RET_NODE, err_code, node); setTryBody(ag, &else_scope, try_inst, operand); // else_scope unstacked by setTryBody. // For ref/ref_coerced_ty, return directly; otherwise rvalue // (AstGen.zig:6025-6028). uint32_t result = try_inst + ZIR_REF_START_INDEX; // toRef() if (RL_IS_REF(rl)) return result; return rvalue(gz, rl, result, node); } // --- boolBinOp (AstGen.zig:6274) --- // Short-circuiting boolean and/or. static uint32_t boolBinOp( GenZir* gz, Scope* scope, uint32_t node, ZirInstTag zir_tag) { AstGenCtx* ag = gz->astgen; AstData nd = ag->tree->nodes.datas[node]; uint32_t lhs_node = nd.lhs; uint32_t rhs_node = nd.rhs; // Evaluate LHS (AstGen.zig:6285). uint32_t lhs = expr(gz, scope, lhs_node); // Reserve the bool_br instruction (payload set later) // (AstGen.zig:6286). uint32_t bool_br = reserveInstructionIndex(ag); gzAppendInstruction(gz, bool_br); // Evaluate RHS in sub-block (AstGen.zig:6288-6293). GenZir rhs_scope = makeSubBlock(gz, scope); uint32_t rhs = expr(&rhs_scope, &rhs_scope.base, rhs_node); if (!ag->has_compile_errors) { // break_inline from rhs to bool_br (AstGen.zig:6292). makeBreakInline(&rhs_scope, bool_br, rhs, (int32_t)rhs_node - (int32_t)rhs_scope.decl_node_index); } // setBoolBrBody (AstGen.zig:6294, 11929-11944). uint32_t raw_body_len = gzInstructionsLen(&rhs_scope); const uint32_t* body = gzInstructionsSlice(&rhs_scope); uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len); ensureExtraCapacity(ag, 2 + body_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = lhs; // BoolBr.lhs ag->extra[ag->extra_len++] = body_len; // BoolBr.body_len for (uint32_t i = 0; i < raw_body_len; i++) appendPossiblyRefdBodyInst(ag, body[i]); gzUnstack(&rhs_scope); // Fill in the bool_br instruction. ag->inst_tags[bool_br] = zir_tag; ag->inst_datas[bool_br].pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; ag->inst_datas[bool_br].pl_node.payload_index = payload_index; return bool_br + ZIR_REF_START_INDEX; } // Mirrors expr (AstGen.zig:634) — main expression dispatcher. static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; if (node == 0) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } AstNodeTag tag = ag->tree->nodes.tags[node]; AstData nd = ag->tree->nodes.datas[node]; switch (tag) { case AST_NODE_NUMBER_LITERAL: return rvalue( gz, rl, numberLiteral(gz, node, node, NUM_SIGN_POSITIVE), node); case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: return builtinCall(gz, scope, rl, node); case AST_NODE_FIELD_ACCESS: return fieldAccessExpr(gz, scope, rl, node); case AST_NODE_IDENTIFIER: return identifierExpr(gz, scope, rl, node); case AST_NODE_STRING_LITERAL: { // Mirrors stringLiteral (AstGen.zig:8626). uint32_t str_lit_token = ag->tree->nodes.main_tokens[node]; uint32_t str_index, str_len; strLitAsString(ag, str_lit_token, &str_index, &str_len); ZirInstData data; data.str.start = str_index; data.str.len = str_len; uint32_t str_result = addInstruction(gz, ZIR_INST_STR, data); return rvalue(gz, rl, str_result, node); } // address_of (AstGen.zig:953-960): evaluate operand with .ref rl. case AST_NODE_ADDRESS_OF: { uint32_t operand_node = ag->tree->nodes.datas[node].lhs; // Check for result type to emit validate_ref_ty (AstGen.zig:954-956). uint32_t res_ty = rlResultType(gz, rl, node); ResultLoc operand_rl; if (res_ty != 0) { addUnTok(gz, ZIR_INST_VALIDATE_REF_TY, res_ty, firstToken(ag->tree, node)); // Pass ref_coerced_ty so init expressions can use the type // (AstGen.zig:958). operand_rl = (ResultLoc) { .tag = RL_REF_COERCED_TY, .data = res_ty, .src_node = 0 }; } else { operand_rl = RL_REF_VAL; } uint32_t result = exprRl(gz, scope, operand_rl, operand_node); return rvalue(gz, rl, result, node); } // ptr_type (AstGen.zig:1077-1081). case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: return rvalue(gz, rl, ptrTypeExpr(gz, scope, node), node); // array_type (AstGen.zig:940). case AST_NODE_ARRAY_TYPE: return rvalue(gz, rl, arrayTypeExpr(gz, scope, node), node); // array_init variants (AstGen.zig:836-856). case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: return arrayInitExpr(gz, scope, rl, node); // array_cat (AstGen.zig:772): ++ binary operator. case AST_NODE_ARRAY_CAT: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_ARRAY_CAT), node); // grouped_expression (AstGen.zig:1100): passthrough. case AST_NODE_GROUPED_EXPRESSION: return exprRl(gz, scope, rl, ag->tree->nodes.datas[node].lhs); // unreachable_literal (AstGen.zig:846-854). case AST_NODE_UNREACHABLE_LITERAL: { emitDbgNode(gz, node); ZirInstData udata; memset(&udata, 0, sizeof(udata)); udata.unreachable_data.src_node = (int32_t)node - (int32_t)gz->decl_node_index; addInstruction(gz, ZIR_INST_UNREACHABLE, udata); return ZIR_REF_UNREACHABLE_VALUE; } // enum_literal (AstGen.zig:993). case AST_NODE_ENUM_LITERAL: { uint32_t ident_token = ag->tree->nodes.main_tokens[node]; uint32_t str_index = identAsString(ag, ident_token); // If result type available, emit decl_literal (AstGen.zig:993-1003). uint32_t res_ty = rlResultType(gz, rl, node); if (res_ty != 0) { uint32_t res = addPlNodeBin( gz, ZIR_INST_DECL_LITERAL, node, res_ty, str_index); // decl_literal does the coercion for us (AstGen.zig:1001). // Only need rvalue for ptr/inferred_ptr/ref_coerced_ty. if (rl.tag == RL_TY || rl.tag == RL_COERCED_TY) return res; return rvalue(gz, rl, res, node); } return rvalue(gz, rl, addStrTok(gz, ZIR_INST_ENUM_LITERAL, str_index, ident_token), node); } // multiline_string_literal (AstGen.zig:8645). case AST_NODE_MULTILINE_STRING_LITERAL: return rvalue(gz, rl, multilineStringLiteral(gz, scope, node), node); // return (AstGen.zig:856). case AST_NODE_RETURN: return retExpr(gz, scope, node); // call (AstGen.zig:783-790). case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: return rvalue(gz, rl, callExpr(gz, scope, rl, node), node); // struct_init (AstGen.zig:836-839). case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: return structInitExpr(gz, scope, rl, node); // container_decl (AstGen.zig:1083-1098). case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: return rvalue( gz, rl, containerDecl(gz, scope, node, 2 /* anon */), node); // try (AstGen.zig:1115). case AST_NODE_TRY: return tryExpr(gz, scope, rl, node); // Comparison operators (AstGen.zig:714-726). case AST_NODE_EQUAL_EQUAL: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_EQ), node); case AST_NODE_BANG_EQUAL: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_NEQ), node); case AST_NODE_LESS_THAN: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_LT), node); case AST_NODE_GREATER_THAN: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_GT), node); case AST_NODE_LESS_OR_EQUAL: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_LTE), node); case AST_NODE_GREATER_OR_EQUAL: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_CMP_GTE), node); // Arithmetic (AstGen.zig:656-698). case AST_NODE_ADD: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_ADD), node); case AST_NODE_SUB: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_SUB), node); case AST_NODE_MUL: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_MUL), node); case AST_NODE_DIV: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_DIV), node); case AST_NODE_MOD: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_MOD), node); // Bitwise (AstGen.zig:700-712). case AST_NODE_BIT_AND: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_BIT_AND), node); case AST_NODE_BIT_OR: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_BIT_OR), node); case AST_NODE_BIT_XOR: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_XOR), node); case AST_NODE_SHL: return rvalue(gz, rl, shiftOp(gz, scope, node, ZIR_INST_SHL), node); case AST_NODE_SHR: return rvalue(gz, rl, shiftOp(gz, scope, node, ZIR_INST_SHR), node); // Boolean operators (AstGen.zig:728-731) — special: boolBinOp. case AST_NODE_BOOL_AND: return rvalue( gz, rl, boolBinOp(gz, scope, node, ZIR_INST_BOOL_BR_AND), node); case AST_NODE_BOOL_OR: return rvalue( gz, rl, boolBinOp(gz, scope, node, ZIR_INST_BOOL_BR_OR), node); // Unary operators (AstGen.zig:919-938). case AST_NODE_BOOL_NOT: return rvalue(gz, rl, addUnNode(gz, ZIR_INST_BOOL_NOT, expr(gz, scope, nd.lhs), node), node); case AST_NODE_BIT_NOT: return rvalue(gz, rl, addUnNode(gz, ZIR_INST_BIT_NOT, expr(gz, scope, nd.lhs), node), node); // negation (AstGen.zig:9863-9882). case AST_NODE_NEGATION: { // Check for number_literal as sub-expression to preserve negativity // (AstGen.zig:9875-9877). if (ag->tree->nodes.tags[nd.lhs] == AST_NODE_NUMBER_LITERAL) { return rvalue(gz, rl, numberLiteral(gz, nd.lhs, node, NUM_SIGN_NEGATIVE), node); } return rvalue(gz, rl, addUnNode(gz, ZIR_INST_NEGATE, expr(gz, scope, nd.lhs), node), node); } case AST_NODE_NEGATION_WRAP: return rvalue(gz, rl, addUnNode(gz, ZIR_INST_NEGATE_WRAP, expr(gz, scope, nd.lhs), node), node); // deref (AstGen.zig:942-951). case AST_NODE_DEREF: { uint32_t lhs = expr(gz, scope, nd.lhs); addUnNode(gz, ZIR_INST_VALIDATE_DEREF, lhs, node); if (RL_IS_REF(rl)) return lhs; return rvalue(gz, rl, addUnNode(gz, ZIR_INST_LOAD, lhs, node), node); } // optional_type (AstGen.zig:961-964). case AST_NODE_OPTIONAL_TYPE: return rvalue(gz, rl, addUnNode( gz, ZIR_INST_OPTIONAL_TYPE, typeExpr(gz, scope, nd.lhs), node), node); // unwrap_optional (AstGen.zig:966-983). case AST_NODE_UNWRAP_OPTIONAL: { if (RL_IS_REF(rl)) { uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; emitDbgStmt(gz, saved_line, saved_col); return addUnNode( gz, ZIR_INST_OPTIONAL_PAYLOAD_SAFE_PTR, lhs, node); } else { uint32_t lhs = expr(gz, scope, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; emitDbgStmt(gz, saved_line, saved_col); return rvalue(gz, rl, addUnNode(gz, ZIR_INST_OPTIONAL_PAYLOAD_SAFE, lhs, node), node); } } // error_union type (AstGen.zig:788-797). case AST_NODE_ERROR_UNION: { uint32_t lhs = typeExpr(gz, scope, nd.lhs); uint32_t rhs = typeExpr(gz, scope, nd.rhs); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_ERROR_UNION_TYPE, node, lhs, rhs), node); } // char_literal (AstGen.zig:8662-8675). case AST_NODE_CHAR_LITERAL: { uint32_t main_tok = ag->tree->nodes.main_tokens[node]; uint32_t tok_start = ag->tree->tokens.starts[main_tok]; const char* src = ag->tree->source; uint32_t ci = tok_start + 1; // skip opening quote uint64_t char_val; if (src[ci] == '\\') { // Escape sequence (AstGen.zig:8668-8675). ci++; switch (src[ci]) { case 'n': char_val = '\n'; break; case 'r': char_val = '\r'; break; case 't': char_val = '\t'; break; case '\\': char_val = '\\'; break; case '\'': char_val = '\''; break; case '"': char_val = '"'; break; case 'x': { // \xNN hex escape. uint8_t val = 0; for (int k = 0; k < 2; k++) { ci++; char c = src[ci]; if (c >= '0' && c <= '9') val = (uint8_t)(val * 16 + (uint8_t)(c - '0')); else if (c >= 'a' && c <= 'f') val = (uint8_t)(val * 16 + 10 + (uint8_t)(c - 'a')); else if (c >= 'A' && c <= 'F') val = (uint8_t)(val * 16 + 10 + (uint8_t)(c - 'A')); } char_val = val; break; } case 'u': { // \u{NNNNNN} unicode escape (string_literal.zig:194-231). // Skip past '{'. ci++; uint32_t codepoint = 0; while (true) { ci++; char c = src[ci]; if (c >= '0' && c <= '9') codepoint = codepoint * 16 + (uint32_t)(c - '0'); else if (c >= 'a' && c <= 'f') codepoint = codepoint * 16 + 10 + (uint32_t)(c - 'a'); else if (c >= 'A' && c <= 'F') codepoint = codepoint * 16 + 10 + (uint32_t)(c - 'A'); else break; // Must be '}'. } char_val = codepoint; break; } default: char_val = (uint8_t)src[ci]; break; } } else { char_val = (uint64_t)(uint8_t)src[ci]; } return rvalue(gz, rl, addInt(gz, char_val), node); } // arrayAccess (AstGen.zig:6192-6221). case AST_NODE_ARRAY_ACCESS: { if (RL_IS_REF(rl)) { uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t rhs = expr(gz, scope, nd.rhs); emitDbgStmt(gz, saved_line, saved_col); return addPlNodeBin(gz, ZIR_INST_ELEM_PTR_NODE, node, lhs, rhs); } uint32_t lhs = expr(gz, scope, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t rhs = expr(gz, scope, nd.rhs); emitDbgStmt(gz, saved_line, saved_col); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_ELEM_VAL_NODE, node, lhs, rhs), node); } // slice (AstGen.zig:882-939). case AST_NODE_SLICE_OPEN: { // (AstGen.zig:908-937). uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; ResultLoc usize_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_USIZE_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t start = exprRl(gz, scope, usize_rl, nd.rhs); emitDbgStmt(gz, saved_line, saved_col); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_SLICE_START, node, lhs, start), node); } case AST_NODE_SLICE: { // Slice[rhs]: { start, end } (AstGen.zig:908-937). const Ast* stree = ag->tree; uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t start_node = stree->extra_data.arr[nd.rhs]; uint32_t end_node = stree->extra_data.arr[nd.rhs + 1]; ResultLoc usize_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_USIZE_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t start_ref = exprRl(gz, scope, usize_rl, start_node); uint32_t end_ref = exprRl(gz, scope, usize_rl, end_node); emitDbgStmt(gz, saved_line, saved_col); ensureExtraCapacity(ag, 3); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = lhs; ag->extra[ag->extra_len++] = start_ref; ag->extra[ag->extra_len++] = end_ref; ZirInstData data; data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; return rvalue( gz, rl, addInstruction(gz, ZIR_INST_SLICE_END, data), node); } case AST_NODE_SLICE_SENTINEL: { // SliceSentinel[rhs]: { start, end, sentinel } // (AstGen.zig:908-925). const Ast* stree = ag->tree; uint32_t lhs = exprRl(gz, scope, RL_REF_VAL, nd.lhs); advanceSourceCursorToMainToken(ag, gz, node); uint32_t saved_line = ag->source_line - gz->decl_line; uint32_t saved_col = ag->source_column; uint32_t start_node = stree->extra_data.arr[nd.rhs]; uint32_t end_node = stree->extra_data.arr[nd.rhs + 1]; uint32_t sentinel_node = stree->extra_data.arr[nd.rhs + 2]; // start/end coerced to usize (AstGen.zig:911-912). ResultLoc usize_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_USIZE_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t start_ref = exprRl(gz, scope, usize_rl, start_node); uint32_t end_ref = (end_node != 0) ? exprRl(gz, scope, usize_rl, end_node) : ZIR_REF_NONE; // sentinel: create slice_sentinel_ty and coerce (AstGen.zig:913-916). uint32_t sentinel_ty = addUnNode(gz, ZIR_INST_SLICE_SENTINEL_TY, lhs, node); ResultLoc sent_rl = { .tag = RL_COERCED_TY, .data = sentinel_ty, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t sentinel_ref = exprRl(gz, scope, sent_rl, sentinel_node); emitDbgStmt(gz, saved_line, saved_col); ensureExtraCapacity(ag, 4); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = lhs; ag->extra[ag->extra_len++] = start_ref; ag->extra[ag->extra_len++] = end_ref; ag->extra[ag->extra_len++] = sentinel_ref; ZirInstData data; data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; return rvalue( gz, rl, addInstruction(gz, ZIR_INST_SLICE_SENTINEL, data), node); } // orelse (AstGen.zig:1054-1075). case AST_NODE_ORELSE: if (RL_IS_REF(rl)) { return orelseCatchExpr(gz, scope, rl, node, ZIR_INST_IS_NON_NULL_PTR, ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR, (ZirInstTag)0); } else { return orelseCatchExpr(gz, scope, rl, node, ZIR_INST_IS_NON_NULL, ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE, (ZirInstTag)0); } // catch (AstGen.zig:1017-1052). case AST_NODE_CATCH: if (RL_IS_REF(rl)) { return orelseCatchExpr(gz, scope, rl, node, ZIR_INST_IS_NON_ERR_PTR, ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR, ZIR_INST_ERR_UNION_CODE_PTR); } else { return orelseCatchExpr(gz, scope, rl, node, ZIR_INST_IS_NON_ERR, ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE, ZIR_INST_ERR_UNION_CODE); } // Block expressions (AstGen.zig:984-992). case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: return blockExprExpr(gz, scope, rl, node); // Anonymous array init (AstGen.zig:1119-1127). case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: return arrayInitDotExpr(gz, scope, rl, node); // if (AstGen.zig:1013-1024). case AST_NODE_IF_SIMPLE: case AST_NODE_IF: return ifExpr(gz, scope, rlBr(rl), node); // for (AstGen.zig:1043-1060). case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: return forExpr(gz, scope, rlBr(rl), node, false); // Merge error sets (AstGen.zig:788-797). case AST_NODE_MERGE_ERROR_SETS: { uint32_t lhs = typeExpr(gz, scope, nd.lhs); uint32_t rhs = typeExpr(gz, scope, nd.rhs); return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_MERGE_ERROR_SETS, node, lhs, rhs), node); } // Wrapping arithmetic (AstGen.zig:751-758). case AST_NODE_ADD_WRAP: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_ADDWRAP), node); case AST_NODE_SUB_WRAP: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_SUBWRAP), node); case AST_NODE_MUL_WRAP: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_MULWRAP), node); // Saturating arithmetic (AstGen.zig:752-761). case AST_NODE_ADD_SAT: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_ADD_SAT), node); case AST_NODE_SUB_SAT: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_SUB_SAT), node); case AST_NODE_MUL_SAT: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_MUL_SAT), node); case AST_NODE_SHL_SAT: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_SHL_SAT), node); // break (AstGen.zig:2150-2237). case AST_NODE_BREAK: { uint32_t opt_break_label = nd.lhs; // UINT32_MAX = none uint32_t opt_rhs = nd.rhs; // 0 = none // Walk scope chain to find target block (AstGen.zig:2157-2187). for (Scope* s = scope; s != NULL;) { if (s->tag == SCOPE_GEN_ZIR) { GenZir* block_gz = (GenZir*)s; uint32_t block_inst = UINT32_MAX; if (opt_break_label != UINT32_MAX) { // Labeled break: check label on GenZir. // Use direct source text comparison, not identAsString, // to avoid adding label names to string_bytes // (AstGen.zig:2176 uses tokenIdentEql). if (block_gz->label_token != UINT32_MAX && tokenIdentEql(ag->tree, opt_break_label, block_gz->label_token)) { block_inst = block_gz->label_block_inst; } } else { // Unlabeled break: check break_block. if (block_gz->break_block != UINT32_MAX) block_inst = block_gz->break_block; } if (block_inst != UINT32_MAX) { // Found target (AstGen.zig:2188-2228). ZirInstTag break_tag = block_gz->is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; if (opt_rhs == 0) { // Void break (AstGen.zig:2195-2206). rvalue(gz, block_gz->break_result_info, ZIR_REF_VOID_VALUE, node); genDefers(gz, s, scope, DEFER_NORMAL_ONLY); if (!block_gz->is_comptime) { ZirInstData rdata; rdata.un_node.operand = block_inst + ZIR_REF_START_INDEX; rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; addInstruction(gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); } addBreak(gz, break_tag, block_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); } else { // Value break (AstGen.zig:2208-2228). uint32_t operand = exprRl( gz, scope, block_gz->break_result_info, opt_rhs); genDefers(gz, s, scope, DEFER_NORMAL_ONLY); if (!block_gz->is_comptime) restoreErrRetIndex(gz, block_inst, block_gz->break_result_info, opt_rhs, operand); switch (block_gz->break_result_info.tag) { case RL_PTR: case RL_DISCARD: addBreak(gz, break_tag, block_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); break; default: addBreak(gz, break_tag, block_inst, operand, (int32_t)opt_rhs - (int32_t)gz->decl_node_index); break; } } return ZIR_REF_UNREACHABLE_VALUE; } s = block_gz->parent; } else if (s->tag == SCOPE_LOCAL_VAL) { s = ((ScopeLocalVal*)s)->parent; } else if (s->tag == SCOPE_LOCAL_PTR) { s = ((ScopeLocalPtr*)s)->parent; } else if (s->tag == SCOPE_DEFER_NORMAL || s->tag == SCOPE_DEFER_ERROR) { s = ((ScopeDefer*)s)->parent; } else if (s->tag == SCOPE_LABEL) { s = ((ScopeLabel*)s)->parent; } else { break; } } SET_ERROR(ag); return ZIR_REF_UNREACHABLE_VALUE; } // continue (AstGen.zig:2246-2340). case AST_NODE_CONTINUE: { // Walk scope chain to find GenZir with continue_block. for (Scope* s = scope; s != NULL;) { if (s->tag == SCOPE_GEN_ZIR) { GenZir* gz2 = (GenZir*)s; if (gz2->continue_block != UINT32_MAX) { genDefers(gz, s, scope, DEFER_NORMAL_ONLY); ZirInstTag break_tag = gz2->is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; if (break_tag == ZIR_INST_BREAK_INLINE) { // AstGen.zig:2328-2330. addUnNode(gz, ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW, gz2->continue_block + ZIR_REF_START_INDEX, node); } // Restore error return index (AstGen.zig:2333-2334). if (!gz2->is_comptime) { ZirInstData rdata; rdata.un_node.operand = gz2->continue_block + ZIR_REF_START_INDEX; rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; addInstruction(gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); } addBreak(gz, break_tag, gz2->continue_block, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); return ZIR_REF_UNREACHABLE_VALUE; } s = gz2->parent; } else if (s->tag == SCOPE_LOCAL_VAL) { s = ((ScopeLocalVal*)s)->parent; } else if (s->tag == SCOPE_LOCAL_PTR) { s = ((ScopeLocalPtr*)s)->parent; } else if (s->tag == SCOPE_DEFER_NORMAL || s->tag == SCOPE_DEFER_ERROR) { s = ((ScopeDefer*)s)->parent; } else if (s->tag == SCOPE_LABEL) { s = ((ScopeLabel*)s)->parent; } else { break; } } SET_ERROR(ag); return ZIR_REF_UNREACHABLE_VALUE; } // comptime (AstGen.zig:1104-1105). case AST_NODE_COMPTIME: { // comptimeExprAst / comptimeExpr2 (AstGen.zig:2104, 1982). uint32_t body_node = nd.lhs; // If already comptime, just pass through (AstGen.zig:1990-1992). if (gz->is_comptime) return exprRl(gz, scope, rl, body_node); // Create comptime block (AstGen.zig:2078-2098). uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK_COMPTIME, gz, node); GenZir block_scope = makeSubBlock(gz, scope); block_scope.is_comptime = true; // Transform RL to type-only (AstGen.zig:2084-2090). // Runtime-to-comptime boundary: can't pass runtime pointers. ResultLoc ty_only_rl; uint32_t res_ty = rlResultType(gz, rl, node); if (res_ty != 0) ty_only_rl = (ResultLoc) { .tag = RL_COERCED_TY, .data = res_ty, .src_node = 0, .ctx = rl.ctx }; else ty_only_rl = (ResultLoc) { .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = rl.ctx }; uint32_t result = exprRl(&block_scope, scope, ty_only_rl, body_node); addBreak(&block_scope, ZIR_INST_BREAK_INLINE, block_inst, result, AST_NODE_OFFSET_NONE); setBlockComptimeBody( ag, &block_scope, block_inst, COMPTIME_REASON_COMPTIME_KEYWORD); gzAppendInstruction(gz, block_inst); // Apply rvalue to handle RL_PTR etc (AstGen.zig:2098). return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node); } // switch (AstGen.zig:1072-1078). case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: return switchExpr(gz, scope, rlBr(rl), node); // while (AstGen.zig:1037-1042). case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: return whileExpr(gz, scope, rlBr(rl), node, false); // error_value (AstGen.zig:1005). case AST_NODE_ERROR_VALUE: { uint32_t error_token = ag->tree->nodes.main_tokens[node] + 2; uint32_t str = identAsString(ag, error_token); return rvalue(gz, rl, addStrTok(gz, ZIR_INST_ERROR_VALUE, str, error_token), node); } // error_set_decl (AstGen.zig:5905-5955). case AST_NODE_ERROR_SET_DECL: { AstData esd = ag->tree->nodes.datas[node]; uint32_t lbrace = esd.lhs; uint32_t rbrace = esd.rhs; // Reserve 1 extra word for ErrorSetDecl.fields_len. ensureExtraCapacity(ag, 1 + (rbrace - lbrace)); uint32_t payload_index = ag->extra_len; ag->extra_len++; // placeholder for fields_len uint32_t fields_len = 0; for (uint32_t tok = lbrace + 1; tok < rbrace; tok++) { TokenizerTag ttag = ag->tree->tokens.tags[tok]; if (ttag == TOKEN_DOC_COMMENT || ttag == TOKEN_COMMA) continue; if (ttag == TOKEN_IDENTIFIER) { uint32_t str_index = identAsString(ag, tok); ensureExtraCapacity(ag, 1); ag->extra[ag->extra_len++] = str_index; fields_len++; } } ag->extra[payload_index] = fields_len; return rvalue(gz, rl, addPlNodePayloadIndex( gz, ZIR_INST_ERROR_SET_DECL, node, payload_index), node); } // assign in expr context (AstGen.zig:1011-1014). case AST_NODE_ASSIGN: assignStmt(gz, scope, node); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); // Compound assignment operators (AstGen.zig:689-744). case AST_NODE_ASSIGN_ADD: assignOp(gz, scope, node, ZIR_INST_ADD); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB: assignOp(gz, scope, node, ZIR_INST_SUB); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL: assignOp(gz, scope, node, ZIR_INST_MUL); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_DIV: assignOp(gz, scope, node, ZIR_INST_DIV); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MOD: assignOp(gz, scope, node, ZIR_INST_MOD_REM); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_AND: assignOp(gz, scope, node, ZIR_INST_BIT_AND); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_OR: assignOp(gz, scope, node, ZIR_INST_BIT_OR); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_XOR: assignOp(gz, scope, node, ZIR_INST_XOR); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_ADD_WRAP: assignOp(gz, scope, node, ZIR_INST_ADDWRAP); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB_WRAP: assignOp(gz, scope, node, ZIR_INST_SUBWRAP); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL_WRAP: assignOp(gz, scope, node, ZIR_INST_MULWRAP); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_ADD_SAT: assignOp(gz, scope, node, ZIR_INST_ADD_SAT); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB_SAT: assignOp(gz, scope, node, ZIR_INST_SUB_SAT); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL_SAT: assignOp(gz, scope, node, ZIR_INST_MUL_SAT); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); // Shift assignment operators (AstGen.zig:676-687). case AST_NODE_ASSIGN_SHL: assignShift(gz, scope, node, ZIR_INST_SHL); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SHR: assignShift(gz, scope, node, ZIR_INST_SHR); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SHL_SAT: assignShiftSat(gz, scope, node); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); default: SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } } static uint32_t expr(GenZir* gz, Scope* scope, uint32_t node) { return exprRl(gz, scope, RL_NONE_VAL, node); } // --- blockExprExpr (AstGen.zig:2388-2536) --- // Handles block expressions (labeled and unlabeled). // Unlabeled blocks just execute statements and return void. // Labeled blocks (blk: { ... break :blk val; }) need a block instruction. static uint32_t blockExprExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { (void)rl; AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // Extract statements. uint32_t stmt_buf[2]; const uint32_t* statements = NULL; uint32_t stmt_count = 0; switch (tag) { case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: { uint32_t idx = 0; if (nd.lhs != 0) stmt_buf[idx++] = nd.lhs; if (nd.rhs != 0) stmt_buf[idx++] = nd.rhs; statements = stmt_buf; stmt_count = idx; break; } case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: { uint32_t start = nd.lhs; uint32_t end = nd.rhs; statements = tree->extra_data.arr + start; stmt_count = end - start; break; } default: SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Check if labeled (AstGen.zig:2397-2402). // A labeled block has: identifier colon before the lbrace. uint32_t lbrace = tree->nodes.main_tokens[node]; bool is_labeled = (lbrace >= 2 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER); if (!is_labeled) { if (!gz->is_comptime) { // Non-comptime unlabeled block (AstGen.zig:2404-2425). // Create block_inst FIRST, add to gz, then process body. uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node); gzAppendInstruction(gz, block_inst); GenZir block_scope = makeSubBlock(gz, scope); blockExprStmts( &block_scope, &block_scope.base, statements, stmt_count); if (!endsWithNoReturn(&block_scope)) { // restore_err_ret_index on gz (AstGen.zig:2420). ZirInstData rdata; rdata.un_node.operand = block_inst + ZIR_REF_START_INDEX; rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; addInstruction( gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); // break on block_scope (AstGen.zig:2422). addBreak(&block_scope, ZIR_INST_BREAK, block_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); } setBlockBody(ag, &block_scope, block_inst); } else { // Comptime unlabeled block: inline statements // (AstGen.zig:2426-2429). GenZir sub_gz = makeSubBlock(gz, scope); blockExprStmts(&sub_gz, &sub_gz.base, statements, stmt_count); } return ZIR_REF_VOID_VALUE; } // Labeled block (AstGen.zig:2466-2536). // Note: upstream blockExpr always passes force_comptime=false. uint32_t label_token = lbrace - 2; // Compute break result info (AstGen.zig:2484-2492). bool need_rl = nodesNeedRlContains(ag, node); ResultLoc break_ri = breakResultInfo(gz, rl, node, need_rl); bool need_result_rvalue = (break_ri.tag != rl.tag); // Reserve the block instruction (AstGen.zig:2500-2501). uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node); gzAppendInstruction(gz, block_inst); GenZir block_scope = makeSubBlock(gz, scope); // Set label on block_scope (AstGen.zig:2504-2508). block_scope.label_token = label_token; block_scope.label_block_inst = block_inst; block_scope.break_result_info = break_ri; // Process statements (AstGen.zig:2512). blockExprStmts(&block_scope, &block_scope.base, statements, stmt_count); if (!endsWithNoReturn(&block_scope)) { // Emit restore_err_ret_index (AstGen.zig:2515). ZirInstData rdata; rdata.un_node.operand = block_inst + ZIR_REF_START_INDEX; rdata.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; addInstruction( gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); // rvalue + break (AstGen.zig:2516-2518). uint32_t result = rvalue( gz, block_scope.break_result_info, ZIR_REF_VOID_VALUE, node); addBreak(&block_scope, ZIR_INST_BREAK, block_inst, result, AST_NODE_OFFSET_NONE); } setBlockBody(ag, &block_scope, block_inst); // AstGen.zig:2531-2534. if (need_result_rvalue) return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node); return block_inst + ZIR_REF_START_INDEX; } // --- arrayInitDotExpr (AstGen.zig:1576-1595) --- // Handles anonymous array init: `.{a, b, c}`. // Emits array_init_anon instruction with MultiOp payload. static uint32_t arrayInitDotExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // Extract elements. uint32_t elem_buf[2]; const uint32_t* elements = NULL; uint32_t elem_count = 0; switch (tag) { case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: { uint32_t idx = 0; if (nd.lhs != 0) elem_buf[idx++] = nd.lhs; if (nd.rhs != 0) elem_buf[idx++] = nd.rhs; elements = elem_buf; elem_count = idx; break; } case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: { uint32_t start = nd.lhs; uint32_t end = nd.rhs; elements = tree->extra_data.arr + start; elem_count = end - start; break; } default: SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Dispatch based on RL (AstGen.zig:1515-1572). switch (rl.tag) { case RL_NONE: { // arrayInitExprAnon (AstGen.zig:1576-1595). ensureExtraCapacity(ag, 1 + elem_count); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = elem_count; uint32_t extra_start = ag->extra_len; ag->extra_len += elem_count; for (uint32_t i = 0; i < elem_count; i++) { uint32_t elem_ref = expr(gz, scope, elements[i]); ag->extra[extra_start + i] = elem_ref; } return addPlNodePayloadIndex( gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index); } case RL_TY: case RL_COERCED_TY: { // validate_array_init_result_ty + arrayInitExprTyped // (AstGen.zig:1534-1539). uint32_t result_ty = rl.data; // Emit ArrayInit { ty, init_count } payload for // validate_array_init_result_ty. ensureExtraCapacity(ag, 2); uint32_t val_payload = ag->extra_len; ag->extra[ag->extra_len++] = result_ty; ag->extra[ag->extra_len++] = elem_count; addPlNodePayloadIndex( gz, ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY, node, val_payload); // arrayInitExprTyped (AstGen.zig:1598-1642) with elem_ty=none. uint32_t operands_len = elem_count + 1; // +1 for type ensureExtraCapacity(ag, 1 + operands_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = operands_len; ag->extra[ag->extra_len++] = result_ty; uint32_t extra_start = ag->extra_len; ag->extra_len += elem_count; for (uint32_t i = 0; i < elem_count; i++) { // array_init_elem_type uses bin data (AstGen.zig:1626-1632). uint32_t elem_ty = addBin(gz, ZIR_INST_ARRAY_INIT_ELEM_TYPE, result_ty, i); ResultLoc elem_rl = { .tag = RL_COERCED_TY, .data = elem_ty, .src_node = 0 }; uint32_t elem_ref = exprRl(gz, scope, elem_rl, elements[i]); ag->extra[extra_start + i] = elem_ref; } return addPlNodePayloadIndex( gz, ZIR_INST_ARRAY_INIT, node, payload_index); } case RL_INFERRED_PTR: { // arrayInitExprAnon + rvalue (AstGen.zig:1545-1551). ensureExtraCapacity(ag, 1 + elem_count); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = elem_count; uint32_t extra_start = ag->extra_len; ag->extra_len += elem_count; for (uint32_t i = 0; i < elem_count; i++) { uint32_t elem_ref = expr(gz, scope, elements[i]); ag->extra[extra_start + i] = elem_ref; } uint32_t result = addPlNodePayloadIndex( gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index); return rvalue(gz, rl, result, node); } case RL_DISCARD: { // Evaluate and discard each element (AstGen.zig:1517-1522). for (uint32_t i = 0; i < elem_count; i++) { exprRl(gz, scope, RL_DISCARD_VAL, elements[i]); } return ZIR_REF_VOID_VALUE; } case RL_REF: { // arrayInitExprAnon + ref (AstGen.zig:1523-1526). ensureExtraCapacity(ag, 1 + elem_count); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = elem_count; uint32_t extra_start = ag->extra_len; ag->extra_len += elem_count; for (uint32_t i = 0; i < elem_count; i++) { uint32_t elem_ref = expr(gz, scope, elements[i]); ag->extra[extra_start + i] = elem_ref; } uint32_t result = addPlNodePayloadIndex( gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index); return rvalue(gz, rl, result, node); } case RL_REF_COERCED_TY: { // validate_array_init_ref_ty + arrayInitExprTyped // (AstGen.zig:1527-1532). uint32_t ptr_ty_inst = rl.data; ensureExtraCapacity(ag, 2); uint32_t val_payload = ag->extra_len; ag->extra[ag->extra_len++] = ptr_ty_inst; ag->extra[ag->extra_len++] = elem_count; uint32_t dest_arr_ty_inst = addPlNodePayloadIndex( gz, ZIR_INST_VALIDATE_ARRAY_INIT_REF_TY, node, val_payload); // arrayInitExprTyped with elem_ty=none, is_ref=true. uint32_t operands_len = elem_count + 1; ensureExtraCapacity(ag, 1 + operands_len); uint32_t ai_payload = ag->extra_len; ag->extra[ag->extra_len++] = operands_len; ag->extra[ag->extra_len++] = dest_arr_ty_inst; uint32_t extra_start2 = ag->extra_len; ag->extra_len += elem_count; for (uint32_t i = 0; i < elem_count; i++) { // array_init_elem_type uses bin data (AstGen.zig:1626-1632). uint32_t elem_ty = addBin( gz, ZIR_INST_ARRAY_INIT_ELEM_TYPE, dest_arr_ty_inst, i); ResultLoc elem_rl = { .tag = RL_COERCED_TY, .data = elem_ty, .src_node = 0 }; uint32_t elem_ref = exprRl(gz, scope, elem_rl, elements[i]); ag->extra[extra_start2 + i] = elem_ref; } return addPlNodePayloadIndex( gz, ZIR_INST_ARRAY_INIT_REF, node, ai_payload); } case RL_PTR: { // arrayInitExprPtr (AstGen.zig:1541-1543, 1645-1672). uint32_t array_ptr_inst = addUnNode(gz, ZIR_INST_OPT_EU_BASE_PTR_INIT, rl.data, node); // Block payload: body_len = elem_count. ensureExtraCapacity(ag, 1 + elem_count); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = elem_count; uint32_t items_start = ag->extra_len; ag->extra_len += elem_count; for (uint32_t i = 0; i < elem_count; i++) { // array_init_elem_ptr: ElemPtrImm{ptr, index}. uint32_t elem_ptr_inst = addPlNodeBin(gz, ZIR_INST_ARRAY_INIT_ELEM_PTR, elements[i], array_ptr_inst, i); ag->extra[items_start + i] = elem_ptr_inst - ZIR_REF_START_INDEX; // .toIndex() // Evaluate element with ptr RL (AstGen.zig:1668). // Upstream creates fresh ResultInfo with default ctx (.none). ResultLoc ptr_rl = { .tag = RL_PTR, .data = elem_ptr_inst, .src_node = 0, .ctx = RI_CTX_NONE }; exprRl(gz, scope, ptr_rl, elements[i]); } addPlNodePayloadIndex( gz, ZIR_INST_VALIDATE_PTR_ARRAY_INIT, node, payload_index); return ZIR_REF_VOID_VALUE; } } // Fallback: anon init + rvalue. ensureExtraCapacity(ag, 1 + elem_count); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = elem_count; uint32_t extra_start = ag->extra_len; ag->extra_len += elem_count; for (uint32_t i = 0; i < elem_count; i++) { uint32_t elem_ref = expr(gz, scope, elements[i]); ag->extra[extra_start + i] = elem_ref; } uint32_t result = addPlNodePayloadIndex( gz, ZIR_INST_ARRAY_INIT_ANON, node, payload_index); return rvalue(gz, rl, result, node); } // --- ifExpr (AstGen.zig:6300-6528) --- // Handles if and if_simple expressions. // Pattern: block_scope with condbr → then/else branches → setCondBrPayload. static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; bool need_rl = nodesNeedRlContains(ag, node); ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl); AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; uint32_t cond_node = nd.lhs; uint32_t then_node, else_node; if (tag == AST_NODE_IF_SIMPLE) { then_node = nd.rhs; else_node = 0; } else { // AST_NODE_IF: rhs is index into extra -> If{then_expr, else_expr} then_node = tree->extra_data.arr[nd.rhs]; else_node = tree->extra_data.arr[nd.rhs + 1]; } // Detect payload capture: if (cond) |x| (AstGen.zig Ast.fullIf). // payload_pipe = lastToken(cond_expr) + 2; if pipe -> payload_token + 1. uint32_t payload_token = 0; // 0 = no payload uint32_t last_cond_tok = lastToken(tree, cond_node); uint32_t pipe_tok = last_cond_tok + 2; if (pipe_tok < tree->tokens.len && tree->tokens.tags[pipe_tok] == TOKEN_PIPE) { payload_token = pipe_tok + 1; // identifier or * token } // Detect error token: then_expr else |e| (AstGen.zig Ast.fullIf). uint32_t error_token = 0; if (else_node != 0) { uint32_t else_tok = lastToken(tree, then_node) + 1; // "else" keyword if (else_tok + 1 < tree->tokens.len && tree->tokens.tags[else_tok + 1] == TOKEN_PIPE) { error_token = else_tok + 2; } } // Detect payload_is_ref: if payload_token is '*' (AstGen.zig:6330-6333). bool payload_is_ref = (payload_token != 0 && tree->tokens.tags[payload_token] == TOKEN_ASTERISK); // Create block_scope (AstGen.zig:6326-6328). GenZir block_scope = makeSubBlock(gz, scope); // Emit DBG_STMT for condition (AstGen.zig:6335). // NOTE: upstream emits into parent_gz AFTER block_scope is created, // so the dbg_stmt ends up in block_scope's range (shared array). emitDbgNode(gz, cond_node); // Evaluate condition (AstGen.zig:6336-6363). uint32_t cond_inst; // the value (optional/err-union/bool) uint32_t bool_bit; // the boolean for condbr if (error_token != 0) { // Error union condition: if (err_union) |val| else |err|. // (AstGen.zig:6340-6347). ResultLoc cond_rl = payload_is_ref ? RL_REF_VAL : RL_NONE_VAL; cond_rl.ctx = RI_CTX_ERROR_HANDLING_EXPR; cond_inst = exprRl(&block_scope, &block_scope.base, cond_rl, cond_node); ZirInstTag cond_tag = payload_is_ref ? ZIR_INST_IS_NON_ERR_PTR : ZIR_INST_IS_NON_ERR; bool_bit = addUnNode(&block_scope, cond_tag, cond_inst, cond_node); } else if (payload_token != 0) { // Optional condition: if (optional) |val| (AstGen.zig:6348-6355). ResultLoc cond_rl = payload_is_ref ? RL_REF_VAL : RL_NONE_VAL; cond_inst = exprRl(&block_scope, &block_scope.base, cond_rl, cond_node); ZirInstTag cond_tag = payload_is_ref ? ZIR_INST_IS_NON_NULL_PTR : ZIR_INST_IS_NON_NULL; bool_bit = addUnNode(&block_scope, cond_tag, cond_inst, cond_node); } else { // Bool condition (AstGen.zig:6356-6362). ResultLoc coerced_bool_ri = { .tag = RL_COERCED_TY, .data = ZIR_REF_BOOL_TYPE, .src_node = 0, .ctx = RI_CTX_NONE, }; cond_inst = exprRl( &block_scope, &block_scope.base, coerced_bool_ri, cond_node); bool_bit = cond_inst; } uint32_t condbr = addCondBr(&block_scope, ZIR_INST_CONDBR, node); uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node); setBlockBody(ag, &block_scope, block_inst); gzAppendInstruction(gz, block_inst); // Then branch (AstGen.zig:6372-6441). GenZir then_scope = makeSubBlock(gz, scope); Scope* then_sub_scope = &then_scope.base; ScopeLocalVal payload_val_scope; memset(&payload_val_scope, 0, sizeof(payload_val_scope)); if (error_token != 0 && payload_token != 0) { // Error union with payload: unwrap payload (AstGen.zig:6379-6403). ZirInstTag unwrap_tag = payload_is_ref ? ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR : ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE; uint32_t payload_inst = addUnNode(&then_scope, unwrap_tag, cond_inst, then_node); uint32_t name_token = payload_token + (payload_is_ref ? 1u : 0u); if (tokenIsUnderscore(tree, name_token)) { // Discard (AstGen.zig:6389-6391). if (payload_is_ref) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // then_sub_scope stays as &then_scope.base } else { uint32_t ident_name = identAsString(ag, name_token); payload_val_scope = (ScopeLocalVal) { .base = { .tag = SCOPE_LOCAL_VAL }, .parent = &then_scope.base, .gen_zir = &then_scope, .inst = payload_inst, .token_src = name_token, .name = ident_name, }; addDbgVar( &then_scope, ZIR_INST_DBG_VAR_VAL, ident_name, payload_inst); then_sub_scope = &payload_val_scope.base; } } else if (error_token != 0) { // Error union without payload: ensure payload is void // (AstGen.zig:6404-6406). addUnNode(&then_scope, ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID, cond_inst, node); } else if (payload_token != 0) { // Optional with payload: unwrap optional (AstGen.zig:6408-6431). uint32_t ident_token = payload_token + (payload_is_ref ? 1u : 0u); if (tokenIsUnderscore(tree, ident_token)) { // Discard (AstGen.zig:6415-6417). if (payload_is_ref) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // then_sub_scope stays as &then_scope.base } else { ZirInstTag unwrap_tag = payload_is_ref ? ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR : ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE; uint32_t payload_inst = addUnNode(&then_scope, unwrap_tag, cond_inst, then_node); uint32_t ident_name = identAsString(ag, ident_token); payload_val_scope = (ScopeLocalVal) { .base = { .tag = SCOPE_LOCAL_VAL }, .parent = &then_scope.base, .gen_zir = &then_scope, .inst = payload_inst, .token_src = ident_token, .name = ident_name, }; addDbgVar( &then_scope, ZIR_INST_DBG_VAR_VAL, ident_name, payload_inst); then_sub_scope = &payload_val_scope.base; } } // Use fullBodyExpr for then body (AstGen.zig:6437). uint32_t then_result = fullBodyExpr(&then_scope, then_sub_scope, break_rl, then_node); if (!endsWithNoReturn(&then_scope)) { addBreak(&then_scope, ZIR_INST_BREAK, block_inst, then_result, (int32_t)then_node - (int32_t)gz->decl_node_index); } // Else branch (AstGen.zig:6443-6489). GenZir else_scope = makeSubBlock(gz, scope); // save_err_ret_index (AstGen.zig:6448-6449). bool do_err_trace = ag->fn_ret_ty != 0 && error_token != 0; if (do_err_trace && nodeMayAppendToErrorTrace(tree, cond_node)) addSaveErrRetIndex(&else_scope, ZIR_REF_NONE); if (else_node != 0) { Scope* else_sub_scope = &else_scope.base; ScopeLocalVal error_val_scope; memset(&error_val_scope, 0, sizeof(error_val_scope)); if (error_token != 0) { // Error capture: else |err| (AstGen.zig:6452-6475). ZirInstTag err_tag = payload_is_ref ? ZIR_INST_ERR_UNION_CODE_PTR : ZIR_INST_ERR_UNION_CODE; uint32_t err_inst = addUnNode(&else_scope, err_tag, cond_inst, cond_node); if (tokenIsUnderscore(tree, error_token)) { // Discard |_| (AstGen.zig:6461-6462). // else_sub_scope stays as &else_scope.base } else { uint32_t err_name = identAsString(ag, error_token); error_val_scope = (ScopeLocalVal) { .base = { .tag = SCOPE_LOCAL_VAL }, .parent = &else_scope.base, .gen_zir = &else_scope, .inst = err_inst, .token_src = error_token, .name = err_name, }; addDbgVar( &else_scope, ZIR_INST_DBG_VAR_VAL, err_name, err_inst); else_sub_scope = &error_val_scope.base; } } // Use fullBodyExpr for else body (AstGen.zig:6478). uint32_t else_result = fullBodyExpr(&else_scope, else_sub_scope, break_rl, else_node); if (!endsWithNoReturn(&else_scope)) { // Restore error return index (AstGen.zig:6480-6482). if (do_err_trace) restoreErrRetIndex( &else_scope, block_inst, break_rl, else_node, else_result); addBreak(&else_scope, ZIR_INST_BREAK, block_inst, else_result, (int32_t)else_node - (int32_t)gz->decl_node_index); } } else { // No else branch (AstGen.zig:6486-6488). uint32_t else_result = rvalue(&else_scope, rl, ZIR_REF_VOID_VALUE, node); addBreak(&else_scope, ZIR_INST_BREAK, block_inst, else_result, AST_NODE_OFFSET_NONE); } // Wire up condbr (AstGen.zig:6491). setCondBrPayload(ag, condbr, bool_bit, &then_scope, &else_scope); // AstGen.zig:6493-6497. bool need_result_rvalue = (break_rl.tag != rl.tag); if (need_result_rvalue) return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node); return block_inst + ZIR_REF_START_INDEX; } // --- forExpr (AstGen.zig:6819-7125) --- // Handles for_simple and for (multi-input). // Supports both indexable and for_range inputs. #define FOR_MAX_INPUTS 16 static uint32_t forExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_statement) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[node]; AstNodeTag node_tag = tree->nodes.tags[node]; // Detect inline keyword (AstGen.zig:6847). uint32_t main_token = tree->nodes.main_tokens[node]; bool is_inline = (main_token > 0 && tree->tokens.tags[main_token - 1] == TOKEN_KEYWORD_INLINE); // Compute label_token (AstGen.zig fullForComponents:2341-2348). uint32_t label_token = UINT32_MAX; { uint32_t tok_i = main_token; if (is_inline) tok_i = main_token - 1; if (tok_i >= 2 && tree->tokens.tags[tok_i - 2] == TOKEN_IDENTIFIER && tree->tokens.tags[tok_i - 1] == TOKEN_COLON) label_token = tok_i - 2; } // Compute break_rl from rl (AstGen.zig:6833-6845). bool need_rl = nodesNeedRlContains(ag, node); ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl); bool need_result_rvalue = (break_rl.tag != rl.tag); // Extract input nodes and body/else nodes. // FOR_SIMPLE: lhs = input node, rhs = body (Ast.zig:1960-1968). // FOR: lhs = extra_data index, rhs = packed AstFor (Ast.zig:1970-1981). uint32_t input_nodes[FOR_MAX_INPUTS]; uint32_t num_inputs; uint32_t body_node; uint32_t else_node = 0; if (node_tag == AST_NODE_FOR_SIMPLE) { input_nodes[0] = nd.lhs; num_inputs = 1; body_node = nd.rhs; } else { uint32_t extra_idx = nd.lhs; AstFor for_data; memcpy(&for_data, &nd.rhs, sizeof(AstFor)); num_inputs = for_data.inputs; if (num_inputs == 0 || num_inputs > FOR_MAX_INPUTS) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } for (uint32_t i = 0; i < num_inputs; i++) input_nodes[i] = tree->extra_data.arr[extra_idx + i]; body_node = tree->extra_data.arr[extra_idx + num_inputs]; if (for_data.has_else) else_node = tree->extra_data.arr[extra_idx + num_inputs + 1]; } // Per-input arrays (AstGen.zig:6858-6862). uint32_t indexables[FOR_MAX_INPUTS]; uint32_t lens[FOR_MAX_INPUTS][2]; // [ref0, ref1] per input // Allocate index counter (AstGen.zig:6865-6874). ZirInstTag alloc_tag = is_inline ? ZIR_INST_ALLOC_COMPTIME_MUT : ZIR_INST_ALLOC; uint32_t index_ptr = addUnNode(gz, alloc_tag, ZIR_REF_USIZE_TYPE, node); addPlNodeBin(gz, ZIR_INST_STORE_NODE, node, index_ptr, ZIR_REF_ZERO_USIZE); // Compute payload_token (AstGen.zig fullForComponents:2349-2350). // payload_token = lastToken(inputs[last]) + 3 + has_comma uint32_t last_cond_tok = lastToken(tree, input_nodes[num_inputs - 1]); bool has_comma = (last_cond_tok + 1 < tree->tokens.len && tree->tokens.tags[last_cond_tok + 1] == TOKEN_COMMA); uint32_t payload_token = last_cond_tok + 3 + (has_comma ? 1 : 0); bool any_len_checks = false; // Process each input (AstGen.zig:6878-6925). uint32_t capture_token = payload_token; for (uint32_t i = 0; i < num_inputs; i++) { uint32_t input = input_nodes[i]; // Advance capture_token past this capture's ident (+comma). bool capture_is_ref = (tree->tokens.tags[capture_token] == TOKEN_ASTERISK); uint32_t ident_tok = capture_token + (capture_is_ref ? 1u : 0u); bool is_discard = tokenIsUnderscore(tree, ident_tok); capture_token = ident_tok + 2; // skip ident + comma/pipe // Diagnostic: pointer modifier invalid on discard // (AstGen.zig:6885-6887). if (is_discard && capture_is_ref) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } emitDbgNode(gz, input); if (tree->nodes.tags[input] == AST_NODE_FOR_RANGE) { // Diagnostic: cannot capture reference to range // (AstGen.zig:6893-6895). if (capture_is_ref) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Range input (AstGen.zig:6896-6916). AstData range_nd = tree->nodes.datas[input]; uint32_t start_node = range_nd.lhs; uint32_t end_node = range_nd.rhs; // AstGen.zig:6897-6902: expr with .rl = .{ .ty = .usize_type } ResultLoc usize_rl = { .tag = RL_TY, .data = ZIR_REF_USIZE_TYPE, .src_node = 0, .ctx = RI_CTX_NONE, }; uint32_t start_val = exprRl(gz, scope, usize_rl, start_node); uint32_t end_val = ZIR_REF_NONE; if (end_node != 0) { end_val = exprRl(gz, scope, usize_rl, end_node); } // Diagnostic: discard of unbounded counter // (AstGen.zig:6904-6906). if (end_val == ZIR_REF_NONE && is_discard) { SET_ERROR(ag); } if (end_val == ZIR_REF_NONE) { lens[i][0] = ZIR_REF_NONE; lens[i][1] = ZIR_REF_NONE; } else { any_len_checks = true; lens[i][0] = start_val; lens[i][1] = end_val; } // Check if start is trivially zero. bool start_is_zero = false; if (tree->nodes.tags[start_node] == AST_NODE_NUMBER_LITERAL) { uint32_t tok = tree->nodes.main_tokens[start_node]; uint32_t ts = tree->tokens.starts[tok]; if (tree->source[ts] == '0' && (ts + 1 >= tree->source_len || tree->source[ts + 1] < '0' || tree->source[ts + 1] > '9')) start_is_zero = true; } indexables[i] = start_is_zero ? ZIR_REF_NONE : start_val; } else { // Regular indexable (AstGen.zig:6918-6923). uint32_t indexable = expr(gz, scope, input); any_len_checks = true; indexables[i] = indexable; lens[i][0] = indexable; lens[i][1] = ZIR_REF_NONE; } } // Error if no length checks exist (AstGen.zig:6927-6929). if (!any_len_checks) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Emit for_len as MultiOp (AstGen.zig:6933-6942). uint32_t len; { uint32_t operands_len = num_inputs * 2; ensureExtraCapacity(ag, 1 + operands_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = operands_len; for (uint32_t i = 0; i < num_inputs; i++) { ag->extra[ag->extra_len++] = lens[i][0]; ag->extra[ag->extra_len++] = lens[i][1]; } ZirInstData data; data.pl_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; len = addInstruction(gz, ZIR_INST_FOR_LEN, data); } // Create loop (AstGen.zig:6944-6946). ZirInstTag loop_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_LOOP; uint32_t loop_inst = makeBlockInst(ag, loop_tag, gz, node); // Issue 3: append loop_inst to parent_gz immediately (AstGen.zig:6946). gzAppendInstruction(gz, loop_inst); GenZir loop_scope = makeSubBlock(gz, scope); loop_scope.is_inline = is_inline; // Issue 1: set break_result_info (AstGen.zig:6950). loop_scope.break_result_info = break_rl; // Load index (AstGen.zig:6955-6956). // We need to finish loop_scope later once we have the deferred refs from // then_scope. However, the load must be removed from instructions in the // meantime or it appears to be part of parent_gz. uint32_t index = addUnNode(&loop_scope, ZIR_INST_LOAD, index_ptr, node); ag->scratch_inst_len--; // pop from loop_scope (AstGen.zig:6956) // Condition: added to cond_scope (AstGen.zig:6958-6962). GenZir cond_scope = makeSubBlock(gz, &loop_scope.base); uint32_t cond = addPlNodeBin(&cond_scope, ZIR_INST_CMP_LT, node, index, len); // Create condbr + block (AstGen.zig:6967-6974). ZirInstTag condbr_tag = is_inline ? ZIR_INST_CONDBR_INLINE : ZIR_INST_CONDBR; uint32_t condbr = addCondBr(&cond_scope, condbr_tag, node); ZirInstTag block_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_BLOCK; uint32_t cond_block = makeBlockInst(ag, block_tag, &loop_scope, node); setBlockBody(ag, &cond_scope, cond_block); loop_scope.break_block = loop_inst; loop_scope.continue_block = cond_block; // AstGen.zig:6974 // Issue 4: set label on loop_scope (AstGen.zig:6975-6980). if (label_token != UINT32_MAX) { loop_scope.label_token = label_token; loop_scope.label_block_inst = loop_inst; } // Then branch: loop body (AstGen.zig:6982-7065). GenZir then_scope = makeSubBlock(gz, &cond_scope.base); // Set up capture scopes for all inputs (AstGen.zig:6986-7045). ScopeLocalVal capture_scopes[FOR_MAX_INPUTS]; Scope* body_scope_parent = &then_scope.base; { capture_token = payload_token; for (uint32_t i = 0; i < num_inputs; i++) { uint32_t input = input_nodes[i]; bool capture_is_ref = (tree->tokens.tags[capture_token] == TOKEN_ASTERISK); uint32_t ident_tok = capture_token + (capture_is_ref ? 1u : 0u); capture_token = ident_tok + 2; // Check if discard (AstGen.zig:6999). bool is_discard = tokenIsUnderscore(tree, ident_tok); if (is_discard) continue; // Compute capture inst (AstGen.zig:7004-7028). uint32_t capture_inst; bool is_counter = (tree->nodes.tags[input] == AST_NODE_FOR_RANGE); if (indexables[i] == ZIR_REF_NONE) { // Start=0 counter: use index directly. capture_inst = index; } else if (is_counter) { // Counter with nonzero start: add. capture_inst = addPlNodeBin( &then_scope, ZIR_INST_ADD, input, indexables[i], index); } else if (capture_is_ref) { // Indexable by ref: elem_ptr. capture_inst = addPlNodeBin(&then_scope, ZIR_INST_ELEM_PTR, input, indexables[i], index); } else { // Indexable by val: elem_val. capture_inst = addPlNodeBin(&then_scope, ZIR_INST_ELEM_VAL, input, indexables[i], index); } uint32_t name_str = identAsString(ag, ident_tok); capture_scopes[i] = (ScopeLocalVal) { .base = { .tag = SCOPE_LOCAL_VAL }, .parent = body_scope_parent, .gen_zir = &then_scope, .inst = capture_inst, .token_src = ident_tok, .name = name_str, }; // AstGen.zig:7040. addDbgVar( &then_scope, ZIR_INST_DBG_VAR_VAL, name_str, capture_inst); body_scope_parent = &capture_scopes[i].base; } } // Execute body (AstGen.zig:7047-7048). uint32_t then_result = fullBodyExpr(&then_scope, body_scope_parent, RL_NONE_VAL, body_node); addEnsureResult(&then_scope, then_result, body_node); // dbg_stmt + dbg_empty_stmt (AstGen.zig:7052-7061). advanceSourceCursor(ag, tree->tokens.starts[lastToken(tree, body_node)]); emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column); { ZirInstData ext_data; ext_data.extended.opcode = (uint16_t)ZIR_EXT_DBG_EMPTY_STMT; ext_data.extended.small = 0; ext_data.extended.operand = 0; addInstruction(gz, ZIR_INST_EXTENDED, ext_data); } ZirInstTag break_tag = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; addBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); // Else branch (AstGen.zig:7066-7091). GenZir else_scope = makeSubBlock(gz, &cond_scope.base); if (else_node != 0) { // Issue 2: evaluate else expression (AstGen.zig:7069-7081). // Remove continue/break blocks so that control flow applies // to outer loops (AstGen.zig:7073-7074). loop_scope.continue_block = UINT32_MAX; loop_scope.break_block = UINT32_MAX; uint32_t else_result = fullBodyExpr(&else_scope, &else_scope.base, loop_scope.break_result_info, else_node); if (is_statement) { addEnsureResult(&else_scope, else_result, else_node); } if (!endsWithNoReturn(&else_scope)) { addBreak(&else_scope, break_tag, loop_inst, else_result, (int32_t)else_node - (int32_t)gz->decl_node_index); } } else { // No else: break with void (AstGen.zig:7082-7085). uint32_t void_result = rvalue(&else_scope, rl, ZIR_REF_VOID_VALUE, node); addBreak(&else_scope, break_tag, loop_inst, void_result, AST_NODE_OFFSET_NONE); } // Issue 4: check unused label (AstGen.zig:7087-7091). if (label_token != UINT32_MAX) { // Note: upstream checks loop_scope.label.used; we don't track usage // yet, so skip the "unused for loop label" error for now. } setCondBrPayload(ag, condbr, cond, &then_scope, &else_scope); // then_scope and else_scope unstacked now. Resurrect loop_scope to // finally finish it (AstGen.zig:7095-7113). { // Reset loop_scope instructions and re-add index + cond_block. loop_scope.instructions_top = ag->scratch_inst_len; gzAppendInstruction(&loop_scope, index - ZIR_REF_START_INDEX); gzAppendInstruction(&loop_scope, cond_block); // Increment the index variable (AstGen.zig:7100-7108). uint32_t index_plus_one = addPlNodeBin( &loop_scope, ZIR_INST_ADD_UNSAFE, node, index, ZIR_REF_ONE_USIZE); addPlNodeBin( &loop_scope, ZIR_INST_STORE_NODE, node, index_ptr, index_plus_one); // Repeat (AstGen.zig:7110-7111). ZirInstTag repeat_tag = is_inline ? ZIR_INST_REPEAT_INLINE : ZIR_INST_REPEAT; ZirInstData repeat_data; memset(&repeat_data, 0, sizeof(repeat_data)); repeat_data.node = (int32_t)node - (int32_t)loop_scope.decl_node_index; addInstruction(&loop_scope, repeat_tag, repeat_data); setBlockBody(ag, &loop_scope, loop_inst); } // Issue 1: apply rvalue if needed (AstGen.zig:7116-7119). uint32_t result; if (need_result_rvalue) result = rvalue(gz, rl, loop_inst + ZIR_REF_START_INDEX, node); else result = loop_inst + ZIR_REF_START_INDEX; // Emit ensure_result_used when used as statement (AstGen.zig:7121-7123). if (is_statement) { addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, result, node); } return result; } // --- orelseCatchExpr (AstGen.zig:6031-6142) --- // Handles `lhs orelse rhs` and `lhs catch rhs`. static uint32_t orelseCatchExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, ZirInstTag cond_op, ZirInstTag unwrap_op, ZirInstTag unwrap_code_op) { // unwrap_code_op used for catch payload capture scope (not yet // implemented in C, but passed for correctness/future use). (void)unwrap_code_op; AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[node]; bool do_err_trace = ag->fn_ret_ty != 0 && (cond_op == ZIR_INST_IS_NON_ERR || cond_op == ZIR_INST_IS_NON_ERR_PTR); // breakResultInfo (AstGen.zig:6046-6058). bool need_rl = nodesNeedRlContains(ag, node); ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl); bool need_result_rvalue = (break_rl.tag != rl.tag); // Create block_scope (AstGen.zig:6062-6063). GenZir block_scope = makeSubBlock(gz, scope); // Evaluate operand in block_scope (AstGen.zig:6066-6074). ResultLoc operand_rl; if (RL_IS_REF(break_rl)) { operand_rl = RL_REF_VAL; } else { operand_rl = RL_NONE_VAL; } if (do_err_trace) { operand_rl.ctx = RI_CTX_ERROR_HANDLING_EXPR; } uint32_t operand = exprRl(&block_scope, &block_scope.base, operand_rl, nd.lhs); // Check condition in block_scope (AstGen.zig:6075). uint32_t condition = addUnNode(&block_scope, cond_op, operand, node); // condbr in block_scope (AstGen.zig:6076). uint32_t condbr = addCondBr(&block_scope, ZIR_INST_CONDBR, node); // Create block in parent gz (AstGen.zig:6078-6081). uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node); setBlockBody(ag, &block_scope, block_inst); // block_scope unstacked now. gzAppendInstruction(gz, block_inst); // Then branch: unwrap payload (AstGen.zig:6083-6092). GenZir then_scope = makeSubBlock(&block_scope, scope); uint32_t unwrapped = addUnNode(&then_scope, unwrap_op, operand, node); // Apply rvalue coercion unless rl is ref/ref_coerced_ty // (AstGen.zig:6088-6091). uint32_t then_result = (rl.tag == RL_REF || rl.tag == RL_REF_COERCED_TY) ? unwrapped : rvalue(&then_scope, break_rl, unwrapped, node); addBreak(&then_scope, ZIR_INST_BREAK, block_inst, then_result, (int32_t)node - (int32_t)gz->decl_node_index); // Else branch: evaluate RHS (AstGen.zig:6094-6131). GenZir else_scope = makeSubBlock(&block_scope, scope); // save_err_ret_index (AstGen.zig:6099-6100). if (do_err_trace && nodeMayAppendToErrorTrace(tree, nd.lhs)) addSaveErrRetIndex(&else_scope, ZIR_REF_NONE); // Use fullBodyExpr (not expr) to inline unlabeled blocks // (AstGen.zig:6125). uint32_t else_result = fullBodyExpr(&else_scope, &else_scope.base, break_rl, nd.rhs); if (!endsWithNoReturn(&else_scope)) { // restoreErrRetIndex (AstGen.zig:6128-6129). if (do_err_trace) restoreErrRetIndex( &else_scope, block_inst, break_rl, nd.rhs, else_result); addBreak(&else_scope, ZIR_INST_BREAK, block_inst, else_result, (int32_t)nd.rhs - (int32_t)gz->decl_node_index); } setCondBrPayload(ag, condbr, condition, &then_scope, &else_scope); // AstGen.zig:6137-6141. if (need_result_rvalue) return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node); return block_inst + ZIR_REF_START_INDEX; } // --- whileExpr (AstGen.zig:6529-6817) --- // Handles while_simple, while_cont, while. // Structure: loop { cond_block { cond, condbr }, repeat } // condbr → then { [payload], continue_block { [cont_expr], body, // break continue }, break cond } // → else { [err_capture], else_body / break loop } static uint32_t whileExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_statement) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag node_tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // Compute break_rl from rl (AstGen.zig:6540-6548). bool need_rl = nodesNeedRlContains(ag, node); ResultLoc break_rl = breakResultInfo(gz, rl, node, need_rl); bool need_result_rvalue = (break_rl.tag != rl.tag); // Detect inline keyword (AstGen.zig:6558). uint32_t main_token = tree->nodes.main_tokens[node]; bool is_inline = (main_token > 0 && tree->tokens.tags[main_token - 1] == TOKEN_KEYWORD_INLINE); // Compute label_token (AstGen.zig fullWhileComponents:2310-2317). uint32_t label_token = UINT32_MAX; { uint32_t tok_i = main_token; if (is_inline) tok_i = main_token - 1; if (tok_i >= 2 && tree->tokens.tags[tok_i - 2] == TOKEN_IDENTIFIER && tree->tokens.tags[tok_i - 1] == TOKEN_COLON) label_token = tok_i - 2; } // Extract AST nodes depending on node type (Ast.zig:1925-1958). uint32_t cond_node = nd.lhs; uint32_t body_node; uint32_t else_node = 0; uint32_t cont_expr = 0; // 0 = none if (node_tag == AST_NODE_WHILE_SIMPLE) { body_node = nd.rhs; } else if (node_tag == AST_NODE_WHILE_CONT) { // WhileCont[rhs]: { cont_expr, then_expr } cont_expr = tree->extra_data.arr[nd.rhs]; body_node = tree->extra_data.arr[nd.rhs + 1]; } else { // AST_NODE_WHILE: While[rhs]: { cont_expr, then_expr, else_expr } // cont_expr is stored via OPT(): UINT32_MAX means none. uint32_t raw_cont = tree->extra_data.arr[nd.rhs]; cont_expr = (raw_cont == UINT32_MAX) ? 0 : raw_cont; body_node = tree->extra_data.arr[nd.rhs + 1]; else_node = tree->extra_data.arr[nd.rhs + 2]; } // Detect payload capture (Ast.zig fullWhileComponents:2318-2321). uint32_t payload_token = 0; { uint32_t last_cond_tok = lastToken(tree, cond_node); if (last_cond_tok + 2 < tree->tokens.len && tree->tokens.tags[last_cond_tok + 2] == TOKEN_PIPE) { payload_token = last_cond_tok + 3; } } // Detect error token (Ast.zig fullWhileComponents:2322-2329). uint32_t error_token = 0; if (else_node != 0) { uint32_t else_tok = lastToken(tree, body_node) + 1; if (else_tok + 1 < tree->tokens.len && tree->tokens.tags[else_tok + 1] == TOKEN_PIPE) { error_token = else_tok + 2; } } // Detect payload_is_ref (AstGen.zig:6574-6577). bool payload_is_ref = (payload_token != 0 && tree->tokens.tags[payload_token] == TOKEN_ASTERISK); // Create loop instruction (AstGen.zig:6562-6564). ZirInstTag loop_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_LOOP; uint32_t loop_inst = makeBlockInst(ag, loop_tag, gz, node); gzAppendInstruction(gz, loop_inst); GenZir loop_scope = makeSubBlock(gz, scope); loop_scope.is_inline = is_inline; loop_scope.break_result_info = break_rl; // Evaluate condition in cond_scope (AstGen.zig:6571-6607). GenZir cond_scope = makeSubBlock(gz, &loop_scope.base); // Emit debug node for the condition expression (AstGen.zig:6579). emitDbgNode(gz, cond_node); uint32_t cond_inst; // the value (optional/err-union/bool) uint32_t bool_bit; // the boolean for condbr if (error_token != 0) { // Error union condition (AstGen.zig:6584-6591). ResultLoc cond_rl = payload_is_ref ? RL_REF_VAL : RL_NONE_VAL; cond_inst = fullBodyExpr(&cond_scope, &cond_scope.base, cond_rl, cond_node); ZirInstTag cond_tag = payload_is_ref ? ZIR_INST_IS_NON_ERR_PTR : ZIR_INST_IS_NON_ERR; bool_bit = addUnNode(&cond_scope, cond_tag, cond_inst, cond_node); } else if (payload_token != 0) { // Optional condition (AstGen.zig:6592-6599). ResultLoc cond_rl = payload_is_ref ? RL_REF_VAL : RL_NONE_VAL; cond_inst = fullBodyExpr(&cond_scope, &cond_scope.base, cond_rl, cond_node); ZirInstTag cond_tag = payload_is_ref ? ZIR_INST_IS_NON_NULL_PTR : ZIR_INST_IS_NON_NULL; bool_bit = addUnNode(&cond_scope, cond_tag, cond_inst, cond_node); } else { // Bool condition (AstGen.zig:6600-6606). ResultLoc coerced_bool_ri = { .tag = RL_COERCED_TY, .data = ZIR_REF_BOOL_TYPE, .src_node = 0, .ctx = RI_CTX_NONE, }; cond_inst = fullBodyExpr( &cond_scope, &cond_scope.base, coerced_bool_ri, cond_node); bool_bit = cond_inst; } // Create condbr + cond_block (AstGen.zig:6609-6615). ZirInstTag condbr_tag = is_inline ? ZIR_INST_CONDBR_INLINE : ZIR_INST_CONDBR; uint32_t condbr = addCondBr(&cond_scope, condbr_tag, node); ZirInstTag block_tag = is_inline ? ZIR_INST_BLOCK_INLINE : ZIR_INST_BLOCK; uint32_t cond_block = makeBlockInst(ag, block_tag, &loop_scope, node); setBlockBody(ag, &cond_scope, cond_block); // unstacks cond_scope gzAppendInstruction(&loop_scope, cond_block); // Payload handling (AstGen.zig:6623-6690). // Create payload instructions in the global inst array but don't add to // any scope's scratch area yet. then_scope and continue_scope are created // after loop_scope is finalized (to avoid scratch array overlap). uint32_t dbg_var_name = 0; uint32_t dbg_var_inst = 0; uint32_t opt_payload_raw_inst = UINT32_MAX; // raw inst index, not ref uint32_t payload_ref = 0; uint32_t payload_ident_token = 0; uint32_t payload_ident_name = 0; bool payload_has_scope = false; // true if we need a ScopeLocalVal if (error_token != 0 && payload_token != 0) { // Error union with payload: makeUnNode (AstGen.zig:6628-6655). ZirInstTag unwrap_tag = payload_is_ref ? ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE_PTR : ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE; ensureInstCapacity(ag, 1); uint32_t raw_idx = ag->inst_len; ag->inst_tags[raw_idx] = unwrap_tag; ZirInstData d; d.un_node.src_node = (int32_t)cond_node - (int32_t)gz->decl_node_index; d.un_node.operand = cond_inst; ag->inst_datas[raw_idx] = d; ag->inst_len++; payload_ref = raw_idx + ZIR_REF_START_INDEX; opt_payload_raw_inst = raw_idx; payload_ident_token = payload_token + (payload_is_ref ? 1u : 0u); if (tokenIsUnderscore(tree, payload_ident_token)) { if (payload_is_ref) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } } else { payload_ident_name = identAsString(ag, payload_ident_token); payload_has_scope = true; } } else if (error_token != 0) { // Error union without payload (AstGen.zig:6656-6658). ensureInstCapacity(ag, 1); uint32_t raw_idx = ag->inst_len; ag->inst_tags[raw_idx] = ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID; ZirInstData d; d.un_node.src_node = (int32_t)node - (int32_t)gz->decl_node_index; d.un_node.operand = cond_inst; ag->inst_datas[raw_idx] = d; ag->inst_len++; opt_payload_raw_inst = raw_idx; } else if (payload_token != 0) { // Optional with payload: makeUnNode (AstGen.zig:6660-6686). ZirInstTag unwrap_tag = payload_is_ref ? ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE_PTR : ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE; ensureInstCapacity(ag, 1); uint32_t raw_idx = ag->inst_len; ag->inst_tags[raw_idx] = unwrap_tag; ZirInstData d; d.un_node.src_node = (int32_t)cond_node - (int32_t)gz->decl_node_index; d.un_node.operand = cond_inst; ag->inst_datas[raw_idx] = d; ag->inst_len++; payload_ref = raw_idx + ZIR_REF_START_INDEX; opt_payload_raw_inst = raw_idx; payload_ident_token = payload_token + (payload_is_ref ? 1u : 0u); if (tokenIsUnderscore(tree, payload_ident_token)) { if (payload_is_ref) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } } else { payload_ident_name = identAsString(ag, payload_ident_token); payload_has_scope = true; } } // Create continue_block (AstGen.zig:6695). // makeBlockInst doesn't add to scratch. uint32_t continue_block_inst = makeBlockInst(ag, block_tag, gz, node); // Add repeat to loop_scope (AstGen.zig:6697-6698). { ZirInstTag repeat_tag = is_inline ? ZIR_INST_REPEAT_INLINE : ZIR_INST_REPEAT; ZirInstData repeat_data; memset(&repeat_data, 0, sizeof(repeat_data)); repeat_data.node = (int32_t)node - (int32_t)loop_scope.decl_node_index; addInstruction(&loop_scope, repeat_tag, repeat_data); } // Set loop body and configure break/continue (AstGen.zig:6700-6708). setBlockBody(ag, &loop_scope, loop_inst); // unstacks loop_scope loop_scope.break_block = loop_inst; loop_scope.continue_block = continue_block_inst; if (label_token != UINT32_MAX) { loop_scope.label_token = label_token; loop_scope.label_block_inst = loop_inst; } // Now create then_scope (AstGen.zig:6617-6621, 6711). // loop_scope is unstacked, scratch is clean. GenZir then_scope = makeSubBlock(gz, &cond_scope.base); Scope* then_sub_scope = &then_scope.base; ScopeLocalVal payload_val_scope; memset(&payload_val_scope, 0, sizeof(payload_val_scope)); if (payload_has_scope) { payload_val_scope = (ScopeLocalVal) { .base = { .tag = SCOPE_LOCAL_VAL }, .parent = &then_scope.base, .gen_zir = &then_scope, .inst = payload_ref, .token_src = payload_ident_token, .name = payload_ident_name, }; dbg_var_name = payload_ident_name; dbg_var_inst = payload_ref; then_sub_scope = &payload_val_scope.base; } // Add payload instruction to then_scope (AstGen.zig:6714-6716). if (opt_payload_raw_inst != UINT32_MAX) gzAppendInstruction(&then_scope, opt_payload_raw_inst); // Add dbg_var_val for payload (AstGen.zig:6717). if (dbg_var_name != 0) addDbgVar( &then_scope, ZIR_INST_DBG_VAR_VAL, dbg_var_name, dbg_var_inst); // Add continue_block to then_scope (AstGen.zig:6718). gzAppendInstruction(&then_scope, continue_block_inst); // Emit continue expression if present (AstGen.zig:6723-6725). // Upstream: unusedResultExpr = emitDbgNode + expr + addEnsureResult. if (cont_expr != 0) { emitDbgNode(&then_scope, cont_expr); uint32_t cont_result = expr(&then_scope, then_sub_scope, cont_expr); addEnsureResult(&then_scope, cont_result, cont_expr); } // Create continue_scope (AstGen.zig:6692-6694, 6727). GenZir continue_scope = makeSubBlock(gz, then_sub_scope); // Execute body (AstGen.zig:6728-6731). uint32_t then_node = body_node; emitDbgNode(&continue_scope, then_node); uint32_t unused_result = fullBodyExpr( &continue_scope, &continue_scope.base, RL_NONE_VAL, then_node); addEnsureResult(&continue_scope, unused_result, then_node); // Break continue_block if not noreturn (AstGen.zig:6735-6747). ZirInstTag break_tag = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; if (!endsWithNoReturn(&continue_scope)) { // dbg_stmt + dbg_empty_stmt (AstGen.zig:6736-6745). advanceSourceCursor( ag, tree->tokens.starts[lastToken(tree, then_node)]); emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column); { ZirInstData ext_data; ext_data.extended.opcode = (uint16_t)ZIR_EXT_DBG_EMPTY_STMT; ext_data.extended.small = 0; ext_data.extended.operand = 0; addInstruction(gz, ZIR_INST_EXTENDED, ext_data); } addBreak(&continue_scope, break_tag, continue_block_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); } setBlockBody(ag, &continue_scope, continue_block_inst); // Break cond_block from then_scope (AstGen.zig:6749). addBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); // Else scope (AstGen.zig:6751-6797). GenZir else_scope = makeSubBlock(gz, &cond_scope.base); if (else_node != 0) { Scope* else_sub_scope = &else_scope.base; ScopeLocalVal error_val_scope; memset(&error_val_scope, 0, sizeof(error_val_scope)); if (error_token != 0) { // Error capture: else |err| (AstGen.zig:6756-6776). ZirInstTag err_tag = payload_is_ref ? ZIR_INST_ERR_UNION_CODE_PTR : ZIR_INST_ERR_UNION_CODE; uint32_t err_inst = addUnNode(&else_scope, err_tag, cond_inst, cond_node); if (tokenIsUnderscore(tree, error_token)) { // Discard |_| — else_sub_scope stays as &else_scope.base } else { uint32_t err_name = identAsString(ag, error_token); error_val_scope = (ScopeLocalVal) { .base = { .tag = SCOPE_LOCAL_VAL }, .parent = &else_scope.base, .gen_zir = &else_scope, .inst = err_inst, .token_src = error_token, .name = err_name, }; addDbgVar( &else_scope, ZIR_INST_DBG_VAR_VAL, err_name, err_inst); else_sub_scope = &error_val_scope.base; } } // Remove continue/break blocks so control flow applies to outer // loops (AstGen.zig:6783-6784). loop_scope.continue_block = UINT32_MAX; loop_scope.break_block = UINT32_MAX; uint32_t else_result = fullBodyExpr(&else_scope, else_sub_scope, loop_scope.break_result_info, else_node); if (is_statement) { addEnsureResult(&else_scope, else_result, else_node); } if (!endsWithNoReturn(&else_scope)) { addBreak(&else_scope, break_tag, loop_inst, else_result, (int32_t)else_node - (int32_t)gz->decl_node_index); } } else { // No else: break with void (AstGen.zig:6794-6796). uint32_t void_result = rvalue(&else_scope, rl, ZIR_REF_VOID_VALUE, node); addBreak(&else_scope, break_tag, loop_inst, void_result, AST_NODE_OFFSET_NONE); } // Wire up condbr (AstGen.zig:6805). setCondBrPayload(ag, condbr, bool_bit, &then_scope, &else_scope); // Apply rvalue if needed (AstGen.zig:6807-6810). uint32_t result; if (need_result_rvalue) result = rvalue(gz, rl, loop_inst + ZIR_REF_START_INDEX, node); else result = loop_inst + ZIR_REF_START_INDEX; // Emit ensure_result_used when used as statement (AstGen.zig:6812-6814). if (is_statement) { addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, result, node); } return result; } // --- switchExpr (AstGen.zig:7625-8117) --- // Handles switch and switch_comma expressions. // Encoding: switch_block pl_node with SwitchBlock extra payload. // Helper: append body instruction with ref_table fixups to pay buffer. // Mirrors appendPossiblyRefdBodyInst (AstGen.zig:13675-13683) but writes // to a dynamically-grown pay buffer instead of extra. static void appendPossiblyRefdBodyInstPay(AstGenCtx* ag, uint32_t body_inst, uint32_t** pay, uint32_t* pay_len, uint32_t* pay_cap) { if (*pay_len >= *pay_cap) { *pay_cap *= 2; uint32_t* p = realloc(*pay, *pay_cap * sizeof(uint32_t)); if (!p) abort(); *pay = p; } (*pay)[(*pay_len)++] = body_inst; uint32_t ref_inst; if (refTableFetchRemove(ag, body_inst, &ref_inst)) { appendPossiblyRefdBodyInstPay(ag, ref_inst, pay, pay_len, pay_cap); } } // Helper: append body with fixups and extra refs to pay buffer. // Mirrors appendBodyWithFixupsExtraRefsArrayList (AstGen.zig:13659-13673). static void appendBodyWithFixupsExtraRefsPay(AstGenCtx* ag, const uint32_t* body, uint32_t body_len, const uint32_t* extra_refs, uint32_t extra_refs_len, uint32_t** pay, uint32_t* pay_len_p, uint32_t* pay_cap_p) { for (uint32_t i = 0; i < extra_refs_len; i++) { uint32_t ref_inst; if (refTableFetchRemove(ag, extra_refs[i], &ref_inst)) { appendPossiblyRefdBodyInstPay( ag, ref_inst, pay, pay_len_p, pay_cap_p); } } for (uint32_t i = 0; i < body_len; i++) { appendPossiblyRefdBodyInstPay(ag, body[i], pay, pay_len_p, pay_cap_p); } } // Helper: ensure pay buffer has capacity for `additional` more entries. static void ensurePayCapacity( uint32_t** pay, uint32_t* pay_cap, uint32_t pay_len, uint32_t additional) { uint32_t needed = pay_len + additional; if (needed > *pay_cap) { while (*pay_cap < needed) *pay_cap *= 2; uint32_t* p = realloc(*pay, *pay_cap * sizeof(uint32_t)); if (!p) abort(); *pay = p; } } // Helper: get values for a switch case node. For SWITCH_CASE_ONE / // SWITCH_CASE_INLINE_ONE, returns the single value (or NULL if else). // For SWITCH_CASE / SWITCH_CASE_INLINE, returns the SubRange values. // Returns values count and sets *values_arr to point to the values. // For _ONE variants, writes to single_buf and returns pointer to it. static uint32_t switchCaseValues(const Ast* tree, uint32_t case_node, uint32_t* single_buf, const uint32_t** values_arr) { AstNodeTag ct = tree->nodes.tags[case_node]; AstData cd = tree->nodes.datas[case_node]; switch (ct) { case AST_NODE_SWITCH_CASE_ONE: case AST_NODE_SWITCH_CASE_INLINE_ONE: if (cd.lhs == 0) { *values_arr = NULL; return 0; // else prong } *single_buf = cd.lhs; *values_arr = single_buf; return 1; case AST_NODE_SWITCH_CASE: case AST_NODE_SWITCH_CASE_INLINE: { uint32_t ist = tree->extra_data.arr[cd.lhs]; uint32_t ien = tree->extra_data.arr[cd.lhs + 1]; *values_arr = tree->extra_data.arr + ist; return ien - ist; } default: *values_arr = NULL; return 0; } } static uint32_t switchExpr( GenZir* parent_gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = parent_gz->astgen; const Ast* tree = ag->tree; bool need_rl = nodesNeedRlContains(ag, node); ResultLoc break_rl = breakResultInfo(parent_gz, rl, node, need_rl); AstData nd = tree->nodes.datas[node]; // Detect label (AstGen.zig:7652-7654 / Ast.zig switchFull). uint32_t main_token = tree->nodes.main_tokens[node]; uint32_t label_token = UINT32_MAX; // none if (tree->tokens.tags[main_token] == TOKEN_IDENTIFIER) { label_token = main_token; // switch_token = main_token + 2 (skip label + colon) } // AST_NODE_SWITCH: lhs = condition node, rhs = extra index for SubRange. // SubRange[rhs] = { cases_start, cases_end }. // Case nodes are at extra_data[cases_start..cases_end]. uint32_t cond_node = nd.lhs; uint32_t extra_idx = nd.rhs; uint32_t cases_start = tree->extra_data.arr[extra_idx]; uint32_t cases_end = tree->extra_data.arr[extra_idx + 1]; const uint32_t* case_nodes_arr = tree->extra_data.arr + cases_start; uint32_t case_count = cases_end - cases_start; // --- First pass: categorize cases (AstGen.zig:7659-7762) --- bool any_payload_is_ref = false; bool any_has_tag_capture = false; bool any_non_inline_capture = false; uint32_t scalar_cases_len = 0; uint32_t multi_cases_len = 0; bool has_else = false; // Underscore prong tracking (AstGen.zig:7667-7670). uint32_t underscore_case_idx = UINT32_MAX; // index into case_nodes_arr uint32_t underscore_node = UINT32_MAX; // the `_` value node // underscore_additional_items: 0=none, 2=under, 4=under_one_item, // 6=under_many_items (matching SpecialProngs bit patterns). uint32_t underscore_additional_items = 2; // .under for (uint32_t ci = 0; ci < case_count; ci++) { uint32_t cn = case_nodes_arr[ci]; AstNodeTag ct = tree->nodes.tags[cn]; AstData cd = tree->nodes.datas[cn]; bool is_inline = (ct == AST_NODE_SWITCH_CASE_INLINE_ONE || ct == AST_NODE_SWITCH_CASE_INLINE); // Check payload token for ref/tag capture (AstGen.zig:7673-7689). uint32_t arrow_token = tree->nodes.main_tokens[cn]; // => if (tree->tokens.tags[arrow_token + 1] == TOKEN_PIPE) { uint32_t payload_token = arrow_token + 2; uint32_t ident = payload_token; if (tree->tokens.tags[payload_token] == TOKEN_ASTERISK) { any_payload_is_ref = true; ident = payload_token + 1; } if (tree->tokens.tags[ident + 1] == TOKEN_COMMA) { any_has_tag_capture = true; } if (!tokenIsUnderscore(tree, ident)) { any_non_inline_capture = true; } } // Get values for this case. uint32_t single_buf; const uint32_t* values; uint32_t values_len = switchCaseValues(tree, cn, &single_buf, &values); // Check for else prong (values_len == 0) (AstGen.zig:7693-7711). if (values_len == 0) { has_else = true; continue; } // Check for '_' prong (AstGen.zig:7714-7752). bool case_has_underscore = false; for (uint32_t vi = 0; vi < values_len; vi++) { uint32_t val = values[vi]; if (tree->nodes.tags[val] == AST_NODE_IDENTIFIER && isUnderscoreIdent(tree, val)) { underscore_case_idx = ci; underscore_node = val; switch (values_len) { case 1: underscore_additional_items = 2; // .under break; case 2: underscore_additional_items = 4; // .under_one_item break; default: underscore_additional_items = 6; // .under_many_items break; } case_has_underscore = true; } } if (case_has_underscore) continue; // Categorize as scalar or multi (AstGen.zig:7754-7758). if (values_len == 1 && tree->nodes.tags[values[0]] != AST_NODE_SWITCH_RANGE) { scalar_cases_len++; } else { multi_cases_len++; } (void)is_inline; // inline_cases_len tracking skipped (issue 13) (void)cd; } // Compute special_prongs (AstGen.zig:7764-7770). // SpecialProngs is a 3-bit field: // bit 0: has_else // bits 1-2: underscore variant (0=none, 1=under, 2=under_one_item, // 3=under_many_items) bool has_under = (underscore_case_idx != UINT32_MAX); uint32_t special_prongs; // 3-bit SpecialProngs enum value { uint32_t else_bit = has_else ? 1u : 0u; uint32_t under_bits = has_under ? underscore_additional_items : 0u; special_prongs = else_bit | under_bits; } // Operand result info (AstGen.zig:7772). ResultLoc operand_ri = any_payload_is_ref ? RL_REF_VAL : RL_NONE_VAL; // Save operand source location before evaluating (AstGen.zig:7774-7775). advanceSourceCursorToNode(ag, cond_node); uint32_t operand_lc_line = ag->source_line - parent_gz->decl_line; uint32_t operand_lc_col = ag->source_column; // Evaluate switch operand (AstGen.zig:7777). uint32_t raw_operand = exprRl(parent_gz, scope, operand_ri, cond_node); // Compute typeof for labeled switch continue support // (AstGen.zig:7782-7784). uint32_t raw_operand_ty_ref = 0; if (label_token != UINT32_MAX) { raw_operand_ty_ref = addUnNode(parent_gz, ZIR_INST_TYPEOF, raw_operand, cond_node); } // Sema expects a dbg_stmt immediately before switch_block(_ref) // (AstGen.zig:7806). emitDbgStmtForceCurrentIndex(parent_gz, operand_lc_line, operand_lc_col); // Create switch_block instruction (AstGen.zig:7808-7809). ZirInstTag switch_tag = any_payload_is_ref ? ZIR_INST_SWITCH_BLOCK_REF : ZIR_INST_SWITCH_BLOCK; uint32_t switch_inst = makeBlockInst(ag, switch_tag, parent_gz, node); // Set up block_scope (AstGen.zig:7800-7826). GenZir block_scope = makeSubBlock(parent_gz, scope); block_scope.instructions_top = UINT32_MAX; // unstacked block_scope.break_result_info = break_rl; // Label support (AstGen.zig:7811-7826). if (label_token != UINT32_MAX) { block_scope.continue_block = switch_inst; // continue_result_info: for ref, use ref_coerced_ty; else coerced_ty // (AstGen.zig:7813-7818). if (any_payload_is_ref) { block_scope.break_result_info = (ResultLoc) { .tag = RL_REF_COERCED_TY, .data = raw_operand_ty_ref, .src_node = 0, .ctx = RI_CTX_NONE }; } // Note: we store continue_result_info as break_result_info on // block_scope for now; the label's block_inst is switch_inst. block_scope.label_token = label_token; block_scope.label_block_inst = switch_inst; } // Allocate shared value_placeholder for tag captures // (AstGen.zig:7833-7844). uint32_t tag_inst = 0; if (any_has_tag_capture) { tag_inst = ag->inst_len; ensureInstCapacity(ag, 1); ag->inst_tags[tag_inst] = ZIR_INST_EXTENDED; ZirInstData tdata; memset(&tdata, 0, sizeof(tdata)); tdata.extended.opcode = (uint16_t)ZIR_EXT_VALUE_PLACEHOLDER; ag->inst_datas[tag_inst] = tdata; ag->inst_len++; } // Case scope — re-used for all cases (AstGen.zig:7829-7830). GenZir case_scope = makeSubBlock(parent_gz, &block_scope.base); case_scope.instructions_top = UINT32_MAX; // unstacked initially // --- Payload buffer (AstGen.zig:7789-7798) --- // Table layout: [else?] [under?] [scalar_0..N] [multi_0..N] uint32_t else_tbl = 0; uint32_t under_tbl = (has_else ? 1u : 0u); uint32_t scalar_tbl = under_tbl + (has_under ? 1u : 0u); uint32_t multi_tbl = scalar_tbl + scalar_cases_len; uint32_t table_size = multi_tbl + multi_cases_len; uint32_t pay_cap = table_size + case_count * 16; if (pay_cap < 64) pay_cap = 64; uint32_t* pay = malloc(pay_cap * sizeof(uint32_t)); uint32_t pay_len = table_size; uint32_t scalar_ci = 0; uint32_t multi_ci = 0; // --- Second pass: emit items and bodies (AstGen.zig:7849-8027) --- for (uint32_t ci = 0; ci < case_count; ci++) { uint32_t cn = case_nodes_arr[ci]; AstNodeTag ct = tree->nodes.tags[cn]; AstData cd = tree->nodes.datas[cn]; bool is_inline = (ct == AST_NODE_SWITCH_CASE_INLINE_ONE || ct == AST_NODE_SWITCH_CASE_INLINE); // Get values for this case. uint32_t single_buf; const uint32_t* values; uint32_t values_len = switchCaseValues(tree, cn, &single_buf, &values); // Determine if this is the else, underscore, scalar, or multi case. bool is_else_case = (values_len == 0); bool is_underscore_case = (ci == underscore_case_idx); bool is_multi_case = false; if (!is_else_case && !is_underscore_case) { is_multi_case = (values_len > 1 || (values_len == 1 && tree->nodes.tags[values[0]] == AST_NODE_SWITCH_RANGE)); } // Parse payload token (AstGen.zig:7855-7921). uint32_t dbg_var_name = 0; // NullTerminatedString, 0 = empty uint32_t dbg_var_inst = 0; uint32_t dbg_var_tag_name = 0; uint32_t dbg_var_tag_inst = 0; bool has_tag_capture = false; ScopeLocalVal capture_val_scope; ScopeLocalVal tag_scope_val; memset(&capture_val_scope, 0, sizeof(capture_val_scope)); memset(&tag_scope_val, 0, sizeof(tag_scope_val)); uint32_t capture = 0; // 0=none, 1=by_val, 2=by_ref uint32_t arrow_token = tree->nodes.main_tokens[cn]; bool has_payload = (tree->tokens.tags[arrow_token + 1] == TOKEN_PIPE); Scope* sub_scope = &case_scope.base; if (has_payload) { uint32_t payload_token = arrow_token + 2; bool capture_is_ref = (tree->tokens.tags[payload_token] == TOKEN_ASTERISK); uint32_t ident = payload_token + (capture_is_ref ? 1u : 0u); capture = capture_is_ref ? 2u : 1u; // by_ref : by_val if (tokenIsUnderscore(tree, ident)) { // Discard capture (AstGen.zig:7874-7878). if (capture_is_ref) { SET_ERROR(ag); free(pay); return ZIR_REF_VOID_VALUE; } capture = 0; // none // sub_scope stays as &case_scope.base } else { // Named capture (AstGen.zig:7880-7892). uint32_t capture_name = identAsString(ag, ident); capture_val_scope = (ScopeLocalVal) { .base = { .tag = SCOPE_LOCAL_VAL }, .parent = &case_scope.base, .gen_zir = &case_scope, .inst = switch_inst + ZIR_REF_START_INDEX, .token_src = ident, .name = capture_name, }; dbg_var_name = capture_name; dbg_var_inst = switch_inst + ZIR_REF_START_INDEX; sub_scope = &capture_val_scope.base; } // Check for tag capture: ident followed by comma // (AstGen.zig:7895-7921). if (tree->tokens.tags[ident + 1] == TOKEN_COMMA) { uint32_t tag_token = ident + 2; uint32_t tag_name = identAsString(ag, tag_token); has_tag_capture = true; tag_scope_val = (ScopeLocalVal) { .base = { .tag = SCOPE_LOCAL_VAL }, .parent = sub_scope, .gen_zir = &case_scope, .inst = tag_inst + ZIR_REF_START_INDEX, .token_src = tag_token, .name = tag_name, }; dbg_var_tag_name = tag_name; dbg_var_tag_inst = tag_inst + ZIR_REF_START_INDEX; sub_scope = &tag_scope_val.base; } } ensurePayCapacity(&pay, &pay_cap, pay_len, 32); uint32_t hdr = pay_len; uint32_t prong_info_slot = 0; // Determine case kind and fill item data (AstGen.zig:7924-7995). if (is_underscore_case && is_multi_case) { // Underscore case with additional items as multi // (AstGen.zig:7926-7942). pay[under_tbl] = hdr; if (underscore_additional_items == 4) { // One additional item (AstGen.zig:7928-7937). // [item_ref, prong_info] uint32_t item_node; if (values[0] == underscore_node) item_node = values[1]; else item_node = values[0]; pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, item_node, COMPTIME_REASON_SWITCH_ITEM); prong_info_slot = pay_len++; } else { // Many additional items: multi format // (AstGen.zig:7943-7977). uint32_t nitems = 0; uint32_t nranges = 0; for (uint32_t vi = 0; vi < values_len; vi++) { if (values[vi] == underscore_node) continue; if (tree->nodes.tags[values[vi]] == AST_NODE_SWITCH_RANGE) nranges++; else nitems++; } pay[pay_len++] = nitems; pay[pay_len++] = nranges; prong_info_slot = pay_len++; // Non-range items. for (uint32_t vi = 0; vi < values_len; vi++) { if (values[vi] == underscore_node) continue; if (tree->nodes.tags[values[vi]] != AST_NODE_SWITCH_RANGE) { ensurePayCapacity(&pay, &pay_cap, pay_len, 1); pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, values[vi], COMPTIME_REASON_SWITCH_ITEM); } } // Range pairs. for (uint32_t vi = 0; vi < values_len; vi++) { if (values[vi] == underscore_node) continue; if (tree->nodes.tags[values[vi]] == AST_NODE_SWITCH_RANGE) { AstData rng = tree->nodes.datas[values[vi]]; ensurePayCapacity(&pay, &pay_cap, pay_len, 2); pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, rng.lhs, COMPTIME_REASON_SWITCH_ITEM); pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, rng.rhs, COMPTIME_REASON_SWITCH_ITEM); } } } } else if (is_else_case) { // Else prong (AstGen.zig:7978-7981). pay[else_tbl] = hdr; prong_info_slot = pay_len++; } else if (is_underscore_case) { // Underscore-only prong, no additional items // (AstGen.zig:7982-7986). pay[under_tbl] = hdr; prong_info_slot = pay_len++; } else if (!is_multi_case) { // Scalar case (AstGen.zig:7987-7994). pay[scalar_tbl + scalar_ci++] = hdr; pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, values[0], COMPTIME_REASON_SWITCH_ITEM); prong_info_slot = pay_len++; } else { // Multi case (AstGen.zig:7939-7977 non-underscore path). pay[multi_tbl + multi_ci++] = hdr; uint32_t nitems = 0; uint32_t nranges = 0; for (uint32_t vi = 0; vi < values_len; vi++) { if (tree->nodes.tags[values[vi]] == AST_NODE_SWITCH_RANGE) nranges++; else nitems++; } pay[pay_len++] = nitems; pay[pay_len++] = nranges; prong_info_slot = pay_len++; // Non-range items. for (uint32_t vi = 0; vi < values_len; vi++) { if (tree->nodes.tags[values[vi]] != AST_NODE_SWITCH_RANGE) { ensurePayCapacity(&pay, &pay_cap, pay_len, 1); pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, values[vi], COMPTIME_REASON_SWITCH_ITEM); } } // Range pairs. for (uint32_t vi = 0; vi < values_len; vi++) { if (tree->nodes.tags[values[vi]] == AST_NODE_SWITCH_RANGE) { AstData rng = tree->nodes.datas[values[vi]]; ensurePayCapacity(&pay, &pay_cap, pay_len, 2); pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, rng.lhs, COMPTIME_REASON_SWITCH_ITEM); pay[pay_len++] = comptimeExpr(parent_gz, scope, RL_NONE_VAL, rng.rhs, COMPTIME_REASON_SWITCH_ITEM); } } } // Evaluate body (AstGen.zig:7997-8026). { // Temporarily stack case_scope on parent_gz // (AstGen.zig:7998-8000). case_scope.instructions_top = parent_gz->astgen->scratch_inst_len; if (dbg_var_name != 0) { addDbgVar(&case_scope, ZIR_INST_DBG_VAR_VAL, dbg_var_name, dbg_var_inst); } if (dbg_var_tag_name != 0) { addDbgVar(&case_scope, ZIR_INST_DBG_VAR_VAL, dbg_var_tag_name, dbg_var_tag_inst); } uint32_t body_node = cd.rhs; uint32_t result = fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, body_node); if (!refIsNoReturn(parent_gz, result)) { addBreak(&case_scope, ZIR_INST_BREAK, switch_inst, result, (int32_t)body_node - (int32_t)parent_gz->decl_node_index); } uint32_t raw_body_len = gzInstructionsLen(&case_scope); const uint32_t* body = gzInstructionsSlice(&case_scope); // Body fixups with extra refs (AstGen.zig:8016-8025). uint32_t extra_refs[2]; uint32_t extra_refs_len = 0; extra_refs[extra_refs_len++] = switch_inst; if (has_tag_capture) { extra_refs[extra_refs_len++] = tag_inst; } uint32_t body_len = countBodyLenAfterFixupsExtraRefs( ag, body, raw_body_len, extra_refs, extra_refs_len); // Encode ProngInfo (AstGen.zig:8019-8024). uint32_t prong_info = (body_len & 0x0FFFFFFFu) | ((capture & 3u) << 28) | ((is_inline ? 1u : 0u) << 30) | ((has_tag_capture ? 1u : 0u) << 31); pay[prong_info_slot] = prong_info; ensurePayCapacity(&pay, &pay_cap, pay_len, body_len); appendBodyWithFixupsExtraRefsPay(ag, body, raw_body_len, extra_refs, extra_refs_len, &pay, &pay_len, &pay_cap); gzUnstack(&case_scope); } } // Now add switch_block to parent (AstGen.zig:8034). gzAppendInstruction(parent_gz, switch_inst); // --- Serialize to extra in payload order (AstGen.zig:8036-8110) --- ensureExtraCapacity(ag, 2 + (uint32_t)(multi_cases_len > 0 ? 1 : 0) + (any_has_tag_capture ? 1u : 0u) + pay_len - table_size); uint32_t payload_index = ag->extra_len; // SwitchBlock.operand (AstGen.zig:8042). ag->extra[ag->extra_len++] = raw_operand; // SwitchBlock.bits (AstGen.zig:8043-8050). { uint32_t bits = 0; if (multi_cases_len > 0) bits |= 1u; // has_multi_cases (bit 0) bits |= (special_prongs & 7u) << 1; // special_prongs (bits 1-3) if (any_has_tag_capture) bits |= (1u << 4); // any_has_tag_capture (bit 4) if (any_non_inline_capture) bits |= (1u << 5); // any_non_inline_capture (bit 5) // has_continue (bit 6): set if label used for continue. // We don't track used_for_continue precisely, so set it if label // exists (conservative). Skipping precise tracking per issue 9. // Actually: only set if label_token != UINT32_MAX. // The upstream checks block_scope.label.used_for_continue; we // don't track that, so conservatively set it when there's a label. // This is safe: sema handles the flag as an optimization hint. if (label_token != UINT32_MAX) bits |= (1u << 6); // has_continue bits |= (scalar_cases_len & 0x1FFFFFFu) << 7; // scalar_cases_len ag->extra[ag->extra_len++] = bits; } // multi_cases_len (AstGen.zig:8053-8055). if (multi_cases_len > 0) ag->extra[ag->extra_len++] = multi_cases_len; // tag_inst (AstGen.zig:8057-8059). if (any_has_tag_capture) ag->extra[ag->extra_len++] = tag_inst; ag->inst_datas[switch_inst].pl_node.payload_index = payload_index; // Else prong (AstGen.zig:8064-8070). if (has_else) { uint32_t si = pay[else_tbl]; uint32_t prong_info = pay[si]; uint32_t bl = prong_info & 0x0FFFFFFFu; uint32_t end = si + 1 + bl; for (uint32_t i = si; i < end; i++) ag->extra[ag->extra_len++] = pay[i]; } // Underscore prong (AstGen.zig:8071-8093). if (has_under) { uint32_t si = pay[under_tbl]; uint32_t body_len_idx = si; uint32_t end = si; switch (underscore_additional_items) { case 2: // none end += 1; // just prong_info break; case 4: // one additional item body_len_idx = si + 1; end += 2; // item + prong_info break; case 6: // many additional items body_len_idx = si + 2; end += 3 + pay[si] + 2 * pay[si + 1]; // hdr + items + ranges break; default: break; } uint32_t prong_info = pay[body_len_idx]; uint32_t bl = prong_info & 0x0FFFFFFFu; end += bl; for (uint32_t i = si; i < end; i++) ag->extra[ag->extra_len++] = pay[i]; } // Scalar and multi cases (AstGen.zig:8094-8110). for (uint32_t i = 0; i < scalar_cases_len + multi_cases_len; i++) { uint32_t tbl_idx = scalar_tbl + i; uint32_t si = pay[tbl_idx]; uint32_t body_len_idx; uint32_t end = si; if (tbl_idx < multi_tbl) { // Scalar: [item, prong_info, body...] body_len_idx = si + 1; end += 2; } else { // Multi: [items_len, ranges_len, prong_info, items..., ranges...] body_len_idx = si + 2; uint32_t ni = pay[si]; uint32_t nr = pay[si + 1]; end += 3 + ni + nr * 2; } uint32_t prong_info = pay[body_len_idx]; uint32_t bl = prong_info & 0x0FFFFFFFu; end += bl; for (uint32_t j = si; j < end; j++) ag->extra[ag->extra_len++] = pay[j]; } free(pay); // AstGen.zig:8112-8115. bool need_result_rvalue = (break_rl.tag != rl.tag); if (need_result_rvalue) return rvalue(parent_gz, rl, switch_inst + ZIR_REF_START_INDEX, node); return switch_inst + ZIR_REF_START_INDEX; } // --- rvalue (AstGen.zig:11029) --- // Simplified: handles .none and .discard result locations. static uint32_t rvalueDiscard(GenZir* gz, uint32_t result, uint32_t src_node) { // .discard => emit ensure_result_non_error, return .void_value // (AstGen.zig:11071-11074) ZirInstData data; data.un_node.src_node = (int32_t)src_node - (int32_t)gz->decl_node_index; data.un_node.operand = result; addInstruction(gz, ZIR_INST_ENSURE_RESULT_NON_ERROR, data); return ZIR_REF_VOID_VALUE; } // --- emitDbgNode / emitDbgStmt (AstGen.zig:3422, 13713) --- static void emitDbgStmt(GenZir* gz, uint32_t line, uint32_t column) { if (gz->is_comptime) return; // Check if last instruction is already dbg_stmt; if so, update it. // (AstGen.zig:13715-13724) AstGenCtx* ag = gz->astgen; uint32_t gz_len = gzInstructionsLen(gz); if (gz_len > 0) { uint32_t last = gzInstructionsSlice(gz)[gz_len - 1]; if (ag->inst_tags[last] == ZIR_INST_DBG_STMT) { ag->inst_datas[last].dbg_stmt.line = line; ag->inst_datas[last].dbg_stmt.column = column; return; } } ZirInstData data; data.dbg_stmt.line = line; data.dbg_stmt.column = column; addInstruction(gz, ZIR_INST_DBG_STMT, data); } // Mirrors emitDbgStmtForceCurrentIndex (AstGen.zig:13739-13760). static void emitDbgStmtForceCurrentIndex( GenZir* gz, uint32_t line, uint32_t column) { AstGenCtx* ag = gz->astgen; uint32_t gz_len = gzInstructionsLen(gz); if (gz_len > 0 && gzInstructionsSlice(gz)[gz_len - 1] == ag->inst_len - 1) { uint32_t last = ag->inst_len - 1; if (ag->inst_tags[last] == ZIR_INST_DBG_STMT) { ag->inst_datas[last].dbg_stmt.line = line; ag->inst_datas[last].dbg_stmt.column = column; return; } } ZirInstData data; data.dbg_stmt.line = line; data.dbg_stmt.column = column; addInstruction(gz, ZIR_INST_DBG_STMT, data); } static void emitDbgNode(GenZir* gz, uint32_t node) { if (gz->is_comptime) return; AstGenCtx* ag = gz->astgen; advanceSourceCursorToNode(ag, node); uint32_t line = ag->source_line - gz->decl_line; uint32_t column = ag->source_column; emitDbgStmt(gz, line, column); } // --- assign (AstGen.zig:3434) --- // Handles `_ = expr` discard pattern. static void assignStmt(GenZir* gz, Scope* scope, uint32_t infix_node) { emitDbgNode(gz, infix_node); const AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[infix_node]; uint32_t lhs = nd.lhs; uint32_t rhs = nd.rhs; // Check if LHS is `_` identifier for discard (AstGen.zig:3440-3446). if (tree->nodes.tags[lhs] == AST_NODE_IDENTIFIER) { uint32_t ident_tok = tree->nodes.main_tokens[lhs]; uint32_t tok_start = tree->tokens.starts[ident_tok]; if (tree->source[tok_start] == '_' && (tok_start + 1 >= tree->source_len || !((tree->source[tok_start + 1] >= 'a' && tree->source[tok_start + 1] <= 'z') || (tree->source[tok_start + 1] >= 'A' && tree->source[tok_start + 1] <= 'Z') || tree->source[tok_start + 1] == '_' || (tree->source[tok_start + 1] >= '0' && tree->source[tok_start + 1] <= '9')))) { // Discard: evaluate RHS with .discard result location. uint32_t result = expr(gz, scope, rhs); rvalueDiscard(gz, result, rhs); return; } } // Non-discard assignment: evaluate LHS as lvalue, pass ptr rl to RHS. // (AstGen.zig:3448-3452). { uint32_t lhs_ptr = exprRl(gz, scope, RL_REF_VAL, lhs); ResultLoc ptr_rl = { .tag = RL_PTR, .data = lhs_ptr, .src_node = infix_node }; (void)exprRl(gz, scope, ptr_rl, rhs); } } // --- assignOp (AstGen.zig:3731) --- // Handles compound assignment operators (+=, -=, *=, etc.). static void assignOp( GenZir* gz, Scope* scope, uint32_t infix_node, ZirInstTag op_tag) { emitDbgNode(gz, infix_node); AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[infix_node]; uint32_t lhs_node = nd.lhs; uint32_t rhs_node = nd.rhs; // Evaluate LHS as lvalue pointer (AstGen.zig:3742). uint32_t lhs_ptr = exprRl(gz, scope, RL_REF_VAL, lhs_node); // Advance cursor for add/sub/mul/div/mod_rem (AstGen.zig:3744-3747). uint32_t cursor_line = 0, cursor_col = 0; bool need_dbg = false; if (op_tag == ZIR_INST_ADD || op_tag == ZIR_INST_SUB || op_tag == ZIR_INST_MUL || op_tag == ZIR_INST_DIV || op_tag == ZIR_INST_MOD_REM) { if (!gz->is_comptime) { advanceSourceCursorToMainToken(ag, gz, infix_node); } cursor_line = ag->source_line - gz->decl_line; cursor_col = ag->source_column; need_dbg = true; } // Load current value (AstGen.zig:3748). uint32_t lhs = addUnNode(gz, ZIR_INST_LOAD, lhs_ptr, infix_node); // Determine RHS result type (AstGen.zig:3750-3766). uint32_t rhs_res_ty; if (op_tag == ZIR_INST_ADD || op_tag == ZIR_INST_SUB) { // Emit inplace_arith_result_ty extended instruction. uint16_t inplace_op = (op_tag == ZIR_INST_ADD) ? 0 : 1; // add_eq=0, sub_eq=1 ZirInstData ext_data; memset(&ext_data, 0, sizeof(ext_data)); ext_data.extended.opcode = (uint16_t)ZIR_EXT_INPLACE_ARITH_RESULT_TY; ext_data.extended.small = inplace_op; ext_data.extended.operand = lhs; rhs_res_ty = addInstruction(gz, ZIR_INST_EXTENDED, ext_data); } else { rhs_res_ty = addUnNode(gz, ZIR_INST_TYPEOF, lhs, infix_node); } // Evaluate RHS with type coercion (AstGen.zig:3768). uint32_t rhs_raw = expr(gz, scope, rhs_node); uint32_t rhs = addPlNodeBin(gz, ZIR_INST_AS_NODE, rhs_node, rhs_res_ty, rhs_raw); // Emit debug statement for arithmetic ops (AstGen.zig:3770-3775). if (need_dbg) { emitDbgStmt(gz, cursor_line, cursor_col); } // Emit the operation (AstGen.zig:3776-3779). uint32_t result = addPlNodeBin(gz, op_tag, infix_node, lhs, rhs); // Store result back (AstGen.zig:3780-3783). addPlNodeBin(gz, ZIR_INST_STORE_NODE, infix_node, lhs_ptr, result); } // --- assignShift (AstGen.zig:3786) --- // Handles <<= and >>= assignment operators. static void assignShift( GenZir* gz, Scope* scope, uint32_t infix_node, ZirInstTag op_tag) { emitDbgNode(gz, infix_node); const AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[infix_node]; uint32_t lhs_node = nd.lhs; uint32_t rhs_node = nd.rhs; // Evaluate LHS as lvalue pointer (AstGen.zig:3797). uint32_t lhs_ptr = exprRl(gz, scope, RL_REF_VAL, lhs_node); // Load current value (AstGen.zig:3798). uint32_t lhs = addUnNode(gz, ZIR_INST_LOAD, lhs_ptr, infix_node); // RHS type is typeof_log2_int_type of LHS (AstGen.zig:3799). uint32_t rhs_type = addUnNode(gz, ZIR_INST_TYPEOF_LOG2_INT_TYPE, lhs, infix_node); ResultLoc rhs_rl = { .tag = RL_TY, .data = rhs_type, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t rhs = exprRl(gz, scope, rhs_rl, rhs_node); // Emit the shift operation (AstGen.zig:3802-3805). uint32_t result = addPlNodeBin(gz, op_tag, infix_node, lhs, rhs); // Store result back (AstGen.zig:3806-3809). addPlNodeBin(gz, ZIR_INST_STORE_NODE, infix_node, lhs_ptr, result); } // --- assignShiftSat (AstGen.zig:3812) --- // Handles <<|= saturating shift-left assignment. static void assignShiftSat(GenZir* gz, Scope* scope, uint32_t infix_node) { emitDbgNode(gz, infix_node); const AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[infix_node]; uint32_t lhs_node = nd.lhs; uint32_t rhs_node = nd.rhs; // Evaluate LHS as lvalue pointer (AstGen.zig:3818). uint32_t lhs_ptr = exprRl(gz, scope, RL_REF_VAL, lhs_node); // Load current value (AstGen.zig:3819). uint32_t lhs = addUnNode(gz, ZIR_INST_LOAD, lhs_ptr, infix_node); // Saturating shift-left allows any integer type for both LHS and RHS // (AstGen.zig:3820-3821). uint32_t rhs = expr(gz, scope, rhs_node); // Emit shl_sat (AstGen.zig:3823-3825). uint32_t result = addPlNodeBin(gz, ZIR_INST_SHL_SAT, infix_node, lhs, rhs); // Store result back (AstGen.zig:3827-3830). addPlNodeBin(gz, ZIR_INST_STORE_NODE, infix_node, lhs_ptr, result); } // --- builtinEvalToError (BuiltinFn.zig) --- // Returns per-builtin eval_to_error. Default is .never; only a few are // .maybe or .always. Mirrors BuiltinFn.list lookup in AstGen.zig:10539. static int builtinEvalToError(const Ast* tree, uint32_t node) { uint32_t main_tok = tree->nodes.main_tokens[node]; uint32_t tok_start = tree->tokens.starts[main_tok]; const char* source = tree->source; uint32_t name_start = tok_start + 1; // skip '@' uint32_t name_end = name_start; while (name_end < tree->source_len && ((source[name_end] >= 'a' && source[name_end] <= 'z') || (source[name_end] >= 'A' && source[name_end] <= 'Z') || source[name_end] == '_')) { name_end++; } uint32_t name_len = name_end - name_start; const char* name = source + name_start; // clang-format off // .always: if (name_len == 12 && memcmp(name, "errorFromInt", 12) == 0) return 1; // EVAL_TO_ERROR_ALWAYS // .maybe: if (name_len == 2 && memcmp(name, "as", 2) == 0) return 2; if (name_len == 4 && memcmp(name, "call", 4) == 0) return 2; if (name_len == 5 && memcmp(name, "field", 5) == 0) return 2; if (name_len == 9 && memcmp(name, "errorCast", 9) == 0) return 2; // clang-format on // Default: .never return 0; } // --- nodeMayEvalToError (AstGen.zig:10340) --- // Three-way result: 0=never, 1=always, 2=maybe. #define EVAL_TO_ERROR_NEVER 0 #define EVAL_TO_ERROR_ALWAYS 1 #define EVAL_TO_ERROR_MAYBE 2 static int nodeMayEvalToError(const Ast* tree, uint32_t node) { uint32_t n = node; while (true) { AstNodeTag tag = tree->nodes.tags[n]; switch (tag) { case AST_NODE_ERROR_VALUE: return EVAL_TO_ERROR_ALWAYS; // These may evaluate to errors. case AST_NODE_IDENTIFIER: case AST_NODE_FIELD_ACCESS: case AST_NODE_DEREF: case AST_NODE_ARRAY_ACCESS: case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: case AST_NODE_IF_SIMPLE: case AST_NODE_IF: case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: case AST_NODE_ASM_SIMPLE: case AST_NODE_ASM_LEGACY: case AST_NODE_ASM: case AST_NODE_CATCH: case AST_NODE_ORELSE: return EVAL_TO_ERROR_MAYBE; // Forward to sub-expression. case AST_NODE_TRY: case AST_NODE_COMPTIME: case AST_NODE_NOSUSPEND: n = tree->nodes.datas[n].lhs; continue; case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_UNWRAP_OPTIONAL: n = tree->nodes.datas[n].lhs; continue; // Labeled blocks may need a memory location. case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: { uint32_t lbrace = tree->nodes.main_tokens[n]; if (lbrace > 0 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON) return EVAL_TO_ERROR_MAYBE; return EVAL_TO_ERROR_NEVER; } // Builtins: look up per-builtin eval_to_error // (AstGen.zig:10530-10541). case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: return builtinEvalToError(tree, n); // Everything else: .never default: return EVAL_TO_ERROR_NEVER; } } } // --- nodeMayAppendToErrorTrace (AstGen.zig:10315) --- // Returns true if the expression may append to the error return trace. static bool nodeMayAppendToErrorTrace(const Ast* tree, uint32_t node) { uint32_t n = node; while (true) { AstNodeTag tag = tree->nodes.tags[n]; switch (tag) { // These don't call runtime functions. case AST_NODE_ERROR_VALUE: case AST_NODE_IDENTIFIER: case AST_NODE_COMPTIME: return false; // Forward to sub-expression. case AST_NODE_TRY: case AST_NODE_NOSUSPEND: n = tree->nodes.datas[n].lhs; continue; case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_UNWRAP_OPTIONAL: n = tree->nodes.datas[n].lhs; continue; // Anything else: check if it may eval to error. default: return nodeMayEvalToError(tree, n) != EVAL_TO_ERROR_NEVER; } } } // --- addSaveErrRetIndex (AstGen.zig:12556) --- // Emits SAVE_ERR_RET_INDEX instruction. // operand is the init inst ref (or ZIR_REF_NONE for .always). static void addSaveErrRetIndex(GenZir* gz, uint32_t operand) { ZirInstData data; data.save_err_ret_index.operand = operand; data.save_err_ret_index._pad = 0; addInstruction(gz, ZIR_INST_SAVE_ERR_RET_INDEX, data); } // --- addRestoreErrRetIndexBlock (AstGen.zig:12607-12614) --- // Emits extended RESTORE_ERR_RET_INDEX with block target (if_non_error // condition). Payload: src_node, block_ref, operand. static void addRestoreErrRetIndexBlock( GenZir* gz, uint32_t block_inst, uint32_t operand, uint32_t node) { AstGenCtx* ag = gz->astgen; ensureExtraCapacity(ag, 3); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index); ag->extra[ag->extra_len++] = block_inst + ZIR_REF_START_INDEX; ag->extra[ag->extra_len++] = operand; ZirInstData ext_data; ext_data.extended.opcode = (uint16_t)ZIR_EXT_RESTORE_ERR_RET_INDEX; ext_data.extended.small = 0; ext_data.extended.operand = payload_index; addInstruction(gz, ZIR_INST_EXTENDED, ext_data); } // --- restoreErrRetIndex (AstGen.zig:2121-2148) --- // Emits restore_err_ret_index for block target based on nodeMayEvalToError. static void restoreErrRetIndex(GenZir* gz, uint32_t block_inst, ResultLoc rl, uint32_t node, uint32_t result) { const Ast* tree = gz->astgen->tree; int eval = nodeMayEvalToError(tree, node); if (eval == EVAL_TO_ERROR_ALWAYS) return; // never restore/pop uint32_t op; if (eval == EVAL_TO_ERROR_NEVER) { op = ZIR_REF_NONE; // always restore/pop } else { // EVAL_TO_ERROR_MAYBE // Simplified: without ri.ctx, treat non-ptr RL as result // (AstGen.zig:2131-2144). if (rl.tag == RL_PTR) { op = addUnNode(gz, ZIR_INST_LOAD, rl.data, node); } else if (rl.tag == RL_INFERRED_PTR) { op = ZIR_REF_NONE; } else { op = result; } } addRestoreErrRetIndexBlock(gz, block_inst, op, node); } // --- varDecl (AstGen.zig:3189) --- // Handles local const/var declarations. Returns new scope with the variable. // scope_out: set to new scope if variable is added; unchanged otherwise. static void varDecl(GenZir* gz, Scope* scope, uint32_t node, ScopeLocalVal* val_out, ScopeLocalPtr* ptr_out, Scope** scope_out) { AstGenCtx* ag = gz->astgen; emitDbgNode(gz, node); // AstGen.zig:3196 const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[node]; AstNodeTag tag = tree->nodes.tags[node]; uint32_t mut_token = tree->nodes.main_tokens[node]; uint32_t name_token = mut_token + 1; bool is_const = (tree->source[tree->tokens.starts[mut_token]] == 'c'); uint32_t ident_name = identAsString(ag, name_token); // Extract comptime_token by scanning backwards from mut_token // (Ast.zig:2012-2023, fullVarDeclComponents). bool has_comptime_token = false; if (mut_token > 0 && tree->tokens.tags[mut_token - 1] == TOKEN_KEYWORD_COMPTIME) { has_comptime_token = true; } // Extract type_node and init_node based on variant. uint32_t type_node = 0; uint32_t init_node = 0; if (tag == AST_NODE_SIMPLE_VAR_DECL) { // lhs = type (optional), rhs = init (optional). type_node = nd.lhs; init_node = nd.rhs; } else if (tag == AST_NODE_LOCAL_VAR_DECL) { // lhs = extra_data index, rhs = init. // extra: {type_node, align_node, addrspace_node, section_node} // Simplified: just extract type_node. uint32_t extra_idx = nd.lhs; type_node = tree->extra_data.arr[extra_idx]; // type_node init_node = nd.rhs; } else if (tag == AST_NODE_ALIGNED_VAR_DECL) { // lhs = align expr, rhs = init. // No type node in this variant. init_node = nd.rhs; } else { // global_var_decl or unknown — bail. SET_ERROR(ag); return; } if (init_node == 0) { // Variables must be initialized (AstGen.zig:3228). SET_ERROR(ag); return; } if (is_const) { // --- CONST path (AstGen.zig:3232-3340) --- // `comptime const` is a non-fatal error; treat it like the init was // marked `comptime` (AstGen.zig:3234-3239). uint32_t force_comptime = has_comptime_token ? COMPTIME_REASON_COMPTIME_KEYWORD : 0; if (!nodesNeedRlContains(ag, node)) { // Rvalue path (AstGen.zig:3246-3271). // Evaluate type annotation and build result_info // (AstGen.zig:3247-3250). ResultLoc result_info; if (type_node != 0) { uint32_t type_ref = typeExpr(gz, scope, type_node); result_info = (ResultLoc) { .tag = RL_TY, .data = type_ref, .src_node = 0, .ctx = RI_CTX_CONST_INIT }; } else { result_info = (ResultLoc) { .tag = RL_NONE, .data = 0, .src_node = 0, .ctx = RI_CTX_CONST_INIT }; } // Evaluate init expression (AstGen.zig:3251-3252). uint32_t init_ref = reachableExprComptime( gz, scope, result_info, init_node, node, force_comptime); if (ag->has_compile_errors) return; // validate_const (AstGen.zig:3266). addUnNode(gz, ZIR_INST_VALIDATE_CONST, init_ref, init_node); // dbg_var_val (AstGen.zig:3269). addDbgVar(gz, ZIR_INST_DBG_VAR_VAL, ident_name, init_ref); // save_err_ret_index (AstGen.zig:3259-3260). if (nodeMayAppendToErrorTrace(tree, init_node)) addSaveErrRetIndex(gz, init_ref); // Create ScopeLocalVal (AstGen.zig:3276-3284). val_out->base.tag = SCOPE_LOCAL_VAL; val_out->parent = *scope_out; val_out->gen_zir = gz; val_out->inst = init_ref; val_out->token_src = name_token; val_out->name = ident_name; *scope_out = &val_out->base; } else { // Alloc path (AstGen.zig:3277-3340). // The init expression needs a result pointer (nodes_need_rl). bool is_comptime_init = gz->is_comptime || tree->nodes.tags[init_node] == AST_NODE_COMPTIME; uint32_t var_ptr; bool resolve_inferred; if (type_node != 0) { // Typed const: alloc (AstGen.zig:3280). uint32_t type_ref = typeExpr(gz, scope, type_node); var_ptr = addUnNode(gz, ZIR_INST_ALLOC, type_ref, node); resolve_inferred = false; } else { // Inferred type: alloc_inferred (AstGen.zig:3291-3296). ZirInstTag alloc_tag = is_comptime_init ? ZIR_INST_ALLOC_INFERRED_COMPTIME : ZIR_INST_ALLOC_INFERRED; ZirInstData adata; adata.node = (int32_t)node - (int32_t)gz->decl_node_index; var_ptr = addInstruction(gz, alloc_tag, adata); resolve_inferred = true; } // Evaluate init with RL pointing to alloc (AstGen.zig:3313-3316). ResultLoc init_rl; if (type_node != 0) { init_rl.tag = RL_PTR; init_rl.data = var_ptr; init_rl.src_node = 0; // upstream: .none (PtrResultLoc.src_node // defaults to null) } else { init_rl.tag = RL_INFERRED_PTR; init_rl.data = var_ptr; init_rl.src_node = 0; } init_rl.ctx = RI_CTX_CONST_INIT; uint32_t init_ref = reachableExprComptime( gz, scope, init_rl, init_node, node, force_comptime); if (ag->has_compile_errors) return; // save_err_ret_index (AstGen.zig:3320-3321). if (nodeMayAppendToErrorTrace(tree, init_node)) addSaveErrRetIndex(gz, init_ref); // resolve_inferred_alloc or make_ptr_const (AstGen.zig:3323-3326). uint32_t const_ptr; if (resolve_inferred) const_ptr = addUnNode( gz, ZIR_INST_RESOLVE_INFERRED_ALLOC, var_ptr, node); else const_ptr = addUnNode(gz, ZIR_INST_MAKE_PTR_CONST, var_ptr, node); // dbg_var_ptr (AstGen.zig:3328). addDbgVar(gz, ZIR_INST_DBG_VAR_PTR, ident_name, const_ptr); // Create ScopeLocalPtr (AstGen.zig:3330-3340). ptr_out->base.tag = SCOPE_LOCAL_PTR; ptr_out->parent = *scope_out; ptr_out->gen_zir = gz; ptr_out->ptr = const_ptr; ptr_out->token_src = name_token; ptr_out->name = ident_name; ptr_out->maybe_comptime = true; *scope_out = &ptr_out->base; } } else { // --- VAR path (AstGen.zig:3342-3416) --- // comptime_token handling (AstGen.zig:3343-3345). bool is_comptime = has_comptime_token || gz->is_comptime; uint32_t alloc_ref; bool resolve_inferred = false; if (type_node != 0) { // Typed var: alloc_mut (AstGen.zig:3361-3375). uint32_t type_ref = typeExpr(gz, scope, type_node); ZirInstTag alloc_tag = is_comptime ? ZIR_INST_ALLOC_COMPTIME_MUT : ZIR_INST_ALLOC_MUT; alloc_ref = addUnNode(gz, alloc_tag, type_ref, node); } else { // Inferred type var: alloc_inferred_mut // (AstGen.zig:3384-3392). ZirInstTag alloc_tag = is_comptime ? ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT : ZIR_INST_ALLOC_INFERRED_MUT; ZirInstData adata; adata.node = (int32_t)node - (int32_t)gz->decl_node_index; alloc_ref = addInstruction(gz, alloc_tag, adata); resolve_inferred = true; } // Evaluate init with RL pointing to alloc (AstGen.zig:3395-3402). ResultLoc var_init_rl; if (type_node != 0) { var_init_rl.tag = RL_PTR; var_init_rl.data = alloc_ref; var_init_rl.src_node = 0; // upstream: .none (PtrResultLoc.src_node // defaults to null) } else { var_init_rl.tag = RL_INFERRED_PTR; var_init_rl.data = alloc_ref; var_init_rl.src_node = 0; } var_init_rl.ctx = RI_CTX_NONE; uint32_t comptime_reason = has_comptime_token ? COMPTIME_REASON_COMPTIME_KEYWORD : 0; uint32_t init_ref = reachableExprComptime( gz, scope, var_init_rl, init_node, node, comptime_reason); (void)init_ref; if (ag->has_compile_errors) return; // resolve_inferred_alloc if type was inferred // (AstGen.zig:3407-3408). uint32_t final_ptr = alloc_ref; if (resolve_inferred) final_ptr = addUnNode( gz, ZIR_INST_RESOLVE_INFERRED_ALLOC, alloc_ref, node); // dbg_var_ptr (AstGen.zig:3411). addDbgVar(gz, ZIR_INST_DBG_VAR_PTR, ident_name, final_ptr); // Create ScopeLocalPtr (AstGen.zig:3413-3422). ptr_out->base.tag = SCOPE_LOCAL_PTR; ptr_out->parent = *scope_out; ptr_out->gen_zir = gz; ptr_out->ptr = final_ptr; ptr_out->token_src = name_token; ptr_out->name = ident_name; ptr_out->maybe_comptime = is_comptime; *scope_out = &ptr_out->base; } } // --- addEnsureResult (AstGen.zig:2649) --- // After evaluating an expression as a statement, optionally emits // ensure_result_used. For call/field_call, sets flag in extra data instead. // Returns true if the result is noreturn (AstGen.zig:2909). static bool addEnsureResult( GenZir* gz, uint32_t maybe_unused_result, uint32_t statement) { AstGenCtx* ag = gz->astgen; bool elide_check; bool is_noreturn = false; if (maybe_unused_result >= ZIR_REF_START_INDEX) { uint32_t inst = maybe_unused_result - ZIR_REF_START_INDEX; ZirInstTag tag = ag->inst_tags[inst]; switch (tag) { // For call/field_call: set ensure_result_used flag // (bit 3 of flags at offset 0). Flags *must* be at offset 0 // (AstGen.zig:2658-2665, Zir.zig:3022). case ZIR_INST_CALL: case ZIR_INST_FIELD_CALL: { uint32_t pi = ag->inst_datas[inst].pl_node.payload_index; ag->extra[pi] |= (1u << 3); // ensure_result_used elide_check = true; break; } // For builtin_call: ensure_result_used is at bit 1, not bit 3. case ZIR_INST_BUILTIN_CALL: { uint32_t pi = ag->inst_datas[inst].pl_node.payload_index; ag->extra[pi] |= (1u << 1); // ensure_result_used elide_check = true; break; } // Always noreturn → elide (AstGen.zig:2909). case ZIR_INST_BREAK: case ZIR_INST_BREAK_INLINE: case ZIR_INST_CONDBR: case ZIR_INST_CONDBR_INLINE: case ZIR_INST_RET_NODE: case ZIR_INST_RET_LOAD: case ZIR_INST_RET_IMPLICIT: case ZIR_INST_RET_ERR_VALUE: case ZIR_INST_UNREACHABLE: case ZIR_INST_REPEAT: case ZIR_INST_REPEAT_INLINE: case ZIR_INST_PANIC: case ZIR_INST_TRAP: case ZIR_INST_CHECK_COMPTIME_CONTROL_FLOW: case ZIR_INST_SWITCH_CONTINUE: case ZIR_INST_COMPILE_ERROR: is_noreturn = true; elide_check = true; break; // Always void → elide. case ZIR_INST_DBG_STMT: case ZIR_INST_DBG_VAR_PTR: case ZIR_INST_DBG_VAR_VAL: case ZIR_INST_ENSURE_RESULT_USED: case ZIR_INST_ENSURE_RESULT_NON_ERROR: case ZIR_INST_ENSURE_ERR_UNION_PAYLOAD_VOID: case ZIR_INST_EXPORT: case ZIR_INST_SET_EVAL_BRANCH_QUOTA: case ZIR_INST_ATOMIC_STORE: case ZIR_INST_STORE_NODE: case ZIR_INST_STORE_TO_INFERRED_PTR: case ZIR_INST_RESOLVE_INFERRED_ALLOC: case ZIR_INST_SET_RUNTIME_SAFETY: case ZIR_INST_MEMCPY: case ZIR_INST_MEMSET: case ZIR_INST_MEMMOVE: case ZIR_INST_VALIDATE_DEREF: case ZIR_INST_VALIDATE_DESTRUCTURE: case ZIR_INST_SAVE_ERR_RET_INDEX: case ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL: case ZIR_INST_RESTORE_ERR_RET_INDEX_FN_ENTRY: case ZIR_INST_VALIDATE_STRUCT_INIT_TY: case ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY: case ZIR_INST_VALIDATE_PTR_STRUCT_INIT: case ZIR_INST_VALIDATE_ARRAY_INIT_TY: case ZIR_INST_VALIDATE_ARRAY_INIT_RESULT_TY: case ZIR_INST_VALIDATE_PTR_ARRAY_INIT: case ZIR_INST_VALIDATE_REF_TY: case ZIR_INST_VALIDATE_CONST: elide_check = true; break; // Extended: check opcode. case ZIR_INST_EXTENDED: { uint32_t opcode = ag->inst_datas[inst].extended.opcode; elide_check = (opcode == ZIR_EXT_BREAKPOINT || opcode == ZIR_EXT_BRANCH_HINT || opcode == ZIR_EXT_SET_FLOAT_MODE || opcode == ZIR_EXT_DISABLE_INSTRUMENTATION || opcode == ZIR_EXT_DISABLE_INTRINSICS); break; } // Everything else: might produce non-void result → emit check. default: elide_check = false; break; } } else { // Named ref constant. is_noreturn = (maybe_unused_result == ZIR_REF_UNREACHABLE_VALUE); elide_check = (is_noreturn || maybe_unused_result == ZIR_REF_VOID_VALUE); } if (!elide_check) { addUnNode( gz, ZIR_INST_ENSURE_RESULT_USED, maybe_unused_result, statement); } return is_noreturn; } // --- countDefers (AstGen.zig:2966) --- // Walk scope chain and count defer types. static DeferCounts countDefers(const Scope* outer_scope, Scope* inner_scope) { DeferCounts c = { false, false, false, false }; Scope* s = inner_scope; while (s != outer_scope) { switch (s->tag) { case SCOPE_GEN_ZIR: s = ((GenZir*)s)->parent; break; case SCOPE_LOCAL_VAL: s = ((ScopeLocalVal*)s)->parent; break; case SCOPE_LOCAL_PTR: s = ((ScopeLocalPtr*)s)->parent; break; case SCOPE_DEFER_NORMAL: { ScopeDefer* d = (ScopeDefer*)s; s = d->parent; c.have_normal = true; break; } case SCOPE_DEFER_ERROR: { ScopeDefer* d = (ScopeDefer*)s; s = d->parent; c.have_err = true; // need_err_code if remapped_err_code exists (we don't // implement err capture yet, so always false). break; } default: return c; } } c.have_any = c.have_normal || c.have_err; return c; } // --- genDefers (AstGen.zig:3014) --- // Walk scope chain from inner to outer, emitting .defer instructions. // which: DEFER_NORMAL_ONLY or DEFER_BOTH_SANS_ERR. static void genDefers( GenZir* gz, const Scope* outer_scope, Scope* inner_scope, int which) { Scope* s = inner_scope; while (s != outer_scope) { switch (s->tag) { case SCOPE_GEN_ZIR: { GenZir* g = (GenZir*)s; s = g->parent; break; } case SCOPE_LOCAL_VAL: { ScopeLocalVal* lv = (ScopeLocalVal*)s; s = lv->parent; break; } case SCOPE_LOCAL_PTR: { ScopeLocalPtr* lp = (ScopeLocalPtr*)s; s = lp->parent; break; } case SCOPE_DEFER_NORMAL: { ScopeDefer* d = (ScopeDefer*)s; s = d->parent; // Emit ZIR_INST_DEFER (AstGen.zig:3031). ZirInstData data; data.defer_data.index = d->index; data.defer_data.len = d->len; addInstruction(gz, ZIR_INST_DEFER, data); break; } case SCOPE_DEFER_ERROR: { ScopeDefer* d = (ScopeDefer*)s; s = d->parent; if (which == DEFER_BOTH_SANS_ERR) { // Emit regular DEFER for error defers too (AstGen.zig:3038). ZirInstData data; data.defer_data.index = d->index; data.defer_data.len = d->len; addInstruction(gz, ZIR_INST_DEFER, data); } // DEFER_NORMAL_ONLY: skip error defers (AstGen.zig:3063). break; } case SCOPE_LABEL: { // Labels store parent in the GenZir they're attached to. // Just skip by going to the parent scope stored in parent. // Actually labels don't have a separate parent pointer in our // representation; they're part of GenZir. This case shouldn't // appear when walking from blockExprStmts scope. return; } case SCOPE_NAMESPACE: case SCOPE_TOP: default: return; } } } // --- blockExprStmts (AstGen.zig:2538) --- // Processes block statements sequentially, threading scope. static void blockExprStmts(GenZir* gz, Scope* scope, const uint32_t* statements, uint32_t stmt_count) { AstGenCtx* ag = gz->astgen; // Stack-allocated scope storage for local variables and defers. // Max 64 local variable declarations and 64 defers per block. ScopeLocalVal val_scopes[64]; ScopeLocalPtr ptr_scopes[64]; ScopeDefer defer_scopes[64]; uint32_t val_idx = 0; uint32_t ptr_idx = 0; uint32_t defer_idx = 0; Scope* cur_scope = scope; bool noreturn_stmt = false; for (uint32_t i = 0; i < stmt_count; i++) { if (ag->has_compile_errors) return; uint32_t stmt = statements[i]; // Unwrap grouped_expression (parentheses) before dispatching // (AstGen.zig:2569-2630). uint32_t inner_node = stmt; for (;;) { AstNodeTag tag = ag->tree->nodes.tags[inner_node]; switch (tag) { case AST_NODE_ASSIGN: assignStmt(gz, cur_scope, inner_node); break; // Shift assignment operators (AstGen.zig:2585-2586). case AST_NODE_ASSIGN_SHL: assignShift(gz, cur_scope, inner_node, ZIR_INST_SHL); break; case AST_NODE_ASSIGN_SHR: assignShift(gz, cur_scope, inner_node, ZIR_INST_SHR); break; // Saturating shift-left assignment (AstGen.zig:680-682 via expr). case AST_NODE_ASSIGN_SHL_SAT: assignShiftSat(gz, cur_scope, inner_node); break; // Compound assignment operators (AstGen.zig:2588-2607). case AST_NODE_ASSIGN_ADD: assignOp(gz, cur_scope, inner_node, ZIR_INST_ADD); break; case AST_NODE_ASSIGN_SUB: assignOp(gz, cur_scope, inner_node, ZIR_INST_SUB); break; case AST_NODE_ASSIGN_MUL: assignOp(gz, cur_scope, inner_node, ZIR_INST_MUL); break; case AST_NODE_ASSIGN_DIV: assignOp(gz, cur_scope, inner_node, ZIR_INST_DIV); break; case AST_NODE_ASSIGN_MOD: assignOp(gz, cur_scope, inner_node, ZIR_INST_MOD_REM); break; case AST_NODE_ASSIGN_BIT_AND: assignOp(gz, cur_scope, inner_node, ZIR_INST_BIT_AND); break; case AST_NODE_ASSIGN_BIT_OR: assignOp(gz, cur_scope, inner_node, ZIR_INST_BIT_OR); break; case AST_NODE_ASSIGN_BIT_XOR: assignOp(gz, cur_scope, inner_node, ZIR_INST_XOR); break; case AST_NODE_ASSIGN_ADD_WRAP: assignOp(gz, cur_scope, inner_node, ZIR_INST_ADDWRAP); break; case AST_NODE_ASSIGN_SUB_WRAP: assignOp(gz, cur_scope, inner_node, ZIR_INST_SUBWRAP); break; case AST_NODE_ASSIGN_MUL_WRAP: assignOp(gz, cur_scope, inner_node, ZIR_INST_MULWRAP); break; case AST_NODE_ASSIGN_ADD_SAT: assignOp(gz, cur_scope, inner_node, ZIR_INST_ADD_SAT); break; case AST_NODE_ASSIGN_SUB_SAT: assignOp(gz, cur_scope, inner_node, ZIR_INST_SUB_SAT); break; case AST_NODE_ASSIGN_MUL_SAT: assignOp(gz, cur_scope, inner_node, ZIR_INST_MUL_SAT); break; case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: if (val_idx < 64 && ptr_idx < 64) { varDecl(gz, cur_scope, stmt, &val_scopes[val_idx], &ptr_scopes[ptr_idx], &cur_scope); // Check which one was used: if scope now points to // val_scopes[val_idx], advance val_idx; same for ptr. if (cur_scope == &val_scopes[val_idx].base) val_idx++; else if (cur_scope == &ptr_scopes[ptr_idx].base) ptr_idx++; } else { SET_ERROR(ag); } break; // defer/errdefer (AstGen.zig:2580-2581). case AST_NODE_DEFER: case AST_NODE_ERRDEFER: { if (defer_idx >= 64) { SET_ERROR(ag); break; } ScopeTag scope_tag = (tag == AST_NODE_DEFER) ? SCOPE_DEFER_NORMAL : SCOPE_DEFER_ERROR; // Create sub-block for defer body (AstGen.zig:3123-3126). GenZir defer_gen = makeSubBlock(gz, cur_scope); defer_gen.any_defer_node = stmt; // AstGen.zig:3125 // Evaluate deferred expression (AstGen.zig:3165). // DEFER: lhs is the deferred expression, rhs = 0. // ERRDEFER: lhs is optional error capture token, rhs is expr. AstData dnd = ag->tree->nodes.datas[stmt]; uint32_t expr_node; if (tag == AST_NODE_DEFER) { expr_node = dnd.lhs; } else { expr_node = dnd.rhs; } // unusedResultExpr pattern (AstGen.zig:3165, 2641-2646). emitDbgNode(&defer_gen, expr_node); uint32_t defer_result = expr(&defer_gen, &defer_gen.base, expr_node); addEnsureResult(&defer_gen, defer_result, expr_node); // Add break_inline at end (AstGen.zig:3167). addBreak(&defer_gen, ZIR_INST_BREAK_INLINE, 0, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); // Write body to extra (AstGen.zig:3173-3175). uint32_t raw_body_len = gzInstructionsLen(&defer_gen); const uint32_t* body = gzInstructionsSlice(&defer_gen); uint32_t extra_index = ag->extra_len; uint32_t fixup_len = countBodyLenAfterFixups(ag, body, raw_body_len); ensureExtraCapacity(ag, fixup_len); for (uint32_t b = 0; b < raw_body_len; b++) appendPossiblyRefdBodyInst(ag, body[b]); gzUnstack(&defer_gen); // Create scope (AstGen.zig:3179-3185). defer_scopes[defer_idx] = (ScopeDefer) { .base = { .tag = scope_tag }, .parent = cur_scope, .index = extra_index, .len = fixup_len, }; cur_scope = &defer_scopes[defer_idx].base; defer_idx++; break; } // Grouped expression: unwrap parentheses (AstGen.zig:2600-2602). case AST_NODE_GROUPED_EXPRESSION: inner_node = ag->tree->nodes.datas[inner_node].lhs; continue; // while/for as statements (AstGen.zig:2605-2610). // These do NOT get emitDbgNode; they emit their own dbg_stmt. case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: (void)whileExpr(gz, cur_scope, RL_NONE_VAL, inner_node, true); break; case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: (void)forExpr(gz, cur_scope, RL_NONE_VAL, inner_node, true); break; default: { // Expression statement (AstGen.zig:2627 unusedResultExpr). emitDbgNode(gz, inner_node); uint32_t result = expr(gz, cur_scope, inner_node); noreturn_stmt = addEnsureResult(gz, result, inner_node); break; } } break; // Break out of the for(;;) unwrapping loop. } } // Emit normal defers at block exit (AstGen.zig:2633-2634). if (!noreturn_stmt) { genDefers(gz, scope, cur_scope, DEFER_NORMAL_ONLY); } } // --- fullBodyExpr (AstGen.zig:2358) --- // Processes a body expression. If it's an unlabeled block, processes // statements inline without creating a BLOCK instruction (unlike blockExprExpr // which wraps in BLOCK). Returns the result ref. static uint32_t fullBodyExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { const Ast* tree = gz->astgen->tree; AstNodeTag tag = tree->nodes.tags[node]; // Extract block statements (AstGen.zig:2368). AstData nd = tree->nodes.datas[node]; uint32_t stmt_buf[2]; const uint32_t* statements = NULL; uint32_t stmt_count = 0; switch (tag) { case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: { uint32_t idx = 0; if (nd.lhs != 0) stmt_buf[idx++] = nd.lhs; if (nd.rhs != 0) stmt_buf[idx++] = nd.rhs; statements = stmt_buf; stmt_count = idx; break; } case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: { uint32_t start = nd.lhs; uint32_t end = nd.rhs; statements = tree->extra_data.arr + start; stmt_count = end - start; break; } default: // Not a block — treat as single expression (AstGen.zig:2369). return exprRl(gz, scope, rl, node); } // Check if labeled (AstGen.zig:2373-2377). uint32_t lbrace = tree->nodes.main_tokens[node]; bool is_labeled = (lbrace >= 2 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER); if (is_labeled) { // Labeled blocks need a proper block instruction. return blockExprExpr(gz, scope, rl, node); } // Unlabeled block: process statements inline (AstGen.zig:2380-2383). GenZir sub_gz = makeSubBlock(gz, scope); blockExprStmts(&sub_gz, &sub_gz.base, statements, stmt_count); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); } // --- lastToken (Ast.zig:874) --- // Mechanical port of Ast.lastToken. Uses iterative end_offset accumulation. static uint32_t lastToken(const Ast* tree, uint32_t node) { uint32_t n = node; uint32_t end_offset = 0; while (1) { AstNodeTag tag = tree->nodes.tags[n]; AstData nd = tree->nodes.datas[n]; switch (tag) { case AST_NODE_ROOT: return tree->tokens.len - 1; // Binary ops: recurse into RHS (Ast.zig:893-948). case AST_NODE_ASSIGN_MUL: case AST_NODE_ASSIGN_DIV: case AST_NODE_ASSIGN_MOD: case AST_NODE_ASSIGN_ADD: case AST_NODE_ASSIGN_SUB: case AST_NODE_ASSIGN_SHL: case AST_NODE_ASSIGN_SHL_SAT: case AST_NODE_ASSIGN_SHR: case AST_NODE_ASSIGN_BIT_AND: case AST_NODE_ASSIGN_BIT_XOR: case AST_NODE_ASSIGN_BIT_OR: case AST_NODE_ASSIGN_MUL_WRAP: case AST_NODE_ASSIGN_ADD_WRAP: case AST_NODE_ASSIGN_SUB_WRAP: case AST_NODE_ASSIGN_MUL_SAT: case AST_NODE_ASSIGN_ADD_SAT: case AST_NODE_ASSIGN_SUB_SAT: case AST_NODE_ASSIGN: case AST_NODE_ADD: case AST_NODE_SUB: case AST_NODE_MUL: case AST_NODE_DIV: case AST_NODE_MOD: case AST_NODE_BIT_AND: case AST_NODE_BIT_OR: case AST_NODE_BIT_XOR: case AST_NODE_SHL: case AST_NODE_SHR: case AST_NODE_ARRAY_CAT: case AST_NODE_ARRAY_MULT: case AST_NODE_ADD_WRAP: case AST_NODE_SUB_WRAP: case AST_NODE_ADD_SAT: case AST_NODE_SUB_SAT: case AST_NODE_MUL_WRAP: case AST_NODE_MUL_SAT: case AST_NODE_MERGE_ERROR_SETS: case AST_NODE_EQUAL_EQUAL: case AST_NODE_BANG_EQUAL: case AST_NODE_LESS_THAN: case AST_NODE_GREATER_THAN: case AST_NODE_LESS_OR_EQUAL: case AST_NODE_GREATER_OR_EQUAL: case AST_NODE_BOOL_AND: case AST_NODE_BOOL_OR: case AST_NODE_ORELSE: case AST_NODE_CATCH: case AST_NODE_ERROR_UNION: case AST_NODE_SHL_SAT: n = nd.rhs; continue; // field_access: return field token + end_offset (Ast.zig:979). case AST_NODE_FIELD_ACCESS: return nd.rhs + end_offset; // test_decl: recurse into body node (Ast.zig:950). case AST_NODE_TEST_DECL: n = nd.rhs; continue; // defer: recurse into body (lhs) (Ast.zig:951). case AST_NODE_DEFER: n = nd.lhs; continue; // errdefer: recurse into body (rhs) (Ast.zig:950). case AST_NODE_ERRDEFER: n = nd.rhs; continue; // block (Ast.zig:1085): end_offset += 1 (rbrace), recurse into last. case AST_NODE_BLOCK: { uint32_t start = nd.lhs; uint32_t end = nd.rhs; assert(start != end); end_offset += 1; n = tree->extra_data.arr[end - 1]; continue; } // block_semicolon (Ast.zig:1097): += 2 (semicolon + rbrace). case AST_NODE_BLOCK_SEMICOLON: { uint32_t start = nd.lhs; uint32_t end = nd.rhs; assert(start != end); end_offset += 2; n = tree->extra_data.arr[end - 1]; continue; } // block_two (Ast.zig:1117): if rhs, recurse rhs +1; if lhs, +1; else // +1. Note: C parser uses 0 for "none" (OptionalIndex), not // UINT32_MAX. case AST_NODE_BLOCK_TWO: { if (nd.rhs != 0) { end_offset += 1; n = nd.rhs; } else if (nd.lhs != 0) { end_offset += 1; n = nd.lhs; } else { end_offset += 1; return tree->nodes.main_tokens[n] + end_offset; } continue; } // block_two_semicolon (Ast.zig:1153). case AST_NODE_BLOCK_TWO_SEMICOLON: { if (nd.rhs != 0) { end_offset += 2; n = nd.rhs; } else if (nd.lhs != 0) { end_offset += 2; n = nd.lhs; } else { end_offset += 1; return tree->nodes.main_tokens[n] + end_offset; } continue; } // builtin_call_two (Ast.zig:1118): recurse into args + rparen. case AST_NODE_BUILTIN_CALL_TWO: { if (nd.rhs != 0) { end_offset += 1; n = nd.rhs; } else if (nd.lhs != 0) { end_offset += 1; n = nd.lhs; } else { end_offset += 2; // lparen + rparen return tree->nodes.main_tokens[n] + end_offset; } continue; } case AST_NODE_BUILTIN_CALL_TWO_COMMA: { if (nd.rhs != 0) { end_offset += 2; // comma + rparen n = nd.rhs; } else if (nd.lhs != 0) { end_offset += 2; n = nd.lhs; } else { end_offset += 1; return tree->nodes.main_tokens[n] + end_offset; } continue; } // Unary ops: recurse into lhs (Ast.zig:880-891). case AST_NODE_BOOL_NOT: case AST_NODE_BIT_NOT: case AST_NODE_NEGATION: case AST_NODE_NEGATION_WRAP: case AST_NODE_ADDRESS_OF: case AST_NODE_TRY: case AST_NODE_AWAIT: case AST_NODE_OPTIONAL_TYPE: case AST_NODE_SUSPEND: case AST_NODE_COMPTIME: case AST_NODE_NOSUSPEND: case AST_NODE_RESUME: n = nd.lhs; continue; // return: optional operand (Ast.zig:998-1002). case AST_NODE_RETURN: if (nd.lhs != 0) { n = nd.lhs; continue; } return tree->nodes.main_tokens[n] + end_offset; // deref: main_token is the `.*` token (Ast.zig:993). case AST_NODE_DEREF: return tree->nodes.main_tokens[n] + end_offset; // unwrap_optional (Ast.zig:980): return rhs token + end_offset. case AST_NODE_UNWRAP_OPTIONAL: return nd.rhs + end_offset; // for_range (Ast.zig:973-977): recurse into rhs if present, else // main_token + end_offset. case AST_NODE_FOR_RANGE: if (nd.rhs != 0) { n = nd.rhs; } else { return tree->nodes.main_tokens[n] + end_offset; } continue; // error_value: main_token is `error`, last token is name (+2) // (Ast.zig:986). case AST_NODE_ERROR_VALUE: return tree->nodes.main_tokens[n] + 2 + end_offset; // Terminals: return main_token + end_offset (Ast.zig:988-996). case AST_NODE_NUMBER_LITERAL: case AST_NODE_STRING_LITERAL: case AST_NODE_IDENTIFIER: case AST_NODE_ENUM_LITERAL: case AST_NODE_CHAR_LITERAL: case AST_NODE_UNREACHABLE_LITERAL: case AST_NODE_ANYFRAME_LITERAL: return tree->nodes.main_tokens[n] + end_offset; // call_one (Ast.zig:1107-1114): +1 for rparen, recurse into // first_param if present. case AST_NODE_CALL_ONE: end_offset += 1; // rparen if (nd.rhs != 0) { n = nd.rhs; } else { return tree->nodes.main_tokens[n] + end_offset; } continue; case AST_NODE_CALL_ONE_COMMA: end_offset += 2; // comma + rparen n = nd.rhs; continue; // array_access: end_offset += 1 (rbracket), recurse rhs. case AST_NODE_ARRAY_ACCESS: end_offset += 1; n = nd.rhs; continue; // simple_var_decl: recurse into init/type (Ast.zig:1169-1178). case AST_NODE_SIMPLE_VAR_DECL: if (nd.rhs != 0) { n = nd.rhs; // init expr } else if (nd.lhs != 0) { n = nd.lhs; // type expr } else { end_offset += 1; // from mut token to name return tree->nodes.main_tokens[n] + end_offset; } continue; // aligned_var_decl: recurse into init/align (Ast.zig:1180-1187). case AST_NODE_ALIGNED_VAR_DECL: if (nd.rhs != 0) { n = nd.rhs; // init expr } else { end_offset += 1; // rparen n = nd.lhs; // align expr } continue; // local_var_decl (Ast.zig:1209-1217). // extra[lhs] = LocalVarDecl { type_node, align_node } case AST_NODE_LOCAL_VAR_DECL: if (nd.rhs != 0) { n = nd.rhs; // init expr } else { end_offset += 1; // rparen n = tree->extra_data.arr[nd.lhs + 1]; // align_node } continue; // global_var_decl (Ast.zig:1189-1207). // extra[lhs] = GlobalVarDecl { type_node, align_node, // addrspace_node, section_node } case AST_NODE_GLOBAL_VAR_DECL: if (nd.rhs != 0) { n = nd.rhs; // init expr } else { uint32_t section_node = tree->extra_data.arr[nd.lhs + 3]; uint32_t align_node = tree->extra_data.arr[nd.lhs + 1]; uint32_t type_node = tree->extra_data.arr[nd.lhs]; if (section_node != 0) { end_offset += 1; // rparen n = section_node; } else if (align_node != 0) { end_offset += 1; // rparen n = align_node; } else if (type_node != 0) { n = type_node; } else { end_offset += 1; // from mut token to name return tree->nodes.main_tokens[n] + end_offset; } } continue; // slice_open: end_offset += 2 (ellipsis2 + rbracket), recurse rhs // (Ast.zig:1245-1248). case AST_NODE_SLICE_OPEN: end_offset += 2; n = nd.rhs; continue; // grouped_expression (Ast.zig:983): return rhs token + end_offset. case AST_NODE_GROUPED_EXPRESSION: return nd.rhs + end_offset; // if_simple: recurse into body (rhs) (Ast.zig:942). case AST_NODE_IF_SIMPLE: case AST_NODE_WHILE_SIMPLE: case AST_NODE_FOR_SIMPLE: case AST_NODE_FN_DECL: case AST_NODE_ARRAY_TYPE: n = nd.rhs; continue; // if: recurse into else_expr (Ast.zig:1295). case AST_NODE_IF: { // If[rhs]: { then_expr, else_expr } n = tree->extra_data.arr[nd.rhs + 1]; // else_expr continue; } // while: recurse into else_expr (Ast.zig:1290). case AST_NODE_WHILE: { // While[rhs]: { cont_expr, then_expr, else_expr } n = tree->extra_data.arr[nd.rhs + 2]; // else_expr continue; } // while_cont: recurse into then_expr (Ast.zig:943-like). case AST_NODE_WHILE_CONT: { // WhileCont[rhs]: { cont_expr, then_expr } n = tree->extra_data.arr[nd.rhs + 1]; // then_expr continue; } // switch: recurse into last case (Ast.zig:1031-1041). case AST_NODE_SWITCH: { uint32_t ei = nd.rhs; uint32_t cs = tree->extra_data.arr[ei]; uint32_t ce = tree->extra_data.arr[ei + 1]; if (cs == ce) { end_offset += 3; // rparen, lbrace, rbrace n = nd.lhs; } else { end_offset += 1; // rbrace n = tree->extra_data.arr[ce - 1]; } continue; } case AST_NODE_SWITCH_COMMA: { uint32_t ei = nd.rhs; uint32_t cs = tree->extra_data.arr[ei]; uint32_t ce = tree->extra_data.arr[ei + 1]; assert(cs != ce); end_offset += 2; // comma + rbrace n = tree->extra_data.arr[ce - 1]; continue; } // switch_case_one: recurse into rhs (body) (Ast.zig:942). case AST_NODE_SWITCH_CASE_ONE: case AST_NODE_SWITCH_CASE_INLINE_ONE: case AST_NODE_SWITCH_CASE: case AST_NODE_SWITCH_CASE_INLINE: n = nd.rhs; continue; // switch_range: recurse into rhs (Ast.zig: binary op pattern). case AST_NODE_SWITCH_RANGE: n = nd.rhs; continue; // struct_init_one: recurse into field if present, +1. case AST_NODE_STRUCT_INIT_ONE: end_offset += 1; // rbrace if (nd.rhs != 0) { n = nd.rhs; } else { return tree->nodes.main_tokens[n] + end_offset; } continue; case AST_NODE_STRUCT_INIT_ONE_COMMA: end_offset += 2; // comma + rbrace n = nd.rhs; continue; // struct_init_dot_two: similar to block_two. case AST_NODE_STRUCT_INIT_DOT_TWO: if (nd.rhs != 0) { end_offset += 1; n = nd.rhs; } else if (nd.lhs != 0) { end_offset += 1; n = nd.lhs; } else { end_offset += 1; // rbrace return tree->nodes.main_tokens[n] + end_offset; } continue; case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: end_offset += 2; if (nd.rhs != 0) { n = nd.rhs; } else { n = nd.lhs; } continue; // struct_init_dot: SubRange pattern. case AST_NODE_STRUCT_INIT_DOT: assert(nd.lhs != nd.rhs); end_offset += 1; n = tree->extra_data.arr[nd.rhs - 1]; continue; // struct_init: node_and_extra SubRange pattern. case AST_NODE_STRUCT_INIT: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 1; n = tree->extra_data.arr[se - 1]; continue; } // call: SubRange pattern. case AST_NODE_CALL: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 1; n = tree->extra_data.arr[se - 1]; continue; } case AST_NODE_CALL_COMMA: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 2; n = tree->extra_data.arr[se - 1]; continue; } // fn_proto_simple: recurse into rhs (return type). case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO: n = nd.rhs; continue; // error_set_decl: rhs is the closing rbrace token. case AST_NODE_ERROR_SET_DECL: return nd.rhs + end_offset; // ptr_type variants: recurse into rhs (child type). case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: n = nd.rhs; continue; // container_decl: extra_range pattern. case AST_NODE_CONTAINER_DECL: case AST_NODE_TAGGED_UNION: assert(nd.lhs != nd.rhs); end_offset += 1; n = tree->extra_data.arr[nd.rhs - 1]; continue; case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_TAGGED_UNION_TRAILING: assert(nd.lhs != nd.rhs); end_offset += 2; n = tree->extra_data.arr[nd.rhs - 1]; continue; // container_decl_two / tagged_union_two (Ast.zig:1120-1151). case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_TAGGED_UNION_TWO: if (nd.rhs != 0) { end_offset += 1; n = nd.rhs; } else if (nd.lhs != 0) { end_offset += 1; n = nd.lhs; } else { if (tag == AST_NODE_CONTAINER_DECL_TWO) { uint32_t i = 2; // lbrace + rbrace while (tree->tokens.tags[tree->nodes.main_tokens[n] + i] == TOKEN_CONTAINER_DOC_COMMENT) i += 1; end_offset += i; } else { // tagged_union_two: (enum) {} uint32_t i = 5; while (tree->tokens.tags[tree->nodes.main_tokens[n] + i] == TOKEN_CONTAINER_DOC_COMMENT) i += 1; end_offset += i; } return tree->nodes.main_tokens[n] + end_offset; } continue; case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_TAGGED_UNION_TWO_TRAILING: end_offset += 2; if (nd.rhs != 0) { n = nd.rhs; } else { n = nd.lhs; } continue; // container_decl_arg: node_and_extra SubRange. case AST_NODE_CONTAINER_DECL_ARG: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; if (si == se) { end_offset += 3; // rparen + lbrace + rbrace n = nd.lhs; } else { end_offset += 1; n = tree->extra_data.arr[se - 1]; } continue; } case AST_NODE_CONTAINER_DECL_ARG_TRAILING: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 2; n = tree->extra_data.arr[se - 1]; continue; } // slice: extra data pattern. case AST_NODE_SLICE: { // Slice[rhs]: { start, end } end_offset += 1; n = tree->extra_data.arr[nd.rhs + 1]; // end continue; } case AST_NODE_SLICE_SENTINEL: { // SliceSentinel[rhs]: { start, end, sentinel } end_offset += 1; n = tree->extra_data.arr[nd.rhs + 2]; // sentinel continue; } // array_type_sentinel: extra data. case AST_NODE_ARRAY_TYPE_SENTINEL: { // ArrayTypeSentinel[rhs]: { sentinel, elem_type } n = tree->extra_data.arr[nd.rhs + 1]; // elem_type continue; } // multiline_string_literal: main_token + end_offset. case AST_NODE_MULTILINE_STRING_LITERAL: return nd.rhs + end_offset; // break/continue (Ast.zig:1275-1283). // lhs is opt_token (null_token = UINT32_MAX), rhs is opt_node (0 = // none). case AST_NODE_BREAK: case AST_NODE_CONTINUE: if (nd.rhs != 0) { n = nd.rhs; // optional rhs expression } else if (nd.lhs != UINT32_MAX) { return nd.lhs + end_offset; // label token } else { return tree->nodes.main_tokens[n] + end_offset; } continue; // array_init_one: end_offset += 1 (rbrace), recurse rhs // (Ast.zig:1224-1230). case AST_NODE_ARRAY_INIT_ONE: end_offset += 1; n = nd.rhs; continue; case AST_NODE_ARRAY_INIT_ONE_COMMA: end_offset += 2; // comma + rbrace n = nd.rhs; continue; // struct_init_dot_comma: SubRange pattern. case AST_NODE_STRUCT_INIT_DOT_COMMA: assert(nd.lhs != nd.rhs); end_offset += 2; // comma + rbrace n = tree->extra_data.arr[nd.rhs - 1]; continue; // struct_init_comma: node_and_extra SubRange. case AST_NODE_STRUCT_INIT_COMMA: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 2; n = tree->extra_data.arr[se - 1]; continue; } // array_init variants. case AST_NODE_ARRAY_INIT: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 1; n = tree->extra_data.arr[se - 1]; continue; } case AST_NODE_ARRAY_INIT_COMMA: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 2; n = tree->extra_data.arr[se - 1]; continue; } // array_init_dot variants. case AST_NODE_ARRAY_INIT_DOT_TWO: if (nd.rhs != 0) { end_offset += 1; n = nd.rhs; } else if (nd.lhs != 0) { end_offset += 1; n = nd.lhs; } else { end_offset += 1; return tree->nodes.main_tokens[n] + end_offset; } continue; case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: end_offset += 2; if (nd.rhs != 0) { n = nd.rhs; } else { n = nd.lhs; } continue; case AST_NODE_ARRAY_INIT_DOT: assert(nd.lhs != nd.rhs); end_offset += 1; n = tree->extra_data.arr[nd.rhs - 1]; continue; case AST_NODE_ARRAY_INIT_DOT_COMMA: assert(nd.lhs != nd.rhs); end_offset += 2; n = tree->extra_data.arr[nd.rhs - 1]; continue; // builtin_call (Ast.zig:1083-1105). case AST_NODE_BUILTIN_CALL: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 1; n = tree->extra_data.arr[se - 1]; continue; } case AST_NODE_BUILTIN_CALL_COMMA: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 2; n = tree->extra_data.arr[se - 1]; continue; } // for (Ast.zig:1300-1303): complex extra data. case AST_NODE_FOR: { // lhs = span.start (extra_data index), // rhs = packed(inputs:u31, has_else:u1 at bit 31). // extra[lhs..] = input nodes, then_body, [else_body]. uint32_t span_start = nd.lhs; uint32_t for_packed = nd.rhs; uint32_t inputs = for_packed & 0x7FFFFFFFu; bool has_else = (for_packed >> 31) != 0; uint32_t last_idx = span_start + inputs + (has_else ? 1 : 0); n = tree->extra_data.arr[last_idx]; continue; } // anyframe_type (Ast.zig:952): recurse into rhs. case AST_NODE_ANYFRAME_TYPE: n = nd.rhs; continue; // assign_destructure (Ast.zig:960-965): recurse into rhs. case AST_NODE_ASSIGN_DESTRUCTURE: n = nd.rhs; continue; // asm_simple (Ast.zig:981): return nd.rhs + end_offset. case AST_NODE_ASM_SIMPLE: return nd.rhs + end_offset; // asm_input (Ast.zig:983): return nd.rhs + end_offset. case AST_NODE_ASM_INPUT: return nd.rhs + end_offset; // asm_output (Ast.zig:985): return nd.rhs + end_offset. case AST_NODE_ASM_OUTPUT: return nd.rhs + end_offset; // asm_legacy (Ast.zig:1053-1057): read rparen from extra data. case AST_NODE_ASM_LEGACY: { // extra[rhs] = AsmLegacy { items_start, items_end, rparen } uint32_t rparen = tree->extra_data.arr[nd.rhs + 2]; return rparen + end_offset; } // asm (Ast.zig:1058-1062): read rparen from extra data. case AST_NODE_ASM: { // extra[rhs] = Asm { items_start, items_end, clobbers, rparen } uint32_t rparen = tree->extra_data.arr[nd.rhs + 3]; return rparen + end_offset; } // container_field_init (Ast.zig:1219-1222): recurse into // value_expr or type_expr. case AST_NODE_CONTAINER_FIELD_INIT: if (nd.rhs != 0) { n = nd.rhs; // value_expr } else { n = nd.lhs; // type_expr } continue; // container_field_align (Ast.zig:1224-1231): +1 for rparen, // recurse rhs. case AST_NODE_CONTAINER_FIELD_ALIGN: end_offset += 1; n = nd.rhs; continue; // container_field (Ast.zig:1232-1236): read value_expr from // extra data. case AST_NODE_CONTAINER_FIELD: { // extra[rhs] = ContainerField { align_expr, value_expr } uint32_t value_expr = tree->extra_data.arr[nd.rhs + 1]; n = value_expr; continue; } // tagged_union_enum_tag (Ast.zig:1011-1021): SubRange handling. case AST_NODE_TAGGED_UNION_ENUM_TAG: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; if (si == se) { end_offset += 4; // rparen + rparen + lbrace + rbrace n = nd.lhs; } else { end_offset += 1; // rbrace n = tree->extra_data.arr[se - 1]; } continue; } // tagged_union_enum_tag_trailing (Ast.zig:1022-1030). case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: { uint32_t si = tree->extra_data.arr[nd.rhs]; uint32_t se = tree->extra_data.arr[nd.rhs + 1]; assert(si != se); end_offset += 2; // comma/semicolon + rbrace n = tree->extra_data.arr[se - 1]; continue; } default: // Fallback: return main_token + end_offset. return tree->nodes.main_tokens[n] + end_offset; } } } // --- addParam (AstGen.zig:12390) --- // Creates a param instruction with pl_tok data and type body in extra. static uint32_t addParam(GenZir* gz, GenZir* param_gz, ZirInstTag tag, uint32_t abs_tok_index, uint32_t name) { AstGenCtx* ag = gz->astgen; uint32_t body_len = gzInstructionsLen(param_gz); const uint32_t* param_body = gzInstructionsSlice(param_gz); // Param payload: name, type{body_len:u31|is_generic:u1} ensureExtraCapacity(ag, 2 + body_len); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = name; ag->extra[ag->extra_len++] = body_len & 0x7FFFFFFFu; // is_generic = false for (uint32_t i = 0; i < body_len; i++) { ag->extra[ag->extra_len++] = param_body[i]; } gzUnstack(param_gz); // Emit the param instruction. ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; ag->inst_tags[idx] = tag; ZirInstData data; data.pl_tok.src_tok = tokenIndexToRelative(gz, abs_tok_index); data.pl_tok.payload_index = payload_index; ag->inst_datas[idx] = data; ag->inst_len++; gzAppendInstruction(gz, idx); return idx; } // --- addDbgVar (AstGen.zig:13196) --- static void addDbgVar( GenZir* gz, ZirInstTag tag, uint32_t name, uint32_t inst) { if (gz->is_comptime) return; ZirInstData data; data.str_op.str = name; data.str_op.operand = inst; addInstruction(gz, tag, data); } // --- addFunc (AstGen.zig:12023) --- // Handles non-fancy func/func_inferred instructions. // ret_body/ret_body_len: instructions for the return type sub-block (may be // 0). ret_ref: if ret_body_len==0, the return type as a simple Ref. static uint32_t addFunc(GenZir* gz, uint32_t src_node, uint32_t block_node, uint32_t param_block, uint32_t ret_ref, const uint32_t* ret_body, uint32_t ret_body_len, const uint32_t* body, uint32_t body_len, const uint32_t* param_insts, uint32_t param_insts_len, uint32_t lbrace_line, uint32_t lbrace_column, bool is_inferred_error) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; uint32_t rbrace_tok = lastToken(tree, block_node); uint32_t rbrace_start = tree->tokens.starts[rbrace_tok]; advanceSourceCursor(ag, rbrace_start); uint32_t rbrace_line = ag->source_line - gz->decl_line; uint32_t rbrace_column = ag->source_column; // Build Func payload (Zir.Inst.Func: ret_ty, param_block, body_len). // (AstGen.zig:12187-12194) uint32_t ret_ty_packed_len; if (ret_body_len > 0) { ret_ty_packed_len = ret_body_len; // body-based return type } else if (ret_ref != ZIR_REF_NONE) { ret_ty_packed_len = 1; // simple Ref } else { ret_ty_packed_len = 0; // void return } // Pack RetTy: body_len:u31 | is_generic:bool(u1) = just body_len. uint32_t ret_ty_packed = ret_ty_packed_len & 0x7FFFFFFFu; // is_generic=false uint32_t fixup_body_len = countBodyLenAfterFixupsExtraRefs( ag, body, body_len, param_insts, param_insts_len); ensureExtraCapacity(ag, 3 + ret_ty_packed_len + fixup_body_len + 7); uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = ret_ty_packed; // Func.ret_ty ag->extra[ag->extra_len++] = param_block; // Func.param_block ag->extra[ag->extra_len++] = fixup_body_len; // Func.body_len // Trailing ret_ty: either body instructions or a single ref. if (ret_body_len > 0) { for (uint32_t i = 0; i < ret_body_len; i++) ag->extra[ag->extra_len++] = ret_body[i]; } else if (ret_ref != ZIR_REF_NONE) { ag->extra[ag->extra_len++] = ret_ref; } // Body instructions with extra_refs for param_insts // (AstGen.zig:12206). appendBodyWithFixupsExtraRefs( ag, body, body_len, param_insts, param_insts_len); // SrcLocs (AstGen.zig:12098-12106). uint32_t columns = (lbrace_column & 0xFFFFu) | (rbrace_column << 16); ag->extra[ag->extra_len++] = lbrace_line; ag->extra[ag->extra_len++] = rbrace_line; ag->extra[ag->extra_len++] = columns; // proto_hash (4 words): zero for now. ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; // Emit the func instruction (AstGen.zig:12220-12226). ZirInstTag tag = is_inferred_error ? ZIR_INST_FUNC_INFERRED : ZIR_INST_FUNC; ZirInstData data; data.pl_node.src_node = (int32_t)src_node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; return addInstruction(gz, tag, data); } // --- addFuncFancy (AstGen.zig:12112-12173) --- // Emits func_fancy instruction when cc_ref, is_var_args, noalias_bits, // or is_noinline are present. static uint32_t addFuncFancy(GenZir* gz, uint32_t src_node, uint32_t block_node, uint32_t param_block, uint32_t ret_ref, const uint32_t* ret_body, uint32_t ret_body_len, uint32_t cc_ref, const uint32_t* cc_body, uint32_t cc_body_len, const uint32_t* body, uint32_t body_len, const uint32_t* param_insts, uint32_t param_insts_len, uint32_t lbrace_line, uint32_t lbrace_column, bool is_var_args, bool is_inferred_error, bool is_noinline, uint32_t noalias_bits) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; uint32_t rbrace_tok = lastToken(tree, block_node); uint32_t rbrace_start = tree->tokens.starts[rbrace_tok]; advanceSourceCursor(ag, rbrace_start); uint32_t rbrace_line = ag->source_line - gz->decl_line; uint32_t rbrace_column = ag->source_column; uint32_t fixup_body_len = countBodyLenAfterFixupsExtraRefs( ag, body, body_len, param_insts, param_insts_len); // Calculate cc extra len (AstGen.zig:12231-12236). uint32_t cc_extra_len = 0; if (cc_body_len > 0) { cc_extra_len = countBodyLenAfterFixups(ag, cc_body, cc_body_len) + 1; } else if (cc_ref != ZIR_REF_NONE) { cc_extra_len = 1; } // Calculate ret extra len (AstGen.zig:12231-12236). // Note: ret_param_refs are empty (no generic tracking yet). uint32_t ret_extra_len = 0; if (ret_body_len > 0) { ret_extra_len = countBodyLenAfterFixups(ag, ret_body, ret_body_len) + 1; } else if (ret_ref != ZIR_REF_NONE) { ret_extra_len = 1; } // FuncFancy has 3 fields: param_block, body_len, bits. uint32_t total_extra = 3 + cc_extra_len + ret_extra_len + ((noalias_bits != 0) ? 1u : 0u) + fixup_body_len + 7; ensureExtraCapacity(ag, total_extra); // FuncFancy payload (Zir.zig:2589-2610). uint32_t payload_index = ag->extra_len; ag->extra[ag->extra_len++] = param_block; ag->extra[ag->extra_len++] = fixup_body_len; // Bits packed as u32 (Zir.zig:2598-2609). uint32_t bits = 0; if (is_var_args) bits |= (1u << 0); if (is_inferred_error) bits |= (1u << 1); if (is_noinline) bits |= (1u << 2); if (cc_ref != ZIR_REF_NONE) bits |= (1u << 3); // has_cc_ref if (cc_body_len > 0) bits |= (1u << 4); // has_cc_body if (ret_ref != ZIR_REF_NONE) bits |= (1u << 5); // has_ret_ty_ref if (ret_body_len > 0) bits |= (1u << 6); // has_ret_ty_body if (noalias_bits != 0) bits |= (1u << 7); // has_any_noalias // bit 8 = ret_ty_is_generic (false for now, no generic tracking) ag->extra[ag->extra_len++] = bits; // Trailing cc (AstGen.zig:12143-12151). if (cc_body_len > 0) { ag->extra[ag->extra_len++] = countBodyLenAfterFixups(ag, cc_body, cc_body_len); for (uint32_t i = 0; i < cc_body_len; i++) appendPossiblyRefdBodyInst(ag, cc_body[i]); } else if (cc_ref != ZIR_REF_NONE) { ag->extra[ag->extra_len++] = cc_ref; } // Trailing ret_ty (AstGen.zig:12152-12164). // Note: no ret_param_refs (generic tracking not implemented). if (ret_body_len > 0) { ag->extra[ag->extra_len++] = countBodyLenAfterFixups(ag, ret_body, ret_body_len); for (uint32_t i = 0; i < ret_body_len; i++) appendPossiblyRefdBodyInst(ag, ret_body[i]); } else if (ret_ref != ZIR_REF_NONE) { ag->extra[ag->extra_len++] = ret_ref; } // Trailing noalias_bits (AstGen.zig:12166-12168). if (noalias_bits != 0) ag->extra[ag->extra_len++] = noalias_bits; // Body (AstGen.zig:12170). appendBodyWithFixupsExtraRefs( ag, body, body_len, param_insts, param_insts_len); // SrcLocs (AstGen.zig:12098-12106). uint32_t columns = (lbrace_column & 0xFFFFu) | (rbrace_column << 16); ag->extra[ag->extra_len++] = lbrace_line; ag->extra[ag->extra_len++] = rbrace_line; ag->extra[ag->extra_len++] = columns; // proto_hash (4 words): zero for now. ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; // Emit the func_fancy instruction (AstGen.zig:12220-12226). ZirInstData data; data.pl_node.src_node = (int32_t)src_node - (int32_t)gz->decl_node_index; data.pl_node.payload_index = payload_index; return addInstruction(gz, ZIR_INST_FUNC_FANCY, data); } // --- testDecl (AstGen.zig:4708) --- static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t* decl_idx, uint32_t node) { const Ast* tree = ag->tree; AstData nd = tree->nodes.datas[node]; uint32_t body_node = nd.rhs; // makeDeclaration before advanceSourceCursorToNode (AstGen.zig:4726-4729). uint32_t decl_inst = makeDeclaration(ag, node); wip_decl_insts[*decl_idx] = decl_inst; (*decl_idx)++; advanceSourceCursorToNode(ag, node); uint32_t decl_line = ag->source_line; uint32_t decl_column = ag->source_column; // Extract test name (AstGen.zig:4748-4835). uint32_t test_token = tree->nodes.main_tokens[node]; uint32_t test_name_token = test_token + 1; uint32_t test_name = 0; // NullTerminatedString.empty DeclFlagsId decl_id = DECL_ID_UNNAMED_TEST; // Check if the token after 'test' is a string literal. // We identify string literals by checking the source character. uint32_t name_tok_start = tree->tokens.starts[test_name_token]; if (name_tok_start < tree->source_len && tree->source[name_tok_start] == '"') { // String literal name. uint32_t name_len; strLitAsString(ag, test_name_token, &test_name, &name_len); decl_id = DECL_ID_TEST; } else if (tree->tokens.tags[test_name_token] == TOKEN_IDENTIFIER) { // Identifier test name (decltest) (AstGen.zig:4763-4834). // Upstream performs full scope resolution for validation; we skip // the validation and just record the identifier as the test name. test_name = identAsString(ag, test_name_token); decl_id = DECL_ID_DECLTEST; } // Set up decl_block GenZir (AstGen.zig:4735-4743). GenZir decl_block; memset(&decl_block, 0, sizeof(decl_block)); decl_block.base.tag = SCOPE_GEN_ZIR; decl_block.parent = NULL; decl_block.astgen = ag; decl_block.decl_node_index = node; decl_block.decl_line = decl_line; decl_block.is_comptime = true; decl_block.instructions_top = ag->scratch_inst_len; decl_block.break_block = UINT32_MAX; decl_block.any_defer_node = UINT32_MAX; // Set up fn_block GenZir (AstGen.zig:4837-4845). GenZir fn_block; memset(&fn_block, 0, sizeof(fn_block)); fn_block.base.tag = SCOPE_GEN_ZIR; fn_block.parent = &decl_block.base; fn_block.astgen = ag; fn_block.decl_node_index = node; fn_block.decl_line = decl_line; fn_block.is_comptime = false; fn_block.instructions_top = ag->scratch_inst_len; fn_block.break_block = UINT32_MAX; fn_block.any_defer_node = UINT32_MAX; // Set within_fn, fn_block and fn_ret_ty for the body // (AstGen.zig:4848-4853). bool prev_within_fn = ag->within_fn; void* prev_fn_block = ag->fn_block; uint32_t prev_fn_ret_ty = ag->fn_ret_ty; ag->within_fn = true; setFnBlock(ag, &fn_block); ag->fn_ret_ty = ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE; // Compute lbrace source location (AstGen.zig:4860-4862). advanceSourceCursorToNode(ag, body_node); uint32_t lbrace_line = ag->source_line - decl_line; uint32_t lbrace_column = ag->source_column; // Process test body (AstGen.zig:4864). uint32_t block_result = fullBodyExpr(&fn_block, &fn_block.base, RL_NONE_VAL, body_node); ag->within_fn = prev_within_fn; ag->fn_block = prev_fn_block; ag->fn_ret_ty = prev_fn_ret_ty; // If we hit unimplemented features, bail out. if (ag->has_compile_errors) return; // Add restore_err_ret_index + ret_implicit (AstGen.zig:4865-4871). if (gzInstructionsLen(&fn_block) == 0 || !refIsNoReturn(&fn_block, block_result)) { ZirInstData rdata; rdata.un_node.operand = ZIR_REF_NONE; // .none for .ret rdata.un_node.src_node = (int32_t)node - (int32_t)fn_block.decl_node_index; addInstruction( &fn_block, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); uint32_t body_last_tok = lastToken(tree, body_node); ZirInstData rdata2; rdata2.un_tok.operand = ZIR_REF_VOID_VALUE; rdata2.un_tok.src_tok = tokenIndexToRelative(&fn_block, body_last_tok); addInstruction(&fn_block, ZIR_INST_RET_IMPLICIT, rdata2); } // Read fn_block body before unstacking (AstGen.zig:4874). // Upstream unstacks fn_block inside addFunc before appending the func // instruction to decl_block. We must unstack fn_block first so that // addFunc's addInstruction goes into decl_block's range. const uint32_t* fn_body = gzInstructionsSlice(&fn_block); uint32_t fn_body_len = gzInstructionsLen(&fn_block); gzUnstack(&fn_block); // Create func instruction (AstGen.zig:4874-4897). uint32_t func_ref = addFunc(&decl_block, node, body_node, decl_inst, ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE, NULL, 0, fn_body, fn_body_len, NULL, 0, lbrace_line, lbrace_column, false); // break_inline returning func to declaration (AstGen.zig:4899). makeBreakInline(&decl_block, decl_inst, func_ref, AST_NODE_OFFSET_NONE); // setDeclaration (AstGen.zig:4903-4923). setDeclaration(ag, decl_inst, (SetDeclArgs) { .src_line = decl_line, .src_column = decl_column, .id = decl_id, .name = test_name, .lib_name = UINT32_MAX, .value_body = gzInstructionsSlice(&decl_block), .value_body_len = gzInstructionsLen(&decl_block) }); gzUnstack(&decl_block); (void)gz; } // --- fnDecl (AstGen.zig:4067) / fnDeclInner (AstGen.zig:4228) --- // Handles function declarations with bodies (fn_decl) and // function prototypes without bodies (fn_proto*). static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t* decl_idx, uint32_t node) { const Ast* tree = ag->tree; AstNodeTag node_tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // For fn_decl: data.lhs = fn_proto node, data.rhs = body node. // For fn_proto*: the node itself IS the proto, no body. uint32_t proto_node; uint32_t body_node; if (node_tag == AST_NODE_FN_DECL) { proto_node = nd.lhs; body_node = nd.rhs; } else { // fn_proto_simple, fn_proto_multi, fn_proto_one, fn_proto proto_node = node; body_node = 0; } // Get function name token (main_token of proto + 1 = fn name). uint32_t fn_token = tree->nodes.main_tokens[proto_node]; uint32_t fn_name_token = fn_token + 1; // Check for 'pub', 'export', 'inline', 'noinline' modifiers // (Ast.zig:2003-2025, AstGen.zig:4102-4106, 4240-4247). bool is_pub = false; bool is_export = false; bool is_noinline = false; bool has_inline_keyword = false; for (uint32_t i = fn_token; i > 0;) { i--; uint32_t ttag = tree->tokens.tags[i]; if (ttag == TOKEN_KEYWORD_PUB) is_pub = true; else if (ttag == TOKEN_KEYWORD_EXPORT) is_export = true; else if (ttag == TOKEN_KEYWORD_NOINLINE) is_noinline = true; else if (ttag == TOKEN_KEYWORD_INLINE) has_inline_keyword = true; else break; } // makeDeclaration on fn_proto node (AstGen.zig:4090). uint32_t decl_inst = makeDeclaration(ag, proto_node); wip_decl_insts[*decl_idx] = decl_inst; (*decl_idx)++; advanceSourceCursorToNode(ag, node); uint32_t decl_line = ag->source_line; uint32_t decl_column = ag->source_column; // Set this now, since parameter types, return type, etc may be generic // (AstGen.zig:4097-4100). bool prev_within_fn = ag->within_fn; ag->within_fn = true; // Save source cursor for restoring after ret_gz (AstGen.zig:4387-4388). uint32_t saved_source_offset = ag->source_offset; uint32_t saved_source_line = ag->source_line; uint32_t saved_source_column = ag->source_column; AstNodeTag proto_tag = tree->nodes.tags[proto_node]; AstData proto_data = tree->nodes.datas[proto_node]; // Extract return type node (rhs for all fn_proto variants). uint32_t return_type_node = proto_data.rhs; // Detect inferred error set: token before return type is '!' // (AstGen.zig:4249-4251). bool is_inferred_error = false; if (return_type_node != 0) { uint32_t ret_first_tok = firstToken(tree, return_type_node); if (ret_first_tok > 0) { uint32_t maybe_bang = ret_first_tok - 1; uint32_t bang_start = tree->tokens.starts[maybe_bang]; if (tree->source[bang_start] == '!') is_inferred_error = true; } } // Extract param type nodes and callconv_expr from proto variant // (AstGen.zig:4253-4254, Ast.zig:1456-1520). uint32_t param_nodes_buf[1]; // buffer for fn_proto_simple/fn_proto_one const uint32_t* param_nodes = NULL; uint32_t params_len = 0; uint32_t callconv_expr_node = 0; // 0 = none (OptionalIndex) if (proto_tag == AST_NODE_FN_PROTO_SIMPLE) { // data.lhs = optional param node, data.rhs = return type. // callconv_expr = .none (Ast.zig:1468). if (proto_data.lhs != 0) { param_nodes_buf[0] = proto_data.lhs; param_nodes = param_nodes_buf; params_len = 1; } } else if (proto_tag == AST_NODE_FN_PROTO_ONE) { // data.lhs = extra_data index → AstFnProtoOne. uint32_t extra_idx = proto_data.lhs; uint32_t param = tree->extra_data.arr[extra_idx]; // AstFnProtoOne.param if (param != 0) { param_nodes_buf[0] = param; param_nodes = param_nodes_buf; params_len = 1; } // AstFnProtoOne.callconv_expr at offset 4 (Ast.zig:4076). callconv_expr_node = tree->extra_data.arr[extra_idx + 4]; } else if (proto_tag == AST_NODE_FN_PROTO_MULTI) { // data.lhs = extra_data index → SubRange{start, end}. // callconv_expr = .none (Ast.zig:1484). uint32_t extra_idx = proto_data.lhs; uint32_t range_start = tree->extra_data.arr[extra_idx]; uint32_t range_end = tree->extra_data.arr[extra_idx + 1]; param_nodes = tree->extra_data.arr + range_start; params_len = range_end - range_start; } else if (proto_tag == AST_NODE_FN_PROTO) { // data.lhs = extra_data index → AstFnProto{params_start, params_end, // ...}. uint32_t extra_idx = proto_data.lhs; uint32_t pstart = tree->extra_data.arr[extra_idx]; // params_start uint32_t pend = tree->extra_data.arr[extra_idx + 1]; // params_end param_nodes = tree->extra_data.arr + pstart; params_len = pend - pstart; // AstFnProto.callconv_expr at offset 5 (Ast.zig:4089). callconv_expr_node = tree->extra_data.arr[extra_idx + 5]; } // decl_gz (called value_gz in caller, decl_gz in fnDeclInner) // (AstGen.zig:4194-4201). GenZir decl_gz; memset(&decl_gz, 0, sizeof(decl_gz)); decl_gz.base.tag = SCOPE_GEN_ZIR; decl_gz.parent = NULL; decl_gz.astgen = ag; decl_gz.decl_node_index = proto_node; decl_gz.decl_line = decl_line; decl_gz.is_comptime = true; decl_gz.instructions_top = ag->scratch_inst_len; decl_gz.break_block = UINT32_MAX; decl_gz.any_defer_node = UINT32_MAX; // --- Parameter iteration (AstGen.zig:4260-4363) --- // Walk params, creating param instructions and ScopeLocalVal entries. // We keep param scopes on the C stack (max 32 params like upstream). Scope* params_scope = &decl_gz.base; ScopeLocalVal param_scopes[32]; uint32_t param_scope_count = 0; // Collect param instruction indices (AstGen.zig:4254, 4360). uint32_t param_insts[32]; uint32_t param_insts_len = 0; // noalias_bits tracking (AstGen.zig:4259). uint32_t noalias_bits = 0; // is_var_args detection (AstGen.zig:4261). bool is_var_args = false; // Parameter iteration using token-based iterator, mirroring upstream // FnProto.Iterator (Ast.zig:2680-2768, AstGen.zig:4260-4363). // The params array only contains type-expression params; anytype params // exist as tokens between/after the type-expression nodes. uint32_t lparen = fn_name_token + 1; // '(' token uint32_t iter_tok_i = lparen + 1; // first token after '(' bool iter_tok_flag = true; // start in token-scanning mode uint32_t iter_param_i = 0; ResultLoc coerced_type_ri = { .tag = RL_COERCED_TY, .data = ZIR_REF_TYPE_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t param_type_i = 0; // index for noalias_bits (AstGen.zig:4262) while (true) { uint32_t name_token = 0; uint32_t comptime_noalias_token = 0; bool is_comptime_param = false; bool is_anytype = false; uint32_t param_type_node = 0; if (!iter_tok_flag) { // Return next param from params array. if (iter_param_i >= params_len) break; param_type_node = param_nodes[iter_param_i]; // Scan backwards from type expression to find // name/comptime/noalias (Ast.zig:2698-2705). uint32_t tok_i = firstToken(tree, param_type_node); while (tok_i > 0) { tok_i--; uint32_t ttag = tree->tokens.tags[tok_i]; if (ttag == TOKEN_COLON) continue; if (ttag == TOKEN_IDENTIFIER) { name_token = tok_i; continue; } if (ttag == TOKEN_KEYWORD_COMPTIME || ttag == TOKEN_KEYWORD_NOALIAS) { comptime_noalias_token = tok_i; continue; } break; } iter_param_i++; iter_tok_i = lastToken(tree, param_type_node) + 1; // Skip comma after param for anytype scanning. if (tree->tokens.tags[iter_tok_i] == TOKEN_COMMA) iter_tok_i++; iter_tok_flag = true; } else { // Token-scanning mode: look for anytype/ellipsis params // (Ast.zig:2721-2767). if (tree->tokens.tags[iter_tok_i] == TOKEN_COMMA) iter_tok_i++; if (tree->tokens.tags[iter_tok_i] == TOKEN_R_PAREN) break; // Skip doc comments. while (tree->tokens.tags[iter_tok_i] == TOKEN_DOC_COMMENT) iter_tok_i++; // Check for ellipsis3 (varargs) (AstGen.zig:4275-4281). if (tree->tokens.tags[iter_tok_i] == TOKEN_ELLIPSIS3) { is_var_args = true; break; } // Check for comptime/noalias prefix. if (tree->tokens.tags[iter_tok_i] == TOKEN_KEYWORD_COMPTIME || tree->tokens.tags[iter_tok_i] == TOKEN_KEYWORD_NOALIAS) { comptime_noalias_token = iter_tok_i; iter_tok_i++; } // Check for name: identifier followed by colon. if (tree->tokens.tags[iter_tok_i] == TOKEN_IDENTIFIER && tree->tokens.tags[iter_tok_i + 1] == TOKEN_COLON) { name_token = iter_tok_i; iter_tok_i += 2; } // Check for anytype keyword. if (tree->tokens.tags[iter_tok_i] == TOKEN_KEYWORD_ANYTYPE) { is_anytype = true; iter_tok_i++; } else { // Not an anytype param; switch to param-array mode. iter_tok_flag = false; continue; } } // Determine is_comptime and noalias from comptime_noalias token // (AstGen.zig:4265-4273). if (comptime_noalias_token != 0 && tree->tokens.tags[comptime_noalias_token] == TOKEN_KEYWORD_NOALIAS) { // Track noalias_bits (AstGen.zig:4266-4269). if (param_type_i < 32) noalias_bits |= (1u << param_type_i); } if (comptime_noalias_token != 0 && tree->tokens.tags[comptime_noalias_token] == TOKEN_KEYWORD_COMPTIME) { is_comptime_param = true; } // Determine param name string (AstGen.zig:4283-4321). // Must be resolved BEFORE type expression to match upstream string // table ordering. uint32_t param_name_str = 0; // NullTerminatedString.empty if (name_token != 0) { uint32_t name_start = tree->tokens.starts[name_token]; char nch = tree->source[name_start]; // Skip "_" params (AstGen.zig:4285-4286). if (nch == '_') { uint32_t next_start = tree->tokens.starts[name_token + 1]; if (next_start == name_start + 1) { // Single underscore: empty name. param_name_str = 0; } else { param_name_str = identAsString(ag, name_token); } } else { param_name_str = identAsString(ag, name_token); } } // Emit param instruction (AstGen.zig:4323-4345). uint32_t param_inst_ref; if (is_anytype) { // anytype parameter: emit param_anytype/param_anytype_comptime // (AstGen.zig:4323-4329). uint32_t anytype_name_token = name_token != 0 ? name_token : (iter_tok_i - 1); ZirInstTag anytype_tag = is_comptime_param ? ZIR_INST_PARAM_ANYTYPE_COMPTIME : ZIR_INST_PARAM_ANYTYPE; uint32_t anytype_inst = addStrTok( &decl_gz, anytype_tag, param_name_str, anytype_name_token); param_inst_ref = anytype_inst + ZIR_REF_START_INDEX; if (param_insts_len < 32) param_insts[param_insts_len++] = anytype_inst; } else { // Type-expression parameter (AstGen.zig:4330-4344). GenZir param_gz = makeSubBlock(&decl_gz, params_scope); uint32_t param_type_ref = fullBodyExpr( ¶m_gz, params_scope, coerced_type_ri, param_type_node); if (ag->has_compile_errors) return; // The break_inline target is the param instruction we're about // to create (AstGen.zig:4336-4337). uint32_t param_inst_expected = ag->inst_len + 1; makeBreakInline(¶m_gz, param_inst_expected, param_type_ref, (int32_t)param_type_node - (int32_t)param_gz.decl_node_index); // Create param instruction (AstGen.zig:4341-4343). ZirInstTag param_tag = is_comptime_param ? ZIR_INST_PARAM_COMPTIME : ZIR_INST_PARAM; uint32_t name_tok_for_src = name_token != 0 ? name_token : tree->nodes.main_tokens[param_type_node]; uint32_t param_inst = addParam(&decl_gz, ¶m_gz, param_tag, name_tok_for_src, param_name_str); (void)param_inst_expected; param_inst_ref = param_inst + ZIR_REF_START_INDEX; if (param_insts_len < 32) param_insts[param_insts_len++] = param_inst; } // Create ScopeLocalVal for this param (AstGen.zig:4349-4359). if (param_name_str != 0 && param_scope_count < 32) { ScopeLocalVal* lv = ¶m_scopes[param_scope_count++]; lv->base.tag = SCOPE_LOCAL_VAL; lv->parent = params_scope; lv->gen_zir = &decl_gz; lv->inst = param_inst_ref; lv->token_src = name_token; lv->name = param_name_str; params_scope = &lv->base; } param_type_i++; } // --- Return type (AstGen.zig:4369-4383) --- GenZir ret_gz = makeSubBlock(&decl_gz, params_scope); uint32_t ret_ref = ZIR_REF_NONE; if (return_type_node != 0) { ret_ref = fullBodyExpr( &ret_gz, params_scope, coerced_type_ri, return_type_node); if (ag->has_compile_errors) return; // If ret_gz produced instructions, add break_inline // (AstGen.zig:4377-4381). if (gzInstructionsLen(&ret_gz) > 0) { // break_inline targets the func instruction (which doesn't // exist yet). We use 0 as placeholder and patch later. makeBreakInline(&ret_gz, 0, ret_ref, AST_NODE_OFFSET_NONE); } } // Map void_type → .none (AstGen.zig:12054). if (ret_ref == ZIR_REF_VOID_TYPE) ret_ref = ZIR_REF_NONE; uint32_t ret_body_len = gzInstructionsLen(&ret_gz); // Copy ret_body before unstacking: body_gz reuses the same scratch area. uint32_t* ret_body = NULL; if (ret_body_len > 0) { ret_body = malloc(ret_body_len * sizeof(uint32_t)); if (!ret_body) abort(); memcpy(ret_body, gzInstructionsSlice(&ret_gz), ret_body_len * sizeof(uint32_t)); } gzUnstack(&ret_gz); // Restore source cursor (AstGen.zig:4387-4388). ag->source_offset = saved_source_offset; ag->source_line = saved_source_line; ag->source_column = saved_source_column; // --- Calling convention (AstGen.zig:4390-4413) --- // Note: cc_gz uses `scope` (= &decl_gz.base), not params_scope. GenZir cc_gz = makeSubBlock(&decl_gz, &decl_gz.base); uint32_t cc_ref = ZIR_REF_NONE; if (callconv_expr_node != 0) { // Explicit callconv(expr) (AstGen.zig:4393-4405). uint32_t cc_ty = addBuiltinValue( &cc_gz, callconv_expr_node, ZIR_BUILTIN_VALUE_CALLING_CONVENTION); ResultLoc cc_ri = { .tag = RL_COERCED_TY, .data = cc_ty, .src_node = 0, .ctx = RI_CTX_NONE }; cc_ref = exprRl(&cc_gz, &decl_gz.base, cc_ri, callconv_expr_node); if (ag->has_compile_errors) { free(ret_body); return; } if (gzInstructionsLen(&cc_gz) > 0) { // break_inline targets the func instruction (patched later). makeBreakInline(&cc_gz, 0, cc_ref, AST_NODE_OFFSET_NONE); } } else if (has_inline_keyword) { // inline keyword → calling_convention_inline // (AstGen.zig:4406-4409). cc_ref = addBuiltinValue( &cc_gz, node, ZIR_BUILTIN_VALUE_CALLING_CONVENTION_INLINE); makeBreakInline(&cc_gz, 0, cc_ref, AST_NODE_OFFSET_NONE); } uint32_t cc_body_len = gzInstructionsLen(&cc_gz); uint32_t* cc_body = NULL; if (cc_body_len > 0) { cc_body = malloc(cc_body_len * sizeof(uint32_t)); if (!cc_body) abort(); memcpy(cc_body, gzInstructionsSlice(&cc_gz), cc_body_len * sizeof(uint32_t)); } gzUnstack(&cc_gz); // --- Body handling --- // For fn_proto* (no body): extern function, emit type only // (AstGen.zig:4136-4164). // For fn_decl (has body): emit function value (AstGen.zig:4197-4201). uint32_t func_ref; if (body_node == 0) { // fn_proto without body: extern function type. // Upstream emits fnProtoExprInner; we SET_ERROR for now as // fnProtoExprInner is not yet ported. // TODO: implement fnProtoExprInner for extern fn support. SET_ERROR(ag); free(ret_body); free(cc_body); gzUnstack(&decl_gz); ag->within_fn = prev_within_fn; return; } // --- Body (AstGen.zig:4415-4424) --- GenZir body_gz; memset(&body_gz, 0, sizeof(body_gz)); body_gz.base.tag = SCOPE_GEN_ZIR; body_gz.parent = params_scope; body_gz.astgen = ag; body_gz.decl_node_index = proto_node; body_gz.decl_line = decl_line; body_gz.is_comptime = false; body_gz.instructions_top = ag->scratch_inst_len; body_gz.any_defer_node = UINT32_MAX; // Set fn_block, fn_ret_ty, fn_var_args for the body // (AstGen.zig:4442-4459). void* prev_fn_block = ag->fn_block; setFnBlock(ag, &body_gz); uint32_t prev_fn_ret_ty = ag->fn_ret_ty; bool prev_fn_var_args = ag->fn_var_args; ag->fn_var_args = is_var_args; if (is_inferred_error || ret_ref == ZIR_REF_NONE) { // Non-void non-trivial return type: emit ret_type instruction. if (ret_body_len > 0 || is_inferred_error) { ZirInstData rtdata; memset(&rtdata, 0, sizeof(rtdata)); rtdata.node = (int32_t)node - (int32_t)body_gz.decl_node_index; ag->fn_ret_ty = addInstruction(&body_gz, ZIR_INST_RET_TYPE, rtdata); } else { ag->fn_ret_ty = ret_ref; // void } } else { // ret_ref is a simple ref (not void, not inferred error). // Still need ret_type instruction if it resolved to an inst. if (ret_ref >= ZIR_REF_START_INDEX) { ZirInstData rtdata; memset(&rtdata, 0, sizeof(rtdata)); rtdata.node = (int32_t)node - (int32_t)body_gz.decl_node_index; ag->fn_ret_ty = addInstruction(&body_gz, ZIR_INST_RET_TYPE, rtdata); } else { ag->fn_ret_ty = ret_ref; } } // Process function body (AstGen.zig:4461-4465). advanceSourceCursorToNode(ag, body_node); uint32_t lbrace_line = ag->source_line - decl_line; uint32_t lbrace_column = ag->source_column; fullBodyExpr(&body_gz, &body_gz.base, RL_NONE_VAL, body_node); ag->within_fn = prev_within_fn; ag->fn_block = prev_fn_block; ag->fn_ret_ty = prev_fn_ret_ty; ag->fn_var_args = prev_fn_var_args; if (ag->has_compile_errors) { free(ret_body); free(cc_body); return; } // Add implicit return at end of function body // (AstGen.zig:4465-4871). if (!endsWithNoReturn(&body_gz)) { ZirInstData rdata; rdata.un_node.operand = ZIR_REF_NONE; rdata.un_node.src_node = (int32_t)node - (int32_t)body_gz.decl_node_index; addInstruction( &body_gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); uint32_t body_last_tok = lastToken(tree, body_node); ZirInstData rdata2; rdata2.un_tok.operand = ZIR_REF_VOID_VALUE; rdata2.un_tok.src_tok = tokenIndexToRelative(&body_gz, body_last_tok); addInstruction(&body_gz, ZIR_INST_RET_IMPLICIT, rdata2); } // Read body before unstacking (AstGen.zig:12215-12218). const uint32_t* fn_body = gzInstructionsSlice(&body_gz); uint32_t fn_body_len = gzInstructionsLen(&body_gz); gzUnstack(&body_gz); // Create func/func_fancy instruction (AstGen.zig:4476-4494, // 12112-12173). bool need_fancy = cc_ref != ZIR_REF_NONE || is_var_args || noalias_bits != 0 || is_noinline; if (need_fancy) { func_ref = addFuncFancy(&decl_gz, node, body_node, decl_inst, ret_ref, ret_body, ret_body_len, cc_ref, cc_body, cc_body_len, fn_body, fn_body_len, param_insts, param_insts_len, lbrace_line, lbrace_column, is_var_args, is_inferred_error, is_noinline, noalias_bits); } else { func_ref = addFunc(&decl_gz, node, body_node, decl_inst, ret_ref, ret_body, ret_body_len, fn_body, fn_body_len, param_insts, param_insts_len, lbrace_line, lbrace_column, is_inferred_error); } // Patch ret_body break_inline to point to func instruction // (AstGen.zig:12199-12202). if (ret_body_len > 0) { uint32_t break_inst = ret_body[ret_body_len - 1]; uint32_t break_payload = ag->inst_datas[break_inst].break_data.payload_index; ag->extra[break_payload + 1] = func_ref - ZIR_REF_START_INDEX; } // Patch cc_body break_inline to point to func instruction // (AstGen.zig:12146-12148). if (cc_body_len > 0) { uint32_t break_inst = cc_body[cc_body_len - 1]; uint32_t break_payload = ag->inst_datas[break_inst].break_data.payload_index; ag->extra[break_payload + 1] = func_ref - ZIR_REF_START_INDEX; } free(ret_body); free(cc_body); // break_inline returning func to declaration (AstGen.zig:4495). // nodeIndexToRelative(decl_node) = node - decl_gz.decl_node_index. makeBreakInline( &decl_gz, decl_inst, func_ref, (int32_t)node - (int32_t)proto_node); // setDeclaration (AstGen.zig:4208-4225). // Linkage: export > normal (AstGen.zig:4217). DeclFlagsId decl_id; if (is_export) decl_id = is_pub ? DECL_ID_PUB_EXPORT_CONST : DECL_ID_EXPORT_CONST; else decl_id = is_pub ? DECL_ID_PUB_CONST_SIMPLE : DECL_ID_CONST_SIMPLE; uint32_t name_str = identAsString(ag, fn_name_token); setDeclaration(ag, decl_inst, (SetDeclArgs) { .src_line = decl_line, .src_column = decl_column, .id = decl_id, .name = name_str, .lib_name = UINT32_MAX, .value_body = gzInstructionsSlice(&decl_gz), .value_body_len = gzInstructionsLen(&decl_gz) }); gzUnstack(&decl_gz); (void)gz; } // --- comptimeDecl (AstGen.zig:4645) --- static void comptimeDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t* decl_idx, uint32_t node) { // makeDeclaration before advanceSourceCursorToNode (AstGen.zig:4663-4665). uint32_t decl_inst = makeDeclaration(ag, node); wip_decl_insts[*decl_idx] = decl_inst; (*decl_idx)++; advanceSourceCursorToNode(ag, node); uint32_t decl_line = ag->source_line; uint32_t decl_column = ag->source_column; // Value sub-block (AstGen.zig:4675-4686). GenZir value_gz; memset(&value_gz, 0, sizeof(value_gz)); value_gz.base.tag = SCOPE_GEN_ZIR; value_gz.parent = NULL; value_gz.astgen = ag; value_gz.decl_node_index = node; value_gz.decl_line = decl_line; value_gz.is_comptime = true; value_gz.instructions_top = ag->scratch_inst_len; value_gz.any_defer_node = UINT32_MAX; // For comptime {}: body is empty block → no instructions generated. // comptime_gz.isEmpty() == true → addBreak(.break_inline, decl_inst, // .void_value) (AstGen.zig:4685-4686) makeBreakInline( &value_gz, decl_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); setDeclaration(ag, decl_inst, (SetDeclArgs) { .src_line = decl_line, .src_column = decl_column, .id = DECL_ID_COMPTIME, .name = 0, .lib_name = UINT32_MAX, .value_body = gzInstructionsSlice(&value_gz), .value_body_len = gzInstructionsLen(&value_gz) }); gzUnstack(&value_gz); (void)gz; } // --- globalVarDecl (AstGen.zig:4498) --- // Extract VarDecl fields from an AST node (Ast.zig:1326-1380). typedef struct { uint32_t mut_token; uint32_t type_node; // 0 = none uint32_t align_node; // 0 = none uint32_t addrspace_node; // 0 = none uint32_t section_node; // 0 = none uint32_t init_node; // UINT32_MAX = none bool is_pub; bool is_extern; bool is_export; bool is_mutable; bool is_threadlocal; uint32_t lib_name_token; // UINT32_MAX = none } VarDeclInfo; static VarDeclInfo extractVarDecl(const Ast* tree, uint32_t node) { AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; uint32_t mut_token = tree->nodes.main_tokens[node]; VarDeclInfo info; memset(&info, 0, sizeof(info)); info.mut_token = mut_token; info.init_node = UINT32_MAX; info.lib_name_token = UINT32_MAX; switch (tag) { case AST_NODE_SIMPLE_VAR_DECL: // lhs = type_node (optional), rhs = init_node (optional) info.type_node = nd.lhs; info.init_node = nd.rhs; break; case AST_NODE_ALIGNED_VAR_DECL: // lhs = align_node, rhs = init_node (optional) info.align_node = nd.lhs; info.init_node = nd.rhs; break; case AST_NODE_GLOBAL_VAR_DECL: { // lhs = extra_data index, rhs = init_node (optional) uint32_t ei = nd.lhs; info.type_node = tree->extra_data.arr[ei + 0]; info.align_node = tree->extra_data.arr[ei + 1]; info.addrspace_node = tree->extra_data.arr[ei + 2]; info.section_node = tree->extra_data.arr[ei + 3]; info.init_node = nd.rhs; break; } case AST_NODE_LOCAL_VAR_DECL: { // lhs = extra_data index, rhs = init_node (optional) uint32_t ei = nd.lhs; info.type_node = tree->extra_data.arr[ei + 0]; info.align_node = tree->extra_data.arr[ei + 1]; info.init_node = nd.rhs; break; } default: break; } // Scan backwards from mut_token to find modifiers (Ast.zig:2003-2025). info.is_mutable = (tree->tokens.tags[mut_token] == TOKEN_KEYWORD_VAR); for (uint32_t i = mut_token; i > 0;) { i--; TokenizerTag ttag = tree->tokens.tags[i]; if (ttag == TOKEN_KEYWORD_EXTERN) info.is_extern = true; else if (ttag == TOKEN_KEYWORD_EXPORT) info.is_export = true; else if (ttag == TOKEN_KEYWORD_PUB) info.is_pub = true; else if (ttag == TOKEN_KEYWORD_THREADLOCAL) info.is_threadlocal = true; else if (ttag == TOKEN_STRING_LITERAL) info.lib_name_token = i; else break; } return info; } // Compute DeclFlagsId from VarDecl properties (AstGen.zig:13916-13972). static DeclFlagsId computeVarDeclId(bool is_mutable, bool is_pub, bool is_extern, bool is_export, bool is_threadlocal, bool has_type_body, bool has_special_body, bool has_lib_name) { if (!is_mutable) { // const if (is_extern) { if (is_pub) { if (has_lib_name || has_special_body) return DECL_ID_PUB_EXTERN_CONST; return DECL_ID_PUB_EXTERN_CONST_SIMPLE; } if (has_lib_name || has_special_body) return DECL_ID_EXTERN_CONST; return DECL_ID_EXTERN_CONST_SIMPLE; } if (is_export) return is_pub ? DECL_ID_PUB_EXPORT_CONST : DECL_ID_EXPORT_CONST; if (is_pub) { if (has_special_body) return DECL_ID_PUB_CONST; if (has_type_body) return DECL_ID_PUB_CONST_TYPED; return DECL_ID_PUB_CONST_SIMPLE; } if (has_special_body) return DECL_ID_CONST; if (has_type_body) return DECL_ID_CONST_TYPED; return DECL_ID_CONST_SIMPLE; } // var if (is_extern) { if (is_pub) { if (is_threadlocal) return DECL_ID_PUB_EXTERN_VAR_THREADLOCAL; return DECL_ID_PUB_EXTERN_VAR; } if (is_threadlocal) return DECL_ID_EXTERN_VAR_THREADLOCAL; return DECL_ID_EXTERN_VAR; } if (is_export) { if (is_pub) { if (is_threadlocal) return DECL_ID_PUB_EXPORT_VAR_THREADLOCAL; return DECL_ID_PUB_EXPORT_VAR; } if (is_threadlocal) return DECL_ID_EXPORT_VAR_THREADLOCAL; return DECL_ID_EXPORT_VAR; } if (is_pub) { if (is_threadlocal) return DECL_ID_PUB_VAR_THREADLOCAL; if (has_special_body || has_type_body) return DECL_ID_PUB_VAR; return DECL_ID_PUB_VAR_SIMPLE; } if (is_threadlocal) return DECL_ID_VAR_THREADLOCAL; if (has_special_body || has_type_body) return DECL_ID_VAR; return DECL_ID_VAR_SIMPLE; } // Mirrors nameStratExpr (AstGen.zig:1160-1199). // Checks if node is a container decl or @Type builtin; if so, dispatches // with the given name_strategy. Returns true if handled (result stored in // *out_ref), false if caller should fall back to expr(). static bool nameStratExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, uint8_t name_strategy, uint32_t* out_ref) { const AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; (void)rl; // Used by builtinReify (not yet implemented). switch (tag) { case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: *out_ref = containerDecl(gz, scope, node, name_strategy); return true; // @Type builtin: upstream calls builtinReify (AstGen.zig:1186-1196). // Not yet implemented; fall through to expr(). default: return false; } } static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t* decl_idx, uint32_t node) { const Ast* tree = ag->tree; VarDeclInfo vd = extractVarDecl(tree, node); uint32_t name_token = vd.mut_token + 1; // "threadlocal variable cannot be constant" (AstGen.zig:4526-4528). if (vd.is_threadlocal && !vd.is_mutable) { SET_ERROR(ag); return; } // lib_name validation (AstGen.zig:4531-4540). uint32_t lib_name = UINT32_MAX; if (vd.lib_name_token != UINT32_MAX) { uint32_t li, ll; strLitAsString(ag, vd.lib_name_token, &li, &ll); // "library name cannot contain null bytes" (AstGen.zig:4534-4535). if (memchr(ag->string_bytes + li, 0, ll) != NULL) { SET_ERROR(ag); return; } // "library name cannot be empty" (AstGen.zig:4536-4537). if (ll == 0) { SET_ERROR(ag); return; } lib_name = li; } // advanceSourceCursorToNode before makeDeclaration (AstGen.zig:4542-4546). advanceSourceCursorToNode(ag, node); uint32_t decl_column = ag->source_column; uint32_t decl_inst = makeDeclaration(ag, node); wip_decl_insts[*decl_idx] = decl_inst; (*decl_idx)++; // "extern variables have no initializers" (AstGen.zig:4549-4556). if (vd.init_node != UINT32_MAX && vd.init_node != 0) { if (vd.is_extern) { SET_ERROR(ag); return; } } else { // "variables must be initialized" (AstGen.zig:4557-4561). if (!vd.is_extern) { SET_ERROR(ag); return; } } // "unable to infer variable type" (AstGen.zig:4563-4565). if (vd.is_extern && vd.type_node == 0) { SET_ERROR(ag); return; } // Set up type sub-block (AstGen.zig:4574-4582). GenZir type_gz; memset(&type_gz, 0, sizeof(type_gz)); type_gz.base.tag = SCOPE_GEN_ZIR; type_gz.astgen = ag; type_gz.decl_node_index = node; type_gz.instructions_top = ag->scratch_inst_len; type_gz.decl_line = ag->source_line; type_gz.is_comptime = true; type_gz.any_defer_node = UINT32_MAX; if (vd.type_node != 0) { uint32_t type_inst = typeExpr(&type_gz, &type_gz.base, vd.type_node); makeBreakInline(&type_gz, decl_inst, type_inst, 0); } // Record type_gz boundary for slicing. uint32_t type_top = ag->scratch_inst_len; // Align sub-block (AstGen.zig:4585-4591). GenZir align_gz; memset(&align_gz, 0, sizeof(align_gz)); align_gz.base.tag = SCOPE_GEN_ZIR; align_gz.astgen = ag; align_gz.decl_node_index = node; align_gz.instructions_top = type_top; align_gz.decl_line = ag->source_line; align_gz.is_comptime = true; align_gz.any_defer_node = UINT32_MAX; if (vd.align_node != 0) { // coerced_align_ri = { .rl = .{ .coerced_ty = .u29_type } } // (AstGen.zig:389, 4589). ResultLoc align_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_U29_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t align_inst = exprRl(&align_gz, &align_gz.base, align_rl, vd.align_node); makeBreakInline(&align_gz, decl_inst, align_inst, 0); } uint32_t align_top = ag->scratch_inst_len; // Linksection sub-block (AstGen.zig:4593-4599). GenZir linksection_gz; memset(&linksection_gz, 0, sizeof(linksection_gz)); linksection_gz.base.tag = SCOPE_GEN_ZIR; linksection_gz.astgen = ag; linksection_gz.decl_node_index = node; linksection_gz.instructions_top = align_top; linksection_gz.decl_line = ag->source_line; linksection_gz.is_comptime = true; linksection_gz.any_defer_node = UINT32_MAX; if (vd.section_node != 0) { // coerced_linksection_ri = { .rl = .{ .coerced_ty = // .slice_const_u8_type } } (AstGen.zig:390, 4597). ResultLoc ls_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_SLICE_CONST_U8_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t ls_inst = exprRl( &linksection_gz, &linksection_gz.base, ls_rl, vd.section_node); makeBreakInline(&linksection_gz, decl_inst, ls_inst, 0); } uint32_t linksection_top = ag->scratch_inst_len; // Addrspace sub-block (AstGen.zig:4601-4608). GenZir addrspace_gz; memset(&addrspace_gz, 0, sizeof(addrspace_gz)); addrspace_gz.base.tag = SCOPE_GEN_ZIR; addrspace_gz.astgen = ag; addrspace_gz.decl_node_index = node; addrspace_gz.instructions_top = linksection_top; addrspace_gz.decl_line = ag->source_line; addrspace_gz.is_comptime = true; addrspace_gz.any_defer_node = UINT32_MAX; if (vd.addrspace_node != 0) { // Upstream: addBuiltinValue(addrspace_node, .address_space) then // coerced_ty with that result (AstGen.zig:4605-4606). uint32_t addrspace_ty = addBuiltinValue( &addrspace_gz, vd.addrspace_node, ZIR_BUILTIN_VALUE_ADDRESS_SPACE); ResultLoc as_rl = { .tag = RL_COERCED_TY, .data = addrspace_ty, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t as_inst = exprRl( &addrspace_gz, &addrspace_gz.base, as_rl, vd.addrspace_node); makeBreakInline(&addrspace_gz, decl_inst, as_inst, 0); } uint32_t addrspace_top = ag->scratch_inst_len; // Value sub-block (AstGen.zig:4610-4621). GenZir value_gz; memset(&value_gz, 0, sizeof(value_gz)); value_gz.base.tag = SCOPE_GEN_ZIR; value_gz.astgen = ag; value_gz.decl_node_index = node; value_gz.instructions_top = addrspace_top; value_gz.decl_line = ag->source_line; value_gz.is_comptime = true; value_gz.any_defer_node = UINT32_MAX; if (vd.init_node != UINT32_MAX && vd.init_node != 0) { // Upstream: coerced_ty = decl_inst.toRef() when type_node present // (AstGen.zig:4614-4616). ResultLoc init_rl; memset(&init_rl, 0, sizeof(init_rl)); if (vd.type_node != 0) { init_rl.tag = RL_COERCED_TY; init_rl.data = decl_inst + ZIR_REF_START_INDEX; } else { init_rl.tag = RL_NONE; } // nameStratExpr: check if init is container decl (AstGen.zig:4617). uint32_t init_ref; if (!nameStratExpr(&value_gz, &value_gz.base, init_rl, vd.init_node, 0 /* parent */, &init_ref)) { init_ref = exprRl(&value_gz, &value_gz.base, init_rl, vd.init_node); } makeBreakInline(&value_gz, decl_inst, init_ref, 0); } // Compute body slices (instructionsSliceUpto). const uint32_t* type_body = ag->scratch_instructions + type_gz.instructions_top; uint32_t type_body_len = type_top - type_gz.instructions_top; const uint32_t* align_body = ag->scratch_instructions + align_gz.instructions_top; uint32_t align_body_len = align_top - align_gz.instructions_top; const uint32_t* ls_body = ag->scratch_instructions + linksection_gz.instructions_top; uint32_t ls_body_len = linksection_top - linksection_gz.instructions_top; const uint32_t* as_body = ag->scratch_instructions + addrspace_gz.instructions_top; uint32_t as_body_len = addrspace_top - addrspace_gz.instructions_top; const uint32_t* val_body = gzInstructionsSlice(&value_gz); uint32_t val_body_len = gzInstructionsLen(&value_gz); bool has_type_body = (type_body_len > 0); bool has_special_body = (align_body_len > 0 || ls_body_len > 0 || as_body_len > 0); bool has_lib_name = (vd.lib_name_token != UINT32_MAX); uint32_t name_str = identAsString(ag, name_token); DeclFlagsId decl_id = computeVarDeclId(vd.is_mutable, vd.is_pub, vd.is_extern, vd.is_export, vd.is_threadlocal, has_type_body, has_special_body, has_lib_name); setDeclaration(ag, decl_inst, (SetDeclArgs) { .src_line = ag->source_line, .src_column = decl_column, .id = decl_id, .name = name_str, .lib_name = lib_name, .type_body = type_body, .type_body_len = type_body_len, .align_body = align_body, .align_body_len = align_body_len, .linksection_body = ls_body, .linksection_body_len = ls_body_len, .addrspace_body = as_body, .addrspace_body_len = as_body_len, .value_body = val_body, .value_body_len = val_body_len }); gzUnstack(&value_gz); (void)gz; } // --- nodeImpliesMoreThanOnePossibleValue (AstGen.zig:10548) --- // Check if an identifier is a primitive type with more than one value. static bool identImpliesMoreThanOnePossibleValue( const Ast* tree, uint32_t main_token) { uint32_t start = tree->tokens.starts[main_token]; const char* src = tree->source + start; // Match known primitive types that have more than one possible value. // (AstGen.zig:10729-10766) if (src[0] == 'u' || src[0] == 'i') { // u8, u16, u32, u64, u128, u1, u29, usize, i8, i16, i32, i64, i128, // isize char c1 = src[1]; if (c1 >= '0' && c1 <= '9') return true; if (c1 == 's') // usize, isize return (src[2] == 'i' && src[3] == 'z' && src[4] == 'e'); } if (src[0] == 'f') { // f16, f32, f64, f80, f128 char c1 = src[1]; if (c1 >= '0' && c1 <= '9') return true; } if (src[0] == 'b' && src[1] == 'o' && src[2] == 'o' && src[3] == 'l' && !(src[4] >= 'a' && src[4] <= 'z') && !(src[4] >= 'A' && src[4] <= 'Z') && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_') return true; if (src[0] == 'c' && src[1] == '_') return true; // c_int, c_long, etc. if (src[0] == 'a' && src[1] == 'n' && src[2] == 'y') { // anyerror, anyframe, anyopaque return true; } if (src[0] == 'c' && src[1] == 'o' && src[2] == 'm' && src[3] == 'p' && src[4] == 't' && src[5] == 'i' && src[6] == 'm' && src[7] == 'e') return true; // comptime_float, comptime_int if (src[0] == 't' && src[1] == 'y' && src[2] == 'p' && src[3] == 'e' && !(src[4] >= 'a' && src[4] <= 'z') && !(src[4] >= 'A' && src[4] <= 'Z') && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_') return true; return false; } static bool nodeImpliesMoreThanOnePossibleValue( const Ast* tree, uint32_t node) { uint32_t cur = node; while (1) { AstNodeTag tag = tree->nodes.tags[cur]; switch (tag) { // Pointer/optional/array/anyframe types → true // (AstGen.zig:10718-10725) case AST_NODE_PTR_TYPE_ALIGNED: case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: case AST_NODE_OPTIONAL_TYPE: case AST_NODE_ANYFRAME_TYPE: case AST_NODE_ARRAY_TYPE_SENTINEL: return true; // Forward to LHS: try, comptime, nosuspend // (AstGen.zig:10710-10713) case AST_NODE_TRY: case AST_NODE_COMPTIME: case AST_NODE_NOSUSPEND: cur = tree->nodes.datas[cur].lhs; continue; // Forward to LHS: grouped_expression, unwrap_optional // (AstGen.zig:10714-10716) case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_UNWRAP_OPTIONAL: cur = tree->nodes.datas[cur].lhs; continue; // Identifier: check primitives (AstGen.zig:10727-10780) case AST_NODE_IDENTIFIER: return identImpliesMoreThanOnePossibleValue( tree, tree->nodes.main_tokens[cur]); default: return false; } } } // --- nodeImpliesComptimeOnly (AstGen.zig:10787) --- static bool identImpliesComptimeOnly(const Ast* tree, uint32_t main_token) { uint32_t start = tree->tokens.starts[main_token]; const char* src = tree->source + start; // Only comptime_float, comptime_int, type → true // (AstGen.zig:11010-11013) if (src[0] == 'c' && src[1] == 'o' && src[2] == 'm' && src[3] == 'p' && src[4] == 't' && src[5] == 'i' && src[6] == 'm' && src[7] == 'e') return true; // comptime_float, comptime_int if (src[0] == 't' && src[1] == 'y' && src[2] == 'p' && src[3] == 'e' && !(src[4] >= 'a' && src[4] <= 'z') && !(src[4] >= 'A' && src[4] <= 'Z') && !(src[4] >= '0' && src[4] <= '9') && src[4] != '_') return true; return false; } static bool nodeImpliesComptimeOnly(const Ast* tree, uint32_t node) { uint32_t cur = node; while (1) { AstNodeTag tag = tree->nodes.tags[cur]; switch (tag) { // Function prototypes → true (AstGen.zig:10950-10955) case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: return true; // Forward to LHS: try, comptime, nosuspend case AST_NODE_TRY: case AST_NODE_COMPTIME: case AST_NODE_NOSUSPEND: cur = tree->nodes.datas[cur].lhs; continue; case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_UNWRAP_OPTIONAL: cur = tree->nodes.datas[cur].lhs; continue; // Identifier: check primitives case AST_NODE_IDENTIFIER: return identImpliesComptimeOnly( tree, tree->nodes.main_tokens[cur]); default: return false; } } } // --- WipMembers (AstGen.zig:3989) --- // Tracks decl indices, field bit-flags, and per-field data during container // processing. All data lives in a single malloc'd array laid out as: // [decls (decl_count)] [field_bits (ceil)] [fields (up to field_count*max)] // Bodies are tracked separately in a dynamic array. typedef struct { uint32_t* payload; // malloc'd array uint32_t payload_top; // always 0 (start of decls region) uint32_t field_bits_start; uint32_t fields_start; uint32_t fields_end; uint32_t decl_index; uint32_t field_index; // Bodies scratch: dynamically grown array for field type/align/init // bodies. uint32_t* bodies; uint32_t bodies_len; uint32_t bodies_cap; } WipMembers; // Parameterized init (AstGen.zig:3989 WipMembers.init). static WipMembers wipMembersInitEx(uint32_t decl_count, uint32_t field_count, uint32_t bits_per_field, uint32_t max_field_size) { uint32_t fields_per_u32 = bits_per_field > 0 ? 32 / bits_per_field : 0; uint32_t field_bits_start = decl_count; uint32_t bit_words = (field_count > 0 && fields_per_u32 > 0) ? (field_count + fields_per_u32 - 1) / fields_per_u32 : 0; uint32_t fields_start = field_bits_start + bit_words; uint32_t payload_end = fields_start + field_count * max_field_size; uint32_t alloc_size = payload_end > 0 ? payload_end : 1; uint32_t* payload = calloc(alloc_size, sizeof(uint32_t)); if (!payload) exit(1); WipMembers wm; memset(&wm, 0, sizeof(wm)); wm.payload = payload; wm.payload_top = 0; wm.field_bits_start = field_bits_start; wm.fields_start = fields_start; wm.fields_end = fields_start; wm.decl_index = 0; wm.field_index = 0; wm.bodies = NULL; wm.bodies_len = 0; wm.bodies_cap = 0; return wm; } static WipMembers wipMembersInit(uint32_t decl_count, uint32_t field_count) { // bits_per_field = 4, max_field_size = 5 return wipMembersInitEx(decl_count, field_count, 4, 5); } static void wipMembersDeinit(WipMembers* wm) { free(wm->payload); free(wm->bodies); } static void wipMembersNextDecl(WipMembers* wm, uint32_t decl_inst) { wm->payload[wm->payload_top + wm->decl_index] = decl_inst; wm->decl_index++; } // bits_per_field = 4: bits[0]=have_align, bits[1]=have_value, // bits[2]=is_comptime, bits[3]=have_type_body static void wipMembersNextField(WipMembers* wm, bool bits[4]) { uint32_t fields_per_u32 = 8; // 32 / 4 uint32_t index = wm->field_bits_start + wm->field_index / fields_per_u32; uint32_t bit_bag = (wm->field_index % fields_per_u32 == 0) ? 0 : wm->payload[index]; bit_bag >>= 4; for (int i = 0; i < 4; i++) { bit_bag |= ((uint32_t)(bits[i] ? 1 : 0)) << (32 - 4 + i); } wm->payload[index] = bit_bag; wm->field_index++; } static void wipMembersAppendToField(WipMembers* wm, uint32_t data) { wm->payload[wm->fields_end] = data; wm->fields_end++; } static void wipMembersFinishBits(WipMembers* wm) { uint32_t fields_per_u32 = 8; // 32 / 4 uint32_t empty_field_slots = fields_per_u32 - (wm->field_index % fields_per_u32); if (wm->field_index > 0 && empty_field_slots < fields_per_u32) { uint32_t index = wm->field_bits_start + wm->field_index / fields_per_u32; wm->payload[index] >>= (empty_field_slots * 4); } } // bits_per_field = 1: bits[0]=have_value (for enum fields). static void wipMembersNextFieldEnum(WipMembers* wm, bool have_value) { uint32_t fields_per_u32 = 32; // 32 / 1 uint32_t index = wm->field_bits_start + wm->field_index / fields_per_u32; uint32_t bit_bag = (wm->field_index % fields_per_u32 == 0) ? 0 : wm->payload[index]; bit_bag >>= 1; bit_bag |= ((uint32_t)(have_value ? 1 : 0)) << 31; wm->payload[index] = bit_bag; wm->field_index++; } static void wipMembersFinishBitsEnum(WipMembers* wm) { uint32_t fields_per_u32 = 32; // 32 / 1 uint32_t empty_field_slots = fields_per_u32 - (wm->field_index % fields_per_u32); if (wm->field_index > 0 && empty_field_slots < fields_per_u32) { uint32_t index = wm->field_bits_start + wm->field_index / fields_per_u32; wm->payload[index] >>= empty_field_slots; } } // Returns pointer to decls region and its length. static const uint32_t* wipMembersDeclsSlice( const WipMembers* wm, uint32_t* out_len) { *out_len = wm->decl_index; return wm->payload + wm->payload_top; } // Returns pointer to fields region (field_bits + field_data) and its length. static const uint32_t* wipMembersFieldsSlice( const WipMembers* wm, uint32_t* out_len) { *out_len = wm->fields_end - wm->field_bits_start; return wm->payload + wm->field_bits_start; } // Append body instructions to the WipMembers bodies scratch. static void wipMembersBodiesAppend( WipMembers* wm, const uint32_t* data, uint32_t len) { if (wm->bodies_len + len > wm->bodies_cap) { uint32_t new_cap = wm->bodies_cap == 0 ? 64 : wm->bodies_cap * 2; while (new_cap < wm->bodies_len + len) new_cap *= 2; wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t)); if (!wm->bodies) exit(1); wm->bodies_cap = new_cap; } memcpy(wm->bodies + wm->bodies_len, data, len * sizeof(uint32_t)); wm->bodies_len += len; } // Append body instructions with ref_table fixups to wm->bodies. static void wipMembersBodiesAppendWithFixups( WipMembers* wm, AstGenCtx* ag, const uint32_t* body, uint32_t body_len) { for (uint32_t i = 0; i < body_len; i++) { uint32_t inst = body[i]; // Grow if needed. if (wm->bodies_len + 1 > wm->bodies_cap) { uint32_t new_cap = wm->bodies_cap == 0 ? 64 : wm->bodies_cap * 2; wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t)); if (!wm->bodies) exit(1); wm->bodies_cap = new_cap; } wm->bodies[wm->bodies_len++] = inst; // Check for ref fixup. uint32_t ref_inst; while (refTableFetchRemove(ag, inst, &ref_inst)) { if (wm->bodies_len + 1 > wm->bodies_cap) { uint32_t new_cap = wm->bodies_cap * 2; wm->bodies = realloc(wm->bodies, new_cap * sizeof(uint32_t)); if (!wm->bodies) exit(1); wm->bodies_cap = new_cap; } wm->bodies[wm->bodies_len++] = ref_inst; inst = ref_inst; } } } // --- containerDecl (AstGen.zig:5468) --- // Handles container declarations as expressions (struct{}, enum{}, etc.). static uint32_t containerDecl( GenZir* gz, Scope* scope, uint32_t node, uint8_t name_strategy) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // Extract members based on node type (Ast.zig:2459-2470). uint32_t members_buf[2]; const uint32_t* members; uint32_t members_len; switch (tag) { case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: { // lhs and rhs are optional member nodes (0 = none). members_len = 0; if (nd.lhs != 0) members_buf[members_len++] = nd.lhs; if (nd.rhs != 0) members_buf[members_len++] = nd.rhs; members = members_buf; break; } case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: { // extra_data[lhs..rhs] contains members. members = tree->extra_data.arr + nd.lhs; members_len = nd.rhs - nd.lhs; break; } case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: { // lhs is arg node, rhs is extra index → SubRange(start, end). uint32_t start = tree->extra_data.arr[nd.rhs]; uint32_t end = tree->extra_data.arr[nd.rhs + 1]; members = tree->extra_data.arr + start; members_len = end - start; break; } default: SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } // Save/clear fn_block for nested containers (AstGen.zig:5480-5482). void* prev_fn_block = ag->fn_block; ag->fn_block = NULL; // Extract arg node for container_decl_arg variants (AstGen.zig:5638). // For enum(u8), lhs is the arg node. uint32_t arg_node = 0; // 0 = none switch (tag) { case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: arg_node = nd.lhs; break; default: break; } // Dispatch based on container keyword (AstGen.zig:5485-5536). uint32_t main_token = tree->nodes.main_tokens[node]; TokenizerTag kw_tag = tree->tokens.tags[main_token]; uint32_t decl_inst; switch (kw_tag) { case TOKEN_KEYWORD_STRUCT: { // Extract layout from token before main_token (AstGen.zig:5489-5493). // auto=0, extern=1, packed=2. uint8_t layout = 0; // auto if (main_token > 0) { TokenizerTag prev_tag = tree->tokens.tags[main_token - 1]; if (prev_tag == TOKEN_KEYWORD_PACKED) layout = 2; else if (prev_tag == TOKEN_KEYWORD_EXTERN) layout = 1; } decl_inst = structDeclInner(ag, gz, node, members, members_len, layout, arg_node, name_strategy); break; } case TOKEN_KEYWORD_ENUM: decl_inst = enumDeclInner( ag, gz, node, members, members_len, arg_node, name_strategy); break; default: // union/opaque: fall back to struct for now. decl_inst = structDeclInner( ag, gz, node, members, members_len, 0, 0, name_strategy); break; } (void)scope; ag->fn_block = prev_fn_block; return decl_inst + ZIR_REF_START_INDEX; } // --- EnumDecl.Small packing (Zir.zig EnumDecl.Small) --- typedef struct { bool has_tag_type; bool has_captures_len; bool has_body_len; bool has_fields_len; bool has_decls_len; uint8_t name_strategy; // 2 bits bool nonexhaustive; } EnumDeclSmall; static uint16_t packEnumDeclSmall(EnumDeclSmall s) { uint16_t r = 0; if (s.has_tag_type) r |= (1u << 0); if (s.has_captures_len) r |= (1u << 1); if (s.has_body_len) r |= (1u << 2); if (s.has_fields_len) r |= (1u << 3); if (s.has_decls_len) r |= (1u << 4); r |= (uint16_t)(s.name_strategy & 0x3u) << 5; if (s.nonexhaustive) r |= (1u << 7); return r; } // Mirrors GenZir.setEnum (AstGen.zig:13064-13123). static void setEnum(AstGenCtx* ag, uint32_t inst, uint32_t src_node, uint32_t tag_type, uint32_t captures_len, uint32_t body_len, uint32_t fields_len, uint32_t decls_len, bool nonexhaustive, uint8_t name_strategy) { EnumDeclSmall small; memset(&small, 0, sizeof(small)); small.has_tag_type = (tag_type != ZIR_REF_NONE); small.has_captures_len = (captures_len != 0); small.has_body_len = (body_len != 0); small.has_fields_len = (fields_len != 0); small.has_decls_len = (decls_len != 0); small.name_strategy = name_strategy; small.nonexhaustive = nonexhaustive; ensureExtraCapacity(ag, 6 + 5); uint32_t payload_index = ag->extra_len; // fields_hash (4 words): zero-filled; hash comparison skipped in tests. ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = 0; ag->extra[ag->extra_len++] = ag->source_line; ag->extra[ag->extra_len++] = src_node; // Trailing data in upstream order (AstGen.zig:13092-13106). if (small.has_tag_type) ag->extra[ag->extra_len++] = tag_type; if (small.has_captures_len) ag->extra[ag->extra_len++] = captures_len; if (small.has_body_len) ag->extra[ag->extra_len++] = body_len; if (small.has_fields_len) ag->extra[ag->extra_len++] = fields_len; if (small.has_decls_len) ag->extra[ag->extra_len++] = decls_len; ag->inst_tags[inst] = ZIR_INST_EXTENDED; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_ENUM_DECL; data.extended.small = packEnumDeclSmall(small); data.extended.operand = payload_index; ag->inst_datas[inst] = data; } // Returns true if the identifier token at `ident_token` is "_". static bool tokenIsUnderscore(const Ast* tree, uint32_t ident_token) { uint32_t start = tree->tokens.starts[ident_token]; const char* src = tree->source; if (src[start] != '_') return false; // Check that the next character is not alphanumeric/underscore // (i.e., the identifier is exactly "_"). char next = src[start + 1]; if ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') || (next >= '0' && next <= '9') || next == '_') return false; return true; } // --- enumDeclInner (AstGen.zig:5508-5728) --- // Handles enum container declarations. // arg_node: the tag type expression node (e.g. u8 in enum(u8)), 0 if none. static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len, uint32_t arg_node, uint8_t name_strategy) { const Ast* tree = ag->tree; // --- First pass: count fields, values, decls, detect nonexhaustive --- // (AstGen.zig:5513-5590) uint32_t total_fields = 0; uint32_t decl_count = 0; uint32_t nonexhaustive_index = UINT32_MAX; // index into members[] for (uint32_t i = 0; i < members_len; i++) { uint32_t member_node = members[i]; AstNodeTag mtag = tree->nodes.tags[member_node]; switch (mtag) { case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { uint32_t main_token = tree->nodes.main_tokens[member_node]; // Check for "_" (nonexhaustive marker). if (tokenIsUnderscore(tree, main_token)) { nonexhaustive_index = i; continue; } total_fields++; break; } default: decl_count++; break; } } bool nonexhaustive = (nonexhaustive_index != UINT32_MAX); uint32_t decl_inst = reserveInstructionIndex(ag); gzAppendInstruction(gz, decl_inst); advanceSourceCursorToNode(ag, node); // scanContainer to register names in string table (AstGen.zig:5635). scanContainer(ag, members, members_len); // Set up block_scope for tag value expressions (AstGen.zig:5624-5632). GenZir block_scope; memset(&block_scope, 0, sizeof(block_scope)); block_scope.base.tag = SCOPE_GEN_ZIR; block_scope.parent = NULL; block_scope.astgen = ag; block_scope.decl_node_index = node; block_scope.decl_line = ag->source_line; block_scope.is_comptime = true; block_scope.instructions_top = ag->scratch_inst_len; block_scope.any_defer_node = UINT32_MAX; // Evaluate tag type argument if present (AstGen.zig:5638-5641). uint32_t arg_inst = ZIR_REF_NONE; if (arg_node != 0) arg_inst = typeExpr(&block_scope, &block_scope.base, arg_node); // WipMembers with bits_per_field=1, max_field_size=2 (AstGen.zig:5645). WipMembers wm = wipMembersInitEx(decl_count, total_fields, 1, 2); // --- Second pass: process members (AstGen.zig:5656-5693) --- for (uint32_t i = 0; i < members_len; i++) { uint32_t member_node = members[i]; // Skip nonexhaustive marker field (AstGen.zig:5657-5658). if (i == nonexhaustive_index) continue; AstNodeTag mtag = tree->nodes.tags[member_node]; switch (mtag) { case AST_NODE_COMPTIME: comptimeDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: globalVarDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_FN_DECL: fnDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_TEST_DECL: testDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { // Enum field (AstGen.zig:5669-5692). uint32_t main_token = tree->nodes.main_tokens[member_node]; uint32_t field_name = identAsString(ag, main_token); wipMembersAppendToField(&wm, field_name); // Extract value expression. AstData mnd = tree->nodes.datas[member_node]; uint32_t value_node = 0; if (mtag == AST_NODE_CONTAINER_FIELD_INIT) { value_node = mnd.rhs; } else if (mtag == AST_NODE_CONTAINER_FIELD && mnd.rhs != 0) { value_node = tree->extra_data.arr[mnd.rhs + 1]; } bool have_value = (value_node != 0); wipMembersNextFieldEnum(&wm, have_value); // Evaluate tag value expression (AstGen.zig:5690-5691). if (have_value) { ResultLoc val_rl = { .tag = RL_COERCED_TY, .data = arg_inst, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t tag_value_inst = exprRl( &block_scope, &block_scope.base, val_rl, value_node); wipMembersAppendToField(&wm, tag_value_inst); } break; } default: SET_ERROR(ag); break; } } // Emit break_inline if block_scope has instructions // (AstGen.zig:5695-5697). if (gzInstructionsLen(&block_scope) > 0) { addBreak(&block_scope, ZIR_INST_BREAK_INLINE, decl_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); } uint32_t raw_body_len = gzInstructionsLen(&block_scope); const uint32_t* body = gzInstructionsSlice(&block_scope); uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len); // setEnum (AstGen.zig:5705-5715). setEnum(ag, decl_inst, node, arg_inst, 0 /* captures_len */, body_len, total_fields, decl_count, nonexhaustive, name_strategy); wipMembersFinishBitsEnum(&wm); // Append trailing data (AstGen.zig:5718-5725): // captures (none), decls, body, fields. uint32_t decls_len_out; const uint32_t* decls_slice = wipMembersDeclsSlice(&wm, &decls_len_out); uint32_t fields_len_out; const uint32_t* fields_slice = wipMembersFieldsSlice(&wm, &fields_len_out); ensureExtraCapacity(ag, decls_len_out + body_len + fields_len_out); for (uint32_t i = 0; i < decls_len_out; i++) ag->extra[ag->extra_len++] = decls_slice[i]; // Body instructions with fixups (AstGen.zig:5724). for (uint32_t i = 0; i < raw_body_len; i++) appendPossiblyRefdBodyInst(ag, body[i]); // Fields (bit bags + field data). for (uint32_t i = 0; i < fields_len_out; i++) ag->extra[ag->extra_len++] = fields_slice[i]; gzUnstack(&block_scope); wipMembersDeinit(&wm); return decl_inst; } // --- tupleDecl (AstGen.zig:5192) --- static uint32_t tupleDecl(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len, uint8_t layout, uint32_t backing_int_node) { const Ast* tree = ag->tree; // layout must be auto for tuples (AstGen.zig:5204-5207). if (layout != 0) { SET_ERROR(ag); return reserveInstructionIndex(ag); } // tuples don't support backing int (AstGen.zig:5209-5211). if (backing_int_node != 0) { SET_ERROR(ag); return reserveInstructionIndex(ag); } // Build fields_start scratch area: for each field, type ref + init ref. uint32_t fields_start = ag->extra_len; // We use extra as scratch temporarily; will finalize below. // Actually, upstream uses astgen.scratch; we use a temporary buffer. uint32_t* tuple_scratch = NULL; uint32_t tuple_scratch_len = 0; uint32_t tuple_scratch_cap = 0; for (uint32_t i = 0; i < members_len; i++) { uint32_t member_node = members[i]; AstNodeTag mtag = tree->nodes.tags[member_node]; // Non-field nodes are errors in tuples (AstGen.zig:5224-5238). if (mtag != AST_NODE_CONTAINER_FIELD_INIT && mtag != AST_NODE_CONTAINER_FIELD_ALIGN && mtag != AST_NODE_CONTAINER_FIELD) { SET_ERROR(ag); free(tuple_scratch); return reserveInstructionIndex(ag); } // Extract field info. uint32_t main_token = tree->nodes.main_tokens[member_node]; AstData nd = tree->nodes.datas[member_node]; uint32_t type_node = nd.lhs; uint32_t align_node = 0; uint32_t value_node = 0; bool has_comptime_token = false; switch (mtag) { case AST_NODE_CONTAINER_FIELD_INIT: value_node = nd.rhs; break; case AST_NODE_CONTAINER_FIELD_ALIGN: align_node = nd.rhs; break; case AST_NODE_CONTAINER_FIELD: if (nd.rhs != 0) { align_node = tree->extra_data.arr[nd.rhs]; value_node = tree->extra_data.arr[nd.rhs + 1]; } break; default: break; } if (main_token > 0 && tree->tokens.tags[main_token - 1] == TOKEN_KEYWORD_COMPTIME) { has_comptime_token = true; } // Check tuple_like: must be tuple-like (AstGen.zig:5240-5241). bool tuple_like = tree->tokens.tags[main_token] != TOKEN_IDENTIFIER || tree->tokens.tags[main_token + 1] != TOKEN_COLON; if (!tuple_like) { // Named field in tuple: error (AstGen.zig:5241). SET_ERROR(ag); free(tuple_scratch); return reserveInstructionIndex(ag); } // Tuple fields cannot have alignment (AstGen.zig:5244-5246). if (align_node != 0) { SET_ERROR(ag); free(tuple_scratch); return reserveInstructionIndex(ag); } // Non-comptime tuple field with default init: error // (AstGen.zig:5248-5250). if (value_node != 0 && !has_comptime_token) { SET_ERROR(ag); free(tuple_scratch); return reserveInstructionIndex(ag); } // Comptime field without default init: error // (AstGen.zig:5252-5254). if (value_node == 0 && has_comptime_token) { SET_ERROR(ag); free(tuple_scratch); return reserveInstructionIndex(ag); } // Type expression (AstGen.zig:5256). uint32_t field_type_ref = typeExpr(gz, &gz->base, type_node); // Grow scratch buffer. if (tuple_scratch_len + 2 > tuple_scratch_cap) { uint32_t new_cap = tuple_scratch_cap == 0 ? 16 : tuple_scratch_cap * 2; tuple_scratch = realloc(tuple_scratch, new_cap * sizeof(uint32_t)); if (!tuple_scratch) exit(1); tuple_scratch_cap = new_cap; } tuple_scratch[tuple_scratch_len++] = field_type_ref; // Default init (AstGen.zig:5259-5264). if (value_node != 0) { ResultLoc init_rl = { .tag = RL_COERCED_TY, .data = field_type_ref, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t field_init_ref = comptimeExpr(gz, &gz->base, init_rl, value_node, COMPTIME_REASON_TUPLE_FIELD_DEFAULT_VALUE); tuple_scratch[tuple_scratch_len++] = field_init_ref; } else { tuple_scratch[tuple_scratch_len++] = ZIR_REF_NONE; } } if (members_len > 65535) { SET_ERROR(ag); free(tuple_scratch); return reserveInstructionIndex(ag); } uint16_t fields_len = (uint16_t)members_len; // Write TupleDecl payload (AstGen.zig:5274-5286). (void)fields_start; ensureExtraCapacity(ag, 1 + tuple_scratch_len); uint32_t payload_index = ag->extra_len; // src_node as node offset relative to gz->decl_node_index. ag->extra[ag->extra_len++] = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index); for (uint32_t i = 0; i < tuple_scratch_len; i++) ag->extra[ag->extra_len++] = tuple_scratch[i]; free(tuple_scratch); // Emit extended instruction (AstGen.zig:5279-5286). ensureInstCapacity(ag, 1); uint32_t idx = ag->inst_len; ag->inst_tags[idx] = ZIR_INST_EXTENDED; ZirInstData data; memset(&data, 0, sizeof(data)); data.extended.opcode = (uint16_t)ZIR_EXT_TUPLE_DECL; data.extended.small = fields_len; data.extended.operand = payload_index; ag->inst_datas[idx] = data; ag->inst_len++; gzAppendInstruction(gz, idx); return idx; } // --- structDeclInner (AstGen.zig:4926) --- static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len, uint8_t layout, uint32_t backing_int_node, uint8_t name_strategy) { const Ast* tree = ag->tree; // Tuple detection (AstGen.zig:4939-4950). // Scan for tuple-like fields; if any found, dispatch to tupleDecl. for (uint32_t i = 0; i < members_len; i++) { uint32_t member_node = members[i]; AstNodeTag mtag = tree->nodes.tags[member_node]; if (mtag != AST_NODE_CONTAINER_FIELD_INIT && mtag != AST_NODE_CONTAINER_FIELD_ALIGN && mtag != AST_NODE_CONTAINER_FIELD) continue; uint32_t main_token = tree->nodes.main_tokens[member_node]; bool tuple_like = tree->tokens.tags[main_token] != TOKEN_IDENTIFIER || tree->tokens.tags[main_token + 1] != TOKEN_COLON; if (tuple_like) { if (node == 0) { // Root node: file cannot be a tuple // (AstGen.zig:4946). SET_ERROR(ag); return reserveInstructionIndex(ag); } return tupleDecl( ag, gz, node, members, members_len, layout, backing_int_node); } } uint32_t decl_inst = reserveInstructionIndex(ag); gzAppendInstruction(gz, decl_inst); // Fast path: no members, no backing int (AstGen.zig:4954-4970). if (members_len == 0 && backing_int_node == 0) { StructDeclSmall small; memset(&small, 0, sizeof(small)); small.layout = layout; small.name_strategy = name_strategy; setStruct(ag, decl_inst, node, small, 0, 0, 0); return decl_inst; } // Non-empty container (AstGen.zig:4973-5189). advanceSourceCursorToNode(ag, node); uint32_t decl_count = scanContainer(ag, members, members_len); uint32_t field_count = members_len - decl_count; WipMembers wm = wipMembersInit(decl_count, field_count); // Set up block_scope for field type/align/init expressions. // (AstGen.zig:4986-4994) GenZir block_scope; memset(&block_scope, 0, sizeof(block_scope)); block_scope.base.tag = SCOPE_GEN_ZIR; block_scope.parent = NULL; block_scope.astgen = ag; block_scope.decl_node_index = node; block_scope.decl_line = gz->decl_line; // Fix #7: use gz->decl_line block_scope.is_comptime = true; block_scope.instructions_top = ag->scratch_inst_len; block_scope.any_defer_node = UINT32_MAX; // Handle backing_int_node for packed structs (AstGen.zig:5000-5024). // We store the raw body instructions and apply fixups at the final append. uint32_t backing_int_body_raw_len = 0; uint32_t backing_int_ref = ZIR_REF_NONE; uint32_t* backing_int_body_raw = NULL; if (backing_int_node != 0) { if (layout != 2) { // not packed SET_ERROR(ag); // non-packed struct with backing int } else { backing_int_ref = typeExpr(&block_scope, &block_scope.base, backing_int_node); if (gzInstructionsLen(&block_scope) > 0) { if (!endsWithNoReturn(&block_scope)) { makeBreakInline(&block_scope, decl_inst, backing_int_ref, AST_NODE_OFFSET_NONE); } backing_int_body_raw_len = gzInstructionsLen(&block_scope); const uint32_t* body = gzInstructionsSlice(&block_scope); backing_int_body_raw = malloc(backing_int_body_raw_len * sizeof(uint32_t)); if (!backing_int_body_raw) exit(1); memcpy(backing_int_body_raw, body, backing_int_body_raw_len * sizeof(uint32_t)); ag->scratch_inst_len = block_scope.instructions_top; } } } bool known_non_opv = false; bool known_comptime_only = false; bool any_comptime_fields = false; bool any_aligned_fields = false; bool any_default_inits = false; // Process each member (AstGen.zig:5060-5147). for (uint32_t i = 0; i < members_len; i++) { uint32_t member_node = members[i]; AstNodeTag mtag = tree->nodes.tags[member_node]; switch (mtag) { case AST_NODE_COMPTIME: comptimeDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_SIMPLE_VAR_DECL: globalVarDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_TEST_DECL: testDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_FN_DECL: fnDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; // fn_proto* dispatch (AstGen.zig:5809-5813, issue #9). case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: fnDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_USINGNAMESPACE: case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: globalVarDecl(ag, gz, wm.payload, &wm.decl_index, member_node); break; case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { // Extract field info from AST node (Ast.zig:1413-1454). uint32_t main_token = tree->nodes.main_tokens[member_node]; AstData nd = tree->nodes.datas[member_node]; uint32_t type_node = nd.lhs; uint32_t align_node = 0; uint32_t value_node = 0; bool has_comptime_token = false; switch (mtag) { case AST_NODE_CONTAINER_FIELD_INIT: // lhs = type_expr, rhs = value_expr (optional, 0=none) value_node = nd.rhs; break; case AST_NODE_CONTAINER_FIELD_ALIGN: // lhs = type_expr, rhs = align_expr align_node = nd.rhs; break; case AST_NODE_CONTAINER_FIELD: // lhs = type_expr, rhs = extra index to {align, value} if (nd.rhs != 0) { align_node = tree->extra_data.arr[nd.rhs]; value_node = tree->extra_data.arr[nd.rhs + 1]; } break; default: break; } // Check for comptime token preceding main_token // (Ast.zig:2071-2082). if (main_token > 0 && tree->tokens.tags[main_token - 1] == TOKEN_KEYWORD_COMPTIME) { has_comptime_token = true; } // Field name (AstGen.zig:5068). // convertToNonTupleLike: for struct fields, if type_expr is // an identifier node, it's actually a named field with the // identifier as the name (AstGen.zig:5069-5070). uint32_t field_name = identAsString(ag, main_token); wipMembersAppendToField(&wm, field_name); // Type expression: struct field missing type is an error // (AstGen.zig:5073-5075, issue #12). if (type_node == 0) { SET_ERROR(ag); break; } bool have_type_body = false; uint32_t field_type = 0; field_type = typeExpr(&block_scope, &block_scope.base, type_node); have_type_body = (gzInstructionsLen(&block_scope) > 0); bool have_align = (align_node != 0); bool have_value = (value_node != 0); bool is_comptime = has_comptime_token; // Packed/extern struct comptime field error // (AstGen.zig:5083-5087, issue #15). if (is_comptime) { if (layout == 2 || layout == 1) { // packed or extern struct fields cannot be comptime. SET_ERROR(ag); break; } any_comptime_fields = true; } else { // (AstGen.zig:5089-5093) known_non_opv = known_non_opv || nodeImpliesMoreThanOnePossibleValue(tree, type_node); known_comptime_only = known_comptime_only || nodeImpliesComptimeOnly(tree, type_node); } bool field_bits[4] = { have_align, have_value, is_comptime, have_type_body }; wipMembersNextField(&wm, field_bits); if (have_type_body) { // Emit break_inline to carry the type value // (AstGen.zig:5097-5099). if (!endsWithNoReturn(&block_scope)) { makeBreakInline(&block_scope, decl_inst, field_type, AST_NODE_OFFSET_NONE); } uint32_t raw_len = gzInstructionsLen(&block_scope); const uint32_t* body = gzInstructionsSlice(&block_scope); uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_len); uint32_t bodies_before = wm.bodies_len; wipMembersBodiesAppendWithFixups(&wm, ag, body, raw_len); (void)bodies_before; wipMembersAppendToField(&wm, body_len); // Reset block_scope. ag->scratch_inst_len = block_scope.instructions_top; } else { wipMembersAppendToField(&wm, field_type); } if (have_align) { // Packed struct fields cannot have alignment overrides // (AstGen.zig:5111-5113, issue #15). if (layout == 2) { // packed SET_ERROR(ag); break; } any_aligned_fields = true; // Use coerced_align_ri: RL_COERCED_TY with u29_type // (AstGen.zig:5115, issue #14). ResultLoc align_rl = { .tag = RL_COERCED_TY, .data = ZIR_REF_U29_TYPE, .src_node = 0, .ctx = RI_CTX_NONE }; uint32_t align_ref = exprRl( &block_scope, &block_scope.base, align_rl, align_node); if (!endsWithNoReturn(&block_scope)) { makeBreakInline(&block_scope, decl_inst, align_ref, AST_NODE_OFFSET_NONE); } uint32_t raw_len = gzInstructionsLen(&block_scope); const uint32_t* body = gzInstructionsSlice(&block_scope); uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_len); wipMembersBodiesAppendWithFixups(&wm, ag, body, raw_len); wipMembersAppendToField(&wm, body_len); ag->scratch_inst_len = block_scope.instructions_top; } if (have_value) { any_default_inits = true; // Use coerced_ty with decl_inst when field type is present // (AstGen.zig:5132, issue #11). ResultLoc value_rl; if (field_type == 0) { value_rl = RL_NONE_VAL; } else { uint32_t dref = decl_inst + ZIR_REF_START_INDEX; value_rl = (ResultLoc) { .tag = RL_COERCED_TY, .data = dref, .src_node = 0, .ctx = RI_CTX_NONE }; } uint32_t default_ref = exprRl( &block_scope, &block_scope.base, value_rl, value_node); if (!endsWithNoReturn(&block_scope)) { makeBreakInline(&block_scope, decl_inst, default_ref, AST_NODE_OFFSET_NONE); } uint32_t raw_len = gzInstructionsLen(&block_scope); const uint32_t* body = gzInstructionsSlice(&block_scope); uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_len); wipMembersBodiesAppendWithFixups(&wm, ag, body, raw_len); wipMembersAppendToField(&wm, body_len); ag->scratch_inst_len = block_scope.instructions_top; } else if (has_comptime_token) { // Comptime field without default init: error // (AstGen.zig:5144-5145, issue #13). SET_ERROR(ag); } break; } default: SET_ERROR(ag); break; } } wipMembersFinishBits(&wm); // setStruct (AstGen.zig:5152-5166). StructDeclSmall small; memset(&small, 0, sizeof(small)); small.has_decls_len = (decl_count > 0); small.has_fields_len = (field_count > 0); small.has_backing_int = (backing_int_ref != ZIR_REF_NONE); small.known_non_opv = known_non_opv; small.known_comptime_only = known_comptime_only; small.any_comptime_fields = any_comptime_fields; small.any_default_inits = any_default_inits; small.any_aligned_fields = any_aligned_fields; small.layout = layout; small.name_strategy = name_strategy; setStruct(ag, decl_inst, node, small, 0, field_count, decl_count); // Append: captures (none), backing_int, decls, fields, bodies // (AstGen.zig:5172-5186). uint32_t decls_len; const uint32_t* decls_slice = wipMembersDeclsSlice(&wm, &decls_len); uint32_t fields_len; const uint32_t* fields_slice = wipMembersFieldsSlice(&wm, &fields_len); // Compute backing_int_body_len (with fixups) for capacity estimation. uint32_t backing_int_body_len = 0; if (backing_int_body_raw_len > 0) { backing_int_body_len = countBodyLenAfterFixups( ag, backing_int_body_raw, backing_int_body_raw_len); } ensureExtraCapacity(ag, (backing_int_ref != ZIR_REF_NONE ? backing_int_body_len + 2 : 0) + decls_len + fields_len + wm.bodies_len); // backing_int (AstGen.zig:5176-5183). if (backing_int_ref != ZIR_REF_NONE) { ag->extra[ag->extra_len++] = backing_int_body_len; if (backing_int_body_len == 0) { ag->extra[ag->extra_len++] = backing_int_ref; } else { for (uint32_t j = 0; j < backing_int_body_raw_len; j++) appendPossiblyRefdBodyInst(ag, backing_int_body_raw[j]); } } free(backing_int_body_raw); for (uint32_t i = 0; i < decls_len; i++) ag->extra[ag->extra_len++] = decls_slice[i]; for (uint32_t i = 0; i < fields_len; i++) ag->extra[ag->extra_len++] = fields_slice[i]; for (uint32_t i = 0; i < wm.bodies_len; i++) ag->extra[ag->extra_len++] = wm.bodies[i]; gzUnstack(&block_scope); wipMembersDeinit(&wm); return decl_inst; } // --- AstRlAnnotate (AstRlAnnotate.zig) --- // Pre-pass to determine which AST nodes need result locations. typedef struct { bool have_type; bool have_ptr; } RlResultInfo; #define RL_RI_NONE ((RlResultInfo) { false, false }) #define RL_RI_TYPED_PTR ((RlResultInfo) { true, true }) #define RL_RI_INFERRED_PTR ((RlResultInfo) { false, true }) #define RL_RI_TYPE_ONLY ((RlResultInfo) { true, false }) // Block for label tracking (AstRlAnnotate.zig:56-62). typedef struct RlBlock { struct RlBlock* parent; uint32_t label_token; // UINT32_MAX = no label bool is_loop; RlResultInfo ri; bool consumes_res_ptr; } RlBlock; static void nodesNeedRlAdd(AstGenCtx* ag, uint32_t node) { if (ag->nodes_need_rl_len >= ag->nodes_need_rl_cap) { uint32_t new_cap = ag->nodes_need_rl_cap == 0 ? 16 : ag->nodes_need_rl_cap * 2; ag->nodes_need_rl = realloc(ag->nodes_need_rl, new_cap * sizeof(uint32_t)); ag->nodes_need_rl_cap = new_cap; } ag->nodes_need_rl[ag->nodes_need_rl_len++] = node; } static bool nodesNeedRlContains(const AstGenCtx* ag, uint32_t node) { for (uint32_t i = 0; i < ag->nodes_need_rl_len; i++) { if (ag->nodes_need_rl[i] == node) return true; } return false; } // Compare two identifier tokens by their source text. // Handles both regular identifiers and @"..."-quoted identifiers. static bool rlTokenIdentEqual( const Ast* tree, uint32_t tok_a, uint32_t tok_b) { const char* src = tree->source; uint32_t a_start = tree->tokens.starts[tok_a]; uint32_t b_start = tree->tokens.starts[tok_b]; bool a_quoted = (src[a_start] == '@'); bool b_quoted = (src[b_start] == '@'); if (a_quoted != b_quoted) return false; if (a_quoted) { // Both are @"..."-quoted: skip '@"' prefix, compare up to '"'. uint32_t ai = a_start + 2; uint32_t bi = b_start + 2; for (;;) { char ca = src[ai]; char cb = src[bi]; if (ca == '"' && cb == '"') return true; if (ca == '"' || cb == '"') return false; if (ca != cb) return false; ai++; bi++; } } for (uint32_t i = 0;; i++) { char ca = src[a_start + i]; char cb = src[b_start + i]; bool a_id = (ca >= 'a' && ca <= 'z') || (ca >= 'A' && ca <= 'Z') || (ca >= '0' && ca <= '9') || ca == '_'; bool b_id = (cb >= 'a' && cb <= 'z') || (cb >= 'A' && cb <= 'Z') || (cb >= '0' && cb <= '9') || cb == '_'; if (!a_id && !b_id) return true; if (!a_id || !b_id) return false; if (ca != cb) return false; } } // Forward declarations. static bool rlExpr( AstGenCtx* ag, uint32_t node, RlBlock* block, RlResultInfo ri); static void rlContainerDecl(AstGenCtx* ag, RlBlock* block, uint32_t node); static bool rlBlockExpr(AstGenCtx* ag, RlBlock* parent_block, RlResultInfo ri, uint32_t node, const uint32_t* stmts, uint32_t count); static bool rlBuiltinCall(AstGenCtx* ag, RlBlock* block, uint32_t node, const uint32_t* args, uint32_t nargs); // containerDecl (AstRlAnnotate.zig:89-127). static void rlContainerDecl(AstGenCtx* ag, RlBlock* block, uint32_t node) { const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; // Extract arg and members depending on variant. // All container decls: recurse arg with type_only, members with none. // (The keyword type — struct/union/enum/opaque — doesn't matter for RL.) uint32_t member_buf[2]; const uint32_t* members = NULL; uint32_t members_len = 0; uint32_t arg_node = 0; // 0 = no arg switch (tag) { case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: { uint32_t idx = 0; if (nd.lhs != 0) member_buf[idx++] = nd.lhs; if (nd.rhs != 0) member_buf[idx++] = nd.rhs; members = member_buf; members_len = idx; break; } case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: members = tree->extra_data.arr + nd.lhs; members_len = nd.rhs - nd.lhs; break; case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: { arg_node = nd.lhs; uint32_t extra_idx = nd.rhs; uint32_t start = tree->extra_data.arr[extra_idx]; uint32_t end = tree->extra_data.arr[extra_idx + 1]; members = tree->extra_data.arr + start; members_len = end - start; break; } default: return; } if (arg_node != 0) (void)rlExpr(ag, arg_node, block, RL_RI_TYPE_ONLY); for (uint32_t i = 0; i < members_len; i++) (void)rlExpr(ag, members[i], block, RL_RI_NONE); } // blockExpr (AstRlAnnotate.zig:787-814). static bool rlBlockExpr(AstGenCtx* ag, RlBlock* parent_block, RlResultInfo ri, uint32_t node, const uint32_t* stmts, uint32_t count) { const Ast* tree = ag->tree; uint32_t lbrace = tree->nodes.main_tokens[node]; bool is_labeled = (lbrace >= 2 && tree->tokens.tags[lbrace - 1] == TOKEN_COLON && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER); if (is_labeled) { RlBlock new_block; new_block.parent = parent_block; new_block.label_token = lbrace - 2; new_block.is_loop = false; new_block.ri = ri; new_block.consumes_res_ptr = false; for (uint32_t i = 0; i < count; i++) (void)rlExpr(ag, stmts[i], &new_block, RL_RI_NONE); if (new_block.consumes_res_ptr) nodesNeedRlAdd(ag, node); return new_block.consumes_res_ptr; } else { for (uint32_t i = 0; i < count; i++) (void)rlExpr(ag, stmts[i], parent_block, RL_RI_NONE); return false; } } // builtinCall (AstRlAnnotate.zig:816-1100). // Simplified: no builtin currently consumes its result location, // so we just recurse into all args with RL_RI_NONE. static bool rlBuiltinCall(AstGenCtx* ag, RlBlock* block, uint32_t node, const uint32_t* args, uint32_t nargs) { (void)node; for (uint32_t i = 0; i < nargs; i++) (void)rlExpr(ag, args[i], block, RL_RI_NONE); return false; } // expr (AstRlAnnotate.zig:130-771). static bool rlExpr( AstGenCtx* ag, uint32_t node, RlBlock* block, RlResultInfo ri) { const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; switch (tag) { // Unreachable nodes (AstRlAnnotate.zig:133-142). case AST_NODE_ROOT: case AST_NODE_SWITCH_CASE_ONE: case AST_NODE_SWITCH_CASE_INLINE_ONE: case AST_NODE_SWITCH_CASE: case AST_NODE_SWITCH_CASE_INLINE: case AST_NODE_SWITCH_RANGE: case AST_NODE_FOR_RANGE: case AST_NODE_ASM_OUTPUT: case AST_NODE_ASM_INPUT: return false; // unreachable in upstream // errdefer (AstRlAnnotate.zig:144-147). case AST_NODE_ERRDEFER: (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE); return false; // defer (AstRlAnnotate.zig:148-151). case AST_NODE_DEFER: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; // container_field (AstRlAnnotate.zig:153-167). case AST_NODE_CONTAINER_FIELD_INIT: { // lhs = type_expr, rhs = value_expr if (nd.lhs != 0) (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); if (nd.rhs != 0) (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; } case AST_NODE_CONTAINER_FIELD_ALIGN: { // lhs = type_expr, rhs = align_expr if (nd.lhs != 0) (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); if (nd.rhs != 0) (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; } case AST_NODE_CONTAINER_FIELD: { // lhs = type_expr, rhs = extra index to {align_expr, value_expr} if (nd.lhs != 0) (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); if (nd.rhs != 0) { uint32_t align_node = tree->extra_data.arr[nd.rhs]; uint32_t value_node = tree->extra_data.arr[nd.rhs + 1]; if (align_node != 0) (void)rlExpr(ag, align_node, block, RL_RI_TYPE_ONLY); if (value_node != 0) (void)rlExpr(ag, value_node, block, RL_RI_TYPE_ONLY); } return false; } // test_decl (AstRlAnnotate.zig:168-171). case AST_NODE_TEST_DECL: (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE); return false; // var_decl (AstRlAnnotate.zig:172-202). case AST_NODE_GLOBAL_VAR_DECL: case AST_NODE_LOCAL_VAR_DECL: case AST_NODE_SIMPLE_VAR_DECL: case AST_NODE_ALIGNED_VAR_DECL: { uint32_t type_node = 0; uint32_t init_node = 0; uint32_t mut_token = tree->nodes.main_tokens[node]; if (tag == AST_NODE_SIMPLE_VAR_DECL) { type_node = nd.lhs; init_node = nd.rhs; } else if (tag == AST_NODE_LOCAL_VAR_DECL || tag == AST_NODE_GLOBAL_VAR_DECL) { type_node = tree->extra_data.arr[nd.lhs]; init_node = nd.rhs; } else { // ALIGNED_VAR_DECL init_node = nd.rhs; } RlResultInfo init_ri; if (type_node != 0) { (void)rlExpr(ag, type_node, block, RL_RI_TYPE_ONLY); init_ri = RL_RI_TYPED_PTR; } else { init_ri = RL_RI_INFERRED_PTR; } if (init_node == 0) return false; bool is_const = (tree->source[tree->tokens.starts[mut_token]] == 'c'); if (is_const) { bool init_consumes_rl = rlExpr(ag, init_node, block, init_ri); if (init_consumes_rl) nodesNeedRlAdd(ag, node); return false; } else { (void)rlExpr(ag, init_node, block, init_ri); return false; } } // assign (AstRlAnnotate.zig:212-217). case AST_NODE_ASSIGN: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPED_PTR); return false; // compound assign (AstRlAnnotate.zig:218-240). case AST_NODE_ASSIGN_SHL: case AST_NODE_ASSIGN_SHL_SAT: case AST_NODE_ASSIGN_SHR: case AST_NODE_ASSIGN_BIT_AND: case AST_NODE_ASSIGN_BIT_OR: case AST_NODE_ASSIGN_BIT_XOR: case AST_NODE_ASSIGN_DIV: case AST_NODE_ASSIGN_SUB: case AST_NODE_ASSIGN_SUB_WRAP: case AST_NODE_ASSIGN_SUB_SAT: case AST_NODE_ASSIGN_MOD: case AST_NODE_ASSIGN_ADD: case AST_NODE_ASSIGN_ADD_WRAP: case AST_NODE_ASSIGN_ADD_SAT: case AST_NODE_ASSIGN_MUL: case AST_NODE_ASSIGN_MUL_WRAP: case AST_NODE_ASSIGN_MUL_SAT: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE); return false; // shl/shr (AstRlAnnotate.zig:241-246). case AST_NODE_SHL: case AST_NODE_SHR: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; // binary arithmetic/comparison (AstRlAnnotate.zig:247-274). case AST_NODE_ADD: case AST_NODE_ADD_WRAP: case AST_NODE_ADD_SAT: case AST_NODE_SUB: case AST_NODE_SUB_WRAP: case AST_NODE_SUB_SAT: case AST_NODE_MUL: case AST_NODE_MUL_WRAP: case AST_NODE_MUL_SAT: case AST_NODE_DIV: case AST_NODE_MOD: case AST_NODE_SHL_SAT: case AST_NODE_BIT_AND: case AST_NODE_BIT_OR: case AST_NODE_BIT_XOR: case AST_NODE_BANG_EQUAL: case AST_NODE_EQUAL_EQUAL: case AST_NODE_GREATER_THAN: case AST_NODE_GREATER_OR_EQUAL: case AST_NODE_LESS_THAN: case AST_NODE_LESS_OR_EQUAL: case AST_NODE_ARRAY_CAT: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE); return false; // array_mult (AstRlAnnotate.zig:276-281). case AST_NODE_ARRAY_MULT: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; // error_union, merge_error_sets (AstRlAnnotate.zig:282-287). case AST_NODE_ERROR_UNION: case AST_NODE_MERGE_ERROR_SETS: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE); return false; // bool_and, bool_or (AstRlAnnotate.zig:288-295). case AST_NODE_BOOL_AND: case AST_NODE_BOOL_OR: (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; // bool_not (AstRlAnnotate.zig:296-299). case AST_NODE_BOOL_NOT: (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); return false; // bit_not, negation, negation_wrap (AstRlAnnotate.zig:300-303). case AST_NODE_BIT_NOT: case AST_NODE_NEGATION: case AST_NODE_NEGATION_WRAP: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; // Leaves (AstRlAnnotate.zig:305-320). case AST_NODE_IDENTIFIER: case AST_NODE_STRING_LITERAL: case AST_NODE_MULTILINE_STRING_LITERAL: case AST_NODE_NUMBER_LITERAL: case AST_NODE_UNREACHABLE_LITERAL: case AST_NODE_ASM_SIMPLE: case AST_NODE_ASM: case AST_NODE_ASM_LEGACY: case AST_NODE_ENUM_LITERAL: case AST_NODE_ERROR_VALUE: case AST_NODE_ANYFRAME_LITERAL: case AST_NODE_CONTINUE: case AST_NODE_CHAR_LITERAL: case AST_NODE_ERROR_SET_DECL: return false; // builtin_call (AstRlAnnotate.zig:322-330). case AST_NODE_BUILTIN_CALL_TWO: case AST_NODE_BUILTIN_CALL_TWO_COMMA: { uint32_t args[2]; uint32_t nargs = 0; if (nd.lhs != 0) args[nargs++] = nd.lhs; if (nd.rhs != 0) args[nargs++] = nd.rhs; return rlBuiltinCall(ag, block, node, args, nargs); } case AST_NODE_BUILTIN_CALL: case AST_NODE_BUILTIN_CALL_COMMA: { uint32_t start = nd.lhs; uint32_t end = nd.rhs; return rlBuiltinCall( ag, block, node, tree->extra_data.arr + start, end - start); } // call (AstRlAnnotate.zig:332-351). case AST_NODE_CALL_ONE: case AST_NODE_CALL_ONE_COMMA: { (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); if (nd.rhs != 0) (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; } case AST_NODE_CALL: case AST_NODE_CALL_COMMA: { (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); uint32_t start = tree->extra_data.arr[nd.rhs]; uint32_t end = tree->extra_data.arr[nd.rhs + 1]; for (uint32_t i = start; i < end; i++) (void)rlExpr(ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY); return false; } // return (AstRlAnnotate.zig:353-361). case AST_NODE_RETURN: if (nd.lhs != 0) { bool ret_consumes_rl = rlExpr(ag, nd.lhs, block, RL_RI_TYPED_PTR); if (ret_consumes_rl) nodesNeedRlAdd(ag, node); } return false; // field_access (AstRlAnnotate.zig:363-367). case AST_NODE_FIELD_ACCESS: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; // if_simple, if (AstRlAnnotate.zig:369-387). case AST_NODE_IF_SIMPLE: case AST_NODE_IF: { uint32_t cond_node = nd.lhs; uint32_t then_node, else_node = 0; if (tag == AST_NODE_IF_SIMPLE) { then_node = nd.rhs; } else { then_node = tree->extra_data.arr[nd.rhs]; else_node = tree->extra_data.arr[nd.rhs + 1]; } // Detect payload/error token. uint32_t last_cond_tok = lastToken(tree, cond_node); uint32_t pipe_tok = last_cond_tok + 2; bool has_payload = (pipe_tok < tree->tokens.len && tree->tokens.tags[pipe_tok] == TOKEN_PIPE); bool has_error = false; if (else_node != 0) { uint32_t else_tok = lastToken(tree, then_node) + 1; has_error = (else_tok + 1 < tree->tokens.len && tree->tokens.tags[else_tok + 1] == TOKEN_PIPE); } if (has_error || has_payload) (void)rlExpr(ag, cond_node, block, RL_RI_NONE); else (void)rlExpr(ag, cond_node, block, RL_RI_TYPE_ONLY); if (else_node != 0) { bool then_uses = rlExpr(ag, then_node, block, ri); bool else_uses = rlExpr(ag, else_node, block, ri); bool uses_rl = then_uses || else_uses; if (uses_rl) nodesNeedRlAdd(ag, node); return uses_rl; } else { (void)rlExpr(ag, then_node, block, RL_RI_NONE); return false; } } // while (AstRlAnnotate.zig:389-419). case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: { uint32_t cond_node = nd.lhs; uint32_t body_node, cont_node = 0, else_node = 0; if (tag == AST_NODE_WHILE_SIMPLE) { body_node = nd.rhs; } else if (tag == AST_NODE_WHILE_CONT) { cont_node = tree->extra_data.arr[nd.rhs]; body_node = tree->extra_data.arr[nd.rhs + 1]; } else { cont_node = tree->extra_data.arr[nd.rhs]; body_node = tree->extra_data.arr[nd.rhs + 1]; else_node = tree->extra_data.arr[nd.rhs + 2]; } uint32_t main_tok = tree->nodes.main_tokens[node]; uint32_t tok_i = main_tok; if (tok_i >= 1 && tree->tokens.tags[tok_i - 1] == TOKEN_KEYWORD_INLINE) tok_i = tok_i - 1; bool is_labeled = (tok_i >= 2 && tree->tokens.tags[tok_i - 1] == TOKEN_COLON && tree->tokens.tags[tok_i - 2] == TOKEN_IDENTIFIER); uint32_t label_token = is_labeled ? tok_i - 2 : UINT32_MAX; // Detect payload/error. uint32_t last_cond_tok = lastToken(tree, cond_node); uint32_t pipe_tok = last_cond_tok + 2; bool has_payload = (pipe_tok < tree->tokens.len && tree->tokens.tags[pipe_tok] == TOKEN_PIPE); // Error token detection for while: check for else |err|. bool has_error = false; if (else_node != 0) { uint32_t else_tok = lastToken(tree, body_node) + 1; has_error = (else_tok + 1 < tree->tokens.len && tree->tokens.tags[else_tok + 1] == TOKEN_PIPE); } if (has_error || has_payload) (void)rlExpr(ag, cond_node, block, RL_RI_NONE); else (void)rlExpr(ag, cond_node, block, RL_RI_TYPE_ONLY); RlBlock new_block; new_block.parent = block; new_block.label_token = label_token; new_block.is_loop = true; new_block.ri = ri; new_block.consumes_res_ptr = false; if (cont_node != 0) (void)rlExpr(ag, cont_node, &new_block, RL_RI_NONE); (void)rlExpr(ag, body_node, &new_block, RL_RI_NONE); bool else_consumes = false; if (else_node != 0) else_consumes = rlExpr(ag, else_node, block, ri); if (new_block.consumes_res_ptr || else_consumes) { nodesNeedRlAdd(ag, node); return true; } return false; } // for (AstRlAnnotate.zig:421-454). case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: { uint32_t input_buf[16]; const uint32_t* inputs = NULL; uint32_t num_inputs = 0; uint32_t body_node = 0; uint32_t else_node = 0; if (tag == AST_NODE_FOR_SIMPLE) { input_buf[0] = nd.lhs; inputs = input_buf; num_inputs = 1; body_node = nd.rhs; } else { AstFor for_data; memcpy(&for_data, &nd.rhs, sizeof(AstFor)); num_inputs = for_data.inputs; if (num_inputs > 16) num_inputs = 16; for (uint32_t i = 0; i < num_inputs; i++) input_buf[i] = tree->extra_data.arr[nd.lhs + i]; inputs = input_buf; body_node = tree->extra_data.arr[nd.lhs + num_inputs]; if (for_data.has_else) else_node = tree->extra_data.arr[nd.lhs + num_inputs + 1]; } uint32_t main_tok = tree->nodes.main_tokens[node]; uint32_t for_tok_i = main_tok; if (for_tok_i >= 1 && tree->tokens.tags[for_tok_i - 1] == TOKEN_KEYWORD_INLINE) for_tok_i = for_tok_i - 1; bool is_labeled = (for_tok_i >= 2 && tree->tokens.tags[for_tok_i - 1] == TOKEN_COLON && tree->tokens.tags[for_tok_i - 2] == TOKEN_IDENTIFIER); uint32_t label_token = is_labeled ? for_tok_i - 2 : UINT32_MAX; for (uint32_t i = 0; i < num_inputs; i++) { uint32_t input = inputs[i]; if (tree->nodes.tags[input] == AST_NODE_FOR_RANGE) { AstData range_nd = tree->nodes.datas[input]; (void)rlExpr(ag, range_nd.lhs, block, RL_RI_TYPE_ONLY); if (range_nd.rhs != 0) (void)rlExpr(ag, range_nd.rhs, block, RL_RI_TYPE_ONLY); } else { (void)rlExpr(ag, input, block, RL_RI_NONE); } } RlBlock new_block; new_block.parent = block; new_block.label_token = label_token; new_block.is_loop = true; new_block.ri = ri; new_block.consumes_res_ptr = false; (void)rlExpr(ag, body_node, &new_block, RL_RI_NONE); bool else_consumes = false; if (else_node != 0) else_consumes = rlExpr(ag, else_node, block, ri); if (new_block.consumes_res_ptr || else_consumes) { nodesNeedRlAdd(ag, node); return true; } return false; } // slice (AstRlAnnotate.zig:456-480). case AST_NODE_SLICE_OPEN: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; case AST_NODE_SLICE: { (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); uint32_t start = tree->extra_data.arr[nd.rhs]; uint32_t end = tree->extra_data.arr[nd.rhs + 1]; (void)rlExpr(ag, start, block, RL_RI_TYPE_ONLY); (void)rlExpr(ag, end, block, RL_RI_TYPE_ONLY); return false; } case AST_NODE_SLICE_SENTINEL: { (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); AstSliceSentinel ss; ss.start = tree->extra_data.arr[nd.rhs]; ss.end = tree->extra_data.arr[nd.rhs + 1]; ss.sentinel = tree->extra_data.arr[nd.rhs + 2]; (void)rlExpr(ag, ss.start, block, RL_RI_TYPE_ONLY); if (ss.end != 0) (void)rlExpr(ag, ss.end, block, RL_RI_TYPE_ONLY); (void)rlExpr(ag, ss.sentinel, block, RL_RI_NONE); return false; } // deref (AstRlAnnotate.zig:481-484). case AST_NODE_DEREF: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; // address_of (AstRlAnnotate.zig:485-488). case AST_NODE_ADDRESS_OF: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; // optional_type (AstRlAnnotate.zig:489-492). case AST_NODE_OPTIONAL_TYPE: (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); return false; // try, nosuspend (AstRlAnnotate.zig:493-495). case AST_NODE_TRY: case AST_NODE_NOSUSPEND: return rlExpr(ag, nd.lhs, block, ri); // grouped_expression, unwrap_optional (AstRlAnnotate.zig:496-498). case AST_NODE_GROUPED_EXPRESSION: case AST_NODE_UNWRAP_OPTIONAL: return rlExpr(ag, nd.lhs, block, ri); // block (AstRlAnnotate.zig:500-508). case AST_NODE_BLOCK_TWO: case AST_NODE_BLOCK_TWO_SEMICOLON: { uint32_t stmts[2]; uint32_t count = 0; if (nd.lhs != 0) stmts[count++] = nd.lhs; if (nd.rhs != 0) stmts[count++] = nd.rhs; return rlBlockExpr(ag, block, ri, node, stmts, count); } case AST_NODE_BLOCK: case AST_NODE_BLOCK_SEMICOLON: return rlBlockExpr(ag, block, ri, node, tree->extra_data.arr + nd.lhs, nd.rhs - nd.lhs); // anyframe_type (AstRlAnnotate.zig:509-513). case AST_NODE_ANYFRAME_TYPE: (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; // catch/orelse (AstRlAnnotate.zig:514-522). case AST_NODE_CATCH: case AST_NODE_ORELSE: { (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); bool rhs_consumes = rlExpr(ag, nd.rhs, block, ri); if (rhs_consumes) nodesNeedRlAdd(ag, node); return rhs_consumes; } // ptr_type (AstRlAnnotate.zig:524-546). case AST_NODE_PTR_TYPE_ALIGNED: if (nd.lhs != 0) (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; case AST_NODE_PTR_TYPE_SENTINEL: if (nd.lhs != 0) (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; case AST_NODE_PTR_TYPE: { AstPtrType pt; pt.sentinel = tree->extra_data.arr[nd.lhs]; pt.align_node = tree->extra_data.arr[nd.lhs + 1]; pt.addrspace_node = tree->extra_data.arr[nd.lhs + 2]; (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); if (pt.sentinel != 0) (void)rlExpr(ag, pt.sentinel, block, RL_RI_TYPE_ONLY); if (pt.align_node != 0) (void)rlExpr(ag, pt.align_node, block, RL_RI_TYPE_ONLY); if (pt.addrspace_node != 0) (void)rlExpr(ag, pt.addrspace_node, block, RL_RI_TYPE_ONLY); return false; } case AST_NODE_PTR_TYPE_BIT_RANGE: { AstPtrTypeBitRange pt; pt.sentinel = tree->extra_data.arr[nd.lhs]; pt.align_node = tree->extra_data.arr[nd.lhs + 1]; pt.addrspace_node = tree->extra_data.arr[nd.lhs + 2]; pt.bit_range_start = tree->extra_data.arr[nd.lhs + 3]; pt.bit_range_end = tree->extra_data.arr[nd.lhs + 4]; (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); if (pt.sentinel != 0) (void)rlExpr(ag, pt.sentinel, block, RL_RI_TYPE_ONLY); if (pt.align_node != 0) (void)rlExpr(ag, pt.align_node, block, RL_RI_TYPE_ONLY); if (pt.addrspace_node != 0) (void)rlExpr(ag, pt.addrspace_node, block, RL_RI_TYPE_ONLY); if (pt.bit_range_start != 0) { (void)rlExpr(ag, pt.bit_range_start, block, RL_RI_TYPE_ONLY); (void)rlExpr(ag, pt.bit_range_end, block, RL_RI_TYPE_ONLY); } return false; } // container_decl (AstRlAnnotate.zig:548-564). case AST_NODE_CONTAINER_DECL: case AST_NODE_CONTAINER_DECL_TRAILING: case AST_NODE_CONTAINER_DECL_ARG: case AST_NODE_CONTAINER_DECL_ARG_TRAILING: case AST_NODE_CONTAINER_DECL_TWO: case AST_NODE_CONTAINER_DECL_TWO_TRAILING: case AST_NODE_TAGGED_UNION: case AST_NODE_TAGGED_UNION_TRAILING: case AST_NODE_TAGGED_UNION_ENUM_TAG: case AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING: case AST_NODE_TAGGED_UNION_TWO: case AST_NODE_TAGGED_UNION_TWO_TRAILING: rlContainerDecl(ag, block, node); return false; // break (AstRlAnnotate.zig:566-596). case AST_NODE_BREAK: { uint32_t opt_label_tok = nd.lhs; // 0 = no label uint32_t rhs_node = nd.rhs; // 0 = void break if (rhs_node == 0) return false; RlBlock* opt_cur_block = block; if (opt_label_tok != 0) { // Labeled break: find matching block. while (opt_cur_block != NULL) { if (opt_cur_block->label_token != UINT32_MAX && rlTokenIdentEqual( tree, opt_cur_block->label_token, opt_label_tok)) break; opt_cur_block = opt_cur_block->parent; } } else { // No label: breaking from innermost loop. while (opt_cur_block != NULL) { if (opt_cur_block->is_loop) break; opt_cur_block = opt_cur_block->parent; } } if (opt_cur_block != NULL) { bool consumes = rlExpr(ag, rhs_node, block, opt_cur_block->ri); if (consumes) opt_cur_block->consumes_res_ptr = true; } else { (void)rlExpr(ag, rhs_node, block, RL_RI_NONE); } return false; } // array_type (AstRlAnnotate.zig:598-611). case AST_NODE_ARRAY_TYPE: (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; case AST_NODE_ARRAY_TYPE_SENTINEL: { (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); uint32_t elem_type = tree->extra_data.arr[nd.rhs + 1]; uint32_t sentinel = tree->extra_data.arr[nd.rhs]; (void)rlExpr(ag, elem_type, block, RL_RI_TYPE_ONLY); (void)rlExpr(ag, sentinel, block, RL_RI_TYPE_ONLY); return false; } // array_access (AstRlAnnotate.zig:612-617). case AST_NODE_ARRAY_ACCESS: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_TYPE_ONLY); return false; // comptime (AstRlAnnotate.zig:618-623). case AST_NODE_COMPTIME: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; // switch (AstRlAnnotate.zig:624-650). case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: { uint32_t cond_node = nd.lhs; uint32_t extra_idx = nd.rhs; uint32_t cases_start = tree->extra_data.arr[extra_idx]; uint32_t cases_end = tree->extra_data.arr[extra_idx + 1]; (void)rlExpr(ag, cond_node, block, RL_RI_NONE); bool any_consumed = false; for (uint32_t ci = cases_start; ci < cases_end; ci++) { uint32_t case_node = tree->extra_data.arr[ci]; AstNodeTag ct = tree->nodes.tags[case_node]; AstData cd = tree->nodes.datas[case_node]; // Process case values. if (ct == AST_NODE_SWITCH_CASE_ONE || ct == AST_NODE_SWITCH_CASE_INLINE_ONE) { if (cd.lhs != 0) { if (tree->nodes.tags[cd.lhs] == AST_NODE_SWITCH_RANGE) { AstData rd = tree->nodes.datas[cd.lhs]; (void)rlExpr(ag, rd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, rd.rhs, block, RL_RI_NONE); } else { (void)rlExpr(ag, cd.lhs, block, RL_RI_NONE); } } } else { // SWITCH_CASE / SWITCH_CASE_INLINE: SubRange[lhs] uint32_t items_start = tree->extra_data.arr[cd.lhs]; uint32_t items_end = tree->extra_data.arr[cd.lhs + 1]; for (uint32_t ii = items_start; ii < items_end; ii++) { uint32_t item = tree->extra_data.arr[ii]; if (tree->nodes.tags[item] == AST_NODE_SWITCH_RANGE) { AstData rd = tree->nodes.datas[item]; (void)rlExpr(ag, rd.lhs, block, RL_RI_NONE); (void)rlExpr(ag, rd.rhs, block, RL_RI_NONE); } else { (void)rlExpr(ag, item, block, RL_RI_NONE); } } } // Process case target expr. if (rlExpr(ag, cd.rhs, block, ri)) any_consumed = true; } if (any_consumed) nodesNeedRlAdd(ag, node); return any_consumed; } // suspend (AstRlAnnotate.zig:651-654). case AST_NODE_SUSPEND: if (nd.lhs != 0) (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; // resume (AstRlAnnotate.zig:655-658). case AST_NODE_RESUME: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; // array_init (AstRlAnnotate.zig:660-695). case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: { // Extract type_expr and elements. uint32_t type_expr = 0; uint32_t elem_buf[2]; const uint32_t* elems = NULL; uint32_t nelem = 0; switch (tag) { case AST_NODE_ARRAY_INIT_ONE: case AST_NODE_ARRAY_INIT_ONE_COMMA: type_expr = nd.lhs; if (nd.rhs != 0) { elem_buf[0] = nd.rhs; elems = elem_buf; nelem = 1; } break; case AST_NODE_ARRAY_INIT_DOT_TWO: case AST_NODE_ARRAY_INIT_DOT_TWO_COMMA: { uint32_t idx = 0; if (nd.lhs != 0) elem_buf[idx++] = nd.lhs; if (nd.rhs != 0) elem_buf[idx++] = nd.rhs; elems = elem_buf; nelem = idx; break; } case AST_NODE_ARRAY_INIT_DOT: case AST_NODE_ARRAY_INIT_DOT_COMMA: elems = tree->extra_data.arr + nd.lhs; nelem = nd.rhs - nd.lhs; break; case AST_NODE_ARRAY_INIT: case AST_NODE_ARRAY_INIT_COMMA: { type_expr = nd.lhs; uint32_t start = tree->extra_data.arr[nd.rhs]; uint32_t end = tree->extra_data.arr[nd.rhs + 1]; elems = tree->extra_data.arr + start; nelem = end - start; break; } default: break; } if (type_expr != 0) { (void)rlExpr(ag, type_expr, block, RL_RI_NONE); for (uint32_t i = 0; i < nelem; i++) (void)rlExpr(ag, elems[i], block, RL_RI_TYPE_ONLY); return false; } if (ri.have_type) { for (uint32_t i = 0; i < nelem; i++) (void)rlExpr(ag, elems[i], block, ri); return ri.have_ptr; } else { for (uint32_t i = 0; i < nelem; i++) (void)rlExpr(ag, elems[i], block, RL_RI_NONE); return false; } } // struct_init (AstRlAnnotate.zig:697-732). case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: { uint32_t type_expr = 0; uint32_t field_buf[2]; const uint32_t* fields = NULL; uint32_t nfields = 0; switch (tag) { case AST_NODE_STRUCT_INIT_ONE: case AST_NODE_STRUCT_INIT_ONE_COMMA: type_expr = nd.lhs; if (nd.rhs != 0) { field_buf[0] = nd.rhs; fields = field_buf; nfields = 1; } break; case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: { uint32_t idx = 0; if (nd.lhs != 0) field_buf[idx++] = nd.lhs; if (nd.rhs != 0) field_buf[idx++] = nd.rhs; fields = field_buf; nfields = idx; break; } case AST_NODE_STRUCT_INIT_DOT: case AST_NODE_STRUCT_INIT_DOT_COMMA: fields = tree->extra_data.arr + nd.lhs; nfields = nd.rhs - nd.lhs; break; case AST_NODE_STRUCT_INIT: case AST_NODE_STRUCT_INIT_COMMA: { type_expr = nd.lhs; uint32_t start = tree->extra_data.arr[nd.rhs]; uint32_t end = tree->extra_data.arr[nd.rhs + 1]; fields = tree->extra_data.arr + start; nfields = end - start; break; } default: break; } if (type_expr != 0) { (void)rlExpr(ag, type_expr, block, RL_RI_NONE); for (uint32_t i = 0; i < nfields; i++) (void)rlExpr(ag, fields[i], block, RL_RI_TYPE_ONLY); return false; } if (ri.have_type) { for (uint32_t i = 0; i < nfields; i++) (void)rlExpr(ag, fields[i], block, ri); return ri.have_ptr; } else { for (uint32_t i = 0; i < nfields; i++) (void)rlExpr(ag, fields[i], block, RL_RI_NONE); return false; } } // fn_proto, fn_decl (AstRlAnnotate.zig:734-770). case AST_NODE_FN_PROTO_SIMPLE: case AST_NODE_FN_PROTO_MULTI: case AST_NODE_FN_PROTO_ONE: case AST_NODE_FN_PROTO: case AST_NODE_FN_DECL: { // Extract return type and body. uint32_t return_type = 0; uint32_t body_node = 0; if (tag == AST_NODE_FN_DECL) { body_node = nd.rhs; // fn_proto is nd.lhs uint32_t proto = nd.lhs; AstNodeTag ptag = tree->nodes.tags[proto]; AstData pnd = tree->nodes.datas[proto]; if (ptag == AST_NODE_FN_PROTO_SIMPLE) { return_type = pnd.rhs; if (pnd.lhs != 0) (void)rlExpr(ag, pnd.lhs, block, RL_RI_TYPE_ONLY); } else if (ptag == AST_NODE_FN_PROTO_MULTI) { return_type = pnd.rhs; uint32_t ps = tree->extra_data.arr[pnd.lhs]; uint32_t pe = tree->extra_data.arr[pnd.lhs + 1]; for (uint32_t i = ps; i < pe; i++) (void)rlExpr( ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY); } else if (ptag == AST_NODE_FN_PROTO_ONE) { return_type = pnd.rhs; AstFnProtoOne fp; fp.param = tree->extra_data.arr[pnd.lhs]; fp.align_expr = tree->extra_data.arr[pnd.lhs + 1]; fp.addrspace_expr = tree->extra_data.arr[pnd.lhs + 2]; fp.section_expr = tree->extra_data.arr[pnd.lhs + 3]; fp.callconv_expr = tree->extra_data.arr[pnd.lhs + 4]; if (fp.param != 0) (void)rlExpr(ag, fp.param, block, RL_RI_TYPE_ONLY); if (fp.align_expr != 0) (void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY); if (fp.addrspace_expr != 0) (void)rlExpr( ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY); if (fp.section_expr != 0) (void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY); if (fp.callconv_expr != 0) (void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY); } else if (ptag == AST_NODE_FN_PROTO) { return_type = pnd.rhs; AstFnProto fp; fp.params_start = tree->extra_data.arr[pnd.lhs]; fp.params_end = tree->extra_data.arr[pnd.lhs + 1]; fp.align_expr = tree->extra_data.arr[pnd.lhs + 2]; fp.addrspace_expr = tree->extra_data.arr[pnd.lhs + 3]; fp.section_expr = tree->extra_data.arr[pnd.lhs + 4]; fp.callconv_expr = tree->extra_data.arr[pnd.lhs + 5]; for (uint32_t i = fp.params_start; i < fp.params_end; i++) (void)rlExpr( ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY); if (fp.align_expr != 0) (void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY); if (fp.addrspace_expr != 0) (void)rlExpr( ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY); if (fp.section_expr != 0) (void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY); if (fp.callconv_expr != 0) (void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY); } } else { // Standalone fn_proto (no body). if (tag == AST_NODE_FN_PROTO_SIMPLE) { return_type = nd.rhs; if (nd.lhs != 0) (void)rlExpr(ag, nd.lhs, block, RL_RI_TYPE_ONLY); } else if (tag == AST_NODE_FN_PROTO_MULTI) { return_type = nd.rhs; uint32_t ps = tree->extra_data.arr[nd.lhs]; uint32_t pe = tree->extra_data.arr[nd.lhs + 1]; for (uint32_t i = ps; i < pe; i++) (void)rlExpr( ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY); } else if (tag == AST_NODE_FN_PROTO_ONE) { return_type = nd.rhs; AstFnProtoOne fp; fp.param = tree->extra_data.arr[nd.lhs]; fp.align_expr = tree->extra_data.arr[nd.lhs + 1]; fp.addrspace_expr = tree->extra_data.arr[nd.lhs + 2]; fp.section_expr = tree->extra_data.arr[nd.lhs + 3]; fp.callconv_expr = tree->extra_data.arr[nd.lhs + 4]; if (fp.param != 0) (void)rlExpr(ag, fp.param, block, RL_RI_TYPE_ONLY); if (fp.align_expr != 0) (void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY); if (fp.addrspace_expr != 0) (void)rlExpr( ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY); if (fp.section_expr != 0) (void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY); if (fp.callconv_expr != 0) (void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY); } else if (tag == AST_NODE_FN_PROTO) { return_type = nd.rhs; AstFnProto fp; fp.params_start = tree->extra_data.arr[nd.lhs]; fp.params_end = tree->extra_data.arr[nd.lhs + 1]; fp.align_expr = tree->extra_data.arr[nd.lhs + 2]; fp.addrspace_expr = tree->extra_data.arr[nd.lhs + 3]; fp.section_expr = tree->extra_data.arr[nd.lhs + 4]; fp.callconv_expr = tree->extra_data.arr[nd.lhs + 5]; for (uint32_t i = fp.params_start; i < fp.params_end; i++) (void)rlExpr( ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY); if (fp.align_expr != 0) (void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY); if (fp.addrspace_expr != 0) (void)rlExpr( ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY); if (fp.section_expr != 0) (void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY); if (fp.callconv_expr != 0) (void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY); } } if (return_type != 0) (void)rlExpr(ag, return_type, block, RL_RI_TYPE_ONLY); if (body_node != 0) (void)rlExpr(ag, body_node, block, RL_RI_NONE); return false; } // Remaining: usingnamespace, await, assign_destructure, async calls. case AST_NODE_USINGNAMESPACE: return false; case AST_NODE_AWAIT: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; case AST_NODE_ASSIGN_DESTRUCTURE: { uint32_t extra_start = nd.lhs; uint32_t variable_count = tree->extra_data.arr[extra_start]; for (uint32_t i = 0; i < variable_count; i++) (void)rlExpr(ag, tree->extra_data.arr[extra_start + 1 + i], block, RL_RI_NONE); (void)rlExpr(ag, nd.rhs, block, RL_RI_NONE); return false; } case AST_NODE_ASYNC_CALL_ONE: case AST_NODE_ASYNC_CALL_ONE_COMMA: case AST_NODE_ASYNC_CALL: case AST_NODE_ASYNC_CALL_COMMA: return false; // async not relevant default: return false; } } // astRlAnnotate (AstRlAnnotate.zig:64-83). // Entry point: run the RL annotation pre-pass. static void astRlAnnotate(AstGenCtx* ag) { const Ast* tree = ag->tree; if (tree->has_error) return; // Get root container members (same as in astGen). AstData root_data = tree->nodes.datas[0]; uint32_t members_start = root_data.lhs; uint32_t members_end = root_data.rhs; const uint32_t* members = tree->extra_data.arr + members_start; uint32_t members_len = members_end - members_start; for (uint32_t i = 0; i < members_len; i++) (void)rlExpr(ag, members[i], NULL, RL_RI_NONE); } // --- Public API: astGen (AstGen.zig:144) --- Zir astGen(const Ast* ast) { AstGenCtx ag; memset(&ag, 0, sizeof(ag)); ag.tree = ast; // Initial allocations (AstGen.zig:162-172). uint32_t nodes_len = ast->nodes.len; uint32_t init_cap = nodes_len > 8 ? nodes_len : 8; ag.inst_cap = init_cap; ag.inst_tags = ARR_INIT(ZirInstTag, ag.inst_cap); ag.inst_datas = ARR_INIT(ZirInstData, ag.inst_cap); ag.extra_cap = init_cap + ZIR_EXTRA_RESERVED_COUNT; ag.extra = ARR_INIT(uint32_t, ag.extra_cap); ag.string_bytes_cap = 16; ag.string_bytes = ARR_INIT(uint8_t, ag.string_bytes_cap); // String table index 0 is reserved for NullTerminatedString.empty // (AstGen.zig:163). ag.string_bytes[0] = 0; ag.string_bytes_len = 1; // Reserve extra[0..1] (AstGen.zig:170-172). ag.extra[ZIR_EXTRA_COMPILE_ERRORS] = 0; ag.extra[ZIR_EXTRA_IMPORTS] = 0; ag.extra_len = ZIR_EXTRA_RESERVED_COUNT; // Run AstRlAnnotate pre-pass (AstGen.zig:150-151). astRlAnnotate(&ag); // Set up root GenZir scope (AstGen.zig:176-185). GenZir gen_scope; memset(&gen_scope, 0, sizeof(gen_scope)); gen_scope.base.tag = SCOPE_GEN_ZIR; gen_scope.parent = NULL; gen_scope.astgen = &ag; gen_scope.is_comptime = true; gen_scope.decl_node_index = 0; // root gen_scope.decl_line = 0; gen_scope.break_block = UINT32_MAX; gen_scope.any_defer_node = UINT32_MAX; // Get root container members: containerDeclRoot (AstGen.zig:191-195). AstData root_data = ast->nodes.datas[0]; uint32_t members_start = root_data.lhs; uint32_t members_end = root_data.rhs; const uint32_t* members = ast->extra_data.arr + members_start; uint32_t members_len = members_end - members_start; structDeclInner( &ag, &gen_scope, 0, members, members_len, 0, 0, 0 /* parent */); // Write imports list (AstGen.zig:227-244). writeImports(&ag); // Build output Zir (AstGen.zig:211-239). Zir zir; zir.inst_len = ag.inst_len; zir.inst_cap = ag.inst_cap; zir.inst_tags = ag.inst_tags; zir.inst_datas = ag.inst_datas; zir.extra_len = ag.extra_len; zir.extra_cap = ag.extra_cap; zir.extra = ag.extra; zir.string_bytes_len = ag.string_bytes_len; zir.string_bytes_cap = ag.string_bytes_cap; zir.string_bytes = ag.string_bytes; zir.has_compile_errors = ag.has_compile_errors; free(ag.imports); free(ag.decl_names); free(ag.decl_nodes); free(ag.scratch_instructions); free(ag.scratch_extra); free(ag.ref_table_keys); free(ag.ref_table_vals); free(ag.nodes_need_rl); free(ag.string_table); return zir; }