This repository has been archived by the owner on Jun 26, 2020. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use a struct interface for creating and reading encoding bits on x86.…
… #1156 (#1212)
- Loading branch information
Showing
7 changed files
with
273 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,5 @@ readme = "README.md" | |
edition = "2018" | ||
|
||
[dependencies] | ||
packed_struct = "0.3" | ||
packed_struct_codegen = "0.3" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
//! Shared ISA-specific definitions. | ||
|
||
pub mod x86; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
//! Shared x86-specific definitions. | ||
|
||
mod encoding_bits; | ||
pub use encoding_bits::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.