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 {...@@ -44,24 +44,23 @@ pub fn main() void {
44 if (!have_tty) {44 if (!have_tty) {
45 std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name });45 std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name });
46 }46 }
47 if (result: {47 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
48 if (test_fn.async_frame_size) |size| switch (io_mode) {48 .evented => blk: {
49 .evented => {49 if (async_frame_buffer.len < size) {
50 if (async_frame_buffer.len < size) {50 std.heap.page_allocator.free(async_frame_buffer);
51 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 async_frame_buffer = std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size) catch @panic("out of memory");52 }
53 }53 const casted_fn = @ptrCast(fn () callconv(.Async) anyerror!void, test_fn.func);
54 const casted_fn = @ptrCast(fn () callconv(.Async) anyerror!void, test_fn.func);54 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn, .{});
55 break :result await @asyncCall(async_frame_buffer, {}, casted_fn, .{});55 },
56 },56 .blocking => {
57 .blocking => {57 skip_count += 1;
58 skip_count += 1;58 test_node.end();
59 test_node.end();59 progress.log("SKIP (async test)\n", .{});
60 progress.log("SKIP (async test)\n", .{});60 continue;
61 continue;61 },
62 },62 } else test_fn.func();
63 } else break :result test_fn.func();63 if (result) |_| {
64 }) |_| {
65 ok_count += 1;64 ok_count += 1;
66 test_node.end();65 test_node.end();
67 if (!have_tty) std.debug.print("OK\n", .{});66 if (!have_tty) std.debug.print("OK\n", .{});
src/Air.zig+1-1
...@@ -734,7 +734,7 @@ pub const Inst = struct {...@@ -734,7 +734,7 @@ pub const Inst = struct {
734 addrspace_cast,734 addrspace_cast,
735735
736 /// Saves the error return trace index, if any. Otherwise, returns 0.736 /// Saves the error return trace index, if any. Otherwise, returns 0.
737 /// Uses the `ty_op` field.737 /// Uses the `ty_pl` field.
738 save_err_return_trace_index,738 save_err_return_trace_index,
739739
740 pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag {740 pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag {
src/AstGen.zig+194-152
...@@ -337,6 +337,8 @@ pub const ResultInfo = struct {...@@ -337,6 +337,8 @@ pub const ResultInfo = struct {
337 shift_op,337 shift_op,
338 /// The expression is an argument in a function call.338 /// The expression is an argument in a function call.
339 fn_arg,339 fn_arg,
340 /// The expression is the right-hand side of an initializer for a `const` variable
341 const_init,
340 /// No specific operator in particular.342 /// No specific operator in particular.
341 none,343 none,
342 };344 };
...@@ -1850,6 +1852,45 @@ fn comptimeExprAst(...@@ -1850,6 +1852,45 @@ fn comptimeExprAst(
1850 return result;1852 return result;
1851}1853}
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
1853fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {1894fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
1854 const astgen = parent_gz.astgen;1895 const astgen = parent_gz.astgen;
1855 const tree = astgen.tree;1896 const tree = astgen.tree;
...@@ -1857,13 +1898,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn...@@ -1857,13 +1898,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
1857 const break_label = node_datas[node].lhs;1898 const break_label = node_datas[node].lhs;
1858 const rhs = node_datas[node].rhs;1899 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
1867 // Look for the label in the scope.1901 // Look for the label in the scope.
1868 var scope = parent_scope;1902 var scope = parent_scope;
1869 while (true) {1903 while (true) {
...@@ -1882,11 +1916,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn...@@ -1882,11 +1916,6 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
1882 });1916 });
1883 }1917 }
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
1890 const block_inst = blk: {1919 const block_inst = blk: {
1891 if (break_label != 0) {1920 if (break_label != 0) {
1892 if (block_gz.label) |*label| {1921 if (block_gz.label) |*label| {
...@@ -1913,10 +1942,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn...@@ -1913,10 +1942,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
1913 try genDefers(parent_gz, scope, parent_scope, .normal_only);1942 try genDefers(parent_gz, scope, parent_scope, .normal_only);
19141943
1915 // As our last action before the break, "pop" the error trace if needed1944 // As our last action before the break, "pop" the error trace if needed
1916 if (err_trace_index_to_restore != .none) {1945 if (!block_gz.force_comptime)
1917 // void is a non-error so we always pop - no need to call `popErrorReturnTrace`1946 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = block_inst }, .always);
1918 _ = try parent_gz.addUnNode(.restore_err_ret_index, err_trace_index_to_restore, node);
1919 }
19201947
1921 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);1948 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
1922 return Zir.Inst.Ref.unreachable_value;1949 return Zir.Inst.Ref.unreachable_value;
...@@ -1929,17 +1956,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn...@@ -1929,17 +1956,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
1929 try genDefers(parent_gz, scope, parent_scope, .normal_only);1956 try genDefers(parent_gz, scope, parent_scope, .normal_only);
19301957
1931 // As our last action before the break, "pop" the error trace if needed1958 // As our last action before the break, "pop" the error trace if needed
1932 if (err_trace_index_to_restore != .none) {1959 if (!block_gz.force_comptime)
1933 // Pop the error trace, unless the operand is an error and breaking to an error-handling expr.1960 try restoreErrRetIndex(parent_gz, .{ .block = block_inst }, block_gz.break_result_info, rhs, operand);
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 }
19431961
1944 switch (block_gz.break_result_info.rl) {1962 switch (block_gz.break_result_info.rl) {
1945 .block_ptr => {1963 .block_ptr => {
...@@ -2066,8 +2084,34 @@ fn blockExpr(...@@ -2066,8 +2084,34 @@ fn blockExpr(
2066 return labeledBlockExpr(gz, scope, ri, block_node, statements);2084 return labeledBlockExpr(gz, scope, ri, block_node, statements);
2067 }2085 }
20682086
2069 var sub_gz = gz.makeSubBlock(scope);2087 if (!gz.force_comptime) {
2070 try blockExprStmts(&sub_gz, &sub_gz.base, statements);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
2071 return rvalue(gz, ri, .void_value, block_node);2115 return rvalue(gz, ri, .void_value, block_node);
2072}2116}
20732117
...@@ -2141,6 +2185,9 @@ fn labeledBlockExpr(...@@ -2141,6 +2185,9 @@ fn labeledBlockExpr(
21412185
2142 try blockExprStmts(&block_scope, &block_scope.base, statements);2186 try blockExprStmts(&block_scope, &block_scope.base, statements);
2143 if (!block_scope.endsWithNoReturn()) {2187 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
2144 const break_tag: Zir.Inst.Tag = if (block_scope.force_comptime) .break_inline else .@"break";2191 const break_tag: Zir.Inst.Tag = if (block_scope.force_comptime) .break_inline else .@"break";
2145 _ = try block_scope.addBreak(break_tag, block_inst, .void_value);2192 _ = try block_scope.addBreak(break_tag, block_inst, .void_value);
2146 }2193 }
...@@ -2164,7 +2211,8 @@ fn labeledBlockExpr(...@@ -2164,7 +2211,8 @@ fn labeledBlockExpr(
2164 return indexToRef(block_inst);2211 return indexToRef(block_inst);
2165 },2212 },
2166 .break_operand => {2213 .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).
2168 // The break instructions need to have their operands coerced if the2216 // The break instructions need to have their operands coerced if the
2169 // block's result location is a `ty`. In this case we overwrite the2217 // block's result location is a `ty`. In this case we overwrite the
2170 // `store_to_block_ptr` instruction with an `as` instruction and repurpose2218 // `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...@@ -2528,7 +2576,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
2528 .try_ptr,2576 .try_ptr,
2529 //.try_inline,2577 //.try_inline,
2530 //.try_ptr_inline,2578 //.try_ptr_inline,
2531 .save_err_ret_index,
2532 => break :b false,2579 => break :b false,
25332580
2534 .extended => switch (gz.astgen.instructions.items(.data)[inst].extended.opcode) {2581 .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...@@ -2591,6 +2638,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
2591 .validate_array_init_ty,2638 .validate_array_init_ty,
2592 .validate_struct_init_ty,2639 .validate_struct_init_ty,
2593 .validate_deref,2640 .validate_deref,
2641 .save_err_ret_index,
2594 .restore_err_ret_index,2642 .restore_err_ret_index,
2595 => break :b true,2643 => break :b true,
25962644
...@@ -2877,7 +2925,8 @@ fn varDecl(...@@ -2877,7 +2925,8 @@ fn varDecl(
2877 {2925 {
2878 const result_info: ResultInfo = if (type_node != 0) .{2926 const result_info: ResultInfo = if (type_node != 0) .{
2879 .rl = .{ .ty = try typeExpr(gz, scope, type_node) },2927 .rl = .{ .ty = try typeExpr(gz, scope, type_node) },
2880 } else .{ .rl = .none };2928 .ctx = .const_init,
2929 } else .{ .rl = .none, .ctx = .const_init };
2881 const prev_anon_name_strategy = gz.anon_name_strategy;2930 const prev_anon_name_strategy = gz.anon_name_strategy;
2882 gz.anon_name_strategy = .dbg_var;2931 gz.anon_name_strategy = .dbg_var;
2883 const init_inst = try reachableExpr(gz, scope, result_info, var_decl.ast.init_node, node);2932 const init_inst = try reachableExpr(gz, scope, result_info, var_decl.ast.init_node, node);
...@@ -2885,6 +2934,11 @@ fn varDecl(...@@ -2885,6 +2934,11 @@ fn varDecl(
28852934
2886 try gz.addDbgVar(.dbg_var_val, ident_name, init_inst);2935 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
2888 const sub_scope = try block_arena.create(Scope.LocalVal);2942 const sub_scope = try block_arena.create(Scope.LocalVal);
2889 sub_scope.* = .{2943 sub_scope.* = .{
2890 .parent = scope,2944 .parent = scope,
...@@ -2950,9 +3004,14 @@ fn varDecl(...@@ -2950,9 +3004,14 @@ fn varDecl(
2950 init_scope.rl_ptr = alloc;3004 init_scope.rl_ptr = alloc;
2951 init_scope.rl_ty_inst = .none;3005 init_scope.rl_ty_inst = .none;
2952 }3006 }
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 };
2954 const init_inst = try reachableExpr(&init_scope, &init_scope.base, init_result_info, var_decl.ast.init_node, node);3008 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
2956 const zir_tags = astgen.instructions.items(.tag);3015 const zir_tags = astgen.instructions.items(.tag);
2957 const zir_datas = astgen.instructions.items(.data);3016 const zir_datas = astgen.instructions.items(.data);
29583017
...@@ -3775,6 +3834,9 @@ fn fnDecl(...@@ -3775,6 +3834,9 @@ fn fnDecl(
3775 try checkUsed(gz, &fn_gz.base, params_scope);3834 try checkUsed(gz, &fn_gz.base, params_scope);
37763835
3777 if (!fn_gz.endsWithNoReturn()) {3836 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
3778 // Since we are adding the return instruction here, we must handle the coercion.3840 // Since we are adding the return instruction here, we must handle the coercion.
3779 // We do this by using the `ret_tok` instruction.3841 // We do this by using the `ret_tok` instruction.
3780 _ = try fn_gz.addUnTok(.ret_tok, .void_value, tree.lastToken(body_node));3842 _ = try fn_gz.addUnTok(.ret_tok, .void_value, tree.lastToken(body_node));
...@@ -4217,6 +4279,10 @@ fn testDecl(...@@ -4217,6 +4279,10 @@ fn testDecl(
42174279
4218 const block_result = try expr(&fn_block, &fn_block.base, .{ .rl = .none }, body_node);4280 const block_result = try expr(&fn_block, &fn_block.base, .{ .rl = .none }, body_node);
4219 if (fn_block.isEmpty() or !fn_block.refIsNoReturn(block_result)) {4281 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
4220 // Since we are adding the return instruction here, we must handle the coercion.4286 // Since we are adding the return instruction here, we must handle the coercion.
4221 // We do this by using the `ret_tok` instruction.4287 // We do this by using the `ret_tok` instruction.
4222 _ = try fn_block.addUnTok(.ret_tok, .void_value, tree.lastToken(body_node));4288 _ = try fn_block.addUnTok(.ret_tok, .void_value, tree.lastToken(body_node));
...@@ -5196,76 +5262,6 @@ fn tryExpr(...@@ -5196,76 +5262,6 @@ fn tryExpr(
5196 }5262 }
5197}5263}
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
5269fn orelseCatchExpr(5265fn orelseCatchExpr(
5270 parent_gz: *GenZir,5266 parent_gz: *GenZir,
5271 scope: *Scope,5267 scope: *Scope,
...@@ -5287,8 +5283,6 @@ fn orelseCatchExpr(...@@ -5287,8 +5283,6 @@ fn orelseCatchExpr(
5287 block_scope.setBreakResultInfo(ri);5283 block_scope.setBreakResultInfo(ri);
5288 defer block_scope.unstack();5284 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
5292 const operand_ri: ResultInfo = switch (block_scope.break_result_info.rl) {5286 const operand_ri: ResultInfo = switch (block_scope.break_result_info.rl) {
5293 .ref => .{ .rl = .ref, .ctx = if (do_err_trace) .error_handling_expr else .none },5287 .ref => .{ .rl = .ref, .ctx = if (do_err_trace) .error_handling_expr else .none },
5294 else => .{ .rl = .none, .ctx = if (do_err_trace) .error_handling_expr else .none },5288 else => .{ .rl = .none, .ctx = if (do_err_trace) .error_handling_expr else .none },
...@@ -5320,11 +5314,10 @@ fn orelseCatchExpr(...@@ -5320,11 +5314,10 @@ fn orelseCatchExpr(
5320 var else_scope = block_scope.makeSubBlock(scope);5314 var else_scope = block_scope.makeSubBlock(scope);
5321 defer else_scope.unstack();5315 defer else_scope.unstack();
53225316
5323 // Any break (of a non-error value) that navigates out of this scope means5317 // We know that the operand (almost certainly) modified the error return trace,
5324 // that the error was handled successfully, so this index will be restored.5318 // so signal to Sema that it should save the new index for restoring later.
5325 else_scope.saved_err_trace_index = saved_err_trace_index;5319 if (do_err_trace and nodeMayAppendToErrorTrace(tree, lhs))
5326 if (else_scope.outermost_err_trace_index == .none)5320 _ = try else_scope.addSaveErrRetIndex(.always);
5327 else_scope.outermost_err_trace_index = saved_err_trace_index;
53285321
5329 var err_val_scope: Scope.LocalVal = undefined;5322 var err_val_scope: Scope.LocalVal = undefined;
5330 const else_sub_scope = blk: {5323 const else_sub_scope = blk: {
...@@ -5352,16 +5345,9 @@ fn orelseCatchExpr(...@@ -5352,16 +5345,9 @@ fn orelseCatchExpr(
5352 if (!else_scope.endsWithNoReturn()) {5345 if (!else_scope.endsWithNoReturn()) {
5353 block_scope.break_count += 1;5346 block_scope.break_count += 1;
53545347
5355 if (do_err_trace) {5348 // As our last action before the break, "pop" the error trace if needed
5356 try popErrorReturnTrace(5349 if (do_err_trace)
5357 &else_scope,5350 try restoreErrRetIndex(&else_scope, .{ .block = block }, block_scope.break_result_info, rhs, else_result);
5358 else_sub_scope,
5359 block_scope.break_result_info,
5360 rhs,
5361 else_result,
5362 saved_err_trace_index,
5363 );
5364 }
5365 }5351 }
5366 try checkUsed(parent_gz, &else_scope.base, else_sub_scope);5352 try checkUsed(parent_gz, &else_scope.base, else_sub_scope);
53675353
...@@ -5587,8 +5573,6 @@ fn ifExpr(...@@ -5587,8 +5573,6 @@ fn ifExpr(
5587 block_scope.setBreakResultInfo(ri);5573 block_scope.setBreakResultInfo(ri);
5588 defer block_scope.unstack();5574 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
5592 const payload_is_ref = if (if_full.payload_token) |payload_token|5576 const payload_is_ref = if (if_full.payload_token) |payload_token|
5593 token_tags[payload_token] == .asterisk5577 token_tags[payload_token] == .asterisk
5594 else5578 else
...@@ -5705,11 +5689,10 @@ fn ifExpr(...@@ -5705,11 +5689,10 @@ fn ifExpr(
5705 var else_scope = parent_gz.makeSubBlock(scope);5689 var else_scope = parent_gz.makeSubBlock(scope);
5706 defer else_scope.unstack();5690 defer else_scope.unstack();
57075691
5708 // Any break (of a non-error value) that navigates out of this scope means5692 // We know that the operand (almost certainly) modified the error return trace,
5709 // that the error was handled successfully, so this index will be restored.5693 // so signal to Sema that it should save the new index for restoring later.
5710 else_scope.saved_err_trace_index = saved_err_trace_index;5694 if (do_err_trace and nodeMayAppendToErrorTrace(tree, if_full.ast.cond_expr))
5711 if (else_scope.outermost_err_trace_index == .none)5695 _ = try else_scope.addSaveErrRetIndex(.always);
5712 else_scope.outermost_err_trace_index = saved_err_trace_index;
57135696
5714 const else_node = if_full.ast.else_expr;5697 const else_node = if_full.ast.else_expr;
5715 const else_info: struct {5698 const else_info: struct {
...@@ -5747,16 +5730,9 @@ fn ifExpr(...@@ -5747,16 +5730,9 @@ fn ifExpr(
5747 if (!else_scope.endsWithNoReturn()) {5730 if (!else_scope.endsWithNoReturn()) {
5748 block_scope.break_count += 1;5731 block_scope.break_count += 1;
57495732
5750 if (do_err_trace) {5733 // As our last action before the break, "pop" the error trace if needed
5751 try popErrorReturnTrace(5734 if (do_err_trace)
5752 &else_scope,5735 try restoreErrRetIndex(&else_scope, .{ .block = block }, block_scope.break_result_info, else_node, e);
5753 sub_scope,
5754 block_scope.break_result_info,
5755 else_node,
5756 e,
5757 saved_err_trace_index,
5758 );
5759 }
5760 }5736 }
5761 try checkUsed(parent_gz, &else_scope.base, sub_scope);5737 try checkUsed(parent_gz, &else_scope.base, sub_scope);
5762 try else_scope.addDbgBlockEnd();5738 try else_scope.addDbgBlockEnd();
...@@ -6886,6 +6862,10 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref...@@ -6886,6 +6862,10 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
6886 if (operand_node == 0) {6862 if (operand_node == 0) {
6887 // Returning a void value; skip error defers.6863 // Returning a void value; skip error defers.
6888 try genDefers(gz, defer_outer, scope, .normal_only);6864 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
6889 _ = try gz.addUnNode(.ret_node, .void_value, node);6869 _ = try gz.addUnNode(.ret_node, .void_value, node);
6890 return Zir.Inst.Ref.unreachable_value;6870 return Zir.Inst.Ref.unreachable_value;
6891 }6871 }
...@@ -6921,15 +6901,13 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref...@@ -6921,15 +6901,13 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
6921 const operand = try reachableExpr(gz, scope, ri, operand_node, node);6901 const operand = try reachableExpr(gz, scope, ri, operand_node, node);
6922 gz.anon_name_strategy = prev_anon_name_strategy;6902 gz.anon_name_strategy = prev_anon_name_strategy;
69236903
6924 // TODO: This should be almost identical for every break/ret
6925 switch (nodeMayEvalToError(tree, operand_node)) {6904 switch (nodeMayEvalToError(tree, operand_node)) {
6926 .never => {6905 .never => {
6927 // Returning a value that cannot be an error; skip error defers.6906 // Returning a value that cannot be an error; skip error defers.
6928 try genDefers(gz, defer_outer, scope, .normal_only);6907 try genDefers(gz, defer_outer, scope, .normal_only);
69296908
6930 // As our last action before the return, "pop" the error trace if needed6909 // As our last action before the return, "pop" the error trace if needed
6931 if (gz.outermost_err_trace_index != .none)6910 _ = try gz.addRestoreErrRetIndex(.ret, .always);
6932 _ = try gz.addUnNode(.restore_err_ret_index, gz.outermost_err_trace_index, node);
69336911
6934 try emitDbgStmt(gz, ret_line, ret_column);6912 try emitDbgStmt(gz, ret_line, ret_column);
6935 try gz.addRet(ri, operand, node);6913 try gz.addRet(ri, operand, node);
...@@ -6949,6 +6927,11 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref...@@ -6949,6 +6927,11 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
6949 // Only regular defers; no branch needed.6927 // Only regular defers; no branch needed.
6950 try genDefers(gz, defer_outer, scope, .normal_only);6928 try genDefers(gz, defer_outer, scope, .normal_only);
6951 try emitDbgStmt(gz, ret_line, ret_column);6929 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
6952 try gz.addRet(ri, operand, node);6935 try gz.addRet(ri, operand, node);
6953 return Zir.Inst.Ref.unreachable_value;6936 return Zir.Inst.Ref.unreachable_value;
6954 }6937 }
...@@ -6964,8 +6947,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref...@@ -6964,8 +6947,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
6964 try genDefers(&then_scope, defer_outer, scope, .normal_only);6947 try genDefers(&then_scope, defer_outer, scope, .normal_only);
69656948
6966 // As our last action before the return, "pop" the error trace if needed6949 // As our last action before the return, "pop" the error trace if needed
6967 if (then_scope.outermost_err_trace_index != .none)6950 _ = try then_scope.addRestoreErrRetIndex(.ret, .always);
6968 _ = try then_scope.addUnNode(.restore_err_ret_index, then_scope.outermost_err_trace_index, node);
69696951
6970 try emitDbgStmt(&then_scope, ret_line, ret_column);6952 try emitDbgStmt(&then_scope, ret_line, ret_column);
6971 try then_scope.addRet(ri, operand, node);6953 try then_scope.addRet(ri, operand, node);
...@@ -8561,10 +8543,11 @@ fn callExpr(...@@ -8561,10 +8543,11 @@ fn callExpr(
8561 scratch_index += 1;8543 scratch_index += 1;
8562 }8544 }
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.
8565 // Otherwise, it should always be popped (handled in Sema).8548 // Otherwise, it should always be popped (handled in Sema).
8566 const propagate_error_trace = switch (ri.ctx) {8549 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 calls8550 .error_handling_expr, .@"return", .fn_arg, .const_init => true,
8568 else => false,8551 else => false,
8569 };8552 };
85708553
...@@ -8932,6 +8915,33 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_...@@ -8932,6 +8915,33 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_
8932 }8915 }
8933}8916}
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
8935fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.EvalToError {8945fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.EvalToError {
8936 const node_tags = tree.nodes.items(.tag);8946 const node_tags = tree.nodes.items(.tag);
8937 const node_datas = tree.nodes.items(.data);8947 const node_datas = tree.nodes.items(.data);
...@@ -10494,13 +10504,6 @@ const GenZir = struct {...@@ -10494,13 +10504,6 @@ const GenZir = struct {
10494 /// Keys are the raw instruction index, values are the closure_capture instruction.10504 /// Keys are the raw instruction index, values are the closure_capture instruction.
10495 captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{},10505 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
10504 const unstacked_top = std.math.maxInt(usize);10507 const unstacked_top = std.math.maxInt(usize);
10505 /// Call unstack before adding any new instructions to containing GenZir.10508 /// Call unstack before adding any new instructions to containing GenZir.
10506 fn unstack(self: *GenZir) void {10509 fn unstack(self: *GenZir) void {
...@@ -10545,7 +10548,6 @@ const GenZir = struct {...@@ -10545,7 +10548,6 @@ const GenZir = struct {
10545 .any_defer_node = gz.any_defer_node,10548 .any_defer_node = gz.any_defer_node,
10546 .instructions = gz.instructions,10549 .instructions = gz.instructions,
10547 .instructions_top = gz.instructions.items.len,10550 .instructions_top = gz.instructions.items.len,
10548 .outermost_err_trace_index = gz.outermost_err_trace_index,
10549 };10551 };
10550 }10552 }
1055110553
...@@ -11359,6 +11361,46 @@ const GenZir = struct {...@@ -11359,6 +11361,46 @@ const GenZir = struct {
11359 });11361 });
11360 }11362 }
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
11362 fn addBreak(11404 fn addBreak(
11363 gz: *GenZir,11405 gz: *GenZir,
11364 tag: Zir.Inst.Tag,11406 tag: Zir.Inst.Tag,
src/Module.zig+7
...@@ -5633,6 +5633,13 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {...@@ -5633,6 +5633,13 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
56335633
5634 const last_arg_index = inner_block.instructions.items.len;5634 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
5636 sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {5643 sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {
5637 // TODO make these unreachable instead of @panic5644 // TODO make these unreachable instead of @panic
5638 error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"),5645 error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"),
src/Sema.zig+147-70
...@@ -153,6 +153,12 @@ pub const Block = struct {...@@ -153,6 +153,12 @@ pub const Block = struct {
153 is_typeof: bool = false,153 is_typeof: bool = false,
154 is_coerce_result_ptr: bool = false,154 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
156 /// when null, it is determined by build mode, changed by @setRuntimeSafety162 /// when null, it is determined by build mode, changed by @setRuntimeSafety
157 want_safety: ?bool = null,163 want_safety: ?bool = null,
158164
...@@ -226,6 +232,9 @@ pub const Block = struct {...@@ -226,6 +232,9 @@ pub const Block = struct {
226 .float_mode = parent.float_mode,232 .float_mode = parent.float_mode,
227 .c_import_buf = parent.c_import_buf,233 .c_import_buf = parent.c_import_buf,
228 .switch_else_err_ty = parent.switch_else_err_ty,234 .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,
229 };238 };
230 }239 }
231240
...@@ -945,8 +954,6 @@ fn analyzeBodyInner(...@@ -945,8 +954,6 @@ fn analyzeBodyInner(
945 .ret_ptr => try sema.zirRetPtr(block, inst),954 .ret_ptr => try sema.zirRetPtr(block, inst),
946 .ret_type => try sema.addType(sema.fn_ret_ty),955 .ret_type => try sema.addType(sema.fn_ret_ty),
947956
948 .save_err_ret_index => try sema.zirSaveErrRetIndex(block, inst),
949
950 // Instructions that we know to *always* be noreturn based solely on their tag.957 // Instructions that we know to *always* be noreturn based solely on their tag.
951 // These functions match the return type of analyzeBody so that we can958 // These functions match the return type of analyzeBody so that we can
952 // tail call them here.959 // tail call them here.
...@@ -1229,6 +1236,11 @@ fn analyzeBodyInner(...@@ -1229,6 +1236,11 @@ fn analyzeBodyInner(
1229 i += 1;1236 i += 1;
1230 continue;1237 continue;
1231 },1238 },
1239 .save_err_ret_index => {
1240 try sema.zirSaveErrRetIndex(block, inst);
1241 i += 1;
1242 continue;
1243 },
1232 .restore_err_ret_index => {1244 .restore_err_ret_index => {
1233 try sema.zirRestoreErrRetIndex(block, inst);1245 try sema.zirRestoreErrRetIndex(block, inst);
1234 i += 1;1246 i += 1;
...@@ -1326,31 +1338,32 @@ fn analyzeBodyInner(...@@ -1326,31 +1338,32 @@ fn analyzeBodyInner(
1326 const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);1338 const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
1327 const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len];1339 const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len];
1328 const gpa = sema.gpa;1340 const gpa = sema.gpa;
1329 // If this block contains a function prototype, we need to reset the1341
1330 // current list of parameters and restore it later.1342 const opt_break_data = b: {
1331 // Note: this probably needs to be resolved in a more general manner.1343 // Create a temporary child block so that this inline block is properly
1332 const prev_params = block.params;1344 // labeled for any .restore_err_ret_index instructions
1333 const need_sub_block = tags[inline_body[inline_body.len - 1]] == .repeat_inline;1345 var child_block = block.makeSubBlock();
1334 var sub_block = block;1346
1335 var block_space: Block = undefined;1347 // If this block contains a function prototype, we need to reset the
1336 // NOTE: this has to be done like this because branching in1348 // current list of parameters and restore it later.
1337 // defers here breaks stage1.1349 // Note: this probably needs to be resolved in a more general manner.
1338 block_space.instructions = .{};1350 if (tags[inline_body[inline_body.len - 1]] == .repeat_inline) {
1339 if (need_sub_block) {1351 child_block.inline_block = inline_body[0];
1340 block_space = block.makeSubBlock();1352 } else child_block.inline_block = block.inline_block;
1341 block_space.inline_block = inline_body[0];1353
1342 sub_block = &block_space;1354 var label: Block.Label = .{
1343 }1355 .zir_block = inst,
1344 block.params = .{};1356 .merges = undefined,
1345 defer {1357 };
1346 block.params.deinit(gpa);1358 child_block.label = &label;
1347 block.params = prev_params;1359 defer child_block.params.deinit(gpa);
1348 block_space.instructions.deinit(gpa);1360
1349 }1361 // Write these instructions directly into the parent block
1350 const opt_break_data = try sema.analyzeBodyBreak(sub_block, inline_body);1362 child_block.instructions = block.instructions;
1351 if (need_sub_block) {1363 defer block.instructions = child_block.instructions;
1352 try block.instructions.appendSlice(gpa, block_space.instructions.items);1364
1353 }1365 break :b try sema.analyzeBodyBreak(&child_block, inline_body);
1366 };
13541367
1355 // A runtime conditional branch that needs a post-hoc block to be1368 // A runtime conditional branch that needs a post-hoc block to be
1356 // emitted communicates this by mapping the block index into the inst map.1369 // 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...@@ -4994,7 +5007,7 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro
49945007
4995 // Reserve space for a Block instruction so that generated Break instructions can5008 // Reserve space for a Block instruction so that generated Break instructions can
4996 // point to it, even if it doesn't end up getting used because the code ends up being5009 // 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.
4998 const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);5011 const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
4999 try sema.air_instructions.append(gpa, .{5012 try sema.air_instructions.append(gpa, .{
5000 .tag = .block,5013 .tag = .block,
...@@ -5025,6 +5038,9 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro...@@ -5025,6 +5038,9 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro
5025 .runtime_cond = parent_block.runtime_cond,5038 .runtime_cond = parent_block.runtime_cond,
5026 .runtime_loop = parent_block.runtime_loop,5039 .runtime_loop = parent_block.runtime_loop,
5027 .runtime_index = parent_block.runtime_index,5040 .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,
5028 };5044 };
50295045
5030 defer child_block.instructions.deinit(gpa);5046 defer child_block.instructions.deinit(gpa);
...@@ -5667,19 +5683,51 @@ fn funcDeclSrc(sema: *Sema, block: *Block, src: LazySrcLoc, func_inst: Air.Inst....@@ -5667,19 +5683,51 @@ fn funcDeclSrc(sema: *Sema, block: *Block, src: LazySrcLoc, func_inst: Air.Inst.
5667 return owner_decl.srcLoc();5683 return owner_decl.srcLoc();
5668}5684}
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
5670/// Add instructions to block to "pop" the error return trace.5718/// Add instructions to block to "pop" the error return trace.
5671/// If `operand` is provided, only pops if operand is non-error.5719/// If `operand` is provided, only pops if operand is non-error.
5672fn popErrorReturnTrace(5720fn popErrorReturnTrace(
5673 sema: *Sema,5721 sema: *Sema,
5674 block: *Block,5722 block: *Block,
5675 src: LazySrcLoc,5723 src: LazySrcLoc,
5676 operand: ?Air.Inst.Ref,5724 operand: Air.Inst.Ref,
5677 saved_error_trace_index: Air.Inst.Ref,5725 saved_error_trace_index: Air.Inst.Ref,
5678) CompileError!void {5726) CompileError!void {
5679 var is_non_error: ?bool = null;5727 var is_non_error: ?bool = null;
5680 var is_non_error_inst: Air.Inst.Ref = undefined;5728 var is_non_error_inst: Air.Inst.Ref = undefined;
5681 if (operand) |op| {5729 if (operand != .none) {
5682 is_non_error_inst = try sema.analyzeIsNonErr(block, src, op);5730 is_non_error_inst = try sema.analyzeIsNonErr(block, src, operand);
5683 if (try sema.resolveDefinedValue(block, src, is_non_error_inst)) |cond_val|5731 if (try sema.resolveDefinedValue(block, src, is_non_error_inst)) |cond_val|
5684 is_non_error = cond_val.toBool();5732 is_non_error = cond_val.toBool();
5685 } else is_non_error = true; // no operand means pop unconditionally5733 } else is_non_error = true; // no operand means pop unconditionally
...@@ -5906,7 +5954,7 @@ fn zirCall(...@@ -5906,7 +5954,7 @@ fn zirCall(
5906 });5954 });
59075955
5908 // Pop the error return trace, testing the result for non-error if necessary5956 // 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;
5910 try sema.popErrorReturnTrace(block, call_src, operand, save_inst);5958 try sema.popErrorReturnTrace(block, call_src, operand, save_inst);
5911 }5959 }
59125960
...@@ -6221,6 +6269,9 @@ fn analyzeCall(...@@ -6221,6 +6269,9 @@ fn analyzeCall(
6221 .label = null,6269 .label = null,
6222 .inlining = &inlining,6270 .inlining = &inlining,
6223 .is_comptime = is_comptime_call,6271 .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,
6224 };6275 };
62256276
6226 const merges = &child_block.inlining.?.merges;6277 const merges = &child_block.inlining.?.merges;
...@@ -6966,6 +7017,14 @@ fn instantiateGenericCall(...@@ -6966,6 +7017,14 @@ fn instantiateGenericCall(
6966 }7017 }
6967 arg_i += 1;7018 arg_i += 1;
6968 }7019 }
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
6969 const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| {7028 const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| {
6970 // TODO look up the compile error that happened here and attach a note to it7029 // TODO look up the compile error that happened here and attach a note to it
6971 // pointing here, at the generic instantiation callsite.7030 // pointing here, at the generic instantiation callsite.
...@@ -9855,6 +9914,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -9855,6 +9914,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
9855 .defer_err_code,9914 .defer_err_code,
9856 .err_union_code,9915 .err_union_code,
9857 .ret_err_value_code,9916 .ret_err_value_code,
9917 .restore_err_ret_index,
9858 .is_non_err,9918 .is_non_err,
9859 .condbr,9919 .condbr,
9860 => {},9920 => {},
...@@ -10157,6 +10217,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -10157,6 +10217,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
10157 .runtime_cond = block.runtime_cond,10217 .runtime_cond = block.runtime_cond,
10158 .runtime_loop = block.runtime_loop,10218 .runtime_loop = block.runtime_loop,
10159 .runtime_index = block.runtime_index,10219 .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,
10160 };10223 };
10161 const merges = &child_block.label.?.merges;10224 const merges = &child_block.label.?.merges;
10162 defer child_block.instructions.deinit(gpa);10225 defer child_block.instructions.deinit(gpa);
...@@ -11040,6 +11103,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op...@@ -11040,6 +11103,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
11040 const tags = sema.code.instructions.items(.tag);11103 const tags = sema.code.instructions.items(.tag);
11041 for (body) |inst| {11104 for (body) |inst| {
11042 switch (tags[inst]) {11105 switch (tags[inst]) {
11106 .save_err_ret_index,
11043 .dbg_block_begin,11107 .dbg_block_begin,
11044 .dbg_block_end,11108 .dbg_block_end,
11045 .dbg_stmt,11109 .dbg_stmt,
...@@ -11062,6 +11126,10 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op...@@ -11062,6 +11126,10 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
11062 try sema.zirDbgStmt(block, inst);11126 try sema.zirDbgStmt(block, inst);
11063 continue;11127 continue;
11064 },11128 },
11129 .save_err_ret_index => {
11130 try sema.zirSaveErrRetIndex(block, inst);
11131 continue;
11132 },
11065 .str => try sema.zirStr(block, inst),11133 .str => try sema.zirStr(block, inst),
11066 .as_node => try sema.zirAsNode(block, inst),11134 .as_node => try sema.zirAsNode(block, inst),
11067 .field_val => try sema.zirFieldVal(block, inst),11135 .field_val => try sema.zirFieldVal(block, inst),
...@@ -15672,6 +15740,9 @@ fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr...@@ -15672,6 +15740,9 @@ fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
15672 .is_comptime = false,15740 .is_comptime = false,
15673 .is_typeof = true,15741 .is_typeof = true,
15674 .want_safety = false,15742 .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,
15675 };15746 };
15676 defer child_block.instructions.deinit(sema.gpa);15747 defer child_block.instructions.deinit(sema.gpa);
1567715748
...@@ -16329,43 +16400,35 @@ fn wantErrorReturnTracing(sema: *Sema, fn_ret_ty: Type) bool {...@@ -16329,43 +16400,35 @@ fn wantErrorReturnTracing(sema: *Sema, fn_ret_ty: Type) bool {
16329 backend_supports_error_return_tracing;16400 backend_supports_error_return_tracing;
16330}16401}
1633116402
16332fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {16403fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
16333 const inst_data = sema.code.instructions.items(.data)[inst].node;16404 const inst_data = sema.code.instructions.items(.data)[inst].save_err_ret_index;
16334 const src = LazySrcLoc.nodeOffset(inst_data);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
16336 // This is only relevant at runtime.16410 // 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;16413 // This is only relevant within functions.
16340 const ok = sema.mod.comp.bin_file.options.error_return_tracing and16414 if (sema.func == null) return;
16341 backend_supports_error_return_tracing;
16342 if (!ok) return Air.Inst.Ref.zero_usize;
1634316415
16344 // This is encoded as a primitive AIR instruction to resolve one corner case: A function16416 const save_index = inst_data.operand == .none or b: {
16345 // may include a `catch { ... }` or `else |err| { ... }` block but not call any errorable16417 const operand = try sema.resolveInst(inst_data.operand);
16346 // fn. In that case, there is no error return trace to save the index of and codegen needs16418 const operand_ty = sema.typeOf(operand);
16347 // to avoid interacting with the non-existing error trace.16419 break :b operand_ty.isError();
16348 //16420 };
16349 // By using a primitive AIR op, we can depend on Liveness to mark this unused in this corner case.
1635016421
16351 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");16422 if (save_index)
16352 const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);16423 block.error_return_trace_index = try sema.analyzeSaveErrRetIndex(block);
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 });
16361}16424}
1636216425
16363fn zirRestoreErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {16426fn zirRestoreErrRetIndex(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
16364 const inst_data = sema.code.instructions.items(.data)[inst].un_node;16427 const inst_data = sema.code.instructions.items(.data)[inst].restore_err_ret_index;
16365 const src = inst_data.src();16428 const src = sema.src; // TODO
1636616429
16367 // This is only relevant at runtime.16430 // This is only relevant at runtime.
16368 if (block.is_comptime) return;16431 if (start_block.is_comptime) return;
1636916432
16370 const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;16433 const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
16371 const ok = sema.owner_func.?.calls_or_awaits_errorable_fn and16434 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...@@ -16373,17 +16436,31 @@ fn zirRestoreErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi
16373 backend_supports_error_return_tracing;16436 backend_supports_error_return_tracing;
16374 if (!ok) return;16437 if (!ok) return;
1637516438
16376 const operand = if (inst_data.operand != .none)16439 const tracy = trace(@src());
16377 try sema.resolveInst(inst_data.operand)16440 defer tracy.end();
16378 else
16379 .zero_usize;
1638016441
16381 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");16442 const saved_index = if (Zir.refToIndex(inst_data.block)) |zir_block| b: {
16382 const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);16443 var block = start_block;
16383 const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);16444 while (true) {
16384 const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);16445 if (block.label) |label| {
16385 const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, "index", src, stack_trace_ty, true);16446 if (label.zir_block == zir_block) {
16386 try sema.storePtr2(block, src, field_ptr, src, operand, src, .store);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);
16387}16464}
1638816465
16389fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void {16466fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void {
src/Zir.zig+16-7
...@@ -988,13 +988,13 @@ pub const Inst = struct {...@@ -988,13 +988,13 @@ pub const Inst = struct {
988 /// Uses the `err_defer_code` union field.988 /// Uses the `err_defer_code` union field.
989 defer_err_code,989 defer_err_code,
990990
991 /// Saves the current error return case if it exists,991 /// Requests that Sema update the saved error return trace index for the enclosing
992 /// otherwise just returns zero.992 /// block, if the operand is .none or of an error/error-union type.
993 /// Uses the `node` union field.993 /// Uses the `save_err_ret_index` field.
994 save_err_ret_index,994 save_err_ret_index,
995 /// Sets error return trace to zero if no operand is given,995 /// Sets error return trace to zero if no operand is given,
996 /// otherwise sets the value to the given amount.996 /// otherwise sets the value to the given amount.
997 /// Uses the `un_node` union field.997 /// Uses the `restore_err_ret_index` union field.
998 restore_err_ret_index,998 restore_err_ret_index,
999999
1000 /// The ZIR instruction tag is one of the `Extended` ones.1000 /// The ZIR instruction tag is one of the `Extended` ones.
...@@ -1317,6 +1317,7 @@ pub const Inst = struct {...@@ -1317,6 +1317,7 @@ pub const Inst = struct {
1317 .@"defer",1317 .@"defer",
1318 .defer_err_code,1318 .defer_err_code,
1319 .restore_err_ret_index,1319 .restore_err_ret_index,
1320 .save_err_ret_index,
1320 => true,1321 => true,
13211322
1322 .param,1323 .param,
...@@ -1542,7 +1543,6 @@ pub const Inst = struct {...@@ -1542,7 +1543,6 @@ pub const Inst = struct {
1542 .try_ptr,1543 .try_ptr,
1543 //.try_inline,1544 //.try_inline,
1544 //.try_ptr_inline,1545 //.try_ptr_inline,
1545 .save_err_ret_index,
1546 => false,1546 => false,
15471547
1548 .extended => switch (data.extended.opcode) {1548 .extended => switch (data.extended.opcode) {
...@@ -1823,8 +1823,8 @@ pub const Inst = struct {...@@ -1823,8 +1823,8 @@ pub const Inst = struct {
1823 .@"defer" = .@"defer",1823 .@"defer" = .@"defer",
1824 .defer_err_code = .defer_err_code,1824 .defer_err_code = .defer_err_code,
18251825
1826 .save_err_ret_index = .node,1826 .save_err_ret_index = .save_err_ret_index,
1827 .restore_err_ret_index = .un_node,1827 .restore_err_ret_index = .restore_err_ret_index,
18281828
1829 .extended = .extended,1829 .extended = .extended,
1830 });1830 });
...@@ -2602,6 +2602,13 @@ pub const Inst = struct {...@@ -2602,6 +2602,13 @@ pub const Inst = struct {
2602 err_code: Ref,2602 err_code: Ref,
2603 payload_index: u32,2603 payload_index: u32,
2604 },2604 },
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
2606 // Make sure we don't accidentally add a field to make this union2613 // Make sure we don't accidentally add a field to make this union
2607 // bigger than expected. Note that in Debug builds, Zig is allowed2614 // bigger than expected. Note that in Debug builds, Zig is allowed
...@@ -2640,6 +2647,8 @@ pub const Inst = struct {...@@ -2640,6 +2647,8 @@ pub const Inst = struct {
2640 str_op,2647 str_op,
2641 @"defer",2648 @"defer",
2642 defer_err_code,2649 defer_err_code,
2650 save_err_ret_index,
2651 restore_err_ret_index,
2643 };2652 };
2644 };2653 };
26452654
src/print_zir.zig+19-2
...@@ -232,7 +232,6 @@ const Writer = struct {...@@ -232,7 +232,6 @@ const Writer = struct {
232 .validate_deref,232 .validate_deref,
233 .overflow_arithmetic_ptr,233 .overflow_arithmetic_ptr,
234 .check_comptime_control_flow,234 .check_comptime_control_flow,
235 .restore_err_ret_index,
236 => try self.writeUnNode(stream, inst),235 => try self.writeUnNode(stream, inst),
237236
238 .ref,237 .ref,
...@@ -255,6 +254,9 @@ const Writer = struct {...@@ -255,6 +254,9 @@ const Writer = struct {
255 .str => try self.writeStr(stream, inst),254 .str => try self.writeStr(stream, inst),
256 .int_type => try self.writeIntType(stream, inst),255 .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
258 .@"break",260 .@"break",
259 .break_inline,261 .break_inline,
260 => try self.writeBreak(stream, inst),262 => try self.writeBreak(stream, inst),
...@@ -406,7 +408,6 @@ const Writer = struct {...@@ -406,7 +408,6 @@ const Writer = struct {
406 .alloc_inferred_comptime_mut,408 .alloc_inferred_comptime_mut,
407 .ret_ptr,409 .ret_ptr,
408 .ret_type,410 .ret_type,
409 .save_err_ret_index,
410 => try self.writeNode(stream, inst),411 => try self.writeNode(stream, inst),
411412
412 .error_value,413 .error_value,
...@@ -2274,6 +2275,22 @@ const Writer = struct {...@@ -2274,6 +2275,22 @@ const Writer = struct {
2274 try self.writeSrc(stream, int_type.src());2275 try self.writeSrc(stream, int_type.src());
2275 }2276 }
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
2277 fn writeBreak(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {2294 fn writeBreak(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
2278 const inst_data = self.code.instructions.items(.data)[inst].@"break";2295 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" {...@@ -830,3 +830,16 @@ test "compare error union and error set" {
830 try expect(a != b);830 try expect(a != b);
831 try expect(b != a);831 try expect(b != a);
832}832}
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 {...@@ -97,6 +97,59 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
97 ,97 ,
98 },98 },
99 });99 });
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
101 cases.addCase(.{154 cases.addCase(.{
102 .name = "try return + handled catch/if-else",155 .name = "try return + handled catch/if-else",
...@@ -155,6 +208,59 @@ pub fn addCases(cases: *tests.StackTracesContext) void {...@@ -155,6 +208,59 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
155 },208 },
156 });209 });
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
158 cases.addCase(.{264 cases.addCase(.{
159 .name = "catch and re-throw error",265 .name = "catch and re-throw error",
160 .source = 266 .source =
...@@ -209,7 +315,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {...@@ -209,7 +315,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
209 });315 });
210316
211 cases.addCase(.{317 cases.addCase(.{
212 .name = "stored errors do not contribute to error trace",318 .name = "errors stored in var do not contribute to error trace",
213 .source = 319 .source =
214 \\fn foo() !void {320 \\fn foo() !void {
215 \\ return error.TheSkyIsFalling;321 \\ return error.TheSkyIsFalling;
...@@ -260,6 +366,82 @@ pub fn addCases(cases: *tests.StackTracesContext) void {...@@ -260,6 +366,82 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
260 },366 },
261 });367 });
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
263 cases.addCase(.{445 cases.addCase(.{
264 .name = "error passed to function has its trace preserved for duration of the call",446 .name = "error passed to function has its trace preserved for duration of the call",
265 .source = 447 .source =