authorgravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-09-25 19:51:38-07:00
committergravatar for topolarity@tapscott.meCody Tapscott <topolarity@tapscott.me> 2022-10-21 12:40:29-07:00
logd060cbbec75ac7b0204c706e4dfdfb38f1b24dfd
tree3504dbae6e338daddf97eddf892557f05ca1d186
parent597ead5318421befba3619fed389820d241ecc78

stage2: Keep error return traces alive when storing to `const`

This change extends the "lifetime" of the error return trace associated with an error to continue throughout the block of a `const` variable that it is assigned to. This is necessary to support patterns like this one in test_runner.zig: ```zig const result = foo(); if (result) |_| { // ... success logic } else |err| { // `foo()` should be included in the error trace here return error.TestFailed; } ``` To make this happen, the majority of the error return trace popping logic needed to move into Sema, since `const x = foo();` cannot be examined syntactically to determine whether it modifies the error return trace. We also have to make sure not to delete pertinent block information before it makes it to Sema, so that Sema can pop/restore around blocks correctly. * Why do this only for `const` and not `var`? * There is room to relax things for `var`, but only a little bit. We could do the same thing we do for const and keep the error trace alive for the remainder of the block where the *assignment* happens. Any wider scope would violate the stack discipline for traces, so it's not viable. In the end, I decided the most consistent behavior for the user is just to kill all error return traces assigned to a mutable `var`.

9 files changed, 597 insertions(+), 251 deletions(-)

lib/test_runner.zig+17-18
......@@ -44,24 +44,23 @@ pub fn main() void {
4444 if (!have_tty) {
4545 std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name });
4646 }
47 if (result: {
48 if (test_fn.async_frame_size) |size| switch (io_mode) {
49 .evented => {
50 if (async_frame_buffer.len < size) {
51 std.heap.page_allocator.free(async_frame_buffer);
52 async_frame_buffer = std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size) catch @panic("out of memory");
53 }
54 const casted_fn = @ptrCast(fn () callconv(.Async) anyerror!void, test_fn.func);
55 break :result await @asyncCall(async_frame_buffer, {}, casted_fn, .{});
56 },
57 .blocking => {
58 skip_count += 1;
59 test_node.end();
60 progress.log("SKIP (async test)\n", .{});
61 continue;
62 },
63 } else break :result test_fn.func();
64 }) |_| {
47 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
48 .evented => blk: {
49 if (async_frame_buffer.len < size) {
50 std.heap.page_allocator.free(async_frame_buffer);
51 async_frame_buffer = std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size) catch @panic("out of memory");
52 }
53 const casted_fn = @ptrCast(fn () callconv(.Async) anyerror!void, test_fn.func);
54 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn, .{});
55 },
56 .blocking => {
57 skip_count += 1;
58 test_node.end();
59 progress.log("SKIP (async test)\n", .{});
60 continue;
61 },
62 } else test_fn.func();
63 if (result) |_| {
6564 ok_count += 1;
6665 test_node.end();
6766 if (!have_tty) std.debug.print("OK\n", .{});
src/Air.zig+1-1
......@@ -734,7 +734,7 @@ pub const Inst = struct {
734734 addrspace_cast,
735735
736736 /// Saves the error return trace index, if any. Otherwise, returns 0.
737 /// Uses the `ty_op` field.
737 /// Uses the `ty_pl` field.
738738 save_err_return_trace_index,
739739
740740 pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag {
src/AstGen.zig+194-152
......@@ -337,6 +337,8 @@ pub const ResultInfo = struct {
337337 shift_op,
338338 /// The expression is an argument in a function call.
339339 fn_arg,
340 /// The expression is the right-hand side of an initializer for a `const` variable
341 const_init,
340342 /// No specific operator in particular.
341343 none,
342344 };
......@@ -1850,6 +1852,45 @@ fn comptimeExprAst(
18501852 return result;
18511853}
18521854
1855/// Restore the error return trace index. Performs the restore only if the result is a non-error or
1856/// if the result location is a non-error-handling expression.
1857fn restoreErrRetIndex(
1858 gz: *GenZir,
1859 bt: GenZir.BranchTarget,
1860 ri: ResultInfo,
1861 node: Ast.Node.Index,
1862 result: Zir.Inst.Ref,
1863) !void {
1864 const op = switch (nodeMayEvalToError(gz.astgen.tree, node)) {
1865 .always => return, // never restore/pop
1866 .never => .none, // always restore/pop
1867 .maybe => switch (ri.ctx) {
1868 .error_handling_expr, .@"return", .fn_arg, .const_init => switch (ri.rl) {
1869 .ptr => |ptr_res| try gz.addUnNode(.load, ptr_res.inst, node),
1870 .inferred_ptr => |ptr| try gz.addUnNode(.load, ptr, node),
1871 .block_ptr => |block_scope| if (block_scope.rvalue_rl_count != block_scope.break_count) b: {
1872 // The result location may have been used by this expression, in which case
1873 // the operand is not the result and we need to load the rl ptr.
1874 switch (gz.astgen.instructions.items(.tag)[Zir.refToIndex(block_scope.rl_ptr).?]) {
1875 .alloc_inferred, .alloc_inferred_mut => {
1876 // This is a terrible workaround for Sema's inability to load from a .alloc_inferred ptr
1877 // before its type has been resolved. The operand we use here instead is not guaranteed
1878 // to be valid, and when it's not, we will pop error traces prematurely.
1879 //
1880 // TODO: Update this to do a proper load from the rl_ptr, once Sema can support it.
1881 break :b result;
1882 },
1883 else => break :b try gz.addUnNode(.load, block_scope.rl_ptr, node),
1884 }
1885 } else result,
1886 else => result,
1887 },
1888 else => .none, // always restore/pop
1889 },
1890 };
1891 _ = try gz.addRestoreErrRetIndex(bt, .{ .if_non_error = op });
1892}
1893
18531894fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
18541895 const astgen = parent_gz.astgen;
18551896 const tree = astgen.tree;
......@@ -1857,13 +1898,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
18571898 const break_label = node_datas[node].lhs;
18581899 const rhs = node_datas[node].rhs;
18591900
1860 // Breaking out of a `catch { ... }` or `else |err| { ... }` block with a non-error value
1861 // means that the corresponding error was correctly handled, and the error trace index
1862 // needs to be restored so that any entries from the caught error are effectively "popped"
1863 //
1864 // Note: We only restore for the outermost block, since that will "pop" any nested blocks.
1865 var err_trace_index_to_restore: Zir.Inst.Ref = .none;
1866
18671901 // Look for the label in the scope.
18681902 var scope = parent_scope;
18691903 while (true) {
......@@ -1882,11 +1916,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
18821916 });
18831917 }
18841918
1885 if (block_gz.saved_err_trace_index != .none) {
1886 // We are breaking out of a `catch { ... }` or `else |err| { ... }`.
1887 err_trace_index_to_restore = block_gz.saved_err_trace_index;
1888 }
1889
18901919 const block_inst = blk: {
18911920 if (break_label != 0) {
18921921 if (block_gz.label) |*label| {
......@@ -1913,10 +1942,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
19131942 try genDefers(parent_gz, scope, parent_scope, .normal_only);
19141943
19151944 // As our last action before the break, "pop" the error trace if needed
1916 if (err_trace_index_to_restore != .none) {
1917 // void is a non-error so we always pop - no need to call `popErrorReturnTrace`
1918 _ = try parent_gz.addUnNode(.restore_err_ret_index, err_trace_index_to_restore, node);
1919 }
1945 if (!block_gz.force_comptime)
1946 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = block_inst }, .always);
19201947
19211948 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
19221949 return Zir.Inst.Ref.unreachable_value;
......@@ -1929,17 +1956,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
19291956 try genDefers(parent_gz, scope, parent_scope, .normal_only);
19301957
19311958 // As our last action before the break, "pop" the error trace if needed
1932 if (err_trace_index_to_restore != .none) {
1933 // Pop the error trace, unless the operand is an error and breaking to an error-handling expr.
1934 try popErrorReturnTrace(
1935 parent_gz,
1936 scope,
1937 block_gz.break_result_info,
1938 rhs,
1939 operand,
1940 err_trace_index_to_restore,
1941 );
1942 }
1959 if (!block_gz.force_comptime)
1960 try restoreErrRetIndex(parent_gz, .{ .block = block_inst }, block_gz.break_result_info, rhs, operand);
19431961
19441962 switch (block_gz.break_result_info.rl) {
19451963 .block_ptr => {
......@@ -2066,8 +2084,34 @@ fn blockExpr(
20662084 return labeledBlockExpr(gz, scope, ri, block_node, statements);
20672085 }
20682086
2069 var sub_gz = gz.makeSubBlock(scope);
2070 try blockExprStmts(&sub_gz, &sub_gz.base, statements);
2087 if (!gz.force_comptime) {
2088 // Since this block is unlabeled, its control flow is effectively linear and we
2089 // can *almost* get away with inlining the block here. However, we actually need
2090 // to preserve the .block for Sema, to properly pop the error return trace.
2091
2092 const block_tag: Zir.Inst.Tag = .block;
2093 const block_inst = try gz.makeBlockInst(block_tag, block_node);
2094 try gz.instructions.append(astgen.gpa, block_inst);
2095
2096 var block_scope = gz.makeSubBlock(scope);
2097 defer block_scope.unstack();
2098
2099 try blockExprStmts(&block_scope, &block_scope.base, statements);
2100
2101 if (!block_scope.endsWithNoReturn()) {
2102 // As our last action before the break, "pop" the error trace if needed
2103 _ = try gz.addRestoreErrRetIndex(.{ .block = block_inst }, .always);
2104
2105 const break_tag: Zir.Inst.Tag = if (block_scope.force_comptime) .break_inline else .@"break";
2106 _ = try block_scope.addBreak(break_tag, block_inst, .void_value);
2107 }
2108
2109 try block_scope.setBlockBody(block_inst);
2110 } else {
2111 var sub_gz = gz.makeSubBlock(scope);
2112 try blockExprStmts(&sub_gz, &sub_gz.base, statements);
2113 }
2114
20712115 return rvalue(gz, ri, .void_value, block_node);
20722116}
20732117
......@@ -2141,6 +2185,9 @@ fn labeledBlockExpr(
21412185
21422186 try blockExprStmts(&block_scope, &block_scope.base, statements);
21432187 if (!block_scope.endsWithNoReturn()) {
2188 // As our last action before the return, "pop" the error trace if needed
2189 _ = try gz.addRestoreErrRetIndex(.{ .block = block_inst }, .always);
2190
21442191 const break_tag: Zir.Inst.Tag = if (block_scope.force_comptime) .break_inline else .@"break";
21452192 _ = try block_scope.addBreak(break_tag, block_inst, .void_value);
21462193 }
......@@ -2164,7 +2211,8 @@ fn labeledBlockExpr(
21642211 return indexToRef(block_inst);
21652212 },
21662213 .break_operand => {
2167 // All break operands are values that did not use the result location pointer.
2214 // All break operands are values that did not use the result location pointer
2215 // (except for a single .store_to_block_ptr inst which we re-write here).
21682216 // The break instructions need to have their operands coerced if the
21692217 // block's result location is a `ty`. In this case we overwrite the
21702218 // `store_to_block_ptr` instruction with an `as` instruction and repurpose
......@@ -2528,7 +2576,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
25282576 .try_ptr,
25292577 //.try_inline,
25302578 //.try_ptr_inline,
2531 .save_err_ret_index,
25322579 => break :b false,
25332580
25342581 .extended => switch (gz.astgen.instructions.items(.data)[inst].extended.opcode) {
......@@ -2591,6 +2638,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
25912638 .validate_array_init_ty,
25922639 .validate_struct_init_ty,
25932640 .validate_deref,
2641 .save_err_ret_index,
25942642 .restore_err_ret_index,
25952643 => break :b true,
25962644
......@@ -2877,7 +2925,8 @@ fn varDecl(
28772925 {
28782926 const result_info: ResultInfo = if (type_node != 0) .{
28792927 .rl = .{ .ty = try typeExpr(gz, scope, type_node) },
2880 } else .{ .rl = .none };
2928 .ctx = .const_init,
2929 } else .{ .rl = .none, .ctx = .const_init };
28812930 const prev_anon_name_strategy = gz.anon_name_strategy;
28822931 gz.anon_name_strategy = .dbg_var;
28832932 const init_inst = try reachableExpr(gz, scope, result_info, var_decl.ast.init_node, node);
......@@ -2885,6 +2934,11 @@ fn varDecl(
28852934
28862935 try gz.addDbgVar(.dbg_var_val, ident_name, init_inst);
28872936
2937 // The const init expression may have modified the error return trace, so signal
2938 // to Sema that it should save the new index for restoring later.
2939 if (nodeMayAppendToErrorTrace(tree, var_decl.ast.init_node))
2940 _ = try gz.addSaveErrRetIndex(.{ .if_of_error_type = init_inst });
2941
28882942 const sub_scope = try block_arena.create(Scope.LocalVal);
28892943 sub_scope.* = .{
28902944 .parent = scope,
......@@ -2950,9 +3004,14 @@ fn varDecl(
29503004 init_scope.rl_ptr = alloc;
29513005 init_scope.rl_ty_inst = .none;
29523006 }
2953 const init_result_info: ResultInfo = .{ .rl = .{ .block_ptr = &init_scope } };
3007 const init_result_info: ResultInfo = .{ .rl = .{ .block_ptr = &init_scope }, .ctx = .const_init };
29543008 const init_inst = try reachableExpr(&init_scope, &init_scope.base, init_result_info, var_decl.ast.init_node, node);
29553009
3010 // The const init expression may have modified the error return trace, so signal
3011 // to Sema that it should save the new index for restoring later.
3012 if (nodeMayAppendToErrorTrace(tree, var_decl.ast.init_node))
3013 _ = try init_scope.addSaveErrRetIndex(.{ .if_of_error_type = init_inst });
3014
29563015 const zir_tags = astgen.instructions.items(.tag);
29573016 const zir_datas = astgen.instructions.items(.data);
29583017
......@@ -3775,6 +3834,9 @@ fn fnDecl(
37753834 try checkUsed(gz, &fn_gz.base, params_scope);
37763835
37773836 if (!fn_gz.endsWithNoReturn()) {
3837 // As our last action before the return, "pop" the error trace if needed
3838 _ = try gz.addRestoreErrRetIndex(.ret, .always);
3839
37783840 // Since we are adding the return instruction here, we must handle the coercion.
37793841 // We do this by using the `ret_tok` instruction.
37803842 _ = try fn_gz.addUnTok(.ret_tok, .void_value, tree.lastToken(body_node));
......@@ -4217,6 +4279,10 @@ fn testDecl(
42174279
42184280 const block_result = try expr(&fn_block, &fn_block.base, .{ .rl = .none }, body_node);
42194281 if (fn_block.isEmpty() or !fn_block.refIsNoReturn(block_result)) {
4282
4283 // As our last action before the return, "pop" the error trace if needed
4284 _ = try gz.addRestoreErrRetIndex(.ret, .always);
4285
42204286 // Since we are adding the return instruction here, we must handle the coercion.
42214287 // We do this by using the `ret_tok` instruction.
42224288 _ = try fn_block.addUnTok(.ret_tok, .void_value, tree.lastToken(body_node));
......@@ -5196,76 +5262,6 @@ fn tryExpr(
51965262 }
51975263}
51985264
5199/// Pops the error return trace, unless:
5200/// 1. the result is a non-error, AND
5201/// 2. the result location corresponds to an error-handling expression
5202///
5203/// For reference, the full list of error-handling expressions is:
5204/// - try X
5205/// - X catch ...
5206/// - if (X) |_| { ... } |_| { ... }
5207/// - return X
5208///
5209fn popErrorReturnTrace(
5210 gz: *GenZir,
5211 scope: *Scope,
5212 ri: ResultInfo,
5213 node: Ast.Node.Index,
5214 result_inst: Zir.Inst.Ref,
5215 error_trace_index: Zir.Inst.Ref,
5216) InnerError!void {
5217 const astgen = gz.astgen;
5218 const tree = astgen.tree;
5219
5220 const result_is_err = nodeMayEvalToError(tree, node);
5221
5222 // If we are breaking to a try/catch/error-union-if/return or a function call, the error trace propagates.
5223 const propagate_error_trace = switch (ri.ctx) {
5224 .error_handling_expr, .@"return", .fn_arg => true,
5225 else => false,
5226 };
5227
5228 if (result_is_err == .never or !propagate_error_trace) {
5229 // We are returning a non-error, or returning to a non-error-handling operator.
5230 // In either case, we need to pop the error trace.
5231 _ = try gz.addUnNode(.restore_err_ret_index, error_trace_index, node);
5232 } else if (result_is_err == .maybe) {
5233 // We are returning to an error-handling operator with a maybe-error.
5234 // Restore only if it's a non-error, implying the catch was successfully handled.
5235 var block_scope = gz.makeSubBlock(scope);
5236 block_scope.setBreakResultInfo(.{ .rl = .discard });
5237 defer block_scope.unstack();
5238
5239 // Emit conditional branch for restoring error trace index
5240 const is_non_err = switch (ri.rl) {
5241 .ref => try block_scope.addUnNode(.is_non_err_ptr, result_inst, node),
5242 .ptr => |ptr| try block_scope.addUnNode(.is_non_err_ptr, ptr.inst, node),
5243 .ty, .none => try block_scope.addUnNode(.is_non_err, result_inst, node),
5244 else => unreachable, // Error-handling operators only generate the above result locations
5245 };
5246 const condbr = try block_scope.addCondBr(.condbr, node);
5247
5248 const block = try gz.makeBlockInst(.block, node);
5249 try block_scope.setBlockBody(block);
5250 // block_scope unstacked now, can add new instructions to gz
5251
5252 try gz.instructions.append(astgen.gpa, block);
5253
5254 var then_scope = block_scope.makeSubBlock(scope);
5255 defer then_scope.unstack();
5256
5257 _ = try then_scope.addUnNode(.restore_err_ret_index, error_trace_index, node);
5258 const then_break = try then_scope.makeBreak(.@"break", block, .void_value);
5259
5260 var else_scope = block_scope.makeSubBlock(scope);
5261 defer else_scope.unstack();
5262
5263 const else_break = try else_scope.makeBreak(.@"break", block, .void_value);
5264
5265 try setCondBrPayload(condbr, is_non_err, &then_scope, then_break, &else_scope, else_break);
5266 }
5267}
5268
52695265fn orelseCatchExpr(
52705266 parent_gz: *GenZir,
52715267 scope: *Scope,
......@@ -5287,8 +5283,6 @@ fn orelseCatchExpr(
52875283 block_scope.setBreakResultInfo(ri);
52885284 defer block_scope.unstack();
52895285
5290 const saved_err_trace_index = if (do_err_trace) try parent_gz.addNode(.save_err_ret_index, node) else .none;
5291
52925286 const operand_ri: ResultInfo = switch (block_scope.break_result_info.rl) {
52935287 .ref => .{ .rl = .ref, .ctx = if (do_err_trace) .error_handling_expr else .none },
52945288 else => .{ .rl = .none, .ctx = if (do_err_trace) .error_handling_expr else .none },
......@@ -5320,11 +5314,10 @@ fn orelseCatchExpr(
53205314 var else_scope = block_scope.makeSubBlock(scope);
53215315 defer else_scope.unstack();
53225316
5323 // Any break (of a non-error value) that navigates out of this scope means
5324 // that the error was handled successfully, so this index will be restored.
5325 else_scope.saved_err_trace_index = saved_err_trace_index;
5326 if (else_scope.outermost_err_trace_index == .none)
5327 else_scope.outermost_err_trace_index = saved_err_trace_index;
5317 // We know that the operand (almost certainly) modified the error return trace,
5318 // so signal to Sema that it should save the new index for restoring later.
5319 if (do_err_trace and nodeMayAppendToErrorTrace(tree, lhs))
5320 _ = try else_scope.addSaveErrRetIndex(.always);
53285321
53295322 var err_val_scope: Scope.LocalVal = undefined;
53305323 const else_sub_scope = blk: {
......@@ -5352,16 +5345,9 @@ fn orelseCatchExpr(
53525345 if (!else_scope.endsWithNoReturn()) {
53535346 block_scope.break_count += 1;
53545347
5355 if (do_err_trace) {
5356 try popErrorReturnTrace(
5357 &else_scope,
5358 else_sub_scope,
5359 block_scope.break_result_info,
5360 rhs,
5361 else_result,
5362 saved_err_trace_index,
5363 );
5364 }
5348 // As our last action before the break, "pop" the error trace if needed
5349 if (do_err_trace)
5350 try restoreErrRetIndex(&else_scope, .{ .block = block }, block_scope.break_result_info, rhs, else_result);
53655351 }
53665352 try checkUsed(parent_gz, &else_scope.base, else_sub_scope);
53675353
......@@ -5587,8 +5573,6 @@ fn ifExpr(
55875573 block_scope.setBreakResultInfo(ri);
55885574 defer block_scope.unstack();
55895575
5590 const saved_err_trace_index = if (do_err_trace) try parent_gz.addNode(.save_err_ret_index, node) else .none;
5591
55925576 const payload_is_ref = if (if_full.payload_token) |payload_token|
55935577 token_tags[payload_token] == .asterisk
55945578 else
......@@ -5705,11 +5689,10 @@ fn ifExpr(
57055689 var else_scope = parent_gz.makeSubBlock(scope);
57065690 defer else_scope.unstack();
57075691
5708 // Any break (of a non-error value) that navigates out of this scope means
5709 // that the error was handled successfully, so this index will be restored.
5710 else_scope.saved_err_trace_index = saved_err_trace_index;
5711 if (else_scope.outermost_err_trace_index == .none)
5712 else_scope.outermost_err_trace_index = saved_err_trace_index;
5692 // We know that the operand (almost certainly) modified the error return trace,
5693 // so signal to Sema that it should save the new index for restoring later.
5694 if (do_err_trace and nodeMayAppendToErrorTrace(tree, if_full.ast.cond_expr))
5695 _ = try else_scope.addSaveErrRetIndex(.always);
57135696
57145697 const else_node = if_full.ast.else_expr;
57155698 const else_info: struct {
......@@ -5747,16 +5730,9 @@ fn ifExpr(
57475730 if (!else_scope.endsWithNoReturn()) {
57485731 block_scope.break_count += 1;
57495732
5750 if (do_err_trace) {
5751 try popErrorReturnTrace(
5752 &else_scope,
5753 sub_scope,
5754 block_scope.break_result_info,
5755 else_node,
5756 e,
5757 saved_err_trace_index,
5758 );
5759 }
5733 // As our last action before the break, "pop" the error trace if needed
5734 if (do_err_trace)
5735 try restoreErrRetIndex(&else_scope, .{ .block = block }, block_scope.break_result_info, else_node, e);
57605736 }
57615737 try checkUsed(parent_gz, &else_scope.base, sub_scope);
57625738 try else_scope.addDbgBlockEnd();
......@@ -6886,6 +6862,10 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
68866862 if (operand_node == 0) {
68876863 // Returning a void value; skip error defers.
68886864 try genDefers(gz, defer_outer, scope, .normal_only);
6865
6866 // As our last action before the return, "pop" the error trace if needed
6867 _ = try gz.addRestoreErrRetIndex(.ret, .always);
6868
68896869 _ = try gz.addUnNode(.ret_node, .void_value, node);
68906870 return Zir.Inst.Ref.unreachable_value;
68916871 }
......@@ -6921,15 +6901,13 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
69216901 const operand = try reachableExpr(gz, scope, ri, operand_node, node);
69226902 gz.anon_name_strategy = prev_anon_name_strategy;
69236903
6924 // TODO: This should be almost identical for every break/ret
69256904 switch (nodeMayEvalToError(tree, operand_node)) {
69266905 .never => {
69276906 // Returning a value that cannot be an error; skip error defers.
69286907 try genDefers(gz, defer_outer, scope, .normal_only);
69296908
69306909 // As our last action before the return, "pop" the error trace if needed
6931 if (gz.outermost_err_trace_index != .none)
6932 _ = try gz.addUnNode(.restore_err_ret_index, gz.outermost_err_trace_index, node);
6910 _ = try gz.addRestoreErrRetIndex(.ret, .always);
69336911
69346912 try emitDbgStmt(gz, ret_line, ret_column);
69356913 try gz.addRet(ri, operand, node);
......@@ -6949,6 +6927,11 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
69496927 // Only regular defers; no branch needed.
69506928 try genDefers(gz, defer_outer, scope, .normal_only);
69516929 try emitDbgStmt(gz, ret_line, ret_column);
6930
6931 // As our last action before the return, "pop" the error trace if needed
6932 const result = if (ri.rl == .ptr) try gz.addUnNode(.load, ri.rl.ptr.inst, node) else operand;
6933 _ = try gz.addRestoreErrRetIndex(.ret, .{ .if_non_error = result });
6934
69526935 try gz.addRet(ri, operand, node);
69536936 return Zir.Inst.Ref.unreachable_value;
69546937 }
......@@ -6964,8 +6947,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
69646947 try genDefers(&then_scope, defer_outer, scope, .normal_only);
69656948
69666949 // As our last action before the return, "pop" the error trace if needed
6967 if (then_scope.outermost_err_trace_index != .none)
6968 _ = try then_scope.addUnNode(.restore_err_ret_index, then_scope.outermost_err_trace_index, node);
6950 _ = try then_scope.addRestoreErrRetIndex(.ret, .always);
69696951
69706952 try emitDbgStmt(&then_scope, ret_line, ret_column);
69716953 try then_scope.addRet(ri, operand, node);
......@@ -8561,10 +8543,11 @@ fn callExpr(
85618543 scratch_index += 1;
85628544 }
85638545
8564 // If our result location is a try/catch/error-union-if/return, the error trace propagates.
8546 // If our result location is a try/catch/error-union-if/return, a function argument,
8547 // or an initializer for a `const` variable, the error trace propagates.
85658548 // Otherwise, it should always be popped (handled in Sema).
85668549 const propagate_error_trace = switch (ri.ctx) {
8567 .error_handling_expr, .@"return", .fn_arg => true, // Propagate to try/catch/error-union-if, return, and other function calls
8550 .error_handling_expr, .@"return", .fn_arg, .const_init => true,
85688551 else => false,
85698552 };
85708553
......@@ -8932,6 +8915,33 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_
89328915 }
89338916}
89348917
8918fn nodeMayAppendToErrorTrace(tree: *const Ast, start_node: Ast.Node.Index) bool {
8919 const node_tags = tree.nodes.items(.tag);
8920 const node_datas = tree.nodes.items(.data);
8921
8922 var node = start_node;
8923 while (true) {
8924 switch (node_tags[node]) {
8925 // These don't have the opportunity to call any runtime functions.
8926 .error_value,
8927 .identifier,
8928 .@"comptime",
8929 => return false,
8930
8931 // Forward the question to the LHS sub-expression.
8932 .grouped_expression,
8933 .@"try",
8934 .@"nosuspend",
8935 .unwrap_optional,
8936 => node = node_datas[node].lhs,
8937
8938 // Anything that does not eval to an error is guaranteed to pop any
8939 // additions to the error trace, so it effectively does not append.
8940 else => return nodeMayEvalToError(tree, start_node) != .never,
8941 }
8942 }
8943}
8944
89358945fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.EvalToError {
89368946 const node_tags = tree.nodes.items(.tag);
89378947 const node_datas = tree.nodes.items(.data);
......@@ -10494,13 +10504,6 @@ const GenZir = struct {
1049410504 /// Keys are the raw instruction index, values are the closure_capture instruction.
1049510505 captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{},
1049610506
10497 /// If this GenZir corresponds to a `catch { ... }` or `else |err| { ... }` block,
10498 /// this err_trace_index can be restored to "pop" the trace entries for the block.
10499 saved_err_trace_index: Zir.Inst.Ref = .none,
10500 /// When returning from a function with a non-error, we must pop all trace entries
10501 /// from any containing `catch { ... }` or `else |err| { ... }` blocks.
10502 outermost_err_trace_index: Zir.Inst.Ref = .none,
10503
1050410507 const unstacked_top = std.math.maxInt(usize);
1050510508 /// Call unstack before adding any new instructions to containing GenZir.
1050610509 fn unstack(self: *GenZir) void {
......@@ -10545,7 +10548,6 @@ const GenZir = struct {
1054510548 .any_defer_node = gz.any_defer_node,
1054610549 .instructions = gz.instructions,
1054710550 .instructions_top = gz.instructions.items.len,
10548 .outermost_err_trace_index = gz.outermost_err_trace_index,
1054910551 };
1055010552 }
1055110553
......@@ -11359,6 +11361,46 @@ const GenZir = struct {
1135911361 });
1136011362 }
1136111363
11364 fn addSaveErrRetIndex(
11365 gz: *GenZir,
11366 cond: union(enum) {
11367 always: void,
11368 if_of_error_type: Zir.Inst.Ref,
11369 },
11370 ) !Zir.Inst.Index {
11371 return gz.addAsIndex(.{
11372 .tag = .save_err_ret_index,
11373 .data = .{ .save_err_ret_index = .{
11374 .operand = if (cond == .if_of_error_type) cond.if_of_error_type else .none,
11375 } },
11376 });
11377 }
11378
11379 const BranchTarget = union(enum) {
11380 ret,
11381 block: Zir.Inst.Index,
11382 };
11383
11384 fn addRestoreErrRetIndex(
11385 gz: *GenZir,
11386 bt: BranchTarget,
11387 cond: union(enum) {
11388 always: void,
11389 if_non_error: Zir.Inst.Ref,
11390 },
11391 ) !Zir.Inst.Index {
11392 return gz.addAsIndex(.{
11393 .tag = .restore_err_ret_index,
11394 .data = .{ .restore_err_ret_index = .{
11395 .block = switch (bt) {
11396 .ret => .none,
11397 .block => |b| Zir.indexToRef(b),
11398 },
11399 .operand = if (cond == .if_non_error) cond.if_non_error else .none,
11400 } },
11401 });
11402 }
11403
1136211404 fn addBreak(
1136311405 gz: *GenZir,
1136411406 tag: Zir.Inst.Tag,
src/Module.zig+7
......@@ -5633,6 +5633,13 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
56335633
56345634 const last_arg_index = inner_block.instructions.items.len;
56355635
5636 // Save the error trace as our first action in the function.
5637 // If this is unnecessary after all, Liveness will clean it up for us.
5638 const err_ret_trace_index = try sema.analyzeSaveErrRetIndex(&inner_block);
5639 inner_block.error_return_trace_index = err_ret_trace_index;
5640 inner_block.error_return_trace_index_on_block_entry = err_ret_trace_index;
5641 inner_block.error_return_trace_index_on_function_entry = err_ret_trace_index;
5642
56365643 sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {
56375644 // TODO make these unreachable instead of @panic
56385645 error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"),
src/Sema.zig+147-70
......@@ -153,6 +153,12 @@ pub const Block = struct {
153153 is_typeof: bool = false,
154154 is_coerce_result_ptr: bool = false,
155155
156 /// Keep track of the active error return trace index around blocks so that we can correctly
157 /// pop the error trace upon block exit.
158 error_return_trace_index: Air.Inst.Ref = .none,
159 error_return_trace_index_on_block_entry: Air.Inst.Ref = .none,
160 error_return_trace_index_on_function_entry: Air.Inst.Ref = .none,
161
156162 /// when null, it is determined by build mode, changed by @setRuntimeSafety
157163 want_safety: ?bool = null,
158164
......@@ -226,6 +232,9 @@ pub const Block = struct {
226232 .float_mode = parent.float_mode,
227233 .c_import_buf = parent.c_import_buf,
228234 .switch_else_err_ty = parent.switch_else_err_ty,
235 .error_return_trace_index = parent.error_return_trace_index,
236 .error_return_trace_index_on_block_entry = parent.error_return_trace_index,
237 .error_return_trace_index_on_function_entry = parent.error_return_trace_index_on_function_entry,
229238 };
230239 }
231240
......@@ -945,8 +954,6 @@ fn analyzeBodyInner(
945954 .ret_ptr => try sema.zirRetPtr(block, inst),
946955 .ret_type => try sema.addType(sema.fn_ret_ty),
947956
948 .save_err_ret_index => try sema.zirSaveErrRetIndex(block, inst),
949
950957 // Instructions that we know to *always* be noreturn based solely on their tag.
951958 // These functions match the return type of analyzeBody so that we can
952959 // tail call them here.
......@@ -1229,6 +1236,11 @@ fn analyzeBodyInner(
12291236 i += 1;
12301237 continue;
12311238 },
1239 .save_err_ret_index => {
1240 try sema.zirSaveErrRetIndex(block, inst);
1241 i += 1;
1242 continue;
1243 },
12321244 .restore_err_ret_index => {
12331245 try sema.zirRestoreErrRetIndex(block, inst);
12341246 i += 1;
......@@ -1326,31 +1338,32 @@ fn analyzeBodyInner(
13261338 const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
13271339 const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len];
13281340 const gpa = sema.gpa;
1329 // If this block contains a function prototype, we need to reset the
1330 // current list of parameters and restore it later.
1331 // Note: this probably needs to be resolved in a more general manner.
1332 const prev_params = block.params;
1333 const need_sub_block = tags[inline_body[inline_body.len - 1]] == .repeat_inline;
1334 var sub_block = block;
1335 var block_space: Block = undefined;
1336 // NOTE: this has to be done like this because branching in
1337 // defers here breaks stage1.
1338 block_space.instructions = .{};
1339 if (need_sub_block) {
1340 block_space = block.makeSubBlock();
1341 block_space.inline_block = inline_body[0];
1342 sub_block = &block_space;
1343 }
1344 block.params = .{};
1345 defer {
1346 block.params.deinit(gpa);
1347 block.params = prev_params;
1348 block_space.instructions.deinit(gpa);
1349 }
1350 const opt_break_data = try sema.analyzeBodyBreak(sub_block, inline_body);
1351 if (need_sub_block) {
1352 try block.instructions.appendSlice(gpa, block_space.instructions.items);
1353 }
1341
1342 const opt_break_data = b: {
1343 // Create a temporary child block so that this inline block is properly
1344 // labeled for any .restore_err_ret_index instructions
1345 var child_block = block.makeSubBlock();
1346
1347 // If this block contains a function prototype, we need to reset the
1348 // current list of parameters and restore it later.
1349 // Note: this probably needs to be resolved in a more general manner.
1350 if (tags[inline_body[inline_body.len - 1]] == .repeat_inline) {
1351 child_block.inline_block = inline_body[0];
1352 } else child_block.inline_block = block.inline_block;
1353
1354 var label: Block.Label = .{
1355 .zir_block = inst,
1356 .merges = undefined,
1357 };
1358 child_block.label = &label;
1359 defer child_block.params.deinit(gpa);
1360
1361 // Write these instructions directly into the parent block
1362 child_block.instructions = block.instructions;
1363 defer block.instructions = child_block.instructions;
1364
1365 break :b try sema.analyzeBodyBreak(&child_block, inline_body);
1366 };
13541367
13551368 // A runtime conditional branch that needs a post-hoc block to be
13561369 // emitted communicates this by mapping the block index into the inst map.
......@@ -4994,7 +5007,7 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro
49945007
49955008 // Reserve space for a Block instruction so that generated Break instructions can
49965009 // point to it, even if it doesn't end up getting used because the code ends up being
4997 // comptime evaluated.
5010 // comptime evaluated or is an unlabeled block.
49985011 const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
49995012 try sema.air_instructions.append(gpa, .{
50005013 .tag = .block,
......@@ -5025,6 +5038,9 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro
50255038 .runtime_cond = parent_block.runtime_cond,
50265039 .runtime_loop = parent_block.runtime_loop,
50275040 .runtime_index = parent_block.runtime_index,
5041 .error_return_trace_index = parent_block.error_return_trace_index,
5042 .error_return_trace_index_on_block_entry = parent_block.error_return_trace_index,
5043 .error_return_trace_index_on_function_entry = parent_block.error_return_trace_index_on_function_entry,
50285044 };
50295045
50305046 defer child_block.instructions.deinit(gpa);
......@@ -5667,19 +5683,51 @@ fn funcDeclSrc(sema: *Sema, block: *Block, src: LazySrcLoc, func_inst: Air.Inst.
56675683 return owner_decl.srcLoc();
56685684}
56695685
5686pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref {
5687 const src = sema.src;
5688
5689 const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
5690 if (!backend_supports_error_return_tracing or !sema.mod.comp.bin_file.options.error_return_tracing)
5691 return .none;
5692
5693 if (block.is_comptime)
5694 return .none;
5695
5696 const unresolved_stack_trace_ty = sema.getBuiltinType(block, src, "StackTrace") catch |err| switch (err) {
5697 error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
5698 else => |e| return e,
5699 };
5700 const stack_trace_ty = sema.resolveTypeFields(block, src, unresolved_stack_trace_ty) catch |err| switch (err) {
5701 error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
5702 else => |e| return e,
5703 };
5704 const field_index = sema.structFieldIndex(block, stack_trace_ty, "index", src) catch |err| switch (err) {
5705 error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
5706 else => |e| return e,
5707 };
5708
5709 return try block.addInst(.{
5710 .tag = .save_err_return_trace_index,
5711 .data = .{ .ty_pl = .{
5712 .ty = try sema.addType(stack_trace_ty),
5713 .payload = @intCast(u32, field_index),
5714 } },
5715 });
5716}
5717
56705718/// Add instructions to block to "pop" the error return trace.
56715719/// If `operand` is provided, only pops if operand is non-error.
56725720fn popErrorReturnTrace(
56735721 sema: *Sema,
56745722 block: *Block,
56755723 src: LazySrcLoc,
5676 operand: ?Air.Inst.Ref,
5724 operand: Air.Inst.Ref,
56775725 saved_error_trace_index: Air.Inst.Ref,
56785726) CompileError!void {
56795727 var is_non_error: ?bool = null;
56805728 var is_non_error_inst: Air.Inst.Ref = undefined;
5681 if (operand) |op| {
5682 is_non_error_inst = try sema.analyzeIsNonErr(block, src, op);
5729 if (operand != .none) {
5730 is_non_error_inst = try sema.analyzeIsNonErr(block, src, operand);
56835731 if (try sema.resolveDefinedValue(block, src, is_non_error_inst)) |cond_val|
56845732 is_non_error = cond_val.toBool();
56855733 } else is_non_error = true; // no operand means pop unconditionally
......@@ -5906,7 +5954,7 @@ fn zirCall(
59065954 });
59075955
59085956 // Pop the error return trace, testing the result for non-error if necessary
5909 const operand = if (pop_error_return_trace or modifier == .always_tail) null else call_inst;
5957 const operand = if (pop_error_return_trace or modifier == .always_tail) .none else call_inst;
59105958 try sema.popErrorReturnTrace(block, call_src, operand, save_inst);
59115959 }
59125960
......@@ -6221,6 +6269,9 @@ fn analyzeCall(
62216269 .label = null,
62226270 .inlining = &inlining,
62236271 .is_comptime = is_comptime_call,
6272 .error_return_trace_index = block.error_return_trace_index,
6273 .error_return_trace_index_on_block_entry = block.error_return_trace_index,
6274 .error_return_trace_index_on_function_entry = block.error_return_trace_index,
62246275 };
62256276
62266277 const merges = &child_block.inlining.?.merges;
......@@ -6966,6 +7017,14 @@ fn instantiateGenericCall(
69667017 }
69677018 arg_i += 1;
69687019 }
7020
7021 // Save the error trace as our first action in the function.
7022 // If this is unnecessary after all, Liveness will clean it up for us.
7023 const err_ret_trace_index = try sema.analyzeSaveErrRetIndex(&child_block);
7024 child_block.error_return_trace_index = err_ret_trace_index;
7025 child_block.error_return_trace_index_on_block_entry = err_ret_trace_index;
7026 child_block.error_return_trace_index_on_function_entry = err_ret_trace_index;
7027
69697028 const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| {
69707029 // TODO look up the compile error that happened here and attach a note to it
69717030 // pointing here, at the generic instantiation callsite.
......@@ -9855,6 +9914,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
98559914 .defer_err_code,
98569915 .err_union_code,
98579916 .ret_err_value_code,
9917 .restore_err_ret_index,
98589918 .is_non_err,
98599919 .condbr,
98609920 => {},
......@@ -10157,6 +10217,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
1015710217 .runtime_cond = block.runtime_cond,
1015810218 .runtime_loop = block.runtime_loop,
1015910219 .runtime_index = block.runtime_index,
10220 .error_return_trace_index = block.error_return_trace_index,
10221 .error_return_trace_index_on_block_entry = block.error_return_trace_index,
10222 .error_return_trace_index_on_function_entry = block.error_return_trace_index_on_function_entry,
1016010223 };
1016110224 const merges = &child_block.label.?.merges;
1016210225 defer child_block.instructions.deinit(gpa);
......@@ -11040,6 +11103,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
1104011103 const tags = sema.code.instructions.items(.tag);
1104111104 for (body) |inst| {
1104211105 switch (tags[inst]) {
11106 .save_err_ret_index,
1104311107 .dbg_block_begin,
1104411108 .dbg_block_end,
1104511109 .dbg_stmt,
......@@ -11062,6 +11126,10 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
1106211126 try sema.zirDbgStmt(block, inst);
1106311127 continue;
1106411128 },
11129 .save_err_ret_index => {
11130 try sema.zirSaveErrRetIndex(block, inst);
11131 continue;
11132 },
1106511133 .str => try sema.zirStr(block, inst),
1106611134 .as_node => try sema.zirAsNode(block, inst),
1106711135 .field_val => try sema.zirFieldVal(block, inst),
......@@ -15672,6 +15740,9 @@ fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
1567215740 .is_comptime = false,
1567315741 .is_typeof = true,
1567415742 .want_safety = false,
15743 .error_return_trace_index = block.error_return_trace_index,
15744 .error_return_trace_index_on_block_entry = block.error_return_trace_index,
15745 .error_return_trace_index_on_function_entry = block.error_return_trace_index_on_function_entry,
1567515746 };
1567615747 defer child_block.instructions.deinit(sema.gpa);
1567715748
......@@ -16329,43 +16400,35 @@ fn wantErrorReturnTracing(sema: *Sema, fn_ret_ty: Type) bool {
1632916400 backend_supports_error_return_tracing;
1633016401}
1633116402
16332fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
16333 const inst_data = sema.code.instructions.items(.data)[inst].node;
16334 const src = LazySrcLoc.nodeOffset(inst_data);
16403fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
16404 const inst_data = sema.code.instructions.items(.data)[inst].save_err_ret_index;
16405
16406 const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
16407 const ok = backend_supports_error_return_tracing and sema.mod.comp.bin_file.options.error_return_tracing;
16408 if (!ok) return;
1633516409
1633616410 // This is only relevant at runtime.
16337 if (block.is_comptime) return Air.Inst.Ref.zero_usize;
16411 if (block.is_comptime) return;
1633816412
16339 const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
16340 const ok = sema.mod.comp.bin_file.options.error_return_tracing and
16341 backend_supports_error_return_tracing;
16342 if (!ok) return Air.Inst.Ref.zero_usize;
16413 // This is only relevant within functions.
16414 if (sema.func == null) return;
1634316415
16344 // This is encoded as a primitive AIR instruction to resolve one corner case: A function
16345 // may include a `catch { ... }` or `else |err| { ... }` block but not call any errorable
16346 // fn. In that case, there is no error return trace to save the index of and codegen needs
16347 // to avoid interacting with the non-existing error trace.
16348 //
16349 // By using a primitive AIR op, we can depend on Liveness to mark this unused in this corner case.
16416 const save_index = inst_data.operand == .none or b: {
16417 const operand = try sema.resolveInst(inst_data.operand);
16418 const operand_ty = sema.typeOf(operand);
16419 break :b operand_ty.isError();
16420 };
1635016421
16351 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
16352 const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
16353 const field_index = try sema.structFieldIndex(block, stack_trace_ty, "index", src);
16354 return block.addInst(.{
16355 .tag = .save_err_return_trace_index,
16356 .data = .{ .ty_pl = .{
16357 .ty = try sema.addType(stack_trace_ty),
16358 .payload = @intCast(u32, field_index),
16359 } },
16360 });
16422 if (save_index)
16423 block.error_return_trace_index = try sema.analyzeSaveErrRetIndex(block);
1636116424}
1636216425
16363fn zirRestoreErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
16364 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
16365 const src = inst_data.src();
16426fn zirRestoreErrRetIndex(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
16427 const inst_data = sema.code.instructions.items(.data)[inst].restore_err_ret_index;
16428 const src = sema.src; // TODO
1636616429
1636716430 // This is only relevant at runtime.
16368 if (block.is_comptime) return;
16431 if (start_block.is_comptime) return;
1636916432
1637016433 const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
1637116434 const ok = sema.owner_func.?.calls_or_awaits_errorable_fn and
......@@ -16373,17 +16436,31 @@ fn zirRestoreErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi
1637316436 backend_supports_error_return_tracing;
1637416437 if (!ok) return;
1637516438
16376 const operand = if (inst_data.operand != .none)
16377 try sema.resolveInst(inst_data.operand)
16378 else
16379 .zero_usize;
16439 const tracy = trace(@src());
16440 defer tracy.end();
1638016441
16381 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
16382 const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
16383 const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
16384 const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
16385 const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, "index", src, stack_trace_ty, true);
16386 try sema.storePtr2(block, src, field_ptr, src, operand, src, .store);
16442 const saved_index = if (Zir.refToIndex(inst_data.block)) |zir_block| b: {
16443 var block = start_block;
16444 while (true) {
16445 if (block.label) |label| {
16446 if (label.zir_block == zir_block) {
16447 if (start_block.error_return_trace_index != block.error_return_trace_index_on_block_entry)
16448 break :b block.error_return_trace_index_on_block_entry;
16449 return; // No need to restore
16450 }
16451 }
16452 block = block.parent.?;
16453 }
16454 } else b: {
16455 if (start_block.error_return_trace_index != start_block.error_return_trace_index_on_function_entry)
16456 break :b start_block.error_return_trace_index_on_function_entry;
16457 return; // No need to restore
16458 };
16459
16460 assert(saved_index != .none); // The .error_return_trace_index field was dropped somewhere
16461
16462 const operand = try sema.resolveInst(inst_data.operand);
16463 return sema.popErrorReturnTrace(start_block, src, operand, saved_index);
1638716464}
1638816465
1638916466fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void {
src/Zir.zig+16-7
......@@ -988,13 +988,13 @@ pub const Inst = struct {
988988 /// Uses the `err_defer_code` union field.
989989 defer_err_code,
990990
991 /// Saves the current error return case if it exists,
992 /// otherwise just returns zero.
993 /// Uses the `node` union field.
991 /// Requests that Sema update the saved error return trace index for the enclosing
992 /// block, if the operand is .none or of an error/error-union type.
993 /// Uses the `save_err_ret_index` field.
994994 save_err_ret_index,
995995 /// Sets error return trace to zero if no operand is given,
996996 /// otherwise sets the value to the given amount.
997 /// Uses the `un_node` union field.
997 /// Uses the `restore_err_ret_index` union field.
998998 restore_err_ret_index,
999999
10001000 /// The ZIR instruction tag is one of the `Extended` ones.
......@@ -1317,6 +1317,7 @@ pub const Inst = struct {
13171317 .@"defer",
13181318 .defer_err_code,
13191319 .restore_err_ret_index,
1320 .save_err_ret_index,
13201321 => true,
13211322
13221323 .param,
......@@ -1542,7 +1543,6 @@ pub const Inst = struct {
15421543 .try_ptr,
15431544 //.try_inline,
15441545 //.try_ptr_inline,
1545 .save_err_ret_index,
15461546 => false,
15471547
15481548 .extended => switch (data.extended.opcode) {
......@@ -1823,8 +1823,8 @@ pub const Inst = struct {
18231823 .@"defer" = .@"defer",
18241824 .defer_err_code = .defer_err_code,
18251825
1826 .save_err_ret_index = .node,
1827 .restore_err_ret_index = .un_node,
1826 .save_err_ret_index = .save_err_ret_index,
1827 .restore_err_ret_index = .restore_err_ret_index,
18281828
18291829 .extended = .extended,
18301830 });
......@@ -2602,6 +2602,13 @@ pub const Inst = struct {
26022602 err_code: Ref,
26032603 payload_index: u32,
26042604 },
2605 save_err_ret_index: struct {
2606 operand: Ref, // If error type (or .none), save new trace index
2607 },
2608 restore_err_ret_index: struct {
2609 block: Ref, // If restored, the index is from this block's entrypoint
2610 operand: Ref, // If non-error (or .none), then restore the index
2611 },
26052612
26062613 // Make sure we don't accidentally add a field to make this union
26072614 // bigger than expected. Note that in Debug builds, Zig is allowed
......@@ -2640,6 +2647,8 @@ pub const Inst = struct {
26402647 str_op,
26412648 @"defer",
26422649 defer_err_code,
2650 save_err_ret_index,
2651 restore_err_ret_index,
26432652 };
26442653 };
26452654
src/print_zir.zig+19-2
......@@ -232,7 +232,6 @@ const Writer = struct {
232232 .validate_deref,
233233 .overflow_arithmetic_ptr,
234234 .check_comptime_control_flow,
235 .restore_err_ret_index,
236235 => try self.writeUnNode(stream, inst),
237236
238237 .ref,
......@@ -255,6 +254,9 @@ const Writer = struct {
255254 .str => try self.writeStr(stream, inst),
256255 .int_type => try self.writeIntType(stream, inst),
257256
257 .save_err_ret_index => try self.writeSaveErrRetIndex(stream, inst),
258 .restore_err_ret_index => try self.writeRestoreErrRetIndex(stream, inst),
259
258260 .@"break",
259261 .break_inline,
260262 => try self.writeBreak(stream, inst),
......@@ -406,7 +408,6 @@ const Writer = struct {
406408 .alloc_inferred_comptime_mut,
407409 .ret_ptr,
408410 .ret_type,
409 .save_err_ret_index,
410411 => try self.writeNode(stream, inst),
411412
412413 .error_value,
......@@ -2274,6 +2275,22 @@ const Writer = struct {
22742275 try self.writeSrc(stream, int_type.src());
22752276 }
22762277
2278 fn writeSaveErrRetIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
2279 const inst_data = self.code.instructions.items(.data)[inst].save_err_ret_index;
2280
2281 try self.writeInstRef(stream, inst_data.operand);
2282 try stream.writeAll(")");
2283 }
2284
2285 fn writeRestoreErrRetIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
2286 const inst_data = self.code.instructions.items(.data)[inst].restore_err_ret_index;
2287
2288 try self.writeInstRef(stream, inst_data.block);
2289 try stream.writeAll(", ");
2290 try self.writeInstRef(stream, inst_data.operand);
2291 try stream.writeAll(")");
2292 }
2293
22772294 fn writeBreak(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
22782295 const inst_data = self.code.instructions.items(.data)[inst].@"break";
22792296
test/behavior/error.zig+13
......@@ -830,3 +830,16 @@ test "compare error union and error set" {
830830 try expect(a != b);
831831 try expect(b != a);
832832}
833
834fn non_errorable() void {
835 // Make sure catch works even in a function that does not call any errorable functions.
836 //
837 // This test is needed because stage 2's fix for #1923 means that catch blocks interact
838 // with the error return trace index.
839 var x: error{Foo}!void = {};
840 return x catch {};
841}
842
843test "catch within a function that calls no errorable functions" {
844 non_errorable();
845}
test/stack_traces.zig+183-1
......@@ -97,6 +97,59 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
9797 ,
9898 },
9999 });
100 cases.addCase(.{
101 .name = "non-error return pops error trace",
102 .source =
103 \\fn bar() !void {
104 \\ return error.UhOh;
105 \\}
106 \\
107 \\fn foo() !void {
108 \\ bar() catch {
109 \\ return; // non-error result: success
110 \\ };
111 \\}
112 \\
113 \\pub fn main() !void {
114 \\ try foo();
115 \\ return error.UnrelatedError;
116 \\}
117 ,
118 .Debug = .{
119 .expect =
120 \\error: UnrelatedError
121 \\source.zig:13:5: [address] in main (test)
122 \\ return error.UnrelatedError;
123 \\ ^
124 \\
125 ,
126 },
127 .ReleaseSafe = .{
128 .exclude_os = .{
129 .windows, // TODO
130 .linux, // defeated by aggressive inlining
131 },
132 .expect =
133 \\error: UnrelatedError
134 \\source.zig:13:5: [address] in [function]
135 \\ return error.UnrelatedError;
136 \\ ^
137 \\
138 ,
139 },
140 .ReleaseFast = .{
141 .expect =
142 \\error: UnrelatedError
143 \\
144 ,
145 },
146 .ReleaseSmall = .{
147 .expect =
148 \\error: UnrelatedError
149 \\
150 ,
151 },
152 });
100153
101154 cases.addCase(.{
102155 .name = "try return + handled catch/if-else",
......@@ -155,6 +208,59 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
155208 },
156209 });
157210
211 cases.addCase(.{
212 .name = "break from inline loop pops error return trace",
213 .source =
214 \\fn foo() !void { return error.FooBar; }
215 \\
216 \\pub fn main() !void {
217 \\ comptime var i: usize = 0;
218 \\ b: inline while (i < 5) : (i += 1) {
219 \\ foo() catch {
220 \\ break :b; // non-error break, success
221 \\ };
222 \\ }
223 \\ // foo() was successfully handled, should not appear in trace
224 \\
225 \\ return error.BadTime;
226 \\}
227 ,
228 .Debug = .{
229 .expect =
230 \\error: BadTime
231 \\source.zig:12:5: [address] in main (test)
232 \\ return error.BadTime;
233 \\ ^
234 \\
235 ,
236 },
237 .ReleaseSafe = .{
238 .exclude_os = .{
239 .windows, // TODO
240 .linux, // defeated by aggressive inlining
241 },
242 .expect =
243 \\error: BadTime
244 \\source.zig:12:5: [address] in [function]
245 \\ return error.BadTime;
246 \\ ^
247 \\
248 ,
249 },
250 .ReleaseFast = .{
251 .expect =
252 \\error: BadTime
253 \\
254 ,
255 },
256 .ReleaseSmall = .{
257 .expect =
258 \\error: BadTime
259 \\
260 ,
261 },
262 });
263
158264 cases.addCase(.{
159265 .name = "catch and re-throw error",
160266 .source =
......@@ -209,7 +315,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
209315 });
210316
211317 cases.addCase(.{
212 .name = "stored errors do not contribute to error trace",
318 .name = "errors stored in var do not contribute to error trace",
213319 .source =
214320 \\fn foo() !void {
215321 \\ return error.TheSkyIsFalling;
......@@ -260,6 +366,82 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
260366 },
261367 });
262368
369 cases.addCase(.{
370 .name = "error stored in const has trace preserved for duration of block",
371 .source =
372 \\fn foo() !void { return error.TheSkyIsFalling; }
373 \\fn bar() !void { return error.InternalError; }
374 \\fn baz() !void { return error.UnexpectedReality; }
375 \\
376 \\pub fn main() !void {
377 \\ const x = foo();
378 \\ const y = b: {
379 \\ if (true)
380 \\ break :b bar();
381 \\
382 \\ break :b {};
383 \\ };
384 \\ x catch {};
385 \\ y catch {};
386 \\ // foo()/bar() error traces not popped until end of block
387 \\
388 \\ {
389 \\ const z = baz();
390 \\ z catch {};
391 \\ // baz() error trace still alive here
392 \\ }
393 \\ // baz() error trace popped, foo(), bar() still alive
394 \\ return error.StillUnresolved;
395 \\}
396 ,
397 .Debug = .{
398 .expect =
399 \\error: StillUnresolved
400 \\source.zig:1:18: [address] in foo (test)
401 \\fn foo() !void { return error.TheSkyIsFalling; }
402 \\ ^
403 \\source.zig:2:18: [address] in bar (test)
404 \\fn bar() !void { return error.InternalError; }
405 \\ ^
406 \\source.zig:23:5: [address] in main (test)
407 \\ return error.StillUnresolved;
408 \\ ^
409 \\
410 ,
411 },
412 .ReleaseSafe = .{
413 .exclude_os = .{
414 .windows, // TODO
415 .linux, // defeated by aggressive inlining
416 },
417 .expect =
418 \\error: StillUnresolved
419 \\source.zig:1:18: [address] in [function]
420 \\fn foo() !void { return error.TheSkyIsFalling; }
421 \\ ^
422 \\source.zig:2:18: [address] in [function]
423 \\fn bar() !void { return error.InternalError; }
424 \\ ^
425 \\source.zig:23:5: [address] in [function]
426 \\ return error.StillUnresolved;
427 \\ ^
428 \\
429 ,
430 },
431 .ReleaseFast = .{
432 .expect =
433 \\error: StillUnresolved
434 \\
435 ,
436 },
437 .ReleaseSmall = .{
438 .expect =
439 \\error: StillUnresolved
440 \\
441 ,
442 },
443 });
444
263445 cases.addCase(.{
264446 .name = "error passed to function has its trace preserved for duration of the call",
265447 .source =