Path: blob/main/cranelift/assembler-x64/meta/src/dsl/format.rs
1693 views
//! A DSL for describing x64 instruction formats--the shape of the operands.1//!2//! Every instruction has a format that corresponds to its encoding's expected3//! operands. The format is what allows us to generate code that accepts4//! operands of the right type and check that the operands are used in the right5//! way.6//!7//! The entry point for this module is [`fmt`].8//!9//! ```10//! # use cranelift_assembler_x64_meta::dsl::{fmt, rw, r, Location::*};11//! let f = fmt("rm", [rw(r32), r(rm32)]);12//! assert_eq!(f.to_string(), "rm(r32[rw], rm32)")13//! ```1415/// An abbreviated constructor for an instruction "format."16///17/// These model what the reference manual calls "instruction operand encodings,"18/// usually defined in a table after an instruction's opcodes.19pub fn fmt(name: impl Into<String>, operands: impl IntoIterator<Item = Operand>) -> Format {20Format {21name: name.into(),22operands: operands.into_iter().collect(),23eflags: Eflags::default(),24}25}2627/// An abbreviated constructor for a "read-write" operand.28///29/// # Panics30///31/// This function panics if the location is an immediate (i.e., an immediate32/// cannot be written to).33#[must_use]34pub fn rw(op: impl Into<Operand>) -> Operand {35let op = op.into();36assert!(!matches!(op.location.kind(), OperandKind::Imm(_)));37Operand {38mutability: Mutability::ReadWrite,39..op40}41}4243/// An abbreviated constructor for a "read" operand.44#[must_use]45pub fn r(op: impl Into<Operand>) -> Operand {46let op = op.into();47assert!(op.mutability.is_read());48op49}5051/// An abbreviated constructor for a "write" operand.52#[must_use]53pub fn w(op: impl Into<Operand>) -> Operand {54let op = op.into();55Operand {56mutability: Mutability::Write,57..op58}59}6061/// An abbreviated constructor for a memory operand that requires alignment.62pub fn align(location: Location) -> Operand {63assert!(location.uses_memory());64Operand {65align: true,66..Operand::from(location)67}68}6970/// An abbreviated constructor for an operand that is used by the instruction71/// but not visible in its disassembly.72pub fn implicit(location: Location) -> Operand {73assert!(matches!(location.kind(), OperandKind::FixedReg(_)));74Operand {75implicit: true,76..Operand::from(location)77}78}7980/// An abbreviated constructor for a "read" operand that is sign-extended to 6481/// bits (quadword).82///83/// # Panics84///85/// This function panics if the location size is too large to extend.86#[must_use]87pub fn sxq(location: Location) -> Operand {88assert!(location.bits() <= 64);89Operand {90extension: Extension::SignExtendQuad,91..Operand::from(location)92}93}9495/// An abbreviated constructor for a "read" operand that is sign-extended to 3296/// bits (longword).97///98/// # Panics99///100/// This function panics if the location size is too large to extend.101#[must_use]102pub fn sxl(location: Location) -> Operand {103assert!(location.bits() <= 32);104Operand {105extension: Extension::SignExtendLong,106..Operand::from(location)107}108}109110/// An abbreviated constructor for a "read" operand that is sign-extended to 16111/// bits (word).112///113/// # Panics114///115/// This function panics if the location size is too large to extend.116#[must_use]117pub fn sxw(location: Location) -> Operand {118assert!(location.bits() <= 16);119Operand {120extension: Extension::SignExtendWord,121..Operand::from(location)122}123}124125/// A format describes the operands for an instruction.126#[derive(Clone)]127pub struct Format {128/// This name, when combined with the instruction mnemonic, uniquely129/// identifies an instruction. The reference manual uses this name in the130/// "Instruction Operand Encoding" table.131pub name: String,132/// These operands should match the "Instruction" column in the reference133/// manual.134pub operands: Vec<Operand>,135/// This should match eflags description of an instruction.136pub eflags: Eflags,137}138139impl Format {140/// Iterate over the operand locations.141pub fn locations(&self) -> impl Iterator<Item = &Location> + '_ {142self.operands.iter().map(|o| &o.location)143}144145/// Return the location of the operand that uses memory, if any; return146/// `None` otherwise.147pub fn uses_memory(&self) -> Option<Location> {148debug_assert!(149self.locations()150.copied()151.filter(Location::uses_memory)152.count()153<= 1154);155self.locations().copied().find(Location::uses_memory)156}157158/// Return `true` if any of the operands accepts a register (i.e., not an159/// immediate); return `false` otherwise.160#[must_use]161pub fn uses_register(&self) -> bool {162self.locations().any(Location::uses_register)163}164165/// Collect into operand kinds.166pub fn operands_by_kind(&self) -> Vec<OperandKind> {167self.locations().map(Location::kind).collect()168}169170/// Set the EFLAGS mutability for this instruction.171pub fn flags(mut self, eflags: Eflags) -> Self {172self.eflags = eflags;173self174}175176/// Return true if an instruction uses EFLAGS.177pub fn uses_eflags(&self) -> bool {178self.eflags != Eflags::None179}180}181182impl core::fmt::Display for Format {183fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {184let Format {185name,186operands,187eflags,188} = self;189let operands = operands190.iter()191.map(|operand| format!("{operand}"))192.collect::<Vec<_>>()193.join(", ");194write!(f, "{name}({operands})")?;195196if *eflags != Eflags::None {197write!(f, "[flags:{eflags}]")?;198}199200Ok(())201}202}203204/// An x64 operand.205///206/// This is designed to look and feel like the operands as expressed in Intel's207/// _Instruction Set Reference_.208///209/// ```210/// # use cranelift_assembler_x64_meta::dsl::{align, r, rw, sxq, Location::*};211/// assert_eq!(r(r8).to_string(), "r8");212/// assert_eq!(rw(rm16).to_string(), "rm16[rw]");213/// assert_eq!(sxq(imm32).to_string(), "imm32[sxq]");214/// assert_eq!(align(xmm_m128).to_string(), "xmm_m128[align]");215/// ```216#[derive(Clone, Copy, Debug, PartialEq)]217pub struct Operand {218/// The location of the data: memory, register, immediate.219pub location: Location,220/// An operand can be read-only or read-write.221pub mutability: Mutability,222/// Some operands are sign- or zero-extended.223pub extension: Extension,224/// Some memory operands require alignment; `true` indicates that the memory225/// address used in the operand must align to the size of the operand (e.g.,226/// `m128` must be 16-byte aligned).227pub align: bool,228/// Some register operands are implicit: that is, they do not appear in the229/// disassembled output even though they are used in the instruction.230pub implicit: bool,231}232233impl core::fmt::Display for Operand {234fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {235let Self {236location,237mutability,238extension,239align,240implicit,241} = self;242write!(f, "{location}")?;243let mut flags = vec![];244if !matches!(mutability, Mutability::Read) {245flags.push(format!("{mutability}"));246}247if !matches!(extension, Extension::None) {248flags.push(format!("{extension}"));249}250if *align != false {251flags.push("align".to_owned());252}253if *implicit {254flags.push("implicit".to_owned());255}256if !flags.is_empty() {257write!(f, "[{}]", flags.join(","))?;258}259Ok(())260}261}262263impl From<Location> for Operand {264fn from(location: Location) -> Self {265let mutability = Mutability::default();266let extension = Extension::default();267let align = false;268let implicit = false;269Self {270location,271mutability,272extension,273align,274implicit,275}276}277}278279/// The kind of register used in a [`Location`].280pub enum RegClass {281Gpr,282Xmm,283}284285impl core::fmt::Display for RegClass {286fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {287match self {288RegClass::Gpr => write!(f, "Gpr"),289RegClass::Xmm => write!(f, "Xmm"),290}291}292}293294/// An operand location, as expressed in Intel's _Instruction Set Reference_.295#[derive(Clone, Copy, Debug, PartialEq)]296#[allow(non_camel_case_types, reason = "makes DSL definitions easier to read")]297pub enum Location {298// Fixed registers.299al,300ax,301eax,302rax,303rbx,304dx,305edx,306rdx,307cl,308rcx,309xmm0,310311// Immediate values.312imm8,313imm16,314imm32,315imm64,316317// General-purpose registers, and their memory forms.318r8,319r16,320r32,321r32a,322r32b,323r64,324r64a,325r64b,326rm8,327rm16,328rm32,329rm64,330331// XMM registers, and their memory forms.332xmm1,333xmm2,334xmm3,335xmm_m8,336xmm_m16,337xmm_m32,338xmm_m64,339xmm_m128,340341// Memory-only locations.342m8,343m16,344m32,345m64,346m128,347}348349impl Location {350/// Return the number of bits accessed.351#[must_use]352pub fn bits(&self) -> u16 {353use Location::*;354match self {355al | cl | imm8 | r8 | rm8 | m8 | xmm_m8 => 8,356ax | dx | imm16 | r16 | rm16 | m16 | xmm_m16 => 16,357eax | edx | imm32 | r32 | r32a | r32b | rm32 | m32 | xmm_m32 => 32,358rax | rbx | rcx | rdx | imm64 | r64 | r64a | r64b | rm64 | m64 | xmm_m64 => 64,359xmm1 | xmm2 | xmm3 | xmm_m128 | xmm0 | m128 => 128,360}361}362363/// Return the number of bytes accessed, for convenience.364#[must_use]365pub fn bytes(&self) -> u16 {366self.bits() / 8367}368369/// Return `true` if the location accesses memory; `false` otherwise.370#[must_use]371pub fn uses_memory(&self) -> bool {372use OperandKind::*;373match self.kind() {374FixedReg(_) | Imm(_) | Reg(_) => false,375RegMem(_) | Mem(_) => true,376}377}378379/// Return `true` if any of the operands accepts a register (i.e., not an380/// immediate); return `false` otherwise.381#[must_use]382pub fn uses_register(&self) -> bool {383use OperandKind::*;384match self.kind() {385Imm(_) => false,386FixedReg(_) | Reg(_) | RegMem(_) | Mem(_) => true,387}388}389390/// Convert the location to an [`OperandKind`].391#[must_use]392pub fn kind(&self) -> OperandKind {393use Location::*;394match self {395al | ax | eax | rax | rbx | cl | rcx | dx | edx | rdx | xmm0 => {396OperandKind::FixedReg(*self)397}398imm8 | imm16 | imm32 | imm64 => OperandKind::Imm(*self),399r8 | r16 | r32 | r32a | r32b | r64 | r64a | r64b | xmm1 | xmm2 | xmm3 => {400OperandKind::Reg(*self)401}402rm8 | rm16 | rm32 | rm64 | xmm_m8 | xmm_m16 | xmm_m32 | xmm_m64 | xmm_m128 => {403OperandKind::RegMem(*self)404}405m8 | m16 | m32 | m64 | m128 => OperandKind::Mem(*self),406}407}408409/// If a location directly uses data from a register, return the register410/// class; otherwise, return `None`. Memory-only locations, though their411/// address is stored in a register, use data from memory and thus also412/// return `None`.413#[must_use]414pub fn reg_class(&self) -> Option<RegClass> {415use Location::*;416match self {417imm8 | imm16 | imm32 | imm64 | m8 | m16 | m32 | m64 | m128 => None,418al | ax | eax | rax | rbx | cl | rcx | dx | edx | rdx | r8 | r16 | r32 | r32a419| r32b | r64 | r64a | r64b | rm8 | rm16 | rm32 | rm64 => Some(RegClass::Gpr),420xmm1 | xmm2 | xmm3 | xmm_m8 | xmm_m16 | xmm_m32 | xmm_m64 | xmm_m128 | xmm0 => {421Some(RegClass::Xmm)422}423}424}425}426427impl core::fmt::Display for Location {428fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {429use Location::*;430match self {431imm8 => write!(f, "imm8"),432imm16 => write!(f, "imm16"),433imm32 => write!(f, "imm32"),434imm64 => write!(f, "imm64"),435436al => write!(f, "al"),437ax => write!(f, "ax"),438eax => write!(f, "eax"),439rax => write!(f, "rax"),440rbx => write!(f, "rbx"),441cl => write!(f, "cl"),442rcx => write!(f, "rcx"),443dx => write!(f, "dx"),444edx => write!(f, "edx"),445rdx => write!(f, "rdx"),446xmm0 => write!(f, "xmm0"),447448r8 => write!(f, "r8"),449r16 => write!(f, "r16"),450r32 => write!(f, "r32"),451r32a => write!(f, "r32a"),452r32b => write!(f, "r32b"),453r64 => write!(f, "r64"),454r64a => write!(f, "r64a"),455r64b => write!(f, "r64b"),456rm8 => write!(f, "rm8"),457rm16 => write!(f, "rm16"),458rm32 => write!(f, "rm32"),459rm64 => write!(f, "rm64"),460461xmm1 => write!(f, "xmm1"),462xmm2 => write!(f, "xmm2"),463xmm3 => write!(f, "xmm3"),464xmm_m8 => write!(f, "xmm_m8"),465xmm_m16 => write!(f, "xmm_m16"),466xmm_m32 => write!(f, "xmm_m32"),467xmm_m64 => write!(f, "xmm_m64"),468xmm_m128 => write!(f, "xmm_m128"),469470m8 => write!(f, "m8"),471m16 => write!(f, "m16"),472m32 => write!(f, "m32"),473m64 => write!(f, "m64"),474m128 => write!(f, "m128"),475}476}477}478479/// Organize the operand locations by kind.480///481/// ```482/// # use cranelift_assembler_x64_meta::dsl::{OperandKind, Location};483/// let k: OperandKind = Location::imm32.kind();484/// ```485#[derive(Clone, Copy, Debug)]486pub enum OperandKind {487FixedReg(Location),488Imm(Location),489Reg(Location),490RegMem(Location),491Mem(Location),492}493494/// x64 operands can be mutable or not.495///496/// ```497/// # use cranelift_assembler_x64_meta::dsl::{r, rw, Location::r8, Mutability};498/// assert_eq!(r(r8).mutability, Mutability::Read);499/// assert_eq!(rw(r8).mutability, Mutability::ReadWrite);500/// ```501#[derive(Clone, Copy, Debug, PartialEq)]502pub enum Mutability {503Read,504ReadWrite,505Write,506}507508impl Mutability {509/// Returns whether this represents a read of the operand in question.510///511/// Note that for read/write operands this returns `true`.512pub fn is_read(&self) -> bool {513match self {514Mutability::Read | Mutability::ReadWrite => true,515Mutability::Write => false,516}517}518519/// Returns whether this represents a write of the operand in question.520///521/// Note that for read/write operands this returns `true`.522pub fn is_write(&self) -> bool {523match self {524Mutability::Read => false,525Mutability::ReadWrite | Mutability::Write => true,526}527}528}529530impl Default for Mutability {531fn default() -> Self {532Self::Read533}534}535536impl core::fmt::Display for Mutability {537fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {538match self {539Self::Read => write!(f, "r"),540Self::ReadWrite => write!(f, "rw"),541Self::Write => write!(f, "w"),542}543}544}545546/// x64 operands may be sign- or zero-extended.547///548/// ```549/// # use cranelift_assembler_x64_meta::dsl::{Location::r8, sxw, Extension};550/// assert_eq!(sxw(r8).extension, Extension::SignExtendWord);551/// ```552#[derive(Clone, Copy, Debug, PartialEq)]553pub enum Extension {554None,555SignExtendQuad,556SignExtendLong,557SignExtendWord,558}559560impl Extension {561/// Check if the extension is sign-extended.562#[must_use]563pub fn is_sign_extended(&self) -> bool {564matches!(565self,566Self::SignExtendQuad | Self::SignExtendLong | Self::SignExtendWord567)568}569}570571impl Default for Extension {572fn default() -> Self {573Self::None574}575}576577impl core::fmt::Display for Extension {578fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {579match self {580Extension::None => write!(f, ""),581Extension::SignExtendQuad => write!(f, "sxq"),582Extension::SignExtendLong => write!(f, "sxl"),583Extension::SignExtendWord => write!(f, "sxw"),584}585}586}587588/// Describes if an instruction uses EFLAGS, and whether it reads, writes, or589/// reads/writes the EFLAGS register.590/// In the future, we might want to model specific EFLAGS bits instead of the591/// entire EFLAGS register.592/// Some related discussion in this GitHub issue593/// https://github.com/bytecodealliance/wasmtime/issues/10298594#[derive(Clone, Copy, Debug, PartialEq)]595pub enum Eflags {596None,597R,598W,599RW,600}601602impl Eflags {603/// Returns whether this represents a read of any bit in the EFLAGS604/// register.605pub fn is_read(&self) -> bool {606match self {607Eflags::None | Eflags::W => false,608Eflags::R | Eflags::RW => true,609}610}611612/// Returns whether this represents a writes to any bit in the EFLAGS613/// register.614pub fn is_write(&self) -> bool {615match self {616Eflags::None | Eflags::R => false,617Eflags::W | Eflags::RW => true,618}619}620}621622impl Default for Eflags {623fn default() -> Self {624Self::None625}626}627628impl core::fmt::Display for Eflags {629fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {630match self {631Self::None => write!(f, ""),632Self::R => write!(f, "r"),633Self::W => write!(f, "w"),634Self::RW => write!(f, "rw"),635}636}637}638639640