Skip to content

Commit 85a3f9b

Browse files
authored
Merge pull request #12383 from ziglang/stage2-stack-traces
several improvements to error return tracing in the self-hosted compiler
2 parents c76b5c1 + feb90f6 commit 85a3f9b

File tree

4 files changed

+193
-24
lines changed

4 files changed

+193
-24
lines changed

lib/std/builtin.zig

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -867,10 +867,9 @@ pub fn panicOutOfBounds(index: usize, len: usize) noreturn {
867867
std.debug.panic("attempt to index out of bound: index {d}, len {d}", .{ index, len });
868868
}
869869

870-
pub noinline fn returnError(maybe_st: ?*StackTrace) void {
870+
pub noinline fn returnError(st: *StackTrace) void {
871871
@setCold(true);
872872
@setRuntimeSafety(false);
873-
const st = maybe_st orelse return;
874873
addErrRetTraceAddr(st, @returnAddress());
875874
}
876875

src/AstGen.zig

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3070,6 +3070,19 @@ fn emitDbgNode(gz: *GenZir, node: Ast.Node.Index) !void {
30703070
const line = astgen.source_line - gz.decl_line;
30713071
const column = astgen.source_column;
30723072

3073+
if (gz.instructions.items.len > 0) {
3074+
const last = gz.instructions.items[gz.instructions.items.len - 1];
3075+
const zir_tags = astgen.instructions.items(.tag);
3076+
if (zir_tags[last] == .dbg_stmt) {
3077+
const zir_datas = astgen.instructions.items(.data);
3078+
zir_datas[last].dbg_stmt = .{
3079+
.line = line,
3080+
.column = column,
3081+
};
3082+
return;
3083+
}
3084+
}
3085+
30733086
_ = try gz.add(.{ .tag = .dbg_stmt, .data = .{
30743087
.dbg_stmt = .{
30753088
.line = line,
@@ -5050,6 +5063,16 @@ fn tryExpr(
50505063

50515064
if (parent_gz.in_defer) return astgen.failNode(node, "'try' not allowed inside defer expression", .{});
50525065

5066+
// Ensure debug line/column information is emitted for this try expression.
5067+
// Then we will save the line/column so that we can emit another one that goes
5068+
// "backwards" because we want to evaluate the operand, but then put the debug
5069+
// info back at the try keyword for error return tracing.
5070+
if (!parent_gz.force_comptime) {
5071+
try emitDbgNode(parent_gz, node);
5072+
}
5073+
const try_line = astgen.source_line - parent_gz.decl_line;
5074+
const try_column = astgen.source_column;
5075+
50535076
const operand_rl: ResultLoc = switch (rl) {
50545077
.ref => .ref,
50555078
else => .none,
@@ -5079,6 +5102,7 @@ fn tryExpr(
50795102
};
50805103
const err_code = try else_scope.addUnNode(err_tag, operand, node);
50815104
try genDefers(&else_scope, &fn_block.base, scope, .{ .both = err_code });
5105+
try emitDbgStmt(&else_scope, try_line, try_column);
50825106
_ = try else_scope.addUnNode(.ret_node, err_code, node);
50835107

50845108
try else_scope.setTryBody(try_inst, operand);
@@ -6585,6 +6609,16 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
65856609

65866610
if (gz.in_defer) return astgen.failNode(node, "cannot return from defer expression", .{});
65876611

6612+
// Ensure debug line/column information is emitted for this return expression.
6613+
// Then we will save the line/column so that we can emit another one that goes
6614+
// "backwards" because we want to evaluate the operand, but then put the debug
6615+
// info back at the return keyword for error return tracing.
6616+
if (!gz.force_comptime) {
6617+
try emitDbgNode(gz, node);
6618+
}
6619+
const ret_line = astgen.source_line - gz.decl_line;
6620+
const ret_column = astgen.source_column;
6621+
65886622
const defer_outer = &astgen.fn_block.?.base;
65896623

65906624
const operand_node = node_datas[node].lhs;
@@ -6603,11 +6637,13 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
66036637
const defer_counts = countDefers(astgen, defer_outer, scope);
66046638
if (!defer_counts.need_err_code) {
66056639
try genDefers(gz, defer_outer, scope, .both_sans_err);
6640+
try emitDbgStmt(gz, ret_line, ret_column);
66066641
_ = try gz.addStrTok(.ret_err_value, err_name_str_index, ident_token);
66076642
return Zir.Inst.Ref.unreachable_value;
66086643
}
66096644
const err_code = try gz.addStrTok(.ret_err_value_code, err_name_str_index, ident_token);
66106645
try genDefers(gz, defer_outer, scope, .{ .both = err_code });
6646+
try emitDbgStmt(gz, ret_line, ret_column);
66116647
_ = try gz.addUnNode(.ret_node, err_code, node);
66126648
return Zir.Inst.Ref.unreachable_value;
66136649
}
@@ -6626,13 +6662,15 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
66266662
.never => {
66276663
// Returning a value that cannot be an error; skip error defers.
66286664
try genDefers(gz, defer_outer, scope, .normal_only);
6665+
try emitDbgStmt(gz, ret_line, ret_column);
66296666
try gz.addRet(rl, operand, node);
66306667
return Zir.Inst.Ref.unreachable_value;
66316668
},
66326669
.always => {
66336670
// Value is always an error. Emit both error defers and regular defers.
66346671
const err_code = if (rl == .ptr) try gz.addUnNode(.load, rl.ptr, node) else operand;
66356672
try genDefers(gz, defer_outer, scope, .{ .both = err_code });
6673+
try emitDbgStmt(gz, ret_line, ret_column);
66366674
try gz.addRet(rl, operand, node);
66376675
return Zir.Inst.Ref.unreachable_value;
66386676
},
@@ -6641,6 +6679,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
66416679
if (!defer_counts.have_err) {
66426680
// Only regular defers; no branch needed.
66436681
try genDefers(gz, defer_outer, scope, .normal_only);
6682+
try emitDbgStmt(gz, ret_line, ret_column);
66446683
try gz.addRet(rl, operand, node);
66456684
return Zir.Inst.Ref.unreachable_value;
66466685
}
@@ -6654,6 +6693,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
66546693
defer then_scope.unstack();
66556694

66566695
try genDefers(&then_scope, defer_outer, scope, .normal_only);
6696+
try emitDbgStmt(&then_scope, ret_line, ret_column);
66576697
try then_scope.addRet(rl, operand, node);
66586698

66596699
var else_scope = gz.makeSubBlock(scope);
@@ -6663,6 +6703,7 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
66636703
.both = try else_scope.addUnNode(.err_union_code, result, node),
66646704
};
66656705
try genDefers(&else_scope, defer_outer, scope, which_ones);
6706+
try emitDbgStmt(&else_scope, ret_line, ret_column);
66666707
try else_scope.addRet(rl, operand, node);
66676708

66686709
try setCondBrPayload(condbr, is_non_err, &then_scope, 0, &else_scope, 0);
@@ -11702,3 +11743,14 @@ fn countBodyLenAfterFixups(astgen: *AstGen, body: []const Zir.Inst.Index) u32 {
1170211743
}
1170311744
return @intCast(u32, count);
1170411745
}
11746+
11747+
fn emitDbgStmt(gz: *GenZir, line: u32, column: u32) !void {
11748+
if (gz.force_comptime) return;
11749+
11750+
_ = try gz.add(.{ .tag = .dbg_stmt, .data = .{
11751+
.dbg_stmt = .{
11752+
.line = line,
11753+
.column = column,
11754+
},
11755+
} });
11756+
}

src/Sema.zig

Lines changed: 139 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14489,6 +14489,20 @@ fn zirBoolBr(
1448914489
const rhs_result = try sema.resolveBody(rhs_block, body, inst);
1449014490
_ = try rhs_block.addBr(block_inst, rhs_result);
1449114491

14492+
return finishCondBr(sema, parent_block, &child_block, &then_block, &else_block, lhs, block_inst);
14493+
}
14494+
14495+
fn finishCondBr(
14496+
sema: *Sema,
14497+
parent_block: *Block,
14498+
child_block: *Block,
14499+
then_block: *Block,
14500+
else_block: *Block,
14501+
cond: Air.Inst.Ref,
14502+
block_inst: Air.Inst.Index,
14503+
) !Air.Inst.Ref {
14504+
const gpa = sema.gpa;
14505+
1449214506
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
1449314507
then_block.instructions.items.len + else_block.instructions.items.len +
1449414508
@typeInfo(Air.Block).Struct.fields.len + child_block.instructions.items.len + 1);
@@ -14501,7 +14515,7 @@ fn zirBoolBr(
1450114515
sema.air_extra.appendSliceAssumeCapacity(else_block.instructions.items);
1450214516

1450314517
_ = try child_block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{
14504-
.operand = lhs,
14518+
.operand = cond,
1450514519
.payload = cond_br_payload,
1450614520
} } });
1450714521

@@ -14871,10 +14885,83 @@ fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir
1487114885
const operand = try sema.analyzeLoad(block, src, ret_ptr, src);
1487214886
return sema.analyzeRet(block, operand, src);
1487314887
}
14888+
14889+
if (sema.wantErrorReturnTracing()) {
14890+
const is_non_err = try sema.analyzePtrIsNonErr(block, src, ret_ptr);
14891+
return retWithErrTracing(sema, block, src, is_non_err, .ret_load, ret_ptr);
14892+
}
14893+
1487414894
_ = try block.addUnOp(.ret_load, ret_ptr);
1487514895
return always_noreturn;
1487614896
}
1487714897

14898+
fn retWithErrTracing(
14899+
sema: *Sema,
14900+
block: *Block,
14901+
src: LazySrcLoc,
14902+
is_non_err: Air.Inst.Ref,
14903+
ret_tag: Air.Inst.Tag,
14904+
operand: Air.Inst.Ref,
14905+
) CompileError!Zir.Inst.Index {
14906+
const need_check = switch (is_non_err) {
14907+
.bool_true => {
14908+
_ = try block.addUnOp(ret_tag, operand);
14909+
return always_noreturn;
14910+
},
14911+
.bool_false => false,
14912+
else => true,
14913+
};
14914+
const gpa = sema.gpa;
14915+
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
14916+
const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
14917+
const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
14918+
const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
14919+
const return_err_fn = try sema.getBuiltin(block, src, "returnError");
14920+
const args: [1]Air.Inst.Ref = .{err_return_trace};
14921+
14922+
if (!need_check) {
14923+
_ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args, null);
14924+
_ = try block.addUnOp(ret_tag, operand);
14925+
return always_noreturn;
14926+
}
14927+
14928+
var then_block = block.makeSubBlock();
14929+
defer then_block.instructions.deinit(gpa);
14930+
_ = try then_block.addUnOp(ret_tag, operand);
14931+
14932+
var else_block = block.makeSubBlock();
14933+
defer else_block.instructions.deinit(gpa);
14934+
_ = try sema.analyzeCall(&else_block, return_err_fn, src, src, .never_inline, false, &args, null);
14935+
_ = try else_block.addUnOp(ret_tag, operand);
14936+
14937+
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
14938+
then_block.instructions.items.len + else_block.instructions.items.len +
14939+
@typeInfo(Air.Block).Struct.fields.len + 1);
14940+
14941+
const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
14942+
.then_body_len = @intCast(u32, then_block.instructions.items.len),
14943+
.else_body_len = @intCast(u32, else_block.instructions.items.len),
14944+
});
14945+
sema.air_extra.appendSliceAssumeCapacity(then_block.instructions.items);
14946+
sema.air_extra.appendSliceAssumeCapacity(else_block.instructions.items);
14947+
14948+
_ = try block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{
14949+
.operand = is_non_err,
14950+
.payload = cond_br_payload,
14951+
} } });
14952+
14953+
return always_noreturn;
14954+
}
14955+
14956+
fn wantErrorReturnTracing(sema: *Sema) bool {
14957+
// TODO implement this feature in all the backends and then delete this check.
14958+
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
14959+
14960+
return sema.fn_ret_ty.isError() and
14961+
sema.mod.comp.bin_file.options.error_return_tracing and
14962+
backend_supports_error_return_tracing;
14963+
}
14964+
1487814965
fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void {
1487914966
assert(sema.fn_ret_ty.zigTypeTag() == .ErrorUnion);
1488014967

@@ -14920,27 +15007,15 @@ fn analyzeRet(
1492015007
return always_noreturn;
1492115008
}
1492215009

14923-
// TODO implement this feature in all the backends and then delete this check.
14924-
const backend_supports_error_return_tracing =
14925-
sema.mod.comp.bin_file.options.use_llvm;
15010+
try sema.resolveTypeLayout(block, src, sema.fn_ret_ty);
1492615011

14927-
if (sema.fn_ret_ty.isError() and
14928-
sema.mod.comp.bin_file.options.error_return_tracing and
14929-
backend_supports_error_return_tracing)
14930-
ret_err: {
14931-
if (try sema.resolveMaybeUndefVal(block, src, operand)) |ret_val| {
14932-
if (ret_val.tag() != .@"error") break :ret_err;
14933-
}
14934-
const return_err_fn = try sema.getBuiltin(block, src, "returnError");
14935-
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
14936-
const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
14937-
const ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty);
14938-
const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
14939-
const args: [1]Air.Inst.Ref = .{err_return_trace};
14940-
_ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args, null);
15012+
if (sema.wantErrorReturnTracing()) {
15013+
// Avoid adding a frame to the error return trace in case the value is comptime-known
15014+
// to be not an error.
15015+
const is_non_err = try sema.analyzeIsNonErr(block, src, operand);
15016+
return retWithErrTracing(sema, block, src, is_non_err, .ret, operand);
1494115017
}
1494215018

14943-
try sema.resolveTypeLayout(block, src, sema.fn_ret_ty);
1494415019
_ = try block.addUnOp(.ret, operand);
1494515020
return always_noreturn;
1494615021
}
@@ -25418,6 +25493,27 @@ fn analyzeIsNull(
2541825493
return block.addUnOp(air_tag, operand);
2541925494
}
2542025495

25496+
fn analyzePtrIsNonErrComptimeOnly(
25497+
sema: *Sema,
25498+
block: *Block,
25499+
src: LazySrcLoc,
25500+
operand: Air.Inst.Ref,
25501+
) CompileError!Air.Inst.Ref {
25502+
const ptr_ty = sema.typeOf(operand);
25503+
assert(ptr_ty.zigTypeTag() == .Pointer);
25504+
const child_ty = ptr_ty.childType();
25505+
25506+
const child_tag = child_ty.zigTypeTag();
25507+
if (child_tag != .ErrorSet and child_tag != .ErrorUnion) return Air.Inst.Ref.bool_true;
25508+
if (child_tag == .ErrorSet) return Air.Inst.Ref.bool_false;
25509+
assert(child_tag == .ErrorUnion);
25510+
25511+
_ = block;
25512+
_ = src;
25513+
25514+
return Air.Inst.Ref.none;
25515+
}
25516+
2542125517
fn analyzeIsNonErrComptimeOnly(
2542225518
sema: *Sema,
2542325519
block: *Block,
@@ -25431,10 +25527,16 @@ fn analyzeIsNonErrComptimeOnly(
2543125527
assert(ot == .ErrorUnion);
2543225528

2543325529
if (Air.refToIndex(operand)) |operand_inst| {
25434-
const air_tags = sema.air_instructions.items(.tag);
25435-
if (air_tags[operand_inst] == .wrap_errunion_payload) {
25436-
return Air.Inst.Ref.bool_true;
25530+
switch (sema.air_instructions.items(.tag)[operand_inst]) {
25531+
.wrap_errunion_payload => return Air.Inst.Ref.bool_true,
25532+
.wrap_errunion_err => return Air.Inst.Ref.bool_false,
25533+
else => {},
2543725534
}
25535+
} else if (operand == .undef) {
25536+
return sema.addConstUndef(Type.bool);
25537+
} else {
25538+
// None of the ref tags can be errors.
25539+
return Air.Inst.Ref.bool_true;
2543825540
}
2543925541

2544025542
const maybe_operand_val = try sema.resolveMaybeUndefVal(block, src, operand);
@@ -25510,6 +25612,21 @@ fn analyzeIsNonErr(
2551025612
}
2551125613
}
2551225614

25615+
fn analyzePtrIsNonErr(
25616+
sema: *Sema,
25617+
block: *Block,
25618+
src: LazySrcLoc,
25619+
operand: Air.Inst.Ref,
25620+
) CompileError!Air.Inst.Ref {
25621+
const result = try sema.analyzePtrIsNonErrComptimeOnly(block, src, operand);
25622+
if (result == .none) {
25623+
try sema.requireRuntimeBlock(block, src, null);
25624+
return block.addUnOp(.is_non_err_ptr, operand);
25625+
} else {
25626+
return result;
25627+
}
25628+
}
25629+
2551325630
fn analyzeSlice(
2551425631
sema: *Sema,
2551525632
block: *Block,

test/stack_traces.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
2222
.ReleaseSafe = .{
2323
.exclude_os = .{
2424
.windows, // segfault
25+
.linux, // defeated by aggressive inlining
2526
},
2627
.expect =
2728
\\error: TheSkyIsFalling

0 commit comments

Comments
 (0)