diff --git a/stage0/astgen.c b/stage0/astgen.c index 4342bd7a38..fe0c3e296e 100644 --- a/stage0/astgen.c +++ b/stage0/astgen.c @@ -113,6 +113,8 @@ typedef struct { 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) { @@ -262,6 +264,7 @@ typedef struct { 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). @@ -366,6 +369,7 @@ static GenZir makeSubBlock(GenZir* parent, Scope* scope) { 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; } @@ -572,6 +576,25 @@ static uint32_t addPlNodePayloadIndex( 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). @@ -598,6 +621,7 @@ static void advanceSourceCursor(AstGenCtx* ag, uint32_t end) { // 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]; @@ -651,7 +675,7 @@ static uint32_t firstToken(const Ast* tree, uint32_t node) { case AST_NODE_PTR_TYPE_SENTINEL: case AST_NODE_PTR_TYPE: case AST_NODE_PTR_TYPE_BIT_RANGE: - return tree->nodes.main_tokens[n]; + return tree->nodes.main_tokens[n] - end_offset; // Return main_token - 1: dot-prefixed inits and enum_literal // (Ast.zig:645-654). @@ -664,7 +688,7 @@ static uint32_t firstToken(const Ast* tree, uint32_t node) { 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; + return tree->nodes.main_tokens[n] - 1 - end_offset; // Recurse into LHS: all binary ops and compound expressions // (Ast.zig:656-733). @@ -764,7 +788,7 @@ static uint32_t firstToken(const Ast* tree, uint32_t node) { break; } } - return i; + return i - end_offset; } // Fn decls: scan backwards for modifiers (Ast.zig:737-759). case AST_NODE_FN_DECL: @@ -785,7 +809,7 @@ static uint32_t firstToken(const Ast* tree, uint32_t node) { break; } } - return i; + return i - end_offset; } // Container fields: check for preceding comptime (Ast.zig:761-769). case AST_NODE_CONTAINER_FIELD_INIT: @@ -793,8 +817,8 @@ static uint32_t firstToken(const Ast* tree, uint32_t node) { case AST_NODE_CONTAINER_FIELD: { uint32_t mt = tree->nodes.main_tokens[n]; if (mt > 0 && tree->tokens.tags[mt - 1] == TOKEN_KEYWORD_COMPTIME) - return mt - 1; - return mt; + end_offset++; + return mt - end_offset; } // Blocks: check for label (Ast.zig:794-805). case AST_NODE_BLOCK: @@ -804,12 +828,99 @@ static uint32_t firstToken(const Ast* tree, uint32_t node) { 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) - return lbrace - 2; - return lbrace; + 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]; + return tree->nodes.main_tokens[n] - end_offset; } } } @@ -1906,9 +2017,82 @@ static uint32_t rlResultType(GenZir* gz, ResultLoc rl, uint32_t node) { } } +// 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: @@ -2122,6 +2306,9 @@ 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); @@ -2138,24 +2325,34 @@ 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 uint32_t containerDecl(GenZir* gz, Scope* scope, 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); + 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); + 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, uint32_t node, bool is_statement); -static uint32_t orelseCatchExpr( - GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node, bool is_catch); + 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, uint32_t node, bool is_statement); + 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 @@ -2233,9 +2430,44 @@ static bool refIsNoReturn(GenZir* gz, uint32_t inst_ref) { 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 @@ -2245,6 +2477,7 @@ static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node); #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. @@ -2338,9 +2571,15 @@ static uint32_t typeExpr(GenZir* gz, Scope* scope, uint32_t node) { return comptimeExpr(gz, scope, rl, node, COMPTIME_REASON_TYPE); } -// Mirrors numberLiteral (AstGen.zig:8544). +// 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. -static uint32_t numberLiteral(GenZir* gz, uint32_t node) { +// 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]; @@ -2391,16 +2630,27 @@ static uint32_t numberLiteral(GenZir* gz, uint32_t node) { } // 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 ZIR_REF_ONE; + if (value == 1) { + return (sign == NUM_SIGN_POSITIVE) ? ZIR_REF_ONE + : ZIR_REF_NEGATIVE_ONE; + } - return addInt(gz, value); + // 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, uint32_t node) { +static uint32_t builtinCallImport( + GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { (void)scope; AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; @@ -2415,10 +2665,14 @@ static uint32_t builtinCallImport(GenZir* gz, Scope* scope, uint32_t 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++] = ZIR_REF_NONE; // res_ty = .none + ag->extra[ag->extra_len++] = res_ty; ag->extra[ag->extra_len++] = str_index; // path // Create .import instruction with pl_tok data. @@ -2430,7 +2684,7 @@ static uint32_t builtinCallImport(GenZir* gz, Scope* scope, uint32_t node) { // Track import (AstGen.zig:9269). addImport(ag, str_index, str_lit_token); - return result_ref; + return rvalue(gz, rl, result_ref, node); } // Mirrors cImport (AstGen.zig:10011). @@ -2446,10 +2700,11 @@ static uint32_t cImportExpr(GenZir* gz, Scope* scope, uint32_t node) { block_scope.c_import = true; // Use fullBodyExpr to inline unlabeled block body (AstGen.zig:10028). - fullBodyExpr(&block_scope, &block_scope.base, RL_NONE_VAL, body_node); + 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, ZIR_REF_VOID_VALUE, node); + addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, block_result, node); // break_inline (AstGen.zig:10030-10032). makeBreakInline( @@ -2463,14 +2718,29 @@ static uint32_t cImportExpr(GenZir* gz, Scope* scope, uint32_t node) { } // Mirrors simpleCBuiltin (AstGen.zig:9938). -static uint32_t simpleCBuiltin(GenZir* gz, Scope* scope, uint32_t node, - uint32_t operand_node, uint16_t ext_tag) { +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; - // Evaluate operand as comptime string. - uint32_t operand = expr(gz, scope, operand_node); + // Check c_import scope (AstGen.zig:9947). + if (!gz->c_import) { + SET_ERROR(ag); + return ZIR_REF_VOID_VALUE; + } - // Emit extended instruction with UnNode payload (AstGen.zig:9954). + // 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++] @@ -2484,7 +2754,7 @@ static uint32_t simpleCBuiltin(GenZir* gz, Scope* scope, uint32_t node, data.extended.operand = payload_index; addInstruction(gz, ZIR_INST_EXTENDED, data); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); } // Mirrors builtinCall (AstGen.zig:9191) dispatch. @@ -2511,38 +2781,45 @@ static uint32_t builtinCall( // clang-format off if (name_len == 6 && memcmp(source + name_start, "import", 6) == 0) - return builtinCallImport(gz, scope, node); + 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, node, nd.lhs, (uint16_t)ZIR_EXT_C_INCLUDE); + 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 = rlResultType(gz, rl, node); + 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 addPlNodeBin(gz, ZIR_INST_INT_CAST, node, - result_type, operand); + return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_INT_CAST, node, + result_type, operand), node); } - // @embedFile (AstGen.zig:9626). + // @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]; - uint32_t operand = expr(gz, scope, nd.lhs); - return addUnNode(gz, ZIR_INST_EMBED_FILE, operand, 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 (AstGen.zig:9478). + // @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); - return addUnNode(gz, ZIR_INST_INT_FROM_ENUM, operand, node); + uint32_t result = addUnNode(gz, ZIR_INST_INT_FROM_ENUM, operand, node); + return rvalue(gz, rl, result, node); } - // @tagName (AstGen.zig:9407) — simpleUnOp with dbg_stmt. + // @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; @@ -2550,7 +2827,8 @@ static uint32_t builtinCall( AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); emitDbgStmt(gz, saved_line, saved_col); - return addUnNode(gz, ZIR_INST_TAG_NAME, operand, node); + 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) { @@ -2566,44 +2844,46 @@ static uint32_t builtinCall( 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 = rlResultType(gz, rl, node); + 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 addPlNodeBin(gz, ZIR_INST_TRUNCATE, node, - result_type, operand); + 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 = rlResultType(gz, rl, node); + 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 addPlNodeBin(gz, ZIR_INST_PTR_CAST, node, - result_type, operand); + 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 = rlResultType(gz, rl, node); + 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 addPlNodeBin(gz, ZIR_INST_ENUM_FROM_INT, node, - result_type, operand); + 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 = rlResultType(gz, rl, node); + uint32_t result_type = rlResultTypeForCast(gz, rl, node); AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); - return addPlNodeBin(gz, ZIR_INST_BITCAST, node, - result_type, operand); + 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) { @@ -2611,7 +2891,7 @@ static uint32_t builtinCall( 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 ZIR_REF_VOID_VALUE; + 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) { @@ -2621,24 +2901,27 @@ static uint32_t builtinCall( 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}; + .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 ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); } - // @min (AstGen.zig:9155). + // @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 addPlNodeBin(gz, ZIR_INST_MIN, node, a, b); + return rvalue(gz, rl, + addPlNodeBin(gz, ZIR_INST_MIN, node, a, b), node); } - // @max (AstGen.zig:9155). + // @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 addPlNodeBin(gz, ZIR_INST_MAX, node, a, b); + return rvalue(gz, rl, + addPlNodeBin(gz, ZIR_INST_MAX, node, a, b), node); } // clang-format on @@ -2967,6 +3250,18 @@ static uint32_t ptrTypeExpr(GenZir* gz, Scope* scope, uint32_t node) { } } + // 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); @@ -2979,6 +3274,10 @@ static uint32_t ptrTypeExpr(GenZir* gz, Scope* scope, uint32_t node) { 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 = { @@ -2988,6 +3287,10 @@ static uint32_t ptrTypeExpr(GenZir* gz, Scope* scope, uint32_t node) { 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( @@ -2995,6 +3298,10 @@ static uint32_t ptrTypeExpr(GenZir* gz, Scope* scope, uint32_t node) { 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, @@ -3083,7 +3390,11 @@ static uint32_t arrayTypeExpr(GenZir* gz, Scope* scope, uint32_t node) { } // --- arrayInitExpr (AstGen.zig:1431) --- -// Simplified: handles typed array init with inferred [_] length. +// 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) { @@ -3132,56 +3443,134 @@ static uint32_t arrayInitExpr( return ZIR_REF_VOID_VALUE; } - // Check if the type is [_]T (inferred length) (AstGen.zig:1446-1474). - if (tree->nodes.tags[type_expr_node] == AST_NODE_ARRAY_TYPE) { + // 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; - uint32_t elem_type_node = type_nd.rhs; - // Check if elem_count is `_` identifier. + // 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); - uint32_t elem_type = typeExpr(gz, scope, elem_type_node); - uint32_t array_type_inst = addPlNodeBin( - gz, ZIR_INST_ARRAY_TYPE, type_expr_node, len_inst, elem_type); - // arrayInitExprTyped (AstGen.zig:1484-1513, 1598-1642). - // Only RL_REF produces array_init_ref; all other RLs use - // array_init + rvalue (AstGen.zig:1507-1511). - bool is_ref = (rl.tag == RL_REF); - uint32_t operands_len = elem_count + 1; - ensureExtraCapacity(ag, 1 + operands_len); - uint32_t payload_index = ag->extra_len; - ag->extra[ag->extra_len++] = operands_len; - ag->extra[ag->extra_len++] = array_type_inst; - uint32_t extra_start = ag->extra_len; - ag->extra_len += elem_count; - for (uint32_t i = 0; i < elem_count; i++) { - // Use elem_type as coercion target for each element. - ResultLoc elem_rl = { - .tag = RL_COERCED_TY, .data = elem_type, .src_node = 0 - }; - uint32_t elem_ref = exprRl(gz, scope, elem_rl, elements[i]); - ag->extra[extra_start + i] = elem_ref; + 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); } - ZirInstTag init_tag - = is_ref ? ZIR_INST_ARRAY_INIT_REF : ZIR_INST_ARRAY_INIT; - ZirInstData idata; - idata.pl_node.src_node - = (int32_t)node - (int32_t)gz->decl_node_index; - idata.pl_node.payload_index = payload_index; - uint32_t result = addInstruction(gz, init_tag, idata); - if (is_ref) - return result; - return rvalue(gz, rl, result, node); + goto typed_init; } } - // Non-inferred length: evaluate type normally. - SET_ERROR(ag); - return ZIR_REF_VOID_VALUE; + // 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) --- @@ -3288,6 +3677,18 @@ 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) { @@ -3296,11 +3697,6 @@ static uint32_t retExpr(GenZir* gz, Scope* scope, uint32_t node) { uint32_t ret_lc_line = ag->source_line - gz->decl_line; uint32_t ret_lc_column = ag->source_column; - // AstGen.zig:8123: return outside function is an error. - if (ag->fn_block == NULL) { - SET_ERROR(ag); - return ZIR_REF_UNREACHABLE_VALUE; - } const Scope* defer_outer = &((GenZir*)ag->fn_block)->base; AstData nd = tree->nodes.datas[node]; @@ -3351,7 +3747,12 @@ static uint32_t retExpr(GenZir* gz, Scope* scope, uint32_t node) { ret_rl.data = ag->fn_ret_ty; } ret_rl.ctx = RI_CTX_RETURN; - uint32_t operand = exprRl(gz, scope, ret_rl, operand_node); + // 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). @@ -3648,8 +4049,72 @@ static uint32_t callExpr( 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) --- -// Simplified: handles .{} (empty tuple), .{.a = b} (anon init). static uint32_t structInitExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; @@ -3722,7 +4187,15 @@ static uint32_t structInitExpr( if (rl.tag == RL_DISCARD) { return ZIR_REF_VOID_VALUE; } - return ZIR_REF_EMPTY_TUPLE; + 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. @@ -3734,8 +4207,46 @@ static uint32_t structInitExpr( } if (type_expr_node == 0 && fields_len > 0) { - // structInitExprPtr for RL_PTR (AstGen.zig:1843-1846, 1934-1964). - if (rl.tag == RL_PTR) { + // 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. @@ -3759,68 +4270,22 @@ static uint32_t structInitExpr( ResultLoc ptr_rl = { .tag = RL_PTR, .data = field_ptr, .src_node = 0, - .ctx = rl.ctx }; + .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; } - // Anonymous struct init with RL type (AstGen.zig:1706-1731). - if (rl.tag == RL_TY || rl.tag == RL_COERCED_TY) { - uint32_t ty_inst = rl.data; - // validate_struct_init_result_ty (AstGen.zig:1840). - addUnNode( - gz, ZIR_INST_VALIDATE_STRUCT_INIT_RESULT_TY, ty_inst, node); - // structInitExprTyped (AstGen.zig:1896-1931). - ensureExtraCapacity(ag, 3 + fields_len * 2); - uint32_t payload_index = ag->extra_len; - ag->extra[ag->extra_len++] = node; - ag->extra[ag->extra_len++] = ag->source_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 - }; - uint32_t init_ref = exprRl(gz, scope, elem_rl, field_init); - ag->extra[items_start + i * 2] - = field_ty_inst - ZIR_REF_START_INDEX; - ag->extra[items_start + i * 2 + 1] = init_ref; - } - return addPlNodePayloadIndex( - gz, ZIR_INST_STRUCT_INIT, node, payload_index); + 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); } - // Anonymous struct init without RL type (AstGen.zig:1864). - // StructInitAnon payload: abs_node, abs_line, fields_len. - 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; - // Reserve space for field entries. - 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]; - // field name is 2 tokens before the field init's first token. - 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); + SET_ERROR(ag); + return ZIR_REF_VOID_VALUE; } // Typed init: evaluate type, emit struct_init_empty or struct_init. @@ -3850,7 +4315,7 @@ static uint32_t structInitExpr( 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_COERCED_TY, + ResultLoc sent_rl = { .tag = RL_TY, .data = elem_type, .src_node = 0, .ctx = RI_CTX_NONE }; @@ -3874,41 +4339,15 @@ static uint32_t structInitExpr( 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); - - // structInitExprTyped (AstGen.zig:1896-1931). - // StructInit payload: abs_node, abs_line, fields_len. - 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; - // Reserve space for field items (field_type + init each). - 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); - // struct_init_field_type (AstGen.zig:1918-1921). - uint32_t field_ty_inst - = addPlNodeBin(gz, ZIR_INST_STRUCT_INIT_FIELD_TYPE, field_init, - ty_inst, str_index); - // Evaluate init with coerced_ty (AstGen.zig:1924). - ResultLoc elem_rl = { .tag = RL_COERCED_TY, - .data = field_ty_inst, - .src_node = 0, - .ctx = rl.ctx }; - 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; + // 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); } - - bool is_ref = (RL_IS_REF(rl)); - ZirInstTag init_tag - = is_ref ? ZIR_INST_STRUCT_INIT_REF : ZIR_INST_STRUCT_INIT; - return addPlNodePayloadIndex(gz, init_tag, node, payload_index); + uint32_t struct_inst = structInitExprTyped( + gz, scope, node, fields, fields_len, ty_inst, false); + return rvalue(gz, rl, struct_inst, node); } SET_ERROR(ag); @@ -3916,7 +4355,8 @@ static uint32_t structInitExpr( } // --- tryExpr (AstGen.zig:5957) --- -static uint32_t tryExpr(GenZir* gz, Scope* scope, uint32_t node) { +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; @@ -3927,20 +4367,33 @@ static uint32_t tryExpr(GenZir* gz, Scope* scope, uint32_t node) { uint32_t try_lc_line = ag->source_line - gz->decl_line; uint32_t try_lc_column = ag->source_column; - // Evaluate operand (AstGen.zig:5993-6001). - ResultLoc operand_rl = RL_NONE_VAL; + // 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:6007). - uint32_t try_inst = makeBlockInst(ag, ZIR_INST_TRY, gz, 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:6012-6025). + // Else scope: extract error code, return it (AstGen.zig:6011-6025). GenZir else_scope = makeSubBlock(gz, scope); - uint32_t err_code - = addUnNode(&else_scope, ZIR_INST_ERR_UNION_CODE, operand, node); + uint32_t err_code = addUnNode(&else_scope, err_tag, operand, node); // Emit defers for error path (AstGen.zig:6019). if (ag->fn_block != NULL) { @@ -3957,7 +4410,12 @@ static uint32_t tryExpr(GenZir* gz, Scope* scope, uint32_t node) { setTryBody(ag, &else_scope, try_inst, operand); // else_scope unstacked by setTryBody. - return try_inst + ZIR_REF_START_INDEX; // toRef() + // 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) --- @@ -4021,10 +4479,11 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { switch (tag) { case AST_NODE_NUMBER_LITERAL: - return rvalue(gz, rl, numberLiteral(gz, node), node); + 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 rvalue(gz, rl, builtinCall(gz, scope, rl, node), node); + return builtinCall(gz, scope, rl, node); case AST_NODE_FIELD_ACCESS: return fieldAccessExpr(gz, scope, rl, node); case AST_NODE_IDENTIFIER: @@ -4146,10 +4605,11 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { 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), node); - // try (AstGen.zig:831). + return rvalue( + gz, rl, containerDecl(gz, scope, node, 2 /* anon */), node); + // try (AstGen.zig:1115). case AST_NODE_TRY: - return rvalue(gz, rl, tryExpr(gz, scope, node), node); + return tryExpr(gz, scope, rl, node); // Comparison operators (AstGen.zig:714-726). case AST_NODE_EQUAL_EQUAL: return rvalue( @@ -4215,10 +4675,18 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { return rvalue(gz, rl, addUnNode(gz, ZIR_INST_BIT_NOT, expr(gz, scope, nd.lhs), node), node); - case AST_NODE_NEGATION: + // 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), @@ -4237,15 +4705,26 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { addUnNode( gz, ZIR_INST_OPTIONAL_TYPE, typeExpr(gz, scope, nd.lhs), node), node); - // unwrap_optional (AstGen.zig:966-985). + // unwrap_optional (AstGen.zig:966-983). case AST_NODE_UNWRAP_OPTIONAL: { - 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); + 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: { @@ -4432,18 +4911,32 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { return rvalue( gz, rl, addInstruction(gz, ZIR_INST_SLICE_SENTINEL, data), node); } - // orelse (AstGen.zig:6031-6142). + // orelse (AstGen.zig:1054-1075). case AST_NODE_ORELSE: - return orelseCatchExpr(gz, scope, rl, node, false); - // catch (AstGen.zig:6031-6142). + 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: - return orelseCatchExpr(gz, scope, rl, node, true); + 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 rvalue(gz, rl, blockExprExpr(gz, scope, rl, node), node); + 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: @@ -4457,7 +4950,7 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { // for (AstGen.zig:1043-1060). case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: - return rvalue(gz, rl, forExpr(gz, scope, node, false), node); + 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); @@ -4465,13 +4958,29 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { return rvalue(gz, rl, addPlNodeBin(gz, ZIR_INST_MERGE_ERROR_SETS, node, lhs, rhs), node); } - // Wrapping arithmetic. + // 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 @@ -4654,10 +5163,10 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: case AST_NODE_WHILE: - return rvalue(gz, rl, whileExpr(gz, scope, node, false), node); - // error_value (AstGen.zig:1005-1010). + return whileExpr(gz, scope, rlBr(rl), node, false); + // error_value (AstGen.zig:1005). case AST_NODE_ERROR_VALUE: { - uint32_t error_token = nd.rhs; + 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); @@ -4693,49 +5202,59 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { case AST_NODE_ASSIGN: assignStmt(gz, scope, node); return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); - // Compound assignment operators (AstGen.zig:685-744). + // Compound assignment operators (AstGen.zig:689-744). case AST_NODE_ASSIGN_ADD: assignOp(gz, scope, node, ZIR_INST_ADD); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB: assignOp(gz, scope, node, ZIR_INST_SUB); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL: assignOp(gz, scope, node, ZIR_INST_MUL); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_DIV: assignOp(gz, scope, node, ZIR_INST_DIV); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MOD: assignOp(gz, scope, node, ZIR_INST_MOD_REM); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_AND: assignOp(gz, scope, node, ZIR_INST_BIT_AND); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_OR: assignOp(gz, scope, node, ZIR_INST_BIT_OR); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_BIT_XOR: assignOp(gz, scope, node, ZIR_INST_XOR); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_ADD_WRAP: assignOp(gz, scope, node, ZIR_INST_ADDWRAP); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB_WRAP: assignOp(gz, scope, node, ZIR_INST_SUBWRAP); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL_WRAP: assignOp(gz, scope, node, ZIR_INST_MULWRAP); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_ADD_SAT: assignOp(gz, scope, node, ZIR_INST_ADD_SAT); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_SUB_SAT: assignOp(gz, scope, node, ZIR_INST_SUB_SAT); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); case AST_NODE_ASSIGN_MUL_SAT: assignOp(gz, scope, node, ZIR_INST_MUL_SAT); - return ZIR_REF_VOID_VALUE; + 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; @@ -5050,10 +5569,11 @@ static uint32_t arrayInitDotExpr( 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 = rl.ctx }; + .ctx = RI_CTX_NONE }; exprRl(gz, scope, ptr_rl, elements[i]); } addPlNodePayloadIndex( @@ -5096,19 +5616,19 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { then_node = nd.rhs; else_node = 0; } else { - // AST_NODE_IF: rhs is index into extra → If{then_expr, else_expr} + // 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. + // 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 token + payload_token = pipe_tok + 1; // identifier or * token } // Detect error token: then_expr else |e| (AstGen.zig Ast.fullIf). @@ -5121,6 +5641,10 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { } } + // 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); @@ -5129,26 +5653,37 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { // so the dbg_stmt ends up in block_scope's range (shared array). emitDbgNode(gz, cond_node); - // Evaluate condition (AstGen.zig:6335-6363). + // 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:6341). - ResultLoc cond_rl = RL_NONE_VAL; + // (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); - bool_bit = addUnNode( - &block_scope, ZIR_INST_IS_NON_ERR, cond_inst, 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|. - cond_inst = expr(&block_scope, &block_scope.base, cond_node); - bool_bit = addUnNode( - &block_scope, ZIR_INST_IS_NON_NULL, cond_inst, cond_node); + // 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). - cond_inst = expr(&block_scope, &block_scope.base, cond_node); + 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; } @@ -5164,35 +5699,68 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { 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-6407). - uint32_t payload_inst = addUnNode(&then_scope, - ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE, cond_inst, then_node); - uint32_t ident_name = identAsString(ag, payload_token); - payload_val_scope = (ScopeLocalVal) { - .base = { .tag = SCOPE_LOCAL_VAL }, - .parent = &then_scope.base, - .gen_zir = &then_scope, - .inst = payload_inst, - .token_src = payload_token, - .name = ident_name, - }; - addDbgVar(&then_scope, ZIR_INST_DBG_VAR_VAL, ident_name, payload_inst); - then_sub_scope = &payload_val_scope.base; + // 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 payload_inst = addUnNode(&then_scope, - ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE, cond_inst, then_node); - uint32_t ident_name = identAsString(ag, payload_token); - payload_val_scope = (ScopeLocalVal) { - .base = { .tag = SCOPE_LOCAL_VAL }, - .parent = &then_scope.base, - .gen_zir = &then_scope, - .inst = payload_inst, - .token_src = payload_token, - .name = ident_name, - }; - addDbgVar(&then_scope, ZIR_INST_DBG_VAR_VAL, ident_name, payload_inst); - then_sub_scope = &payload_val_scope.base; + 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). @@ -5218,19 +5786,27 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { if (error_token != 0) { // Error capture: else |err| (AstGen.zig:6452-6475). - uint32_t err_inst = addUnNode( - &else_scope, ZIR_INST_ERR_UNION_CODE, cond_inst, cond_node); - 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; + 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). @@ -5245,7 +5821,10 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { (int32_t)else_node - (int32_t)gz->decl_node_index); } } else { - addBreak(&else_scope, ZIR_INST_BREAK, block_inst, ZIR_REF_VOID_VALUE, + // 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); } @@ -5266,7 +5845,7 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { #define FOR_MAX_INPUTS 16 static uint32_t forExpr( - GenZir* gz, Scope* scope, uint32_t node, bool is_statement) { + 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]; @@ -5277,12 +5856,29 @@ static uint32_t forExpr( 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; @@ -5299,6 +5895,8 @@ static uint32_t forExpr( 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). @@ -5318,6 +5916,8 @@ static uint32_t forExpr( && 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++) { @@ -5326,19 +5926,38 @@ static uint32_t forExpr( 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) { - // Range input (AstGen.zig:6892-6916). + // 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 }; + 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; @@ -5346,10 +5965,17 @@ static uint32_t forExpr( 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; } @@ -5369,12 +5995,19 @@ static uint32_t forExpr( } 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; { @@ -5392,12 +6025,16 @@ static uint32_t forExpr( len = addInstruction(gz, ZIR_INST_FOR_LEN, data); } - // Create loop (AstGen.zig:6944-6956). + // 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 @@ -5421,6 +6058,11 @@ static uint32_t forExpr( 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); @@ -5438,16 +6080,7 @@ static uint32_t forExpr( capture_token = ident_tok + 2; // Check if discard (AstGen.zig:6999). - uint32_t ts = tree->tokens.starts[ident_tok]; - bool is_discard = (tree->source[ts] == '_' - && (ts + 1 >= tree->source_len - || !((tree->source[ts + 1] >= 'a' - && tree->source[ts + 1] <= 'z') - || (tree->source[ts + 1] >= 'A' - && tree->source[ts + 1] <= 'Z') - || tree->source[ts + 1] == '_' - || (tree->source[ts + 1] >= '0' - && tree->source[ts + 1] <= '9')))); + bool is_discard = tokenIsUnderscore(tree, ident_tok); if (is_discard) continue; @@ -5508,10 +6141,37 @@ static uint32_t forExpr( addBreak(&then_scope, break_tag, cond_block, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); - // Else branch: break out of loop (AstGen.zig:7066-7091). + // Else branch (AstGen.zig:7066-7091). GenZir else_scope = makeSubBlock(gz, &cond_scope.base); - addBreak(&else_scope, break_tag, loop_inst, ZIR_REF_VOID_VALUE, - AST_NODE_OFFSET_NONE); + + 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); @@ -5539,9 +6199,13 @@ static uint32_t forExpr( setBlockBody(ag, &loop_scope, loop_inst); } - gzAppendInstruction(gz, loop_inst); - uint32_t result = loop_inst + ZIR_REF_START_INDEX; + // 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) { @@ -5554,13 +6218,19 @@ static uint32_t forExpr( // --- 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, bool is_catch) { +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 = is_catch && ag->fn_ret_ty != 0; + 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); @@ -5571,7 +6241,12 @@ static uint32_t orelseCatchExpr( GenZir block_scope = makeSubBlock(gz, scope); // Evaluate operand in block_scope (AstGen.zig:6066-6074). - ResultLoc operand_rl = RL_NONE_VAL; + 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; } @@ -5579,9 +6254,7 @@ static uint32_t orelseCatchExpr( = exprRl(&block_scope, &block_scope.base, operand_rl, nd.lhs); // Check condition in block_scope (AstGen.zig:6075). - ZirInstTag test_tag - = is_catch ? ZIR_INST_IS_NON_ERR : ZIR_INST_IS_NON_NULL; - uint32_t condition = addUnNode(&block_scope, test_tag, operand, node); + 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); @@ -5594,9 +6267,7 @@ static uint32_t orelseCatchExpr( // Then branch: unwrap payload (AstGen.zig:6083-6092). GenZir then_scope = makeSubBlock(&block_scope, scope); - ZirInstTag unwrap_tag = is_catch ? ZIR_INST_ERR_UNION_PAYLOAD_UNSAFE - : ZIR_INST_OPTIONAL_PAYLOAD_UNSAFE; - uint32_t unwrapped = addUnNode(&then_scope, unwrap_tag, operand, node); + 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) @@ -5633,26 +6304,84 @@ static uint32_t orelseCatchExpr( return block_inst + ZIR_REF_START_INDEX; } -// --- whileExpr (AstGen.zig:6529-6805) --- -// Handles while_simple. +// --- whileExpr (AstGen.zig:6529-6817) --- +// Handles while_simple, while_cont, while. // Structure: loop { cond_block { cond, condbr }, repeat } -// condbr → then { continue_block { body, break continue }, break cond } -// → else { break loop } +// 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, uint32_t node, bool is_statement) { + 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); - // WHILE_SIMPLE: lhs = cond_expr, rhs = body. + // 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 = nd.rhs; + 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; @@ -5661,12 +6390,44 @@ static uint32_t whileExpr( 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(&loop_scope, &loop_scope.base); + GenZir cond_scope = makeSubBlock(gz, &loop_scope.base); + // Emit debug node for the condition expression (AstGen.zig:6579). - emitDbgNode(&cond_scope, cond_node); - uint32_t cond = expr(&cond_scope, &cond_scope.base, cond_node); + 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 @@ -5677,10 +6438,86 @@ static uint32_t whileExpr( setBlockBody(ag, &cond_scope, cond_block); // unstacks cond_scope gzAppendInstruction(&loop_scope, cond_block); - // Create continue_block (AstGen.zig:6694). - uint32_t continue_block = makeBlockInst(ag, block_tag, &loop_scope, node); + // 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 - // Add repeat to loop_scope (AstGen.zig:6696-6697). + 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; @@ -5690,30 +6527,70 @@ static uint32_t whileExpr( addInstruction(&loop_scope, repeat_tag, repeat_data); } - // Set loop body and configure break/continue (AstGen.zig:6699-6701). + // 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; + 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; + } - // Stack then_scope (AstGen.zig:6708-6709). + // 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)); - // Add continue_block to then_scope (AstGen.zig:6716). - gzAppendInstruction(&then_scope, continue_block); + 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; + } - // Create continue_scope inside then_scope (AstGen.zig:6725). - GenZir continue_scope = makeSubBlock(&then_scope, &then_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); - // Execute body (AstGen.zig:6727-6730). - emitDbgNode(&continue_scope, body_node); - fullBodyExpr( - &continue_scope, &continue_scope.base, RL_NONE_VAL, body_node); + // 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:6737-6745). + // dbg_stmt + dbg_empty_stmt (AstGen.zig:6736-6745). advanceSourceCursor( - ag, tree->tokens.starts[lastToken(tree, body_node)]); + ag, tree->tokens.starts[lastToken(tree, then_node)]); emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column); { ZirInstData ext_data; @@ -5722,36 +6599,78 @@ static uint32_t whileExpr( 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(&continue_scope, break_tag, continue_block, + addBreak(&continue_scope, break_tag, continue_block_inst, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); } - setBlockBody(ag, &continue_scope, continue_block); + 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); - // Break cond_block from then_scope (AstGen.zig:7064). - { - 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 scope: break loop with void (AstGen.zig:6785-6788). + // Else scope (AstGen.zig:6751-6797). GenZir else_scope = makeSubBlock(gz, &cond_scope.base); - { - ZirInstTag break_tag - = is_inline ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; - addBreak(&else_scope, break_tag, loop_inst, ZIR_REF_VOID_VALUE, + + 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:6795). - setCondBrPayload(ag, condbr, cond, &then_scope, &else_scope); + // Wire up condbr (AstGen.zig:6805). + setCondBrPayload(ag, condbr, bool_bit, &then_scope, &else_scope); - uint32_t result = loop_inst + ZIR_REF_START_INDEX; + // 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-6813). + // Emit ensure_result_used when used as statement (AstGen.zig:6812-6814). if (is_statement) { addUnNode(gz, ZIR_INST_ENSURE_RESULT_USED, result, node); } @@ -5763,14 +6682,105 @@ static uint32_t whileExpr( // 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* gz, Scope* scope, ResultLoc rl, uint32_t node) { - AstGenCtx* ag = gz->astgen; + 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(gz, rl, node, need_rl); + 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]. @@ -5781,119 +6791,370 @@ static uint32_t switchExpr( const uint32_t* case_nodes_arr = tree->extra_data.arr + cases_start; uint32_t case_count = cases_end - cases_start; - // Save operand source location before evaluating (AstGen.zig:7774-7775). - advanceSourceCursorToNode(ag, cond_node); - uint32_t operand_lc_line = ag->source_line - gz->decl_line; - uint32_t operand_lc_col = ag->source_column; - - // Evaluate switch operand (AstGen.zig:7777). - uint32_t cond_ref = expr(gz, scope, cond_node); - - // --- First pass: categorize cases (AstGen.zig:7671-7762) --- + // --- 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); - switch (ct) { - case AST_NODE_SWITCH_CASE_ONE: - case AST_NODE_SWITCH_CASE_INLINE_ONE: - if (cd.lhs == 0) - has_else = true; - else if (tree->nodes.tags[cd.lhs] == AST_NODE_SWITCH_RANGE) - multi_cases_len++; - else - scalar_cases_len++; - break; - case AST_NODE_SWITCH_CASE: - case AST_NODE_SWITCH_CASE_INLINE: - multi_cases_len++; - break; - default: - break; + // 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; } - // Sema expects a dbg_stmt immediately before switch_block - // (AstGen.zig:7806). - emitDbgStmtForceCurrentIndex(gz, operand_lc_line, operand_lc_col); - // --- Create switch_block instruction (AstGen.zig:7809) --- - uint32_t switch_inst = makeBlockInst(ag, ZIR_INST_SWITCH_BLOCK, gz, node); + // 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; + } - // --- Single-pass evaluation in source order (AstGen.zig:7849-8027) --- - // Case table + payload buffer pattern (like upstream scratch). - // Table layout: [else?] [scalar_0..N] [multi_0..N] - // Each entry points to the start of that case's data in the buffer. - uint32_t table_size - = (has_else ? 1 : 0) + scalar_cases_len + multi_cases_len; + // 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 scalar_tbl = (has_else ? 1 : 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; - // Ensure capacity for items (generous estimate). - if (pay_len + 32 > pay_cap) { - pay_cap *= 2; - uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t)); - if (!p) - abort(); - pay = p; - } - - switch (ct) { - case AST_NODE_SWITCH_CASE_ONE: - case AST_NODE_SWITCH_CASE_INLINE_ONE: - if (cd.lhs == 0) { - // Else: [prong_info, body...] - pay[else_tbl] = hdr; + // 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 if (tree->nodes.tags[cd.lhs] == AST_NODE_SWITCH_RANGE) { - // Single range → multi case: - // [items_len=0, ranges_len=1, prong_info, first, last] - pay[multi_tbl + multi_ci++] = hdr; - pay[pay_len++] = 0; - pay[pay_len++] = 1; - prong_info_slot = pay_len++; - AstData rng = tree->nodes.datas[cd.lhs]; - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, rng.lhs, - COMPTIME_REASON_SWITCH_ITEM); - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, rng.rhs, - COMPTIME_REASON_SWITCH_ITEM); } else { - // Scalar: [item_ref, prong_info, body...] - pay[scalar_tbl + scalar_ci++] = hdr; - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, cd.lhs, - COMPTIME_REASON_SWITCH_ITEM); + // 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); + } + } } - break; - case AST_NODE_SWITCH_CASE: - case AST_NODE_SWITCH_CASE_INLINE: { - // Multi-item: SubRange[lhs] of items, rhs = body. + } 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 ist = tree->extra_data.arr[cd.lhs]; - uint32_t ien = tree->extra_data.arr[cd.lhs + 1]; - uint32_t nitems = 0, nranges = 0; - for (uint32_t j = ist; j < ien; j++) { - if (tree->nodes.tags[tree->extra_data.arr[j]] - == AST_NODE_SWITCH_RANGE) + 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++; @@ -5902,128 +7163,189 @@ static uint32_t switchExpr( pay[pay_len++] = nranges; prong_info_slot = pay_len++; // Non-range items. - for (uint32_t j = ist; j < ien; j++) { - uint32_t item = tree->extra_data.arr[j]; - if (tree->nodes.tags[item] != AST_NODE_SWITCH_RANGE) { - if (pay_len + 2 > pay_cap) { - pay_cap *= 2; - uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t)); - if (!p) - abort(); - pay = p; - } - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, item, - COMPTIME_REASON_SWITCH_ITEM); + 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 j = ist; j < ien; j++) { - uint32_t item = tree->extra_data.arr[j]; - if (tree->nodes.tags[item] == AST_NODE_SWITCH_RANGE) { - AstData rng = tree->nodes.datas[item]; - if (pay_len + 2 > pay_cap) { - pay_cap *= 2; - uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t)); - if (!p) - abort(); - pay = p; - } - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, - rng.lhs, COMPTIME_REASON_SWITCH_ITEM); - pay[pay_len++] = comptimeExpr(gz, scope, RL_NONE_VAL, - rng.rhs, COMPTIME_REASON_SWITCH_ITEM); + 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); } } - break; - } - default: - continue; } // Evaluate body (AstGen.zig:7997-8026). - uint32_t body_node = cd.rhs; - GenZir case_scope = makeSubBlock(gz, scope); + { + // Temporarily stack case_scope on parent_gz + // (AstGen.zig:7998-8000). + case_scope.instructions_top = parent_gz->astgen->scratch_inst_len; - // Note: upstream regular switchExpr (AstGen.zig:7625) does NOT emit - // save_err_ret_index. Only switchExprErrUnion (AstGen.zig:7524) does. + 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); + } - // Use fullBodyExpr to process body inline (AstGen.zig:8009). - uint32_t result - = fullBodyExpr(&case_scope, &case_scope.base, break_rl, body_node); - if (!refIsNoReturn(gz, result)) { - addBreak(&case_scope, ZIR_INST_BREAK, switch_inst, result, - (int32_t)body_node - (int32_t)gz->decl_node_index); + 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); } - uint32_t body_len = gzInstructionsLen(&case_scope); - const uint32_t* body = gzInstructionsSlice(&case_scope); - - pay[prong_info_slot] = body_len & 0x0FFFFFFFu; - - if (pay_len + body_len > pay_cap) { - while (pay_len + body_len > pay_cap) - pay_cap *= 2; - uint32_t* p = realloc(pay, pay_cap * sizeof(uint32_t)); - if (!p) - abort(); - pay = p; - } - for (uint32_t i = 0; i < body_len; i++) - pay[pay_len++] = body[i]; - 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) + pay_len - table_size); + 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; - ag->extra[ag->extra_len++] = cond_ref; + // SwitchBlock.operand (AstGen.zig:8042). + ag->extra[ag->extra_len++] = raw_operand; - uint32_t bits = 0; - if (multi_cases_len > 0) - bits |= 1u; - if (has_else) - bits |= (1u << 1); - bits |= (scalar_cases_len & 0x1FFFFFFu) << 7; - ag->extra[ag->extra_len++] = bits; + // 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; - // Else prong. + // 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 bl = pay[si] & 0x0FFFFFFFu; - for (uint32_t i = 0; i < 1 + bl; i++) - ag->extra[ag->extra_len++] = pay[si + i]; + 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]; } - // Scalar cases. - for (uint32_t i = 0; i < scalar_cases_len; i++) { - uint32_t si = pay[scalar_tbl + i]; - uint32_t bl = pay[si + 1] & 0x0FFFFFFFu; - for (uint32_t j = 0; j < 2 + bl; j++) - ag->extra[ag->extra_len++] = pay[si + j]; + + // 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]; } - // Multi cases. - for (uint32_t i = 0; i < multi_cases_len; i++) { - uint32_t si = pay[multi_tbl + i]; - uint32_t ni = pay[si]; - uint32_t nr = pay[si + 1]; - uint32_t bl = pay[si + 2] & 0x0FFFFFFFu; - uint32_t total = 3 + ni + nr * 2 + bl; - for (uint32_t j = 0; j < total; j++) - ag->extra[ag->extra_len++] = pay[si + j]; + + // 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); - ag->inst_datas[switch_inst].pl_node.payload_index = payload_index; - gzAppendInstruction(gz, switch_inst); - // AstGen.zig:8112-8115. bool need_result_rvalue = (break_rl.tag != rl.tag); if (need_result_rvalue) - return rvalue(gz, rl, switch_inst + ZIR_REF_START_INDEX, node); + return rvalue(parent_gz, rl, switch_inst + ZIR_REF_START_INDEX, node); return switch_inst + ZIR_REF_START_INDEX; } @@ -6201,6 +7523,63 @@ static void assignOp( 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. @@ -6405,6 +7784,14 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, 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; @@ -6438,6 +7825,12 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, 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 @@ -6457,7 +7850,8 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, } // Evaluate init expression (AstGen.zig:3251-3252). - uint32_t init_ref = exprRl(gz, scope, result_info, init_node); + uint32_t init_ref = reachableExprComptime( + gz, scope, result_info, init_node, node, force_comptime); if (ag->has_compile_errors) return; @@ -6518,7 +7912,8 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, init_rl.src_node = 0; } init_rl.ctx = RI_CTX_CONST_INIT; - uint32_t init_ref = exprRl(gz, scope, init_rl, init_node); + uint32_t init_ref = reachableExprComptime( + gz, scope, init_rl, init_node, node, force_comptime); if (ag->has_compile_errors) return; @@ -6552,20 +7947,22 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, } 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 = gz->is_comptime - ? ZIR_INST_ALLOC_COMPTIME_MUT - : ZIR_INST_ALLOC_MUT; + 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 = gz->is_comptime + ZirInstTag alloc_tag = is_comptime ? ZIR_INST_ALLOC_INFERRED_COMPTIME_MUT : ZIR_INST_ALLOC_INFERRED_MUT; ZirInstData adata; @@ -6587,7 +7984,10 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, var_init_rl.src_node = 0; } var_init_rl.ctx = RI_CTX_NONE; - uint32_t init_ref = exprRl(gz, scope, var_init_rl, init_node); + 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) @@ -6610,7 +8010,7 @@ static void varDecl(GenZir* gz, Scope* scope, uint32_t node, ptr_out->ptr = final_ptr; ptr_out->token_src = name_token; ptr_out->name = ident_name; - ptr_out->maybe_comptime = gz->is_comptime; + ptr_out->maybe_comptime = is_comptime; *scope_out = &ptr_out->base; } } @@ -6848,142 +8248,165 @@ static void blockExprStmts(GenZir* gz, Scope* scope, if (ag->has_compile_errors) return; uint32_t stmt = statements[i]; - AstNodeTag tag = ag->tree->nodes.tags[stmt]; - switch (tag) { - case AST_NODE_ASSIGN: - assignStmt(gz, cur_scope, stmt); - break; - // Compound assignment operators (AstGen.zig:2588-2607). - case AST_NODE_ASSIGN_ADD: - assignOp(gz, cur_scope, stmt, ZIR_INST_ADD); - break; - case AST_NODE_ASSIGN_SUB: - assignOp(gz, cur_scope, stmt, ZIR_INST_SUB); - break; - case AST_NODE_ASSIGN_MUL: - assignOp(gz, cur_scope, stmt, ZIR_INST_MUL); - break; - case AST_NODE_ASSIGN_DIV: - assignOp(gz, cur_scope, stmt, ZIR_INST_DIV); - break; - case AST_NODE_ASSIGN_MOD: - assignOp(gz, cur_scope, stmt, ZIR_INST_MOD_REM); - break; - case AST_NODE_ASSIGN_BIT_AND: - assignOp(gz, cur_scope, stmt, ZIR_INST_BIT_AND); - break; - case AST_NODE_ASSIGN_BIT_OR: - assignOp(gz, cur_scope, stmt, ZIR_INST_BIT_OR); - break; - case AST_NODE_ASSIGN_BIT_XOR: - assignOp(gz, cur_scope, stmt, ZIR_INST_XOR); - break; - case AST_NODE_ASSIGN_ADD_WRAP: - assignOp(gz, cur_scope, stmt, ZIR_INST_ADDWRAP); - break; - case AST_NODE_ASSIGN_SUB_WRAP: - assignOp(gz, cur_scope, stmt, ZIR_INST_SUBWRAP); - break; - case AST_NODE_ASSIGN_MUL_WRAP: - assignOp(gz, cur_scope, stmt, ZIR_INST_MULWRAP); - break; - case AST_NODE_ASSIGN_ADD_SAT: - assignOp(gz, cur_scope, stmt, ZIR_INST_ADD_SAT); - break; - case AST_NODE_ASSIGN_SUB_SAT: - assignOp(gz, cur_scope, stmt, ZIR_INST_SUB_SAT); - break; - case AST_NODE_ASSIGN_MUL_SAT: - assignOp(gz, cur_scope, stmt, 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); + // 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; } - 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); - - // 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; + // 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; } - // 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; - } - // 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, stmt, true); - break; - case AST_NODE_FOR_SIMPLE: - case AST_NODE_FOR: - (void)forExpr(gz, cur_scope, stmt, true); - break; - default: { - // Expression statement (AstGen.zig:2627 unusedResultExpr). - emitDbgNode(gz, stmt); - uint32_t result = expr(gz, cur_scope, stmt); - noreturn_stmt = addEnsureResult(gz, result, stmt); - break; - } + } + break; // Break out of the for(;;) unwrapping loop. } } // Emit normal defers at block exit (AstGen.zig:2633-2634). @@ -7063,6 +8486,23 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { 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: @@ -7198,7 +8638,7 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { continue; } - // Unary ops: recurse into lhs (Ast.zig:895-910). + // Unary ops: recurse into lhs (Ast.zig:880-891). case AST_NODE_BOOL_NOT: case AST_NODE_BIT_NOT: case AST_NODE_NEGATION: @@ -7207,6 +8647,7 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { 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: @@ -7221,22 +8662,21 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { } return tree->nodes.main_tokens[n] + end_offset; - // deref: main_token is the dot, +1 for '*' (Ast.zig:974). + // deref: main_token is the `.*` token (Ast.zig:993). case AST_NODE_DEREF: - return tree->nodes.main_tokens[n] + 1 + end_offset; + return tree->nodes.main_tokens[n] + end_offset; - // unwrap_optional: +1 for '?' (Ast.zig:971). + // unwrap_optional (Ast.zig:980): return rhs token + end_offset. case AST_NODE_UNWRAP_OPTIONAL: - return tree->nodes.main_tokens[n] + 1 + end_offset; + return nd.rhs + end_offset; - // for_range: recurse into rhs if present, else lhs. + // 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 { - // Unbounded range: last token is the '..' operator. - // main_token + 1 (the second dot of ..) - return tree->nodes.main_tokens[n] + 1 + end_offset; + return tree->nodes.main_tokens[n] + end_offset; } continue; @@ -7255,22 +8695,19 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { case AST_NODE_ANYFRAME_LITERAL: return tree->nodes.main_tokens[n] + end_offset; - // call_one: recurse into lhs, +1 for ')'. + // 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 { - n = nd.lhs; + return tree->nodes.main_tokens[n] + end_offset; } continue; case AST_NODE_CALL_ONE_COMMA: end_offset += 2; // comma + rparen - if (nd.rhs != 0) { - n = nd.rhs; - } else { - n = nd.lhs; - } + n = nd.rhs; continue; // array_access: end_offset += 1 (rbracket), recurse rhs. @@ -7302,25 +8739,38 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { 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 { - // extra[lhs] has align_node end_offset += 1; // rparen - n = tree->extra_data.arr[nd.lhs]; // align_node + 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 { - // extra[lhs] = {type_node, align_node, ...} - // complex; approximate by using main_token - end_offset += 1; - return tree->nodes.main_tokens[n] + end_offset; + 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; @@ -7331,11 +8781,9 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { n = nd.rhs; continue; - // grouped_expression: end_offset += 1 (rparen), recurse lhs. + // grouped_expression (Ast.zig:983): return rhs token + end_offset. case AST_NODE_GROUPED_EXPRESSION: - end_offset += 1; - n = nd.lhs; - continue; + return nd.rhs + end_offset; // if_simple: recurse into body (rhs) (Ast.zig:942). case AST_NODE_IF_SIMPLE: @@ -7509,7 +8957,7 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { n = tree->extra_data.arr[nd.rhs - 1]; continue; - // container_decl_two: like block_two. + // 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) { @@ -7519,7 +8967,20 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { end_offset += 1; n = nd.lhs; } else { - end_offset += 2; // lbrace + rbrace + 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; @@ -7705,6 +9166,91 @@ static uint32_t lastToken(const Ast* tree, uint32_t node) { 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; @@ -7830,6 +9376,123 @@ static uint32_t addFunc(GenZir* gz, uint32_t src_node, uint32_t block_node, 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, @@ -7862,8 +9525,13 @@ static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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; } - // TODO: handle identifier test names (decltest). // Set up decl_block GenZir (AstGen.zig:4735-4743). GenZir decl_block; @@ -7876,6 +9544,7 @@ static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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; @@ -7888,10 +9557,14 @@ static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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 fn_block and fn_ret_ty for the body (AstGen.zig:4849-4853). + // 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; @@ -7904,6 +9577,7 @@ static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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; @@ -7959,24 +9633,52 @@ static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, } // --- fnDecl (AstGen.zig:4067) / fnDeclInner (AstGen.zig:4228) --- -// Handles non-extern function declarations with bodies, including params. +// 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. - uint32_t proto_node = nd.lhs; - uint32_t body_node = nd.rhs; + // 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' modifier (Ast.zig:2003-2025). - bool is_pub = (fn_token > 0 - && tree->tokens.tags[fn_token - 1] == TOKEN_KEYWORD_PUB); + // 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); @@ -7987,6 +9689,11 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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; @@ -8011,13 +9718,16 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, } } - // Extract param type nodes from proto variant (AstGen.zig:4253-4254). + // 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; @@ -8033,8 +9743,11 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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]; @@ -8048,6 +9761,8 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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) @@ -8062,6 +9777,7 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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. @@ -8072,45 +9788,113 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, // 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; - for (uint32_t param_i = 0; param_i < params_len; param_i++) { - uint32_t param_type_node = param_nodes[param_i]; + // 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 }; - // Find param name token by scanning backwards from firstToken of - // type expression (mirrors FnProto.Iterator.next, Ast.zig:2687). - // Layout: [comptime] [name] [:] type_expr - // So: type_first_tok - 1 is ':', type_first_tok - 2 is name. - uint32_t type_first_tok = firstToken(tree, param_type_node); - uint32_t name_token = 0; // 0 = no name found + 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; - if (type_first_tok >= 2 - && tree->tokens.tags[type_first_tok - 1] == TOKEN_COLON) { - // Named parameter: name is at type_first_tok - 2. - uint32_t maybe_name = type_first_tok - 2; - uint32_t name_start = tree->tokens.starts[maybe_name]; - char ch = tree->source[name_start]; - if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') - || ch == '_' || ch == '@') { - // Could be name or comptime/noalias keyword. - if (name_start + 8 <= tree->source_len - && memcmp(tree->source + name_start, "comptime", 8) == 0) { - is_comptime_param = true; - } else if (name_start + 7 <= tree->source_len - && memcmp(tree->source + name_start, "noalias", 7) == 0) { - // noalias keyword, not a name. - } else { - name_token = maybe_name; - // Check for preceding comptime keyword. - if (maybe_name > 0) { - uint32_t prev = maybe_name - 1; - uint32_t prev_start = tree->tokens.starts[prev]; - if (prev_start + 8 <= tree->source_len - && memcmp(tree->source + prev_start, "comptime", 8) - == 0) - is_comptime_param = true; - } + 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). @@ -8134,38 +9918,49 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, } } - // Evaluate param type expression in a sub-block - // (AstGen.zig:4333-4337). - GenZir param_gz = makeSubBlock(&decl_gz, params_scope); - uint32_t param_type_ref - = expr(¶m_gz, params_scope, param_type_node); + // 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; + 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; - // +1 because: the break_inline is emitted first (uses inst_len), - // then addParam emits the param instruction at inst_len. - // Actually, addParam emits the param after break_inline. The - // break_inline's block_inst field should point to the param inst. - // We know it will be at ag->inst_len after the break_inline. - makeBreakInline(¶m_gz, param_inst_expected, param_type_ref, - (int32_t)param_type_node - (int32_t)param_gz.decl_node_index); + // 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; - // Record param instruction index (AstGen.zig:4360). - if (param_insts_len < 32) - param_insts[param_insts_len++] = param_inst; + // 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) { @@ -8173,18 +9968,20 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, lv->base.tag = SCOPE_LOCAL_VAL; lv->parent = params_scope; lv->gen_zir = &decl_gz; - lv->inst = param_inst + ZIR_REF_START_INDEX; // toRef() + 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 = expr(&ret_gz, params_scope, return_type_node); + 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 @@ -8216,6 +10013,64 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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)); @@ -8226,11 +10081,15 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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 and fn_ret_ty for the body (AstGen.zig:4442-4455). + // 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) { @@ -8263,11 +10122,14 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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; } @@ -8293,22 +10155,40 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t fn_body_len = gzInstructionsLen(&body_gz); gzUnstack(&body_gz); - // Create func instruction (AstGen.zig:4476-4494). - uint32_t 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); + // 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]; - // The break_inline payload is at payload_index; block_inst is at - // offset 1 in the Break struct. + 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. @@ -8316,8 +10196,12 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, &decl_gz, decl_inst, func_ref, (int32_t)node - (int32_t)proto_node); // setDeclaration (AstGen.zig:4208-4225). - DeclFlagsId decl_id - = is_pub ? DECL_ID_PUB_CONST_SIMPLE : DECL_ID_CONST_SIMPLE; + // 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, @@ -8356,6 +10240,7 @@ static void comptimeDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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, @@ -8524,12 +10409,69 @@ static DeclFlagsId computeVarDeclId(bool is_mutable, bool is_pub, 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; @@ -8538,6 +10480,26 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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)); @@ -8547,6 +10509,7 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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); @@ -8556,7 +10519,7 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, // Record type_gz boundary for slicing. uint32_t type_top = ag->scratch_inst_len; - // Align sub-block (AstGen.zig:4592-4596). + // Align sub-block (AstGen.zig:4585-4591). GenZir align_gz; memset(&align_gz, 0, sizeof(align_gz)); align_gz.base.tag = SCOPE_GEN_ZIR; @@ -8565,15 +10528,23 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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) { - uint32_t align_inst = expr(&align_gz, &align_gz.base, vd.align_node); + // 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:4598-4602). + // Linksection sub-block (AstGen.zig:4593-4599). GenZir linksection_gz; memset(&linksection_gz, 0, sizeof(linksection_gz)); linksection_gz.base.tag = SCOPE_GEN_ZIR; @@ -8582,16 +10553,23 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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) { - uint32_t ls_inst - = expr(&linksection_gz, &linksection_gz.base, vd.section_node); + // 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:4604-4608). + // Addrspace sub-block (AstGen.zig:4601-4608). GenZir addrspace_gz; memset(&addrspace_gz, 0, sizeof(addrspace_gz)); addrspace_gz.base.tag = SCOPE_GEN_ZIR; @@ -8600,16 +10578,25 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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) { - uint32_t as_inst - = expr(&addrspace_gz, &addrspace_gz.base, vd.addrspace_node); + // 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-4620). + // Value sub-block (AstGen.zig:4610-4621). GenZir value_gz; memset(&value_gz, 0, sizeof(value_gz)); value_gz.base.tag = SCOPE_GEN_ZIR; @@ -8618,9 +10605,26 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, 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) { - uint32_t init_ref = expr(&value_gz, &value_gz.base, vd.init_node); + // 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); } @@ -8651,14 +10655,6 @@ static void globalVarDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, vd.is_extern, vd.is_export, vd.is_threadlocal, has_type_body, has_special_body, has_lib_name); - // Compute lib_name string index. - uint32_t lib_name = UINT32_MAX; - if (has_lib_name) { - uint32_t li, ll; - strLitAsString(ag, vd.lib_name_token, &li, &ll); - lib_name = li; - } - setDeclaration(ag, decl_inst, (SetDeclArgs) { .src_line = ag->source_line, .src_column = decl_column, @@ -8835,15 +10831,16 @@ typedef struct { uint32_t bodies_cap; } WipMembers; -static WipMembers wipMembersInit(uint32_t decl_count, uint32_t field_count) { - // bits_per_field = 4, max_field_size = 5 - uint32_t fields_per_u32 = 8; // 32 / 4 +// 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 + 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 * 5; + 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) @@ -8863,6 +10860,11 @@ static WipMembers wipMembersInit(uint32_t decl_count, uint32_t field_count) { 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); @@ -8904,6 +10906,29 @@ static void wipMembersFinishBits(WipMembers* wm) { } } +// 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) { @@ -8967,7 +10992,8 @@ static void wipMembersBodiesAppendWithFixups( // --- containerDecl (AstGen.zig:5468) --- // Handles container declarations as expressions (struct{}, enum{}, etc.). -static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node) { +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]; @@ -9021,20 +11047,48 @@ static uint32_t containerDecl(GenZir* gz, Scope* scope, uint32_t node) { 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: - decl_inst = structDeclInner(ag, gz, node, members, members_len); + 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); + 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); + decl_inst = structDeclInner( + ag, gz, node, members, members_len, 0, 0, name_strategy); break; } (void)scope; @@ -9073,14 +11127,26 @@ static uint16_t packEnumDeclSmall(EnumDeclSmall s) { return r; } -// Mirrors GenZir.setEnum (AstGen.zig:13080). +// Mirrors GenZir.setEnum (AstGen.zig:13064-13123). static void setEnum(AstGenCtx* ag, uint32_t inst, uint32_t src_node, - EnumDeclSmall small, uint32_t fields_len, uint32_t decls_len) { - ensureExtraCapacity(ag, 6 + 3); + 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. + // 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; @@ -9089,6 +11155,13 @@ static void setEnum(AstGenCtx* ag, uint32_t inst, uint32_t src_node, 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) @@ -9103,42 +11176,92 @@ static void setEnum(AstGenCtx* ag, uint32_t inst, uint32_t src_node, ag->inst_datas[inst] = data; } -// --- enumDeclInner (AstGen.zig:5508) --- +// 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) { + 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); - if (members_len == 0) { - EnumDeclSmall small; - memset(&small, 0, sizeof(small)); - setEnum(ag, decl_inst, node, small, 0, 0); - return decl_inst; - } - advanceSourceCursorToNode(ag, node); - uint32_t decl_count = scanContainer(ag, members, members_len); - uint32_t field_count = members_len - decl_count; + // scanContainer to register names in string table (AstGen.zig:5635). + scanContainer(ag, members, members_len); - // Use WipMembers for decls and field data. - // Enum fields: 1 bit per field (has_value), max 2 words per field - // (name + value). - WipMembers wm = wipMembersInit(decl_count, field_count); + // 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; - // Enum fields use 1 bit per field: has_value. - // We use the same WipMembers but with 1-bit fields. - // Actually, upstream uses bits_per_field=1, max_field_size=2. - // Re-init with correct params would be better but let's reuse. - // For simplicity: track field data manually. - uint32_t* field_names = NULL; - uint32_t field_names_len = 0; - uint32_t field_names_cap = 0; + // 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: @@ -9159,19 +11282,33 @@ static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, case AST_NODE_CONTAINER_FIELD_INIT: case AST_NODE_CONTAINER_FIELD_ALIGN: case AST_NODE_CONTAINER_FIELD: { - // Enum field: just a name (AstGen.zig:5617-5670). + // Enum field (AstGen.zig:5669-5692). uint32_t main_token = tree->nodes.main_tokens[member_node]; uint32_t field_name = identAsString(ag, main_token); - // Grow field_names array. - if (field_names_len >= field_names_cap) { - uint32_t new_cap - = field_names_cap == 0 ? 8 : field_names_cap * 2; - field_names = realloc(field_names, new_cap * sizeof(uint32_t)); - if (!field_names) - exit(1); - field_names_cap = new_cap; + 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); } - field_names[field_names_len++] = field_name; break; } default: @@ -9180,49 +11317,250 @@ static uint32_t enumDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, } } - EnumDeclSmall small; - memset(&small, 0, sizeof(small)); - small.has_fields_len = (field_count > 0); - small.has_decls_len = (decl_count > 0); - setEnum(ag, decl_inst, node, small, field_count, decl_count); + // 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); + } - // Append: decls, field_bits, field_names (AstGen.zig:5724-5729). + 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); - // Field bits: 1 bit per field (has_value = false for simple enums). - uint32_t fields_per_u32 = 32; - uint32_t bit_words = field_count > 0 - ? (field_count + fields_per_u32 - 1) / fields_per_u32 - : 0; - - ensureExtraCapacity(ag, decls_len_out + bit_words + field_names_len); + 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]; - // Field bits: all zero (no values). - for (uint32_t i = 0; i < bit_words; i++) - ag->extra[ag->extra_len++] = 0; - // Field names. - for (uint32_t i = 0; i < field_names_len; i++) - ag->extra[ag->extra_len++] = field_names[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]; - free(field_names); + 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) { + 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) { + 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; } @@ -9236,16 +11574,46 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, WipMembers wm = wipMembersInit(decl_count, field_count); // Set up block_scope for field type/align/init expressions. - // (AstGen.zig:4983-4992) + // (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 = ag->source_line; + 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; @@ -9270,6 +11638,13 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, 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: @@ -9315,34 +11690,44 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, has_comptime_token = true; } - // Field name (AstGen.zig:5080). + // 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 (AstGen.zig:5089-5109). + // 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; - if (type_node != 0) { - field_type - = typeExpr(&block_scope, &block_scope.base, type_node); - have_type_body = (gzInstructionsLen(&block_scope) > 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:5106-5109) - if (type_node != 0) { - known_non_opv = known_non_opv - || nodeImpliesMoreThanOnePossibleValue( - tree, type_node); - known_comptime_only = known_comptime_only - || nodeImpliesComptimeOnly(tree, type_node); - } + // (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] @@ -9370,9 +11755,21 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, } 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; - uint32_t align_ref - = expr(&block_scope, &block_scope.base, align_node); + // 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); @@ -9387,8 +11784,20 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, if (have_value) { any_default_inits = true; - uint32_t default_ref - = expr(&block_scope, &block_scope.base, value_node); + // 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); @@ -9399,6 +11808,10 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, 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; } @@ -9415,21 +11828,43 @@ static uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, 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 (none), decls, fields, bodies - // (AstGen.zig:5176-5189). + // 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); - ensureExtraCapacity(ag, decls_len + fields_len + wm.bodies_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++) @@ -9484,11 +11919,33 @@ static bool nodesNeedRlContains(const AstGenCtx* ag, uint32_t node) { } // 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]; @@ -9935,10 +12392,13 @@ static bool rlExpr( 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 - = (main_tok >= 2 && tree->tokens.tags[main_tok - 1] == TOKEN_COLON - && tree->tokens.tags[main_tok - 2] == TOKEN_IDENTIFIER); - uint32_t label_token = is_labeled ? main_tok - 2 : UINT32_MAX; + = (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); @@ -10006,10 +12466,14 @@ static bool rlExpr( } uint32_t main_tok = tree->nodes.main_tokens[node]; - bool is_labeled - = (main_tok >= 2 && tree->tokens.tags[main_tok - 1] == TOKEN_COLON - && tree->tokens.tags[main_tok - 2] == TOKEN_IDENTIFIER); - uint32_t label_token = is_labeled ? main_tok - 2 : UINT32_MAX; + 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]; @@ -10583,8 +13047,15 @@ static bool rlExpr( case AST_NODE_AWAIT: (void)rlExpr(ag, nd.lhs, block, RL_RI_NONE); return false; - case AST_NODE_ASSIGN_DESTRUCTURE: - return false; // TODO if needed + 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: @@ -10658,6 +13129,7 @@ Zir astGen(const Ast* ast) { 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]; @@ -10666,7 +13138,8 @@ Zir astGen(const Ast* ast) { 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); + structDeclInner( + &ag, &gen_scope, 0, members, members_len, 0, 0, 0 /* parent */); // Write imports list (AstGen.zig:227-244). writeImports(&ag); diff --git a/stage0/astgen_test.zig b/stage0/astgen_test.zig index 5592d1fc32..a935e0cf07 100644 --- a/stage0/astgen_test.zig +++ b/stage0/astgen_test.zig @@ -325,6 +325,11 @@ fn expectEqualData( .shl_with_overflow, .restore_err_ret_index, .branch_hint, + // Container decl Small packed structs have undefined padding bits. + .struct_decl, + .enum_decl, + .union_decl, + .opaque_decl, => true, else => false, }; diff --git a/stage0/zir.h b/stage0/zir.h index 10950e1249..88b858c27e 100644 --- a/stage0/zir.h +++ b/stage0/zir.h @@ -513,6 +513,23 @@ typedef union { #define ZIR_REF_BOOL_FALSE 122 #define ZIR_REF_EMPTY_TUPLE 123 +// Zir.Inst.BuiltinValue enum (Zir.zig:3476-3494). +#define ZIR_BUILTIN_VALUE_ATOMIC_ORDER 0 +#define ZIR_BUILTIN_VALUE_ATOMIC_RMW_OP 1 +#define ZIR_BUILTIN_VALUE_CALLING_CONVENTION 2 +#define ZIR_BUILTIN_VALUE_ADDRESS_SPACE 3 +#define ZIR_BUILTIN_VALUE_FLOAT_MODE 4 +#define ZIR_BUILTIN_VALUE_REDUCE_OP 5 +#define ZIR_BUILTIN_VALUE_CALL_MODIFIER 6 +#define ZIR_BUILTIN_VALUE_PREFETCH_OPTIONS 7 +#define ZIR_BUILTIN_VALUE_EXPORT_OPTIONS 8 +#define ZIR_BUILTIN_VALUE_EXTERN_OPTIONS 9 +#define ZIR_BUILTIN_VALUE_TYPE_INFO 10 +#define ZIR_BUILTIN_VALUE_BRANCH_HINT 11 +#define ZIR_BUILTIN_VALUE_CLOBBERS 12 +#define ZIR_BUILTIN_VALUE_CALLING_CONVENTION_C 13 +#define ZIR_BUILTIN_VALUE_CALLING_CONVENTION_INLINE 14 + // Ast.Node.OptionalOffset.none = maxInt(i32). #define AST_NODE_OFFSET_NONE ((int32_t)0x7FFFFFFF)