mirror of
https://github.com/ziglang/zig.git
synced 2026-02-14 05:20:34 +00:00
Merge pull request #12837 from topolarity/err-ret-trace-improvements-1923
stage2: Pop error trace frames for handled errors (#1923)
This commit is contained in:
commit
09236d29b7
@ -869,8 +869,10 @@ pub noinline fn returnError(st: *StackTrace) void {
|
||||
}
|
||||
|
||||
pub inline fn addErrRetTraceAddr(st: *StackTrace, addr: usize) void {
|
||||
st.instruction_addresses[st.index & (st.instruction_addresses.len - 1)] = addr;
|
||||
st.index +%= 1;
|
||||
if (st.index < st.instruction_addresses.len)
|
||||
st.instruction_addresses[st.index] = addr;
|
||||
|
||||
st.index += 1;
|
||||
}
|
||||
|
||||
const std = @import("std.zig");
|
||||
|
||||
@ -411,6 +411,14 @@ pub fn writeStackTrace(
|
||||
const return_address = stack_trace.instruction_addresses[frame_index];
|
||||
try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);
|
||||
}
|
||||
|
||||
if (stack_trace.index > stack_trace.instruction_addresses.len) {
|
||||
const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len;
|
||||
|
||||
tty_config.setColor(out_stream, .Bold);
|
||||
try out_stream.print("({d} additional stack frames skipped...)\n", .{dropped_frames});
|
||||
tty_config.setColor(out_stream, .Reset);
|
||||
}
|
||||
}
|
||||
|
||||
pub const StackIterator = struct {
|
||||
|
||||
@ -733,6 +733,10 @@ pub const Inst = struct {
|
||||
/// Uses the `ty_op` field.
|
||||
addrspace_cast,
|
||||
|
||||
/// Saves the error return trace index, if any. Otherwise, returns 0.
|
||||
/// Uses the `ty_pl` field.
|
||||
save_err_return_trace_index,
|
||||
|
||||
pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag {
|
||||
switch (op) {
|
||||
.lt => return if (optimized) .cmp_lt_optimized else .cmp_lt,
|
||||
@ -1179,6 +1183,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
|
||||
.slice_len,
|
||||
.ret_addr,
|
||||
.frame_addr,
|
||||
.save_err_return_trace_index,
|
||||
=> return Type.usize,
|
||||
|
||||
.wasm_memory_grow => return Type.i32,
|
||||
|
||||
1864
src/AstGen.zig
1864
src/AstGen.zig
File diff suppressed because it is too large
Load Diff
@ -228,6 +228,7 @@ pub fn categorizeOperand(
|
||||
.frame_addr,
|
||||
.wasm_memory_size,
|
||||
.err_return_trace,
|
||||
.save_err_return_trace_index,
|
||||
=> return .none,
|
||||
|
||||
.fence => return .write,
|
||||
@ -805,6 +806,7 @@ fn analyzeInst(
|
||||
.frame_addr,
|
||||
.wasm_memory_size,
|
||||
.err_return_trace,
|
||||
.save_err_return_trace_index,
|
||||
=> return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
|
||||
|
||||
.not,
|
||||
|
||||
@ -5633,6 +5633,12 @@ pub fn analyzeFnBody(mod: *Module, func: *Fn, arena: Allocator) SemaError!Air {
|
||||
|
||||
const last_arg_index = inner_block.instructions.items.len;
|
||||
|
||||
// Save the error trace as our first action in the function.
|
||||
// If this is unnecessary after all, Liveness will clean it up for us.
|
||||
const error_return_trace_index = try sema.analyzeSaveErrRetIndex(&inner_block);
|
||||
sema.error_return_trace_index_on_fn_entry = error_return_trace_index;
|
||||
inner_block.error_return_trace_index = error_return_trace_index;
|
||||
|
||||
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"),
|
||||
|
||||
350
src/Sema.zig
350
src/Sema.zig
@ -32,6 +32,8 @@ owner_func: ?*Module.Fn,
|
||||
/// This starts out the same as `owner_func` and then diverges in the case of
|
||||
/// an inline or comptime function call.
|
||||
func: ?*Module.Fn,
|
||||
/// Used to restore the error return trace when returning a non-error from a function.
|
||||
error_return_trace_index_on_fn_entry: Air.Inst.Ref = .none,
|
||||
/// When semantic analysis needs to know the return type of the function whose body
|
||||
/// is being analyzed, this `Type` should be used instead of going through `func`.
|
||||
/// This will correctly handle the case of a comptime/inline function call of a
|
||||
@ -153,6 +155,10 @@ pub const Block = struct {
|
||||
is_typeof: bool = false,
|
||||
is_coerce_result_ptr: bool = false,
|
||||
|
||||
/// Keep track of the active error return trace index around blocks so that we can correctly
|
||||
/// pop the error trace upon block exit.
|
||||
error_return_trace_index: Air.Inst.Ref = .none,
|
||||
|
||||
/// when null, it is determined by build mode, changed by @setRuntimeSafety
|
||||
want_safety: ?bool = null,
|
||||
|
||||
@ -226,6 +232,7 @@ pub const Block = struct {
|
||||
.float_mode = parent.float_mode,
|
||||
.c_import_buf = parent.c_import_buf,
|
||||
.switch_else_err_ty = parent.switch_else_err_ty,
|
||||
.error_return_trace_index = parent.error_return_trace_index,
|
||||
};
|
||||
}
|
||||
|
||||
@ -499,6 +506,25 @@ pub const Block = struct {
|
||||
return result_index;
|
||||
}
|
||||
|
||||
/// Insert an instruction into the block at `index`. Moves all following
|
||||
/// instructions forward in the block to make room. Operation is O(N).
|
||||
pub fn insertInst(block: *Block, index: Air.Inst.Index, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
|
||||
return Air.indexToRef(try block.insertInstAsIndex(index, inst));
|
||||
}
|
||||
|
||||
pub fn insertInstAsIndex(block: *Block, index: Air.Inst.Index, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Index {
|
||||
const sema = block.sema;
|
||||
const gpa = sema.gpa;
|
||||
|
||||
try sema.air_instructions.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
|
||||
sema.air_instructions.appendAssumeCapacity(inst);
|
||||
|
||||
try block.instructions.insert(gpa, index, result_index);
|
||||
return result_index;
|
||||
}
|
||||
|
||||
fn addUnreachable(block: *Block, src: LazySrcLoc, safety_check: bool) !void {
|
||||
if (safety_check and block.wantSafety()) {
|
||||
_ = try block.sema.safetyPanic(block, src, .unreach);
|
||||
@ -1208,6 +1234,16 @@ fn analyzeBodyInner(
|
||||
i += 1;
|
||||
continue;
|
||||
},
|
||||
.save_err_ret_index => {
|
||||
try sema.zirSaveErrRetIndex(block, inst);
|
||||
i += 1;
|
||||
continue;
|
||||
},
|
||||
.restore_err_ret_index => {
|
||||
try sema.zirRestoreErrRetIndex(block, inst);
|
||||
i += 1;
|
||||
continue;
|
||||
},
|
||||
|
||||
// Special case instructions to handle comptime control flow.
|
||||
.@"break" => {
|
||||
@ -1300,31 +1336,32 @@ fn analyzeBodyInner(
|
||||
const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
|
||||
const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len];
|
||||
const gpa = sema.gpa;
|
||||
// If this block contains a function prototype, we need to reset the
|
||||
// current list of parameters and restore it later.
|
||||
// Note: this probably needs to be resolved in a more general manner.
|
||||
const prev_params = block.params;
|
||||
const need_sub_block = tags[inline_body[inline_body.len - 1]] == .repeat_inline;
|
||||
var sub_block = block;
|
||||
var block_space: Block = undefined;
|
||||
// NOTE: this has to be done like this because branching in
|
||||
// defers here breaks stage1.
|
||||
block_space.instructions = .{};
|
||||
if (need_sub_block) {
|
||||
block_space = block.makeSubBlock();
|
||||
block_space.inline_block = inline_body[0];
|
||||
sub_block = &block_space;
|
||||
}
|
||||
block.params = .{};
|
||||
defer {
|
||||
block.params.deinit(gpa);
|
||||
block.params = prev_params;
|
||||
block_space.instructions.deinit(gpa);
|
||||
}
|
||||
const opt_break_data = try sema.analyzeBodyBreak(sub_block, inline_body);
|
||||
if (need_sub_block) {
|
||||
try block.instructions.appendSlice(gpa, block_space.instructions.items);
|
||||
}
|
||||
|
||||
const opt_break_data = b: {
|
||||
// Create a temporary child block so that this inline block is properly
|
||||
// labeled for any .restore_err_ret_index instructions
|
||||
var child_block = block.makeSubBlock();
|
||||
|
||||
// If this block contains a function prototype, we need to reset the
|
||||
// current list of parameters and restore it later.
|
||||
// Note: this probably needs to be resolved in a more general manner.
|
||||
if (tags[inline_body[inline_body.len - 1]] == .repeat_inline) {
|
||||
child_block.inline_block = inline_body[0];
|
||||
} else child_block.inline_block = block.inline_block;
|
||||
|
||||
var label: Block.Label = .{
|
||||
.zir_block = inst,
|
||||
.merges = undefined,
|
||||
};
|
||||
child_block.label = &label;
|
||||
defer child_block.params.deinit(gpa);
|
||||
|
||||
// Write these instructions directly into the parent block
|
||||
child_block.instructions = block.instructions;
|
||||
defer block.instructions = child_block.instructions;
|
||||
|
||||
break :b try sema.analyzeBodyBreak(&child_block, inline_body);
|
||||
};
|
||||
|
||||
// A runtime conditional branch that needs a post-hoc block to be
|
||||
// emitted communicates this by mapping the block index into the inst map.
|
||||
@ -4968,7 +5005,7 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro
|
||||
|
||||
// Reserve space for a Block instruction so that generated Break instructions can
|
||||
// point to it, even if it doesn't end up getting used because the code ends up being
|
||||
// comptime evaluated.
|
||||
// comptime evaluated or is an unlabeled block.
|
||||
const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
|
||||
try sema.air_instructions.append(gpa, .{
|
||||
.tag = .block,
|
||||
@ -4999,6 +5036,7 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro
|
||||
.runtime_cond = parent_block.runtime_cond,
|
||||
.runtime_loop = parent_block.runtime_loop,
|
||||
.runtime_index = parent_block.runtime_index,
|
||||
.error_return_trace_index = parent_block.error_return_trace_index,
|
||||
};
|
||||
|
||||
defer child_block.instructions.deinit(gpa);
|
||||
@ -5641,6 +5679,117 @@ fn funcDeclSrc(sema: *Sema, block: *Block, src: LazySrcLoc, func_inst: Air.Inst.
|
||||
return owner_decl.srcLoc();
|
||||
}
|
||||
|
||||
pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref {
|
||||
const src = sema.src;
|
||||
|
||||
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
|
||||
if (!backend_supports_error_return_tracing or !sema.mod.comp.bin_file.options.error_return_tracing)
|
||||
return .none;
|
||||
|
||||
if (block.is_comptime)
|
||||
return .none;
|
||||
|
||||
const unresolved_stack_trace_ty = sema.getBuiltinType(block, src, "StackTrace") catch |err| switch (err) {
|
||||
error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
const stack_trace_ty = sema.resolveTypeFields(block, src, unresolved_stack_trace_ty) catch |err| switch (err) {
|
||||
error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
const field_index = sema.structFieldIndex(block, stack_trace_ty, "index", src) catch |err| switch (err) {
|
||||
error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
return try block.addInst(.{
|
||||
.tag = .save_err_return_trace_index,
|
||||
.data = .{ .ty_pl = .{
|
||||
.ty = try sema.addType(stack_trace_ty),
|
||||
.payload = @intCast(u32, field_index),
|
||||
} },
|
||||
});
|
||||
}
|
||||
|
||||
/// Add instructions to block to "pop" the error return trace.
|
||||
/// If `operand` is provided, only pops if operand is non-error.
|
||||
fn popErrorReturnTrace(
|
||||
sema: *Sema,
|
||||
block: *Block,
|
||||
src: LazySrcLoc,
|
||||
operand: Air.Inst.Ref,
|
||||
saved_error_trace_index: Air.Inst.Ref,
|
||||
) CompileError!void {
|
||||
var is_non_error: ?bool = null;
|
||||
var is_non_error_inst: Air.Inst.Ref = undefined;
|
||||
if (operand != .none) {
|
||||
is_non_error_inst = try sema.analyzeIsNonErr(block, src, operand);
|
||||
if (try sema.resolveDefinedValue(block, src, is_non_error_inst)) |cond_val|
|
||||
is_non_error = cond_val.toBool();
|
||||
} else is_non_error = true; // no operand means pop unconditionally
|
||||
|
||||
if (is_non_error == true) {
|
||||
// AstGen determined this result does not go to an error-handling expr (try/catch/return etc.), or
|
||||
// the result is comptime-known to be a non-error. Either way, pop unconditionally.
|
||||
|
||||
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.single_mut_pointer.create(sema.arena, stack_trace_ty);
|
||||
const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
|
||||
const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, "index", src, stack_trace_ty, true);
|
||||
try sema.storePtr2(block, src, field_ptr, src, saved_error_trace_index, src, .store);
|
||||
} else if (is_non_error == null) {
|
||||
// The result might be an error. If it is, we leave the error trace alone. If it isn't, we need
|
||||
// to pop any error trace that may have been propagated from our arguments.
|
||||
|
||||
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Block).Struct.fields.len);
|
||||
const cond_block_inst = try block.addInstAsIndex(.{
|
||||
.tag = .block,
|
||||
.data = .{
|
||||
.ty_pl = .{
|
||||
.ty = Air.Inst.Ref.void_type,
|
||||
.payload = undefined, // updated below
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var then_block = block.makeSubBlock();
|
||||
defer then_block.instructions.deinit(sema.gpa);
|
||||
|
||||
// If non-error, then pop the error return trace by restoring the index.
|
||||
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.single_mut_pointer.create(sema.arena, stack_trace_ty);
|
||||
const err_return_trace = try then_block.addTy(.err_return_trace, ptr_stack_trace_ty);
|
||||
const field_ptr = try sema.structFieldPtr(&then_block, src, err_return_trace, "index", src, stack_trace_ty, true);
|
||||
try sema.storePtr2(&then_block, src, field_ptr, src, saved_error_trace_index, src, .store);
|
||||
_ = try then_block.addBr(cond_block_inst, Air.Inst.Ref.void_value);
|
||||
|
||||
// Otherwise, do nothing
|
||||
var else_block = block.makeSubBlock();
|
||||
defer else_block.instructions.deinit(sema.gpa);
|
||||
_ = try else_block.addBr(cond_block_inst, Air.Inst.Ref.void_value);
|
||||
|
||||
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.CondBr).Struct.fields.len +
|
||||
then_block.instructions.items.len + else_block.instructions.items.len +
|
||||
@typeInfo(Air.Block).Struct.fields.len + 1); // +1 for the sole .cond_br instruction in the .block
|
||||
|
||||
const cond_br_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
|
||||
try sema.air_instructions.append(sema.gpa, .{ .tag = .cond_br, .data = .{ .pl_op = .{
|
||||
.operand = is_non_error_inst,
|
||||
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
|
||||
.then_body_len = @intCast(u32, then_block.instructions.items.len),
|
||||
.else_body_len = @intCast(u32, else_block.instructions.items.len),
|
||||
}),
|
||||
} } });
|
||||
sema.air_extra.appendSliceAssumeCapacity(then_block.instructions.items);
|
||||
sema.air_extra.appendSliceAssumeCapacity(else_block.instructions.items);
|
||||
|
||||
sema.air_instructions.items(.data)[cond_block_inst].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
|
||||
sema.air_extra.appendAssumeCapacity(cond_br_inst);
|
||||
}
|
||||
}
|
||||
|
||||
fn zirCall(
|
||||
sema: *Sema,
|
||||
block: *Block,
|
||||
@ -5657,6 +5806,7 @@ fn zirCall(
|
||||
|
||||
const modifier = @intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier);
|
||||
const ensure_result_used = extra.data.flags.ensure_result_used;
|
||||
const pop_error_return_trace = extra.data.flags.pop_error_return_trace;
|
||||
|
||||
var func = try sema.resolveInst(extra.data.callee);
|
||||
var resolved_args: []Air.Inst.Ref = undefined;
|
||||
@ -5729,6 +5879,9 @@ fn zirCall(
|
||||
|
||||
const args_body = sema.code.extra[extra.end..];
|
||||
|
||||
var input_is_error = false;
|
||||
const block_index = @intCast(Air.Inst.Index, block.instructions.items.len);
|
||||
|
||||
const parent_comptime = block.is_comptime;
|
||||
// `extra_index` and `arg_index` are separate since the bound function is passed as the first argument.
|
||||
var extra_index: usize = 0;
|
||||
@ -5746,10 +5899,8 @@ fn zirCall(
|
||||
else
|
||||
func_ty_info.param_types[arg_index];
|
||||
|
||||
const old_comptime = block.is_comptime;
|
||||
defer block.is_comptime = old_comptime;
|
||||
// Generate args to comptime params in comptime block.
|
||||
block.is_comptime = parent_comptime;
|
||||
defer block.is_comptime = parent_comptime;
|
||||
if (arg_index < fn_params_len and func_ty_info.comptime_params[arg_index]) {
|
||||
block.is_comptime = true;
|
||||
}
|
||||
@ -5758,13 +5909,58 @@ fn zirCall(
|
||||
try sema.inst_map.put(sema.gpa, inst, param_ty_inst);
|
||||
|
||||
const resolved = try sema.resolveBody(block, args_body[arg_start..arg_end], inst);
|
||||
if (sema.typeOf(resolved).zigTypeTag() == .NoReturn) {
|
||||
const resolved_ty = sema.typeOf(resolved);
|
||||
if (resolved_ty.zigTypeTag() == .NoReturn) {
|
||||
return resolved;
|
||||
}
|
||||
if (resolved_ty.isError()) {
|
||||
input_is_error = true;
|
||||
}
|
||||
resolved_args[arg_index] = resolved;
|
||||
}
|
||||
if (sema.owner_func == null or !sema.owner_func.?.calls_or_awaits_errorable_fn)
|
||||
input_is_error = false; // input was an error type, but no errorable fn's were actually called
|
||||
|
||||
return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
|
||||
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
|
||||
if (backend_supports_error_return_tracing and sema.mod.comp.bin_file.options.error_return_tracing and
|
||||
!block.is_comptime and (input_is_error or pop_error_return_trace))
|
||||
{
|
||||
const call_inst: Air.Inst.Ref = if (modifier == .always_tail) undefined else b: {
|
||||
break :b try sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
|
||||
};
|
||||
|
||||
const return_ty = sema.typeOf(call_inst);
|
||||
if (modifier != .always_tail and return_ty.isNoReturn())
|
||||
return call_inst; // call to "fn(...) noreturn", don't pop
|
||||
|
||||
// If any input is an error-type, we might need to pop any trace it generated. Otherwise, we only
|
||||
// need to clean-up our own trace if we were passed to a non-error-handling expression.
|
||||
if (input_is_error or (pop_error_return_trace and modifier != .always_tail and return_ty.isError())) {
|
||||
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, call_src, "StackTrace");
|
||||
const stack_trace_ty = try sema.resolveTypeFields(block, call_src, unresolved_stack_trace_ty);
|
||||
const field_index = try sema.structFieldIndex(block, stack_trace_ty, "index", call_src);
|
||||
|
||||
// Insert a save instruction before the arg resolution + call instructions we just generated
|
||||
const save_inst = try block.insertInst(block_index, .{
|
||||
.tag = .save_err_return_trace_index,
|
||||
.data = .{ .ty_pl = .{
|
||||
.ty = try sema.addType(stack_trace_ty),
|
||||
.payload = @intCast(u32, field_index),
|
||||
} },
|
||||
});
|
||||
|
||||
// Pop the error return trace, testing the result for non-error if necessary
|
||||
const operand = if (pop_error_return_trace or modifier == .always_tail) .none else call_inst;
|
||||
try sema.popErrorReturnTrace(block, call_src, operand, save_inst);
|
||||
}
|
||||
|
||||
if (modifier == .always_tail) // Perform the call *after* the restore, so that a tail call is possible.
|
||||
return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
|
||||
|
||||
return call_inst;
|
||||
} else {
|
||||
return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
|
||||
}
|
||||
}
|
||||
|
||||
const GenericCallAdapter = struct {
|
||||
@ -6056,6 +6252,10 @@ fn analyzeCall(
|
||||
sema.func = module_fn;
|
||||
defer sema.func = parent_func;
|
||||
|
||||
const parent_err_ret_index = sema.error_return_trace_index_on_fn_entry;
|
||||
sema.error_return_trace_index_on_fn_entry = block.error_return_trace_index;
|
||||
defer sema.error_return_trace_index_on_fn_entry = parent_err_ret_index;
|
||||
|
||||
var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, fn_owner_decl.src_scope);
|
||||
defer wip_captures.deinit();
|
||||
|
||||
@ -6069,6 +6269,7 @@ fn analyzeCall(
|
||||
.label = null,
|
||||
.inlining = &inlining,
|
||||
.is_comptime = is_comptime_call,
|
||||
.error_return_trace_index = block.error_return_trace_index,
|
||||
};
|
||||
|
||||
const merges = &child_block.inlining.?.merges;
|
||||
@ -6814,6 +7015,13 @@ fn instantiateGenericCall(
|
||||
}
|
||||
arg_i += 1;
|
||||
}
|
||||
|
||||
// Save the error trace as our first action in the function.
|
||||
// If this is unnecessary after all, Liveness will clean it up for us.
|
||||
const error_return_trace_index = try sema.analyzeSaveErrRetIndex(&child_block);
|
||||
child_sema.error_return_trace_index_on_fn_entry = error_return_trace_index;
|
||||
child_block.error_return_trace_index = error_return_trace_index;
|
||||
|
||||
const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| {
|
||||
// TODO look up the compile error that happened here and attach a note to it
|
||||
// pointing here, at the generic instantiation callsite.
|
||||
@ -9703,6 +9911,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
|
||||
.defer_err_code,
|
||||
.err_union_code,
|
||||
.ret_err_value_code,
|
||||
.restore_err_ret_index,
|
||||
.is_non_err,
|
||||
.condbr,
|
||||
=> {},
|
||||
@ -10005,6 +10214,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
|
||||
.runtime_cond = block.runtime_cond,
|
||||
.runtime_loop = block.runtime_loop,
|
||||
.runtime_index = block.runtime_index,
|
||||
.error_return_trace_index = block.error_return_trace_index,
|
||||
};
|
||||
const merges = &child_block.label.?.merges;
|
||||
defer child_block.instructions.deinit(gpa);
|
||||
@ -10888,6 +11098,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
|
||||
const tags = sema.code.instructions.items(.tag);
|
||||
for (body) |inst| {
|
||||
switch (tags[inst]) {
|
||||
.save_err_ret_index,
|
||||
.dbg_block_begin,
|
||||
.dbg_block_end,
|
||||
.dbg_stmt,
|
||||
@ -10910,6 +11121,10 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
|
||||
try sema.zirDbgStmt(block, inst);
|
||||
continue;
|
||||
},
|
||||
.save_err_ret_index => {
|
||||
try sema.zirSaveErrRetIndex(block, inst);
|
||||
continue;
|
||||
},
|
||||
.str => try sema.zirStr(block, inst),
|
||||
.as_node => try sema.zirAsNode(block, inst),
|
||||
.field_val => try sema.zirFieldVal(block, inst),
|
||||
@ -10955,6 +11170,7 @@ fn maybeErrorUnwrapCondbr(sema: *Sema, block: *Block, body: []const Zir.Inst.Ind
|
||||
return;
|
||||
}
|
||||
if (try sema.resolveDefinedValue(block, cond_src, err_operand)) |val| {
|
||||
if (!operand_ty.isError()) return;
|
||||
if (val.getError() == null) return;
|
||||
try sema.maybeErrorUnwrapComptime(block, body, err_operand);
|
||||
}
|
||||
@ -15519,6 +15735,7 @@ fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
|
||||
.is_comptime = false,
|
||||
.is_typeof = true,
|
||||
.want_safety = false,
|
||||
.error_return_trace_index = block.error_return_trace_index,
|
||||
};
|
||||
defer child_block.instructions.deinit(sema.gpa);
|
||||
|
||||
@ -16176,6 +16393,75 @@ fn wantErrorReturnTracing(sema: *Sema, fn_ret_ty: Type) bool {
|
||||
backend_supports_error_return_tracing;
|
||||
}
|
||||
|
||||
fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
|
||||
const inst_data = sema.code.instructions.items(.data)[inst].save_err_ret_index;
|
||||
|
||||
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
|
||||
const ok = backend_supports_error_return_tracing and sema.mod.comp.bin_file.options.error_return_tracing;
|
||||
if (!ok) return;
|
||||
|
||||
// This is only relevant at runtime.
|
||||
if (block.is_comptime) return;
|
||||
|
||||
// This is only relevant within functions.
|
||||
if (sema.func == null) return;
|
||||
|
||||
const save_index = inst_data.operand == .none or b: {
|
||||
const operand = try sema.resolveInst(inst_data.operand);
|
||||
const operand_ty = sema.typeOf(operand);
|
||||
break :b operand_ty.isError();
|
||||
};
|
||||
|
||||
if (save_index)
|
||||
block.error_return_trace_index = try sema.analyzeSaveErrRetIndex(block);
|
||||
}
|
||||
|
||||
fn zirRestoreErrRetIndex(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
|
||||
const inst_data = sema.code.instructions.items(.data)[inst].restore_err_ret_index;
|
||||
const src = sema.src; // TODO
|
||||
|
||||
// This is only relevant at runtime.
|
||||
if (start_block.is_comptime) return;
|
||||
|
||||
const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm;
|
||||
const ok = sema.owner_func.?.calls_or_awaits_errorable_fn and
|
||||
sema.mod.comp.bin_file.options.error_return_tracing and
|
||||
backend_supports_error_return_tracing;
|
||||
if (!ok) return;
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const saved_index = if (Zir.refToIndex(inst_data.block)) |zir_block| b: {
|
||||
var block = start_block;
|
||||
while (true) {
|
||||
if (block.label) |label| {
|
||||
if (label.zir_block == zir_block) {
|
||||
const target_trace_index = if (block.parent) |parent_block| tgt: {
|
||||
break :tgt parent_block.error_return_trace_index;
|
||||
} else sema.error_return_trace_index_on_fn_entry;
|
||||
|
||||
if (start_block.error_return_trace_index != target_trace_index)
|
||||
break :b target_trace_index;
|
||||
|
||||
return; // No need to restore
|
||||
}
|
||||
}
|
||||
block = block.parent.?;
|
||||
}
|
||||
} else b: {
|
||||
if (start_block.error_return_trace_index != sema.error_return_trace_index_on_fn_entry)
|
||||
break :b sema.error_return_trace_index_on_fn_entry;
|
||||
|
||||
return; // No need to restore
|
||||
};
|
||||
|
||||
assert(saved_index != .none); // The .error_return_trace_index field was dropped somewhere
|
||||
|
||||
const operand = try sema.resolveInst(inst_data.operand);
|
||||
return sema.popErrorReturnTrace(start_block, src, operand, saved_index);
|
||||
}
|
||||
|
||||
fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void {
|
||||
assert(sema.fn_ret_ty.zigTypeTag() == .ErrorUnion);
|
||||
|
||||
@ -17181,8 +17467,6 @@ fn zirBoolToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
|
||||
|
||||
fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
|
||||
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
|
||||
const src = inst_data.src();
|
||||
_ = src;
|
||||
const operand = try sema.resolveInst(inst_data.operand);
|
||||
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
|
||||
|
||||
|
||||
28
src/Zir.zig
28
src/Zir.zig
@ -988,6 +988,15 @@ pub const Inst = struct {
|
||||
/// Uses the `err_defer_code` union field.
|
||||
defer_err_code,
|
||||
|
||||
/// Requests that Sema update the saved error return trace index for the enclosing
|
||||
/// block, if the operand is .none or of an error/error-union type.
|
||||
/// Uses the `save_err_ret_index` field.
|
||||
save_err_ret_index,
|
||||
/// Sets error return trace to zero if no operand is given,
|
||||
/// otherwise sets the value to the given amount.
|
||||
/// Uses the `restore_err_ret_index` union field.
|
||||
restore_err_ret_index,
|
||||
|
||||
/// The ZIR instruction tag is one of the `Extended` ones.
|
||||
/// Uses the `extended` union field.
|
||||
extended,
|
||||
@ -1236,6 +1245,8 @@ pub const Inst = struct {
|
||||
//.try_ptr_inline,
|
||||
.@"defer",
|
||||
.defer_err_code,
|
||||
.save_err_ret_index,
|
||||
.restore_err_ret_index,
|
||||
=> false,
|
||||
|
||||
.@"break",
|
||||
@ -1305,6 +1316,8 @@ pub const Inst = struct {
|
||||
.check_comptime_control_flow,
|
||||
.@"defer",
|
||||
.defer_err_code,
|
||||
.restore_err_ret_index,
|
||||
.save_err_ret_index,
|
||||
=> true,
|
||||
|
||||
.param,
|
||||
@ -1810,6 +1823,9 @@ pub const Inst = struct {
|
||||
.@"defer" = .@"defer",
|
||||
.defer_err_code = .defer_err_code,
|
||||
|
||||
.save_err_ret_index = .save_err_ret_index,
|
||||
.restore_err_ret_index = .restore_err_ret_index,
|
||||
|
||||
.extended = .extended,
|
||||
});
|
||||
};
|
||||
@ -2586,6 +2602,13 @@ pub const Inst = struct {
|
||||
err_code: Ref,
|
||||
payload_index: u32,
|
||||
},
|
||||
save_err_ret_index: struct {
|
||||
operand: Ref, // If error type (or .none), save new trace index
|
||||
},
|
||||
restore_err_ret_index: struct {
|
||||
block: Ref, // If restored, the index is from this block's entrypoint
|
||||
operand: Ref, // If non-error (or .none), then restore the index
|
||||
},
|
||||
|
||||
// Make sure we don't accidentally add a field to make this union
|
||||
// bigger than expected. Note that in Debug builds, Zig is allowed
|
||||
@ -2624,6 +2647,8 @@ pub const Inst = struct {
|
||||
str_op,
|
||||
@"defer",
|
||||
defer_err_code,
|
||||
save_err_ret_index,
|
||||
restore_err_ret_index,
|
||||
};
|
||||
};
|
||||
|
||||
@ -2809,10 +2834,11 @@ pub const Inst = struct {
|
||||
pub const Flags = packed struct {
|
||||
/// std.builtin.CallOptions.Modifier in packed form
|
||||
pub const PackedModifier = u3;
|
||||
pub const PackedArgsLen = u28;
|
||||
pub const PackedArgsLen = u27;
|
||||
|
||||
packed_modifier: PackedModifier,
|
||||
ensure_result_used: bool = false,
|
||||
pop_error_return_trace: bool,
|
||||
args_len: PackedArgsLen,
|
||||
|
||||
comptime {
|
||||
|
||||
@ -702,6 +702,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
|
||||
.err_return_trace => try self.airErrReturnTrace(inst),
|
||||
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
|
||||
.save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
|
||||
|
||||
.wrap_optional => try self.airWrapOptional(inst),
|
||||
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
|
||||
@ -2867,6 +2868,11 @@ fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
|
||||
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
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: {
|
||||
|
||||
@ -751,6 +751,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
|
||||
.err_return_trace => try self.airErrReturnTrace(inst),
|
||||
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
|
||||
.save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
|
||||
|
||||
.wrap_optional => try self.airWrapOptional(inst),
|
||||
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
|
||||
@ -2116,6 +2117,11 @@ fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
|
||||
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
/// T to E!T
|
||||
fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
|
||||
@ -665,6 +665,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
|
||||
.err_return_trace => try self.airErrReturnTrace(inst),
|
||||
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
|
||||
.save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
|
||||
|
||||
.wrap_optional => try self.airWrapOptional(inst),
|
||||
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
|
||||
@ -1329,6 +1330,11 @@ fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
|
||||
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
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: {
|
||||
|
||||
@ -679,6 +679,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
|
||||
.err_return_trace => @panic("TODO try self.airErrReturnTrace(inst)"),
|
||||
.set_err_return_trace => @panic("TODO try self.airSetErrReturnTrace(inst)"),
|
||||
.save_err_return_trace_index=> @panic("TODO try self.airSaveErrReturnTraceIndex(inst)"),
|
||||
|
||||
.wrap_optional => try self.airWrapOptional(inst),
|
||||
.wrap_errunion_payload => @panic("TODO try self.airWrapErrUnionPayload(inst)"),
|
||||
|
||||
@ -1857,6 +1857,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
|
||||
.tag_name,
|
||||
.err_return_trace,
|
||||
.set_err_return_trace,
|
||||
.save_err_return_trace_index,
|
||||
.is_named_enum_value,
|
||||
.error_set_has_value,
|
||||
.addrspace_cast,
|
||||
|
||||
@ -756,6 +756,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
|
||||
.err_return_trace => try self.airErrReturnTrace(inst),
|
||||
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
|
||||
.save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
|
||||
|
||||
.wrap_optional => try self.airWrapOptional(inst),
|
||||
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
|
||||
@ -1973,6 +1974,11 @@ fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
|
||||
return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
||||
@ -1935,6 +1935,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
|
||||
.errunion_payload_ptr_set => try airErrUnionPayloadPtrSet(f, inst),
|
||||
.err_return_trace => try airErrReturnTrace(f, inst),
|
||||
.set_err_return_trace => try airSetErrReturnTrace(f, inst),
|
||||
.save_err_return_trace_index => try airSaveErrReturnTraceIndex(f, inst),
|
||||
|
||||
.wasm_memory_size => try airWasmMemorySize(f, inst),
|
||||
.wasm_memory_grow => try airWasmMemoryGrow(f, inst),
|
||||
@ -3625,6 +3626,11 @@ fn airSetErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue {
|
||||
return f.fail("TODO: C backend: implement airSetErrReturnTrace", .{});
|
||||
}
|
||||
|
||||
fn airSaveErrReturnTraceIndex(f: *Function, inst: Air.Inst.Index) !CValue {
|
||||
_ = inst;
|
||||
return f.fail("TODO: C backend: implement airSaveErrReturnTraceIndex", .{});
|
||||
}
|
||||
|
||||
fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue {
|
||||
if (f.liveness.isUnused(inst))
|
||||
return CValue.none;
|
||||
|
||||
@ -4592,6 +4592,7 @@ pub const FuncGen = struct {
|
||||
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
|
||||
.err_return_trace => try self.airErrReturnTrace(inst),
|
||||
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
|
||||
.save_err_return_trace_index => try self.airSaveErrReturnTraceIndex(inst),
|
||||
|
||||
.wrap_optional => try self.airWrapOptional(inst),
|
||||
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
|
||||
@ -6543,6 +6544,24 @@ pub const FuncGen = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn airSaveErrReturnTraceIndex(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
||||
if (self.liveness.isUnused(inst)) return null;
|
||||
|
||||
const target = self.dg.module.getTarget();
|
||||
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
//const struct_ty = try self.resolveInst(ty_pl.ty);
|
||||
const struct_ty = self.air.getRefType(ty_pl.ty);
|
||||
const field_index = ty_pl.payload;
|
||||
|
||||
var ptr_ty_buf: Type.Payload.Pointer = undefined;
|
||||
const llvm_field_index = llvmFieldIndex(struct_ty, field_index, target, &ptr_ty_buf).?;
|
||||
const struct_llvm_ty = try self.dg.lowerType(struct_ty);
|
||||
const field_ptr = self.builder.buildStructGEP(struct_llvm_ty, self.err_ret_trace.?, llvm_field_index, "");
|
||||
const field_ptr_ty = Type.initPayload(&ptr_ty_buf.base);
|
||||
return self.load(field_ptr, field_ptr_ty);
|
||||
}
|
||||
|
||||
fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
||||
if (self.liveness.isUnused(inst)) return null;
|
||||
|
||||
|
||||
@ -197,6 +197,7 @@ const Writer = struct {
|
||||
.unreach,
|
||||
.ret_addr,
|
||||
.frame_addr,
|
||||
.save_err_return_trace_index,
|
||||
=> try w.writeNoOp(s, inst),
|
||||
|
||||
.const_ty,
|
||||
|
||||
@ -254,6 +254,9 @@ const Writer = struct {
|
||||
.str => try self.writeStr(stream, inst),
|
||||
.int_type => try self.writeIntType(stream, inst),
|
||||
|
||||
.save_err_ret_index => try self.writeSaveErrRetIndex(stream, inst),
|
||||
.restore_err_ret_index => try self.writeRestoreErrRetIndex(stream, inst),
|
||||
|
||||
.@"break",
|
||||
.break_inline,
|
||||
=> try self.writeBreak(stream, inst),
|
||||
@ -440,7 +443,7 @@ const Writer = struct {
|
||||
|
||||
.dbg_block_begin,
|
||||
.dbg_block_end,
|
||||
=> try stream.writeAll("))"),
|
||||
=> try stream.writeAll(")"),
|
||||
|
||||
.closure_get => try self.writeInstNode(stream, inst),
|
||||
|
||||
@ -2272,6 +2275,22 @@ const Writer = struct {
|
||||
try self.writeSrc(stream, int_type.src());
|
||||
}
|
||||
|
||||
fn writeSaveErrRetIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
||||
const inst_data = self.code.instructions.items(.data)[inst].save_err_ret_index;
|
||||
|
||||
try self.writeInstRef(stream, inst_data.operand);
|
||||
try stream.writeAll(")");
|
||||
}
|
||||
|
||||
fn writeRestoreErrRetIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
||||
const inst_data = self.code.instructions.items(.data)[inst].restore_err_ret_index;
|
||||
|
||||
try self.writeInstRef(stream, inst_data.block);
|
||||
try stream.writeAll(", ");
|
||||
try self.writeInstRef(stream, inst_data.operand);
|
||||
try stream.writeAll(")");
|
||||
}
|
||||
|
||||
fn writeBreak(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
|
||||
const inst_data = self.code.instructions.items(.data)[inst].@"break";
|
||||
|
||||
|
||||
@ -2971,9 +2971,10 @@ pub const Value = extern union {
|
||||
};
|
||||
}
|
||||
|
||||
/// Valid for all types. Asserts the value is not undefined and not unreachable.
|
||||
/// Prefer `errorUnionIsPayload` to find out whether something is an error or not
|
||||
/// because it works without having to figure out the string.
|
||||
/// Valid only for error (union) types. Asserts the value is not undefined and not
|
||||
/// unreachable. For error unions, prefer `errorUnionIsPayload` to find out whether
|
||||
/// something is an error or not because it works without having to figure out the
|
||||
/// string.
|
||||
pub fn getError(self: Value) ?[]const u8 {
|
||||
return switch (self.tag()) {
|
||||
.@"error" => self.castTag(.@"error").?.data.name,
|
||||
|
||||
@ -7,6 +7,7 @@ test "issue12891" {
|
||||
try std.testing.expect(i < f);
|
||||
}
|
||||
test "nan" {
|
||||
if (builtin.zig_backend == .stage1) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
|
||||
const f = comptime std.math.nan(f64);
|
||||
|
||||
@ -830,3 +830,16 @@ test "compare error union and error set" {
|
||||
try expect(a != b);
|
||||
try expect(b != a);
|
||||
}
|
||||
|
||||
fn non_errorable() void {
|
||||
// Make sure catch works even in a function that does not call any errorable functions.
|
||||
//
|
||||
// This test is needed because stage 2's fix for #1923 means that catch blocks interact
|
||||
// with the error return trace index.
|
||||
var x: error{Foo}!void = {};
|
||||
return x catch {};
|
||||
}
|
||||
|
||||
test "catch within a function that calls no errorable functions" {
|
||||
non_errorable();
|
||||
}
|
||||
|
||||
@ -1401,7 +1401,21 @@ test "continue in inline for inside a comptime switch" {
|
||||
try expect(count == 4);
|
||||
}
|
||||
|
||||
test "length of global array is determinable at comptime" {
|
||||
const S = struct {
|
||||
var bytes: [1024]u8 = undefined;
|
||||
|
||||
fn foo() !void {
|
||||
try std.testing.expect(bytes.len == 1024);
|
||||
}
|
||||
};
|
||||
comptime try S.foo();
|
||||
}
|
||||
|
||||
test "continue nested inline for loop" {
|
||||
// TODO: https://github.com/ziglang/zig/issues/13175
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest;
|
||||
|
||||
var a: u8 = 0;
|
||||
loop: inline for ([_]u8{ 1, 2 }) |x| {
|
||||
inline for ([_]u8{1}) |y| {
|
||||
@ -1415,13 +1429,21 @@ test "continue nested inline for loop" {
|
||||
try expect(a == 2);
|
||||
}
|
||||
|
||||
test "length of global array is determinable at comptime" {
|
||||
const S = struct {
|
||||
var bytes: [1024]u8 = undefined;
|
||||
test "continue nested inline for loop in named block expr" {
|
||||
// TODO: https://github.com/ziglang/zig/issues/13175
|
||||
if (builtin.zig_backend != .stage1) return error.SkipZigTest;
|
||||
|
||||
fn foo() !void {
|
||||
try std.testing.expect(bytes.len == 1024);
|
||||
}
|
||||
};
|
||||
comptime try S.foo();
|
||||
var a: u8 = 0;
|
||||
loop: inline for ([_]u8{ 1, 2 }) |x| {
|
||||
a = b: {
|
||||
inline for ([_]u8{1}) |y| {
|
||||
if (x == y) {
|
||||
continue :loop;
|
||||
}
|
||||
}
|
||||
break :b x;
|
||||
};
|
||||
try expect(x == 2);
|
||||
}
|
||||
try expect(a == 2);
|
||||
}
|
||||
|
||||
@ -97,6 +97,547 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
|
||||
,
|
||||
},
|
||||
});
|
||||
cases.addCase(.{
|
||||
.name = "non-error return pops error trace",
|
||||
.source =
|
||||
\\fn bar() !void {
|
||||
\\ return error.UhOh;
|
||||
\\}
|
||||
\\
|
||||
\\fn foo() !void {
|
||||
\\ bar() catch {
|
||||
\\ return; // non-error result: success
|
||||
\\ };
|
||||
\\}
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ try foo();
|
||||
\\ return error.UnrelatedError;
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: UnrelatedError
|
||||
\\source.zig:13:5: [address] in main (test)
|
||||
\\ return error.UnrelatedError;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
.linux, // defeated by aggressive inlining
|
||||
},
|
||||
.expect =
|
||||
\\error: UnrelatedError
|
||||
\\source.zig:13:5: [address] in [function]
|
||||
\\ return error.UnrelatedError;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: UnrelatedError
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: UnrelatedError
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "try return + handled catch/if-else",
|
||||
.source =
|
||||
\\fn foo() !void {
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\}
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ foo() catch {}; // should not affect error trace
|
||||
\\ if (foo()) |_| {} else |_| {
|
||||
\\ // should also not affect error trace
|
||||
\\ }
|
||||
\\ try foo();
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: TheSkyIsFalling
|
||||
\\source.zig:2:5: [address] in foo (test)
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\ ^
|
||||
\\source.zig:10:5: [address] in main (test)
|
||||
\\ try foo();
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
.linux, // defeated by aggressive inlining
|
||||
},
|
||||
.expect =
|
||||
\\error: TheSkyIsFalling
|
||||
\\source.zig:2:5: [address] in [function]
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\ ^
|
||||
\\source.zig:10:5: [address] in [function]
|
||||
\\ try foo();
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: TheSkyIsFalling
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: TheSkyIsFalling
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "break from inline loop pops error return trace",
|
||||
.source =
|
||||
\\fn foo() !void { return error.FooBar; }
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ comptime var i: usize = 0;
|
||||
\\ b: inline while (i < 5) : (i += 1) {
|
||||
\\ foo() catch {
|
||||
\\ break :b; // non-error break, success
|
||||
\\ };
|
||||
\\ }
|
||||
\\ // foo() was successfully handled, should not appear in trace
|
||||
\\
|
||||
\\ return error.BadTime;
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: BadTime
|
||||
\\source.zig:12:5: [address] in main (test)
|
||||
\\ return error.BadTime;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
.linux, // defeated by aggressive inlining
|
||||
},
|
||||
.expect =
|
||||
\\error: BadTime
|
||||
\\source.zig:12:5: [address] in [function]
|
||||
\\ return error.BadTime;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: BadTime
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: BadTime
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "catch and re-throw error",
|
||||
.source =
|
||||
\\fn foo() !void {
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\}
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ return foo() catch error.AndMyCarIsOutOfGas;
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\source.zig:2:5: [address] in foo (test)
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\ ^
|
||||
\\source.zig:6:5: [address] in main (test)
|
||||
\\ return foo() catch error.AndMyCarIsOutOfGas;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
.linux, // defeated by aggressive inlining
|
||||
},
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\source.zig:2:5: [address] in [function]
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\ ^
|
||||
\\source.zig:6:5: [address] in [function]
|
||||
\\ return foo() catch error.AndMyCarIsOutOfGas;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "errors stored in var do not contribute to error trace",
|
||||
.source =
|
||||
\\fn foo() !void {
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\}
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ // Once an error is stored in a variable, it is popped from the trace
|
||||
\\ var x = foo();
|
||||
\\ x = {};
|
||||
\\
|
||||
\\ // As a result, this error trace will still be clean
|
||||
\\ return error.SomethingUnrelatedWentWrong;
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: SomethingUnrelatedWentWrong
|
||||
\\source.zig:11:5: [address] in main (test)
|
||||
\\ return error.SomethingUnrelatedWentWrong;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
.linux, // defeated by aggressive inlining
|
||||
},
|
||||
.expect =
|
||||
\\error: SomethingUnrelatedWentWrong
|
||||
\\source.zig:11:5: [address] in [function]
|
||||
\\ return error.SomethingUnrelatedWentWrong;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: SomethingUnrelatedWentWrong
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: SomethingUnrelatedWentWrong
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "error stored in const has trace preserved for duration of block",
|
||||
.source =
|
||||
\\fn foo() !void { return error.TheSkyIsFalling; }
|
||||
\\fn bar() !void { return error.InternalError; }
|
||||
\\fn baz() !void { return error.UnexpectedReality; }
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ const x = foo();
|
||||
\\ const y = b: {
|
||||
\\ if (true)
|
||||
\\ break :b bar();
|
||||
\\
|
||||
\\ break :b {};
|
||||
\\ };
|
||||
\\ x catch {};
|
||||
\\ y catch {};
|
||||
\\ // foo()/bar() error traces not popped until end of block
|
||||
\\
|
||||
\\ {
|
||||
\\ const z = baz();
|
||||
\\ z catch {};
|
||||
\\ // baz() error trace still alive here
|
||||
\\ }
|
||||
\\ // baz() error trace popped, foo(), bar() still alive
|
||||
\\ return error.StillUnresolved;
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: StillUnresolved
|
||||
\\source.zig:1:18: [address] in foo (test)
|
||||
\\fn foo() !void { return error.TheSkyIsFalling; }
|
||||
\\ ^
|
||||
\\source.zig:2:18: [address] in bar (test)
|
||||
\\fn bar() !void { return error.InternalError; }
|
||||
\\ ^
|
||||
\\source.zig:23:5: [address] in main (test)
|
||||
\\ return error.StillUnresolved;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
.linux, // defeated by aggressive inlining
|
||||
},
|
||||
.expect =
|
||||
\\error: StillUnresolved
|
||||
\\source.zig:1:18: [address] in [function]
|
||||
\\fn foo() !void { return error.TheSkyIsFalling; }
|
||||
\\ ^
|
||||
\\source.zig:2:18: [address] in [function]
|
||||
\\fn bar() !void { return error.InternalError; }
|
||||
\\ ^
|
||||
\\source.zig:23:5: [address] in [function]
|
||||
\\ return error.StillUnresolved;
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: StillUnresolved
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: StillUnresolved
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "error passed to function has its trace preserved for duration of the call",
|
||||
.source =
|
||||
\\pub fn expectError(expected_error: anyerror, actual_error: anyerror!void) !void {
|
||||
\\ actual_error catch |err| {
|
||||
\\ if (err == expected_error) return {};
|
||||
\\ };
|
||||
\\ return error.TestExpectedError;
|
||||
\\}
|
||||
\\
|
||||
\\fn alwaysErrors() !void { return error.ThisErrorShouldNotAppearInAnyTrace; }
|
||||
\\fn foo() !void { return error.Foo; }
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
|
||||
\\ try expectError(error.ThisErrorShouldNotAppearInAnyTrace, alwaysErrors());
|
||||
\\ try expectError(error.Foo, foo());
|
||||
\\
|
||||
\\ // Only the error trace for this failing check should appear:
|
||||
\\ try expectError(error.Bar, foo());
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: TestExpectedError
|
||||
\\source.zig:9:18: [address] in foo (test)
|
||||
\\fn foo() !void { return error.Foo; }
|
||||
\\ ^
|
||||
\\source.zig:5:5: [address] in expectError (test)
|
||||
\\ return error.TestExpectedError;
|
||||
\\ ^
|
||||
\\source.zig:17:5: [address] in main (test)
|
||||
\\ try expectError(error.Bar, foo());
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
},
|
||||
.expect =
|
||||
\\error: TestExpectedError
|
||||
\\source.zig:9:18: [address] in [function]
|
||||
\\fn foo() !void { return error.Foo; }
|
||||
\\ ^
|
||||
\\source.zig:5:5: [address] in [function]
|
||||
\\ return error.TestExpectedError;
|
||||
\\ ^
|
||||
\\source.zig:17:5: [address] in [function]
|
||||
\\ try expectError(error.Bar, foo());
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: TestExpectedError
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: TestExpectedError
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "try return from within catch",
|
||||
.source =
|
||||
\\fn foo() !void {
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\}
|
||||
\\
|
||||
\\fn bar() !void {
|
||||
\\ return error.AndMyCarIsOutOfGas;
|
||||
\\}
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ foo() catch { // error trace should include foo()
|
||||
\\ try bar();
|
||||
\\ };
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\source.zig:2:5: [address] in foo (test)
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\ ^
|
||||
\\source.zig:6:5: [address] in bar (test)
|
||||
\\ return error.AndMyCarIsOutOfGas;
|
||||
\\ ^
|
||||
\\source.zig:11:9: [address] in main (test)
|
||||
\\ try bar();
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
},
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\source.zig:2:5: [address] in [function]
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\ ^
|
||||
\\source.zig:6:5: [address] in [function]
|
||||
\\ return error.AndMyCarIsOutOfGas;
|
||||
\\ ^
|
||||
\\source.zig:11:9: [address] in [function]
|
||||
\\ try bar();
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "try return from within if-else",
|
||||
.source =
|
||||
\\fn foo() !void {
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\}
|
||||
\\
|
||||
\\fn bar() !void {
|
||||
\\ return error.AndMyCarIsOutOfGas;
|
||||
\\}
|
||||
\\
|
||||
\\pub fn main() !void {
|
||||
\\ if (foo()) |_| {} else |_| { // error trace should include foo()
|
||||
\\ try bar();
|
||||
\\ }
|
||||
\\}
|
||||
,
|
||||
.Debug = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\source.zig:2:5: [address] in foo (test)
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\ ^
|
||||
\\source.zig:6:5: [address] in bar (test)
|
||||
\\ return error.AndMyCarIsOutOfGas;
|
||||
\\ ^
|
||||
\\source.zig:11:9: [address] in main (test)
|
||||
\\ try bar();
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSafe = .{
|
||||
.exclude_os = .{
|
||||
.windows, // TODO
|
||||
},
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\source.zig:2:5: [address] in [function]
|
||||
\\ return error.TheSkyIsFalling;
|
||||
\\ ^
|
||||
\\source.zig:6:5: [address] in [function]
|
||||
\\ return error.AndMyCarIsOutOfGas;
|
||||
\\ ^
|
||||
\\source.zig:11:9: [address] in [function]
|
||||
\\ try bar();
|
||||
\\ ^
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseFast = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\
|
||||
,
|
||||
},
|
||||
.ReleaseSmall = .{
|
||||
.expect =
|
||||
\\error: AndMyCarIsOutOfGas
|
||||
\\
|
||||
,
|
||||
},
|
||||
});
|
||||
|
||||
cases.addCase(.{
|
||||
.name = "try try return return",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user