authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-08-08 19:16:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-08-08 19:16:21-07:00
log1a500b969943dc6a4c549fdcbd72659702e867a2
treeb65d3f11b30e1c43f0a63636fe908929e83b0c27
parent50b36e84fab023ec418eb794d6a5b4510ac6b984

Sema: avoid error return traces when possible

stage2 was adding bogus error return trace frames when an error was not being returned. This commit makes several improvements: * Make a runtime check if necessary to only emit a frame into the error return trace when an actual error is returned. * Use the `analyzeIsNonErrComptimeOnly` machinery to avoid runtime checks when it is compile-time-known that the value is an error, or a non-error. * Make std.builtin.returnError take a non-optional stack trace pointer. closes #12174

2 files changed, 69 insertions(+), 13 deletions(-)

lib/std/builtin.zig+1-2
......@@ -867,10 +867,9 @@ pub fn panicOutOfBounds(index: usize, len: usize) noreturn {
867867 std.debug.panic("attempt to index out of bound: index {d}, len {d}", .{ index, len });
868868}
869869
870pub noinline fn returnError(maybe_st: ?*StackTrace) void {
870pub noinline fn returnError(st: *StackTrace) void {
871871 @setCold(true);
872872 @setRuntimeSafety(false);
873 const st = maybe_st orelse return;
874873 addErrRetTraceAddr(st, @returnAddress());
875874}
876875
src/Sema.zig+68-11
......@@ -14492,6 +14492,20 @@ fn zirBoolBr(
1449214492 const rhs_result = try sema.resolveBody(rhs_block, body, inst);
1449314493 _ = try rhs_block.addBr(block_inst, rhs_result);
1449414494
14495 return finishCondBr(sema, parent_block, &child_block, &then_block, &else_block, lhs, block_inst);
14496}
14497
14498fn finishCondBr(
14499 sema: *Sema,
14500 parent_block: *Block,
14501 child_block: *Block,
14502 then_block: *Block,
14503 else_block: *Block,
14504 cond: Air.Inst.Ref,
14505 block_inst: Air.Inst.Index,
14506) !Air.Inst.Ref {
14507 const gpa = sema.gpa;
14508
1449514509 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
1449614510 then_block.instructions.items.len + else_block.instructions.items.len +
1449714511 @typeInfo(Air.Block).Struct.fields.len + child_block.instructions.items.len + 1);
......@@ -14504,7 +14518,7 @@ fn zirBoolBr(
1450414518 sema.air_extra.appendSliceAssumeCapacity(else_block.instructions.items);
1450514519
1450614520 _ = try child_block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{
14507 .operand = lhs,
14521 .operand = cond,
1450814522 .payload = cond_br_payload,
1450914523 } } });
1451014524
......@@ -14901,6 +14915,8 @@ fn analyzeRet(
1490114915 uncasted_operand: Air.Inst.Ref,
1490214916 src: LazySrcLoc,
1490314917) CompileError!Zir.Inst.Index {
14918 const gpa = sema.gpa;
14919
1490414920 // Special case for returning an error to an inferred error set; we need to
1490514921 // add the error tag to the inferred error set of the in-scope function, so
1490614922 // that the coercion below works correctly.
......@@ -14918,11 +14934,13 @@ fn analyzeRet(
1491814934 return error.ComptimeReturn;
1491914935 }
1492014936 // We are inlining a function call; rewrite the `ret` as a `break`.
14921 try inlining.merges.results.append(sema.gpa, operand);
14937 try inlining.merges.results.append(gpa, operand);
1492214938 _ = try block.addBr(inlining.merges.block_inst, operand);
1492314939 return always_noreturn;
1492414940 }
1492514941
14942 try sema.resolveTypeLayout(block, src, sema.fn_ret_ty);
14943
1492614944 // TODO implement this feature in all the backends and then delete this check.
1492714945 const backend_supports_error_return_tracing =
1492814946 sema.mod.comp.bin_file.options.use_llvm;
......@@ -14931,19 +14949,52 @@ fn analyzeRet(
1493114949 sema.mod.comp.bin_file.options.error_return_tracing and
1493214950 backend_supports_error_return_tracing)
1493314951 ret_err: {
14934 if (try sema.resolveMaybeUndefVal(block, src, operand)) |ret_val| {
14935 if (ret_val.tag() != .@"error") break :ret_err;
14936 }
14937 const return_err_fn = try sema.getBuiltin(block, src, "returnError");
14952 // Avoid adding a frame to the error return trace in case the value is comptime-known
14953 // to be not an error.
14954 const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, src, operand);
14955 const need_check = switch (is_non_err) {
14956 .bool_true => break :ret_err,
14957 .bool_false => false,
14958 else => true,
14959 };
14960
1493814961 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
1493914962 const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
14940 const ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty);
14963 const ptr_stack_trace_ty = try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty);
1494114964 const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
14965 const return_err_fn = try sema.getBuiltin(block, src, "returnError");
1494214966 const args: [1]Air.Inst.Ref = .{err_return_trace};
14967
14968 if (!need_check) {
14969 _ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args, null);
14970 break :ret_err;
14971 }
14972
14973 const block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
14974 try sema.air_instructions.append(gpa, .{
14975 .tag = .block,
14976 .data = .{ .ty_pl = .{
14977 .ty = .void_type,
14978 .payload = undefined,
14979 } },
14980 });
14981
14982 var child_block = block.makeSubBlock();
14983 defer child_block.instructions.deinit(gpa);
14984
14985 var then_block = child_block.makeSubBlock();
14986 defer then_block.instructions.deinit(gpa);
14987 _ = try then_block.addUnOp(.ret, operand);
14988
14989 var else_block = child_block.makeSubBlock();
14990 defer else_block.instructions.deinit(gpa);
1494314991 _ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args, null);
14992 _ = try else_block.addUnOp(.ret, operand);
14993
14994 _ = try finishCondBr(sema, block, &child_block, &then_block, &else_block, is_non_err, block_inst);
14995 return always_noreturn;
1494414996 }
1494514997
14946 try sema.resolveTypeLayout(block, src, sema.fn_ret_ty);
1494714998 _ = try block.addUnOp(.ret, operand);
1494814999 return always_noreturn;
1494915000}
......@@ -25436,10 +25487,16 @@ fn analyzeIsNonErrComptimeOnly(
2543625487 assert(ot == .ErrorUnion);
2543725488
2543825489 if (Air.refToIndex(operand)) |operand_inst| {
25439 const air_tags = sema.air_instructions.items(.tag);
25440 if (air_tags[operand_inst] == .wrap_errunion_payload) {
25441 return Air.Inst.Ref.bool_true;
25490 switch (sema.air_instructions.items(.tag)[operand_inst]) {
25491 .wrap_errunion_payload => return Air.Inst.Ref.bool_true,
25492 .wrap_errunion_err => return Air.Inst.Ref.bool_false,
25493 else => {},
2544225494 }
25495 } else if (operand == .undef) {
25496 return sema.addConstUndef(Type.bool);
25497 } else {
25498 // None of the ref tags can be errors.
25499 return Air.Inst.Ref.bool_true;
2544325500 }
2544425501
2544525502 const maybe_operand_val = try sema.resolveMaybeUndefVal(block, src, operand);