use std::ops::RangeInclusive;
#[derive(Copy, Clone, PartialEq)]
pub struct EncodingBits(u16);
const OPCODE: RangeInclusive<u16> = 0..=7;
const OPCODE_PREFIX: RangeInclusive<u16> = 8..=11;
const RRR: RangeInclusive<u16> = 12..=14;
const REX_W: RangeInclusive<u16> = 15..=15;
impl From<u16> for EncodingBits {
fn from(bits: u16) -> Self {
Self(bits)
}
}
impl EncodingBits {
pub fn new(op_bytes: &[u8], rrr: u16, rex_w: u16) -> Self {
assert!(
!op_bytes.is_empty(),
"op_bytes must include at least one opcode byte"
);
let mut new = Self::from(0);
let last_byte = op_bytes[op_bytes.len() - 1];
new.write(OPCODE, last_byte as u16);
let prefix: u8 = OpcodePrefix::from_opcode(op_bytes).into();
new.write(OPCODE_PREFIX, prefix as u16);
new.write(RRR, rrr);
new.write(REX_W, rex_w);
new
}
#[inline]
pub fn with_rrr(mut self, rrr: u8) -> Self {
debug_assert_eq!(self.rrr(), 0);
self.write(RRR, rrr.into());
self
}
#[inline]
pub fn with_rex_w(mut self) -> Self {
debug_assert_eq!(self.rex_w(), 0);
self.write(REX_W, 1);
self
}
#[inline]
pub fn bits(self) -> u16 {
self.0
}
#[inline]
fn write(&mut self, range: RangeInclusive<u16>, value: u16) {
assert!(ExactSizeIterator::len(&range) > 0);
let size = range.end() - range.start() + 1;
let mask = (1 << size) - 1;
debug_assert!(
value <= mask,
"The written value should have fewer than {} bits.",
size
);
let mask_complement = !(mask << *range.start());
self.0 &= mask_complement;
let value = (value & mask) << *range.start();
self.0 |= value;
}
#[inline]
fn read(self, range: RangeInclusive<u16>) -> u8 {
assert!(ExactSizeIterator::len(&range) > 0);
let size = range.end() - range.start() + 1;
debug_assert!(size <= 8, "This structure expects ranges of at most 8 bits");
let mask = (1 << size) - 1;
((self.0 >> *range.start()) & mask) as u8
}
#[inline]
pub fn opcode_byte(self) -> u8 {
self.read(OPCODE)
}
#[inline]
pub fn prefix(self) -> OpcodePrefix {
OpcodePrefix::from(self.read(OPCODE_PREFIX))
}
#[inline]
pub fn pp(self) -> u8 {
self.prefix().to_primitive() & 0x3
}
#[inline]
pub fn mm(self) -> u8 {
(self.prefix().to_primitive() >> 2) & 0x3
}
#[inline]
pub fn rrr(self) -> u8 {
self.read(RRR)
}
#[inline]
pub fn rex_w(self) -> u8 {
self.read(REX_W)
}
}
#[allow(non_camel_case_types)]
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OpcodePrefix {
Op1,
Mp1_66,
Mp1_f3,
Mp1_f2,
Op2_0f,
Mp2_66_0f,
Mp2_f3_0f,
Mp2_f2_0f,
Op3_0f_38,
Mp3_66_0f_38,
Mp3_f3_0f_38,
Mp3_f2_0f_38,
Op3_0f_3a,
Mp3_66_0f_3a,
Mp3_f3_0f_3a,
Mp3_f2_0f_3a,
}
impl From<u8> for OpcodePrefix {
fn from(n: u8) -> Self {
use OpcodePrefix::*;
match n {
0b0000 => Op1,
0b0001 => Mp1_66,
0b0010 => Mp1_f3,
0b0011 => Mp1_f2,
0b0100 => Op2_0f,
0b0101 => Mp2_66_0f,
0b0110 => Mp2_f3_0f,
0b0111 => Mp2_f2_0f,
0b1000 => Op3_0f_38,
0b1001 => Mp3_66_0f_38,
0b1010 => Mp3_f3_0f_38,
0b1011 => Mp3_f2_0f_38,
0b1100 => Op3_0f_3a,
0b1101 => Mp3_66_0f_3a,
0b1110 => Mp3_f3_0f_3a,
0b1111 => Mp3_f2_0f_3a,
_ => panic!("invalid opcode prefix"),
}
}
}
impl Into<u8> for OpcodePrefix {
fn into(self) -> u8 {
use OpcodePrefix::*;
match self {
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 OpcodePrefix {
fn to_primitive(self) -> u8 {
self.into()
}
pub fn from_opcode(op_bytes: &[u8]) -> Self {
assert!(!op_bytes.is_empty(), "at least one opcode byte");
let prefix_bytes = &op_bytes[..op_bytes.len() - 1];
match prefix_bytes {
[] => Self::Op1,
[0x66] => Self::Mp1_66,
[0xf3] => Self::Mp1_f3,
[0xf2] => Self::Mp1_f2,
[0x0f] => Self::Op2_0f,
[0x66, 0x0f] => Self::Mp2_66_0f,
[0xf3, 0x0f] => Self::Mp2_f3_0f,
[0xf2, 0x0f] => Self::Mp2_f2_0f,
[0x0f, 0x38] => Self::Op3_0f_38,
[0x66, 0x0f, 0x38] => Self::Mp3_66_0f_38,
[0xf3, 0x0f, 0x38] => Self::Mp3_f3_0f_38,
[0xf2, 0x0f, 0x38] => Self::Mp3_f2_0f_38,
[0x0f, 0x3a] => Self::Op3_0f_3a,
[0x66, 0x0f, 0x3a] => Self::Mp3_66_0f_3a,
[0xf3, 0x0f, 0x3a] => Self::Mp3_f3_0f_3a,
[0xf2, 0x0f, 0x3a] => Self::Mp3_f2_0f_3a,
_ => {
panic!("unexpected opcode sequence: {:?}", op_bytes);
}
}
}
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::*;
fn test_roundtrip(p: OpcodePrefix) {
assert_eq!(p, OpcodePrefix::from(p.to_primitive()));
}
#[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);
}
#[test]
fn prefix_to_name() {
assert_eq!(OpcodePrefix::Op1.recipe_name_prefix(), "Op1");
assert_eq!(OpcodePrefix::Op2_0f.recipe_name_prefix(), "Op2");
assert_eq!(OpcodePrefix::Op3_0f_38.recipe_name_prefix(), "Op3");
assert_eq!(OpcodePrefix::Mp1_66.recipe_name_prefix(), "Mp1");
assert_eq!(OpcodePrefix::Mp2_66_0f.recipe_name_prefix(), "Mp2");
assert_eq!(OpcodePrefix::Mp3_66_0f_3a.recipe_name_prefix(), "Mp3");
}
#[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!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x0);
let enc = EncodingBits::from(0x00cd);
assert_eq!(enc.opcode_byte(), 0xcd);
}
#[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!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x0);
}
#[test]
fn encodingbits_pp() {
let enc = EncodingBits::from(0x0300);
assert_eq!(enc.opcode_byte(), 0x0);
assert_eq!(enc.pp(), 0x3);
assert_eq!(enc.mm(), 0x0);
assert_eq!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x0);
}
#[test]
fn encodingbits_mm() {
let enc = EncodingBits::from(0x0c00);
assert_eq!(enc.opcode_byte(), 0x0);
assert_eq!(enc.pp(), 0x00);
assert_eq!(enc.mm(), 0x3);
assert_eq!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x0);
}
#[test]
fn encodingbits_rrr() {
let enc = EncodingBits::from(0x5000);
assert_eq!(enc.opcode_byte(), 0x0);
assert_eq!(enc.prefix().to_primitive(), 0x0);
assert_eq!(enc.rrr(), 0x5);
assert_eq!(enc.rex_w(), 0x0);
}
#[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!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x1);
}
#[test]
fn encodingbits_flip() {
let mut bits = EncodingBits::from(0);
let range = 2..=2;
bits.write(range.clone(), 1);
assert_eq!(bits.bits(), 0b100);
bits.write(range, 0);
assert_eq!(bits.bits(), 0b000);
}
#[test]
fn encodingbits_roundtrip() {
let bits: u16 = 0x1234;
assert_eq!(EncodingBits::from(bits).bits(), bits);
}
#[test]
#[allow(clippy::inconsistent_digit_grouping)]
fn encodingbits_construction() {
assert_eq!(
EncodingBits::new(&[0x66, 0x40], 5, 1).bits(),
0b1_101_0001_01000000
);
}
#[test]
#[should_panic]
fn encodingbits_panics_at_write_to_invalid_range() {
EncodingBits::from(0).write(1..=0, 42);
}
#[test]
#[should_panic]
fn encodingbits_panics_at_read_to_invalid_range() {
EncodingBits::from(0).read(1..=0);
}
}