use crate::aarch64::insts::{Form, form};
use crate::timing::{Timing, TimingInsts, Unit};
pub const MODEL: &str = "Arm Neoverse N1, from Arm's published software optimization guide";
pub const LOAD: u32 = 4;
const WIDTH: u32 = 4;
pub static TIMING: TimingInsts = TimingInsts {
prefix: "a64.",
model: MODEL,
accurate: false,
width: WIDTH,
slots,
timing,
};
fn slots(unit: Unit) -> u32 {
match unit {
Unit::Int => 3,
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).unwrap_or_else(|| plain(form)))
}
fn plain(form: Form) -> Timing {
use Form::*;
let (latency, unit) = match form {
LoadImm | Insert | Alu | AluI | Unary | Convert | Move | Lea | Cmp | CmpI | Test | Csel
| Set => (1, Unit::Int),
CmpSet | CmpSetI | Select => (2, Unit::Int),
Address => (2, Unit::Int),
MulAdd => (2, Unit::Mul),
Load => (LOAD, Unit::Load),
Store | Probe => (1, Unit::Store),
Push | PushPair => (1, Unit::Store),
Pop | PopPair => (LOAD, Unit::Load),
LoadFp => (LOAD + 1, Unit::Load),
StoreFp => (1, Unit::Store),
FAlu => (2, Unit::Float),
FUnary | FConvert => (2, Unit::Float),
FMove => (2, Unit::Float),
IntToFp => (3, Unit::Float),
FpToInt => (3, Unit::Float),
FCmp => (2, Unit::Float),
FCmpSet => (3, Unit::Float),
Jump | Jcc | JumpAway => (1, Unit::Branch),
JumpReg | Call | Ret => (2, Unit::Branch),
RetVal | RetVal2 | RetValFp | RetVal2Fp | RetVal3Fp | RetVal4Fp | ArgVal | ArgValFp
| BrCond => (0, Unit::Free),
Barrier | Trap | Nop | Template => (1, Unit::Fixed),
};
Timing { latency, unit }
}
fn slow(name: &str) -> Option<Timing> {
let stem = name.split('_').next().unwrap_or(name);
let wide = name.ends_with("_64") || name.ends_with("_f64");
let (latency, unit) = match stem {
"mul" => (2, Unit::Mul),
"smulh" | "umulh" => (3, Unit::Mul),
"sdiv" | "udiv" => (if wide { 20 } else { 12 }, Unit::Div),
"fmul" => (3, Unit::Float),
"fdiv" => (if wide { 15 } else { 10 }, Unit::FloatDiv),
"fsqrt" => (if wide { 17 } else { 10 }, Unit::FloatDiv),
"got" => (1 + LOAD, Unit::Load),
"tls" => (3, Unit::Int),
"align" => (2, Unit::Int),
_ => return None,
};
Some(Timing { latency, unit })
}
#[cfg(test)]
mod tests {
use super::{LOAD, TIMING};
use crate::aarch64::insts::INSTS;
use crate::timing::Unit;
#[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");
}
assert_eq!(TIMING.of("a64.fmla_rrr_f64"), None);
assert_eq!(TIMING.of("a64.add_rr_32"), TIMING.of("add_rr_32"));
}
#[test]
fn the_slow_instructions_are_slower_than_an_addition_and_on_a_unit_there_is_one_of() {
let add = TIMING.of("a64.add_rr_64").expect("described");
for name in ["a64.mul_rr_64", "a64.umulh_rr_64", "a64.madd_rrr_32"] {
let timing = TIMING.of(name).expect("described");
assert!(timing.latency > add.latency, "{name}");
assert_eq!(timing.unit, Unit::Mul, "{name}");
}
let narrow = TIMING.of("a64.sdiv_rr_32").expect("described");
let wide = TIMING.of("a64.udiv_rr_64").expect("described");
assert!(wide.latency > narrow.latency);
assert_eq!(wide.unit, Unit::Div);
assert_eq!(TIMING.slots(Unit::Div), 1);
assert_eq!(TIMING.of("a64.fdiv_f64").expect("described").unit, Unit::FloatDiv);
assert_eq!(TIMING.of("a64.fadd_f64").expect("described").unit, Unit::Float);
}
#[test]
fn a_load_costs_what_the_cache_takes_and_an_address_through_the_table_costs_one_as_well() {
assert_eq!(TIMING.of("a64.ldr_64").expect("described").latency, LOAD);
assert_eq!(TIMING.of("a64.got_64").expect("described").latency, 1 + LOAD);
assert_eq!(TIMING.of("a64.got_64").expect("described").unit, Unit::Load);
}
#[test]
fn an_instruction_that_encodes_to_nothing_takes_no_time_and_one_that_must_not_move_is_fixed() {
for name in ["a64.arg_val_32", "a64.ret_val_f64", "a64.br_cond_32"] {
assert_eq!(TIMING.of(name).expect("described").unit, Unit::Free, "{name}");
assert_eq!(TIMING.of(name).expect("described").latency, 0, "{name}");
}
for name in ["a64.fence", "a64.trap", "a64.nop"] {
assert_eq!(TIMING.of(name).expect("described").unit, Unit::Fixed, "{name}");
}
assert!(!TIMING.accurate);
for &unit in Unit::ALL {
assert!(TIMING.slots(unit) >= 1, "{unit:?}");
}
}
}