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 {...@@ -867,10 +867,9 @@ pub fn panicOutOfBounds(index: usize, len: usize) noreturn {
867 std.debug.panic("attempt to index out of bound: index {d}, len {d}", .{ index, len });867 std.debug.panic("attempt to index out of bound: index {d}, len {d}", .{ index, len });
868}868}
869869
870pub noinline fn returnError(maybe_st: ?*StackTrace) void {870pub noinline fn returnError(st: *StackTrace) void {
871 @setCold(true);871 @setCold(true);
872 @setRuntimeSafety(false);872 @setRuntimeSafety(false);
873 const st = maybe_st orelse return;
874 addErrRetTraceAddr(st, @returnAddress());873 addErrRetTraceAddr(st, @returnAddress());
875}874}
876875
src/Sema.zig+68-11
...@@ -14492,6 +14492,20 @@ fn zirBoolBr(...@@ -14492,6 +14492,20 @@ fn zirBoolBr(
14492 const rhs_result = try sema.resolveBody(rhs_block, body, inst);14492 const rhs_result = try sema.resolveBody(rhs_block, body, inst);
14493 _ = try rhs_block.addBr(block_inst, rhs_result);14493 _ = 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
14495 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +14509 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
14496 then_block.instructions.items.len + else_block.instructions.items.len +14510 then_block.instructions.items.len + else_block.instructions.items.len +
14497 @typeInfo(Air.Block).Struct.fields.len + child_block.instructions.items.len + 1);14511 @typeInfo(Air.Block).Struct.fields.len + child_block.instructions.items.len + 1);
...@@ -14504,7 +14518,7 @@ fn zirBoolBr(...@@ -14504,7 +14518,7 @@ fn zirBoolBr(
14504 sema.air_extra.appendSliceAssumeCapacity(else_block.instructions.items);14518 sema.air_extra.appendSliceAssumeCapacity(else_block.instructions.items);
1450514519
14506 _ = try child_block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{14520 _ = try child_block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{
14507 .operand = lhs,14521 .operand = cond,
14508 .payload = cond_br_payload,14522 .payload = cond_br_payload,
14509 } } });14523 } } });
1451014524
...@@ -14901,6 +14915,8 @@ fn analyzeRet(...@@ -14901,6 +14915,8 @@ fn analyzeRet(
14901 uncasted_operand: Air.Inst.Ref,14915 uncasted_operand: Air.Inst.Ref,
14902 src: LazySrcLoc,14916 src: LazySrcLoc,
14903) CompileError!Zir.Inst.Index {14917) CompileError!Zir.Inst.Index {
14918 const gpa = sema.gpa;
14919
14904 // Special case for returning an error to an inferred error set; we need to14920 // Special case for returning an error to an inferred error set; we need to
14905 // add the error tag to the inferred error set of the in-scope function, so14921 // add the error tag to the inferred error set of the in-scope function, so
14906 // that the coercion below works correctly.14922 // that the coercion below works correctly.
...@@ -14918,11 +14934,13 @@ fn analyzeRet(...@@ -14918,11 +14934,13 @@ fn analyzeRet(
14918 return error.ComptimeReturn;14934 return error.ComptimeReturn;
14919 }14935 }
14920 // We are inlining a function call; rewrite the `ret` as a `break`.14936 // 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);
14922 _ = try block.addBr(inlining.merges.block_inst, operand);14938 _ = try block.addBr(inlining.merges.block_inst, operand);
14923 return always_noreturn;14939 return always_noreturn;
14924 }14940 }
1492514941
14942 try sema.resolveTypeLayout(block, src, sema.fn_ret_ty);
14943
14926 // TODO implement this feature in all the backends and then delete this check.14944 // TODO implement this feature in all the backends and then delete this check.
14927 const backend_supports_error_return_tracing =14945 const backend_supports_error_return_tracing =
14928 sema.mod.comp.bin_file.options.use_llvm;14946 sema.mod.comp.bin_file.options.use_llvm;
...@@ -14931,19 +14949,52 @@ fn analyzeRet(...@@ -14931,19 +14949,52 @@ fn analyzeRet(
14931 sema.mod.comp.bin_file.options.error_return_tracing and14949 sema.mod.comp.bin_file.options.error_return_tracing and
14932 backend_supports_error_return_tracing)14950 backend_supports_error_return_tracing)
14933 ret_err: {14951 ret_err: {
14934 if (try sema.resolveMaybeUndefVal(block, src, operand)) |ret_val| {14952 // Avoid adding a frame to the error return trace in case the value is comptime-known
14935 if (ret_val.tag() != .@"error") break :ret_err;14953 // to be not an error.
14936 }14954 const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, src, operand);
14937 const return_err_fn = try sema.getBuiltin(block, src, "returnError");14955 const need_check = switch (is_non_err) {
14956 .bool_true => break :ret_err,
14957 .bool_false => false,
14958 else => true,
14959 };
14960
14938 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");14961 const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
14939 const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);14962 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);
14941 const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);14964 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");
14942 const args: [1]Air.Inst.Ref = .{err_return_trace};14966 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);
14943 _ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args, null);14991 _ = 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;
14944 }14996 }
1494514997
14946 try sema.resolveTypeLayout(block, src, sema.fn_ret_ty);
14947 _ = try block.addUnOp(.ret, operand);14998 _ = try block.addUnOp(.ret, operand);
14948 return always_noreturn;14999 return always_noreturn;
14949}15000}
...@@ -25436,10 +25487,16 @@ fn analyzeIsNonErrComptimeOnly(...@@ -25436,10 +25487,16 @@ fn analyzeIsNonErrComptimeOnly(
25436 assert(ot == .ErrorUnion);25487 assert(ot == .ErrorUnion);
2543725488
25438 if (Air.refToIndex(operand)) |operand_inst| {25489 if (Air.refToIndex(operand)) |operand_inst| {
25439 const air_tags = sema.air_instructions.items(.tag);25490 switch (sema.air_instructions.items(.tag)[operand_inst]) {
25440 if (air_tags[operand_inst] == .wrap_errunion_payload) {25491 .wrap_errunion_payload => return Air.Inst.Ref.bool_true,
25441 return Air.Inst.Ref.bool_true;25492 .wrap_errunion_err => return Air.Inst.Ref.bool_false,
25493 else => {},
25442 }25494 }
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;
25443 }25500 }
2544425501
25445 const maybe_operand_val = try sema.resolveMaybeUndefVal(block, src, operand);25502 const maybe_operand_val = try sema.resolveMaybeUndefVal(block, src, operand);