From fb227cb389bb357265d13cb4ccbd502bca62f1a0 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 10 Jul 2017 14:06:08 -0700 Subject: [PATCH] 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. --- lib/cretonne/meta/isa/intel/recipes.py | 163 ++++++++++-- lib/cretonne/src/isa/intel/binemit.rs | 332 +++---------------------- 2 files changed, 176 insertions(+), 319 deletions(-) diff --git a/lib/cretonne/meta/isa/intel/recipes.py b/lib/cretonne/meta/isa/intel/recipes.py index 3700ff60b5..ebeacc5968 100644 --- a/lib/cretonne/meta/isa/intel/recipes.py +++ b/lib/cretonne/meta/isa/intel/recipes.py @@ -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); + ''') diff --git a/lib/cretonne/src/isa/intel/binemit.rs b/lib/cretonne/src/isa/intel/binemit.rs index 831ef4c756..e2cb5396bc 100644 --- a/lib/cretonne/src/isa/intel/binemit.rs +++ b/lib/cretonne/src/isa/intel/binemit.rs @@ -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 for RelocKind { } } -// Emit single-byte opcode. -fn put_op1(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(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(bits: u16, sink: &mut CS) { - debug_assert_eq!(bits & 0x0f00, 0x0400, "Invalid encoding bits for Op2*"); +fn put_op2(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(bits: u16, sink: &mut CS) { +fn put_mp1(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(rm: RegUnit, reg: RegUnit, sink: &mut CS) b |= rm; sink.put1(b); } - -fn recipe_op1rr(_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(_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(_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(_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(_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(_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(_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(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(_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(_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(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(_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(_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(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(_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(_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(_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(_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(_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(_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(_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(_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(_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(_func: &Function, _inst: Inst, sink: &mut CS, bits: u16) { - put_op1(bits, sink); -}