use crate::heuristics;
use crate::{Bytes, CostTable, Cycles, Goal, TargetCosts, TuneFlag, Tuning};
use std::sync::LazyLock;
const fn bytes(n: i64) -> Cycles {
Cycles::hundredths(n * (Cycles::SCALE / 2))
}
static SPEED: LazyLock<CostTable> = LazyLock::new(|| {
CostTable::builder()
.add(Cycles::ONE)
.lea(Cycles::ONE)
.shift_const(Cycles::ONE)
.shift_var(Cycles::insns(2))
.mult([Cycles::insns(4), Cycles::insns(4), Cycles::insns(3), Cycles::insns(5)])
.mult_bit(Cycles::ONE)
.divide([Cycles::insns(20), Cycles::insns(20), Cycles::insns(26), Cycles::insns(42)])
.movsx(Cycles::ONE)
.movzx(Cycles::ONE)
.reg_move(Cycles::ONE)
.move_int_load([Cycles::insns(4); 4])
.move_int_store([Cycles::ONE; 4])
.move_int_reg(Cycles::ONE)
.move_fp_load([Cycles::insns(5); 2])
.move_fp_store([Cycles::ONE; 2])
.move_fp_reg(Cycles::ONE)
.move_fp_to_int(Cycles::insns(3))
.move_int_to_fp(Cycles::insns(3))
.addr([Cycles::ZERO, Cycles::ZERO, Cycles::ONE, Cycles::ONE, Cycles::ONE])
.branch_cost(Cycles::insns(3))
.mispredict_penalty(Cycles::insns(20))
.move_ratio(heuristics::BLOCK_COPY_MOVES_FOR_SPEED)
.clear_ratio(heuristics::BLOCK_COPY_MOVES_FOR_SPEED)
.cheapest_store(CHEAPEST_STORE)
.reassoc_int(2)
.reassoc_fp(4)
.build()
});
static SIZE: LazyLock<CostTable> = LazyLock::new(|| {
CostTable::builder()
.add(bytes(2))
.lea(bytes(3))
.shift_const(bytes(3))
.shift_var(bytes(2))
.mult([bytes(3); 4])
.mult_bit(Cycles::ZERO)
.divide([bytes(3); 4])
.movsx(bytes(3))
.movzx(bytes(3))
.reg_move(bytes(2))
.move_int_load([bytes(2); 4])
.move_int_store([bytes(2); 4])
.move_int_reg(bytes(2))
.move_fp_load([bytes(4); 2])
.move_fp_store([bytes(4); 2])
.move_fp_reg(bytes(4))
.move_fp_to_int(bytes(4))
.move_int_to_fp(bytes(4))
.addr([Cycles::ZERO, bytes(1), bytes(1), bytes(1), bytes(2)])
.branch_cost(heuristics::BRANCH_COST_FOR_SIZE)
.mispredict_penalty(Cycles::insns(20))
.move_ratio(heuristics::BLOCK_COPY_MOVES_FOR_SIZE)
.clear_ratio(heuristics::BLOCK_COPY_MOVES_FOR_SIZE)
.cheapest_store(CHEAPEST_STORE)
.reassoc_int(heuristics::REASSOC_WIDTH_UNTUNED)
.reassoc_fp(heuristics::REASSOC_WIDTH_UNTUNED)
.build()
});
const CHEAPEST_STORE: Bytes = Bytes(4);
const TUNING: Tuning = Tuning::untuned()
.with(TuneFlag::Schedule)
.with(TuneFlag::FastUnalignedAccess)
.with(TuneFlag::FastMultiply);
struct X86_64;
impl TargetCosts for X86_64 {
fn table(&self, goal: Goal) -> &CostTable {
match goal {
Goal::Speed => &SPEED,
Goal::Size => &SIZE,
}
}
fn tune(&self, flag: TuneFlag) -> bool {
TUNING.get(flag)
}
fn name(&self) -> &'static str {
"x86-64"
}
}
pub static COSTS: &(dyn TargetCosts + 'static) = &X86_64;
#[cfg(test)]
mod tests {
use super::{COSTS, SIZE, SPEED, bytes};
use crate::table::{AddrMode, Width};
use crate::{Cost, Cycles, Goal, TuneFlag, heuristics};
#[test]
fn both_tables_are_complete_or_neither_exists() {
assert!(!SPEED.add.is_infinite());
assert!(!SIZE.add.is_infinite());
}
#[test]
fn the_two_tables_agree_about_what_the_machine_can_do() {
let speed = SPEED.capabilities();
let size = SIZE.capabilities();
assert_eq!(speed.len(), size.len());
for ((name, from_speed), (also, from_size)) in speed.iter().zip(size.iter()) {
assert_eq!(name, also);
assert_eq!(from_speed, from_size, "the two tables disagree about `{name}`");
}
}
#[test]
fn the_two_tables_are_not_the_same_table() {
assert_ne!(*SPEED, *SIZE);
assert!(SPEED.divide_of(Width::W32) > SPEED.mult_of(Width::W32));
assert_eq!(SIZE.divide_of(Width::W32), SIZE.mult_of(Width::W32));
}
#[test]
fn a_divide_is_the_expensive_one_at_every_width() {
for width in Width::ALL {
assert!(
SPEED.divide_of(width) > SPEED.mult_of(width),
"a divide is not dearer than a multiply at {} bits",
width.bits()
);
assert!(SPEED.mult_of(width) > SPEED.add);
}
}
#[test]
fn every_addressing_mode_exists_on_this_target() {
for mode in AddrMode::ALL {
assert!(SPEED.has_addr(mode), "{mode:?} should exist on x86-64");
assert_eq!(SPEED.addr_cost(mode).complexity, mode.complexity());
}
assert_eq!(SPEED.addr_cost(AddrMode::Base), Cost::new(Cycles::ZERO, 0));
}
#[test]
fn an_index_costs_a_cycle_and_a_displacement_does_not() {
assert_eq!(SPEED.addr[AddrMode::BaseDisp.index()], Cycles::ZERO);
assert_eq!(SPEED.addr[AddrMode::BaseIndex.index()], Cycles::ONE);
}
#[test]
fn a_lea_ties_with_an_add_for_speed_and_loses_for_size() {
assert_eq!(SPEED.lea, SPEED.add);
assert!(SIZE.lea > SIZE.add);
}
#[test]
fn a_branch_is_free_when_predicted_and_costs_the_table_otherwise() {
assert_eq!(COSTS.branch_cost(Goal::Speed, true), heuristics::BRANCH_COST_PREDICTABLE);
assert_eq!(COSTS.branch_cost(Goal::Speed, false), SPEED.branch_cost);
assert_eq!(COSTS.branch_cost(Goal::Size, false), heuristics::BRANCH_COST_FOR_SIZE);
assert_eq!(COSTS.branch_cost(Goal::Size, true), COSTS.branch_cost(Goal::Size, false));
}
#[test]
fn a_mispredict_is_worth_far_more_than_the_branch_it_came_from() {
assert!(SPEED.mispredict_penalty > SPEED.branch_cost * 4);
}
#[test]
fn the_size_table_reads_in_bytes() {
assert_eq!(bytes(2), Cycles::ONE);
assert_eq!(SIZE.add, bytes(2));
assert_eq!(bytes(3), Cycles::hundredths(150));
}
#[test]
fn the_target_answers_the_tuning_flags_it_has_reasons_for() {
assert!(COSTS.tune(TuneFlag::Schedule));
assert!(COSTS.tune(TuneFlag::FastUnalignedAccess));
assert!(COSTS.tune(TuneFlag::FastMultiply));
assert!(!COSTS.tune(TuneFlag::PreferLea));
assert!(!COSTS.tune(TuneFlag::IfConvertPredictable));
assert!(!COSTS.tune(TuneFlag::PartialRegisterStall));
}
#[test]
fn a_block_copy_gets_fewer_moves_when_optimizing_for_size() {
assert_eq!(SPEED.move_ratio, heuristics::BLOCK_COPY_MOVES_FOR_SPEED);
assert_eq!(SIZE.move_ratio, heuristics::BLOCK_COPY_MOVES_FOR_SIZE);
assert!(SPEED.move_ratio > SIZE.move_ratio);
}
#[test]
fn nothing_in_either_table_is_free_unless_it_really_is() {
assert_eq!(SPEED.add, Cycles::ONE);
assert!(SPEED.reg_move > Cycles::ZERO);
assert!(SIZE.reg_move > Cycles::ZERO);
for width in Width::ALL {
assert!(SPEED.int_load(width) > Cycles::ZERO);
assert!(SPEED.int_store(width) > Cycles::ZERO);
assert!(SIZE.int_load(width) > Cycles::ZERO);
}
}
}