Move Intel recipe_* bodies into intel/recipes.py.

Use a PUT_OP macro in the TailRecipe Python class to replace the code
snippet that emits the prefixes + opcode part of the instruction encoding.

Prepare for the addition of REX prefixes by giving the PUT_OP functions
a third argument representing the REX prefix. For the non-REX encodings,
verify that no REX bits wold be needed.
This commit is contained in:
Jakob Stoklund Olesen
2017-07-10 14:06:08 -07:00
parent c5cddc3eac
commit 263779ac56
2 changed files with 176 additions and 319 deletions

View File

@@ -43,7 +43,9 @@ OPCODE_PREFIX = {
(0xf2, 0x0f, 0x3a): ('Mp3', 0b1111)
}
# VEX/XOP and EVEX prefixes are not yet supported.
# The table above does not include the REX prefix which goes after the
# mandatory prefix. VEX/XOP and EVEX prefixes are not yet supported. Encodings
# using any of these prefixes are represented by separate recipes.
#
# The encoding bits are:
#
@@ -79,6 +81,18 @@ def decode_ops(ops, rrr=0, w=0):
return (name, op | (mmpp << 8) | (rrr << 12) | (w << 15))
def replace_put_op(emit, prefix):
# type: (str, str) -> str
"""
Given a snippet of Rust code (or None), replace the `PUT_OP` macro with the
corresponding `put_*` function from the `binemit.rs` module.
"""
if emit is None:
return None
else:
return emit.replace('PUT_OP', 'put_' + prefix.lower())
class TailRecipe:
"""
Generate encoding recipes on demand.
@@ -92,6 +106,10 @@ class TailRecipe:
The arguments are the same as for an `EncRecipe`, except for `size` which
does not include the size of the opcode.
The `emit` parameter contains Rust code to actually emit an encoding, like
`EncRecipe` does it. Additionally, the text `PUT_OP` is substituted with
the proper `put_*` function from the `intel/binemit.rs` module.
"""
def __init__(
@@ -103,7 +121,8 @@ class TailRecipe:
outs, # type: ConstraintSeq
branch_range=None, # type: BranchRange
instp=None, # type: PredNode
isap=None # type: PredNode
isap=None, # type: PredNode
emit=None # type: str
):
# type: (...) -> None
self.name = name
@@ -114,6 +133,7 @@ class TailRecipe:
self.branch_range = branch_range
self.instp = instp
self.isap = isap
self.emit = emit
# Cached recipes, keyed by name prefix.
self.recipes = dict() # type: Dict[str, EncRecipe]
@@ -136,34 +156,69 @@ class TailRecipe:
outs=self.outs,
branch_range=self.branch_range,
instp=self.instp,
isap=self.isap)
isap=self.isap,
emit=replace_put_op(self.emit, name))
return (self.recipes[name], bits)
# XX /r
rr = TailRecipe('rr', Binary, size=1, ins=(GPR, GPR), outs=0)
rr = TailRecipe(
'rr', Binary, size=1, ins=(GPR, GPR), outs=0,
emit='''
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
modrm_rr(in_reg0, in_reg1, sink);
''')
# XX /r, but for a unary operator with separate input/output register, like
# copies.
ur = TailRecipe('ur', Unary, size=1, ins=GPR, outs=GPR)
ur = TailRecipe(
'ur', Unary, size=1, ins=GPR, outs=GPR,
emit='''
PUT_OP(bits, rex2(out_reg0, in_reg0), sink);
modrm_rr(out_reg0, in_reg0, sink);
''')
# XX /n with one arg in %rcx, for shifts.
rc = TailRecipe('rc', Binary, size=1, ins=(GPR, GPR.rcx), outs=0)
rc = TailRecipe(
'rc', Binary, size=1, ins=(GPR, GPR.rcx), outs=0,
emit='''
PUT_OP(bits, rex1(in_reg0), sink);
modrm_r_bits(in_reg0, bits, sink);
''')
# XX /n ib with 8-bit immediate sign-extended.
rib = TailRecipe(
'rib', BinaryImm, size=2, ins=GPR, outs=0,
instp=IsSignedInt(BinaryImm.imm, 8))
instp=IsSignedInt(BinaryImm.imm, 8),
emit='''
PUT_OP(bits, rex1(in_reg0), sink);
modrm_r_bits(in_reg0, bits, sink);
let imm: i64 = imm.into();
sink.put1(imm as u8);
''')
# XX /n id with 32-bit immediate sign-extended.
rid = TailRecipe(
'rid', BinaryImm, size=5, ins=GPR, outs=0,
instp=IsSignedInt(BinaryImm.imm, 32))
instp=IsSignedInt(BinaryImm.imm, 32),
emit='''
PUT_OP(bits, rex1(in_reg0), sink);
modrm_r_bits(in_reg0, bits, sink);
let imm: i64 = imm.into();
sink.put4(imm as u32);
''')
# XX+rd id unary with 32-bit immediate.
uid = TailRecipe(
'uid', UnaryImm, size=4, ins=(), outs=GPR,
instp=IsSignedInt(UnaryImm.imm, 32))
instp=IsSignedInt(UnaryImm.imm, 32),
emit='''
// The destination register is encoded in the low bits of the opcode.
// No ModR/M.
PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink);
let imm: i64 = imm.into();
sink.put4(imm as u32);
''')
#
# Store recipes.
@@ -172,26 +227,59 @@ uid = TailRecipe(
# XX /r register-indirect store with no offset.
st = TailRecipe(
'st', Store, size=1, ins=(GPR, GPR), outs=(),
instp=IsEqual(Store.offset, 0))
instp=IsEqual(Store.offset, 0),
emit='''
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
modrm_rm(in_reg1, in_reg0, sink);
''')
# XX /r register-indirect store with no offset.
# Only ABCD allowed for stored value. This is for byte stores.
st_abcd = TailRecipe(
'st_abcd', Store, size=1, ins=(ABCD, GPR), outs=(),
instp=IsEqual(Store.offset, 0))
instp=IsEqual(Store.offset, 0),
emit='''
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
modrm_rm(in_reg1, in_reg0, sink);
''')
# XX /r register-indirect store with 8-bit offset.
stDisp8 = TailRecipe(
'stDisp8', Store, size=2, ins=(GPR, GPR), outs=(),
instp=IsSignedInt(Store.offset, 8))
instp=IsSignedInt(Store.offset, 8),
emit='''
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
modrm_disp8(in_reg1, in_reg0, sink);
let offset: i32 = offset.into();
sink.put1(offset as u8);
''')
stDisp8_abcd = TailRecipe(
'stDisp8_abcd', Store, size=2, ins=(ABCD, GPR), outs=(),
instp=IsSignedInt(Store.offset, 8))
instp=IsSignedInt(Store.offset, 8),
emit='''
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
modrm_disp8(in_reg1, in_reg0, sink);
let offset: i32 = offset.into();
sink.put1(offset as u8);
''')
# XX /r register-indirect store with 32-bit offset.
stDisp32 = TailRecipe('stDisp32', Store, size=5, ins=(GPR, GPR), outs=())
stDisp32 = TailRecipe(
'stDisp32', Store, size=5, ins=(GPR, GPR), outs=(),
emit='''
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
modrm_disp32(in_reg1, in_reg0, sink);
let offset: i32 = offset.into();
sink.put4(offset as u32);
''')
stDisp32_abcd = TailRecipe(
'stDisp32_abcd', Store, size=5, ins=(ABCD, GPR), outs=())
'stDisp32_abcd', Store, size=5, ins=(ABCD, GPR), outs=(),
emit='''
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
modrm_disp32(in_reg1, in_reg0, sink);
let offset: i32 = offset.into();
sink.put4(offset as u32);
''')
#
# Load recipes
@@ -200,21 +288,54 @@ stDisp32_abcd = TailRecipe(
# XX /r load with no offset.
ld = TailRecipe(
'ld', Load, size=1, ins=(GPR), outs=(GPR),
instp=IsEqual(Load.offset, 0))
instp=IsEqual(Load.offset, 0),
emit='''
PUT_OP(bits, rex2(out_reg0, in_reg0), sink);
modrm_rm(in_reg0, out_reg0, sink);
''')
# XX /r load with 8-bit offset.
ldDisp8 = TailRecipe(
'ldDisp8', Load, size=2, ins=(GPR), outs=(GPR),
instp=IsSignedInt(Load.offset, 8))
instp=IsSignedInt(Load.offset, 8),
emit='''
PUT_OP(bits, rex2(out_reg0, in_reg0), sink);
modrm_disp8(in_reg0, out_reg0, sink);
let offset: i32 = offset.into();
sink.put1(offset as u8);
''')
# XX /r load with 32-bit offset.
ldDisp32 = TailRecipe(
'ldDisp32', Load, size=5, ins=(GPR), outs=(GPR),
instp=IsSignedInt(Load.offset, 32))
instp=IsSignedInt(Load.offset, 32),
emit='''
PUT_OP(bits, rex2(out_reg0, in_reg0), sink);
modrm_disp32(in_reg0, out_reg0, sink);
let offset: i32 = offset.into();
sink.put4(offset as u32);
''')
#
# Call/return
#
call_id = TailRecipe('call_id', Call, size=4, ins=(), outs=())
call_r = TailRecipe('call_r', IndirectCall, size=1, ins=GPR, outs=())
ret = TailRecipe('ret', MultiAry, size=0, ins=(), outs=())
call_id = TailRecipe(
'call_id', Call, size=4, ins=(), outs=(),
emit='''
PUT_OP(bits, BASE_REX, sink);
sink.reloc_func(RelocKind::PCRel4.into(), func_ref);
sink.put4(0);
''')
call_r = TailRecipe(
'call_r', IndirectCall, size=1, ins=GPR, outs=(),
emit='''
PUT_OP(bits, rex1(in_reg0), sink);
modrm_r_bits(in_reg0, bits, sink);
''')
ret = TailRecipe(
'ret', MultiAry, size=0, ins=(), outs=(),
emit='''
PUT_OP(bits, BASE_REX, sink);
''')

View File

@@ -1,8 +1,7 @@
//! Emitting binary Intel machine code.
use binemit::{CodeSink, Reloc, bad_encoding};
use ir::{self, Function, Inst, InstructionData, MemFlags};
use ir::immediates::{Imm64, Offset32};
use ir::{Function, Inst, InstructionData};
use isa::RegUnit;
include!(concat!(env!("OUT_DIR"), "/binemit-intel.rs"));
@@ -21,27 +20,51 @@ impl Into<Reloc> for RelocKind {
}
}
// Emit single-byte opcode.
fn put_op1<CS: CodeSink + ?Sized>(bits: u16, sink: &mut CS) {
debug_assert_eq!(bits & 0x0f00, 0, "Invalid encoding bits for Op1*");
// Mandatory prefix bytes for Mp* opcodes.
const PREFIX: [u8; 3] = [0x66, 0xf3, 0xf2];
// A REX prefix with no bits set: 0b0100WRXB.
const BASE_REX: u8 = 0b0100_0000;
// Create a single-register REX prefix, setting the B bit to bit 3 of the register.
// This is used for instructions that encode a register in the low 3 bits of the opcode and for
// instructions that use the ModR/M `reg` field for something else.
fn rex1(reg_b: RegUnit) -> u8 {
let b = ((reg_b >> 3) & 1) as u8;
BASE_REX | b
}
// Create a dual-register REX prefix, setting:
//
// REX.B = bit 3 of r/m register.
// REX.R = bit 3 of reg register.
fn rex2(rm: RegUnit, reg: RegUnit) -> u8 {
let b = ((rm >> 3) & 1) as u8;
let r = ((reg >> 3) & 1) as u8;
BASE_REX | b | (r << 2)
}
// Emit a single-byte opcode with no REX prefix.
fn put_op1<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
debug_assert_eq!(bits & 0x8f00, 0, "Invalid encoding bits for Op1*");
debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding");
sink.put1(bits as u8);
}
// Emit two-byte opcode: 0F XX
fn put_op2<CS: CodeSink + ?Sized>(bits: u16, sink: &mut CS) {
debug_assert_eq!(bits & 0x0f00, 0x0400, "Invalid encoding bits for Op2*");
fn put_op2<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
debug_assert_eq!(bits & 0x8f00, 0x0400, "Invalid encoding bits for Op2*");
debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding");
sink.put1(0x0f);
sink.put1(bits as u8);
}
// Mandatory prefix bytes for Mp* opcodes.
const PREFIX: [u8; 3] = [0x66, 0xf3, 0xf2];
// Emit single-byte opcode with mandatory prefix.
fn put_mp1<CS: CodeSink + ?Sized>(bits: u16, sink: &mut CS) {
fn put_mp1<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
debug_assert_eq!(bits & 0x0c00, 0, "Invalid encoding bits for Mp1*");
let pp = (bits >> 8) & 3;
sink.put1(PREFIX[(pp - 1) as usize]);
debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less encoding");
sink.put1(bits as u8);
}
@@ -100,290 +123,3 @@ fn modrm_disp32<CS: CodeSink + ?Sized>(rm: RegUnit, reg: RegUnit, sink: &mut CS)
b |= rm;
sink.put1(b);
}
fn recipe_op1rr<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit) {
put_op1(bits, sink);
modrm_rr(in_reg0, in_reg1, sink);
}
fn recipe_op1ur<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
out_reg0: RegUnit) {
put_op1(bits, sink);
modrm_rr(out_reg0, in_reg0, sink);
}
fn recipe_op1rc<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit) {
put_op1(bits, sink);
modrm_r_bits(in_reg0, bits, sink);
}
fn recipe_op1rib<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
imm: Imm64) {
put_op1(bits, sink);
modrm_r_bits(in_reg0, bits, sink);
let imm: i64 = imm.into();
sink.put1(imm as u8);
}
fn recipe_op1rid<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
imm: Imm64) {
put_op1(bits, sink);
modrm_r_bits(in_reg0, bits, sink);
let imm: i64 = imm.into();
sink.put4(imm as u32);
}
fn recipe_op1uid<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
imm: Imm64,
out_reg0: RegUnit) {
// The destination register is encoded in the low bits of the opcode. No ModR/M
put_op1(bits | (out_reg0 & 7), sink);
let imm: i64 = imm.into();
sink.put4(imm as u32);
}
// Store recipes.
fn recipe_op1st<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
_flags: MemFlags,
_offset: Offset32) {
put_op1(bits, sink);
modrm_rm(in_reg1, in_reg0, sink);
}
// This is just a tighter register class constraint.
fn recipe_op1st_abcd<CS: CodeSink + ?Sized>(func: &Function,
inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
flags: MemFlags,
offset: Offset32) {
recipe_op1st(func, inst, sink, bits, in_reg0, in_reg1, flags, offset)
}
fn recipe_mp1st<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
_flags: MemFlags,
_offset: Offset32) {
put_mp1(bits, sink);
modrm_rm(in_reg1, in_reg0, sink);
}
fn recipe_op1stdisp8<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
_flags: MemFlags,
offset: Offset32) {
put_op1(bits, sink);
modrm_disp8(in_reg1, in_reg0, sink);
let offset: i32 = offset.into();
sink.put1(offset as u8);
}
fn recipe_op1stdisp8_abcd<CS: CodeSink + ?Sized>(func: &Function,
inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
flags: MemFlags,
offset: Offset32) {
recipe_op1stdisp8(func, inst, sink, bits, in_reg0, in_reg1, flags, offset)
}
fn recipe_mp1stdisp8<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
_flags: MemFlags,
offset: Offset32) {
put_mp1(bits, sink);
modrm_disp8(in_reg1, in_reg0, sink);
let offset: i32 = offset.into();
sink.put1(offset as u8);
}
fn recipe_op1stdisp32<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
_flags: MemFlags,
offset: Offset32) {
put_op1(bits, sink);
modrm_disp32(in_reg1, in_reg0, sink);
let offset: i32 = offset.into();
sink.put4(offset as u32);
}
fn recipe_op1stdisp32_abcd<CS: CodeSink + ?Sized>(func: &Function,
inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
flags: MemFlags,
offset: Offset32) {
recipe_op1stdisp32(func, inst, sink, bits, in_reg0, in_reg1, flags, offset)
}
fn recipe_mp1stdisp32<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
in_reg1: RegUnit,
_flags: MemFlags,
offset: Offset32) {
put_mp1(bits, sink);
modrm_disp32(in_reg1, in_reg0, sink);
let offset: i32 = offset.into();
sink.put4(offset as u32);
}
// Load recipes
fn recipe_op1ld<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
_flags: MemFlags,
_offset: Offset32,
out_reg0: RegUnit) {
put_op1(bits, sink);
modrm_rm(in_reg0, out_reg0, sink);
}
fn recipe_op1lddisp8<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
_flags: MemFlags,
offset: Offset32,
out_reg0: RegUnit) {
put_op1(bits, sink);
modrm_disp8(in_reg0, out_reg0, sink);
let offset: i32 = offset.into();
sink.put1(offset as u8);
}
fn recipe_op1lddisp32<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
_flags: MemFlags,
offset: Offset32,
out_reg0: RegUnit) {
put_op1(bits, sink);
modrm_disp32(in_reg0, out_reg0, sink);
let offset: i32 = offset.into();
sink.put4(offset as u32);
}
fn recipe_op2ld<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
_flags: MemFlags,
_offset: Offset32,
out_reg0: RegUnit) {
put_op2(bits, sink);
modrm_rm(in_reg0, out_reg0, sink);
}
fn recipe_op2lddisp8<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
_flags: MemFlags,
offset: Offset32,
out_reg0: RegUnit) {
put_op2(bits, sink);
modrm_disp8(in_reg0, out_reg0, sink);
let offset: i32 = offset.into();
sink.put1(offset as u8);
}
fn recipe_op2lddisp32<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
_flags: MemFlags,
offset: Offset32,
out_reg0: RegUnit) {
put_op2(bits, sink);
modrm_disp32(in_reg0, out_reg0, sink);
let offset: i32 = offset.into();
sink.put4(offset as u32);
}
fn recipe_op1call_id<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
func_ref: ir::FuncRef) {
put_op1(bits, sink);
sink.reloc_func(RelocKind::PCRel4.into(), func_ref);
sink.put4(0);
}
fn recipe_op1call_r<CS: CodeSink + ?Sized>(_func: &Function,
_inst: Inst,
sink: &mut CS,
bits: u16,
in_reg0: RegUnit,
_sig_ref: ir::SigRef) {
put_op1(bits, sink);
modrm_r_bits(in_reg0, bits, sink);
}
fn recipe_op1ret<CS: CodeSink + ?Sized>(_func: &Function, _inst: Inst, sink: &mut CS, bits: u16) {
put_op1(bits, sink);
}