Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcrichton committed Feb 14, 2025
1 parent 9f4a9e1 commit 6556587
Show file tree
Hide file tree
Showing 13 changed files with 548 additions and 34 deletions.
30 changes: 26 additions & 4 deletions cranelift/assembler-x64/meta/src/dsl/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub fn rex(opcode: u8) -> Rex {
w: false,
r: false,
digit: 0,
escape: None,
imm: Imm::None,
}
}
Expand Down Expand Up @@ -65,6 +66,7 @@ impl fmt::Display for Encoding {
/// but we use it here to distinguish this from other encoding formats (e.g.,
/// VEX, EVEX). The "REX" _byte_ is still optional in this encoding and only
/// emitted when necessary.
#[derive(Copy, Clone)]
pub struct Rex {
/// Any legacy prefixes that should be included with the instruction.
pub prefix: LegacyPrefix,
Expand Down Expand Up @@ -102,6 +104,10 @@ pub struct Rex {
/// the operand is a signed value. All words, doublewords, and quadwords are
/// given with the low-order byte first."
pub imm: Imm,
/// An optional "escape" byte discussed in section 2.1.2 of the Intel
/// manual which is used for some instructions as a prefix to the actual
/// opcode byte.
pub escape: Option<u8>,
}

impl Rex {
Expand Down Expand Up @@ -184,6 +190,12 @@ impl Rex {
Self { imm: Imm::io, ..self }
}

/// Set the escape prefix byte for the instruction.
#[must_use]
pub fn escape(self, byte: u8) -> Self {
Self { escape: Some(byte), ..self }
}

/// Check a subset of the rules for valid encodings outlined in chapter 2,
/// _Instruction Format_, of the Intel® 64 and IA-32 Architectures Software
/// Developer’s Manual, Volume 2A.
Expand All @@ -198,7 +210,10 @@ impl Rex {

if self.prefix.contains_66() {
assert!(
operands.iter().all(|&op| op.location.bits() == 16),
operands
.iter()
.all(|&op| matches!(op.location.kind(), OperandKind::Imm(_) | OperandKind::FixedReg(_))
|| op.location.bits() == 16),
"when we encode the 66 prefix, we expect all operands to be 16-bit wide"
);
}
Expand All @@ -215,6 +230,11 @@ impl Rex {
);
}
}

/// Returns the sequence of bytes that makes up this opcode.
pub fn opcode_bytes(&self) -> impl Iterator<Item = u8> {
self.escape.into_iter().chain([self.opcode])
}
}

impl From<Rex> for Encoding {
Expand All @@ -237,7 +257,9 @@ impl fmt::Display for Rex {
if self.w {
write!(f, "REX.W + ")?;
}
write!(f, "{:#04x}", self.opcode)?;
for byte in self.opcode_bytes() {
write!(f, "{byte:#04x}")?;
}
if self.r {
write!(f, " /r")?;
}
Expand All @@ -251,7 +273,7 @@ impl fmt::Display for Rex {
}
}

#[derive(PartialEq)]
#[derive(Copy, Clone, PartialEq)]
pub enum LegacyPrefix {
/// No prefix bytes.
NoPrefix,
Expand Down Expand Up @@ -279,7 +301,7 @@ impl LegacyPrefix {
}
}

#[derive(Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(non_camel_case_types)]
pub enum Imm {
None,
Expand Down
12 changes: 8 additions & 4 deletions cranelift/assembler-x64/meta/src/dsl/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ pub enum Location {
eax,
rax,

cl,

imm8,
imm16,
imm32,
Expand All @@ -222,7 +224,7 @@ impl Location {
pub fn bits(&self) -> u8 {
use Location::*;
match self {
al | imm8 | r8 | rm8 => 8,
al | cl | imm8 | r8 | rm8 => 8,
ax | imm16 | r16 | rm16 => 16,
eax | imm32 | r32 | rm32 => 32,
rax | r64 | rm64 => 64,
Expand All @@ -240,7 +242,7 @@ impl Location {
pub fn uses_memory(&self) -> bool {
use Location::*;
match self {
al | ax | eax | rax | imm8 | imm16 | imm32 | r8 | r16 | r32 | r64 => false,
al | cl | ax | eax | rax | imm8 | imm16 | imm32 | r8 | r16 | r32 | r64 => false,
rm8 | rm16 | rm32 | rm64 => true,
}
}
Expand All @@ -251,7 +253,7 @@ impl Location {
pub fn uses_variable_register(&self) -> bool {
use Location::*;
match self {
al | ax | eax | rax | imm8 | imm16 | imm32 => false,
al | ax | eax | rax | cl | imm8 | imm16 | imm32 => false,
r8 | r16 | r32 | r64 | rm8 | rm16 | rm32 | rm64 => true,
}
}
Expand All @@ -261,7 +263,7 @@ impl Location {
pub fn kind(&self) -> OperandKind {
use Location::*;
match self {
al | ax | eax | rax => OperandKind::FixedReg(*self),
al | ax | eax | rax | cl => OperandKind::FixedReg(*self),
imm8 | imm16 | imm32 => OperandKind::Imm(*self),
r8 | r16 | r32 | r64 => OperandKind::Reg(*self),
rm8 | rm16 | rm32 | rm64 => OperandKind::RegMem(*self),
Expand All @@ -278,6 +280,8 @@ impl core::fmt::Display for Location {
eax => write!(f, "eax"),
rax => write!(f, "rax"),

cl => write!(f, "cl"),

imm8 => write!(f, "imm8"),
imm16 => write!(f, "imm16"),
imm32 => write!(f, "imm32"),
Expand Down
25 changes: 15 additions & 10 deletions cranelift/assembler-x64/meta/src/generate/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@ impl dsl::Format {
/// (TODO).
#[must_use]
pub fn generate_att_style_operands(&self) -> String {
let mut ordered_ops: Vec<_> = self
let ordered_ops: Vec<_> = self
.operands
.iter()
.rev()
.map(|o| format!("{{{}}}", o.location))
.collect();
if ordered_ops.len() > 1 {
let first = ordered_ops.remove(0);
ordered_ops.push(first);
}
ordered_ops.join(", ")
}

Expand Down Expand Up @@ -64,7 +61,9 @@ impl dsl::Format {
fn generate_opcode(&self, f: &mut Formatter, rex: &dsl::Rex) {
f.empty_line();
f.comment("Emit opcode.");
fmtln!(f, "buf.put1(0x{:x});", rex.opcode);
for byte in rex.opcode_bytes() {
fmtln!(f, "buf.put1(0x{byte:x});");
}
}

fn generate_rex_prefix(&self, f: &mut Formatter, rex: &dsl::Rex) {
Expand Down Expand Up @@ -111,7 +110,9 @@ impl dsl::Format {
});
fmtln!(f, "}}");
}
[RegMem(dst), Reg(src)] => {
[RegMem(dst), Reg(src)]
| [RegMem(dst), Reg(src), Imm(_)]
| [RegMem(dst), Reg(src), FixedReg(_)] => {
fmtln!(f, "let {src} = self.{src}.enc();");
fmtln!(f, "match &self.{dst} {{");
f.indent(|f| {
Expand Down Expand Up @@ -158,7 +159,9 @@ impl dsl::Format {
});
fmtln!(f, "}}");
}
[RegMem(dst), Reg(src)] => {
[RegMem(dst), Reg(src)]
| [RegMem(dst), Reg(src), Imm(_)]
| [RegMem(dst), Reg(src), FixedReg(_)] => {
fmtln!(f, "let {src} = self.{src}.enc();");
fmtln!(f, "match &self.{dst} {{");
f.indent(|f| {
Expand All @@ -175,7 +178,9 @@ impl dsl::Format {
fn generate_immediate(&self, f: &mut Formatter) {
use dsl::OperandKind::Imm;
match self.operands_by_kind().as_slice() {
[_, Imm(imm)] => {
[prefix @ .., Imm(imm)] => {
assert!(!prefix.iter().any(|o| matches!(o, Imm(_))));

f.empty_line();
f.comment("Emit immediate.");
fmtln!(f, "let bytes = {};", imm.bytes());
Expand All @@ -188,7 +193,7 @@ impl dsl::Format {
}
unknown => {
// Do nothing: no immediates expected.
debug_assert!(!unknown.iter().any(|o| matches!(o, Imm(_))));
assert!(!unknown.iter().any(|o| matches!(o, Imm(_))));
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions cranelift/assembler-x64/meta/src/generate/operand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl dsl::Location {
None => String::new(),
};
match self {
al | ax | eax | rax => None,
al | ax | eax | rax | cl => None,
imm8 => Some("Imm8".into()),
imm16 => Some("Imm16".into()),
imm32 => Some("Imm32".into()),
Expand All @@ -57,6 +57,7 @@ impl dsl::Location {
ax => "\"%ax\"".into(),
eax => "\"%eax\"".into(),
rax => "\"%rax\"".into(),
cl => "\"%cl\"".into(),
imm8 | imm16 | imm32 => {
let variant = extension.generate_variant();
format!("self.{self}.to_string({variant})")
Expand All @@ -73,7 +74,7 @@ impl dsl::Location {
pub fn generate_size(&self) -> Option<&str> {
use dsl::Location::*;
match self {
al | ax | eax | rax | imm8 | imm16 | imm32 => None,
al | ax | eax | rax | cl | imm8 | imm16 | imm32 => None,
r8 | rm8 => Some("Size::Byte"),
r16 | rm16 => Some("Size::Word"),
r32 | rm32 => Some("Size::Doubleword"),
Expand All @@ -87,6 +88,7 @@ impl dsl::Location {
use dsl::Location::*;
match self {
al | ax | eax | rax => Some("reg::enc::RAX"),
cl => Some("reg::enc::RCX"),
imm8 | imm16 | imm32 | r8 | r16 | r32 | r64 | rm8 | rm16 | rm32 | rm64 => None,
}
}
Expand Down
6 changes: 5 additions & 1 deletion cranelift/assembler-x64/meta/src/instructions.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
//! Defines x64 instructions using the DSL.
mod and;
mod shld;

use crate::dsl::Inst;

#[must_use]
pub fn list() -> Vec<Inst> {
and::list()
let mut ret = Vec::new();
ret.extend(and::list());
ret.extend(shld::list());
ret
}
15 changes: 15 additions & 0 deletions cranelift/assembler-x64/meta/src/instructions/shld.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::dsl::{fmt, inst, r, rex, rw};
use crate::dsl::{Feature::*, Inst, LegacyPrefix::*, Location::*};

pub fn list() -> Vec<Inst> {
let mri = rex(0xA4).escape(0x0F).ib();
let mrc = rex(0xA5).escape(0x0F).ib();
vec![
inst("shldw", fmt("MRI", [rw(rm16), r(r16), r(imm8)]), mri.prefix(_66), _64b | compat),
inst("shldw", fmt("MRC", [rw(rm16), r(r16), r(cl)]), mrc.prefix(_66), _64b | compat),
inst("shldl", fmt("MRI", [rw(rm32), r(r32), r(imm8)]), mri, _64b | compat),
inst("shldq", fmt("MRI", [rw(rm64), r(r64), r(imm8)]), mri.w(), _64b),
inst("shldl", fmt("MRC", [rw(rm32), r(r32), r(cl)]), mrc, _64b | compat),
inst("shldq", fmt("MRC", [rw(rm64), r(r64), r(cl)]), mrc.w(), _64b),
]
}
14 changes: 12 additions & 2 deletions cranelift/assembler-x64/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub fn roundtrip(inst: &Inst<FuzzRegs>) {
println!("> {inst}");
println!(" debug: {inst:x?}");
println!(" assembled: {}", pretty_print_hexadecimal(&assembled));
println!(" expected (capstone): {expected}");
println!(" actual (to_string): {actual}");
assert_eq!(expected, &actual);
}
}
Expand Down Expand Up @@ -55,9 +57,17 @@ fn disassemble(assembled: &[u8]) -> String {
let insns = cs
.disasm_all(assembled, 0x0)
.expect("failed to disassemble");
assert_eq!(insns.len(), 1, "not a single instruction: {assembled:x?}");
assert_eq!(insns.len(), 1, "not a single instruction: {assembled:02x?}");
let insn = insns.first().expect("at least one instruction");
assert_eq!(assembled.len(), insn.len());
assert_eq!(
assembled.len(),
insn.len(),
"\ncranelift generated {} bytes: {assembled:02x?}\n\
capstone generated {} bytes: {:02x?}",
assembled.len(),
insn.len(),
insn.bytes(),
);
insn.to_string()
}

Expand Down
Loading

0 comments on commit 6556587

Please sign in to comment.