use crate::heuristics;
use crate::{Bytes, CostTable, Cycles, Goal, RegClass, TargetCosts, TuneFlag, Tuning};
use std::sync::LazyLock;
static SPEED: LazyLock<CostTable> = LazyLock::new(|| {
CostTable::builder()
.add(Cycles::ONE)
.lea(Cycles::ONE)
.shift_const(Cycles::ONE)
.shift_var(Cycles::ONE)
.mult([Cycles::insns(3), Cycles::insns(3), Cycles::insns(3), Cycles::insns(4)])
.mult_bit(Cycles::ONE)
.divide([Cycles::insns(21), Cycles::insns(21), Cycles::insns(27), Cycles::insns(43)])
.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::INFINITE, Cycles::INFINITE, Cycles::INFINITE])
.compare_reg(Cycles::ONE)
.compare_zero(Cycles::ZERO)
.branch_cost(Cycles::insns(3))
.mispredict_penalty(Cycles::insns(20))
.move_ratio(crate::param!(heuristics::BLOCK_COPY_MOVES_FOR_SPEED))
.clear_ratio(crate::param!(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(Cycles::ONE)
.lea(Cycles::ONE)
.shift_const(Cycles::ONE)
.shift_var(Cycles::ONE)
.mult([Cycles::ONE; 4])
.mult_bit(Cycles::ZERO)
.divide([Cycles::ONE; 4])
.movsx(Cycles::ONE)
.movzx(Cycles::ONE)
.reg_move(Cycles::insns(4))
.move_int_load([Cycles::insns(3); 4])
.move_int_store([Cycles::insns(3); 4])
.move_int_reg(Cycles::insns(4))
.move_fp_load([Cycles::insns(3); 2])
.move_fp_store([Cycles::insns(3); 2])
.move_fp_reg(Cycles::insns(4))
.move_fp_to_int(Cycles::ONE)
.move_int_to_fp(Cycles::ONE)
.addr([Cycles::ZERO, Cycles::ZERO, Cycles::INFINITE, Cycles::INFINITE, Cycles::INFINITE])
.compare_reg(Cycles::ONE)
.compare_zero(Cycles::ZERO)
.branch_cost(crate::param!(heuristics::BRANCH_COST_FOR_SIZE))
.mispredict_penalty(Cycles::insns(20))
.move_ratio(crate::param!(heuristics::BLOCK_COPY_MOVES_FOR_SIZE))
.clear_ratio(crate::param!(heuristics::BLOCK_COPY_MOVES_FOR_SIZE))
.cheapest_store(CHEAPEST_STORE)
.reassoc_int(crate::param!(heuristics::REASSOC_WIDTH_UNTUNED))
.reassoc_fp(crate::param!(heuristics::REASSOC_WIDTH_UNTUNED))
.build()
});
const CHEAPEST_STORE: Bytes = Bytes(4);
const TUNING: Tuning =
Tuning::untuned().with(TuneFlag::FastUnalignedAccess).with(TuneFlag::FastMultiply);
const ALLOCATABLE_GPR: u32 = 12;
const ALLOCATABLE_VECTOR: u32 = 14;
struct Wasm32;
impl TargetCosts for Wasm32 {
fn table(&self, goal: Goal) -> &CostTable {
match goal {
Goal::Speed => &SPEED,
Goal::Size => &SIZE,
}
}
fn tune(&self, flag: TuneFlag) -> bool {
TUNING.get(flag)
}
fn allocatable(&self, class: RegClass) -> u32 {
match class {
RegClass::Integer => ALLOCATABLE_GPR,
RegClass::Float => ALLOCATABLE_VECTOR,
}
}
fn name(&self) -> &'static str {
"wasm32"
}
}
pub static COSTS: &(dyn TargetCosts + 'static) = &Wasm32;
#[cfg(test)]
mod tests {
use super::{COSTS, SIZE, SPEED};
use crate::table::{AddrMode, Width};
use crate::{Cycles, Goal, TuneFlag, heuristics};
#[test]
fn both_tables_are_complete() {
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 an_address_is_a_base_and_an_offset_and_nothing_else() {
assert!(SPEED.has_addr(AddrMode::Base));
assert!(SPEED.has_addr(AddrMode::BaseDisp));
for mode in [AddrMode::BaseIndex, AddrMode::BaseIndexScale, AddrMode::BaseIndexScaleDisp] {
assert!(!SPEED.has_addr(mode), "{mode:?} does not exist on wasm32");
assert!(!SIZE.has_addr(mode), "{mode:?} does not exist on wasm32");
}
assert_eq!(SPEED.addr[AddrMode::BaseDisp.index()], Cycles::ZERO);
}
#[test]
fn an_add_is_the_unit_in_both_tables() {
assert_eq!(SPEED.add, Cycles::ONE);
assert_eq!(SIZE.add, Cycles::ONE);
assert_eq!(SIZE.branch_cost, heuristics::BRANCH_COST_FOR_SIZE);
}
#[test]
fn a_divide_is_dearer_than_a_multiply_for_speed_and_not_for_size() {
for width in Width::ALL {
assert!(SPEED.divide_of(width) > SPEED.mult_of(width));
assert_eq!(SIZE.divide_of(width), SIZE.mult_of(width));
}
}
#[test]
fn a_countdown_has_no_comparison() {
assert_eq!(SPEED.compare_zero, Cycles::ZERO);
assert_eq!(SIZE.compare_zero, Cycles::ZERO);
}
#[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_eq!(COSTS.name(), "wasm32");
assert_eq!(COSTS.branch_cost(Goal::Speed, false), SPEED.branch_cost);
}
}