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:
Tyler McMullen
2018-10-03 11:04:21 -06:00
committed by Dan Gohman
parent de1d82b4ba
commit 79cea5e18b
39 changed files with 627 additions and 100 deletions

View File

@@ -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)

View File

@@ -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(

View File

@@ -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),
)
)

View File

@@ -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())

View File

@@ -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?',

View File

@@ -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))

View File

@@ -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
#

View File

@@ -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.
#