Skip to content

Commit 747ed9f

Browse files
committed
stage2: "Pop" error trace for break/return within catch
This implement trace "popping" for correctly handled errors within `catch { ... }` and `else { ... }` blocks. When breaking from these blocks with any non-error, we pop the error trace frames corresponding to the operand. When breaking with an error, we preserve the frames so that error traces "chain" together as usual. ```zig fn foo(cond1: bool, cond2: bool) !void { bar() catch { if (cond1) { // If baz() result is a non-error, pop the error trace frames from bar() // If baz() result is an error, leave the bar() frames on the error trace return baz(); } else if (cond2) { // If we break/return an error, then leave the error frames from bar() on the error trace return error.Foo; } }; // An error returned from here does not include bar()'s error frames in the trace return error.Bar; } ``` Notice that if foo() does not return an error it, it leaves no extra frames on the error trace. This is piece (1/3) of #1923 (comment)
1 parent b2e990f commit 747ed9f

File tree

3 files changed

+301
-29
lines changed

3 files changed

+301
-29
lines changed

src/AstGen.zig

Lines changed: 108 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,13 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
18191819
const break_label = node_datas[node].lhs;
18201820
const rhs = node_datas[node].rhs;
18211821

1822+
// Breaking out of a `catch { ... }` or `else |err| { ... }` block with a non-error value
1823+
// means that the corresponding error was correctly handled, and the error trace index
1824+
// needs to be restored so that any entries from the caught error are effectively "popped"
1825+
//
1826+
// Note: We only restore for the outermost block, since that will "pop" any nested blocks.
1827+
var err_trace_index_to_restore: Zir.Inst.Ref = .none;
1828+
18221829
// Look for the label in the scope.
18231830
var scope = parent_scope;
18241831
while (true) {
@@ -1827,6 +1834,7 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
18271834
const block_gz = scope.cast(GenZir).?;
18281835

18291836
if (block_gz.cur_defer_node != 0) {
1837+
// We are breaking out of a `defer` block.
18301838
return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
18311839
try astgen.errNoteNode(
18321840
block_gz.cur_defer_node,
@@ -1836,6 +1844,11 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
18361844
});
18371845
}
18381846

1847+
if (block_gz.saved_err_trace_index != .none) {
1848+
// We are breaking out of a `catch { ... }` or `else |err| { ... }`.
1849+
err_trace_index_to_restore = block_gz.saved_err_trace_index;
1850+
}
1851+
18391852
const block_inst = blk: {
18401853
if (break_label != 0) {
18411854
if (block_gz.label) |*label| {
@@ -1847,9 +1860,11 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
18471860
} else if (block_gz.break_block != 0) {
18481861
break :blk block_gz.break_block;
18491862
}
1863+
// If not the target, start over with the parent
18501864
scope = block_gz.parent;
18511865
continue;
18521866
};
1867+
// If we made it here, this block is the target of the break expr
18531868

18541869
const break_tag: Zir.Inst.Tag = if (block_gz.is_inline or block_gz.force_comptime)
18551870
.break_inline
@@ -1859,6 +1874,19 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
18591874
if (rhs == 0) {
18601875
try genDefers(parent_gz, scope, parent_scope, .normal_only);
18611876

1877+
// As our last action before the break, "pop" the error trace if needed
1878+
if (err_trace_index_to_restore != .none) {
1879+
// TODO: error-liveness and is_non_err
1880+
1881+
_ = try parent_gz.add(.{
1882+
.tag = .restore_err_ret_index,
1883+
.data = .{ .un_node = .{
1884+
.operand = err_trace_index_to_restore,
1885+
.src_node = parent_gz.nodeIndexToRelative(node),
1886+
} },
1887+
});
1888+
}
1889+
18621890
_ = try parent_gz.addBreak(break_tag, block_inst, .void_value);
18631891
return Zir.Inst.Ref.unreachable_value;
18641892
}
@@ -1876,6 +1904,19 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
18761904

18771905
try genDefers(parent_gz, scope, parent_scope, .normal_only);
18781906

1907+
// As our last action before the break, "pop" the error trace if needed
1908+
if (err_trace_index_to_restore != .none) {
1909+
// TODO: error-liveness and is_non_err
1910+
1911+
_ = try parent_gz.add(.{
1912+
.tag = .restore_err_ret_index,
1913+
.data = .{ .un_node = .{
1914+
.operand = err_trace_index_to_restore,
1915+
.src_node = parent_gz.nodeIndexToRelative(node),
1916+
} },
1917+
});
1918+
}
1919+
18791920
switch (block_gz.break_result_loc) {
18801921
.block_ptr => {
18811922
const br = try parent_gz.addBreak(break_tag, block_inst, operand);
@@ -5146,9 +5187,7 @@ fn orelseCatchExpr(
51465187
block_scope.setBreakResultLoc(rl);
51475188
defer block_scope.unstack();
51485189

5149-
if (do_err_trace) {
5150-
block_scope.saved_err_trace_index = try parent_gz.addNode(.save_err_ret_index, node);
5151-
}
5190+
const saved_err_trace_index = if (do_err_trace) try parent_gz.addNode(.save_err_ret_index, node) else .none;
51525191

51535192
const operand_rl: ResultLoc = switch (block_scope.break_result_loc) {
51545193
.ref => .ref,
@@ -5181,6 +5220,12 @@ fn orelseCatchExpr(
51815220
var else_scope = block_scope.makeSubBlock(scope);
51825221
defer else_scope.unstack();
51835222

5223+
// Any break (of a non-error value) that navigates out of this scope means
5224+
// that the error was handled successfully, so this index will be restored.
5225+
else_scope.saved_err_trace_index = saved_err_trace_index;
5226+
if (else_scope.outermost_err_trace_index == .none)
5227+
else_scope.outermost_err_trace_index = saved_err_trace_index;
5228+
51845229
var err_val_scope: Scope.LocalVal = undefined;
51855230
const else_sub_scope = blk: {
51865231
const payload = payload_token orelse break :blk &else_scope.base;
@@ -5206,6 +5251,17 @@ fn orelseCatchExpr(
52065251
const else_result = try expr(&else_scope, else_sub_scope, block_scope.break_result_loc, rhs);
52075252
if (!else_scope.endsWithNoReturn()) {
52085253
block_scope.break_count += 1;
5254+
5255+
// TODO: Add is_non_err and break check
5256+
if (do_err_trace) {
5257+
_ = try else_scope.add(.{
5258+
.tag = .restore_err_ret_index,
5259+
.data = .{ .un_node = .{
5260+
.operand = saved_err_trace_index,
5261+
.src_node = parent_gz.nodeIndexToRelative(node),
5262+
} },
5263+
});
5264+
}
52095265
}
52105266
try checkUsed(parent_gz, &else_scope.base, else_sub_scope);
52115267

@@ -5229,15 +5285,6 @@ fn orelseCatchExpr(
52295285
block,
52305286
break_tag,
52315287
);
5232-
if (do_err_trace) {
5233-
_ = try parent_gz.add(.{
5234-
.tag = .restore_err_ret_index,
5235-
.data = .{ .un_node = .{
5236-
.operand = parent_gz.saved_err_trace_index,
5237-
.src_node = parent_gz.nodeIndexToRelative(node),
5238-
} },
5239-
});
5240-
}
52415288
return result;
52425289
}
52435290

@@ -5440,9 +5487,7 @@ fn ifExpr(
54405487
block_scope.setBreakResultLoc(rl);
54415488
defer block_scope.unstack();
54425489

5443-
if (do_err_trace) {
5444-
block_scope.saved_err_trace_index = try parent_gz.addNode(.save_err_ret_index, node);
5445-
}
5490+
const saved_err_trace_index = if (do_err_trace) try parent_gz.addNode(.save_err_ret_index, node) else .none;
54465491

54475492
const payload_is_ref = if (if_full.payload_token) |payload_token|
54485493
token_tags[payload_token] == .asterisk
@@ -5559,6 +5604,12 @@ fn ifExpr(
55595604
var else_scope = parent_gz.makeSubBlock(scope);
55605605
defer else_scope.unstack();
55615606

5607+
// Any break (of a non-error value) that navigates out of this scope means
5608+
// that the error was handled successfully, so this index will be restored.
5609+
else_scope.saved_err_trace_index = saved_err_trace_index;
5610+
if (else_scope.outermost_err_trace_index == .none)
5611+
else_scope.outermost_err_trace_index = saved_err_trace_index;
5612+
55625613
const else_node = if_full.ast.else_expr;
55635614
const else_info: struct {
55645615
src: Ast.Node.Index,
@@ -5606,6 +5657,18 @@ fn ifExpr(
56065657
.result = .none,
56075658
};
56085659

5660+
if (do_err_trace and !else_scope.endsWithNoReturn()) {
5661+
// TODO: is_non_err and other checks
5662+
5663+
_ = try else_scope.add(.{
5664+
.tag = .restore_err_ret_index,
5665+
.data = .{ .un_node = .{
5666+
.operand = saved_err_trace_index,
5667+
.src_node = parent_gz.nodeIndexToRelative(node),
5668+
} },
5669+
});
5670+
}
5671+
56095672
const break_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .break_inline else .@"break";
56105673
const result = try finishThenElseBlock(
56115674
parent_gz,
@@ -5622,15 +5685,6 @@ fn ifExpr(
56225685
block,
56235686
break_tag,
56245687
);
5625-
if (do_err_trace) {
5626-
_ = try parent_gz.add(.{
5627-
.tag = .restore_err_ret_index,
5628-
.data = .{ .un_node = .{
5629-
.operand = parent_gz.saved_err_trace_index,
5630-
.src_node = parent_gz.nodeIndexToRelative(node),
5631-
} },
5632-
});
5633-
}
56345688
return result;
56355689
}
56365690

@@ -6710,11 +6764,24 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
67106764
const operand = try reachableExpr(gz, scope, rl, operand_node, node);
67116765
gz.anon_name_strategy = prev_anon_name_strategy;
67126766

6767+
// TODO: This should be almost identical for every break/ret
67136768
switch (nodeMayEvalToError(tree, operand_node)) {
67146769
.never => {
67156770
// Returning a value that cannot be an error; skip error defers.
67166771
try genDefers(gz, defer_outer, scope, .normal_only);
67176772
try emitDbgStmt(gz, ret_line, ret_column);
6773+
6774+
// As our last action before the return, "pop" the error trace if needed
6775+
if (gz.outermost_err_trace_index != .none) {
6776+
_ = try gz.add(.{
6777+
.tag = .restore_err_ret_index,
6778+
.data = .{ .un_node = .{
6779+
.operand = gz.outermost_err_trace_index,
6780+
.src_node = gz.nodeIndexToRelative(node),
6781+
} },
6782+
});
6783+
}
6784+
67186785
try gz.addRet(rl, operand, node);
67196786
return Zir.Inst.Ref.unreachable_value;
67206787
},
@@ -6756,6 +6823,17 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
67566823
};
67576824
try genDefers(&else_scope, defer_outer, scope, which_ones);
67586825
try emitDbgStmt(&else_scope, ret_line, ret_column);
6826+
6827+
// As our last action before the return, "pop" the error trace if needed
6828+
if (else_scope.outermost_err_trace_index != .none) {
6829+
_ = try else_scope.add(.{
6830+
.tag = .restore_err_ret_index,
6831+
.data = .{ .un_node = .{
6832+
.operand = else_scope.outermost_err_trace_index,
6833+
.src_node = else_scope.nodeIndexToRelative(node),
6834+
} },
6835+
});
6836+
}
67596837
try else_scope.addRet(rl, operand, node);
67606838

67616839
try setCondBrPayload(condbr, is_non_err, &then_scope, 0, &else_scope, 0);
@@ -10225,7 +10303,12 @@ const GenZir = struct {
1022510303
/// Keys are the raw instruction index, values are the closure_capture instruction.
1022610304
captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{},
1022710305

10306+
/// If this GenZir corresponds to a `catch { ... }` or `else |err| { ... }` block,
10307+
/// this err_trace_index can be restored to "pop" the trace entries for the block.
1022810308
saved_err_trace_index: Zir.Inst.Ref = .none,
10309+
/// When returning from a function with a non-error, we must pop all trace entries
10310+
/// from any containing `catch { ... }` or `else |err| { ... }` blocks.
10311+
outermost_err_trace_index: Zir.Inst.Ref = .none,
1022910312

1023010313
const unstacked_top = std.math.maxInt(usize);
1023110314
/// Call unstack before adding any new instructions to containing GenZir.
@@ -10271,7 +10354,7 @@ const GenZir = struct {
1027110354
.any_defer_node = gz.any_defer_node,
1027210355
.instructions = gz.instructions,
1027310356
.instructions_top = gz.instructions.items.len,
10274-
.saved_err_trace_index = gz.saved_err_trace_index,
10357+
.outermost_err_trace_index = gz.outermost_err_trace_index,
1027510358
};
1027610359
}
1027710360

src/Sema.zig

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15598,9 +15598,14 @@ fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
1559815598
// This is only relevant at runtime.
1559915599
if (block.is_comptime) return Air.Inst.Ref.zero_usize;
1560015600

15601+
// In the corner case that `catch { ... }` or `else |err| { ... }` is used in a function
15602+
// that does *not* make any errorable calls, we still need an error trace to interact with
15603+
// the AIR instructions we've already emitted.
15604+
if (sema.owner_func != null)
15605+
sema.owner_func.?.calls_or_awaits_errorable_fn = true;
15606+
1560115607
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
15602-
const ok = sema.owner_func.?.calls_or_awaits_errorable_fn and
15603-
sema.mod.comp.bin_file.options.error_return_tracing and
15608+
const ok = sema.mod.comp.bin_file.options.error_return_tracing and
1560415609
backend_supports_error_return_tracing;
1560515610
if (!ok) return Air.Inst.Ref.zero_usize;
1560615611

@@ -15619,8 +15624,7 @@ fn zirRestoreErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi
1561915624
if (block.is_comptime) return;
1562015625

1562115626
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
15622-
const ok = sema.owner_func.?.calls_or_awaits_errorable_fn and
15623-
sema.mod.comp.bin_file.options.error_return_tracing and
15627+
const ok = sema.mod.comp.bin_file.options.error_return_tracing and
1562415628
backend_supports_error_return_tracing;
1562515629
if (!ok) return;
1562615630

0 commit comments

Comments
 (0)