From 4fc156d63710541410782f14a742d6af3d36f7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Thu, 12 Feb 2026 13:24:27 +0000 Subject: [PATCH] astgen: RL threading, labeled blocks, comptime block payload Port several AstGen.zig patterns to C: - Thread ResultLoc through fullBodyExpr, ifExpr, switchExpr, callExpr, calleeExpr (for proper type coercion and decl_literal handling) - Add rlBr() and breakResultInfo() helpers mirroring upstream ri.br() and setBreakResultInfo - Implement labeled blocks with label on GenZir (matching upstream), restoreErrRetIndex before break, and break_result_info - Fix breakExpr to emit restoreErrRetIndex and use break_result_info for value/void breaks (AstGen.zig:2150-2237) - Add setBlockComptimeBody with comptime_reason field (was using setBlockBody which omitted the reason, causing wrong extra layout) - Add comptime_reason parameter to comptimeExpr with correct reasons for type/array_sentinel/switch_item/comptime_keyword contexts - Handle enum_literal in calleeExpr (decl_literal_no_coerce) - Fix decl_literal rvalue wrapping for ty/coerced_ty result locs All 5 corpus files now pass byte-by-byte ZIR comparison. Co-Authored-By: Claude Opus 4.6 --- astgen.c | 515 ++++++++++++++++++++++++++++++++++-------------- astgen_test.zig | 42 ++-- 2 files changed, 394 insertions(+), 163 deletions(-) diff --git a/astgen.c b/astgen.c index 7c8411bde1..b0bb113ed9 100644 --- a/astgen.c +++ b/astgen.c @@ -159,6 +159,32 @@ static bool refTableFetchRemove(AstGenCtx* ag, uint32_t key, uint32_t* val) { return false; } +// --- Result location (AstGen.zig:11808) --- +// Simplified version of ResultInfo.Loc. +// Defined here (before GenZir) because GenZir.break_result_info uses it. + +typedef enum { + RL_NONE, // Just compute the value. + RL_REF, // Compute a pointer to the value. + RL_DISCARD, // Compute but discard (emit ensure_result_non_error). + RL_TY, // Coerce to specific type. + RL_COERCED_TY, // Coerce to specific type, result is the coercion. + RL_PTR, // Store result to typed pointer. data=alloc inst, src_node=node. + RL_INFERRED_PTR, // Store result to inferred pointer. data=alloc inst. +} ResultLocTag; + +typedef struct { + ResultLocTag tag; + uint32_t data; // ZirInstRef: ty_inst for TY/COERCED_TY, alloc inst for + // PTR/INFERRED_PTR. + uint32_t src_node; // Only used for RL_PTR. +} ResultLoc; + +#define RL_NONE_VAL ((ResultLoc) { .tag = RL_NONE, .data = 0, .src_node = 0 }) +#define RL_REF_VAL ((ResultLoc) { .tag = RL_REF, .data = 0, .src_node = 0 }) +#define RL_DISCARD_VAL \ + ((ResultLoc) { .tag = RL_DISCARD, .data = 0, .src_node = 0 }) + // --- Scope types (AstGen.zig:11621-11768) --- typedef enum { @@ -193,6 +219,10 @@ typedef struct { uint32_t instructions_top; // start index in shared array uint32_t break_block; // UINT32_MAX = none (AstGen.zig:11780) uint32_t continue_block; // UINT32_MAX = none (AstGen.zig:11784) + // Label for labeled blocks (AstGen.zig:11800, 11869-11874). + uint32_t label_token; // UINT32_MAX = no label + uint32_t label_block_inst; // the BLOCK instruction index + ResultLoc break_result_info; // RL for break values } GenZir; // Scope.LocalVal (AstGen.zig:11682). @@ -283,6 +313,7 @@ static GenZir makeSubBlock(GenZir* parent, Scope* scope) { sub.instructions_top = parent->astgen->scratch_inst_len; sub.break_block = UINT32_MAX; sub.continue_block = UINT32_MAX; + sub.label_token = UINT32_MAX; return sub; } @@ -764,6 +795,25 @@ static void setTryBody( gzUnstack(gz); } +// Mirrors GenZir.setBlockComptimeBody (AstGen.zig:11972). +// Like setBlockBody but prepends comptime_reason before body_len. +// Asserts inst is a BLOCK_COMPTIME. +static void setBlockComptimeBody( + AstGenCtx* ag, GenZir* gz, uint32_t inst, uint32_t comptime_reason) { + uint32_t raw_body_len = gzInstructionsLen(gz); + const uint32_t* body = gzInstructionsSlice(gz); + uint32_t body_len = countBodyLenAfterFixups(ag, body, raw_body_len); + ensureExtraCapacity(ag, 2 + body_len); + uint32_t payload_index = ag->extra_len; + ag->extra[ag->extra_len++] = comptime_reason; + ag->extra[ag->extra_len++] = body_len; + for (uint32_t i = 0; i < raw_body_len; i++) { + appendPossiblyRefdBodyInst(ag, body[i]); + } + ag->inst_datas[inst].pl_node.payload_index = payload_index; + gzUnstack(gz); +} + // Mirrors GenZir.addBreak (AstGen.zig:12623). // Creates a ZIR_INST_BREAK instruction. static uint32_t addBreak(GenZir* gz, ZirInstTag tag, uint32_t block_inst, @@ -1081,30 +1131,46 @@ static void writeImports(AstGenCtx* ag) { ag->extra[ZIR_EXTRA_IMPORTS] = imports_index; } -// --- Result location (AstGen.zig:11808) --- -// Simplified version of ResultInfo.Loc. +// ri.br() (AstGen.zig:274-282): convert coerced_ty to ty for branching. +static inline ResultLoc rlBr(ResultLoc rl) { + if (rl.tag == RL_COERCED_TY) { + return (ResultLoc) { .tag = RL_TY, .data = rl.data, .src_node = 0 }; + } + return rl; +} -typedef enum { - RL_NONE, // Just compute the value. - RL_REF, // Compute a pointer to the value. - RL_DISCARD, // Compute but discard (emit ensure_result_non_error). - RL_TY, // Coerce to specific type. - RL_COERCED_TY, // Coerce to specific type, result is the coercion. - RL_PTR, // Store result to typed pointer. data=alloc inst, src_node=node. - RL_INFERRED_PTR, // Store result to inferred pointer. data=alloc inst. -} ResultLocTag; - -typedef struct { - ResultLocTag tag; - uint32_t data; // ZirInstRef: ty_inst for TY/COERCED_TY, alloc inst for - // PTR/INFERRED_PTR. - uint32_t src_node; // Only used for RL_PTR. -} ResultLoc; - -#define RL_NONE_VAL ((ResultLoc) { .tag = RL_NONE, .data = 0, .src_node = 0 }) -#define RL_REF_VAL ((ResultLoc) { .tag = RL_REF, .data = 0, .src_node = 0 }) -#define RL_DISCARD_VAL \ - ((ResultLoc) { .tag = RL_DISCARD, .data = 0, .src_node = 0 }) +// setBreakResultInfo (AstGen.zig:11905-11926): compute break result info +// from parent RL. Converts coerced_ty → ty, discard → discard, else passes +// through. For ptr/inferred_ptr, converts to ty/none respectively. +static ResultLoc breakResultInfo( + GenZir* gz, ResultLoc parent_rl, uint32_t node) { + // First: compute block_ri (AstGen.zig:7639-7646). + ResultLoc block_ri; + switch (parent_rl.tag) { + case RL_PTR: { + uint32_t ptr_ty = addUnNode(gz, ZIR_INST_TYPEOF, parent_rl.data, node); + uint32_t ty = addUnNode(gz, ZIR_INST_ELEM_TYPE, ptr_ty, node); + block_ri = (ResultLoc) { .tag = RL_TY, .data = ty, .src_node = 0 }; + break; + } + case RL_INFERRED_PTR: + block_ri = RL_NONE_VAL; + break; + default: + block_ri = parent_rl; + break; + } + // Then: setBreakResultInfo (AstGen.zig:11910-11925). + switch (block_ri.tag) { + case RL_COERCED_TY: + return ( + ResultLoc) { .tag = RL_TY, .data = block_ri.data, .src_node = 0 }; + case RL_DISCARD: + return RL_DISCARD_VAL; + default: + return block_ri; + } +} // resultType (AstGen.zig:341-351): extract result type from RL. // Returns 0 if no result type available. @@ -1309,15 +1375,18 @@ static void emitDbgStmtForceCurrentIndex( static void emitDbgNode(GenZir* gz, uint32_t node); static void addDbgVar( GenZir* gz, ZirInstTag tag, uint32_t name, uint32_t inst); +static void addEnsureResult( + GenZir* gz, uint32_t maybe_unused_result, uint32_t statement); static void blockExprStmts( GenZir* gz, Scope* scope, const uint32_t* statements, uint32_t stmt_count); -static uint32_t fullBodyExpr(GenZir* gz, Scope* scope, uint32_t node); +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 uint32_t structDeclInner(AstGenCtx* ag, GenZir* gz, uint32_t node, const uint32_t* members, uint32_t members_len); static uint32_t blockExprExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node); -static uint32_t ifExpr(GenZir* gz, Scope* scope, 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); static uint32_t orelseCatchExpr( GenZir* gz, Scope* scope, uint32_t node, bool is_catch); @@ -1332,6 +1401,10 @@ static uint32_t whileExpr(GenZir* gz, Scope* scope, uint32_t node); static int nodeMayEvalToError(const Ast* tree, uint32_t node); static bool nodeMayAppendToErrorTrace(const Ast* tree, uint32_t node); static void addSaveErrRetIndex(GenZir* gz, uint32_t operand); +static void addRestoreErrRetIndexBlock( + GenZir* gz, uint32_t block_inst, uint32_t operand, uint32_t node); +static void restoreErrRetIndex(GenZir* gz, uint32_t block_inst, ResultLoc rl, + uint32_t node, uint32_t result); static uint32_t identAsString(AstGenCtx* ag, uint32_t token); static uint32_t lastToken(const Ast* tree, uint32_t node); static uint32_t simpleBinOp( @@ -1364,9 +1437,17 @@ static bool endsWithNoReturn(GenZir* gz) { static uint32_t tryResolvePrimitiveIdent(GenZir* gz, uint32_t node); +// SimpleComptimeReason (std.zig:727) — values used in block_comptime payload. +#define COMPTIME_REASON_TYPE 29 +#define COMPTIME_REASON_ARRAY_SENTINEL 30 +#define COMPTIME_REASON_ARRAY_LENGTH 33 +#define COMPTIME_REASON_COMPTIME_KEYWORD 53 +#define COMPTIME_REASON_SWITCH_ITEM 56 + // Mirrors comptimeExpr2 (AstGen.zig:1982). // Evaluates a node in a comptime block_comptime scope. -static uint32_t comptimeExpr(GenZir* gz, Scope* scope, uint32_t node) { +static uint32_t comptimeExpr( + GenZir* gz, Scope* scope, uint32_t node, uint32_t reason) { // Skip wrapping when already in comptime context (AstGen.zig:1990). if (gz->is_comptime) return expr(gz, scope, node); @@ -1430,7 +1511,7 @@ static uint32_t comptimeExpr(GenZir* gz, Scope* scope, uint32_t node) { uint32_t result = expr(&block_scope, scope, node); addBreak(&block_scope, ZIR_INST_BREAK_INLINE, block_inst, result, AST_NODE_OFFSET_NONE); - setBlockBody(ag, &block_scope, block_inst); + setBlockComptimeBody(ag, &block_scope, block_inst, reason); gzAppendInstruction(gz, block_inst); return block_inst + ZIR_REF_START_INDEX; } @@ -1438,7 +1519,7 @@ static uint32_t comptimeExpr(GenZir* gz, Scope* scope, uint32_t node) { // Mirrors typeExpr (AstGen.zig:1966). // Evaluates a type expression in comptime context. static uint32_t typeExpr(GenZir* gz, Scope* scope, uint32_t node) { - return comptimeExpr(gz, scope, node); + return comptimeExpr(gz, scope, node, COMPTIME_REASON_TYPE); } // Mirrors numberLiteral (AstGen.zig:8544). @@ -1549,7 +1630,7 @@ 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, body_node); + 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); @@ -1638,10 +1719,12 @@ static uint32_t builtinCall(GenZir* gz, Scope* scope, uint32_t node) { uint32_t operand = expr(gz, scope, nd.lhs); return addUnNode(gz, ZIR_INST_INT_FROM_ENUM, operand, node); } - // @tagName (AstGen.zig:9740). + // @tagName (AstGen.zig:9407) — simpleUnOp with dbg_stmt. if (name_len == 7 && memcmp(source + name_start, "tagName", 7) == 0) { + advanceSourceCursorToMainToken(ag, node); AstData nd = tree->nodes.datas[node]; uint32_t operand = expr(gz, scope, nd.lhs); + emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column); return addUnNode(gz, ZIR_INST_TAG_NAME, operand, node); } // @as (AstGen.zig:9388). @@ -1974,7 +2057,7 @@ static uint32_t arrayTypeExpr(GenZir* gz, Scope* scope, uint32_t node) { SET_ERROR(ag); return ZIR_REF_VOID_VALUE; } - uint32_t len = comptimeExpr(gz, scope, nd.lhs); + uint32_t len = comptimeExpr(gz, scope, nd.lhs, COMPTIME_REASON_TYPE); uint32_t elem_type = typeExpr(gz, scope, nd.rhs); return addPlNodeBin(gz, ZIR_INST_ARRAY_TYPE, node, len, elem_type); } @@ -2206,7 +2289,8 @@ typedef struct { uint32_t direct; // for direct calls: ref to callee } Callee; -static Callee calleeExpr(GenZir* gz, Scope* scope, uint32_t fn_expr_node) { +static Callee calleeExpr( + GenZir* gz, Scope* scope, ResultLoc rl, uint32_t fn_expr_node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[fn_expr_node]; @@ -2236,6 +2320,24 @@ static Callee calleeExpr(GenZir* gz, Scope* scope, uint32_t fn_expr_node) { return c; } + // enum_literal callee: decl literal call syntax (AstGen.zig:10217-10233). + if (tag == AST_NODE_ENUM_LITERAL) { + uint32_t res_ty = rlResultType(gz, rl, fn_expr_node); + if (res_ty != 0) { + uint32_t str_index + = identAsString(ag, tree->nodes.main_tokens[fn_expr_node]); + uint32_t callee = addPlNodeBin(gz, ZIR_INST_DECL_LITERAL_NO_COERCE, + fn_expr_node, res_ty, str_index); + Callee c; + c.is_field = false; + c.direct = callee; + c.obj_ptr = 0; + c.field_name_start = 0; + return c; + } + // No result type: fall through to expr with rl=none. + } + // Default: direct call (AstGen.zig:10235). Callee c; c.is_field = false; @@ -2246,7 +2348,8 @@ static Callee calleeExpr(GenZir* gz, Scope* scope, uint32_t fn_expr_node) { } // --- callExpr (AstGen.zig:10058) --- -static uint32_t callExpr(GenZir* gz, Scope* scope, uint32_t node) { +static uint32_t callExpr( + GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; AstNodeTag tag = tree->nodes.tags[node]; @@ -2287,7 +2390,7 @@ static uint32_t callExpr(GenZir* gz, Scope* scope, uint32_t node) { return ZIR_REF_VOID_VALUE; } - Callee callee = calleeExpr(gz, scope, fn_expr_node); + Callee callee = calleeExpr(gz, scope, rl, fn_expr_node); // dbg_stmt before call (AstGen.zig:10078-10083). { @@ -2502,7 +2605,8 @@ static uint32_t structInitExpr( = tree->extra_data.arr[type_nd.rhs + 1]; uint32_t elem_type = exprRl(gz, scope, RL_NONE_VAL, elem_type_node); - uint32_t sentinel = comptimeExpr(gz, scope, sentinel_node); + uint32_t sentinel = comptimeExpr( + gz, scope, sentinel_node, COMPTIME_REASON_ARRAY_SENTINEL); uint32_t array_type_inst = addPlNodeTriple(gz, ZIR_INST_ARRAY_TYPE_SENTINEL, type_expr_node, ZIR_REF_ZERO_USIZE, elem_type, sentinel); @@ -2730,6 +2834,10 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { if (res_ty != 0) { uint32_t res = addPlNodeBin( gz, ZIR_INST_DECL_LITERAL, node, res_ty, str_index); + // decl_literal does the coercion for us (AstGen.zig:1001). + // Only need rvalue for ptr/inferred_ptr/ref_coerced_ty. + if (rl.tag == RL_TY || rl.tag == RL_COERCED_TY) + return res; return rvalue(gz, rl, res, node); } return rvalue(gz, rl, @@ -2747,7 +2855,7 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { case AST_NODE_CALL_ONE_COMMA: case AST_NODE_CALL: case AST_NODE_CALL_COMMA: - return rvalue(gz, rl, callExpr(gz, scope, node), node); + return rvalue(gz, rl, callExpr(gz, scope, rl, node), node); // struct_init (AstGen.zig:836-839). case AST_NODE_STRUCT_INIT_DOT_TWO: case AST_NODE_STRUCT_INIT_DOT_TWO_COMMA: @@ -2968,7 +3076,7 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { // if (AstGen.zig:1013-1024). case AST_NODE_IF_SIMPLE: case AST_NODE_IF: - return rvalue(gz, rl, ifExpr(gz, scope, node), node); + return ifExpr(gz, scope, rlBr(rl), node); // for (AstGen.zig:1043-1060). case AST_NODE_FOR_SIMPLE: case AST_NODE_FOR: @@ -2984,68 +3092,87 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { case AST_NODE_SUB_WRAP: return rvalue( gz, rl, simpleBinOp(gz, scope, node, ZIR_INST_SUBWRAP), node); - // break (AstGen.zig:2358). + // break (AstGen.zig:2150-2237). case AST_NODE_BREAK: { - // break :label value - // lhs = OptionalTokenIndex to label (UINT32_MAX if none), - // rhs = node index for value (0 if none) - uint32_t value_node = nd.rhs; - uint32_t value_ref = ZIR_REF_VOID_VALUE; - if (value_node != 0) - value_ref = expr(gz, scope, value_node); + uint32_t opt_break_label = nd.lhs; // UINT32_MAX = none + uint32_t opt_rhs = nd.rhs; // 0 = none - // Find target block via scope chain (AstGen.zig:2359-2460). - uint32_t label_tok = nd.lhs; - if (label_tok != UINT32_MAX) { - // Labeled break: walk scope chain for ScopeLabel. - uint32_t label_name = identAsString(ag, label_tok); - for (Scope* s = scope; s != NULL;) { - if (s->tag == SCOPE_LABEL) { - ScopeLabel* sl = (ScopeLabel*)s; - if (sl->label_name == label_name) { - addBreak(gz, ZIR_INST_BREAK, sl->block_inst, value_ref, - (int32_t)node - (int32_t)gz->decl_node_index); - return ZIR_REF_UNREACHABLE_VALUE; + // Walk scope chain to find target block (AstGen.zig:2157-2187). + for (Scope* s = scope; s != NULL;) { + if (s->tag == SCOPE_GEN_ZIR) { + GenZir* block_gz = (GenZir*)s; + uint32_t block_inst = UINT32_MAX; + if (opt_break_label != UINT32_MAX) { + // Labeled break: check label on GenZir. + if (block_gz->label_token != UINT32_MAX) { + uint32_t break_name + = identAsString(ag, opt_break_label); + uint32_t label_name + = identAsString(ag, block_gz->label_token); + if (break_name == label_name) + block_inst = block_gz->label_block_inst; } - s = sl->parent; - } else if (s->tag == SCOPE_GEN_ZIR) { - s = ((GenZir*)s)->parent; - } else if (s->tag == SCOPE_LOCAL_VAL) { - s = ((ScopeLocalVal*)s)->parent; - } else if (s->tag == SCOPE_LOCAL_PTR) { - s = ((ScopeLocalPtr*)s)->parent; - } else if (s->tag == SCOPE_DEFER_NORMAL - || s->tag == SCOPE_DEFER_ERROR) { - s = ((ScopeDefer*)s)->parent; } else { - break; + // Unlabeled break: check break_block. + if (block_gz->break_block != UINT32_MAX) + block_inst = block_gz->break_block; } - } - } else { - // Unlabeled break: find innermost GenZir with break_block - // (AstGen.zig:2435-2460). - for (Scope* s = scope; s != NULL;) { - if (s->tag == SCOPE_GEN_ZIR) { - GenZir* gz2 = (GenZir*)s; - if (gz2->break_block != UINT32_MAX) { - addBreak(gz, ZIR_INST_BREAK, gz2->break_block, - value_ref, - (int32_t)node - (int32_t)gz->decl_node_index); - return ZIR_REF_UNREACHABLE_VALUE; + if (block_inst != UINT32_MAX) { + // Found target (AstGen.zig:2188-2228). + ZirInstTag break_tag = block_gz->is_comptime + ? ZIR_INST_BREAK_INLINE + : ZIR_INST_BREAK; + if (opt_rhs == 0) { + // Void break (AstGen.zig:2195-2206). + rvalue(gz, block_gz->break_result_info, + ZIR_REF_VOID_VALUE, node); + if (!block_gz->is_comptime) { + ZirInstData rdata; + rdata.un_node.operand + = block_inst + ZIR_REF_START_INDEX; + rdata.un_node.src_node = (int32_t)node + - (int32_t)gz->decl_node_index; + addInstruction(gz, + ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, + rdata); + } + addBreak(gz, break_tag, block_inst, + ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); + } else { + // Value break (AstGen.zig:2208-2228). + uint32_t operand = exprRl(gz, scope, + block_gz->break_result_info, opt_rhs); + if (!block_gz->is_comptime) + restoreErrRetIndex(gz, block_inst, + block_gz->break_result_info, opt_rhs, + operand); + switch (block_gz->break_result_info.tag) { + case RL_PTR: + case RL_DISCARD: + addBreak(gz, break_tag, block_inst, + ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); + break; + default: + addBreak(gz, break_tag, block_inst, operand, + (int32_t)opt_rhs + - (int32_t)gz->decl_node_index); + break; + } } - s = gz2->parent; - } else if (s->tag == SCOPE_LOCAL_VAL) { - s = ((ScopeLocalVal*)s)->parent; - } else if (s->tag == SCOPE_LOCAL_PTR) { - s = ((ScopeLocalPtr*)s)->parent; - } else if (s->tag == SCOPE_DEFER_NORMAL - || s->tag == SCOPE_DEFER_ERROR) { - s = ((ScopeDefer*)s)->parent; - } else if (s->tag == SCOPE_LABEL) { - s = ((ScopeLabel*)s)->parent; - } else { - break; + return ZIR_REF_UNREACHABLE_VALUE; } + s = block_gz->parent; + } else if (s->tag == SCOPE_LOCAL_VAL) { + s = ((ScopeLocalVal*)s)->parent; + } else if (s->tag == SCOPE_LOCAL_PTR) { + s = ((ScopeLocalPtr*)s)->parent; + } else if (s->tag == SCOPE_DEFER_NORMAL + || s->tag == SCOPE_DEFER_ERROR) { + s = ((ScopeDefer*)s)->parent; + } else if (s->tag == SCOPE_LABEL) { + s = ((ScopeLabel*)s)->parent; + } else { + break; } } SET_ERROR(ag); @@ -3109,7 +3236,8 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { uint32_t result = exprRl(&block_scope, scope, ty_only_rl, body_node); addBreak(&block_scope, ZIR_INST_BREAK_INLINE, block_inst, result, (int32_t)body_node - (int32_t)gz->decl_node_index); - setBlockBody(ag, &block_scope, block_inst); + setBlockComptimeBody( + ag, &block_scope, block_inst, COMPTIME_REASON_COMPTIME_KEYWORD); gzAppendInstruction(gz, block_inst); // Apply rvalue to handle RL_PTR etc (AstGen.zig:2098). @@ -3118,7 +3246,7 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { // switch (AstGen.zig:1072-1078). case AST_NODE_SWITCH: case AST_NODE_SWITCH_COMMA: - return switchExpr(gz, scope, rl, node); + return switchExpr(gz, scope, rlBr(rl), node); // while (AstGen.zig:1037-1042). case AST_NODE_WHILE_SIMPLE: case AST_NODE_WHILE_CONT: @@ -3278,43 +3406,59 @@ static uint32_t blockExprExpr( } // Labeled block (AstGen.zig:2466-2536). - // Create block instruction. - uint32_t block_inst = makeBlockInst(ag, ZIR_INST_BLOCK, gz, node); + bool force_comptime = gz->is_comptime; + uint32_t label_token = lbrace - 2; + + // Compute break result info (AstGen.zig:2484-2492). + ResultLoc break_ri = breakResultInfo(gz, rl, node); + bool need_result_rvalue = (break_ri.tag != rl.tag); + + // Reserve the block instruction (AstGen.zig:2500-2501). + ZirInstTag block_tag + = force_comptime ? ZIR_INST_BLOCK_COMPTIME : ZIR_INST_BLOCK; + uint32_t block_inst = makeBlockInst(ag, block_tag, gz, node); + gzAppendInstruction(gz, block_inst); GenZir block_scope = makeSubBlock(gz, scope); + if (force_comptime) + block_scope.is_comptime = true; + // Set label on block_scope (AstGen.zig:2504-2508). + block_scope.label_token = label_token; + block_scope.label_block_inst = block_inst; + block_scope.break_result_info = break_ri; - // Create label scope so break :label can find the block_inst. - // These fields are read by breakExpr via scope chain walk. - uint32_t label_token = lbrace - 2; - ScopeLabel label_scope; - label_scope.base.tag = SCOPE_LABEL; - // cppcheck-suppress unreadVariable - label_scope.parent = &block_scope.base; - // cppcheck-suppress unreadVariable - label_scope.label_name = identAsString(ag, label_token); - // cppcheck-suppress unreadVariable - label_scope.block_inst = block_inst; + // Process statements (AstGen.zig:2512). + blockExprStmts(&block_scope, &block_scope.base, statements, stmt_count); - // Process statements with label scope. - blockExprStmts(&block_scope, &label_scope.base, statements, stmt_count); - - // If we reach here without a break, the block evaluates to void. - uint32_t gz_len = gzInstructionsLen(&block_scope); - bool has_noreturn = false; - if (gz_len > 0) { - uint32_t last_inst = gzInstructionsSlice(&block_scope)[gz_len - 1]; - if (ag->inst_tags[last_inst] == ZIR_INST_BREAK - || ag->inst_tags[last_inst] == ZIR_INST_BREAK_INLINE) { - has_noreturn = true; + if (!endsWithNoReturn(&block_scope)) { + // Emit restore_err_ret_index (AstGen.zig:2515). + if (!force_comptime) { + ZirInstData rdata; + rdata.un_node.operand = block_inst + ZIR_REF_START_INDEX; + rdata.un_node.src_node + = (int32_t)node - (int32_t)gz->decl_node_index; + addInstruction( + gz, ZIR_INST_RESTORE_ERR_RET_INDEX_UNCONDITIONAL, rdata); } - } - if (!has_noreturn) { - addBreak(&block_scope, ZIR_INST_BREAK, block_inst, ZIR_REF_VOID_VALUE, + // rvalue + break (AstGen.zig:2516-2518). + uint32_t result = rvalue( + gz, block_scope.break_result_info, ZIR_REF_VOID_VALUE, node); + ZirInstTag break_tag + = force_comptime ? ZIR_INST_BREAK_INLINE : ZIR_INST_BREAK; + addBreak(&block_scope, break_tag, block_inst, result, AST_NODE_OFFSET_NONE); } - setBlockBody(ag, &block_scope, block_inst); - gzAppendInstruction(gz, block_inst); + if (force_comptime) { + setBlockComptimeBody( + ag, &block_scope, block_inst, COMPTIME_REASON_COMPTIME_KEYWORD); + } else { + setBlockBody(ag, &block_scope, block_inst); + } + + // AstGen.zig:2531-2534. + if (need_result_rvalue) + return rvalue(gz, rl, block_inst + ZIR_REF_START_INDEX, node); return block_inst + ZIR_REF_START_INDEX; } @@ -3471,9 +3615,10 @@ static uint32_t arrayInitDotExpr( // Handles if and if_simple expressions. // Pattern: block_scope with condbr → then/else branches → setCondBrPayload. -static uint32_t ifExpr(GenZir* gz, Scope* scope, uint32_t node) { +static uint32_t ifExpr(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; + ResultLoc break_rl = breakResultInfo(gz, rl, node); AstNodeTag tag = tree->nodes.tags[node]; AstData nd = tree->nodes.datas[node]; @@ -3576,7 +3721,7 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, uint32_t node) { // Use fullBodyExpr for then body (AstGen.zig:6437). uint32_t then_result - = fullBodyExpr(&then_scope, then_sub_scope, then_node); + = fullBodyExpr(&then_scope, then_sub_scope, break_rl, then_node); if (!endsWithNoReturn(&then_scope)) { addBreak(&then_scope, ZIR_INST_BREAK, block_inst, then_result, (int32_t)then_node - (int32_t)gz->decl_node_index); @@ -3614,8 +3759,12 @@ static uint32_t ifExpr(GenZir* gz, Scope* scope, uint32_t node) { // Use fullBodyExpr for else body (AstGen.zig:6478). uint32_t else_result - = fullBodyExpr(&else_scope, else_sub_scope, else_node); + = fullBodyExpr(&else_scope, else_sub_scope, break_rl, else_node); if (!endsWithNoReturn(&else_scope)) { + // Restore error return index (AstGen.zig:6480-6482). + if (do_err_trace) + restoreErrRetIndex( + &else_scope, block_inst, break_rl, else_node, else_result); addBreak(&else_scope, ZIR_INST_BREAK, block_inst, else_result, (int32_t)else_node - (int32_t)gz->decl_node_index); } @@ -3842,8 +3991,22 @@ static uint32_t forExpr(GenZir* gz, Scope* scope, uint32_t node) { } } - // Execute body (AstGen.zig:7047). - fullBodyExpr(&then_scope, body_scope_parent, body_node); + // Execute body (AstGen.zig:7047-7048). + uint32_t then_result + = fullBodyExpr(&then_scope, body_scope_parent, RL_NONE_VAL, body_node); + addEnsureResult(&then_scope, then_result, body_node); + + // dbg_stmt + dbg_empty_stmt (AstGen.zig:7052-7061). + advanceSourceCursor(ag, tree->tokens.starts[lastToken(tree, body_node)]); + emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column); + { + ZirInstData ext_data; + ext_data.extended.opcode = (uint16_t)ZIR_EXT_DBG_EMPTY_STMT; + ext_data.extended.small = 0; + ext_data.extended.operand = 0; + addInstruction(gz, ZIR_INST_EXTENDED, ext_data); + } + addBreak(&then_scope, ZIR_INST_BREAK, cond_block, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); @@ -3990,16 +4153,30 @@ static uint32_t whileExpr(GenZir* gz, Scope* scope, uint32_t node) { // Execute body (AstGen.zig:6727-6730). emitDbgNode(&continue_scope, body_node); - fullBodyExpr(&continue_scope, &continue_scope.base, body_node); + fullBodyExpr( + &continue_scope, &continue_scope.base, RL_NONE_VAL, body_node); - // Break continue_block if not noreturn (AstGen.zig:6733-6744). + // Break continue_block if not noreturn (AstGen.zig:6735-6747). if (!endsWithNoReturn(&continue_scope)) { + // dbg_stmt + dbg_empty_stmt (AstGen.zig:6737-6745). + advanceSourceCursor( + ag, tree->tokens.starts[lastToken(tree, body_node)]); + fprintf(stderr, "DBG: forExpr dbg_empty_stmt, is_comptime=%d\n", + gz->is_comptime); + emitDbgStmt(gz, ag->source_line - gz->decl_line, ag->source_column); + { + ZirInstData ext_data; + ext_data.extended.opcode = (uint16_t)ZIR_EXT_DBG_EMPTY_STMT; + ext_data.extended.small = 0; + ext_data.extended.operand = 0; + addInstruction(gz, ZIR_INST_EXTENDED, ext_data); + } addBreak(&continue_scope, ZIR_INST_BREAK, continue_block, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); } setBlockBody(ag, &continue_scope, continue_block); - // Break cond_block from then_scope (AstGen.zig:6746). + // Break cond_block from then_scope (AstGen.zig:7064). addBreak(&then_scope, ZIR_INST_BREAK, cond_block, ZIR_REF_VOID_VALUE, AST_NODE_OFFSET_NONE); @@ -4022,6 +4199,7 @@ static uint32_t switchExpr( GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { AstGenCtx* ag = gz->astgen; const Ast* tree = ag->tree; + ResultLoc break_rl = breakResultInfo(gz, rl, node); AstData nd = tree->nodes.datas[node]; // AST_NODE_SWITCH: lhs = condition node, rhs = extra index for SubRange. @@ -4125,12 +4303,12 @@ static uint32_t switchExpr( pay[pay_len++] = 1; prong_info_slot = pay_len++; AstData rng = tree->nodes.datas[cd.lhs]; - pay[pay_len++] = comptimeExpr(gz, scope, rng.lhs); - pay[pay_len++] = comptimeExpr(gz, scope, rng.rhs); + pay[pay_len++] = comptimeExpr(gz, scope, rng.lhs, COMPTIME_REASON_SWITCH_ITEM); + pay[pay_len++] = comptimeExpr(gz, scope, 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, cd.lhs); + pay[pay_len++] = comptimeExpr(gz, scope, cd.lhs, COMPTIME_REASON_SWITCH_ITEM); prong_info_slot = pay_len++; } break; @@ -4162,7 +4340,7 @@ static uint32_t switchExpr( abort(); pay = p; } - pay[pay_len++] = comptimeExpr(gz, scope, item); + pay[pay_len++] = comptimeExpr(gz, scope, item, COMPTIME_REASON_SWITCH_ITEM); } } // Range pairs. @@ -4177,8 +4355,8 @@ static uint32_t switchExpr( abort(); pay = p; } - pay[pay_len++] = comptimeExpr(gz, scope, rng.lhs); - pay[pay_len++] = comptimeExpr(gz, scope, rng.rhs); + pay[pay_len++] = comptimeExpr(gz, scope, rng.lhs, COMPTIME_REASON_SWITCH_ITEM); + pay[pay_len++] = comptimeExpr(gz, scope, rng.rhs, COMPTIME_REASON_SWITCH_ITEM); } } break; @@ -4195,7 +4373,9 @@ static uint32_t switchExpr( if (ag->fn_ret_ty != 0 && nodeMayAppendToErrorTrace(tree, cond_node)) addSaveErrRetIndex(&case_scope, ZIR_REF_NONE); - uint32_t result = exprRl(&case_scope, &case_scope.base, rl, body_node); + // Use fullBodyExpr to process body inline (AstGen.zig:8009). + uint32_t result + = fullBodyExpr(&case_scope, &case_scope.base, break_rl, body_node); if (!endsWithNoReturn(&case_scope)) { addBreak(&case_scope, ZIR_INST_BREAK, switch_inst, result, (int32_t)body_node - (int32_t)gz->decl_node_index); @@ -4551,6 +4731,52 @@ static void addSaveErrRetIndex(GenZir* gz, uint32_t operand) { addInstruction(gz, ZIR_INST_SAVE_ERR_RET_INDEX, data); } +// --- addRestoreErrRetIndexBlock (AstGen.zig:12607-12614) --- +// Emits extended RESTORE_ERR_RET_INDEX with block target (if_non_error +// condition). Payload: src_node, block_ref, operand. +static void addRestoreErrRetIndexBlock( + GenZir* gz, uint32_t block_inst, uint32_t operand, uint32_t node) { + AstGenCtx* ag = gz->astgen; + ensureExtraCapacity(ag, 3); + uint32_t payload_index = ag->extra_len; + ag->extra[ag->extra_len++] + = (uint32_t)((int32_t)node - (int32_t)gz->decl_node_index); + ag->extra[ag->extra_len++] = block_inst + ZIR_REF_START_INDEX; + ag->extra[ag->extra_len++] = operand; + + ZirInstData ext_data; + ext_data.extended.opcode = (uint16_t)ZIR_EXT_RESTORE_ERR_RET_INDEX; + ext_data.extended.small = 0; + ext_data.extended.operand = payload_index; + addInstruction(gz, ZIR_INST_EXTENDED, ext_data); +} + +// --- restoreErrRetIndex (AstGen.zig:2121-2148) --- +// Emits restore_err_ret_index for block target based on nodeMayEvalToError. +static void restoreErrRetIndex(GenZir* gz, uint32_t block_inst, ResultLoc rl, + uint32_t node, uint32_t result) { + const Ast* tree = gz->astgen->tree; + int eval = nodeMayEvalToError(tree, node); + if (eval == EVAL_TO_ERROR_ALWAYS) + return; // never restore/pop + uint32_t op; + if (eval == EVAL_TO_ERROR_NEVER) { + op = ZIR_REF_NONE; // always restore/pop + } else { + // EVAL_TO_ERROR_MAYBE + // Simplified: without ri.ctx, treat non-ptr RL as result + // (AstGen.zig:2131-2144). + if (rl.tag == RL_PTR) { + op = addUnNode(gz, ZIR_INST_LOAD, rl.data, node); + } else if (rl.tag == RL_INFERRED_PTR) { + op = ZIR_REF_NONE; + } else { + op = result; + } + } + addRestoreErrRetIndexBlock(gz, block_inst, op, node); +} + // --- varDecl (AstGen.zig:3189) --- // Handles local const/var declarations. Returns new scope with the variable. // scope_out: set to new scope if variable is added; unchanged otherwise. @@ -5030,7 +5256,8 @@ static void blockExprStmts(GenZir* gz, Scope* scope, // statements inline without creating a BLOCK instruction (unlike blockExprExpr // which wraps in BLOCK). Returns the result ref. -static uint32_t fullBodyExpr(GenZir* gz, Scope* scope, uint32_t node) { +static uint32_t fullBodyExpr( + GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) { const Ast* tree = gz->astgen->tree; AstNodeTag tag = tree->nodes.tags[node]; @@ -5062,7 +5289,7 @@ static uint32_t fullBodyExpr(GenZir* gz, Scope* scope, uint32_t node) { } default: // Not a block — treat as single expression (AstGen.zig:2369). - return expr(gz, scope, node); + return exprRl(gz, scope, rl, node); } // Check if labeled (AstGen.zig:2373-2377). @@ -5072,13 +5299,13 @@ static uint32_t fullBodyExpr(GenZir* gz, Scope* scope, uint32_t node) { && tree->tokens.tags[lbrace - 2] == TOKEN_IDENTIFIER); if (is_labeled) { // Labeled blocks need a proper block instruction. - return blockExprExpr(gz, scope, RL_NONE_VAL, node); + return blockExprExpr(gz, scope, rl, node); } // Unlabeled block: process statements inline (AstGen.zig:2380-2383). GenZir sub_gz = makeSubBlock(gz, scope); blockExprStmts(&sub_gz, &sub_gz.base, statements, stmt_count); - return ZIR_REF_VOID_VALUE; + return rvalue(gz, rl, ZIR_REF_VOID_VALUE, node); } // --- lastToken (Ast.zig:874) --- @@ -5919,7 +6146,7 @@ static void testDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t lbrace_column = ag->source_column; // Process test body (AstGen.zig:4864). - fullBodyExpr(&fn_block, &fn_block.base, body_node); + fullBodyExpr(&fn_block, &fn_block.base, RL_NONE_VAL, body_node); // If we hit unimplemented features, bail out. if (ag->has_compile_errors) @@ -6263,7 +6490,7 @@ static void fnDecl(AstGenCtx* ag, GenZir* gz, uint32_t* wip_decl_insts, uint32_t lbrace_line = ag->source_line - decl_line; uint32_t lbrace_column = ag->source_column; - fullBodyExpr(&body_gz, params_scope, body_node); + fullBodyExpr(&body_gz, params_scope, RL_NONE_VAL, body_node); ag->fn_ret_ty = prev_fn_ret_ty; diff --git a/astgen_test.zig b/astgen_test.zig index cafbc3dec4..9c46299cf4 100644 --- a/astgen_test.zig +++ b/astgen_test.zig @@ -609,7 +609,7 @@ fn expectEqualData( fn zirMatches(gpa: Allocator, ref: Zir, got: c.Zir) bool { const ref_len: u32 = @intCast(ref.instructions.len); if (ref_len != got.inst_len) { - //std.debug.print(" inst_len: ref={d} got={d}\n", .{ ref_len, got.inst_len }); + std.debug.print(" inst_len: ref={d} got={d}\n", .{ ref_len, got.inst_len }); } const ref_tags = ref.instructions.items(.tag); @@ -624,21 +624,26 @@ fn zirMatches(gpa: Allocator, ref: Zir, got: c.Zir) bool { break; } } - if (first_tag_mismatch) |_| { - //const start = if (ftm > 5) ftm - 5 else 0; - //const end = @min(ftm + 10, min_len); - //std.debug.print(" first tag mismatch at inst[{d}]:\n", .{ftm}); - //for (start..end) |i| { - // const ref_tag: u8 = @intFromEnum(ref_tags[i]); - // const got_tag: u8 = @intCast(got.inst_tags[i]); - // const marker: u8 = if (i == ftm) '>' else ' '; - // std.debug.print(" {c} [{d}] ref_tag={d} got_tag={d}\n", .{ marker, i, ref_tag, got_tag }); - //} + if (first_tag_mismatch) |ftm| { + const start = if (ftm > 5) ftm - 5 else 0; + const end = @min(ftm + 10, min_len); + std.debug.print(" first tag mismatch at inst[{d}]:\n", .{ftm}); + for (start..end) |i| { + const ref_tag: u8 = @intFromEnum(ref_tags[i]); + const got_tag: u8 = @intCast(got.inst_tags[i]); + const marker: u8 = if (i == ftm) '>' else ' '; + if (ref_tag == 251) { + const ext_op: u16 = @intFromEnum(ref_datas[i].extended.opcode); + std.debug.print(" {c} [{d}] ref_tag=251(EXT:{d}) got_tag={d}\n", .{ marker, i, ext_op, got_tag }); + } else { + std.debug.print(" {c} [{d}] ref_tag={d} got_tag={d}\n", .{ marker, i, ref_tag, got_tag }); + } + } return false; } for (0..min_len) |i| { if (!dataMatches(ref_tags[i], ref_datas[i], got.inst_datas[i])) { - //std.debug.print(" inst_datas[{d}] mismatch (tag={d})\n", .{ i, @as(u8, @intFromEnum(ref_tags[i])) }); + std.debug.print(" inst_datas[{d}] mismatch (tag={d})\n", .{ i, @as(u8, @intFromEnum(ref_tags[i])) }); return false; } } @@ -646,7 +651,7 @@ fn zirMatches(gpa: Allocator, ref: Zir, got: c.Zir) bool { const ref_extra_len: u32 = @intCast(ref.extra.len); if (ref_extra_len != got.extra_len) { - //std.debug.print(" extra_len: ref={d} got={d}\n", .{ ref_extra_len, got.extra_len }); + std.debug.print(" extra_len: ref={d} got={d}\n", .{ ref_extra_len, got.extra_len }); return false; } @@ -656,19 +661,19 @@ fn zirMatches(gpa: Allocator, ref: Zir, got: c.Zir) bool { for (0..ref_extra_len) |i| { if (skip[i]) continue; if (ref.extra[i] != got.extra[i]) { - //std.debug.print(" extra[{d}]: ref=0x{x:0>8} got=0x{x:0>8}\n", .{ i, ref.extra[i], got.extra[i] }); + std.debug.print(" extra[{d}]: ref=0x{x:0>8} got=0x{x:0>8}\n", .{ i, ref.extra[i], got.extra[i] }); return false; } } const ref_sb_len: u32 = @intCast(ref.string_bytes.len); if (ref_sb_len != got.string_bytes_len) { - //std.debug.print(" string_bytes_len: ref={d} got={d}\n", .{ ref_sb_len, got.string_bytes_len }); + std.debug.print(" string_bytes_len: ref={d} got={d}\n", .{ ref_sb_len, got.string_bytes_len }); return false; } for (0..ref_sb_len) |i| { if (ref.string_bytes[i] != got.string_bytes[i]) { - //std.debug.print(" string_bytes[{d}]: ref=0x{x:0>2} got=0x{x:0>2}\n", .{ i, ref.string_bytes[i], got.string_bytes[i] }); + std.debug.print(" string_bytes[{d}]: ref=0x{x:0>2} got=0x{x:0>2}\n", .{ i, ref.string_bytes[i], got.string_bytes[i] }); return false; } } @@ -778,17 +783,16 @@ fn corpusCheck(gpa: Allocator, name: []const u8, source: [:0]const u8) enum { pa defer c.zirDeinit(&c_zir); if (c_zir.has_compile_errors) { - //std.debug.print(" -> has_compile_errors\n", .{}); + std.debug.print(" {s} -> has_compile_errors\n", .{name}); return .skip; } if (zirMatches(gpa, ref_zir, c_zir)) { return .pass; } else { - //std.debug.print(" -> zir mismatch\n", .{}); + std.debug.print(" {s} -> zir mismatch\n", .{name}); return .skip; } - _ = name; } test "astgen: corpus" {