use crate::{Bytes, Cycles};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Width {
W8,
W16,
W32,
W64,
}
impl Width {
pub const ALL: [Self; 4] = [Self::W8, Self::W16, Self::W32, Self::W64];
#[must_use]
pub const fn index(self) -> usize {
self as usize
}
#[must_use]
pub const fn bits(self) -> u32 {
match self {
Self::W8 => 8,
Self::W16 => 16,
Self::W32 => 32,
Self::W64 => 64,
}
}
#[must_use]
pub const fn from_bits(bits: u32) -> Option<Self> {
match bits {
8 => Some(Self::W8),
16 => Some(Self::W16),
32 => Some(Self::W32),
64 => Some(Self::W64),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AddrMode {
Base,
BaseDisp,
BaseIndex,
BaseIndexScale,
BaseIndexScaleDisp,
}
impl AddrMode {
pub const ALL: [Self; 5] = [
Self::Base,
Self::BaseDisp,
Self::BaseIndex,
Self::BaseIndexScale,
Self::BaseIndexScaleDisp,
];
#[must_use]
pub const fn index(self) -> usize {
self as usize
}
#[must_use]
pub const fn complexity(self) -> u32 {
match self {
Self::Base => 0,
Self::BaseDisp | Self::BaseIndex => 1,
Self::BaseIndexScale => 2,
Self::BaseIndexScaleDisp => 3,
}
}
#[must_use]
pub const fn scales(self) -> bool {
matches!(self, Self::BaseIndexScale | Self::BaseIndexScaleDisp)
}
}
pub trait Capability {
fn impossible(&self) -> Vec<bool>;
}
impl Capability for Cycles {
fn impossible(&self) -> Vec<bool> {
vec![self.is_infinite()]
}
}
impl<const N: usize> Capability for [Cycles; N] {
fn impossible(&self) -> Vec<bool> {
self.iter().map(|c| c.is_infinite()).collect()
}
}
impl Capability for u32 {
fn impossible(&self) -> Vec<bool> {
Vec::new()
}
}
impl Capability for Bytes {
fn impossible(&self) -> Vec<bool> {
Vec::new()
}
}
macro_rules! cost_table {
($( $(#[$meta:meta])* $name:ident : $ty:ty ),+ $(,)?) => {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CostTable {
$( $(#[$meta])* pub $name: $ty, )+
}
impl CostTable {
pub const FIELDS: &'static [&'static str] = &[ $( stringify!($name) ),+ ];
#[must_use]
pub fn capabilities(&self) -> Vec<(&'static str, Vec<bool>)> {
vec![ $( (stringify!($name), Capability::impossible(&self.$name)) ),+ ]
}
}
#[derive(Debug, Clone, Default)]
pub struct Builder {
$( $name: Option<$ty>, )+
}
impl Builder {
#[must_use]
pub fn new() -> Self {
Self { $( $name: None, )+ }
}
$(
$(#[$meta])*
#[allow(clippy::should_implement_trait)]
#[must_use]
pub fn $name(mut self, value: $ty) -> Self {
self.$name = Some(value);
self
}
)+
#[must_use]
pub fn missing(&self) -> Vec<&'static str> {
let mut missing = Vec::new();
$( if self.$name.is_none() { missing.push(stringify!($name)); } )+
missing
}
#[must_use]
pub fn build(self) -> CostTable {
let missing = self.missing();
assert!(
missing.is_empty(),
"the cost table is missing {} of its {} fields: {}. \
A field left unset would be a zero, and a zero cost makes an operation free.",
missing.len(),
CostTable::FIELDS.len(),
missing.join(", "),
);
CostTable {
$( $name: self.$name.expect("checked just above"), )+
}
}
}
};
}
cost_table! {
add: Cycles,
lea: Cycles,
shift_const: Cycles,
shift_var: Cycles,
mult: [Cycles; 4],
mult_bit: Cycles,
divide: [Cycles; 4],
movsx: Cycles,
movzx: Cycles,
reg_move: Cycles,
move_int_load: [Cycles; 4],
move_int_store: [Cycles; 4],
move_int_reg: Cycles,
move_fp_load: [Cycles; 2],
move_fp_store: [Cycles; 2],
move_fp_reg: Cycles,
move_fp_to_int: Cycles,
move_int_to_fp: Cycles,
addr: [Cycles; 5],
branch_cost: Cycles,
mispredict_penalty: Cycles,
move_ratio: u32,
clear_ratio: u32,
cheapest_store: Bytes,
reassoc_int: u32,
reassoc_fp: u32,
}
impl CostTable {
#[must_use]
pub fn builder() -> Builder {
Builder::new()
}
#[must_use]
pub fn mult_of(&self, width: Width) -> Cycles {
self.mult[width.index()]
}
#[must_use]
pub fn divide_of(&self, width: Width) -> Cycles {
self.divide[width.index()]
}
#[must_use]
pub fn int_load(&self, width: Width) -> Cycles {
self.move_int_load[width.index()]
}
#[must_use]
pub fn int_store(&self, width: Width) -> Cycles {
self.move_int_store[width.index()]
}
#[must_use]
pub fn has_addr(&self, mode: AddrMode) -> bool {
!self.addr[mode.index()].is_infinite()
}
#[must_use]
pub fn addr_cost(&self, mode: AddrMode) -> crate::Cost {
let cycles = self.addr[mode.index()];
if cycles.is_infinite() {
return crate::Cost::INFINITE;
}
let mut complexity = mode.complexity();
if mode.scales() && !self.has_addr(AddrMode::BaseIndex) {
complexity -= 1;
}
crate::Cost::new(cycles, complexity)
}
}
#[cfg(test)]
mod tests {
use super::{AddrMode, Builder, CostTable, Width};
use crate::{Bytes, Cost, Cycles};
fn filled() -> Builder {
let one = Cycles::ONE;
CostTable::builder()
.add(one)
.lea(one)
.shift_const(one)
.shift_var(one)
.mult([one; 4])
.mult_bit(one)
.divide([one; 4])
.movsx(one)
.movzx(one)
.reg_move(one)
.move_int_load([one; 4])
.move_int_store([one; 4])
.move_int_reg(one)
.move_fp_load([one; 2])
.move_fp_store([one; 2])
.move_fp_reg(one)
.move_fp_to_int(one)
.move_int_to_fp(one)
.addr([one; 5])
.branch_cost(one)
.mispredict_penalty(one)
.move_ratio(8)
.clear_ratio(8)
.cheapest_store(Bytes(4))
.reassoc_int(1)
.reassoc_fp(1)
}
#[test]
fn a_full_table_builds() {
let table = filled().build();
assert_eq!(table.add, Cycles::ONE);
assert!(!CostTable::FIELDS.is_empty());
}
#[test]
fn an_empty_builder_is_missing_every_field() {
assert_eq!(Builder::new().missing(), CostTable::FIELDS);
}
#[test]
#[should_panic(expected = "branch_cost")]
fn a_table_missing_a_field_does_not_build_and_says_which() {
let mut incomplete = filled();
incomplete.branch_cost = None;
let _ = incomplete.build();
}
#[test]
fn setting_a_field_twice_keeps_the_second() {
let table = filled().add(Cycles::insns(7)).build();
assert_eq!(table.add, Cycles::insns(7));
}
#[test]
fn every_field_name_is_distinct() {
let mut names = CostTable::FIELDS.to_vec();
names.sort_unstable();
let before = names.len();
names.dedup();
assert_eq!(names.len(), before);
}
#[test]
fn widths_index_their_own_slots() {
for (slot, width) in Width::ALL.iter().enumerate() {
assert_eq!(width.index(), slot);
assert_eq!(Width::from_bits(width.bits()), Some(*width));
}
assert_eq!(Width::from_bits(128), None);
assert_eq!(Width::from_bits(1), None);
}
#[test]
fn an_address_gets_one_point_of_complexity_per_feature() {
assert_eq!(AddrMode::Base.complexity(), 0);
assert_eq!(AddrMode::BaseDisp.complexity(), 1);
assert_eq!(AddrMode::BaseIndex.complexity(), 1);
assert_eq!(AddrMode::BaseIndexScale.complexity(), 2);
assert_eq!(AddrMode::BaseIndexScaleDisp.complexity(), 3);
}
#[test]
fn a_mode_the_target_lacks_is_impossible_rather_than_expensive() {
let mut addrs = [Cycles::ONE; 5];
addrs[AddrMode::BaseIndexScaleDisp.index()] = Cycles::INFINITE;
let table = filled().addr(addrs).build();
assert!(!table.has_addr(AddrMode::BaseIndexScaleDisp));
assert_eq!(table.addr_cost(AddrMode::BaseIndexScaleDisp), Cost::INFINITE);
assert!(table.has_addr(AddrMode::Base));
}
#[test]
fn a_scale_the_target_has_no_alternative_to_does_not_count_as_a_complication() {
let mut addrs = [Cycles::ONE; 5];
addrs[AddrMode::BaseIndex.index()] = Cycles::INFINITE;
let scaled_only = filled().addr(addrs).build();
assert_eq!(scaled_only.addr_cost(AddrMode::BaseIndexScale).complexity, 1);
let both = filled().build();
assert_eq!(both.addr_cost(AddrMode::BaseIndexScale).complexity, 2);
}
}