add @trap builtin

This introduces a new builtin function that compiles down to something that results in an illegal instruction exception/interrupt.
It can be used to exit a program abnormally.

This implements the builtin for all backends.
This commit is contained in:
r00ster91 2023-03-03 18:35:03 +01:00
parent d6bd00e855
commit 65368683ad
26 changed files with 178 additions and 10 deletions

View file

@ -7818,12 +7818,14 @@ comptime {
<p>
This function inserts a platform-specific debug trap instruction which causes
debuggers to break there.
Unlike for {#syntax#}@trap(){#endsyntax#}, execution may continue after this point if the program is resumed.
</p>
<p>
This function is only valid within function scope.
</p>
{#see_also|@trap#}
{#header_close#}
{#header_open|@mulAdd#}
<pre>{#syntax#}@mulAdd(comptime T: type, a: T, b: T, c: T) T{#endsyntax#}</pre>
<p>
@ -9393,6 +9395,19 @@ fn List(comptime T: type) type {
</p>
{#header_close#}
{#header_open|@trap#}
<pre>{#syntax#}@trap() noreturn{#endsyntax#}</pre>
<p>
This function inserts a platform-specific trap/jam instruction which can be used to exit the program abnormally.
This may be implemented by explicitly emitting an invalid instruction which may cause an illegal instruction exception of some sort.
Unlike for {#syntax#}@breakpoint(){#endsyntax#}, execution does not continue after this point.
</p>
<p>
This function is only valid within function scope.
</p>
{#see_also|@breakpoint#}
{#header_close#}
{#header_open|@truncate#}
<pre>{#syntax#}@truncate(comptime T: type, integer: anytype) T{#endsyntax#}</pre>
<p>

View file

@ -180,10 +180,16 @@ typedef char bool;
#define zig_export(sig, symbol, name) __asm(name " = " symbol)
#endif
#if zig_has_builtin(trap)
#define zig_trap() __builtin_trap()
#elif defined(__i386__) || defined(__x86_64__)
#define zig_trap() __asm__ volatile("ud2");
#else
#define zig_trap() raise(SIGILL)
#endif
#if zig_has_builtin(debugtrap)
#define zig_breakpoint() __builtin_debugtrap()
#elif zig_has_builtin(trap) || defined(zig_gnuc)
#define zig_breakpoint() __builtin_trap()
#elif defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
#define zig_breakpoint() __debugbreak()
#elif defined(__i386__) || defined(__x86_64__)

View file

@ -232,7 +232,14 @@ pub const Inst = struct {
/// Result type is always noreturn; no instructions in a block follow this one.
/// Uses the `br` field.
br,
/// Lowers to a hardware trap instruction, or the next best thing.
/// Lowers to a trap/jam instruction causing program abortion.
/// This may lower to an instruction known to be invalid.
/// Sometimes, for the lack of a better instruction, `trap` and `breakpoint` may compile down to the same code.
/// Result type is always noreturn; no instructions in a block follow this one.
trap,
/// Lowers to a trap instruction causing debuggers to break here, or the next best thing.
/// The debugger or something else may allow the program to resume after this point.
/// Sometimes, for the lack of a better instruction, `trap` and `breakpoint` may compile down to the same code.
/// Result type is always void.
breakpoint,
/// Yields the return address of the current function.
@ -1186,6 +1193,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.ret,
.ret_load,
.unreach,
.trap,
=> return Type.initTag(.noreturn),
.breakpoint,

View file

@ -2631,6 +2631,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.repeat_inline,
.panic,
.panic_comptime,
.trap,
.check_comptime_control_flow,
=> {
noreturn_src_node = statement;
@ -8178,6 +8179,11 @@ fn builtinCall(
try emitDbgNode(gz, node);
return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .ty = .const_slice_u8_type } }, params[0], if (gz.force_comptime) .panic_comptime else .panic);
},
.trap => {
try emitDbgNode(gz, node);
_ = try gz.addNode(.trap, node);
return rvalue(gz, ri, .void_value, node);
},
.error_to_int => {
const operand = try expr(gz, scope, .{ .rl = .none }, params[0]);
const result = try gz.addExtendedPayload(.error_to_int, Zir.Inst.UnNode{

View file

@ -109,6 +109,7 @@ pub const Tag = enum {
sub_with_overflow,
tag_name,
This,
trap,
truncate,
Type,
type_info,
@ -915,6 +916,13 @@ pub const list = list: {
.param_count = 0,
},
},
.{
"@trap",
.{
.tag = .trap,
.param_count = 0,
},
},
.{
"@truncate",
.{

View file

@ -226,6 +226,7 @@ pub fn categorizeOperand(
.ret_ptr,
.constant,
.const_ty,
.trap,
.breakpoint,
.dbg_stmt,
.dbg_inline_begin,
@ -848,6 +849,7 @@ fn analyzeInst(
.ret_ptr,
.constant,
.const_ty,
.trap,
.breakpoint,
.dbg_stmt,
.dbg_inline_begin,

View file

@ -1101,6 +1101,7 @@ fn analyzeBodyInner(
.@"unreachable" => break sema.zirUnreachable(block, inst),
.panic => break sema.zirPanic(block, inst, false),
.panic_comptime => break sema.zirPanic(block, inst, true),
.trap => break sema.zirTrap(block, inst),
// zig fmt: on
.extended => ext: {
@ -5144,6 +5145,14 @@ fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index, force_comptime: bo
return always_noreturn;
}
fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {
const src_node = sema.code.instructions.items(.data)[inst].node;
const src = LazySrcLoc.nodeOffset(src_node);
sema.src = src;
_ = try block.addNoOp(.trap);
return always_noreturn;
}
fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();

View file

@ -617,7 +617,7 @@ pub const Inst = struct {
/// Uses the `un_node` field.
typeof_log2_int_type,
/// Asserts control-flow will not reach this instruction (`unreachable`).
/// Uses the `unreachable` union field.
/// Uses the `@"unreachable"` union field.
@"unreachable",
/// Bitwise XOR. `^`
/// Uses the `pl_node` union field. Payload is `Bin`.
@ -808,6 +808,9 @@ pub const Inst = struct {
panic,
/// Same as `panic` but forces comptime.
panic_comptime,
/// Implements `@trap`.
/// Uses the `node` field.
trap,
/// Implement builtin `@setRuntimeSafety`. Uses `un_node`.
set_runtime_safety,
/// Implement builtin `@sqrt`. Uses `un_node`.
@ -1274,6 +1277,7 @@ pub const Inst = struct {
.repeat_inline,
.panic,
.panic_comptime,
.trap,
.check_comptime_control_flow,
=> true,
};
@ -1549,6 +1553,7 @@ pub const Inst = struct {
.repeat_inline,
.panic,
.panic_comptime,
.trap,
.for_len,
.@"try",
.try_ptr,
@ -1746,6 +1751,7 @@ pub const Inst = struct {
.error_name = .un_node,
.panic = .un_node,
.panic_comptime = .un_node,
.trap = .node,
.set_runtime_safety = .un_node,
.sqrt = .un_node,
.sin = .un_node,
@ -1982,6 +1988,7 @@ pub const Inst = struct {
err_set_cast,
/// `operand` is payload index to `UnNode`.
await_nosuspend,
/// Implements `@breakpoint`.
/// `operand` is `src_node: i32`.
breakpoint,
/// Implements the `@select` builtin.
@ -1995,7 +2002,7 @@ pub const Inst = struct {
int_to_error,
/// Implement builtin `@Type`.
/// `operand` is payload index to `UnNode`.
/// `small` contains `NameStrategy
/// `small` contains `NameStrategy`.
reify,
/// Implements the `@asyncCall` builtin.
/// `operand` is payload index to `AsyncCall`.

View file

@ -737,6 +737,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.bitcast => try self.airBitCast(inst),
.block => try self.airBlock(inst),
.br => try self.airBr(inst),
.trap => try self.airTrap(),
.breakpoint => try self.airBreakpoint(),
.ret_addr => try self.airRetAddr(inst),
.frame_addr => try self.airFrameAddress(inst),
@ -4198,10 +4199,18 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ .none, .none, .none });
}
fn airTrap(self: *Self) !void {
_ = try self.addInst(.{
.tag = .brk,
.data = .{ .imm16 = 0x0001 },
});
return self.finishAirBookkeeping();
}
fn airBreakpoint(self: *Self) !void {
_ = try self.addInst(.{
.tag = .brk,
.data = .{ .imm16 = 1 },
.data = .{ .imm16 = 0xf000 },
});
return self.finishAirBookkeeping();
}

View file

@ -721,6 +721,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.bitcast => try self.airBitCast(inst),
.block => try self.airBlock(inst),
.br => try self.airBr(inst),
.trap => try self.airTrap(),
.breakpoint => try self.airBreakpoint(),
.ret_addr => try self.airRetAddr(inst),
.frame_addr => try self.airFrameAddress(inst),
@ -4146,6 +4147,14 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, result, .{ .none, .none, .none });
}
fn airTrap(self: *Self) !void {
_ = try self.addInst(.{
.tag = .undefined_instruction,
.data = .{ .nop = {} },
});
return self.finishAirBookkeeping();
}
fn airBreakpoint(self: *Self) !void {
_ = try self.addInst(.{
.tag = .bkpt,

View file

@ -1,4 +1,4 @@
//! This file contains the functionality for lowering AArch64 MIR into
//! This file contains the functionality for lowering AArch32 MIR into
//! machine code
const Emit = @This();
@ -15,7 +15,7 @@ const Target = std.Target;
const assert = std.debug.assert;
const Instruction = bits.Instruction;
const Register = bits.Register;
const log = std.log.scoped(.aarch64_emit);
const log = std.log.scoped(.aarch32_emit);
const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
const CodeGen = @import("CodeGen.zig");
@ -100,6 +100,7 @@ pub fn emitMir(
.b => try emit.mirBranch(inst),
.undefined_instruction => try emit.mirUndefinedInstruction(),
.bkpt => try emit.mirExceptionGeneration(inst),
.blx => try emit.mirBranchExchange(inst),
@ -494,6 +495,10 @@ fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void {
}
}
fn mirUndefinedInstruction(emit: *Emit) !void {
try emit.writeInstruction(Instruction.undefinedInstruction());
}
fn mirExceptionGeneration(emit: *Emit, inst: Mir.Inst.Index) !void {
const tag = emit.mir.instructions.items(.tag)[inst];
const imm16 = emit.mir.instructions.items(.data)[inst].imm16;

View file

@ -35,6 +35,8 @@ pub const Inst = struct {
asr,
/// Branch
b,
/// Undefined instruction
undefined_instruction,
/// Breakpoint
bkpt,
/// Branch with Link and Exchange

View file

@ -307,6 +307,9 @@ pub const Instruction = union(enum) {
fixed: u4 = 0b1111,
cond: u4,
},
undefined_instruction: packed struct {
imm32: u32 = 0xe7ffdefe,
},
breakpoint: packed struct {
imm4: u4,
fixed_1: u4 = 0b0111,
@ -613,6 +616,7 @@ pub const Instruction = union(enum) {
.branch => |v| @bitCast(u32, v),
.branch_exchange => |v| @bitCast(u32, v),
.supervisor_call => |v| @bitCast(u32, v),
.undefined_instruction => |v| v.imm32,
.breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20),
};
}
@ -890,6 +894,13 @@ pub const Instruction = union(enum) {
};
}
// This instruction has no official mnemonic equivalent so it is public as-is.
pub fn undefinedInstruction() Instruction {
return Instruction{
.undefined_instruction = .{},
};
}
fn breakpoint(imm: u16) Instruction {
return Instruction{
.breakpoint = .{

View file

@ -550,6 +550,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.bitcast => try self.airBitCast(inst),
.block => try self.airBlock(inst),
.br => try self.airBr(inst),
.trap => try self.airTrap(),
.breakpoint => try self.airBreakpoint(),
.ret_addr => try self.airRetAddr(inst),
.frame_addr => try self.airFrameAddress(inst),
@ -1652,6 +1653,14 @@ fn airArg(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, mcv, .{ .none, .none, .none });
}
fn airTrap(self: *Self) !void {
_ = try self.addInst(.{
.tag = .unimp,
.data = .{ .nop = {} },
});
return self.finishAirBookkeeping();
}
fn airBreakpoint(self: *Self) !void {
_ = try self.addInst(.{
.tag = .ebreak,

View file

@ -51,6 +51,7 @@ pub fn emitMir(
.ebreak => try emit.mirSystem(inst),
.ecall => try emit.mirSystem(inst),
.unimp => try emit.mirSystem(inst),
.dbg_line => try emit.mirDbgLine(inst),
@ -153,6 +154,7 @@ fn mirSystem(emit: *Emit, inst: Mir.Inst.Index) !void {
switch (tag) {
.ebreak => try emit.writeInstruction(Instruction.ebreak),
.ecall => try emit.writeInstruction(Instruction.ecall),
.unimp => try emit.writeInstruction(Instruction.unimp),
else => unreachable,
}
}

View file

@ -32,6 +32,7 @@ pub const Inst = struct {
dbg_epilogue_begin,
/// Pseudo-instruction: Update debug line
dbg_line,
unimp,
ebreak,
ecall,
jalr,

View file

@ -380,6 +380,7 @@ pub const Instruction = union(enum) {
pub const ecall = iType(0b1110011, 0b000, .zero, .zero, 0x000);
pub const ebreak = iType(0b1110011, 0b000, .zero, .zero, 0x001);
pub const unimp = iType(0, 0, .zero, .zero, 0);
};
pub const Register = enum(u6) {

View file

@ -566,6 +566,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.bitcast => try self.airBitCast(inst),
.block => try self.airBlock(inst),
.br => try self.airBr(inst),
.trap => try self.airTrap(),
.breakpoint => try self.airBreakpoint(),
.ret_addr => @panic("TODO try self.airRetAddr(inst)"),
.frame_addr => @panic("TODO try self.airFrameAddress(inst)"),
@ -1160,6 +1161,21 @@ fn airBr(self: *Self, inst: Air.Inst.Index) !void {
return self.finishAir(inst, .dead, .{ branch.operand, .none, .none });
}
fn airTrap(self: *Self) !void {
// ta 0x05
_ = try self.addInst(.{
.tag = .tcc,
.data = .{
.trap = .{
.is_imm = true,
.cond = .al,
.rs2_or_imm = .{ .imm = 0x05 },
},
},
});
return self.finishAirBookkeeping();
}
fn airBreakpoint(self: *Self) !void {
// ta 0x01
_ = try self.addInst(.{

View file

@ -1829,6 +1829,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.arg => func.airArg(inst),
.bitcast => func.airBitcast(inst),
.block => func.airBlock(inst),
.trap => func.airTrap(inst),
.breakpoint => func.airBreakpoint(inst),
.br => func.airBr(inst),
.bool_to_int => func.airBoolToInt(inst),
@ -3289,6 +3290,11 @@ fn airNot(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
func.finishAir(inst, result, &.{ty_op.operand});
}
fn airTrap(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
try func.addTag(.@"unreachable");
func.finishAir(inst, .none, &.{});
}
fn airBreakpoint(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
// unsupported by wasm itfunc. Can be implemented once we support DWARF
// for wasm

View file

@ -638,6 +638,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.bitcast => try self.airBitCast(inst),
.block => try self.airBlock(inst),
.br => try self.airBr(inst),
.trap => try self.airTrap(),
.breakpoint => try self.airBreakpoint(),
.ret_addr => try self.airRetAddr(inst),
.frame_addr => try self.airFrameAddress(inst),
@ -3917,6 +3918,15 @@ fn genVarDbgInfo(
}
}
fn airTrap(self: *Self) !void {
_ = try self.addInst(.{
.tag = .ud,
.ops = Mir.Inst.Ops.encode(.{}),
.data = undefined,
});
return self.finishAirBookkeeping();
}
fn airBreakpoint(self: *Self) !void {
_ = try self.addInst(.{
.tag = .interrupt,

View file

@ -166,6 +166,7 @@ pub fn lowerMir(emit: *Emit) InnerError!void {
.@"test" => try emit.mirTest(inst),
.ud => try emit.mirUndefinedInstruction(),
.interrupt => try emit.mirInterrupt(inst),
.nop => {}, // just skip it
@ -234,6 +235,10 @@ fn fixupRelocs(emit: *Emit) InnerError!void {
}
}
fn mirUndefinedInstruction(emit: *Emit) InnerError!void {
return lowerToZoEnc(.ud2, emit.code);
}
fn mirInterrupt(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const tag = emit.mir.instructions.items(.tag)[inst];
assert(tag == .interrupt);
@ -1279,6 +1284,7 @@ const Tag = enum {
push,
pop,
@"test",
ud2,
int3,
nop,
imul,
@ -1571,6 +1577,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) OpCode {
.zo => return switch (tag) {
.ret_near => OpCode.init(&.{0xc3}),
.ret_far => OpCode.init(&.{0xcb}),
.ud2 => OpCode.init(&.{ 0x0F, 0x0B }),
.int3 => OpCode.init(&.{0xcc}),
.nop => OpCode.init(&.{0x90}),
.syscall => OpCode.init(&.{ 0x0f, 0x05 }),

View file

@ -329,6 +329,9 @@ pub const Inst = struct {
/// TODO handle more cases
@"test",
/// Undefined Instruction
ud,
/// Breakpoint form:
/// 0b00 int3
interrupt,

View file

@ -2741,6 +2741,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
.const_ty => unreachable, // excluded from function bodies
.arg => try airArg(f, inst),
.trap => try airTrap(f.object.writer()),
.breakpoint => try airBreakpoint(f.object.writer()),
.ret_addr => try airRetAddr(f, inst),
.frame_addr => try airFrameAddress(f, inst),
@ -4428,6 +4429,11 @@ fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {
return local;
}
fn airTrap(writer: anytype) !CValue {
try writer.writeAll("zig_trap();\n");
return .none;
}
fn airBreakpoint(writer: anytype) !CValue {
try writer.writeAll("zig_breakpoint();\n");
return .none;

View file

@ -4590,6 +4590,7 @@ pub const FuncGen = struct {
.block => try self.airBlock(inst),
.br => try self.airBr(inst),
.switch_br => try self.airSwitchBr(inst),
.trap => try self.airTrap(inst),
.breakpoint => try self.airBreakpoint(inst),
.ret_addr => try self.airRetAddr(inst),
.frame_addr => try self.airFrameAddress(inst),
@ -8256,6 +8257,13 @@ pub const FuncGen = struct {
return fg.load(ptr, ptr_ty);
}
fn airTrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
_ = inst;
const llvm_fn = self.getIntrinsic("llvm.trap", &.{});
_ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, undefined, 0, .Cold, .Auto, "");
return null;
}
fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
_ = inst;
const llvm_fn = self.getIntrinsic("llvm.debugtrap", &.{});

View file

@ -194,6 +194,7 @@ const Writer = struct {
.c_va_end,
=> try w.writeUnOp(s, inst),
.trap,
.breakpoint,
.unreach,
.ret_addr,

View file

@ -410,6 +410,7 @@ const Writer = struct {
.alloc_inferred_comptime_mut,
.ret_ptr,
.ret_type,
.trap,
=> try self.writeNode(stream, inst),
.error_value,