From eee8fffec70b1d3e2900970dbe836e346e499231 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 22 Apr 2022 21:30:54 +0300 Subject: [PATCH 1/6] stage2: implement error return traces --- lib/std/builtin.zig | 11 +++++ src/Air.zig | 8 ++++ src/Liveness.zig | 2 + src/Module.zig | 21 +++++++++ src/Sema.zig | 75 ++++++++++++++++++++++++++++---- src/arch/aarch64/CodeGen.zig | 20 +++++++++ src/arch/arm/CodeGen.zig | 20 +++++++++ src/arch/riscv64/CodeGen.zig | 20 +++++++++ src/arch/sparc64/CodeGen.zig | 2 + src/arch/wasm/CodeGen.zig | 2 + src/arch/x86_64/CodeGen.zig | 20 +++++++++ src/codegen/c.zig | 34 +++++++++++++++ src/codegen/llvm.zig | 84 +++++++++++++++++++++++++++++++++++- src/print_air.zig | 2 + src/type.zig | 7 +++ 15 files changed, 318 insertions(+), 10 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index d352ac29dc..a8069fa490 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -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 root = @import("root"); diff --git a/src/Air.zig b/src/Air.zig index 2dae8454cf..169449a066 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -649,6 +649,12 @@ pub const Inst = struct { /// flush(). 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 { return switch (op) { .lt => .cmp_lt, @@ -961,6 +967,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .alloc, .ret_ptr, .arg, + .err_return_trace, => return datas[inst].ty, .assembly, @@ -1048,6 +1055,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .memcpy, .set_union_tag, .prefetch, + .set_err_return_trace, => return Type.void, .ptrtoint, diff --git a/src/Liveness.zig b/src/Liveness.zig index e606c15b4b..c14c460d67 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -362,6 +362,7 @@ fn analyzeInst( .ret_addr, .frame_addr, .wasm_memory_size, + .err_return_trace, => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }), .not, @@ -434,6 +435,7 @@ fn analyzeInst( .round, .trunc_float, .cmp_lt_errors_len, + .set_err_return_trace, => { const operand = inst_datas[inst].un_op; return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none }); diff --git a/src/Module.zig b/src/Module.zig index de29753b13..cb38ddba45 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1427,6 +1427,7 @@ pub const Fn = struct { state: Analysis, is_cold: 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 /// 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(); + // 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. const reserved_count = @typeInfo(Air.ExtraIndex).Enum.fields.len; 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; 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) { // TODO make these unreachable instead of @panic 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, }; + // 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(); // Copy the block into place and mark that as the main block. diff --git a/src/Sema.zig b/src/Sema.zig index 471639ba96..ab2a59f44a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1411,6 +1411,38 @@ fn analyzeAsType( 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`. /// See `resolveConstValue` for an alternative. /// Value Tag `generic_poison` causes `error.GenericPoison` to be returned. @@ -5236,6 +5268,13 @@ fn analyzeCall( } 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 + args.len); @@ -5645,6 +5684,15 @@ fn instantiateGenericCall( 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 + runtime_args_len); const func_inst = try block.addInst(.{ @@ -12607,6 +12655,16 @@ fn analyzeRet( 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 block.addUnOp(.ret, operand); return always_noreturn; @@ -13338,9 +13396,14 @@ fn zirErrorReturnTrace( const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; 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 opt_stack_trace_ty = try Type.optional(sema.arena, stack_trace_ty); - // https://github.com/ziglang/zig/issues/11259 - return sema.addConstant(opt_stack_trace_ty, Value.@"null"); + const opt_ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty); + if (sema.owner_func != null and + 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( @@ -21817,11 +21880,7 @@ fn resolvePeerTypes( info.data.sentinel = chosen_child_ty.sentinel(); info.data.size = .Slice; info.data.mutable = !(seen_const or chosen_child_ty.isConstPtr()); - info.data.pointee_type = switch (chosen_child_ty.tag()) { - .array => chosen_child_ty.elemType2(), - .array_u8, .array_u8_sentinel_0 => Type.initTag(.u8), - else => unreachable, - }; + info.data.pointee_type = chosen_child_ty.elemType2(); const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data); const opt_ptr_ty = if (any_are_null) diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 10730c446f..56b7b87499 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -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_payload_ptr=> try self.airUnwrapErrPayloadPtr(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_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 }); } +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 { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 93e6e95ba9..f09b37ee69 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -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_payload_ptr=> try self.airUnwrapErrPayloadPtr(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_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 }); } +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 fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 07ce2a9f89..6d813cfbbb 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -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_payload_ptr=> try self.airUnwrapErrPayloadPtr(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_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 }); } +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 { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index 0745cd46c9..a3f5f2b108 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -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_payload_ptr=> @panic("TODO try self.airUnwrapErrPayloadPtr(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_errunion_payload => @panic("TODO try self.airWrapErrUnionPayload(inst)"), diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 8e84b7d1fe..fd3ac0e93a 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1612,6 +1612,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .atomic_store_seq_cst, .atomic_rmw, .tag_name, + .err_return_trace, + .set_err_return_trace, => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), }; } diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 5ba28a3751..31ecc58c66 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -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_payload_ptr=> try self.airUnwrapErrPayloadPtr(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_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 }); } +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 { const ty_op = self.air.instructions.items(.data)[inst].ty_op; if (self.liveness.isUnused(inst)) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 998271cd7f..c3ca79dabe 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -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_err => try airWrapErrUnionErr(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_grow => try airWasmMemoryGrow(f, inst), @@ -3447,6 +3449,38 @@ fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue { 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 { if (f.liveness.isUnused(inst)) return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 7392b2068b..dfb0f8f03b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -636,10 +636,18 @@ pub const Object = struct { const ret_ptr = if (sret) llvm_func.getParam(0) else null; 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); 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| { if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; @@ -711,6 +719,7 @@ pub const Object = struct { .base_line = dg.decl.src_line, .prev_dbg_line = 0, .prev_dbg_column = 0, + .err_ret_trace = err_ret_trace, }; defer fg.deinit(); @@ -1755,6 +1764,17 @@ pub const Object = struct { 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| { if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; @@ -1824,6 +1844,27 @@ pub const Object = struct { "", // 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 { @@ -1976,8 +2017,15 @@ pub const DeclGen = struct { 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. - 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| { if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; @@ -2435,6 +2483,17 @@ pub const DeclGen = struct { 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| { if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; @@ -3449,6 +3508,8 @@ pub const FuncGen = struct { llvm_func: *const llvm.Value, + err_ret_trace: ?*const llvm.Value = null, + /// This data structure is used to implement breaking to blocks. blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, struct { parent_bb: *const llvm.BasicBlock, @@ -3678,6 +3739,8 @@ pub const FuncGen = struct { .unwrap_errunion_err => try self.airErrUnionErr(inst, false), .unwrap_errunion_err_ptr => try self.airErrUnionErr(inst, true), .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_errunion_payload => try self.airWrapErrUnionPayload(inst), @@ -3732,6 +3795,12 @@ pub const FuncGen = struct { 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| { const param_ty = self.air.typeOf(arg); if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; @@ -5149,6 +5218,17 @@ pub const FuncGen = struct { 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 { if (self.liveness.isUnused(inst)) return null; diff --git a/src/print_air.zig b/src/print_air.zig index c01d96ed7f..0524356fa7 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -170,6 +170,7 @@ const Writer = struct { .round, .trunc_float, .cmp_lt_errors_len, + .set_err_return_trace, => try w.writeUnOp(s, inst), .breakpoint, @@ -182,6 +183,7 @@ const Writer = struct { .alloc, .ret_ptr, .arg, + .err_return_trace, => try w.writeTy(s, inst), .not, diff --git a/src/type.zig b/src/type.zig index 54b7d44a3d..cc00f712f0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -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`. /// Might return a false negative if `ty` is an inferred error set and not fully /// resolved yet. From 66c3988e5eebd423844d5dd20c762d6fefe20adf Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 22 Apr 2022 23:10:02 +0300 Subject: [PATCH 2/6] stage2: disable error return tracing on unsupported targets --- lib/test_runner.zig | 4 ++-- src/Sema.zig | 20 ++++++++++++++++++-- src/arch/aarch64/CodeGen.zig | 12 ++++-------- src/arch/arm/CodeGen.zig | 12 ++++-------- src/arch/riscv64/CodeGen.zig | 12 ++++-------- src/arch/x86_64/CodeGen.zig | 12 ++++-------- src/codegen/c.zig | 24 +----------------------- src/codegen/llvm.zig | 10 +++++----- 8 files changed, 42 insertions(+), 64 deletions(-) diff --git a/lib/test_runner.zig b/lib/test_runner.zig index 4b6c80ac66..5ae05464f2 100644 --- a/lib/test_runner.zig +++ b/lib/test_runner.zig @@ -92,9 +92,9 @@ pub fn main() void { fail_count += 1; progress.log("FAIL ({s})\n", .{@errorName(err)}); if (!have_tty) std.debug.print("FAIL ({s})\n", .{@errorName(err)}); - if (builtin.zig_backend != .stage2_llvm) if (@errorReturnTrace()) |trace| { + if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); - }; + } test_node.end(); }, } diff --git a/src/Sema.zig b/src/Sema.zig index ab2a59f44a..1801806084 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1412,6 +1412,12 @@ fn analyzeAsType( } pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) !void { + const backend_supports_error_return_tracing = false; + if (!backend_supports_error_return_tracing) { + // TODO implement this feature in all the backends and then delete this branch + return; + } + var err_trace_block = block.makeSubBlock(); err_trace_block.is_comptime = false; defer err_trace_block.instructions.deinit(sema.gpa); @@ -12655,7 +12661,12 @@ fn analyzeRet( return always_noreturn; } - if (sema.fn_ret_ty.isError() and sema.mod.comp.bin_file.options.error_return_tracing) { + // TODO implement this feature in all the backends and then delete this check. + const backend_supports_error_return_tracing = false; + + if (sema.fn_ret_ty.isError() and sema.mod.comp.bin_file.options.error_return_tracing and + backend_supports_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); @@ -13397,9 +13408,14 @@ fn zirErrorReturnTrace( 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 opt_ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty); + + // TODO implement this feature in all the backends and then delete this check. + const backend_supports_error_return_tracing = false; + if (sema.owner_func != null and sema.owner_func.?.calls_or_awaits_errorable_fn and - sema.mod.comp.bin_file.options.error_return_tracing) + sema.mod.comp.bin_file.options.error_return_tracing and + backend_supports_error_return_tracing) { return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty); } diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 56b7b87499..95a29b840e 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -2333,21 +2333,17 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { } fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; + _ = inst; 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 }); + return self.finishAir(inst, result, .{ .none, .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 }); + _ = inst; + return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch}); } fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void { diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index f09b37ee69..0154548911 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -1846,21 +1846,17 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { } fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; + _ = inst; 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 }); + return self.finishAir(inst, result, .{ .none, .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 }); + _ = inst; + return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch}); } /// T to E!T diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 6d813cfbbb..22c97b9aec 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1270,21 +1270,17 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { } fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; + _ = inst; 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 }); + return self.finishAir(inst, result, .{ .none, .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 }); + _ = inst; + return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch}); } fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void { diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 31ecc58c66..bb3ebec5db 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -1858,21 +1858,17 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void { } fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; + _ = inst; 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 }); + return self.finishAir(inst, result, .{ .none, .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 }); + _ = inst; + return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch}); } fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index c3ca79dabe..92770168f4 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -3451,33 +3451,11 @@ fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue { 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; + _ = inst; return f.fail("TODO: C backend: implement airSetErrReturnTrace", .{}); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index dfb0f8f03b..f6bb989f37 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -637,7 +637,7 @@ pub const Object = struct { const gpa = dg.gpa; const err_return_tracing = fn_info.return_type.isError() and - dg.module.comp.bin_file.options.error_return_tracing; + dg.module.comp.bin_file.options.error_return_tracing and false; const err_ret_trace = if (err_return_tracing) llvm_func.getParam(@boolToInt(ret_ptr != null)) @@ -1765,7 +1765,7 @@ pub const Object = struct { } if (fn_info.return_type.isError() and - o.module.comp.bin_file.options.error_return_tracing) + o.module.comp.bin_file.options.error_return_tracing and false) { var ptr_ty_payload: Type.Payload.ElemType = .{ .base = .{ .tag = .single_mut_pointer }, @@ -2018,7 +2018,7 @@ pub const DeclGen = struct { } const err_return_tracing = fn_info.return_type.isError() and - dg.module.comp.bin_file.options.error_return_tracing; + dg.module.comp.bin_file.options.error_return_tracing and false; if (err_return_tracing) { dg.addArgAttr(llvm_fn, @boolToInt(sret), "nonnull"); @@ -2484,7 +2484,7 @@ pub const DeclGen = struct { } if (fn_info.return_type.isError() and - dg.module.comp.bin_file.options.error_return_tracing) + dg.module.comp.bin_file.options.error_return_tracing and false) { var ptr_ty_payload: Type.Payload.ElemType = .{ .base = .{ .tag = .single_mut_pointer }, @@ -3796,7 +3796,7 @@ pub const FuncGen = struct { }; if (fn_info.return_type.isError() and - self.dg.module.comp.bin_file.options.error_return_tracing) + self.dg.module.comp.bin_file.options.error_return_tracing and false) { try llvm_args.append(self.err_ret_trace.?); } From 53a5aee3b3684a03c91236702c9304dce21279e2 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 23 Apr 2022 09:38:38 +0300 Subject: [PATCH 3/6] stage2: enable error return tracing on llvm backend --- src/Compilation.zig | 3 ++- src/Sema.zig | 22 +++++++++++----------- src/codegen/llvm.zig | 13 ++++++++----- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 407c753193..687b0a6dc6 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1457,7 +1457,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { errdefer if (module) |zm| zm.deinit(); const error_return_tracing = !strip and switch (options.optimize_mode) { - .Debug, .ReleaseSafe => true, + .Debug, .ReleaseSafe => (!options.target.isWasm() or options.target.os.tag == .emscripten) and + !options.target.cpu.arch.isBpf(), .ReleaseFast, .ReleaseSmall => false, }; diff --git a/src/Sema.zig b/src/Sema.zig index 1801806084..86f06c3ad3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1412,7 +1412,8 @@ fn analyzeAsType( } pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) !void { - const backend_supports_error_return_tracing = false; + const backend_supports_error_return_tracing = + sema.mod.comp.bin_file.options.use_llvm; if (!backend_supports_error_return_tracing) { // TODO implement this feature in all the backends and then delete this branch return; @@ -5275,10 +5276,6 @@ fn analyzeCall( 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; } @@ -5692,10 +5689,6 @@ fn instantiateGenericCall( } 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; } @@ -12662,7 +12655,8 @@ fn analyzeRet( } // TODO implement this feature in all the backends and then delete this check. - const backend_supports_error_return_tracing = false; + const backend_supports_error_return_tracing = + sema.mod.comp.bin_file.options.use_llvm; if (sema.fn_ret_ty.isError() and sema.mod.comp.bin_file.options.error_return_tracing and backend_supports_error_return_tracing) @@ -13410,7 +13404,8 @@ fn zirErrorReturnTrace( const opt_ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty); // TODO implement this feature in all the backends and then delete this check. - const backend_supports_error_return_tracing = false; + const backend_supports_error_return_tracing = + sema.mod.comp.bin_file.options.use_llvm; if (sema.owner_func != null and sema.owner_func.?.calls_or_awaits_errorable_fn and @@ -21966,6 +21961,11 @@ pub fn resolveFnTypes( ) CompileError!void { try sema.resolveTypeFully(block, src, fn_info.return_type); + if (sema.mod.comp.bin_file.options.error_return_tracing and fn_info.return_type.isError()) { + // Ensure the type exists so that backends can assume that. + _ = try sema.getBuiltinType(block, src, "StackTrace"); + } + for (fn_info.param_types) |param_ty| { try sema.resolveTypeFully(block, src, param_ty); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index f6bb989f37..9998f1d40f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -637,7 +637,7 @@ pub const Object = struct { const gpa = dg.gpa; const err_return_tracing = fn_info.return_type.isError() and - dg.module.comp.bin_file.options.error_return_tracing and false; + dg.module.comp.bin_file.options.error_return_tracing; const err_ret_trace = if (err_return_tracing) llvm_func.getParam(@boolToInt(ret_ptr != null)) @@ -698,6 +698,9 @@ pub const Object = struct { const lexical_block = dib.createLexicalBlock(subprogram.toScope(), di_file.?, line_number, 1); di_scope = lexical_block.toScope(); + + // Setup a debug location in case there is a call to `returnError` before a `.dbg_stmt`. + builder.setCurrentDebugLocation(line_number + func.lbrace_line, func.lbrace_column, di_scope.?, null); } var fg: FuncGen = .{ @@ -1765,7 +1768,7 @@ pub const Object = struct { } if (fn_info.return_type.isError() and - o.module.comp.bin_file.options.error_return_tracing and false) + o.module.comp.bin_file.options.error_return_tracing) { var ptr_ty_payload: Type.Payload.ElemType = .{ .base = .{ .tag = .single_mut_pointer }, @@ -2018,7 +2021,7 @@ pub const DeclGen = struct { } const err_return_tracing = fn_info.return_type.isError() and - dg.module.comp.bin_file.options.error_return_tracing and false; + dg.module.comp.bin_file.options.error_return_tracing; if (err_return_tracing) { dg.addArgAttr(llvm_fn, @boolToInt(sret), "nonnull"); @@ -2484,7 +2487,7 @@ pub const DeclGen = struct { } if (fn_info.return_type.isError() and - dg.module.comp.bin_file.options.error_return_tracing and false) + dg.module.comp.bin_file.options.error_return_tracing) { var ptr_ty_payload: Type.Payload.ElemType = .{ .base = .{ .tag = .single_mut_pointer }, @@ -3796,7 +3799,7 @@ pub const FuncGen = struct { }; if (fn_info.return_type.isError() and - self.dg.module.comp.bin_file.options.error_return_tracing and false) + self.dg.module.comp.bin_file.options.error_return_tracing) { try llvm_args.append(self.err_ret_trace.?); } From e369752430a1b3a50e57e11b9f0682d026c62feb Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 23 Apr 2022 14:48:16 +0300 Subject: [PATCH 4/6] Sema: do not call `returnError` when returning payload of error union --- src/Module.zig | 2 +- src/Sema.zig | 3 ++- src/codegen/llvm.zig | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index cb38ddba45..11549ccda6 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4839,7 +4839,7 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air { }; defer sema.deinit(); - // reset in case case calls to errorable functions are removed. + // reset in 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. diff --git a/src/Sema.zig b/src/Sema.zig index 86f06c3ad3..a7dd905dd6 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -12658,7 +12658,8 @@ fn analyzeRet( const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm; - if (sema.fn_ret_ty.isError() and sema.mod.comp.bin_file.options.error_return_tracing and + if ((sema.fn_ret_ty.zigTypeTag() == .ErrorSet or sema.typeOf(uncasted_operand).zigTypeTag() == .ErrorUnion) and + sema.mod.comp.bin_file.options.error_return_tracing and backend_supports_error_return_tracing) { const return_err_fn = try sema.getBuiltin(block, src, "returnError"); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 9998f1d40f..dfb0f8f03b 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -698,9 +698,6 @@ pub const Object = struct { const lexical_block = dib.createLexicalBlock(subprogram.toScope(), di_file.?, line_number, 1); di_scope = lexical_block.toScope(); - - // Setup a debug location in case there is a call to `returnError` before a `.dbg_stmt`. - builder.setCurrentDebugLocation(line_number + func.lbrace_line, func.lbrace_column, di_scope.?, null); } var fg: FuncGen = .{ From ab4ec35b8bb3a19361afa315f77cce5f6054b109 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 23 Apr 2022 11:13:31 +0300 Subject: [PATCH 5/6] stage2: add runtime safety for unwrapping error --- lib/compiler_rt.zig | 24 +++++----- lib/std/builtin.zig | 4 ++ src/AstGen.zig | 27 +++++++++++ src/Sema.zig | 106 ++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 139 insertions(+), 22 deletions(-) diff --git a/lib/compiler_rt.zig b/lib/compiler_rt.zig index fdf5940702..563d3d0820 100644 --- a/lib/compiler_rt.zig +++ b/lib/compiler_rt.zig @@ -198,19 +198,17 @@ comptime { const __trunctfxf2 = @import("compiler_rt/trunc_f80.zig").__trunctfxf2; @export(__trunctfxf2, .{ .name = "__trunctfxf2", .linkage = linkage }); - if (builtin.zig_backend == .stage1) { // TODO - switch (arch) { - .i386, - .x86_64, - => { - const zig_probe_stack = @import("compiler_rt/stack_probe.zig").zig_probe_stack; - @export(zig_probe_stack, .{ - .name = "__zig_probe_stack", - .linkage = linkage, - }); - }, - else => {}, - } + switch (arch) { + .i386, + .x86_64, + => { + const zig_probe_stack = @import("compiler_rt/stack_probe.zig").zig_probe_stack; + @export(zig_probe_stack, .{ + .name = "__zig_probe_stack", + .linkage = linkage, + }); + }, + else => {}, } const __unordsf2 = @import("compiler_rt/compareXf2.zig").__unordsf2; diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index a8069fa490..02aa7239f5 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -846,6 +846,10 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn } } +pub fn panicUnwrapError(st: ?*StackTrace, err: anyerror) noreturn { + std.debug.panicExtra(st, "attempt to unwrap error: {s}", .{@errorName(err)}); +} + pub noinline fn returnError(maybe_st: ?*StackTrace) void { @setCold(true); const st = maybe_st orelse return; diff --git a/src/AstGen.zig b/src/AstGen.zig index d4895aa2e4..3e502625db 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -856,6 +856,33 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr catch_token + 2 else null; + + var rhs = node_datas[node].rhs; + while (true) switch (node_tags[rhs]) { + .grouped_expression => rhs = node_datas[rhs].lhs, + .unreachable_literal => { + if (payload_token != null and mem.eql(u8, tree.tokenSlice(payload_token.?), "_")) { + return astgen.failTok(payload_token.?, "discard of error capture; omit it instead", .{}); + } else if (payload_token != null) { + return astgen.failTok(payload_token.?, "unused capture", .{}); + } + const lhs = node_datas[node].lhs; + + const operand = try reachableExpr(gz, scope, switch (rl) { + .ref => .ref, + else => .none, + }, lhs, lhs); + const result = try gz.addUnNode(switch (rl) { + .ref => .err_union_payload_safe_ptr, + else => .err_union_payload_safe, + }, operand, node); + switch (rl) { + .none, .coerced_ty, .discard, .ref => return result, + else => return rvalue(gz, rl, result, lhs), + } + }, + else => break, + }; switch (rl) { .ref => return orelseCatchExpr( gz, diff --git a/src/Sema.zig b/src/Sema.zig index a7dd905dd6..76888834d3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6248,8 +6248,7 @@ fn zirErrUnionPayload( } try sema.requireRuntimeBlock(block, src); if (safety_check and block.wantSafety()) { - const is_non_err = try block.addUnOp(.is_err, operand); - try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion); + try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err); } const result_ty = operand_ty.errorUnionPayload(); return block.addTyOp(.unwrap_errunion_payload, result_ty, operand); @@ -6330,8 +6329,7 @@ fn analyzeErrUnionPayloadPtr( try sema.requireRuntimeBlock(block, src); if (safety_check and block.wantSafety()) { - const is_non_err = try block.addUnOp(.is_err, operand); - try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion); + try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr); } const air_tag: Air.Inst.Tag = if (initializing) .errunion_payload_ptr_set @@ -13400,6 +13398,10 @@ fn zirErrorReturnTrace( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; + return sema.getErrorReturnTrace(block, src); +} + +fn getErrorReturnTrace(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError!Air.Inst.Ref { 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 opt_ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty); @@ -16852,7 +16854,6 @@ fn explainWhyTypeIsComptime( pub const PanicId = enum { unreach, unwrap_null, - unwrap_errunion, cast_to_null, incorrect_alignment, invalid_error_code, @@ -16962,12 +16963,100 @@ fn panicWithMsg( try Type.optional(arena, ptr_stack_trace_ty), Value.@"null", ); - const args = try arena.create([2]Air.Inst.Ref); - args.* = .{ msg_inst, null_stack_trace }; - _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, args); + const args: [2]Air.Inst.Ref = .{ msg_inst, null_stack_trace }; + _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args); return always_noreturn; } +fn panicUnwrapError( + sema: *Sema, + parent_block: *Block, + src: LazySrcLoc, + operand: Air.Inst.Ref, + unwrap_err_tag: Air.Inst.Tag, + is_non_err_tag: Air.Inst.Tag, +) !void { + const ok = try parent_block.addUnOp(is_non_err_tag, operand); + const gpa = sema.gpa; + + var fail_block: Block = .{ + .parent = parent_block, + .sema = sema, + .src_decl = parent_block.src_decl, + .namespace = parent_block.namespace, + .wip_capture_scope = parent_block.wip_capture_scope, + .instructions = .{}, + .inlining = parent_block.inlining, + .is_comptime = parent_block.is_comptime, + }; + + defer fail_block.instructions.deinit(gpa); + + { + const this_feature_is_implemented_in_the_backend = + sema.mod.comp.bin_file.options.object_format == .c or + sema.mod.comp.bin_file.options.use_llvm; + + if (!this_feature_is_implemented_in_the_backend) { + // TODO implement this feature in all the backends and then delete this branch + _ = try fail_block.addNoOp(.breakpoint); + _ = try fail_block.addNoOp(.unreach); + } else { + const panic_fn = try sema.getBuiltin(&fail_block, src, "panicUnwrapError"); + const err = try fail_block.addTyOp(unwrap_err_tag, Type.anyerror, operand); + const err_return_trace = try sema.getErrorReturnTrace(&fail_block, src); + const args: [2]Air.Inst.Ref = .{ err_return_trace, err }; + _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args); + } + } + + try parent_block.instructions.ensureUnusedCapacity(gpa, 1); + + try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len + + 1 + // The main block only needs space for the cond_br. + @typeInfo(Air.CondBr).Struct.fields.len + + 1 + // The ok branch of the cond_br only needs space for the br. + fail_block.instructions.items.len); + + try sema.air_instructions.ensureUnusedCapacity(gpa, 3); + const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len); + const cond_br_inst = block_inst + 1; + const br_inst = cond_br_inst + 1; + sema.air_instructions.appendAssumeCapacity(.{ + .tag = .block, + .data = .{ .ty_pl = .{ + .ty = .void_type, + .payload = sema.addExtraAssumeCapacity(Air.Block{ + .body_len = 1, + }), + } }, + }); + sema.air_extra.appendAssumeCapacity(cond_br_inst); + + sema.air_instructions.appendAssumeCapacity(.{ + .tag = .cond_br, + .data = .{ .pl_op = .{ + .operand = ok, + .payload = sema.addExtraAssumeCapacity(Air.CondBr{ + .then_body_len = 1, + .else_body_len = @intCast(u32, fail_block.instructions.items.len), + }), + } }, + }); + sema.air_extra.appendAssumeCapacity(br_inst); + sema.air_extra.appendSliceAssumeCapacity(fail_block.instructions.items); + + sema.air_instructions.appendAssumeCapacity(.{ + .tag = .br, + .data = .{ .br = .{ + .block_inst = block_inst, + .operand = .void_value, + } }, + }); + + parent_block.instructions.appendAssumeCapacity(block_inst); +} + fn safetyPanic( sema: *Sema, block: *Block, @@ -16977,7 +17066,6 @@ fn safetyPanic( const msg = switch (panic_id) { .unreach => "reached unreachable code", .unwrap_null => "attempt to use null value", - .unwrap_errunion => "unreachable error occurred", .cast_to_null => "cast causes pointer to be null", .incorrect_alignment => "incorrect alignment", .invalid_error_code => "invalid error code", From 0a7f3be42e96361ab8a9a567a11782fb81ea17da Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 23 Apr 2022 11:25:06 +0300 Subject: [PATCH 6/6] Sema: improve index out of bounds panic message --- lib/std/builtin.zig | 6 +++ src/Sema.zig | 110 ++++++++++++++++++++++---------------------- 2 files changed, 60 insertions(+), 56 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 02aa7239f5..894103707c 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -847,9 +847,15 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn } pub fn panicUnwrapError(st: ?*StackTrace, err: anyerror) noreturn { + @setCold(true); std.debug.panicExtra(st, "attempt to unwrap error: {s}", .{@errorName(err)}); } +pub fn panicOutOfBounds(index: usize, len: usize) noreturn { + @setCold(true); + std.debug.panic("attempt to index out of bound: index {d}, len {d}", .{ index, len }); +} + pub noinline fn returnError(maybe_st: ?*StackTrace) void { @setCold(true); const st = maybe_st orelse return; diff --git a/src/Sema.zig b/src/Sema.zig index 76888834d3..c45079eaa7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -16857,7 +16857,6 @@ pub const PanicId = enum { cast_to_null, incorrect_alignment, invalid_error_code, - index_out_of_bounds, cast_truncated_data, integer_overflow, shl_overflow, @@ -16886,6 +16885,17 @@ fn addSafetyCheck( _ = try sema.safetyPanic(&fail_block, .unneeded, panic_id); + try sema.addSafetyCheckExtra(parent_block, ok, &fail_block); +} + +fn addSafetyCheckExtra( + sema: *Sema, + parent_block: *Block, + ok: Air.Inst.Ref, + fail_block: *Block, +) !void { + const gpa = sema.gpa; + try parent_block.instructions.ensureUnusedCapacity(gpa, 1); try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len + @@ -16994,7 +17004,6 @@ fn panicUnwrapError( { const this_feature_is_implemented_in_the_backend = - sema.mod.comp.bin_file.options.object_format == .c or sema.mod.comp.bin_file.options.use_llvm; if (!this_feature_is_implemented_in_the_backend) { @@ -17009,52 +17018,48 @@ fn panicUnwrapError( _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args); } } + try sema.addSafetyCheckExtra(parent_block, ok, &fail_block); +} - try parent_block.instructions.ensureUnusedCapacity(gpa, 1); +fn panicIndexOutOfBounds( + sema: *Sema, + parent_block: *Block, + src: LazySrcLoc, + index: Air.Inst.Ref, + len: Air.Inst.Ref, + cmp_op: Air.Inst.Tag, +) !void { + const ok = try parent_block.addBinOp(cmp_op, index, len); + const gpa = sema.gpa; - try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len + - 1 + // The main block only needs space for the cond_br. - @typeInfo(Air.CondBr).Struct.fields.len + - 1 + // The ok branch of the cond_br only needs space for the br. - fail_block.instructions.items.len); + var fail_block: Block = .{ + .parent = parent_block, + .sema = sema, + .src_decl = parent_block.src_decl, + .namespace = parent_block.namespace, + .wip_capture_scope = parent_block.wip_capture_scope, + .instructions = .{}, + .inlining = parent_block.inlining, + .is_comptime = parent_block.is_comptime, + }; - try sema.air_instructions.ensureUnusedCapacity(gpa, 3); - const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len); - const cond_br_inst = block_inst + 1; - const br_inst = cond_br_inst + 1; - sema.air_instructions.appendAssumeCapacity(.{ - .tag = .block, - .data = .{ .ty_pl = .{ - .ty = .void_type, - .payload = sema.addExtraAssumeCapacity(Air.Block{ - .body_len = 1, - }), - } }, - }); - sema.air_extra.appendAssumeCapacity(cond_br_inst); + defer fail_block.instructions.deinit(gpa); - sema.air_instructions.appendAssumeCapacity(.{ - .tag = .cond_br, - .data = .{ .pl_op = .{ - .operand = ok, - .payload = sema.addExtraAssumeCapacity(Air.CondBr{ - .then_body_len = 1, - .else_body_len = @intCast(u32, fail_block.instructions.items.len), - }), - } }, - }); - sema.air_extra.appendAssumeCapacity(br_inst); - sema.air_extra.appendSliceAssumeCapacity(fail_block.instructions.items); + { + const this_feature_is_implemented_in_the_backend = + sema.mod.comp.bin_file.options.use_llvm; - sema.air_instructions.appendAssumeCapacity(.{ - .tag = .br, - .data = .{ .br = .{ - .block_inst = block_inst, - .operand = .void_value, - } }, - }); - - parent_block.instructions.appendAssumeCapacity(block_inst); + if (!this_feature_is_implemented_in_the_backend) { + // TODO implement this feature in all the backends and then delete this branch + _ = try fail_block.addNoOp(.breakpoint); + _ = try fail_block.addNoOp(.unreach); + } else { + const panic_fn = try sema.getBuiltin(&fail_block, src, "panicOutOfBounds"); + const args: [2]Air.Inst.Ref = .{ index, len }; + _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args); + } + } + try sema.addSafetyCheckExtra(parent_block, ok, &fail_block); } fn safetyPanic( @@ -17069,7 +17074,6 @@ fn safetyPanic( .cast_to_null => "cast causes pointer to be null", .incorrect_alignment => "incorrect alignment", .invalid_error_code => "invalid error code", - .index_out_of_bounds => "attempt to index out of bounds", .cast_truncated_data => "integer cast truncated bits", .integer_overflow => "integer overflow", .shl_overflow => "left shift overflowed bits", @@ -18261,8 +18265,7 @@ fn elemValArray( if (maybe_index_val == null) { const len_inst = try sema.addIntUnsigned(Type.usize, array_len); const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt; - const is_in_bounds = try block.addBinOp(cmp_op, elem_index, len_inst); - try sema.addSafetyCheck(block, is_in_bounds, .index_out_of_bounds); + try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); } } return block.addBinOp(.array_elem_val, array, elem_index); @@ -18317,8 +18320,7 @@ fn elemPtrArray( if (maybe_index_val == null) { const len_inst = try sema.addIntUnsigned(Type.usize, array_len); const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt; - const is_in_bounds = try block.addBinOp(cmp_op, elem_index, len_inst); - try sema.addSafetyCheck(block, is_in_bounds, .index_out_of_bounds); + try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); } } return block.addPtrElemPtr(array_ptr, elem_index, elem_ptr_ty); @@ -18371,8 +18373,7 @@ fn elemValSlice( else try block.addTyOp(.slice_len, Type.usize, slice); const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt; - const is_in_bounds = try block.addBinOp(cmp_op, elem_index, len_inst); - try sema.addSafetyCheck(block, is_in_bounds, .index_out_of_bounds); + try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); } try sema.queueFullTypeResolution(sema.typeOf(slice)); return block.addBinOp(.slice_elem_val, slice, elem_index); @@ -18425,8 +18426,7 @@ fn elemPtrSlice( break :len try block.addTyOp(.slice_len, Type.usize, slice); }; const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt; - const is_in_bounds = try block.addBinOp(cmp_op, elem_index, len_inst); - try sema.addSafetyCheck(block, is_in_bounds, .index_out_of_bounds); + try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); } return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty); } @@ -21111,13 +21111,11 @@ fn analyzeSlice( break :blk try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src); } else null; if (opt_len_inst) |len_inst| { - const end_is_in_bounds = try block.addBinOp(.cmp_lte, end, len_inst); - try sema.addSafetyCheck(block, end_is_in_bounds, .index_out_of_bounds); + try sema.panicIndexOutOfBounds(block, src, end, len_inst, .cmp_lte); } // requirement: start <= end - const start_is_in_bounds = try block.addBinOp(.cmp_lte, start, end); - try sema.addSafetyCheck(block, start_is_in_bounds, .index_out_of_bounds); + try sema.panicIndexOutOfBounds(block, src, start, end, .cmp_lte); } return block.addInst(.{ .tag = .slice,