From f8ae62200303b56c346a00f27d63b44d557e8dc2 Mon Sep 17 00:00:00 2001 From: Sean Stangl Date: Wed, 13 Nov 2019 18:01:13 -0700 Subject: [PATCH] Use a struct interface for creating and reading encoding bits on x86. #1156 (#1212) --- cranelift/codegen/meta/src/isa/x86/recipes.rs | 32 +-- cranelift/codegen/shared/Cargo.toml | 2 + cranelift/codegen/shared/src/isa/mod.rs | 3 + .../shared/src/isa/x86/encoding_bits.rs | 236 ++++++++++++++++++ cranelift/codegen/shared/src/isa/x86/mod.rs | 4 + cranelift/codegen/shared/src/lib.rs | 5 + cranelift/codegen/src/isa/x86/binemit.rs | 36 +-- 7 files changed, 273 insertions(+), 45 deletions(-) create mode 100644 cranelift/codegen/shared/src/isa/mod.rs create mode 100644 cranelift/codegen/shared/src/isa/x86/encoding_bits.rs create mode 100644 cranelift/codegen/shared/src/isa/x86/mod.rs diff --git a/cranelift/codegen/meta/src/isa/x86/recipes.rs b/cranelift/codegen/meta/src/isa/x86/recipes.rs index 6b5367a50e61..6fcd0b45647b 100644 --- a/cranelift/codegen/meta/src/isa/x86/recipes.rs +++ b/cranelift/codegen/meta/src/isa/x86/recipes.rs @@ -1,5 +1,8 @@ +//! Encoding recipes for x86/x86_64. use std::rc::Rc; +use cranelift_codegen_shared::isa::x86::EncodingBits; + use crate::cdsl::ast::Literal; use crate::cdsl::formats::InstructionFormat; use crate::cdsl::instructions::InstructionPredicate; @@ -96,33 +99,8 @@ impl<'builder> RecipeGroup<'builder> { /// Given a sequence of opcode bytes, compute the recipe name prefix and encoding bits. fn decode_opcodes(op_bytes: &[u8], rrr: u16, w: u16) -> (&'static str, u16) { - assert!(!op_bytes.is_empty(), "at least one opcode byte"); - - let prefix_bytes = &op_bytes[..op_bytes.len() - 1]; - let (name, mmpp) = match prefix_bytes { - [] => ("Op1", 0b000), - [0x66] => ("Mp1", 0b0001), - [0xf3] => ("Mp1", 0b0010), - [0xf2] => ("Mp1", 0b0011), - [0x0f] => ("Op2", 0b0100), - [0x66, 0x0f] => ("Mp2", 0b0101), - [0xf3, 0x0f] => ("Mp2", 0b0110), - [0xf2, 0x0f] => ("Mp2", 0b0111), - [0x0f, 0x38] => ("Op3", 0b1000), - [0x66, 0x0f, 0x38] => ("Mp3", 0b1001), - [0xf3, 0x0f, 0x38] => ("Mp3", 0b1010), - [0xf2, 0x0f, 0x38] => ("Mp3", 0b1011), - [0x0f, 0x3a] => ("Op3", 0b1100), - [0x66, 0x0f, 0x3a] => ("Mp3", 0b1101), - [0xf3, 0x0f, 0x3a] => ("Mp3", 0b1110), - [0xf2, 0x0f, 0x3a] => ("Mp3", 0b1111), - _ => { - panic!("unexpected opcode sequence: {:?}", op_bytes); - } - }; - - let opcode_byte = u16::from(op_bytes[op_bytes.len() - 1]); - (name, opcode_byte | (mmpp << 8) | (rrr << 12) | w << 15) + let enc = EncodingBits::new(op_bytes, rrr, w); + (enc.prefix.recipe_name_prefix(), enc.bits()) } /// Given a snippet of Rust code (or None), replace the `PUT_OP` macro with the diff --git a/cranelift/codegen/shared/Cargo.toml b/cranelift/codegen/shared/Cargo.toml index 09d284978551..2ab2bfc49248 100644 --- a/cranelift/codegen/shared/Cargo.toml +++ b/cranelift/codegen/shared/Cargo.toml @@ -9,3 +9,5 @@ readme = "README.md" edition = "2018" [dependencies] +packed_struct = "0.3" +packed_struct_codegen = "0.3" diff --git a/cranelift/codegen/shared/src/isa/mod.rs b/cranelift/codegen/shared/src/isa/mod.rs new file mode 100644 index 000000000000..4d8e485f6c72 --- /dev/null +++ b/cranelift/codegen/shared/src/isa/mod.rs @@ -0,0 +1,3 @@ +//! Shared ISA-specific definitions. + +pub mod x86; diff --git a/cranelift/codegen/shared/src/isa/x86/encoding_bits.rs b/cranelift/codegen/shared/src/isa/x86/encoding_bits.rs new file mode 100644 index 000000000000..cd83e99a32a8 --- /dev/null +++ b/cranelift/codegen/shared/src/isa/x86/encoding_bits.rs @@ -0,0 +1,236 @@ +//! Provides a named interface to the `u16` Encoding bits. + +use packed_struct::prelude::*; + +/// Named interface to the `u16` Encoding bits, representing an opcode. +/// +/// Cranelift requires each recipe to have a single encoding size in bytes. +/// X86 opcodes are variable length, so we use separate recipes for different +/// styles of opcodes and prefixes. The opcode format is indicated by the +/// recipe name 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: +/// +/// 0-7: The opcode byte . +/// 8-9: pp, mandatory prefix: +/// 00: none (Op*) +/// 01: 66 (Mp*) +/// 10: F3 (Mp*) +/// 11: F2 (Mp*) +/// 10-11: mm, opcode map: +/// 00: (Op1/Mp1) +/// 01: 0F (Op2/Mp2) +/// 10: 0F 38 (Op3/Mp3) +/// 11: 0F 3A (Op3/Mp3) +/// 12-14 rrr, opcode bits for the ModR/M byte for certain opcodes. +/// 15: REX.W bit (or VEX.W/E) +#[derive(Copy, Clone, PartialEq, PackedStruct)] +#[packed_struct(size_bytes = "2", bit_numbering = "lsb0")] +pub struct EncodingBits { + /// Instruction opcode byte, without the prefix. + #[packed_field(bits = "0:7")] + pub opcode_byte: u8, + + /// Prefix kind for the instruction, as an enum. + #[packed_field(bits = "8:11", ty = "enum")] + pub prefix: OpcodePrefix, + + /// Bits for the ModR/M byte for certain opcodes. + #[packed_field(bits = "12:14")] + pub rrr: Integer, + + /// REX.W bit (or VEX.W/E). + #[packed_field(bits = "15")] + pub rex_w: Integer, +} + +impl From for EncodingBits { + fn from(bits: u16) -> EncodingBits { + let bytes: [u8; 2] = [((bits >> 8) & 0xff) as u8, (bits & 0xff) as u8]; + EncodingBits::unpack(&bytes).expect("failed creating EncodingBits") + } +} + +impl EncodingBits { + /// Constructs a new EncodingBits from parts. + pub fn new(op_bytes: &[u8], rrr: u16, rex_w: u16) -> Self { + EncodingBits { + opcode_byte: op_bytes[op_bytes.len() - 1], + prefix: OpcodePrefix::from_opcode(op_bytes), + rrr: (rrr as u8).into(), + rex_w: (rex_w as u8).into(), + } + } + + /// Returns the raw bits. + #[inline] + pub fn bits(self) -> u16 { + let bytes: [u8; 2] = self.pack(); + ((bytes[0] as u16) << 8) | (bytes[1] as u16) + } + + /// Extracts the PP bits of the OpcodePrefix. + #[inline] + pub fn pp(self) -> u8 { + self.prefix.to_primitive() & 0x3 + } + + /// Extracts the MM bits of the OpcodePrefix. + #[inline] + pub fn mm(self) -> u8 { + (self.prefix.to_primitive() >> 2) & 0x3 + } +} + +/// Opcode prefix representation. +/// +/// The prefix type occupies four of the EncodingBits. +#[allow(non_camel_case_types)] +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PrimitiveEnum_u8)] +pub enum OpcodePrefix { + Op1 = 0b0000, + Mp1_66 = 0b0001, + Mp1_f3 = 0b0010, + Mp1_f2 = 0b0011, + Op2_0f = 0b0100, + Mp2_66_0f = 0b0101, + Mp2_f3_0f = 0b0110, + Mp2_f2_0f = 0b0111, + Op3_0f_38 = 0b1000, + Mp3_66_0f_38 = 0b1001, + Mp3_f3_0f_38 = 0b1010, + Mp3_f2_0f_38 = 0b1011, + Op3_0f_3a = 0b1100, + Mp3_66_0f_3a = 0b1101, + Mp3_f3_0f_3a = 0b1110, + Mp3_f2_0f_3a = 0b1111, +} + +impl From for OpcodePrefix { + fn from(n: u8) -> OpcodePrefix { + OpcodePrefix::from_primitive(n).expect("invalid OpcodePrefix") + } +} + +impl OpcodePrefix { + /// Extracts the OpcodePrefix from the opcode. + pub fn from_opcode(op_bytes: &[u8]) -> OpcodePrefix { + assert!(!op_bytes.is_empty(), "at least one opcode byte"); + + let prefix_bytes = &op_bytes[..op_bytes.len() - 1]; + match prefix_bytes { + [] => OpcodePrefix::Op1, + [0x66] => OpcodePrefix::Mp1_66, + [0xf3] => OpcodePrefix::Mp1_f3, + [0xf2] => OpcodePrefix::Mp1_f2, + [0x0f] => OpcodePrefix::Op2_0f, + [0x66, 0x0f] => OpcodePrefix::Mp2_66_0f, + [0xf3, 0x0f] => OpcodePrefix::Mp2_f3_0f, + [0xf2, 0x0f] => OpcodePrefix::Mp2_f2_0f, + [0x0f, 0x38] => OpcodePrefix::Op3_0f_38, + [0x66, 0x0f, 0x38] => OpcodePrefix::Mp3_66_0f_38, + [0xf3, 0x0f, 0x38] => OpcodePrefix::Mp3_f3_0f_38, + [0xf2, 0x0f, 0x38] => OpcodePrefix::Mp3_f2_0f_38, + [0x0f, 0x3a] => OpcodePrefix::Op3_0f_3a, + [0x66, 0x0f, 0x3a] => OpcodePrefix::Mp3_66_0f_3a, + [0xf3, 0x0f, 0x3a] => OpcodePrefix::Mp3_f3_0f_3a, + [0xf2, 0x0f, 0x3a] => OpcodePrefix::Mp3_f2_0f_3a, + _ => { + panic!("unexpected opcode sequence: {:?}", op_bytes); + } + } + } + + /// Returns the recipe name prefix. + /// + /// At the moment, each similar OpcodePrefix group is given its own Recipe. + /// In order to distinguish them, this string is prefixed. + pub fn recipe_name_prefix(self) -> &'static str { + use OpcodePrefix::*; + match self { + Op1 => "Op1", + Op2_0f => "Op2", + Op3_0f_38 | Op3_0f_3a => "Op3", + Mp1_66 | Mp1_f3 | Mp1_f2 => "Mp1", + Mp2_66_0f | Mp2_f3_0f | Mp2_f2_0f => "Mp2", + Mp3_66_0f_38 | Mp3_f3_0f_38 | Mp3_f2_0f_38 => "Mp3", + Mp3_66_0f_3a | Mp3_f3_0f_3a | Mp3_f2_0f_3a => "Mp3", + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Helper function for prefix_roundtrip() to avoid long lines. + fn test_roundtrip(p: OpcodePrefix) { + assert_eq!(p, OpcodePrefix::from(p.to_primitive())); + } + + /// Tests that to/from each opcode matches. + #[test] + fn prefix_roundtrip() { + test_roundtrip(OpcodePrefix::Op1); + test_roundtrip(OpcodePrefix::Mp1_66); + test_roundtrip(OpcodePrefix::Mp1_f3); + test_roundtrip(OpcodePrefix::Mp1_f2); + test_roundtrip(OpcodePrefix::Op2_0f); + test_roundtrip(OpcodePrefix::Mp2_66_0f); + test_roundtrip(OpcodePrefix::Mp2_f3_0f); + test_roundtrip(OpcodePrefix::Mp2_f2_0f); + test_roundtrip(OpcodePrefix::Op3_0f_38); + test_roundtrip(OpcodePrefix::Mp3_66_0f_38); + test_roundtrip(OpcodePrefix::Mp3_f3_0f_38); + test_roundtrip(OpcodePrefix::Mp3_f2_0f_38); + test_roundtrip(OpcodePrefix::Op3_0f_3a); + test_roundtrip(OpcodePrefix::Mp3_66_0f_3a); + test_roundtrip(OpcodePrefix::Mp3_f3_0f_3a); + test_roundtrip(OpcodePrefix::Mp3_f2_0f_3a); + } + + /// Tests that the opcode_byte is the lower of the EncodingBits. + #[test] + fn encodingbits_opcode_byte() { + let enc = EncodingBits::from(0x00ff); + assert_eq!(enc.opcode_byte, 0xff); + assert_eq!(enc.prefix.to_primitive(), 0x0); + assert_eq!(u8::from(enc.rrr), 0x0); + assert_eq!(u8::from(enc.rex_w), 0x0); + + let enc = EncodingBits::from(0x00cd); + assert_eq!(enc.opcode_byte, 0xcd); + } + + /// Tests that the OpcodePrefix is encoded correctly. + #[test] + fn encodingbits_prefix() { + let enc = EncodingBits::from(0x0c00); + assert_eq!(enc.opcode_byte, 0x00); + assert_eq!(enc.prefix.to_primitive(), 0xc); + assert_eq!(enc.prefix, OpcodePrefix::Op3_0f_3a); + assert_eq!(u8::from(enc.rrr), 0x0); + assert_eq!(u8::from(enc.rex_w), 0x0); + } + + /// Tests that the REX.W bit is encoded correctly. + #[test] + fn encodingbits_rex_w() { + let enc = EncodingBits::from(0x8000); + assert_eq!(enc.opcode_byte, 0x00); + assert_eq!(enc.prefix.to_primitive(), 0x0); + assert_eq!(u8::from(enc.rrr), 0x0); + assert_eq!(u8::from(enc.rex_w), 0x1); + } + + /// Tests a round-trip of EncodingBits from/to a u16 (hardcoded endianness). + #[test] + fn encodingbits_roundtrip() { + let bits: u16 = 0x1234; + assert_eq!(EncodingBits::from(bits).bits(), bits); + } +} diff --git a/cranelift/codegen/shared/src/isa/x86/mod.rs b/cranelift/codegen/shared/src/isa/x86/mod.rs new file mode 100644 index 000000000000..fb45ae56c3b5 --- /dev/null +++ b/cranelift/codegen/shared/src/isa/x86/mod.rs @@ -0,0 +1,4 @@ +//! Shared x86-specific definitions. + +mod encoding_bits; +pub use encoding_bits::*; diff --git a/cranelift/codegen/shared/src/lib.rs b/cranelift/codegen/shared/src/lib.rs index a2c5f3df33d4..771f5dfd9d03 100644 --- a/cranelift/codegen/shared/src/lib.rs +++ b/cranelift/codegen/shared/src/lib.rs @@ -20,9 +20,14 @@ ) )] +use packed_struct; +#[macro_use] +extern crate packed_struct_codegen; + pub mod condcodes; pub mod constant_hash; pub mod constants; +pub mod isa; /// Version number of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/cranelift/codegen/src/isa/x86/binemit.rs b/cranelift/codegen/src/isa/x86/binemit.rs index cda6ae58074f..626301d0e22f 100644 --- a/cranelift/codegen/src/isa/x86/binemit.rs +++ b/cranelift/codegen/src/isa/x86/binemit.rs @@ -8,6 +8,8 @@ use crate::ir::{Constant, Ebb, Function, Inst, InstructionData, JumpTable, Opcod use crate::isa::{RegUnit, StackBase, StackBaseMask, StackRef, TargetIsa}; use crate::regalloc::RegDiversions; +use cranelift_codegen_shared::isa::x86::EncodingBits; + include!(concat!(env!("OUT_DIR"), "/binemit-x86.rs")); // Convert a stack base to the corresponding register. @@ -65,8 +67,8 @@ fn rex3(rm: RegUnit, reg: RegUnit, index: RegUnit) -> u8 { // extracted from `bits`. fn rex_prefix(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(rex & 0xf8, BASE_REX); - let w = ((bits >> 15) & 1) as u8; - sink.put1(rex | (w << 3)); + let w = EncodingBits::from(bits).rex_w; + sink.put1(rex | (u8::from(w) << 3)); } // Emit a single-byte opcode with no REX prefix. @@ -102,8 +104,8 @@ fn put_rexop2(bits: u16, rex: u8, sink: &mut CS) { // Emit single-byte opcode with mandatory prefix. fn put_mp1(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x8c00, 0, "Invalid encoding bits for Mp1*"); - let pp = (bits >> 8) & 3; - sink.put1(PREFIX[(pp - 1) as usize]); + let enc = EncodingBits::from(bits); + sink.put1(PREFIX[(enc.pp() - 1) as usize]); debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Mp1 encoding"); sink.put1(bits as u8); } @@ -111,8 +113,8 @@ fn put_mp1(bits: u16, rex: u8, sink: &mut CS) { // Emit single-byte opcode with mandatory prefix and REX. fn put_rexmp1(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]); + let enc = EncodingBits::from(bits); + sink.put1(PREFIX[(enc.pp() - 1) as usize]); rex_prefix(bits, rex, sink); sink.put1(bits as u8); } @@ -120,8 +122,8 @@ fn put_rexmp1(bits: u16, rex: u8, sink: &mut CS) { // Emit two-byte opcode (0F XX) with mandatory prefix. fn put_mp2(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x8c00, 0x0400, "Invalid encoding bits for Mp2*"); - let pp = (bits >> 8) & 3; - sink.put1(PREFIX[(pp - 1) as usize]); + let enc = EncodingBits::from(bits); + sink.put1(PREFIX[(enc.pp() - 1) as usize]); debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Mp2 encoding"); sink.put1(0x0f); sink.put1(bits as u8); @@ -130,8 +132,8 @@ fn put_mp2(bits: u16, rex: u8, sink: &mut CS) { // Emit two-byte opcode (0F XX) with mandatory prefix and REX. fn put_rexmp2(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x0c00, 0x0400, "Invalid encoding bits for Mp2*"); - let pp = (bits >> 8) & 3; - sink.put1(PREFIX[(pp - 1) as usize]); + let enc = EncodingBits::from(bits); + sink.put1(PREFIX[(enc.pp() - 1) as usize]); rex_prefix(bits, rex, sink); sink.put1(0x0f); sink.put1(bits as u8); @@ -140,24 +142,22 @@ fn put_rexmp2(bits: u16, rex: u8, sink: &mut CS) { // Emit three-byte opcode (0F 3[8A] XX) with mandatory prefix. fn put_mp3(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x8800, 0x0800, "Invalid encoding bits for Mp3*"); - let pp = (bits >> 8) & 3; - sink.put1(PREFIX[(pp - 1) as usize]); debug_assert_eq!(rex, BASE_REX, "Invalid registers for REX-less Mp3 encoding"); - let mm = (bits >> 10) & 3; + let enc = EncodingBits::from(bits); + sink.put1(PREFIX[(enc.pp() - 1) as usize]); sink.put1(0x0f); - sink.put1(OP3_BYTE2[(mm - 2) as usize]); + sink.put1(OP3_BYTE2[(enc.mm() - 2) as usize]); sink.put1(bits as u8); } // Emit three-byte opcode (0F 3[8A] XX) with mandatory prefix and REX fn put_rexmp3(bits: u16, rex: u8, sink: &mut CS) { debug_assert_eq!(bits & 0x0800, 0x0800, "Invalid encoding bits for Mp3*"); - let pp = (bits >> 8) & 3; - sink.put1(PREFIX[(pp - 1) as usize]); + let enc = EncodingBits::from(bits); + sink.put1(PREFIX[(enc.pp() - 1) as usize]); rex_prefix(bits, rex, sink); - let mm = (bits >> 10) & 3; sink.put1(0x0f); - sink.put1(OP3_BYTE2[(mm - 2) as usize]); + sink.put1(OP3_BYTE2[(enc.mm() - 2) as usize]); sink.put1(bits as u8); }