Add trap codes to the Cretonne IL.

The trap and trapz/trapnz instructions now take a trap code immediate
operand which indicates the reason for trapping.
This commit is contained in:
Jakob Stoklund Olesen
2017-09-20 13:58:57 -07:00
parent 0f21fd342a
commit e8723be33f
28 changed files with 199 additions and 62 deletions

View File

@@ -7,7 +7,7 @@ function %nonsense(i32) {
; check: digraph %nonsense {
ebb0(v1: i32):
trap ; error: terminator instruction was encountered before the end
trap user0 ; error: terminator instruction was encountered before the end
brnz v1, ebb2 ; unordered: ebb0:inst1 -> ebb2
jump ebb1 ; unordered: ebb0:inst2 -> ebb1

View File

@@ -9,7 +9,7 @@ function %not_reached(i32) -> i32 {
ebb0(v0: i32):
brnz v0, ebb2 ; unordered: ebb0:inst0 -> ebb2
trap
trap user0
ebb1:
v1 = iconst.i32 1

View File

@@ -369,5 +369,5 @@ ebb1:
; asm: ebb2:
ebb2:
trap ; bin: 0f 0b
trap user0 ; bin: 0f 0b
}

View File

@@ -861,5 +861,5 @@ ebb0:
; asm: movl %r10d, %ecx
[-,%rcx] v32 = uextend.i64 v13 ; bin: 44 89 d1
trap ; bin: 0f 0b
trap user0 ; bin: 0f 0b
}

View File

@@ -9,22 +9,22 @@ isa intel
function %cond_trap(i32) {
ebb0(v1: i32):
trapz v1
trapz v1, user67
return
; check: $ebb0($v1: i32):
; nextln: brnz $v1, $(new=$EBB)
; nextln: trap
; nextln: trap user67
; check: $new:
; nextln: return
}
function %cond_trap2(i32) {
ebb0(v1: i32):
trapnz v1
trapnz v1, int_ovf
return
; check: $ebb0($v1: i32):
; nextln: brz $v1, $(new=$EBB)
; nextln: trap
; nextln: trap int_ovf
; check: $new:
; nextln: return
}
@@ -32,11 +32,11 @@ ebb0(v1: i32):
function %cond_trap_b1(i32) {
ebb0(v1: i32):
v2 = icmp_imm eq v1, 6
trapz v2
trapz v2, user7
return
; check: $ebb0($v1: i32):
; check: brnz $v2, $(new=$EBB)
; nextln: trap
; nextln: trap user7
; check: $new:
; nextln: return
}
@@ -44,11 +44,11 @@ ebb0(v1: i32):
function %cond_trap2_b1(i32) {
ebb0(v1: i32):
v2 = icmp_imm eq v1, 6
trapnz v2
trapnz v2, user9
return
; check: $ebb0($v1: i32):
; check: brz $v2, $(new=$EBB)
; nextln: trap
; nextln: trap user9
; check: $new:
; nextln: return
}

View File

@@ -64,7 +64,7 @@ ebb0(v0: i32, v999: i64):
; Boundscheck code
; check: $(oob=$V) = icmp
; nextln: brz $oob, $(ok=$EBB)
; nextln: trap
; nextln: trap heap_oob
; check: $ok:
; Checks here are assuming that no pipehole opts fold the load offsets.
; nextln: $(xoff=$V) = uextend.i64 $v0

View File

@@ -86,13 +86,13 @@ function %jumptable(i32) {
ebb10(v3: i32):
br_table v3, jt2
trap
trap user1
ebb20:
trap
trap user2
ebb30:
trap
trap user3
ebb40:
trap
trap user4
}
; sameln: function %jumptable(i32) native {
; nextln: jt0 = jump_table 0
@@ -100,14 +100,14 @@ ebb40:
; nextln:
; nextln: ebb0($v3: i32):
; nextln: br_table $v3, jt1
; nextln: trap
; nextln: trap user1
; nextln:
; nextln: ebb1:
; nextln: trap
; nextln: trap user2
; nextln:
; nextln: ebb2:
; nextln: trap
; nextln: trap user3
; nextln:
; nextln: ebb3:
; nextln: trap
; nextln: trap user4
; nextln: }

View File

@@ -7,7 +7,7 @@ isa riscv
function %foo(i32, i32) {
ebb1(v0: i32 [%x8], v1: i32):
[-,-] v2 = iadd v0, v1
[-] trap
[-] trap heap_oob
[R#1234, %x5, %x11] v6, v7 = iadd_cout v2, v0
[Rshamt#beef, %x25] v8 = ishl_imm v6, 2
v9 = iadd v8, v7
@@ -16,7 +16,7 @@ ebb1(v0: i32 [%x8], v1: i32):
; sameln: function %foo(i32, i32) native {
; nextln: $ebb1($v0: i32 [%x8], $v1: i32):
; nextln: [-,-]$WS $v2 = iadd $v0, $v1
; nextln: [-]$WS trap
; nextln: [-]$WS trap heap_oob
; nextln: [R#1234,%x5,%x11]$WS $v6, $v7 = iadd_cout $v2, $v0
; nextln: [Rshamt#beef,%x25]$WS $v8 = ishl_imm $v6, 2
; nextln: [-,-]$WS $v9 = iadd $v8, $v7

View File

@@ -13,13 +13,13 @@ function %defs() {
ebb100(v20: i32):
v1000 = iconst.i32x8 5
v9200 = f64const 0x4.0p0
trap
trap user4
}
; sameln: function %defs() native {
; nextln: $ebb100($v20: i32):
; nextln: $v1000 = iconst.i32x8 5
; nextln: $v9200 = f64const 0x1.0000000000000p2
; nextln: trap
; nextln: trap user4
; nextln: }
; Using values.

View File

@@ -3,11 +3,11 @@ test cat
; The smallest possible function.
function %minimal() {
ebb0:
trap
trap user0
}
; sameln: function %minimal() native {
; nextln: ebb0:
; nextln: trap
; nextln: trap user0
; nextln: }
; Create and use values.

View File

@@ -4,10 +4,10 @@ set return_at_end
function %ok(i32) {
ebb0(v0: i32):
brnz v0, ebb1
trap
trap int_divz
ebb1:
trapz v0
trapz v0, user5
return
}
@@ -17,6 +17,6 @@ ebb0(v0: i32):
return ; error: Internal return not allowed
ebb1:
trapz v0
trapz v0, user6
return
}

View File

@@ -46,5 +46,5 @@ ebb1(v2: i32):
function %undefined() {
ebb0:
trap
trap user0
}

View File

@@ -44,11 +44,11 @@ fn simple_traversal() {
brz v4, ebb4
jump ebb5
ebb3:
trap
trap user0
ebb4:
trap
trap user0
ebb5:
trap
trap user0
}
",
vec![0, 1, 3, 2, 4, 5],
@@ -123,7 +123,7 @@ fn loops_three() {
jump ebb6
ebb5:
brz v0, ebb4
trap
trap user0
ebb6:
jump ebb7
ebb7:
@@ -152,7 +152,7 @@ fn back_edge_one() {
brnz v0, ebb0
return
ebb4:
trap
trap user0
}
",
vec![0, 1, 3, 2, 4],

View File

@@ -9,7 +9,7 @@ from __future__ import absolute_import
from cdsl.formats import InstructionFormat
from cdsl.operands import VALUE, VARIABLE_ARGS
from .immediates import imm64, uimm8, uimm32, ieee32, ieee64, offset32
from .immediates import boolean, intcc, floatcc, memflags, regunit
from .immediates import boolean, intcc, floatcc, memflags, regunit, trapcode
from . import entities
from .entities import ebb, sig_ref, func_ref, stack_slot, heap
@@ -61,5 +61,8 @@ HeapAddr = InstructionFormat(heap, VALUE, uimm32)
RegMove = InstructionFormat(VALUE, ('src', regunit), ('dst', regunit))
Trap = InstructionFormat(trapcode)
CondTrap = InstructionFormat(VALUE, trapcode)
# Finally extract the names of global variables in this module.
InstructionFormat.extract_names(globals())

View File

@@ -105,3 +105,19 @@ regunit = ImmediateKind(
'regunit',
'A register unit in the target ISA',
rust_type='isa::RegUnit')
#: A trap code indicating the reason for trapping.
#:
#: The Rust enum type also has a `User(u16)` variant for user-provided trap
#: codes.
trapcode = ImmediateKind(
'trapcode',
'A trap reason code.',
default_member='code',
rust_type='ir::TrapCode',
values={
"stk_ovf": 'StackOverflow',
"heap_oob": 'HeapOutOfBounds',
"int_ovf": 'IntegerOverflow',
"int_divz": 'IntegerDivisionByZero',
})

View File

@@ -11,6 +11,7 @@ from cdsl.instructions import Instruction, InstructionGroup
from base.types import f32, f64, b1
from base.immediates import imm64, uimm8, uimm32, ieee32, ieee64, offset32
from base.immediates import boolean, intcc, floatcc, memflags, regunit
from base.immediates import trapcode
from base import entities
from cdsl.ti import WiderOrEq
import base.formats # noqa
@@ -126,11 +127,12 @@ br_table = Instruction(
""",
ins=(x, JT), is_branch=True)
code = Operand('code', trapcode)
trap = Instruction(
'trap', r"""
Terminate execution unconditionally.
""",
is_terminator=True, can_trap=True)
ins=code, is_terminator=True, can_trap=True)
trapz = Instruction(
'trapz', r"""
@@ -138,7 +140,7 @@ trapz = Instruction(
if ``c`` is non-zero, execution continues at the following instruction.
""",
ins=c, can_trap=True)
ins=(c, code), can_trap=True)
trapnz = Instruction(
'trapnz', r"""
@@ -146,7 +148,7 @@ trapnz = Instruction(
if ``c`` is zero, execution continues at the following instruction.
""",
ins=c, can_trap=True)
ins=(c, code), can_trap=True)
rvals = Operand('rvals', VARIABLE_ARGS, doc='return values')

View File

@@ -287,8 +287,8 @@ I64.enc(base.brnz.b1, *r.t8jccb_abcd(0x75))
#
# Trap as ud2
#
I32.enc(base.trap, *r.noop(0x0f, 0x0b))
I64.enc(base.trap, *r.noop(0x0f, 0x0b))
I32.enc(base.trap, *r.trap(0x0f, 0x0b))
I64.enc(base.trap, *r.trap(0x0f, 0x0b))
#
# Comparisons

View File

@@ -5,7 +5,7 @@ from __future__ import absolute_import
from cdsl.isa import EncRecipe
from cdsl.predicates import IsSignedInt, IsEqual
from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry
from base.formats import Nullary, Call, IndirectCall, Store, Load
from base.formats import Trap, Call, IndirectCall, Store, Load
from base.formats import IntCompare
from base.formats import RegMove, Ternary, Jump, Branch, FuncAddr
from .registers import GPR, ABCD, FPR
@@ -195,8 +195,8 @@ class TailRecipe:
null = EncRecipe('null', Unary, size=0, ins=GPR, outs=0, emit='')
# XX opcode, no ModR/M.
noop = TailRecipe(
'noop', Nullary, size=0, ins=(), outs=(),
trap = TailRecipe(
'trap', Trap, size=0, ins=(), outs=(),
emit='PUT_OP(bits, BASE_REX, sink);')
# XX /r

View File

@@ -428,7 +428,7 @@ mod test {
use cursor::{Cursor, FuncCursor};
use flowgraph::ControlFlowGraph;
use ir::types::*;
use ir::{Function, InstBuilder, types};
use ir::{Function, InstBuilder, types, TrapCode};
use settings;
use super::*;
use verifier::verify_context;
@@ -506,7 +506,7 @@ mod test {
let jmp02 = cur.ins().jump(ebb2, &[]);
cur.insert_ebb(ebb1);
let trap = cur.ins().trap();
let trap = cur.ins().trap(TrapCode::User(5));
cur.insert_ebb(ebb2);
let jmp21 = cur.ins().jump(ebb1, &[]);

View File

@@ -214,6 +214,12 @@ pub enum InstructionData {
src: RegUnit,
dst: RegUnit,
},
Trap { opcode: Opcode, code: ir::TrapCode },
CondTrap {
opcode: Opcode,
arg: Value,
code: ir::TrapCode,
},
}
/// A variable list of `Value` operands used for function call arguments and passing arguments to

View File

@@ -17,6 +17,7 @@ mod globalvar;
mod heap;
mod memflags;
mod progpoint;
mod trapcode;
mod valueloc;
pub use ir::builder::{InstBuilder, InstBuilderBase, InstInserterBase, InsertBuilder};
@@ -34,6 +35,7 @@ pub use ir::layout::{Layout, CursorBase, Cursor};
pub use ir::memflags::MemFlags;
pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint};
pub use ir::stackslot::{StackSlots, StackSlotKind, StackSlotData};
pub use ir::trapcode::TrapCode;
pub use ir::types::Type;
pub use ir::valueloc::{ValueLoc, ArgumentLoc};

View File

@@ -0,0 +1,89 @@
//! Trap codes describing the reason for a trap.
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
/// A trap code describing the reason for a trap.
///
/// All trap instructions have an explicit trap code.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum TrapCode {
/// The current stack space was exhausted.
///
/// On some platforms, a stack overflow may also be indicated by a segmentation fault from the
/// stack guard page.
StackOverflow,
/// A `heap_addr` instruction detected an out-of-bounds error.
///
/// Some out-of-bounds heap accesses are detected by a segmentation fault on the heap guard
/// pages.
HeapOutOfBounds,
/// An integer arithmetic operation caused an overflow.
IntegerOverflow,
/// An integer division by zero.
IntegerDivisionByZero,
/// A user-defined trap code.
User(u16),
}
impl Display for TrapCode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use self::TrapCode::*;
let identifier = match *self {
StackOverflow => "stk_ovf",
HeapOutOfBounds => "heap_oob",
IntegerOverflow => "int_ovf",
IntegerDivisionByZero => "int_divz",
User(x) => return write!(f, "user{}", x),
};
f.write_str(identifier)
}
}
impl FromStr for TrapCode {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
use self::TrapCode::*;
match s {
"stk_ovf" => Ok(StackOverflow),
"heap_oob" => Ok(HeapOutOfBounds),
"int_ovf" => Ok(IntegerOverflow),
"int_divz" => Ok(IntegerDivisionByZero),
_ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()),
_ => Err(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
// Everything but user-defined codes.
const CODES: [TrapCode; 4] = [
TrapCode::StackOverflow,
TrapCode::HeapOutOfBounds,
TrapCode::IntegerOverflow,
TrapCode::IntegerDivisionByZero,
];
#[test]
fn display() {
for r in &CODES {
let tc = *r;
assert_eq!(tc.to_string().parse(), Ok(tc));
}
assert_eq!("bogus".parse::<TrapCode>(), Err(()));
assert_eq!(TrapCode::User(17).to_string(), "user17");
assert_eq!("user22".parse(), Ok(TrapCode::User(22)));
assert_eq!("user".parse::<TrapCode>(), Err(()));
assert_eq!("user-1".parse::<TrapCode>(), Err(()));
assert_eq!("users".parse::<TrapCode>(), Err(()));
}
}

View File

@@ -74,14 +74,14 @@ fn dynamic_addr(
// We need an overflow check for the adjusted offset.
let size_val = pos.ins().iconst(offset_ty, size);
let (adj_offset, overflow) = pos.ins().iadd_cout(offset, size_val);
pos.ins().trapnz(overflow);
pos.ins().trapnz(overflow, ir::TrapCode::HeapOutOfBounds);
oob = pos.ins().icmp(
IntCC::UnsignedGreaterThan,
adj_offset,
bound,
);
}
pos.ins().trapnz(oob);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
offset_addr(inst, heap, addr_ty, offset, offset_ty, pos.func);
}
@@ -103,7 +103,7 @@ fn static_addr(
// Start with the bounds check. Trap if `offset + size > bound`.
if size > bound {
// This will simply always trap since `offset >= 0`.
pos.ins().trap();
pos.ins().trap(ir::TrapCode::HeapOutOfBounds);
pos.func.dfg.replace(inst).iconst(addr_ty, 0);
return;
}
@@ -129,7 +129,7 @@ fn static_addr(
limit,
)
};
pos.ins().trapnz(oob);
pos.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds);
}
offset_addr(inst, heap, addr_ty, offset, offset_ty, pos.func);

View File

@@ -106,15 +106,15 @@ include!(concat!(env!("OUT_DIR"), "/legalizer.rs"));
fn expand_cond_trap(inst: ir::Inst, func: &mut ir::Function, cfg: &mut ControlFlowGraph) {
// Parse the instruction.
let trapz;
let arg = match func.dfg[inst] {
ir::InstructionData::Unary { opcode, arg } => {
let (arg, code) = match func.dfg[inst] {
ir::InstructionData::CondTrap { opcode, arg, code } => {
// We want to branch *over* an unconditional trap.
trapz = match opcode {
ir::Opcode::Trapz => true,
ir::Opcode::Trapnz => false,
_ => panic!("Expected cond trap: {}", func.dfg.display_inst(inst, None)),
};
arg
(arg, code)
}
_ => panic!("Expected cond trap: {}", func.dfg.display_inst(inst, None)),
};
@@ -138,7 +138,7 @@ fn expand_cond_trap(inst: ir::Inst, func: &mut ir::Function, cfg: &mut ControlFl
}
let mut pos = FuncCursor::new(func).after_inst(inst);
pos.ins().trap();
pos.ins().trap(code);
pos.insert_ebb(new_ebb);
// Finally update the CFG.

View File

@@ -337,7 +337,9 @@ impl<'a> Verifier<'a> {
FloatCompare { .. } |
Load { .. } |
Store { .. } |
RegMove { .. } => {}
RegMove { .. } |
Trap { .. } |
CondTrap { .. } => {}
}
Ok(())

View File

@@ -376,6 +376,8 @@ pub fn write_operands(
write!(w, " {}, %{} -> %{}", arg, src, dst)
}
}
Trap { code, .. } => write!(w, " {}", code),
CondTrap { arg, code, .. } => write!(w, " {}, {}", arg, code),
}
}

View File

@@ -2192,6 +2192,19 @@ impl<'a> Parser<'a> {
dst,
}
}
InstructionFormat::Trap => {
let code = self.match_enum("expected trap code")?;
InstructionData::Trap { opcode, code }
}
InstructionFormat::CondTrap => {
let arg = self.match_value("expected SSA value operand")?;
self.match_token(
Token::Comma,
"expected ',' between operands",
)?;
let code = self.match_enum("expected trap code")?;
InstructionData::CondTrap { opcode, arg, code }
}
};
Ok(idata)
}
@@ -2365,7 +2378,7 @@ mod tests {
jt10 = jump_table ebb0
; Jumptable
ebb0: ; Basic block
trap ; Instruction
trap user42; Instruction
} ; Trailing.
; More trailing.",
).parse_function(None)
@@ -2459,7 +2472,7 @@ mod tests {
let func = Parser::new(
"function #1234567890AbCdEf() native {
ebb0:
trap
trap int_divz
}",
).parse_function(None)
.unwrap()
@@ -2470,7 +2483,7 @@ mod tests {
let mut parser = Parser::new(
"function #12ww() native {
ebb0:
trap
trap stk_ovf
}",
);
assert!(parser.parse_function(None).is_err());
@@ -2479,7 +2492,7 @@ mod tests {
let mut parser = Parser::new(
"function #1() native {
ebb0:
trap
trap user0
}",
);
assert!(parser.parse_function(None).is_err());
@@ -2488,7 +2501,7 @@ mod tests {
let func = Parser::new(
"function #() native {
ebb0:
trap
trap int_ovf
}",
).parse_function(None)
.unwrap()

View File

@@ -101,7 +101,9 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
// We do nothing
}
Operator::Unreachable => {
builder.ins().trap();
// We use `trap user0` to indicate a user-generated trap.
// We could make the trap code configurable if need be.
builder.ins().trap(ir::TrapCode::User(0));
state.real_unreachable_stack_depth = 1;
}
/***************************** Control flow blocks **********************************