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.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user