Implement jump tables (#453)
* Add 'jump_table_entry' and 'indirect_jump' instructions. * Update CodeSink to keep track of code size. Pretty up clif-util's disassembly output. * Only disassemble the machine portion of output. Pretty print the read-only data after it. * Update switch frontend code to use new br_table instruction w/ default.
This commit is contained in:
committed by
Dan Gohman
parent
de1d82b4ba
commit
79cea5e18b
@@ -50,7 +50,10 @@ Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchInt = InstructionFormat(intcc, VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchFloat = InstructionFormat(floatcc, VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchIcmp = InstructionFormat(intcc, VALUE, VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchTable = InstructionFormat(VALUE, entities.jump_table)
|
||||
BranchTable = InstructionFormat(VALUE, ebb, entities.jump_table)
|
||||
BranchTableEntry = InstructionFormat(VALUE, VALUE, uimm8, entities.jump_table)
|
||||
BranchTableBase = InstructionFormat(entities.jump_table)
|
||||
IndirectJump = InstructionFormat(VALUE, entities.jump_table)
|
||||
|
||||
Call = InstructionFormat(func_ref, VARIABLE_ARGS)
|
||||
CallIndirect = InstructionFormat(sig_ref, VALUE, VARIABLE_ARGS)
|
||||
|
||||
@@ -130,6 +130,8 @@ brff = Instruction(
|
||||
ins=(Cond, f, EBB, args), is_branch=True)
|
||||
|
||||
x = Operand('x', iB, doc='index into jump table')
|
||||
Entry = TypeVar('Entry', 'A scalar integer type', ints=True)
|
||||
entry = Operand('entry', Entry, doc='entry of jump table')
|
||||
JT = Operand('JT', entities.jump_table)
|
||||
br_table = Instruction(
|
||||
'br_table', r"""
|
||||
@@ -137,12 +139,47 @@ br_table = Instruction(
|
||||
|
||||
Use ``x`` as an unsigned index into the jump table ``JT``. If a jump
|
||||
table entry is found, branch to the corresponding EBB. If no entry was
|
||||
found fall through to the next instruction.
|
||||
found or the index is out-of-bounds, branch to the given default EBB.
|
||||
|
||||
Note that this branch instruction can't pass arguments to the targeted
|
||||
blocks. Split critical edges as needed to work around this.
|
||||
""",
|
||||
ins=(x, JT), is_branch=True)
|
||||
ins=(x, EBB, JT), is_branch=True, is_terminator=True)
|
||||
|
||||
Size = Operand('Size', uimm8, 'Size in bytes')
|
||||
jump_table_entry = Instruction(
|
||||
'jump_table_entry', r"""
|
||||
Get an entry from a jump table.
|
||||
|
||||
Load a serialized ``entry`` from a jump table ``JT`` at a given index
|
||||
``addr`` with a specific ``Size``. The retrieved entry may need to be
|
||||
decoded after loading, depending upon the jump table type used.
|
||||
|
||||
Currently, the only type supported is entries which are relative to the
|
||||
base of the jump table.
|
||||
""",
|
||||
ins=(x, addr, Size, JT), outs=entry)
|
||||
|
||||
jump_table_base = Instruction(
|
||||
'jump_table_base', r"""
|
||||
Get the absolute base address of a jump table.
|
||||
|
||||
This is used for jump tables wherein the entries are stored relative to
|
||||
the base of jump table. In order to use these, generated code should first
|
||||
load an entry using ``jump_table_entry``, then use this instruction to add
|
||||
the relative base back to it.
|
||||
""",
|
||||
ins=JT, outs=addr)
|
||||
|
||||
indirect_jump_table_br = Instruction(
|
||||
'indirect_jump_table_br', r"""
|
||||
Branch indirectly via a jump table entry.
|
||||
|
||||
Unconditionally jump via a jump table entry that was previously loaded
|
||||
with the ``jump_table_entry`` instruction.
|
||||
""",
|
||||
ins=(addr, JT),
|
||||
is_branch=True, is_indirect_branch=True, is_terminator=True)
|
||||
|
||||
code = Operand('code', trapcode)
|
||||
trap = Instruction(
|
||||
|
||||
@@ -306,10 +306,10 @@ widen.legalize(
|
||||
|
||||
for int_ty in [types.i8, types.i16]:
|
||||
widen.legalize(
|
||||
br_table.bind(int_ty)(x, y),
|
||||
br_table.bind(int_ty)(x, y, z),
|
||||
Rtl(
|
||||
b << uextend.i32(x),
|
||||
br_table(b, y),
|
||||
br_table(b, y, z),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -166,4 +166,13 @@ probestack_size_log2 = NumSetting(
|
||||
""",
|
||||
default=12)
|
||||
|
||||
#
|
||||
# Jump table options.
|
||||
#
|
||||
jump_tables_enabled = BoolSetting(
|
||||
"""
|
||||
Enable the use of jump tables in generated machine code.
|
||||
""",
|
||||
default=True)
|
||||
|
||||
group.close(globals())
|
||||
|
||||
@@ -89,6 +89,7 @@ class Instruction(object):
|
||||
:param constraints: Tuple of instruction-specific TypeConstraints.
|
||||
:param is_terminator: This is a terminator instruction.
|
||||
:param is_branch: This is a branch instruction.
|
||||
:param is_indirect_branch: This is an indirect branch instruction.
|
||||
:param is_call: This is a call instruction.
|
||||
:param is_return: This is a return instruction.
|
||||
:param can_trap: This instruction can trap.
|
||||
@@ -103,6 +104,8 @@ class Instruction(object):
|
||||
ATTRIBS = {
|
||||
'is_terminator': 'True for instructions that terminate the EBB.',
|
||||
'is_branch': 'True for all branch or jump instructions.',
|
||||
'is_indirect_branch':
|
||||
'True for all indirect branch or jump instructions.',
|
||||
'is_call': 'Is this a call instruction?',
|
||||
'is_return': 'Is this a return instruction?',
|
||||
'can_load': 'Can this instruction read from memory?',
|
||||
|
||||
@@ -469,7 +469,7 @@ class Encoding(object):
|
||||
"Format {} must match recipe: {}".format(
|
||||
self.inst.format, recipe.format))
|
||||
|
||||
if self.inst.is_branch:
|
||||
if self.inst.is_branch and not self.inst.is_indirect_branch:
|
||||
assert recipe.branch_range, (
|
||||
'Recipe {} for {} must have a branch_range'
|
||||
.format(recipe, self.inst.name))
|
||||
|
||||
@@ -507,6 +507,17 @@ enc_both(base.brz.b1, r.t8jccd_abcd, 0x84)
|
||||
enc_both(base.brnz.b1, r.t8jccb_abcd, 0x75)
|
||||
enc_both(base.brnz.b1, r.t8jccd_abcd, 0x85)
|
||||
|
||||
#
|
||||
# Jump tables
|
||||
#
|
||||
X86_64.enc(base.jump_table_entry.i64.any.any, *r.jt_entry.rex(0x63, w=1))
|
||||
X86_32.enc(base.jump_table_entry.i32.any.any, *r.jt_entry(0x8b))
|
||||
|
||||
X86_64.enc(base.jump_table_base.i64, *r.jt_base.rex(0x8d, w=1))
|
||||
X86_32.enc(base.jump_table_base.i32, *r.jt_base(0x8d))
|
||||
|
||||
enc_x86_64(base.indirect_jump_table_br.i64, r.indirect_jmp, 0xff, rrr=4)
|
||||
X86_32.enc(base.indirect_jump_table_br.i32, *r.indirect_jmp(0xff, rrr=4))
|
||||
#
|
||||
# Trap as ud2
|
||||
#
|
||||
|
||||
@@ -14,6 +14,7 @@ from base.formats import IntCompare, IntCompareImm, FloatCompare
|
||||
from base.formats import IntCond, FloatCond
|
||||
from base.formats import IntSelect, IntCondTrap, FloatCondTrap
|
||||
from base.formats import Jump, Branch, BranchInt, BranchFloat
|
||||
from base.formats import BranchTableEntry, BranchTableBase, IndirectJump
|
||||
from base.formats import Ternary, FuncAddr, UnaryGlobalValue
|
||||
from base.formats import RegMove, RegSpill, RegFill, CopySpecial
|
||||
from base.formats import LoadComplex, StoreComplex
|
||||
@@ -276,6 +277,18 @@ def floatccs(iform):
|
||||
return Or(*(IsEqual(iform.cond, cc) for cc in supported_floatccs))
|
||||
|
||||
|
||||
def valid_scale(iform):
|
||||
# type: (InstructionFormat) -> PredNode
|
||||
"""
|
||||
Return an instruction predicate that checks if `iform.imm` is a valid
|
||||
`scale` for a SIB byte.
|
||||
"""
|
||||
return Or(IsEqual(iform.imm, 1),
|
||||
IsEqual(iform.imm, 2),
|
||||
IsEqual(iform.imm, 4),
|
||||
IsEqual(iform.imm, 8))
|
||||
|
||||
|
||||
# A null unary instruction that takes a GPR register. Can be used for identity
|
||||
# copies and no-op conversions.
|
||||
null = EncRecipe('null', Unary, size=0, ins=GPR, outs=0, emit='')
|
||||
@@ -1473,6 +1486,38 @@ brfd = TailRecipe(
|
||||
disp4(destination, func, sink);
|
||||
''')
|
||||
|
||||
indirect_jmp = TailRecipe(
|
||||
'indirect_jmp', IndirectJump, size=1, ins=GPR, outs=(),
|
||||
clobbers_flags=False,
|
||||
emit='''
|
||||
PUT_OP(bits, rex1(in_reg0), sink);
|
||||
modrm_r_bits(in_reg0, bits, sink);
|
||||
''')
|
||||
|
||||
jt_entry = TailRecipe(
|
||||
'jt_entry', BranchTableEntry, size=2,
|
||||
ins=(GPR_DEREF_SAFE, GPR_ZERO_DEREF_SAFE),
|
||||
outs=(GPR),
|
||||
clobbers_flags=False,
|
||||
instp=valid_scale(BranchTableEntry),
|
||||
emit='''
|
||||
PUT_OP(bits, rex3(in_reg1, out_reg0, in_reg0), sink);
|
||||
modrm_sib(out_reg0, sink);
|
||||
sib(imm.trailing_zeros() as u8, in_reg0, in_reg1, sink);
|
||||
''')
|
||||
|
||||
jt_base = TailRecipe(
|
||||
'jt_base', BranchTableBase, size=5, ins=(), outs=(GPR),
|
||||
clobbers_flags=False,
|
||||
emit='''
|
||||
// No reloc is needed here as the jump table is emitted directly after
|
||||
// the function body.
|
||||
PUT_OP(bits, rex2(0, out_reg0), sink);
|
||||
modrm_riprel(out_reg0, sink);
|
||||
|
||||
jt_disp4(table, func, sink);
|
||||
''')
|
||||
|
||||
#
|
||||
# Test flags and set a register.
|
||||
#
|
||||
|
||||
@@ -32,6 +32,8 @@ use std::ptr::write_unaligned;
|
||||
pub struct MemoryCodeSink<'a> {
|
||||
data: *mut u8,
|
||||
offset: isize,
|
||||
/// Size of the machine code portion of output
|
||||
pub code_size: isize,
|
||||
relocs: &'a mut RelocSink,
|
||||
traps: &'a mut TrapSink,
|
||||
}
|
||||
@@ -49,6 +51,7 @@ impl<'a> MemoryCodeSink<'a> {
|
||||
MemoryCodeSink {
|
||||
data,
|
||||
offset: 0,
|
||||
code_size: 0,
|
||||
relocs,
|
||||
traps,
|
||||
}
|
||||
@@ -131,6 +134,10 @@ impl<'a> CodeSink for MemoryCodeSink<'a> {
|
||||
let ofs = self.offset();
|
||||
self.traps.trap(ofs, srcloc, code);
|
||||
}
|
||||
|
||||
fn begin_rodata(&mut self) {
|
||||
self.code_size = self.offset;
|
||||
}
|
||||
}
|
||||
|
||||
/// A `TrapSink` implementation that does nothing, which is convenient when
|
||||
|
||||
@@ -94,6 +94,9 @@ pub trait CodeSink {
|
||||
|
||||
/// Add trap information for the current offset.
|
||||
fn trap(&mut self, TrapCode, SourceLoc);
|
||||
|
||||
/// Code output is complete, read-only data may follow.
|
||||
fn begin_rodata(&mut self);
|
||||
}
|
||||
|
||||
/// Report a bad encoding error.
|
||||
@@ -123,4 +126,20 @@ where
|
||||
emit_inst(func, inst, &mut divert, sink);
|
||||
}
|
||||
}
|
||||
|
||||
sink.begin_rodata();
|
||||
|
||||
// output jump tables
|
||||
for (jt, jt_data) in func.jump_tables.iter() {
|
||||
let jt_offset = func.jt_offsets[jt];
|
||||
for idx in 0..jt_data.len() {
|
||||
match jt_data.get_entry(idx) {
|
||||
Some(ebb) => {
|
||||
let rel_offset: i32 = func.offsets[ebb] as i32 - jt_offset as i32;
|
||||
sink.put4(rel_offset as u32)
|
||||
}
|
||||
None => sink.put4(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,13 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) -> CodegenResult<Cod
|
||||
}
|
||||
}
|
||||
|
||||
for (jt, jt_data) in func.jump_tables.iter() {
|
||||
func.jt_offsets[jt] = offset;
|
||||
// TODO: this should be computed based on the min size needed to hold
|
||||
// the furthest branch.
|
||||
offset += jt_data.len() as u32 * 4;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,11 @@ impl<'a> CFGPrinter<'a> {
|
||||
BranchInfo::SingleDest(dest, _) => {
|
||||
write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)?
|
||||
}
|
||||
BranchInfo::Table(table) => {
|
||||
write!(w, " | <{}>{} {}", inst, idata.opcode(), table)?
|
||||
BranchInfo::Table(table, dest) => {
|
||||
write!(w, " | <{}>{} {}", inst, idata.opcode(), table)?;
|
||||
if let Some(dest) = dest {
|
||||
write!(w, " {}", dest)?
|
||||
}
|
||||
}
|
||||
BranchInfo::NotABranch => {}
|
||||
}
|
||||
|
||||
@@ -345,18 +345,13 @@ impl DominatorTree {
|
||||
fn push_successors(&mut self, func: &Function, ebb: Ebb) {
|
||||
for inst in func.layout.ebb_insts(ebb) {
|
||||
match func.dfg.analyze_branch(inst) {
|
||||
BranchInfo::SingleDest(succ, _) => {
|
||||
if self.nodes[succ].rpo_number == 0 {
|
||||
self.nodes[succ].rpo_number = SEEN;
|
||||
self.stack.push(succ);
|
||||
}
|
||||
}
|
||||
BranchInfo::Table(jt) => {
|
||||
BranchInfo::SingleDest(succ, _) => self.push_if_unseen(succ),
|
||||
BranchInfo::Table(jt, dest) => {
|
||||
for (_, succ) in func.jump_tables[jt].entries() {
|
||||
if self.nodes[succ].rpo_number == 0 {
|
||||
self.nodes[succ].rpo_number = SEEN;
|
||||
self.stack.push(succ);
|
||||
}
|
||||
self.push_if_unseen(succ);
|
||||
}
|
||||
if let Some(dest) = dest {
|
||||
self.push_if_unseen(dest);
|
||||
}
|
||||
}
|
||||
BranchInfo::NotABranch => {}
|
||||
@@ -364,6 +359,14 @@ impl DominatorTree {
|
||||
}
|
||||
}
|
||||
|
||||
/// Push `ebb` onto `self.stack` if it has not already been seen.
|
||||
fn push_if_unseen(&mut self, ebb: Ebb) {
|
||||
if self.nodes[ebb].rpo_number == 0 {
|
||||
self.nodes[ebb].rpo_number = SEEN;
|
||||
self.stack.push(ebb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a dominator tree from a control flow graph using Keith D. Cooper's
|
||||
/// "Simple, Fast Dominator Algorithm."
|
||||
fn compute_domtree(&mut self, func: &Function, cfg: &ControlFlowGraph) {
|
||||
|
||||
@@ -125,7 +125,10 @@ impl ControlFlowGraph {
|
||||
BranchInfo::SingleDest(dest, _) => {
|
||||
self.add_edge(ebb, inst, dest);
|
||||
}
|
||||
BranchInfo::Table(jt) => {
|
||||
BranchInfo::Table(jt, dest) => {
|
||||
if let Some(dest) = dest {
|
||||
self.add_edge(ebb, inst, dest);
|
||||
}
|
||||
for (_, dest) in func.jump_tables[jt].entries() {
|
||||
self.add_edge(ebb, inst, dest);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ use ir::{
|
||||
Ebb, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap, HeapData, JumpTable,
|
||||
JumpTableData, SigRef, StackSlot, StackSlotData, Table, TableData,
|
||||
};
|
||||
use ir::{EbbOffsets, InstEncodings, JumpTables, SourceLocs, StackSlots, ValueLocations};
|
||||
use ir::{EbbOffsets, InstEncodings, SourceLocs, StackSlots, ValueLocations};
|
||||
use ir::{JumpTableOffsets, JumpTables};
|
||||
use isa::{EncInfo, Encoding, Legalize, TargetIsa};
|
||||
use settings::CallConv;
|
||||
use std::fmt;
|
||||
@@ -64,6 +65,9 @@ pub struct Function {
|
||||
/// in the textual IR format.
|
||||
pub offsets: EbbOffsets,
|
||||
|
||||
/// Code offsets of Jump Table headers.
|
||||
pub jt_offsets: JumpTableOffsets,
|
||||
|
||||
/// Source locations.
|
||||
///
|
||||
/// Track the original source location for each instruction. The source locations are not
|
||||
@@ -87,6 +91,7 @@ impl Function {
|
||||
encodings: SecondaryMap::new(),
|
||||
locations: SecondaryMap::new(),
|
||||
offsets: SecondaryMap::new(),
|
||||
jt_offsets: SecondaryMap::new(),
|
||||
srclocs: SecondaryMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,10 @@ impl InstructionData {
|
||||
ref args,
|
||||
..
|
||||
} => BranchInfo::SingleDest(destination, &args.as_slice(pool)[2..]),
|
||||
InstructionData::BranchTable { table, .. } => BranchInfo::Table(table),
|
||||
InstructionData::BranchTable {
|
||||
table, destination, ..
|
||||
} => BranchInfo::Table(table, Some(destination)),
|
||||
InstructionData::IndirectJump { table, .. } => BranchInfo::Table(table, None),
|
||||
_ => {
|
||||
debug_assert!(!self.opcode().is_branch());
|
||||
BranchInfo::NotABranch
|
||||
@@ -213,7 +216,7 @@ impl InstructionData {
|
||||
| InstructionData::BranchInt { destination, .. }
|
||||
| InstructionData::BranchFloat { destination, .. }
|
||||
| InstructionData::BranchIcmp { destination, .. } => Some(destination),
|
||||
InstructionData::BranchTable { .. } => None,
|
||||
InstructionData::BranchTable { .. } | InstructionData::IndirectJump { .. } => None,
|
||||
_ => {
|
||||
debug_assert!(!self.opcode().is_branch());
|
||||
None
|
||||
@@ -284,8 +287,8 @@ pub enum BranchInfo<'a> {
|
||||
/// This is a branch or jump to a single destination EBB, possibly taking value arguments.
|
||||
SingleDest(Ebb, &'a [Value]),
|
||||
|
||||
/// This is a jump table branch which can have many destination EBBs.
|
||||
Table(JumpTable),
|
||||
/// This is a jump table branch which can have many destination EBBs and maybe one default EBB.
|
||||
Table(JumpTable, Option<Ebb>),
|
||||
}
|
||||
|
||||
/// Information about call instructions.
|
||||
|
||||
@@ -45,6 +45,11 @@ impl JumpTableData {
|
||||
self.table.len()
|
||||
}
|
||||
|
||||
/// Boolean that is false if the table has missing entries.
|
||||
pub fn fully_dense(&self) -> bool {
|
||||
self.holes == 0
|
||||
}
|
||||
|
||||
/// Set a table entry.
|
||||
///
|
||||
/// The table will grow as needed to fit `idx`.
|
||||
|
||||
@@ -62,5 +62,8 @@ pub type InstEncodings = SecondaryMap<Inst, isa::Encoding>;
|
||||
/// Code offsets for EBBs.
|
||||
pub type EbbOffsets = SecondaryMap<Ebb, binemit::CodeOffset>;
|
||||
|
||||
/// Code offsets for Jump Tables.
|
||||
pub type JumpTableOffsets = SecondaryMap<JumpTable, binemit::CodeOffset>;
|
||||
|
||||
/// Source locations for instructions.
|
||||
pub type SourceLocs = SecondaryMap<Inst, SourceLoc>;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use super::registers::RU;
|
||||
use binemit::{bad_encoding, CodeSink, Reloc};
|
||||
use ir::condcodes::{CondCode, FloatCC, IntCC};
|
||||
use ir::{Ebb, Function, Inst, InstructionData, Opcode, TrapCode};
|
||||
use ir::{Ebb, Function, Inst, InstructionData, JumpTable, Opcode, TrapCode};
|
||||
use isa::{RegUnit, StackBase, StackBaseMask, StackRef};
|
||||
use regalloc::RegDiversions;
|
||||
|
||||
@@ -249,6 +249,7 @@ fn sib_noindex<CS: CodeSink + ?Sized>(base: RegUnit, sink: &mut CS) {
|
||||
sink.put1(b);
|
||||
}
|
||||
|
||||
/// Emit a SIB byte with a scale, base, and index.
|
||||
fn sib<CS: CodeSink + ?Sized>(scale: u8, index: RegUnit, base: RegUnit, sink: &mut CS) {
|
||||
// SIB SS_III_BBB.
|
||||
debug_assert_eq!(scale & !0x03, 0, "Scale out of range");
|
||||
@@ -332,3 +333,8 @@ fn disp4<CS: CodeSink + ?Sized>(destination: Ebb, func: &Function, sink: &mut CS
|
||||
let delta = func.offsets[destination].wrapping_sub(sink.offset() + 4);
|
||||
sink.put4(delta);
|
||||
}
|
||||
|
||||
fn jt_disp4<CS: CodeSink + ?Sized>(jt: JumpTable, func: &Function, sink: &mut CS) {
|
||||
let delta = func.jt_offsets[jt].wrapping_sub(sink.offset() + 4);
|
||||
sink.put4(delta);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
use bitset::BitSet;
|
||||
use cursor::{Cursor, FuncCursor};
|
||||
use flowgraph::ControlFlowGraph;
|
||||
use ir::types::I32;
|
||||
use ir::{self, InstBuilder, MemFlags};
|
||||
use isa::TargetIsa;
|
||||
use timing;
|
||||
@@ -170,6 +171,72 @@ fn expand_cond_trap(
|
||||
|
||||
/// Jump tables.
|
||||
fn expand_br_table(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
cfg: &mut ControlFlowGraph,
|
||||
isa: &TargetIsa,
|
||||
) {
|
||||
if isa.flags().jump_tables_enabled() {
|
||||
expand_br_table_jt(inst, func, cfg, isa);
|
||||
} else {
|
||||
expand_br_table_conds(inst, func, cfg, isa);
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand br_table to jump table.
|
||||
fn expand_br_table_jt(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
cfg: &mut ControlFlowGraph,
|
||||
isa: &TargetIsa,
|
||||
) {
|
||||
use ir::condcodes::IntCC;
|
||||
|
||||
let (arg, default_ebb, table) = match func.dfg[inst] {
|
||||
ir::InstructionData::BranchTable {
|
||||
opcode: ir::Opcode::BrTable,
|
||||
arg,
|
||||
destination,
|
||||
table,
|
||||
} => (arg, destination, table),
|
||||
_ => panic!("Expected br_table: {}", func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
let table_size = func.jump_tables[table].len();
|
||||
let table_is_fully_dense = func.jump_tables[table].fully_dense();
|
||||
let addr_ty = isa.pointer_type();
|
||||
let entry_ty = I32;
|
||||
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
|
||||
// Bounds check
|
||||
let oob = pos
|
||||
.ins()
|
||||
.icmp_imm(IntCC::UnsignedGreaterThanOrEqual, arg, table_size as i64);
|
||||
|
||||
pos.ins().brnz(oob, default_ebb, &[]);
|
||||
|
||||
let base_addr = pos.ins().jump_table_base(addr_ty, table);
|
||||
let entry = pos
|
||||
.ins()
|
||||
.jump_table_entry(addr_ty, arg, base_addr, entry_ty.bytes() as u8, table);
|
||||
|
||||
// If the table isn't fully dense, zero-check the entry.
|
||||
if !table_is_fully_dense {
|
||||
pos.ins().brz(entry, default_ebb, &[]);
|
||||
}
|
||||
|
||||
let addr = pos.ins().iadd(base_addr, entry);
|
||||
pos.ins().indirect_jump_table_br(addr, table);
|
||||
|
||||
let ebb = pos.current_ebb().unwrap();
|
||||
pos.remove_inst();
|
||||
cfg.recompute_ebb(pos.func, ebb);
|
||||
}
|
||||
|
||||
/// Expand br_table to series of conditionals.
|
||||
fn expand_br_table_conds(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
cfg: &mut ControlFlowGraph,
|
||||
@@ -177,17 +244,17 @@ fn expand_br_table(
|
||||
) {
|
||||
use ir::condcodes::IntCC;
|
||||
|
||||
let (arg, table) = match func.dfg[inst] {
|
||||
let (arg, default_ebb, table) = match func.dfg[inst] {
|
||||
ir::InstructionData::BranchTable {
|
||||
opcode: ir::Opcode::BrTable,
|
||||
arg,
|
||||
destination,
|
||||
table,
|
||||
} => (arg, table),
|
||||
} => (arg, destination, table),
|
||||
_ => panic!("Expected br_table: {}", func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
// This is a poor man's jump table using just a sequence of conditional branches.
|
||||
// TODO: Lower into a jump table load and indirect branch.
|
||||
let table_size = func.jump_tables[table].len();
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
@@ -199,7 +266,9 @@ fn expand_br_table(
|
||||
}
|
||||
}
|
||||
|
||||
// `br_table` falls through when nothing matches.
|
||||
// `br_table` jumps to the default destination if nothing matches
|
||||
pos.ins().jump(default_ebb, &[]);
|
||||
|
||||
let ebb = pos.current_ebb().unwrap();
|
||||
pos.remove_inst();
|
||||
cfg.recompute_ebb(pos.func, ebb);
|
||||
|
||||
@@ -900,11 +900,15 @@ impl<'a> Context<'a> {
|
||||
let lr = &self.liveness[value];
|
||||
lr.is_livein(ebb, ctx)
|
||||
}
|
||||
Table(jt) => {
|
||||
Table(jt, ebb) => {
|
||||
let lr = &self.liveness[value];
|
||||
!lr.is_local() && self.cur.func.jump_tables[jt]
|
||||
.entries()
|
||||
.any(|(_, ebb)| lr.is_livein(ebb, ctx))
|
||||
!lr.is_local()
|
||||
&& (ebb.map_or(false, |ebb| lr.is_livein(ebb, ctx)) || self
|
||||
.cur
|
||||
.func
|
||||
.jump_tables[jt]
|
||||
.entries()
|
||||
.any(|(_, ebb)| lr.is_livein(ebb, ctx)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +380,8 @@ mod tests {
|
||||
allones_funcaddrs = false\n\
|
||||
probestack_enabled = true\n\
|
||||
probestack_func_adjusts_sp = false\n\
|
||||
probestack_size_log2 = 12\n"
|
||||
probestack_size_log2 = 12\n\
|
||||
jump_tables_enabled = true\n"
|
||||
);
|
||||
assert_eq!(f.opt_level(), super::OptLevel::Default);
|
||||
assert_eq!(f.enable_simd(), true);
|
||||
|
||||
@@ -135,7 +135,12 @@ impl<'a> FlagsVerifier<'a> {
|
||||
merge(&mut live_val, val, inst, errors)?;
|
||||
}
|
||||
}
|
||||
BranchInfo::Table(jt) => {
|
||||
BranchInfo::Table(jt, dest) => {
|
||||
if let Some(dest) = dest {
|
||||
if let Some(val) = self.livein[dest].expand() {
|
||||
merge(&mut live_val, val, inst, errors)?;
|
||||
}
|
||||
}
|
||||
for (_, dest) in self.func.jump_tables[jt].entries() {
|
||||
if let Some(val) = self.livein[dest].expand() {
|
||||
merge(&mut live_val, val, inst, errors)?;
|
||||
|
||||
@@ -332,9 +332,21 @@ impl<'a> LocationVerifier<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Table(jt) => {
|
||||
Table(jt, ebb) => {
|
||||
for d in divert.all() {
|
||||
let lr = &liveness[d.value];
|
||||
if let Some(ebb) = ebb {
|
||||
if lr.is_livein(ebb, liveness.context(&self.func.layout)) {
|
||||
return fatal!(
|
||||
errors,
|
||||
inst,
|
||||
"{} is diverted to {} and live in to {}",
|
||||
d.value,
|
||||
d.to.display(&self.reginfo),
|
||||
ebb
|
||||
);
|
||||
}
|
||||
}
|
||||
for (_, ebb) in self.func.jump_tables[jt].entries() {
|
||||
if lr.is_livein(ebb, liveness.context(&self.func.layout)) {
|
||||
return fatal!(
|
||||
|
||||
@@ -657,7 +657,10 @@ impl<'a> Verifier<'a> {
|
||||
self.verify_ebb(inst, destination, errors)?;
|
||||
self.verify_value_list(inst, args, errors)?;
|
||||
}
|
||||
BranchTable { table, .. } => {
|
||||
BranchTable { table, .. }
|
||||
| BranchTableBase { table, .. }
|
||||
| BranchTableEntry { table, .. }
|
||||
| IndirectJump { table, .. } => {
|
||||
self.verify_jump_table(inst, table, errors)?;
|
||||
}
|
||||
Call {
|
||||
@@ -1213,7 +1216,19 @@ impl<'a> Verifier<'a> {
|
||||
.map(|&v| self.func.dfg.value_type(v));
|
||||
self.typecheck_variable_args_iterator(inst, iter, errors)?;
|
||||
}
|
||||
BranchInfo::Table(table) => {
|
||||
BranchInfo::Table(table, ebb) => {
|
||||
if let Some(ebb) = ebb {
|
||||
let arg_count = self.func.dfg.num_ebb_params(ebb);
|
||||
if arg_count != 0 {
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"takes no arguments, but had target {} with {} arguments",
|
||||
ebb,
|
||||
arg_count
|
||||
);
|
||||
}
|
||||
}
|
||||
for (_, ebb) in self.func.jump_tables[table].entries() {
|
||||
let arg_count = self.func.dfg.num_ebb_params(ebb);
|
||||
if arg_count != 0 {
|
||||
|
||||
@@ -443,7 +443,17 @@ pub fn write_operands(
|
||||
write!(w, " {} {}, {}, {}", cond, args[0], args[1], destination)?;
|
||||
write_ebb_args(w, &args[2..])
|
||||
}
|
||||
BranchTable { arg, table, .. } => write!(w, " {}, {}", arg, table),
|
||||
BranchTable {
|
||||
arg,
|
||||
destination,
|
||||
table,
|
||||
..
|
||||
} => write!(w, " {}, {}, {}", arg, destination, table),
|
||||
BranchTableBase { table, .. } => write!(w, " {}", table),
|
||||
BranchTableEntry {
|
||||
args, imm, table, ..
|
||||
} => write!(w, " {}, {}, {}, {}", args[0], args[1], imm, table),
|
||||
IndirectJump { arg, table, .. } => write!(w, " {}, {}", arg, table),
|
||||
Call {
|
||||
func_ref, ref args, ..
|
||||
} => write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool))),
|
||||
|
||||
Reference in New Issue
Block a user