Skip to content

x64: Migrate div instructions to the new assembler #10820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cranelift/assembler-x64/meta/src/dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub fn inst(
format,
encoding,
features: features.into(),
has_trap: false,
}
}

Expand All @@ -56,6 +57,9 @@ pub struct Inst {
/// "64-bit/32-bit Mode Support" and "CPUID Feature Flag" columns of the x64
/// reference manual.
pub features: Features,
/// Whether or not this instruction can trap and thus needs a `TrapCode`
/// payload in the instruction itself.
pub has_trap: bool,
}

impl Inst {
Expand All @@ -77,6 +81,13 @@ impl Inst {
self.format.name.to_lowercase()
)
}

/// Flags this instruction as being able to trap, so needs a `TrapCode` at
/// compile time to track this.
pub fn has_trap(mut self) -> Self {
self.has_trap = true;
self
}
}

impl core::fmt::Display for Inst {
Expand All @@ -86,11 +97,15 @@ impl core::fmt::Display for Inst {
format,
encoding,
features,
has_trap,
} = self;
write!(f, "{name}: {format} => {encoding}")?;
if !features.is_empty() {
write!(f, " [{features}]")?;
}
if *has_trap {
write!(f, " has_trap")?;
}
Ok(())
}
}
29 changes: 27 additions & 2 deletions cranelift/assembler-x64/meta/src/generate/inst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ impl dsl::Inst {
let ty = k.generate_type();
fmtln!(f, "pub {loc}: {ty},");
}

if self.has_trap {
fmtln!(f, "pub trap: TrapCode,");
}
});
}

Expand Down Expand Up @@ -69,7 +73,12 @@ impl dsl::Inst {
self.format
.operands
.iter()
.map(|o| format!("{}: impl Into<{}>", o.location, o.generate_type())),
.map(|o| format!("{}: impl Into<{}>", o.location, o.generate_type()))
.chain(if self.has_trap {
Some("trap: impl Into<TrapCode>".to_string())
} else {
None
}),
);
fmtln!(f, "#[must_use]");
f.add_block(&format!("pub fn new({params}) -> Self"), |f| {
Expand All @@ -78,6 +87,9 @@ impl dsl::Inst {
let loc = o.location;
fmtln!(f, "{loc}: {loc}.into(),");
}
if self.has_trap {
fmtln!(f, "trap: trap.into(),");
}
});
});
}
Expand Down Expand Up @@ -121,6 +133,10 @@ impl dsl::Inst {
_ => unreachable!(),
}
}
if self.has_trap {
f.comment("Emit trap.");
fmtln!(f, "buf.add_trap(self.trap);");
}

match &self.encoding {
dsl::Encoding::Rex(rex) => self.format.generate_rex_encoding(f, rex),
Expand Down Expand Up @@ -167,6 +183,7 @@ impl dsl::Inst {
// memory, not registers.
fmtln!(f, "visitor.read_amode(&mut self.{loc});");
}

}
}
});
Expand Down Expand Up @@ -208,7 +225,15 @@ impl dsl::Inst {
&self.mnemonic
};
let ordered_ops = self.format.generate_att_style_operands();
let implicit_ops = self.format.generate_implicit_operands();
let mut implicit_ops = self.format.generate_implicit_operands();
if self.has_trap {
fmtln!(f, "let trap = self.trap;");
if implicit_ops.is_empty() {
implicit_ops.push_str(" ;; {trap}");
} else {
implicit_ops.push_str(", {trap}");
}
}
fmtln!(f, "write!(f, \"{inst_name} {ordered_ops}{implicit_ops}\")");
},
);
Expand Down
2 changes: 2 additions & 0 deletions cranelift/assembler-x64/meta/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod add;
mod and;
mod bitmanip;
mod cvt;
mod div;
mod max;
mod min;
mod mul;
Expand All @@ -23,6 +24,7 @@ pub fn list() -> Vec<Inst> {
all.extend(and::list());
all.extend(bitmanip::list());
all.extend(cvt::list());
all.extend(div::list());
all.extend(max::list());
all.extend(min::list());
all.extend(mul::list());
Expand Down
16 changes: 16 additions & 0 deletions cranelift/assembler-x64/meta/src/instructions/div.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::dsl::{Feature::*, Inst, Location::*};
use crate::dsl::{fmt, implicit, inst, r, rex, rw};

#[rustfmt::skip] // Keeps instructions on a single line.
pub fn list() -> Vec<Inst> {
vec![
inst("divb", fmt("M", [rw(implicit(ax)), r(rm8)]), rex([0xF6]).digit(6), _64b | compat).has_trap(),
inst("divw", fmt("M", [rw(implicit(ax)), rw(implicit(dx)), r(rm16)]), rex([0x66, 0xF7]).digit(6), _64b | compat).has_trap(),
inst("divl", fmt("M", [rw(implicit(eax)), rw(implicit(edx)), r(rm32)]), rex([0xF7]).digit(6), _64b | compat).has_trap(),
inst("divq", fmt("M", [rw(implicit(rax)), rw(implicit(rdx)), r(rm64)]), rex([0xF7]).digit(6).w(), _64b).has_trap(),
inst("idivb", fmt("M", [rw(implicit(ax)), r(rm8)]), rex([0xF6]).digit(7), _64b | compat).has_trap(),
inst("idivw", fmt("M", [rw(implicit(ax)), rw(implicit(dx)), r(rm16)]), rex([0x66, 0xF7]).digit(7), _64b | compat).has_trap(),
inst("idivl", fmt("M", [rw(implicit(eax)), rw(implicit(edx)), r(rm32)]), rex([0xF7]).digit(7), _64b | compat).has_trap(),
inst("idivq", fmt("M", [rw(implicit(rax)), rw(implicit(rdx)), r(rm64)]), rex([0xF7]).digit(7).w(), _64b).has_trap(),
]
}
11 changes: 9 additions & 2 deletions cranelift/assembler-x64/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::gpr;
use crate::xmm;
use crate::{Amode, GprMem, XmmMem};
use std::fmt;
use std::{num::NonZeroU8, ops::Index, vec::Vec};

/// Describe how an instruction is emitted into a code buffer.
Expand Down Expand Up @@ -82,6 +83,12 @@ pub struct Constant(pub u32);
#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
pub struct TrapCode(pub NonZeroU8);

impl fmt::Display for TrapCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "trap={}", self.0)
}
}

/// A table mapping `KnownOffset` identifiers to their `i32` offset values.
///
/// When encoding instructions, Cranelift may not know all of the information
Expand All @@ -91,14 +98,14 @@ pub struct TrapCode(pub NonZeroU8);
///
/// This table allows up to do a "late" look up of these values by their
/// `KnownOffset`.
pub trait KnownOffsetTable: Index<KnownOffset, Output = i32> {}
pub trait KnownOffsetTable: Index<usize, Output = i32> {}
impl KnownOffsetTable for Vec<i32> {}
/// Provide a convenient implementation for testing.
impl KnownOffsetTable for [i32; 2] {}

/// A `KnownOffset` is a unique identifier for a specific offset known only at
/// emission time.
pub type KnownOffset = usize;
pub type KnownOffset = u8;

/// A type set fixing the register types used in the assembler.
///
Expand Down
2 changes: 1 addition & 1 deletion cranelift/assembler-x64/src/inst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! See also: [`Inst`], an `enum` containing all these instructions.

use crate::Fixed;
use crate::api::{AsReg, CodeSink, KnownOffsetTable, RegisterVisitor, Registers};
use crate::api::{AsReg, CodeSink, KnownOffsetTable, RegisterVisitor, Registers, TrapCode};
use crate::gpr::{self, Gpr, Size};
use crate::imm::{Extension, Imm8, Imm16, Imm32, Simm8, Simm32};
use crate::mem::{Amode, GprMem, XmmMem};
Expand Down
2 changes: 1 addition & 1 deletion cranelift/assembler-x64/src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl AmodeOffsetPlusKnownOffset {
#[must_use]
pub fn value(&self, offsets: &impl KnownOffsetTable) -> i32 {
let known_offset = match self.offset {
Some(offset) => offsets[offset],
Some(offset) => offsets[usize::from(offset)],
None => 0,
};
known_offset
Expand Down
19 changes: 18 additions & 1 deletion cranelift/codegen/meta/src/gen_asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
.iter()
.filter(|o| o.mutability.is_read())
.map(|o| format!("{}: {}", o.location, rust_param_raw(o)))
.chain(if inst.has_trap {
Some(format!("trap: &TrapCode"))
} else {
None
})
.collect::<Vec<_>>()
.join(", ");
f.add_block(
Expand All @@ -109,10 +114,13 @@ pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
let cvt = rust_convert_isle_to_assembler(op);
fmtln!(f, "let {loc} = {cvt};");
}
let args = operands
let mut args = operands
.iter()
.map(|o| format!("{}.clone()", o.location))
.collect::<Vec<_>>();
if inst.has_trap {
args.push(format!("{ASM}::TrapCode(trap.as_raw())"));
}
let args = args.join(", ");
f.empty_line();

Expand Down Expand Up @@ -390,6 +398,12 @@ pub fn isle_constructors(format: &Format) -> Vec<IsleConstructor> {
///
/// This function panics if the instruction has no operands.
pub fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) {
let (trap_type, trap_name) = if inst.has_trap {
(Some("TrapCode".to_string()), Some("trap".to_string()))
} else {
(None, None)
};

// First declare the "raw" constructor which is implemented in Rust
// with `generate_isle_macro` above. This is an "extern" constructor
// with relatively raw types. This is not intended to be used by
Expand All @@ -405,6 +419,7 @@ pub fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) {
let raw_param_tys = params
.iter()
.map(|o| isle_param_raw(o))
.chain(trap_type.clone())
.collect::<Vec<_>>()
.join(" ");
fmtln!(f, "(decl {raw_name} ({raw_param_tys}) AssemblerOutputs)");
Expand All @@ -424,11 +439,13 @@ pub fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) {
let param_tys = params
.iter()
.map(|o| isle_param_for_ctor(o, ctor))
.chain(trap_type.clone())
.collect::<Vec<_>>()
.join(" ");
let param_names = params
.iter()
.map(|o| o.location.to_string())
.chain(trap_name.clone())
.collect::<Vec<_>>()
.join(" ");
let convert = ctor.conversion_constructor();
Expand Down
56 changes: 9 additions & 47 deletions cranelift/codegen/src/isa/x64/inst.isle
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,6 @@
(dst WritableGpr)
(imm u8))

;; Integer quotient and remainder: (div idiv) $rax $rdx (reg addr)
;;
;; Note that this isn't used for 8-bit division which has its own `Div8`
;; instruction.
(Div (size OperandSize) ;; 2, 4, or 8
(sign DivSignedness)
(trap TrapCode)
(divisor GprMem)
(dividend_lo Gpr)
(dividend_hi Gpr)
(dst_quotient WritableGpr)
(dst_remainder WritableGpr))

;; Same as `Div`, but for 8-bits where the regalloc behavior is different
(Div8 (sign DivSignedness)
(trap TrapCode)
(divisor GprMem)
(dividend Gpr)
(dst WritableGpr))

;; Same as `Mul`, but for the BMI2 `mulx` instruction. This is different
;; where the two `dst_*` registers can be arbitrary registers and it
;; is always unsigned multiplication. Note that this instruction does
Expand Down Expand Up @@ -747,10 +727,6 @@
Size32
Size64))

(type DivSignedness
(enum Signed
Unsigned))

(type FenceKind extern
(enum MFence
LFence
Expand Down Expand Up @@ -5456,33 +5432,19 @@
(_ Unit (emit (MInst.CheckedSRemSeq8 dividend divisor dst))))
dst))

;; Helper for creating `Div8` instructions
(decl x64_div8 (Gpr GprMem DivSignedness TrapCode) Gpr)
(rule (x64_div8 dividend divisor sign trap)
(let ((dst WritableGpr (temp_writable_gpr))
(_ Unit (emit (MInst.Div8 sign trap divisor dividend dst))))
dst))

;; Helper for creating `Div` instructions
;;
;; Two registers are returned through `ValueRegs` where the first is the
;; quotient and the second is the remainder.
(decl x64_div (Gpr Gpr GprMem OperandSize DivSignedness TrapCode) ValueRegs)
(rule (x64_div dividend_lo dividend_hi divisor size sign trap)
(let ((dst_quotient WritableGpr (temp_writable_gpr))
(dst_remainder WritableGpr (temp_writable_gpr))
(_ Unit (emit (MInst.Div size sign trap divisor dividend_lo dividend_hi dst_quotient dst_remainder))))
(value_regs dst_quotient dst_remainder)))

;; Helper for `Div`, returning the quotient and discarding the remainder.
(decl x64_div_quotient (Gpr Gpr GprMem OperandSize DivSignedness TrapCode) ValueRegs)
(rule (x64_div_quotient dividend_lo dividend_hi divisor size sign trap)
(value_regs_get (x64_div dividend_lo dividend_hi divisor size sign trap) 0))

;; Helper for `Div`, returning the remainder and discarding the quotient.
(decl x64_div_remainder (Gpr Gpr GprMem OperandSize DivSignedness TrapCode) ValueRegs)
(rule (x64_div_remainder dividend_lo dividend_hi divisor size sign trap)
(value_regs_get (x64_div dividend_lo dividend_hi divisor size sign trap) 1))
(decl x64_div (Type Gpr Gpr GprMem TrapCode) ValueRegs)
(rule (x64_div $I16 lo hi divisor code) (x64_divw_m lo hi divisor code))
(rule (x64_div $I32 lo hi divisor code) (x64_divl_m lo hi divisor code))
(rule (x64_div $I64 lo hi divisor code) (x64_divq_m lo hi divisor code))

(decl x64_idiv (Type Gpr Gpr GprMem TrapCode) ValueRegs)
(rule (x64_idiv $I16 lo hi divisor code) (x64_idivw_m lo hi divisor code))
(rule (x64_idiv $I32 lo hi divisor code) (x64_idivl_m lo hi divisor code))
(rule (x64_idiv $I64 lo hi divisor code) (x64_idivq_m lo hi divisor code))

;;;; Pinned Register ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Expand Down
2 changes: 0 additions & 2 deletions cranelift/codegen/src/isa/x64/inst/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ use smallvec::{SmallVec, smallvec};
use std::fmt;
use std::string::String;

pub use crate::isa::x64::lower::isle::generated_code::DivSignedness;

/// An extension trait for converting `Writable{Xmm,Gpr}` to `Writable<Reg>`.
pub trait ToWritableReg {
/// Convert `Writable{Xmm,Gpr}` to `Writable<Reg>`.
Expand Down
Loading
Loading