This commit is contained in:
@@ -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
|
||||
|
||||
@@ -9,3 +9,5 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
packed_struct = "0.3"
|
||||
packed_struct_codegen = "0.3"
|
||||
|
||||
3
cranelift/codegen/shared/src/isa/mod.rs
Normal file
3
cranelift/codegen/shared/src/isa/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
//! Shared ISA-specific definitions.
|
||||
|
||||
pub mod x86;
|
||||
236
cranelift/codegen/shared/src/isa/x86/encoding_bits.rs
Normal file
236
cranelift/codegen/shared/src/isa/x86/encoding_bits.rs
Normal file
@@ -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 <op>.
|
||||
/// 8-9: pp, mandatory prefix:
|
||||
/// 00: none (Op*)
|
||||
/// 01: 66 (Mp*)
|
||||
/// 10: F3 (Mp*)
|
||||
/// 11: F2 (Mp*)
|
||||
/// 10-11: mm, opcode map:
|
||||
/// 00: <op> (Op1/Mp1)
|
||||
/// 01: 0F <op> (Op2/Mp2)
|
||||
/// 10: 0F 38 <op> (Op3/Mp3)
|
||||
/// 11: 0F 3A <op> (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<u8, packed_bits::Bits3>,
|
||||
|
||||
/// REX.W bit (or VEX.W/E).
|
||||
#[packed_field(bits = "15")]
|
||||
pub rex_w: Integer<u8, packed_bits::Bits1>,
|
||||
}
|
||||
|
||||
impl From<u16> 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<u8> 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);
|
||||
}
|
||||
}
|
||||
4
cranelift/codegen/shared/src/isa/x86/mod.rs
Normal file
4
cranelift/codegen/shared/src/isa/x86/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
//! Shared x86-specific definitions.
|
||||
|
||||
mod encoding_bits;
|
||||
pub use encoding_bits::*;
|
||||
@@ -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");
|
||||
|
||||
@@ -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<CS: CodeSink + ?Sized>(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<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
// Emit single-byte opcode with mandatory prefix.
|
||||
fn put_mp1<CS: CodeSink + ?Sized>(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<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
// Emit single-byte opcode with mandatory prefix and REX.
|
||||
fn put_rexmp1<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]);
|
||||
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<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
// Emit two-byte opcode (0F XX) with mandatory prefix.
|
||||
fn put_mp2<CS: CodeSink + ?Sized>(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<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
// Emit two-byte opcode (0F XX) with mandatory prefix and REX.
|
||||
fn put_rexmp2<CS: CodeSink + ?Sized>(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<CS: CodeSink + ?Sized>(bits: u16, rex: u8, sink: &mut CS) {
|
||||
// Emit three-byte opcode (0F 3[8A] XX) with mandatory prefix.
|
||||
fn put_mp3<CS: CodeSink + ?Sized>(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<CS: CodeSink + ?Sized>(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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user