stage2: implement error return traces

This commit is contained in:
Veikka Tuominen 2022-04-22 21:30:54 +03:00 committed by Andrew Kelley
parent 5888446c03
commit eee8fffec7
15 changed files with 318 additions and 10 deletions

View file

@ -846,5 +846,16 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn
} }
} }
pub noinline fn returnError(maybe_st: ?*StackTrace) void {
@setCold(true);
const st = maybe_st orelse return;
addErrRetTraceAddr(st, @returnAddress());
}
pub inline fn addErrRetTraceAddr(st: *StackTrace, addr: usize) void {
st.instruction_addresses[st.index & (st.instruction_addresses.len - 1)] = addr;
st.index +%= 1;
}
const std = @import("std.zig"); const std = @import("std.zig");
const root = @import("root"); const root = @import("root");

View file

@ -649,6 +649,12 @@ pub const Inst = struct {
/// flush(). /// flush().
cmp_lt_errors_len, cmp_lt_errors_len,
/// Returns pointer to current error return trace.
err_return_trace,
/// Sets the operand as the current error return trace,
set_err_return_trace,
pub fn fromCmpOp(op: std.math.CompareOperator) Tag { pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
return switch (op) { return switch (op) {
.lt => .cmp_lt, .lt => .cmp_lt,
@ -961,6 +967,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.alloc, .alloc,
.ret_ptr, .ret_ptr,
.arg, .arg,
.err_return_trace,
=> return datas[inst].ty, => return datas[inst].ty,
.assembly, .assembly,
@ -1048,6 +1055,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.memcpy, .memcpy,
.set_union_tag, .set_union_tag,
.prefetch, .prefetch,
.set_err_return_trace,
=> return Type.void, => return Type.void,
.ptrtoint, .ptrtoint,

View file

@ -362,6 +362,7 @@ fn analyzeInst(
.ret_addr, .ret_addr,
.frame_addr, .frame_addr,
.wasm_memory_size, .wasm_memory_size,
.err_return_trace,
=> return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }), => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
.not, .not,
@ -434,6 +435,7 @@ fn analyzeInst(
.round, .round,
.trunc_float, .trunc_float,
.cmp_lt_errors_len, .cmp_lt_errors_len,
.set_err_return_trace,
=> { => {
const operand = inst_datas[inst].un_op; const operand = inst_datas[inst].un_op;
return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none }); return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });

View file

@ -1427,6 +1427,7 @@ pub const Fn = struct {
state: Analysis, state: Analysis,
is_cold: bool = false, is_cold: bool = false,
is_noinline: bool = false, is_noinline: bool = false,
calls_or_awaits_errorable_fn: bool = false,
/// Any inferred error sets that this function owns, both its own inferred error set and /// Any inferred error sets that this function owns, both its own inferred error set and
/// inferred error sets of any inline/comptime functions called. Not to be confused /// inferred error sets of any inline/comptime functions called. Not to be confused
@ -4838,6 +4839,9 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
}; };
defer sema.deinit(); defer sema.deinit();
// reset in case case calls to errorable functions are removed.
func.calls_or_awaits_errorable_fn = false;
// First few indexes of extra are reserved and set at the end. // First few indexes of extra are reserved and set at the end.
const reserved_count = @typeInfo(Air.ExtraIndex).Enum.fields.len; const reserved_count = @typeInfo(Air.ExtraIndex).Enum.fields.len;
try sema.air_extra.ensureTotalCapacity(gpa, reserved_count); try sema.air_extra.ensureTotalCapacity(gpa, reserved_count);
@ -4936,6 +4940,8 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
func.state = .in_progress; func.state = .in_progress;
log.debug("set {s} to in_progress", .{decl.name}); log.debug("set {s} to in_progress", .{decl.name});
const last_arg_index = inner_block.instructions.items.len;
sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {
// TODO make these unreachable instead of @panic // TODO make these unreachable instead of @panic
error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"), error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"),
@ -4944,6 +4950,21 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
else => |e| return e, else => |e| return e,
}; };
// If we don't get an error return trace from a caller, create our own.
if (func.calls_or_awaits_errorable_fn and
mod.comp.bin_file.options.error_return_tracing and
!sema.fn_ret_ty.isError())
{
sema.setupErrorReturnTrace(&inner_block, last_arg_index) catch |err| switch (err) {
// TODO make these unreachable instead of @panic
error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"),
error.GenericPoison => @panic("zig compiler bug: GenericPoison"),
error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"),
error.ComptimeBreak => @panic("zig compiler bug: ComptimeBreak"),
else => |e| return e,
};
}
try wip_captures.finalize(); try wip_captures.finalize();
// Copy the block into place and mark that as the main block. // Copy the block into place and mark that as the main block.

View file

@ -1411,6 +1411,38 @@ fn analyzeAsType(
return ty.copy(sema.arena); return ty.copy(sema.arena);
} }
pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) !void {
var err_trace_block = block.makeSubBlock();
err_trace_block.is_comptime = false;
defer err_trace_block.instructions.deinit(sema.gpa);
const src: LazySrcLoc = .unneeded;
// var addrs: [err_return_trace_addr_count]usize = undefined;
const err_return_trace_addr_count = 32;
const addr_arr_ty = try Type.array(sema.arena, err_return_trace_addr_count, null, Type.usize, sema.mod);
const addrs_ptr = try err_trace_block.addTy(.alloc, try Type.Tag.single_mut_pointer.create(sema.arena, addr_arr_ty));
// var st: StackTrace = undefined;
const unresolved_stack_trace_ty = try sema.getBuiltinType(&err_trace_block, src, "StackTrace");
const stack_trace_ty = try sema.resolveTypeFields(&err_trace_block, src, unresolved_stack_trace_ty);
const st_ptr = try err_trace_block.addTy(.alloc, try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty));
// st.instruction_addresses = &addrs;
const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src);
try sema.storePtr2(&err_trace_block, src, addr_field_ptr, src, addrs_ptr, src, .store);
// st.index = 0;
const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src);
const zero = try sema.addConstant(Type.usize, Value.zero);
try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, zero, src, .store);
// @errorReturnTrace() = &st;
_ = try err_trace_block.addUnOp(.set_err_return_trace, st_ptr);
try block.instructions.insertSlice(sema.gpa, last_arg_index, err_trace_block.instructions.items);
}
/// May return Value Tags: `variable`, `undef`. /// May return Value Tags: `variable`, `undef`.
/// See `resolveConstValue` for an alternative. /// See `resolveConstValue` for an alternative.
/// Value Tag `generic_poison` causes `error.GenericPoison` to be returned. /// Value Tag `generic_poison` causes `error.GenericPoison` to be returned.
@ -5236,6 +5268,13 @@ fn analyzeCall(
} }
try sema.queueFullTypeResolution(func_ty_info.return_type); try sema.queueFullTypeResolution(func_ty_info.return_type);
if (sema.owner_func != null and func_ty_info.return_type.isError()) {
if (!sema.owner_func.?.calls_or_awaits_errorable_fn) {
// Ensure the type exists so that backends can assume that.
_ = try sema.getBuiltinType(block, call_src, "StackTrace");
}
sema.owner_func.?.calls_or_awaits_errorable_fn = true;
}
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
args.len); args.len);
@ -5645,6 +5684,15 @@ fn instantiateGenericCall(
try sema.queueFullTypeResolution(new_fn_info.return_type); try sema.queueFullTypeResolution(new_fn_info.return_type);
} }
if (sema.owner_func != null and new_fn_info.return_type.isError()) {
if (!sema.owner_func.?.calls_or_awaits_errorable_fn) {
// Ensure the type exists so that backends can assume that.
_ = try sema.getBuiltinType(block, call_src, "StackTrace");
}
sema.owner_func.?.calls_or_awaits_errorable_fn = true;
}
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len +
runtime_args_len); runtime_args_len);
const func_inst = try block.addInst(.{ const func_inst = try block.addInst(.{
@ -12607,6 +12655,16 @@ fn analyzeRet(
return always_noreturn; return always_noreturn;
} }
if (sema.fn_ret_ty.isError() and sema.mod.comp.bin_file.options.error_return_tracing) {
const return_err_fn = try sema.getBuiltin(block, src, "returnError");
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
const ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty);
const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
const args: [1]Air.Inst.Ref = .{err_return_trace};
_ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args);
}
try sema.resolveTypeLayout(block, src, sema.fn_ret_ty); try sema.resolveTypeLayout(block, src, sema.fn_ret_ty);
_ = try block.addUnOp(.ret, operand); _ = try block.addUnOp(.ret, operand);
return always_noreturn; return always_noreturn;
@ -13338,9 +13396,14 @@ fn zirErrorReturnTrace(
const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) };
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace"); const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty); const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
const opt_stack_trace_ty = try Type.optional(sema.arena, stack_trace_ty); const opt_ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty);
// https://github.com/ziglang/zig/issues/11259 if (sema.owner_func != null and
return sema.addConstant(opt_stack_trace_ty, Value.@"null"); sema.owner_func.?.calls_or_awaits_errorable_fn and
sema.mod.comp.bin_file.options.error_return_tracing)
{
return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty);
}
return sema.addConstant(opt_ptr_stack_trace_ty, Value.@"null");
} }
fn zirFrame( fn zirFrame(
@ -21817,11 +21880,7 @@ fn resolvePeerTypes(
info.data.sentinel = chosen_child_ty.sentinel(); info.data.sentinel = chosen_child_ty.sentinel();
info.data.size = .Slice; info.data.size = .Slice;
info.data.mutable = !(seen_const or chosen_child_ty.isConstPtr()); info.data.mutable = !(seen_const or chosen_child_ty.isConstPtr());
info.data.pointee_type = switch (chosen_child_ty.tag()) { info.data.pointee_type = chosen_child_ty.elemType2();
.array => chosen_child_ty.elemType2(),
.array_u8, .array_u8_sentinel_0 => Type.initTag(.u8),
else => unreachable,
};
const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data); const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data);
const opt_ptr_ty = if (any_are_null) const opt_ptr_ty = if (any_are_null)

View file

@ -718,6 +718,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
.unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst), .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst), .errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.wrap_optional => try self.airWrapOptional(inst), .wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -2330,6 +2332,24 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
} }
fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst))
.dead
else
return self.fail("TODO implement airErrReturnTrace for {}", .{self.target.cpu.arch});
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst))
.dead
else
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void { fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op; const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {

View file

@ -725,6 +725,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
.unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst), .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst), .errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.wrap_optional => try self.airWrapOptional(inst), .wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -1843,6 +1845,24 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
} }
fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst))
.dead
else
return self.fail("TODO implement airErrReturnTrace for {}", .{self.target.cpu.arch});
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst))
.dead
else
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
/// T to E!T /// T to E!T
fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void { fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op; const ty_op = self.air.instructions.items(.data)[inst].ty_op;

View file

@ -654,6 +654,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
.unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst), .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst), .errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.wrap_optional => try self.airWrapOptional(inst), .wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -1267,6 +1269,24 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
} }
fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst))
.dead
else
return self.fail("TODO implement airErrReturnTrace for {}", .{self.target.cpu.arch});
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst))
.dead
else
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void { fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op; const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {

View file

@ -630,6 +630,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.unwrap_errunion_err_ptr => @panic("TODO try self.airUnwrapErrErrPtr(inst)"), .unwrap_errunion_err_ptr => @panic("TODO try self.airUnwrapErrErrPtr(inst)"),
.unwrap_errunion_payload_ptr=> @panic("TODO try self.airUnwrapErrPayloadPtr(inst)"), .unwrap_errunion_payload_ptr=> @panic("TODO try self.airUnwrapErrPayloadPtr(inst)"),
.errunion_payload_ptr_set => @panic("TODO try self.airErrUnionPayloadPtrSet(inst)"), .errunion_payload_ptr_set => @panic("TODO try self.airErrUnionPayloadPtrSet(inst)"),
.err_return_trace => @panic("TODO try self.airErrReturnTrace(inst)"),
.set_err_return_trace => @panic("TODO try self.airSetErrReturnTrace(inst)"),
.wrap_optional => @panic("TODO try self.airWrapOptional(inst)"), .wrap_optional => @panic("TODO try self.airWrapOptional(inst)"),
.wrap_errunion_payload => @panic("TODO try self.airWrapErrUnionPayload(inst)"), .wrap_errunion_payload => @panic("TODO try self.airWrapErrUnionPayload(inst)"),

View file

@ -1612,6 +1612,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
.atomic_store_seq_cst, .atomic_store_seq_cst,
.atomic_rmw, .atomic_rmw,
.tag_name, .tag_name,
.err_return_trace,
.set_err_return_trace,
=> |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
}; };
} }

View file

@ -749,6 +749,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst), .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
.unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst), .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst), .errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.wrap_optional => try self.airWrapOptional(inst), .wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -1855,6 +1857,24 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
} }
fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst))
.dead
else
return self.fail("TODO implement airErrReturnTrace for {}", .{self.target.cpu.arch});
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const result: MCValue = if (self.liveness.isUnused(inst))
.dead
else
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void { fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
const ty_op = self.air.instructions.items(.data)[inst].ty_op; const ty_op = self.air.instructions.items(.data)[inst].ty_op;
if (self.liveness.isUnused(inst)) { if (self.liveness.isUnused(inst)) {

View file

@ -1911,6 +1911,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.wrap_errunion_payload => try airWrapErrUnionPay(f, inst), .wrap_errunion_payload => try airWrapErrUnionPay(f, inst),
.wrap_errunion_err => try airWrapErrUnionErr(f, inst), .wrap_errunion_err => try airWrapErrUnionErr(f, inst),
.errunion_payload_ptr_set => try airErrUnionPayloadPtrSet(f, inst), .errunion_payload_ptr_set => try airErrUnionPayloadPtrSet(f, inst),
.err_return_trace => try airErrReturnTrace(f, inst),
.set_err_return_trace => try airSetErrReturnTrace(f, inst),
.wasm_memory_size => try airWasmMemorySize(f, inst), .wasm_memory_size => try airWasmMemorySize(f, inst),
.wasm_memory_grow => try airWasmMemoryGrow(f, inst), .wasm_memory_grow => try airWasmMemoryGrow(f, inst),
@ -3447,6 +3449,38 @@ fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue {
return local; return local;
} }
fn airErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst)) return CValue.none;
const un_op = f.air.instructions.items(.data)[inst].un_op;
const writer = f.object.writer();
const inst_ty = f.air.typeOfIndex(inst);
const operand = try f.resolveInst(un_op);
const local = try f.allocLocal(inst_ty, .Const);
try writer.writeAll(" = ");
_ = operand;
_ = local;
return f.fail("TODO: C backend: implement airErrReturnTrace", .{});
}
fn airSetErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst)) return CValue.none;
const un_op = f.air.instructions.items(.data)[inst].un_op;
const writer = f.object.writer();
const inst_ty = f.air.typeOfIndex(inst);
const operand = try f.resolveInst(un_op);
const local = try f.allocLocal(inst_ty, .Const);
try writer.writeAll(" = ");
_ = operand;
_ = local;
return f.fail("TODO: C backend: implement airSetErrReturnTrace", .{});
}
fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst)) if (f.liveness.isUnused(inst))
return CValue.none; return CValue.none;

View file

@ -636,10 +636,18 @@ pub const Object = struct {
const ret_ptr = if (sret) llvm_func.getParam(0) else null; const ret_ptr = if (sret) llvm_func.getParam(0) else null;
const gpa = dg.gpa; const gpa = dg.gpa;
const err_return_tracing = fn_info.return_type.isError() and
dg.module.comp.bin_file.options.error_return_tracing;
const err_ret_trace = if (err_return_tracing)
llvm_func.getParam(@boolToInt(ret_ptr != null))
else
null;
var args = std.ArrayList(*const llvm.Value).init(gpa); var args = std.ArrayList(*const llvm.Value).init(gpa);
defer args.deinit(); defer args.deinit();
const param_offset: c_uint = @boolToInt(ret_ptr != null); const param_offset = @as(c_uint, @boolToInt(ret_ptr != null)) + @boolToInt(err_return_tracing);
for (fn_info.param_types) |param_ty| { for (fn_info.param_types) |param_ty| {
if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue;
@ -711,6 +719,7 @@ pub const Object = struct {
.base_line = dg.decl.src_line, .base_line = dg.decl.src_line,
.prev_dbg_line = 0, .prev_dbg_line = 0,
.prev_dbg_column = 0, .prev_dbg_column = 0,
.err_ret_trace = err_ret_trace,
}; };
defer fg.deinit(); defer fg.deinit();
@ -1755,6 +1764,17 @@ pub const Object = struct {
try param_di_types.append(try o.lowerDebugType(Type.void, .full)); try param_di_types.append(try o.lowerDebugType(Type.void, .full));
} }
if (fn_info.return_type.isError() and
o.module.comp.bin_file.options.error_return_tracing)
{
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = o.getStackTraceType(),
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
try param_di_types.append(try o.lowerDebugType(ptr_ty, .full));
}
for (fn_info.param_types) |param_ty| { for (fn_info.param_types) |param_ty| {
if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue;
@ -1824,6 +1844,27 @@ pub const Object = struct {
"", // unique id "", // unique id
); );
} }
fn getStackTraceType(o: *Object) Type {
const mod = o.module;
const std_pkg = mod.main_pkg.table.get("std").?;
const std_file = (mod.importPkg(std_pkg) catch unreachable).file;
const builtin_str: []const u8 = "builtin";
const std_namespace = mod.declPtr(std_file.root_decl.unwrap().?).src_namespace;
const builtin_decl = std_namespace.decls
.getKeyAdapted(builtin_str, Module.DeclAdapter{ .mod = mod }).?;
const stack_trace_str: []const u8 = "StackTrace";
// buffer is only used for int_type, `builtin` is a struct.
const builtin_ty = mod.declPtr(builtin_decl).val.toType(undefined);
const builtin_namespace = builtin_ty.getNamespace().?;
const stack_trace_decl = builtin_namespace.decls
.getKeyAdapted(stack_trace_str, Module.DeclAdapter{ .mod = mod }).?;
return mod.declPtr(stack_trace_decl).val.toType(undefined);
}
}; };
pub const DeclGen = struct { pub const DeclGen = struct {
@ -1976,8 +2017,15 @@ pub const DeclGen = struct {
llvm_fn.addSretAttr(0, raw_llvm_ret_ty); llvm_fn.addSretAttr(0, raw_llvm_ret_ty);
} }
const err_return_tracing = fn_info.return_type.isError() and
dg.module.comp.bin_file.options.error_return_tracing;
if (err_return_tracing) {
dg.addArgAttr(llvm_fn, @boolToInt(sret), "nonnull");
}
// Set parameter attributes. // Set parameter attributes.
var llvm_param_i: c_uint = @boolToInt(sret); var llvm_param_i: c_uint = @as(c_uint, @boolToInt(sret)) + @boolToInt(err_return_tracing);
for (fn_info.param_types) |param_ty| { for (fn_info.param_types) |param_ty| {
if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue;
@ -2435,6 +2483,17 @@ pub const DeclGen = struct {
try llvm_params.append(llvm_sret_ty.pointerType(0)); try llvm_params.append(llvm_sret_ty.pointerType(0));
} }
if (fn_info.return_type.isError() and
dg.module.comp.bin_file.options.error_return_tracing)
{
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = dg.object.getStackTraceType(),
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
try llvm_params.append(try lowerFnParamTy(dg, fn_info.cc, ptr_ty));
}
for (fn_info.param_types) |param_ty| { for (fn_info.param_types) |param_ty| {
if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue;
@ -3449,6 +3508,8 @@ pub const FuncGen = struct {
llvm_func: *const llvm.Value, llvm_func: *const llvm.Value,
err_ret_trace: ?*const llvm.Value = null,
/// This data structure is used to implement breaking to blocks. /// This data structure is used to implement breaking to blocks.
blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, struct { blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
parent_bb: *const llvm.BasicBlock, parent_bb: *const llvm.BasicBlock,
@ -3678,6 +3739,8 @@ pub const FuncGen = struct {
.unwrap_errunion_err => try self.airErrUnionErr(inst, false), .unwrap_errunion_err => try self.airErrUnionErr(inst, false),
.unwrap_errunion_err_ptr => try self.airErrUnionErr(inst, true), .unwrap_errunion_err_ptr => try self.airErrUnionErr(inst, true),
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst), .errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
.err_return_trace => try self.airErrReturnTrace(inst),
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
.wrap_optional => try self.airWrapOptional(inst), .wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
@ -3732,6 +3795,12 @@ pub const FuncGen = struct {
break :blk ret_ptr; break :blk ret_ptr;
}; };
if (fn_info.return_type.isError() and
self.dg.module.comp.bin_file.options.error_return_tracing)
{
try llvm_args.append(self.err_ret_trace.?);
}
for (args) |arg| { for (args) |arg| {
const param_ty = self.air.typeOf(arg); const param_ty = self.air.typeOf(arg);
if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue;
@ -5149,6 +5218,17 @@ pub const FuncGen = struct {
return self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""); return self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
} }
fn airErrReturnTrace(self: *FuncGen, _: Air.Inst.Index) !?*const llvm.Value {
return self.err_ret_trace.?;
}
fn airSetErrReturnTrace(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = try self.resolveInst(un_op);
self.err_ret_trace = operand;
return null;
}
fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null; if (self.liveness.isUnused(inst)) return null;

View file

@ -170,6 +170,7 @@ const Writer = struct {
.round, .round,
.trunc_float, .trunc_float,
.cmp_lt_errors_len, .cmp_lt_errors_len,
.set_err_return_trace,
=> try w.writeUnOp(s, inst), => try w.writeUnOp(s, inst),
.breakpoint, .breakpoint,
@ -182,6 +183,7 @@ const Writer = struct {
.alloc, .alloc,
.ret_ptr, .ret_ptr,
.arg, .arg,
.err_return_trace,
=> try w.writeTy(s, inst), => try w.writeTy(s, inst),
.not, .not,

View file

@ -4093,6 +4093,13 @@ pub const Type = extern union {
}; };
} }
pub fn isError(ty: Type) bool {
return switch (ty.zigTypeTag()) {
.ErrorUnion, .ErrorSet => true,
else => false,
};
}
/// Returns whether ty, which must be an error set, includes an error `name`. /// Returns whether ty, which must be an error set, includes an error `name`.
/// Might return a false negative if `ty` is an inferred error set and not fully /// Might return a false negative if `ty` is an inferred error set and not fully
/// resolved yet. /// resolved yet.