use crate::timing::{Timing, TimingInsts, Unit};
use crate::x86_64::insts::{Form, form};
pub const MODEL: &str =
"Intel Skylake, from the published instruction latency and throughput tables";
pub const LOAD: u32 = 5;
const WIDTH: u32 = 4;
pub static TIMING: TimingInsts = TimingInsts {
prefix: "x64.",
model: MODEL,
accurate: false,
width: WIDTH,
slots,
timing,
};
fn slots(unit: Unit) -> u32 {
match unit {
Unit::Int => 4,
Unit::Mul => 1,
Unit::Div => 1,
Unit::Load => 2,
Unit::Store => 1,
Unit::Branch => 1,
Unit::Float => 2,
Unit::FloatDiv => 1,
Unit::Free | Unit::Fixed => WIDTH,
}
}
fn timing(name: &str) -> Option<Timing> {
let form = form(name)?;
Some(slow(name, form).unwrap_or_else(|| plain(form)))
}
fn plain(form: Form) -> Timing {
use Form::*;
let (latency, unit) = match form {
LoadImm | AluRr | AluRi | UnaryR | ShiftRi | Move | Lea | Convert | Cmp | CmpRi | Test
| Set | Cmov => (1, Unit::Int),
ShiftCl => (2, Unit::Int),
CmpSet | CmpSetRi | TestCmov => (2, Unit::Int),
AluRm => (1 + LOAD, Unit::Load),
DivQuo | DivRem => (26, Unit::Div),
Load => (LOAD, Unit::Load),
Store | Probe => (1, Unit::Store),
Push => (1, Unit::Store),
Pop => (LOAD, Unit::Load),
Jcc | Jmp => (1, Unit::Branch),
JmpReg | Call | Ret => (2, Unit::Branch),
CmpXchg | Rmw => (20, Unit::Store),
AluVec => (4, Unit::Float),
MoveVec => (1, Unit::Float),
LoadVec => (1 + LOAD, Unit::Load),
StoreVec => (1, Unit::Store),
ConvertVec | ConvertToVec => (5, Unit::Float),
ConvertFromVec => (4, Unit::Float),
CmpSetVec => (3, Unit::Float),
CmpSetVecBoth => (4, Unit::Float),
PushX87 => (LOAD, Unit::Load),
PopX87 => (4, Unit::Store),
CtrlX87 => (8, Unit::Fixed),
ArithX87 => (5, Unit::Float),
UnaryX87 => (1, Unit::Float),
CmpSetX87 => (4, Unit::Float),
CmpSetX87Both => (5, Unit::Float),
RetVal | RetVal2 | ArgVal | BrCond | RetValVec | RetVal2Vec | ArgValVec => (0, Unit::Free),
Barrier | Trap | Landing | Nop | Spin => (1, Unit::Fixed),
Prefetch => (1, Unit::Load),
CpuId => (100, Unit::Fixed),
};
Timing { latency, unit }
}
fn slow(name: &str, form: Form) -> Option<Timing> {
let stem = name.split('_').next().unwrap_or(name);
let float = matches!(form, Form::AluVec | Form::ArithX87);
match stem {
"imul" => Some(Timing {
latency: if form == Form::AluRm { 3 + LOAD } else { 3 },
unit: Unit::Mul,
}),
"div" | "idiv" if !float => Some(Timing { latency: width(name), unit: Unit::Div }),
"divss" => Some(Timing { latency: 11, unit: Unit::FloatDiv }),
"divsd" => Some(Timing { latency: 14, unit: Unit::FloatDiv }),
"fdiv" | "fdivr" => Some(Timing { latency: 15, unit: Unit::FloatDiv }),
"fadd" | "fsub" | "fsubr" => Some(Timing { latency: 3, unit: Unit::Float }),
"movd" | "movq" => Some(Timing { latency: 2, unit: Unit::Float }),
_ => None,
}
}
fn width(name: &str) -> u32 {
match name.rsplit('_').next() {
Some("8") | Some("16") => 25,
Some("32") => 26,
_ => 42,
}
}
#[cfg(test)]
mod tests {
use super::{LOAD, TIMING};
use crate::timing::Unit;
use crate::x86_64::insts::INSTS;
#[test]
fn every_instruction_this_target_has_is_one_the_model_has_a_number_for() {
for &(name, _) in INSTS {
assert!(TIMING.of(name).is_some(), "{name} has no timing");
}
}
#[test]
fn a_name_this_target_does_not_have_gets_no_number_rather_than_a_made_up_one() {
assert_eq!(TIMING.of("x64.fma_rrr_64"), None);
assert_eq!(TIMING.of("not_even_prefixed"), None);
}
#[test]
fn the_prefix_is_taken_off_before_the_table_is_asked() {
assert_eq!(TIMING.of("x64.add_rr_32"), TIMING.of("add_rr_32"));
}
#[test]
fn a_multiply_costs_more_than_an_addition_and_wants_a_unit_there_is_one_of() {
let add = TIMING.of("x64.add_rr_32").expect("described");
let imul = TIMING.of("x64.imul_rr_32").expect("described");
assert!(imul.latency > add.latency, "{imul:?} against {add:?}");
assert_eq!(imul.unit, Unit::Mul);
assert_eq!(TIMING.slots(Unit::Mul), 1);
assert_eq!(TIMING.slots(Unit::Int), 4);
}
#[test]
fn a_divide_costs_more_the_wider_it_is() {
let narrow = TIMING.of("x64.idiv_quo_32").expect("described").latency;
let wide = TIMING.of("x64.idiv_quo_64").expect("described").latency;
assert!(wide > narrow, "{wide} against {narrow}");
assert_eq!(TIMING.of("x64.div_rem_64").expect("described").unit, Unit::Div);
}
#[test]
fn an_instruction_that_reads_memory_costs_its_own_work_and_a_load() {
let add = TIMING.of("x64.add_rr_32").expect("described");
let from_memory = TIMING.of("x64.add_rm_32").expect("described");
assert_eq!(from_memory.latency, add.latency + LOAD);
assert_eq!(from_memory.unit, Unit::Load);
assert_eq!(TIMING.of("x64.imul_rm_32").expect("described").latency, 3 + LOAD);
}
#[test]
fn an_instruction_that_encodes_to_nothing_takes_no_time_and_no_unit() {
for name in ["x64.arg_val_32", "x64.ret_val_64", "x64.br_cond_8", "x64.arg_val_f64"] {
let timing = TIMING.of(name).expect("described");
assert_eq!(timing.latency, 0, "{name}");
assert_eq!(timing.unit, Unit::Free, "{name}");
}
}
#[test]
fn a_floating_point_divide_is_on_the_divider_and_an_addition_is_not() {
assert_eq!(TIMING.of("x64.divsd_rr").expect("described").unit, Unit::FloatDiv);
assert_eq!(TIMING.of("x64.addsd_rr").expect("described").unit, Unit::Float);
assert!(
TIMING.of("x64.divsd_rr").expect("described").latency
> TIMING.of("x64.divss_rr").expect("described").latency
);
}
#[test]
fn a_move_of_bits_between_the_files_is_not_priced_as_a_conversion() {
let moved = TIMING.of("x64.movq_to_xmm").expect("described");
let converted = TIMING.of("x64.cvtsi2sd_64").expect("described");
assert!(moved.latency < converted.latency, "{moved:?} against {converted:?}");
assert_eq!(TIMING.of("x64.movd_from_xmm"), Some(moved));
}
#[test]
fn this_model_says_it_is_not_cycle_accurate() {
assert!(!TIMING.accurate, "no target here has an automaton and none should claim one");
assert!(!TIMING.model.is_empty(), "a model has to say which processor it describes");
}
#[test]
fn the_instructions_a_schedule_must_not_move_are_the_ones_the_model_does_not_describe() {
for name in ["x64.mfence", "x64.ud2", "x64.endbr64", "x64.nop", "x64.pause", "x64.fldcw"] {
assert_eq!(TIMING.of(name).expect("described").unit, Unit::Fixed, "{name}");
}
for name in ["x64.add_rr_32", "x64.mov_rm_64", "x64.arg_val_32"] {
let timing = TIMING.of(name).expect("described");
assert_ne!(timing.unit, Unit::Fixed, "{name}");
}
}
#[test]
fn every_unit_has_at_least_one_of_it() {
for &unit in Unit::ALL {
assert!(TIMING.slots(unit) >= 1, "{unit:?}");
}
}
}