use crate::x86::x86_instr_info::{X86InstrInfo, X86Opcode};
use crate::x86::x86_schedule_model::X86SchedModelKind;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InstructionCost {
pub cost: u32,
}
impl InstructionCost {
pub const FREE: Self = Self { cost: 0 };
pub const CHEAP: Self = Self { cost: 1 };
pub const EXPENSIVE: Self = Self { cost: 100 };
pub const INVALID: Self = Self { cost: u32::MAX };
pub fn new(cost: u32) -> Self {
Self { cost }
}
pub fn is_valid(&self) -> bool {
self.cost != u32::MAX
}
pub fn mul(&self, factor: u32) -> Self {
Self {
cost: self.cost.saturating_mul(factor),
}
}
pub fn is_free(&self) -> bool {
self.cost == 0
}
}
impl std::ops::Add for InstructionCost {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
cost: self.cost.saturating_add(rhs.cost),
}
}
}
impl std::ops::Mul<u32> for InstructionCost {
type Output = Self;
fn mul(self, rhs: u32) -> Self {
Self {
cost: self.cost.saturating_mul(rhs),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VectorWidth {
Scalar = 0,
V64 = 64,
V128 = 128,
V256 = 256,
V512 = 512,
}
impl VectorWidth {
pub fn bits(&self) -> u32 {
match self {
VectorWidth::Scalar => 0,
VectorWidth::V64 => 64,
VectorWidth::V128 => 128,
VectorWidth::V256 => 256,
VectorWidth::V512 => 512,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ElementType {
I1,
I8,
I16,
I32,
I64,
F16,
F32,
F64,
BF16,
}
impl ElementType {
pub fn size_bits(&self) -> u32 {
match self {
ElementType::I1 => 1,
ElementType::I8 => 8,
ElementType::I16 | ElementType::F16 | ElementType::BF16 => 16,
ElementType::I32 | ElementType::F32 => 32,
ElementType::I64 | ElementType::F64 => 64,
}
}
pub fn is_integer(&self) -> bool {
matches!(
self,
ElementType::I1
| ElementType::I8
| ElementType::I16
| ElementType::I32
| ElementType::I64
)
}
pub fn is_floating(&self) -> bool {
matches!(
self,
ElementType::F16 | ElementType::F32 | ElementType::F64 | ElementType::BF16
)
}
}
#[derive(Debug, Clone)]
pub struct X86CostModel {
pub kind: X86SchedModelKind,
pub simple_alu_latency: u32,
pub imul_latency: u32,
pub idiv_latency: u32,
pub branch_cost: u32,
pub call_cost: u32,
pub ret_cost: u32,
pub l1_load_latency: u32,
pub l1_store_latency: u32,
pub l2_load_latency: u32,
pub l3_load_latency: u32,
pub mem_load_latency: u32,
pub simd_alu_latency: u32,
pub simd_mul_latency: u32,
pub simd_fma_latency: u32,
pub simd_div_latency: u32,
pub simd_shuffle_latency: u32,
pub simd_hadd_latency: u32,
pub gather_latency: u32,
pub scatter_latency: u32,
pub reg_move_cost: u32,
pub gpr_simd_move_cost: u32,
pub vectorization_overhead: u32,
pub has_avx512: bool,
pub has_fma: bool,
pub has_gather_scatter: bool,
pub opcode_costs: HashMap<u32, InstructionCost>,
pub masked_op_multiplier: u32,
}
impl X86CostModel {
pub fn new(kind: X86SchedModelKind) -> Self {
match kind {
X86SchedModelKind::SkylakeClient => Self::skylake_client(),
X86SchedModelKind::IceLake => Self::ice_lake(),
X86SchedModelKind::AlderLakePcore => Self::alder_lake_pcore(),
X86SchedModelKind::GraniteRapids => Self::granite_rapids(),
X86SchedModelKind::Zen3 => Self::zen3(),
X86SchedModelKind::Zen4 => Self::zen4(),
X86SchedModelKind::Zen5 => Self::zen5(),
X86SchedModelKind::MeteorLakePcore => Self::meteor_lake_pcore(),
X86SchedModelKind::MeteorLakeEcore => Self::meteor_lake_ecore(),
X86SchedModelKind::LunarLakePcore => Self::lunar_lake_pcore(),
X86SchedModelKind::LunarLakeEcore => Self::lunar_lake_ecore(),
X86SchedModelKind::ArrowLakePcore => Self::arrow_lake_pcore(),
X86SchedModelKind::SierraForest => Self::sierra_forest(),
X86SchedModelKind::ClearwaterForest => Self::clearwater_forest(),
X86SchedModelKind::Zen5c => Self::zen5c(),
X86SchedModelKind::Zen6 => Self::zen6(),
X86SchedModelKind::Zen6c => Self::zen6c(),
_ => Self::default(),
}
}
}
impl Default for X86CostModel {
fn default() -> Self {
Self::zen5()
}
}
impl X86CostModel {
fn skylake_client() -> Self {
let mut costs = HashMap::new();
costs.insert(X86Opcode::DIV as u32, InstructionCost::new(26));
costs.insert(X86Opcode::IDIV as u32, InstructionCost::new(26));
costs.insert(X86Opcode::MUL as u32, InstructionCost::new(3));
costs.insert(X86Opcode::IMUL as u32, InstructionCost::new(3));
Self {
kind: X86SchedModelKind::SkylakeClient,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 26,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 4,
l1_store_latency: 1,
l2_load_latency: 14,
l3_load_latency: 42,
mem_load_latency: 200,
simd_alu_latency: 1,
simd_mul_latency: 4,
simd_fma_latency: 4,
simd_div_latency: 13,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 20,
scatter_latency: 20,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 5,
has_avx512: false,
has_fma: true,
has_gather_scatter: true,
opcode_costs: costs,
masked_op_multiplier: 0,
}
}
fn ice_lake() -> Self {
let mut costs = HashMap::new();
costs.insert(X86Opcode::DIV as u32, InstructionCost::new(18));
costs.insert(X86Opcode::IDIV as u32, InstructionCost::new(18));
Self {
kind: X86SchedModelKind::IceLake,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 18,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 13,
l3_load_latency: 54,
mem_load_latency: 200,
simd_alu_latency: 1,
simd_mul_latency: 4,
simd_fma_latency: 4,
simd_div_latency: 11,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 18,
scatter_latency: 18,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 5,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: costs,
masked_op_multiplier: 1,
}
}
fn alder_lake_pcore() -> Self {
Self {
kind: X86SchedModelKind::AlderLakePcore,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 17,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 13,
l3_load_latency: 51,
mem_load_latency: 200,
simd_alu_latency: 1,
simd_mul_latency: 4,
simd_fma_latency: 4,
simd_div_latency: 11,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 17,
scatter_latency: 17,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 5,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn granite_rapids() -> Self {
Self {
kind: X86SchedModelKind::GraniteRapids,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 15,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 48,
mem_load_latency: 180,
simd_alu_latency: 1,
simd_mul_latency: 4,
simd_fma_latency: 4,
simd_div_latency: 10,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 15,
scatter_latency: 15,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 5,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn zen3() -> Self {
let mut costs = HashMap::new();
costs.insert(X86Opcode::DIV as u32, InstructionCost::new(13));
costs.insert(X86Opcode::IDIV as u32, InstructionCost::new(13));
Self {
kind: X86SchedModelKind::Zen3,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 13,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 4,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 46,
mem_load_latency: 190,
simd_alu_latency: 1,
simd_mul_latency: 3,
simd_fma_latency: 4,
simd_div_latency: 13,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 18,
scatter_latency: 18,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 4,
has_avx512: false,
has_fma: true,
has_gather_scatter: true,
opcode_costs: costs,
masked_op_multiplier: 0,
}
}
fn zen4() -> Self {
Self {
kind: X86SchedModelKind::Zen4,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 12,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 4,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 48,
mem_load_latency: 180,
simd_alu_latency: 1,
simd_mul_latency: 3,
simd_fma_latency: 4,
simd_div_latency: 13,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 16,
scatter_latency: 16,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 4,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn zen5() -> Self {
Self {
kind: X86SchedModelKind::Zen5,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 11,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 4,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 46,
mem_load_latency: 175,
simd_alu_latency: 1,
simd_mul_latency: 2,
simd_fma_latency: 3,
simd_div_latency: 12,
simd_shuffle_latency: 1,
simd_hadd_latency: 2,
gather_latency: 14,
scatter_latency: 14,
reg_move_cost: 1,
gpr_simd_move_cost: 2,
vectorization_overhead: 4,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn meteor_lake_pcore() -> Self {
Self {
kind: X86SchedModelKind::MeteorLakePcore,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 17,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 13,
l3_load_latency: 48,
mem_load_latency: 200,
simd_alu_latency: 1,
simd_mul_latency: 4,
simd_fma_latency: 4,
simd_div_latency: 11,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 16,
scatter_latency: 16,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 5,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn meteor_lake_ecore() -> Self {
Self {
kind: X86SchedModelKind::MeteorLakeEcore,
simple_alu_latency: 1,
imul_latency: 4,
idiv_latency: 20,
branch_cost: 1,
call_cost: 4,
ret_cost: 3,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 16,
l3_load_latency: 55,
mem_load_latency: 220,
simd_alu_latency: 1,
simd_mul_latency: 5,
simd_fma_latency: 5,
simd_div_latency: 14,
simd_shuffle_latency: 1,
simd_hadd_latency: 4,
gather_latency: 20,
scatter_latency: 20,
reg_move_cost: 1,
gpr_simd_move_cost: 4,
vectorization_overhead: 6,
has_avx512: false,
has_fma: true,
has_gather_scatter: false,
opcode_costs: HashMap::new(),
masked_op_multiplier: 0,
}
}
fn lunar_lake_pcore() -> Self {
Self {
kind: X86SchedModelKind::LunarLakePcore,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 16,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 46,
mem_load_latency: 200,
simd_alu_latency: 1,
simd_mul_latency: 4,
simd_fma_latency: 4,
simd_div_latency: 10,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 15,
scatter_latency: 15,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 5,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn lunar_lake_ecore() -> Self {
Self {
kind: X86SchedModelKind::LunarLakeEcore,
simple_alu_latency: 1,
imul_latency: 4,
idiv_latency: 19,
branch_cost: 1,
call_cost: 4,
ret_cost: 3,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 15,
l3_load_latency: 52,
mem_load_latency: 210,
simd_alu_latency: 1,
simd_mul_latency: 5,
simd_fma_latency: 5,
simd_div_latency: 13,
simd_shuffle_latency: 1,
simd_hadd_latency: 4,
gather_latency: 19,
scatter_latency: 19,
reg_move_cost: 1,
gpr_simd_move_cost: 4,
vectorization_overhead: 6,
has_avx512: false,
has_fma: true,
has_gather_scatter: false,
opcode_costs: HashMap::new(),
masked_op_multiplier: 0,
}
}
fn arrow_lake_pcore() -> Self {
Self {
kind: X86SchedModelKind::ArrowLakePcore,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 16,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 48,
mem_load_latency: 200,
simd_alu_latency: 1,
simd_mul_latency: 4,
simd_fma_latency: 4,
simd_div_latency: 10,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 15,
scatter_latency: 15,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 5,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn sierra_forest() -> Self {
Self {
kind: X86SchedModelKind::SierraForest,
simple_alu_latency: 1,
imul_latency: 4,
idiv_latency: 20,
branch_cost: 1,
call_cost: 4,
ret_cost: 3,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 15,
l3_load_latency: 50,
mem_load_latency: 210,
simd_alu_latency: 1,
simd_mul_latency: 5,
simd_fma_latency: 5,
simd_div_latency: 13,
simd_shuffle_latency: 1,
simd_hadd_latency: 4,
gather_latency: 18,
scatter_latency: 18,
reg_move_cost: 1,
gpr_simd_move_cost: 4,
vectorization_overhead: 6,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn clearwater_forest() -> Self {
Self {
kind: X86SchedModelKind::ClearwaterForest,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 18,
branch_cost: 1,
call_cost: 4,
ret_cost: 3,
l1_load_latency: 5,
l1_store_latency: 1,
l2_load_latency: 14,
l3_load_latency: 48,
mem_load_latency: 200,
simd_alu_latency: 1,
simd_mul_latency: 4,
simd_fma_latency: 4,
simd_div_latency: 12,
simd_shuffle_latency: 1,
simd_hadd_latency: 3,
gather_latency: 16,
scatter_latency: 16,
reg_move_cost: 1,
gpr_simd_move_cost: 3,
vectorization_overhead: 6,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn zen5c() -> Self {
Self {
kind: X86SchedModelKind::Zen5c,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 12,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 4,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 48,
mem_load_latency: 180,
simd_alu_latency: 1,
simd_mul_latency: 2,
simd_fma_latency: 3,
simd_div_latency: 13,
simd_shuffle_latency: 1,
simd_hadd_latency: 2,
gather_latency: 15,
scatter_latency: 15,
reg_move_cost: 1,
gpr_simd_move_cost: 2,
vectorization_overhead: 4,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn zen6() -> Self {
Self {
kind: X86SchedModelKind::Zen6,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 10,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 4,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 44,
mem_load_latency: 170,
simd_alu_latency: 1,
simd_mul_latency: 2,
simd_fma_latency: 3,
simd_div_latency: 11,
simd_shuffle_latency: 1,
simd_hadd_latency: 2,
gather_latency: 13,
scatter_latency: 13,
reg_move_cost: 1,
gpr_simd_move_cost: 2,
vectorization_overhead: 4,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
fn zen6c() -> Self {
Self {
kind: X86SchedModelKind::Zen6c,
simple_alu_latency: 1,
imul_latency: 3,
idiv_latency: 11,
branch_cost: 1,
call_cost: 3,
ret_cost: 2,
l1_load_latency: 4,
l1_store_latency: 1,
l2_load_latency: 12,
l3_load_latency: 46,
mem_load_latency: 175,
simd_alu_latency: 1,
simd_mul_latency: 2,
simd_fma_latency: 3,
simd_div_latency: 12,
simd_shuffle_latency: 1,
simd_hadd_latency: 2,
gather_latency: 14,
scatter_latency: 14,
reg_move_cost: 1,
gpr_simd_move_cost: 2,
vectorization_overhead: 4,
has_avx512: true,
has_fma: true,
has_gather_scatter: true,
opcode_costs: HashMap::new(),
masked_op_multiplier: 1,
}
}
pub fn get_opcode_cost(&self, opcode: X86Opcode) -> InstructionCost {
if let Some(cost) = self.opcode_costs.get(&(opcode as u32)) {
return *cost;
}
match opcode {
X86Opcode::ADD
| X86Opcode::SUB
| X86Opcode::AND
| X86Opcode::OR
| X86Opcode::XOR
| X86Opcode::NEG
| X86Opcode::NOT
| X86Opcode::INC
| X86Opcode::DEC
| X86Opcode::LEA
| X86Opcode::TEST
| X86Opcode::CMP => InstructionCost::new(self.simple_alu_latency),
X86Opcode::MUL | X86Opcode::IMUL => InstructionCost::new(self.imul_latency),
X86Opcode::DIV | X86Opcode::IDIV => InstructionCost::new(self.idiv_latency),
X86Opcode::SHL | X86Opcode::SHR | X86Opcode::SAR | X86Opcode::ROL | X86Opcode::ROR => {
InstructionCost::new(self.simple_alu_latency)
}
X86Opcode::MOV | X86Opcode::MOVSX | X86Opcode::MOVZX => {
InstructionCost::new(self.reg_move_cost)
}
X86Opcode::MOVABS => InstructionCost::new(self.reg_move_cost + 1),
X86Opcode::JMP | X86Opcode::CALL | X86Opcode::RET => {
InstructionCost::new(self.branch_cost)
}
X86Opcode::ADDSS
| X86Opcode::ADDSD
| X86Opcode::SUBSS
| X86Opcode::SUBSD
| X86Opcode::MULSS
| X86Opcode::MULSD
| X86Opcode::MINSS
| X86Opcode::MINSD
| X86Opcode::MAXSS
| X86Opcode::MAXSD => InstructionCost::new(self.simd_alu_latency),
X86Opcode::DIVSS | X86Opcode::DIVSD => InstructionCost::new(self.simd_div_latency),
X86Opcode::SQRTSS | X86Opcode::SQRTSD => InstructionCost::new(self.simd_div_latency),
X86Opcode::ADDPS
| X86Opcode::ADDPD
| X86Opcode::SUBPS
| X86Opcode::SUBPD
| X86Opcode::ANDPS
| X86Opcode::ANDPD
| X86Opcode::ORPS
| X86Opcode::ORPD
| X86Opcode::XORPS
| X86Opcode::XORPD => InstructionCost::new(self.simd_alu_latency),
X86Opcode::MULPS | X86Opcode::MULPD => InstructionCost::new(self.simd_mul_latency),
X86Opcode::DIVPS | X86Opcode::DIVPD => InstructionCost::new(self.simd_div_latency),
X86Opcode::HADDPS | X86Opcode::HADDPD | X86Opcode::HSUBPS | X86Opcode::HSUBPD => {
InstructionCost::new(self.simd_hadd_latency)
}
X86Opcode::VADDPS
| X86Opcode::VADDPD
| X86Opcode::VSUBPS
| X86Opcode::VSUBPD
| X86Opcode::VANDPS
| X86Opcode::VANDPD
| X86Opcode::VORPS
| X86Opcode::VORPD
| X86Opcode::VXORPS
| X86Opcode::VXORPD => InstructionCost::new(self.simd_alu_latency),
X86Opcode::VMULPS | X86Opcode::VMULPD => InstructionCost::new(self.simd_mul_latency),
X86Opcode::VDIVPS | X86Opcode::VDIVPD => InstructionCost::new(self.simd_div_latency),
X86Opcode::VFMADD132PD
| X86Opcode::VFMADD213PD
| X86Opcode::VFMADD231PD
| X86Opcode::VFMADD132PS
| X86Opcode::VFMADD213PS
| X86Opcode::VFMADD231PS => {
if self.has_fma {
InstructionCost::new(self.simd_fma_latency)
} else {
InstructionCost::new(self.simd_mul_latency + self.simd_alu_latency)
}
}
X86Opcode::VPGATHERDD
| X86Opcode::VPGATHERDQ
| X86Opcode::VPGATHERQD
| X86Opcode::VPGATHERQQ => InstructionCost::new(self.gather_latency),
_ => InstructionCost::new(self.simple_alu_latency),
}
}
pub fn get_memory_load_cost(&self, cache_level: u32) -> InstructionCost {
match cache_level {
0 => InstructionCost::new(self.l1_load_latency),
1 => InstructionCost::new(self.l2_load_latency),
2 => InstructionCost::new(self.l3_load_latency),
_ => InstructionCost::new(self.mem_load_latency),
}
}
pub fn get_arithmetic_cost(&self, elem: ElementType, width: VectorWidth) -> InstructionCost {
let base_latency = if elem.is_integer() {
self.simd_alu_latency
} else {
self.simd_alu_latency
};
let num_elements = if width.bits() == 0 || elem.size_bits() == 0 {
1
} else {
width.bits() / elem.size_bits()
};
InstructionCost::new(base_latency).mul(num_elements)
}
pub fn get_reduction_cost(
&self,
elem: ElementType,
width: VectorWidth,
is_fast: bool,
) -> InstructionCost {
let num_elements = if width.bits() == 0 || elem.size_bits() == 0 {
1
} else {
width.bits() / elem.size_bits()
};
if is_fast && num_elements <= 4 {
InstructionCost::new(self.simd_hadd_latency)
} else {
let steps = (num_elements as f64).log2().ceil() as u32;
InstructionCost::new(self.simd_alu_latency * steps)
}
}
pub fn get_shuffle_cost(&self, width: VectorWidth, is_cross_lane: bool) -> InstructionCost {
let base = self.simd_shuffle_latency;
if is_cross_lane && width.bits() >= 256 {
InstructionCost::new(base * 2)
} else {
InstructionCost::new(base)
}
}
pub fn get_interleaved_access_cost(
&self,
elem: ElementType,
width: VectorWidth,
stride: u32,
is_load: bool,
) -> InstructionCost {
let num_elements = width.bits() / elem.size_bits();
let base_load_cost = if is_load {
self.l1_load_latency
} else {
self.l1_store_latency
};
let load_cost = InstructionCost::new(base_load_cost * stride);
let shuffle_cost = InstructionCost::new(self.simd_shuffle_latency * (stride - 1));
load_cost + shuffle_cost.mul(num_elements / stride)
}
pub fn get_gather_cost(&self, _elem: ElementType, _width: VectorWidth) -> InstructionCost {
if self.has_gather_scatter {
InstructionCost::new(self.gather_latency)
} else {
InstructionCost::EXPENSIVE
}
}
pub fn get_scatter_cost(&self, _elem: ElementType, _width: VectorWidth) -> InstructionCost {
if self.has_gather_scatter {
InstructionCost::new(self.scatter_latency)
} else {
InstructionCost::EXPENSIVE
}
}
pub fn get_masked_op_cost(&self, base_cost: InstructionCost) -> InstructionCost {
if self.has_avx512 && self.masked_op_multiplier > 0 {
InstructionCost::new(base_cost.cost * self.masked_op_multiplier)
} else {
base_cost
}
}
pub fn get_branch_cost(&self) -> InstructionCost {
InstructionCost::new(self.branch_cost)
}
pub fn get_switch_cost(&self, num_cases: u32) -> InstructionCost {
if num_cases <= 3 {
InstructionCost::new(self.branch_cost * num_cases)
} else if num_cases <= 8 {
InstructionCost::new(self.branch_cost + self.l1_load_latency + 2)
} else {
InstructionCost::new(self.branch_cost + self.l2_load_latency + 3)
}
}
pub fn get_call_cost(&self, _is_tail_call: bool) -> InstructionCost {
InstructionCost::new(self.call_cost)
}
pub fn get_address_computation_cost(&self, complexity: u32) -> InstructionCost {
if complexity <= 1 {
InstructionCost::FREE } else if complexity <= 3 {
InstructionCost::new(self.simple_alu_latency)
} else {
InstructionCost::new(self.simple_alu_latency * 2)
}
}
pub fn get_scalarization_overhead(
&self,
elem: ElementType,
width: VectorWidth,
) -> InstructionCost {
let num_elements = width.bits() / elem.size_bits();
InstructionCost::new(self.vectorization_overhead * num_elements)
}
pub fn get_reg_move_cost(&self, is_gpr_to_simd: bool) -> InstructionCost {
if is_gpr_to_simd {
InstructionCost::new(self.gpr_simd_move_cost)
} else {
InstructionCost::new(self.reg_move_cost)
}
}
pub fn get_type_legalization_cost(&self, from_bits: u32, to_bits: u32) -> InstructionCost {
if from_bits == to_bits {
return InstructionCost::FREE;
}
let ratio = if from_bits > to_bits {
from_bits / to_bits
} else {
to_bits / from_bits
};
InstructionCost::new(self.simple_alu_latency * ratio)
}
pub fn get_intrinsic_cost(&self, intrinsic_id: u32) -> InstructionCost {
match intrinsic_id {
0..=999 => InstructionCost::new(self.simd_alu_latency),
1000..=1099 => InstructionCost::EXPENSIVE,
1100..=1199 => InstructionCost::new(self.l1_store_latency),
1200..=1299 => InstructionCost::new(self.l1_store_latency * 3),
_ => InstructionCost::new(self.simple_alu_latency),
}
}
}
pub struct X86TTIDeep {
pub model: X86CostModel,
pub instr_info: X86InstrInfo,
pub prefer_width_minimization: bool,
pub enable_interleaved_access: bool,
pub enable_masked_memops: bool,
pub min_vectorization_factor: u32,
pub max_vectorization_factor: u32,
pub cache_line_size: u32,
pub max_interleave_factor: u32,
}
impl X86TTIDeep {
pub fn new(model: X86CostModel) -> Self {
Self {
cache_line_size: 64,
max_interleave_factor: 8,
model,
instr_info: X86InstrInfo::new(),
prefer_width_minimization: false,
enable_interleaved_access: true,
enable_masked_memops: true,
min_vectorization_factor: 0,
max_vectorization_factor: 0,
}
}
pub fn for_kind(kind: X86SchedModelKind) -> Self {
Self::new(X86CostModel::new(kind))
}
pub fn get_num_vector_elements(&self, elem: ElementType) -> u32 {
let max_width = self.get_max_vector_width();
if max_width == 0 || elem.size_bits() == 0 {
1
} else {
max_width / elem.size_bits()
}
}
pub fn get_max_vector_width(&self) -> u32 {
if self.model.has_avx512 {
512
} else {
256
}
}
pub fn get_preferred_vector_width(&self, elem: ElementType) -> u32 {
let max_width = self.get_max_vector_width();
if self.prefer_width_minimization {
(max_width / 2).max(128)
} else {
max_width
}
}
pub fn use_masked_memops(&self) -> bool {
self.enable_masked_memops && self.model.has_avx512
}
pub fn get_vector_load_cost(
&self,
elem: ElementType,
width: VectorWidth,
is_masked: bool,
) -> InstructionCost {
let base = self.model.get_memory_load_cost(0);
let num_elements = width.bits() / elem.size_bits();
let cost = base.mul(num_elements);
if is_masked {
self.model.get_masked_op_cost(cost)
} else {
cost
}
}
pub fn get_vector_store_cost(
&self,
elem: ElementType,
width: VectorWidth,
is_masked: bool,
) -> InstructionCost {
let base = InstructionCost::new(self.model.l1_store_latency);
let num_elements = width.bits() / elem.size_bits();
let cost = base.mul(num_elements);
if is_masked {
self.model.get_masked_op_cost(cost)
} else {
cost
}
}
pub fn should_vectorize(&self, elem: ElementType, count: u32) -> bool {
if count < 2 {
return false;
}
let width = self.get_max_vector_width();
if width < elem.size_bits() {
return false;
}
let vec_elements = width / elem.size_bits();
vec_elements >= 2 && count >= vec_elements
}
pub fn get_vectorized_loop_cost(
&self,
elem: ElementType,
count: u32,
is_load: bool,
is_store: bool,
op_count: u32,
) -> InstructionCost {
let width = self.get_max_vector_width();
let vec_width = VectorWidth::Scalar;
let vec_width = match width {
512 => VectorWidth::V512,
256 => VectorWidth::V256,
128 => VectorWidth::V128,
_ => VectorWidth::V128,
};
let iterations = (count as f64 / (width as f64 / elem.size_bits() as f64)).ceil() as u32;
let mut total = InstructionCost::new(self.model.vectorization_overhead);
if is_load {
total = total
+ self
.get_vector_load_cost(elem, vec_width, false)
.mul(iterations);
}
for _ in 0..op_count {
total = total
+ self
.model
.get_arithmetic_cost(elem, vec_width)
.mul(iterations);
}
if is_store {
total = total
+ self
.get_vector_store_cost(elem, vec_width, false)
.mul(iterations);
}
total
}
pub fn get_cache_line_size(&self) -> u32 {
self.cache_line_size
}
pub fn get_l1_cache_size(&self) -> u32 {
32 * 1024 }
pub fn get_l2_cache_size(&self) -> u32 {
match self.model.kind {
X86SchedModelKind::MeteorLakeEcore | X86SchedModelKind::LunarLakeEcore => {
2 * 1024 * 1024
}
_ => 1024 * 1024, }
}
pub fn get_l3_cache_size(&self) -> u32 {
match self.model.kind {
X86SchedModelKind::GraniteRapids => 128 * 1024 * 1024,
X86SchedModelKind::Zen4 | X86SchedModelKind::Zen5 => 32 * 1024 * 1024,
_ => 16 * 1024 * 1024,
}
}
}
impl Default for X86TTIDeep {
fn default() -> Self {
Self::for_kind(X86SchedModelKind::Zen5)
}
}
pub fn make_x86_tti(kind: X86SchedModelKind) -> X86TTIDeep {
X86TTIDeep::for_kind(kind)
}
pub fn make_x86_cost_model(kind: X86SchedModelKind) -> X86CostModel {
X86CostModel::new(kind)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_instruction_cost_free() {
assert!(InstructionCost::FREE.is_free());
assert_eq!(InstructionCost::FREE.cost, 0);
}
#[test]
fn test_instruction_cost_add() {
let a = InstructionCost::new(3);
let b = InstructionCost::new(5);
assert_eq!((a + b).cost, 8);
}
#[test]
fn test_instruction_cost_mul() {
let a = InstructionCost::new(4);
assert_eq!((a * 3).cost, 12);
}
#[test]
fn test_instruction_cost_invalid() {
assert!(!InstructionCost::INVALID.is_valid());
}
#[test]
fn test_vector_width_bits() {
assert_eq!(VectorWidth::V128.bits(), 128);
assert_eq!(VectorWidth::V256.bits(), 256);
assert_eq!(VectorWidth::V512.bits(), 512);
assert_eq!(VectorWidth::Scalar.bits(), 0);
}
#[test]
fn test_element_type_size() {
assert_eq!(ElementType::I8.size_bits(), 8);
assert_eq!(ElementType::I32.size_bits(), 32);
assert_eq!(ElementType::F64.size_bits(), 64);
}
#[test]
fn test_element_type_is_integer() {
assert!(ElementType::I32.is_integer());
assert!(!ElementType::F32.is_integer());
}
#[test]
fn test_cost_model_skylake() {
let model = X86CostModel::new(X86SchedModelKind::SkylakeClient);
assert_eq!(model.simple_alu_latency, 1);
assert_eq!(model.imul_latency, 3);
assert_eq!(model.idiv_latency, 26);
assert!(!model.has_avx512);
assert!(model.has_fma);
}
#[test]
fn test_cost_model_zen5() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
assert_eq!(model.simple_alu_latency, 1);
assert_eq!(model.idiv_latency, 11);
assert!(model.has_avx512);
}
#[test]
fn test_cost_model_granite_rapids() {
let model = X86CostModel::new(X86SchedModelKind::GraniteRapids);
assert_eq!(model.idiv_latency, 15);
assert!(model.has_avx512);
}
#[test]
fn test_get_opcode_cost_add() {
let model = X86CostModel::new(X86SchedModelKind::SkylakeClient);
let cost = model.get_opcode_cost(X86Opcode::ADD);
assert_eq!(cost.cost, 1);
}
#[test]
fn test_get_opcode_cost_div() {
let model = X86CostModel::new(X86SchedModelKind::SkylakeClient);
let cost = model.get_opcode_cost(X86Opcode::DIV);
assert_eq!(cost.cost, 26);
}
#[test]
fn test_get_opcode_cost_fma() {
let model = X86CostModel::new(X86SchedModelKind::SkylakeClient);
let cost = model.get_opcode_cost(X86Opcode::VFMADD213PS);
assert_eq!(cost.cost, 4); }
#[test]
fn test_get_memory_load_cost() {
let model = X86CostModel::new(X86SchedModelKind::IceLake);
assert_eq!(model.get_memory_load_cost(0).cost, 5); assert_eq!(model.get_memory_load_cost(1).cost, 13); assert_eq!(model.get_memory_load_cost(2).cost, 54); assert_eq!(model.get_memory_load_cost(3).cost, 200); }
#[test]
fn test_get_reduction_cost_fast() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_reduction_cost(ElementType::F32, VectorWidth::V128, true);
assert_eq!(cost.cost, model.simd_hadd_latency);
}
#[test]
fn test_get_reduction_cost_slow() {
let model = X86CostModel::new(X86SchedModelKind::SkylakeClient);
let cost = model.get_reduction_cost(ElementType::F32, VectorWidth::V256, false);
assert_eq!(cost.cost, model.simd_alu_latency * 3);
}
#[test]
fn test_get_shuffle_cost_cross_lane() {
let model = X86CostModel::new(X86SchedModelKind::IceLake);
let within = model.get_shuffle_cost(VectorWidth::V256, false);
let cross = model.get_shuffle_cost(VectorWidth::V256, true);
assert_eq!(cross.cost, within.cost * 2);
}
#[test]
fn test_get_gather_cost_no_support() {
let model = X86CostModel::new(X86SchedModelKind::MeteorLakeEcore);
let cost = model.get_gather_cost(ElementType::F32, VectorWidth::V256);
assert_eq!(cost, InstructionCost::EXPENSIVE);
}
#[test]
fn test_get_gather_cost_with_support() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_gather_cost(ElementType::F32, VectorWidth::V512);
assert_eq!(cost.cost, model.gather_latency);
}
#[test]
fn test_get_masked_op_cost_no_avx512() {
let model = X86CostModel::new(X86SchedModelKind::SkylakeClient);
let base = InstructionCost::new(10);
let cost = model.get_masked_op_cost(base);
assert_eq!(cost.cost, 10); }
#[test]
fn test_get_masked_op_cost_with_avx512() {
let model = X86CostModel::new(X86SchedModelKind::IceLake);
let base = InstructionCost::new(10);
let cost = model.get_masked_op_cost(base);
assert_eq!(cost.cost, 10); }
#[test]
fn test_get_switch_cost_small() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_switch_cost(2);
assert_eq!(cost.cost, model.branch_cost * 2);
}
#[test]
fn test_get_switch_cost_jump_table() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_switch_cost(5);
assert!(cost.cost > model.branch_cost);
}
#[test]
fn test_get_address_computation_cost_free() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_address_computation_cost(1);
assert!(cost.is_free());
}
#[test]
fn test_get_scalarization_overhead() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_scalarization_overhead(ElementType::F32, VectorWidth::V512);
assert_eq!(cost.cost, 4 * 16);
}
#[test]
fn test_get_reg_move_cost_gpr_simd() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let gpr_to_gpr = model.get_reg_move_cost(false);
let gpr_to_simd = model.get_reg_move_cost(true);
assert!(gpr_to_simd.cost > gpr_to_gpr.cost);
}
#[test]
fn test_type_legalization_cost_same() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_type_legalization_cost(32, 32);
assert!(cost.is_free());
}
#[test]
fn test_type_legalization_cost_widen() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_type_legalization_cost(32, 64);
assert_eq!(cost.cost, model.simple_alu_latency * 2);
}
#[test]
fn test_tti_default() {
let tti = X86TTIDeep::default();
assert_eq!(tti.model.kind, X86SchedModelKind::Zen5);
}
#[test]
fn test_tti_for_kind() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert_eq!(tti.model.kind, X86SchedModelKind::SkylakeClient);
}
#[test]
fn test_tti_max_vector_width() {
let tti_skl = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert_eq!(tti_skl.get_max_vector_width(), 256);
let tti_gnr = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
assert_eq!(tti_gnr.get_max_vector_width(), 512);
}
#[test]
fn test_tti_use_masked_memops() {
let tti_skl = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert!(!tti_skl.use_masked_memops());
let tti_gnr = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
assert!(tti_gnr.use_masked_memops());
}
#[test]
fn test_should_vectorize_small_count() {
let tti = X86TTIDeep::default();
assert!(!tti.should_vectorize(ElementType::F32, 1));
assert!(tti.should_vectorize(ElementType::F32, 8));
}
#[test]
fn test_get_vectorized_loop_cost() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vectorized_loop_cost(ElementType::F32, 32, true, true, 2);
assert!(cost.cost > 0);
}
#[test]
fn test_cache_sizes() {
let tti = X86TTIDeep::default();
assert_eq!(tti.get_cache_line_size(), 64);
assert!(tti.get_l1_cache_size() > 0);
assert!(tti.get_l2_cache_size() > 0);
assert!(tti.get_l3_cache_size() > 0);
}
#[test]
fn test_make_tti_functions() {
let tti = make_x86_tti(X86SchedModelKind::Zen4);
assert_eq!(tti.model.kind, X86SchedModelKind::Zen4);
let model = make_x86_cost_model(X86SchedModelKind::IceLake);
assert_eq!(model.kind, X86SchedModelKind::IceLake);
}
#[test]
fn test_all_models_create() {
let kinds = [
X86SchedModelKind::SkylakeClient,
X86SchedModelKind::IceLake,
X86SchedModelKind::AlderLakePcore,
X86SchedModelKind::GraniteRapids,
X86SchedModelKind::Zen3,
X86SchedModelKind::Zen4,
X86SchedModelKind::Zen5,
X86SchedModelKind::MeteorLakePcore,
X86SchedModelKind::MeteorLakeEcore,
X86SchedModelKind::LunarLakePcore,
X86SchedModelKind::LunarLakeEcore,
X86SchedModelKind::ArrowLakePcore,
X86SchedModelKind::SierraForest,
X86SchedModelKind::ClearwaterForest,
X86SchedModelKind::Zen5c,
X86SchedModelKind::Zen6,
X86SchedModelKind::Zen6c,
];
for kind in &kinds {
let model = X86CostModel::new(*kind);
assert_eq!(model.kind, *kind);
assert!(model.simple_alu_latency > 0);
}
}
#[test]
fn test_get_intrinsic_cost_known() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_intrinsic_cost(500);
assert_eq!(cost.cost, model.simd_alu_latency);
}
#[test]
fn test_get_intrinsic_cost_math() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_intrinsic_cost(1000);
assert_eq!(cost, InstructionCost::EXPENSIVE);
}
#[test]
fn test_get_interleaved_access_cost() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_interleaved_access_cost(ElementType::F32, VectorWidth::V256, 2, true);
assert!(cost.cost > 0);
}
}
#[test]
fn test_all_uarch_models_have_reasonable_costs() {
let kinds = [
X86SchedModelKind::SkylakeClient,
X86SchedModelKind::IceLake,
X86SchedModelKind::AlderLakePcore,
X86SchedModelKind::GraniteRapids,
X86SchedModelKind::Zen3,
X86SchedModelKind::Zen4,
X86SchedModelKind::Zen5,
];
for kind in &kinds {
let model = X86CostModel::new(*kind);
assert!(model.simple_alu_latency >= 1);
assert!(model.imul_latency >= 2);
assert!(model.idiv_latency >= 10);
assert!(model.l1_load_latency >= 3);
assert!(model.l2_load_latency >= 10);
assert!(model.l3_load_latency >= 30);
assert!(model.mem_load_latency >= 100);
}
}
#[test]
fn test_tti_vector_load_cost() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vector_load_cost(ElementType::F32, VectorWidth::V256, false);
assert!(cost.cost > 0);
}
#[test]
fn test_tti_vector_load_masked() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::IceLake);
let cost = tti.get_vector_load_cost(ElementType::F32, VectorWidth::V512, true);
assert!(cost.cost > 0);
}
#[test]
fn test_tti_vector_store_cost() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen4);
let cost = tti.get_vector_store_cost(ElementType::F64, VectorWidth::V256, false);
assert!(cost.cost > 0);
}
#[test]
fn test_tti_vector_store_masked() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
let cost = tti.get_vector_store_cost(ElementType::F64, VectorWidth::V512, true);
assert!(cost.cost > 0);
}
#[test]
fn test_should_vectorize_edge_cases() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
assert!(!tti.should_vectorize(ElementType::F32, 1));
assert!(tti.should_vectorize(ElementType::F64, 8));
assert!(tti.should_vectorize(ElementType::I1, 512));
}
#[test]
fn test_preferred_vector_width_minimization() {
let mut tti = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
assert_eq!(tti.get_preferred_vector_width(ElementType::F32), 512);
tti.prefer_width_minimization = true;
assert_eq!(tti.get_preferred_vector_width(ElementType::F32), 256);
}
#[test]
fn test_num_vector_elements() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
assert_eq!(tti.get_num_vector_elements(ElementType::F32), 16);
assert_eq!(tti.get_num_vector_elements(ElementType::F64), 8);
assert_eq!(tti.get_num_vector_elements(ElementType::I8), 64);
}
#[test]
fn test_num_vector_elements_skl() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert_eq!(tti.get_num_vector_elements(ElementType::F32), 8);
}
#[test]
fn test_vectorized_loop_cost_basic() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vectorized_loop_cost(ElementType::F32, 64, false, false, 1);
assert!(cost.cost > 0);
let cost2 = tti.get_vectorized_loop_cost(ElementType::F32, 64, true, true, 2);
assert!(cost2.cost > cost.cost);
}
#[test]
fn test_use_masked_memops_all_models() {
let tti_skl = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert!(!tti_skl.use_masked_memops());
let tti_gnr = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
assert!(tti_gnr.use_masked_memops());
let mut tti_gnr2 = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
tti_gnr2.enable_masked_memops = false;
assert!(!tti_gnr2.use_masked_memops());
}
#[test]
fn test_interleaved_access_cost_varying_stride() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost2 = model.get_interleaved_access_cost(ElementType::F32, VectorWidth::V256, 2, true);
let cost4 = model.get_interleaved_access_cost(ElementType::F32, VectorWidth::V256, 4, true);
assert!(cost4.cost >= cost2.cost);
}
#[test]
fn test_scalarization_cost_zen3() {
let model = X86CostModel::new(X86SchedModelKind::Zen3);
let cost = model.get_scalarization_overhead(ElementType::F32, VectorWidth::V256);
assert_eq!(cost.cost, 4 * 8);
}
#[test]
fn test_type_legalization_narrow() {
let model = X86CostModel::new(X86SchedModelKind::IceLake);
let cost = model.get_type_legalization_cost(64, 32);
assert_eq!(cost.cost, model.simple_alu_latency * 2);
}
#[test]
fn test_type_legalization_widen() {
let model = X86CostModel::new(X86SchedModelKind::IceLake);
let cost = model.get_type_legalization_cost(32, 64);
assert_eq!(cost.cost, model.simple_alu_latency * 2);
}
#[test]
fn test_arithmetic_cost_i8_vector() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_arithmetic_cost(ElementType::I8, VectorWidth::V128);
assert_eq!(cost.cost, model.simd_alu_latency * 16);
}
#[test]
fn test_arithmetic_cost_f64_scalar() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_arithmetic_cost(ElementType::F64, VectorWidth::Scalar);
assert_eq!(cost.cost, model.simd_alu_latency * 1);
}
#[test]
fn test_gpr_simd_move_cost_all_models() {
let models = [
X86CostModel::new(X86SchedModelKind::Zen5),
X86CostModel::new(X86SchedModelKind::SkylakeClient),
X86CostModel::new(X86SchedModelKind::GraniteRapids),
];
for model in &models {
let gpr = model.get_reg_move_cost(false);
let simd = model.get_reg_move_cost(true);
assert!(simd.cost >= gpr.cost);
}
}
#[test]
fn test_switch_cost_small() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_switch_cost(1);
assert_eq!(cost.cost, model.branch_cost);
}
#[test]
fn test_switch_cost_large() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_switch_cost(20);
assert_eq!(cost.cost, model.branch_cost + model.l2_load_latency + 3);
}
#[test]
fn test_call_cost_tail() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let normal = model.get_call_cost(false);
let tail = model.get_call_cost(true);
assert_eq!(normal.cost, model.call_cost);
assert_eq!(tail.cost, model.call_cost);
}
#[test]
fn test_address_computation_complexity() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
assert!(model.get_address_computation_cost(0).is_free());
assert_eq!(model.get_address_computation_cost(1).cost, 0);
assert_eq!(
model.get_address_computation_cost(2).cost,
model.simple_alu_latency
);
assert_eq!(
model.get_address_computation_cost(4).cost,
model.simple_alu_latency * 2
);
}
#[test]
fn test_get_intrinsic_cost_mem() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_intrinsic_cost(1100);
assert_eq!(cost.cost, model.l1_store_latency);
}
#[test]
fn test_get_intrinsic_cost_atomic() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_intrinsic_cost(1200);
assert_eq!(cost.cost, model.l1_store_latency * 3);
}
#[test]
fn test_gather_scatter_cost_no_support() {
let model = X86CostModel::new(X86SchedModelKind::MeteorLakeEcore);
assert_eq!(
model.get_gather_cost(ElementType::F32, VectorWidth::V256),
InstructionCost::EXPENSIVE
);
assert_eq!(
model.get_scatter_cost(ElementType::F32, VectorWidth::V256),
InstructionCost::EXPENSIVE
);
}
#[test]
fn test_gather_scatter_cost_with_support() {
let model = X86CostModel::new(X86SchedModelKind::IceLake);
let g = model.get_gather_cost(ElementType::F64, VectorWidth::V512);
let s = model.get_scatter_cost(ElementType::F64, VectorWidth::V512);
assert_eq!(g.cost, model.gather_latency);
assert_eq!(s.cost, model.scatter_latency);
}
#[test]
fn test_model_kind_consistency() {
let model = X86CostModel::new(X86SchedModelKind::MeteorLakePcore);
assert!(model.has_avx512);
assert!(model.has_fma);
assert!(model.has_gather_scatter);
let model_e = X86CostModel::new(X86SchedModelKind::MeteorLakeEcore);
assert!(!model_e.has_avx512);
assert!(model_e.has_fma);
assert!(!model_e.has_gather_scatter);
}
#[test]
fn test_opcode_cost_missing_defaults_to_alu() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_opcode_cost(X86Opcode::PSHUFD);
assert_eq!(cost.cost, model.simple_alu_latency);
}
#[test]
fn test_opcode_cost_cmov() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_opcode_cost(X86Opcode::CMOVE);
assert_eq!(cost.cost, model.simple_alu_latency);
}
#[test]
fn test_tti_cache_sizes_by_model() {
let tti_skl = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert_eq!(tti_skl.get_l2_cache_size(), 1024 * 1024);
let tti_ecore = X86TTIDeep::for_kind(X86SchedModelKind::MeteorLakeEcore);
assert_eq!(tti_ecore.get_l2_cache_size(), 2 * 1024 * 1024);
let tti_gnr = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
assert_eq!(tti_gnr.get_l3_cache_size(), 128 * 1024 * 1024);
}
#[test]
fn test_masked_op_cost_multiplier() {
let model = X86CostModel::new(X86SchedModelKind::IceLake);
let base = InstructionCost::new(10);
assert_eq!(model.get_masked_op_cost(base).cost, 10);
let model_skl = X86CostModel::new(X86SchedModelKind::SkylakeClient);
assert_eq!(model_skl.get_masked_op_cost(base).cost, 10);
}
#[test]
fn test_reduction_cost_scalar() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_reduction_cost(ElementType::F32, VectorWidth::Scalar, false);
assert_eq!(cost.cost, 0);
}
#[test]
fn test_shuffle_cost_scalar() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_shuffle_cost(VectorWidth::Scalar, false);
assert_eq!(cost.cost, model.simd_shuffle_latency);
}
#[test]
fn test_zen6_has_lower_latencies() {
let z5 = X86CostModel::new(X86SchedModelKind::Zen5);
let z6 = X86CostModel::new(X86SchedModelKind::Zen6);
assert!(z6.idiv_latency <= z5.idiv_latency);
assert!(z6.mem_load_latency <= z5.mem_load_latency);
}
#[test]
fn test_ecore_higher_latencies() {
let pcore = X86CostModel::new(X86SchedModelKind::MeteorLakePcore);
let ecore = X86CostModel::new(X86SchedModelKind::MeteorLakeEcore);
assert!(ecore.idiv_latency > pcore.idiv_latency);
assert!(ecore.call_cost >= pcore.call_cost);
}
#[test]
fn test_cost_model_each_uarch() {
let test_kinds = [
X86SchedModelKind::SkylakeClient,
X86SchedModelKind::IceLake,
X86SchedModelKind::AlderLakePcore,
X86SchedModelKind::GraniteRapids,
X86SchedModelKind::Zen3,
X86SchedModelKind::Zen4,
X86SchedModelKind::Zen5,
X86SchedModelKind::MeteorLakePcore,
X86SchedModelKind::MeteorLakeEcore,
X86SchedModelKind::LunarLakePcore,
X86SchedModelKind::LunarLakeEcore,
X86SchedModelKind::ArrowLakePcore,
X86SchedModelKind::SierraForest,
X86SchedModelKind::ClearwaterForest,
X86SchedModelKind::Zen5c,
X86SchedModelKind::Zen6,
X86SchedModelKind::Zen6c,
];
for kind in &test_kinds {
let model = X86CostModel::new(*kind);
assert!(model.simple_alu_latency >= 1);
assert!(model.imul_latency >= 2);
assert!(model.idiv_latency >= 10);
assert!(model.branch_cost >= 1);
assert!(model.call_cost >= 2);
assert!(model.ret_cost >= 1);
assert!(model.l1_load_latency >= 3);
assert!(model.l1_store_latency >= 1);
assert!(model.l2_load_latency >= 10);
assert!(model.l3_load_latency >= 30);
assert!(model.mem_load_latency >= 100);
}
}
#[test]
fn test_vector_width_variants() {
assert_eq!(VectorWidth::V64.bits(), 64);
assert_eq!(VectorWidth::V128.bits(), 128);
assert_eq!(VectorWidth::V256.bits(), 256);
assert_eq!(VectorWidth::V512.bits(), 512);
assert_eq!(VectorWidth::Scalar.bits(), 0);
}
#[test]
fn test_element_type_all_sizes() {
assert_eq!(ElementType::I1.size_bits(), 1);
assert_eq!(ElementType::I8.size_bits(), 8);
assert_eq!(ElementType::I16.size_bits(), 16);
assert_eq!(ElementType::I32.size_bits(), 32);
assert_eq!(ElementType::I64.size_bits(), 64);
assert_eq!(ElementType::F16.size_bits(), 16);
assert_eq!(ElementType::F32.size_bits(), 32);
assert_eq!(ElementType::F64.size_bits(), 64);
assert_eq!(ElementType::BF16.size_bits(), 16);
}
#[test]
fn test_element_type_is_floating() {
assert!(ElementType::F16.is_floating());
assert!(ElementType::F32.is_floating());
assert!(ElementType::F64.is_floating());
assert!(ElementType::BF16.is_floating());
assert!(!ElementType::I32.is_floating());
}
#[test]
fn test_instruction_cost_add_saturation() {
let a = InstructionCost::new(u32::MAX - 5);
let b = InstructionCost::new(10);
assert_eq!((a + b).cost, u32::MAX);
}
#[test]
fn test_instruction_cost_mul_saturation() {
let a = InstructionCost::new(u32::MAX / 2 + 1);
assert_eq!((a * 2).cost, u32::MAX);
}
#[test]
fn test_get_opcode_cost_all_alu() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let alu_ops = [
X86Opcode::ADD,
X86Opcode::SUB,
X86Opcode::AND,
X86Opcode::OR,
X86Opcode::XOR,
X86Opcode::NEG,
X86Opcode::NOT,
X86Opcode::LEA,
X86Opcode::TEST,
X86Opcode::CMP,
];
for op in &alu_ops {
let cost = model.get_opcode_cost(*op);
assert_eq!(cost.cost, model.simple_alu_latency, "op {:?}", op);
}
}
#[test]
fn test_get_opcode_cost_shifts() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
for op in &[
X86Opcode::SHL,
X86Opcode::SHR,
X86Opcode::SAR,
X86Opcode::ROL,
X86Opcode::ROR,
] {
let cost = model.get_opcode_cost(*op);
assert_eq!(cost.cost, model.simple_alu_latency);
}
}
#[test]
fn test_get_opcode_cost_moves() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
assert_eq!(
model.get_opcode_cost(X86Opcode::MOV).cost,
model.reg_move_cost
);
assert_eq!(
model.get_opcode_cost(X86Opcode::MOVSX).cost,
model.reg_move_cost
);
assert_eq!(
model.get_opcode_cost(X86Opcode::MOVZX).cost,
model.reg_move_cost
);
}
#[test]
fn test_get_opcode_cost_sse_scalar() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
for op in &[
X86Opcode::ADDSS,
X86Opcode::ADDSD,
X86Opcode::SUBSS,
X86Opcode::SUBSD,
X86Opcode::MINSS,
X86Opcode::MINSD,
X86Opcode::MAXSS,
X86Opcode::MAXSD,
] {
assert_eq!(model.get_opcode_cost(*op).cost, model.simd_alu_latency);
}
}
#[test]
fn test_get_opcode_cost_sse_div_sqrt() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
assert_eq!(
model.get_opcode_cost(X86Opcode::DIVSS).cost,
model.simd_div_latency
);
assert_eq!(
model.get_opcode_cost(X86Opcode::DIVSD).cost,
model.simd_div_latency
);
assert_eq!(
model.get_opcode_cost(X86Opcode::SQRTSS).cost,
model.simd_div_latency
);
assert_eq!(
model.get_opcode_cost(X86Opcode::SQRTSD).cost,
model.simd_div_latency
);
}
#[test]
fn test_get_opcode_cost_sse_packed_bitwise() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
for op in &[
X86Opcode::ANDPS,
X86Opcode::ANDPD,
X86Opcode::ORPS,
X86Opcode::ORPD,
X86Opcode::XORPS,
X86Opcode::XORPD,
] {
assert_eq!(model.get_opcode_cost(*op).cost, model.simd_alu_latency);
}
}
#[test]
fn test_get_opcode_cost_avx_packed_bitwise() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
for op in &[
X86Opcode::VANDPS,
X86Opcode::VANDPD,
X86Opcode::VORPS,
X86Opcode::VORPD,
X86Opcode::VXORPS,
X86Opcode::VXORPD,
] {
assert_eq!(model.get_opcode_cost(*op).cost, model.simd_alu_latency);
}
}
#[test]
fn test_get_opcode_cost_gather() {
let model = X86CostModel::new(X86SchedModelKind::IceLake);
assert_eq!(
model.get_opcode_cost(X86Opcode::VPGATHERDD).cost,
model.gather_latency
);
assert_eq!(
model.get_opcode_cost(X86Opcode::VPGATHERDQ).cost,
model.gather_latency
);
}
#[test]
fn test_get_opcode_cost_branch() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
assert_eq!(
model.get_opcode_cost(X86Opcode::JMP).cost,
model.branch_cost
);
assert_eq!(
model.get_opcode_cost(X86Opcode::CALL).cost,
model.branch_cost
);
assert_eq!(
model.get_opcode_cost(X86Opcode::RET).cost,
model.branch_cost
);
}
#[test]
fn test_get_opcode_cost_fma_no_support() {
let mut model = X86CostModel::new(X86SchedModelKind::SkylakeClient);
model.has_fma = false;
let cost = model.get_opcode_cost(X86Opcode::VFMADD213PS);
assert_eq!(cost.cost, model.simd_mul_latency + model.simd_alu_latency);
}
#[test]
fn test_tti_default_values() {
let tti = X86TTIDeep::default();
assert_eq!(tti.cache_line_size, 64);
assert_eq!(tti.max_interleave_factor, 8);
assert!(!tti.prefer_width_minimization);
assert!(tti.enable_interleaved_access);
assert!(tti.enable_masked_memops);
assert_eq!(tti.min_vectorization_factor, 0);
assert_eq!(tti.max_vectorization_factor, 0);
}
#[test]
fn test_tti_vector_load_cost_scalar() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vector_load_cost(ElementType::F32, VectorWidth::Scalar, false);
assert!(cost.cost > 0);
}
#[test]
fn test_cost_model_zen3_specific() {
let model = X86CostModel::new(X86SchedModelKind::Zen3);
assert!(!model.has_avx512);
assert!(model.has_fma);
assert_eq!(model.gpr_simd_move_cost, 3);
}
#[test]
fn test_cost_model_clearwater_forest() {
let model = X86CostModel::new(X86SchedModelKind::ClearwaterForest);
assert!(model.has_avx512);
assert_eq!(model.simd_alu_latency, 1);
}
#[test]
fn test_cost_model_sierra_forest() {
let model = X86CostModel::new(X86SchedModelKind::SierraForest);
assert!(model.has_avx512);
assert!(model.idiv_latency >= 15);
}
#[test]
fn test_tti_setters() {
let mut tti = X86TTIDeep::default();
tti.cache_line_size = 128;
assert_eq!(tti.get_cache_line_size(), 128);
tti.prefer_width_minimization = true;
assert!(tti.prefer_width_minimization);
tti.max_interleave_factor = 16;
assert_eq!(tti.max_interleave_factor, 16);
}
#[test]
fn test_tti_min_max_vectorization_factor() {
let mut tti = X86TTIDeep::default();
assert_eq!(tti.min_vectorization_factor, 0);
assert_eq!(tti.max_vectorization_factor, 0);
tti.min_vectorization_factor = 2;
tti.max_vectorization_factor = 16;
assert_eq!(tti.min_vectorization_factor, 2);
assert_eq!(tti.max_vectorization_factor, 16);
}
#[test]
fn test_too_wide_type_should_not_vectorize() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert!(!tti.should_vectorize(ElementType::I64, 1));
}
#[test]
fn test_should_vectorize_basic() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
assert!(tti.should_vectorize(ElementType::I8, 128));
assert!(tti.should_vectorize(ElementType::I32, 32));
}
#[test]
fn test_vectorized_loop_cost_no_ops() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vectorized_loop_cost(ElementType::F32, 32, false, false, 0);
assert_eq!(cost.cost, tti.model.vectorization_overhead);
}
#[test]
fn test_vectorized_loop_cost_load_only() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vectorized_loop_cost(ElementType::F32, 32, true, false, 0);
assert!(cost.cost > tti.model.vectorization_overhead);
}
#[test]
fn test_get_opcode_cost_movabs() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
assert_eq!(
model.get_opcode_cost(X86Opcode::MOVABS).cost,
model.reg_move_cost + 1
);
}
#[test]
fn test_get_opcode_cost_hadd_hsub() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
assert_eq!(
model.get_opcode_cost(X86Opcode::HADDPS).cost,
model.simd_hadd_latency
);
assert_eq!(
model.get_opcode_cost(X86Opcode::HSUBPD).cost,
model.simd_hadd_latency
);
}
#[test]
fn test_get_memory_load_cost_default() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_memory_load_cost(99);
assert_eq!(cost.cost, model.mem_load_latency);
}
#[test]
fn test_fma_cost_comparison() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let fma_cost = model.get_opcode_cost(X86Opcode::VFMADD213PS);
assert_eq!(fma_cost.cost, model.simd_fma_latency);
}
#[test]
fn test_scatter_cost_no_support_scalar() {
let model = X86CostModel::new(X86SchedModelKind::MeteorLakeEcore);
let cost = model.get_scatter_cost(ElementType::F32, VectorWidth::Scalar);
assert_eq!(cost, InstructionCost::EXPENSIVE);
}
#[test]
fn test_get_intrinsic_cost_unknown() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_intrinsic_cost(99999);
assert_eq!(cost.cost, model.simple_alu_latency);
}
#[test]
fn test_num_vector_elements_edge() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert_eq!(tti.get_num_vector_elements(ElementType::I8), 32);
}
#[test]
fn test_uarch_model_has_avx512_consistency() {
let avx512_models = [
X86SchedModelKind::IceLake,
X86SchedModelKind::GraniteRapids,
X86SchedModelKind::Zen4,
X86SchedModelKind::Zen5,
];
for kind in &avx512_models {
let model = X86CostModel::new(*kind);
assert!(model.has_avx512, "{:?} should have AVX-512", kind);
}
let non_avx512 = [
X86SchedModelKind::SkylakeClient,
X86SchedModelKind::Zen3,
X86SchedModelKind::MeteorLakeEcore,
X86SchedModelKind::LunarLakeEcore,
];
for kind in &non_avx512 {
let model = X86CostModel::new(*kind);
assert!(!model.has_avx512, "{:?} should NOT have AVX-512", kind);
}
}
#[test]
fn test_uarch_model_has_fma_consistency() {
let fma_models = [
X86SchedModelKind::SkylakeClient,
X86SchedModelKind::IceLake,
X86SchedModelKind::AlderLakePcore,
X86SchedModelKind::GraniteRapids,
X86SchedModelKind::Zen3,
X86SchedModelKind::Zen4,
X86SchedModelKind::Zen5,
];
for kind in &fma_models {
let model = X86CostModel::new(*kind);
assert!(model.has_fma, "{:?} should have FMA", kind);
}
}
impl X86CostModel {
pub fn get_full_reduction_cost(
&self,
elem: ElementType,
width: VectorWidth,
) -> InstructionCost {
let num_elements = width.bits() / elem.size_bits();
if num_elements <= 1 {
return InstructionCost::FREE;
}
let mut remaining = num_elements;
let mut total_latency = 0u32;
while remaining > 1 {
let half = remaining / 2;
total_latency += self.simd_alu_latency;
remaining = half + (remaining % 2);
}
InstructionCost::new(total_latency)
}
pub fn get_strided_access_cost(
&self,
elem: ElementType,
width: VectorWidth,
stride: u32,
is_load: bool,
) -> InstructionCost {
let num_elements = width.bits() / elem.size_bits();
let base_cost = if is_load {
self.l1_load_latency
} else {
self.l1_store_latency
};
let stride_penalty = if stride > 1 {
(stride as u32).min(4)
} else {
0
};
InstructionCost::new(base_cost * num_elements + stride_penalty * 2)
}
pub fn supports_masked_gather_scatter(&self) -> bool {
self.has_avx512 && self.has_gather_scatter
}
pub fn has_full_rate_512(&self) -> bool {
matches!(
self.kind,
X86SchedModelKind::GraniteRapids
| X86SchedModelKind::Zen4
| X86SchedModelKind::Zen5
| X86SchedModelKind::Zen6
)
}
pub fn is_big_core(&self) -> bool {
!matches!(
self.kind,
X86SchedModelKind::MeteorLakeEcore
| X86SchedModelKind::LunarLakeEcore
| X86SchedModelKind::SierraForest
| X86SchedModelKind::ClearwaterForest
| X86SchedModelKind::Zen5c
| X86SchedModelKind::Zen6c
)
}
pub fn is_dense_core(&self) -> bool {
!self.is_big_core()
}
}
impl X86TTIDeep {
pub fn get_gep_cost(&self, num_nonzero_indices: u32) -> InstructionCost {
self.model.get_address_computation_cost(num_nonzero_indices)
}
pub fn is_unroll_profitable(&self, trip_count: u32, loop_body_cost: u32) -> bool {
if trip_count <= 8 && loop_body_cost <= 20 {
return true;
}
if trip_count <= 16 && loop_body_cost <= 10 {
return true;
}
false
}
pub fn get_loop_cost(
&self,
iterations: u32,
per_iter_cost: InstructionCost,
) -> InstructionCost {
let branch_cost = self.model.get_branch_cost();
InstructionCost::new((per_iter_cost.cost + branch_cost.cost) * iterations)
}
pub fn get_interleaving_factor(&self, elem: ElementType) -> u32 {
let width = self.get_max_vector_width();
let vec_elems = width / elem.size_bits();
if vec_elems >= 16 {
2 } else if vec_elems >= 8 {
2
} else {
1 }
}
}
#[cfg(test)]
mod extra_tti_tests {
use super::*;
#[test]
fn test_full_reduction_cost() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_full_reduction_cost(ElementType::F32, VectorWidth::V256);
assert_eq!(cost.cost, model.simd_alu_latency * 3);
}
#[test]
fn test_full_reduction_cost_scalar() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_full_reduction_cost(ElementType::F32, VectorWidth::Scalar);
assert!(cost.is_free());
}
#[test]
fn test_strided_access_cost() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let unit_stride =
model.get_strided_access_cost(ElementType::F32, VectorWidth::V256, 1, true);
let stride4 = model.get_strided_access_cost(ElementType::F32, VectorWidth::V256, 4, true);
assert!(stride4.cost > unit_stride.cost);
}
#[test]
fn test_supports_masked_gather_scatter() {
let ice = X86CostModel::new(X86SchedModelKind::IceLake);
assert!(ice.supports_masked_gather_scatter());
let skl = X86CostModel::new(X86SchedModelKind::SkylakeClient);
assert!(!skl.supports_masked_gather_scatter());
let ecore = X86CostModel::new(X86SchedModelKind::MeteorLakeEcore);
assert!(!ecore.supports_masked_gather_scatter());
}
#[test]
fn test_has_full_rate_512() {
assert!(X86CostModel::new(X86SchedModelKind::GraniteRapids).has_full_rate_512());
assert!(X86CostModel::new(X86SchedModelKind::Zen5).has_full_rate_512());
assert!(!X86CostModel::new(X86SchedModelKind::IceLake).has_full_rate_512());
}
#[test]
fn test_is_big_core() {
assert!(X86CostModel::new(X86SchedModelKind::Zen5).is_big_core());
assert!(!X86CostModel::new(X86SchedModelKind::MeteorLakeEcore).is_big_core());
assert!(!X86CostModel::new(X86SchedModelKind::Zen5c).is_big_core());
}
#[test]
fn test_is_dense_core() {
assert!(!X86CostModel::new(X86SchedModelKind::Zen5).is_dense_core());
assert!(X86CostModel::new(X86SchedModelKind::MeteorLakeEcore).is_dense_core());
assert!(X86CostModel::new(X86SchedModelKind::ClearwaterForest).is_dense_core());
}
#[test]
fn test_gep_cost() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
assert!(tti.get_gep_cost(0).is_free());
assert!(tti.get_gep_cost(2).cost > 0);
}
#[test]
fn test_is_unroll_profitable() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
assert!(tti.is_unroll_profitable(4, 5));
assert!(!tti.is_unroll_profitable(100, 50));
}
#[test]
fn test_get_loop_cost() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_loop_cost(10, InstructionCost::new(4));
assert_eq!(cost.cost, (4 + tti.model.branch_cost) * 10);
}
#[test]
fn test_get_interleaving_factor() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
assert_eq!(tti.get_interleaving_factor(ElementType::F32), 2);
assert_eq!(tti.get_interleaving_factor(ElementType::F64), 2);
}
#[test]
fn test_get_interleaving_factor_skl() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert_eq!(tti.get_interleaving_factor(ElementType::F32), 2);
assert_eq!(tti.get_interleaving_factor(ElementType::I8), 2);
}
#[test]
fn test_strided_access_store() {
let model = X86CostModel::new(X86SchedModelKind::Zen5);
let cost = model.get_strided_access_cost(ElementType::I32, VectorWidth::V128, 2, false);
assert!(cost.cost > 0);
}
}
#[cfg(test)]
mod integration_tti_tests {
use super::*;
#[test]
fn test_simple_loop_cost_skylake() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
let cost = tti.get_vectorized_loop_cost(ElementType::F32, 256, true, true, 1);
assert!(cost.cost > 0);
assert!(cost.cost < 500); }
#[test]
fn test_simple_loop_cost_zen5() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vectorized_loop_cost(ElementType::F32, 256, true, true, 1);
assert!(cost.cost > 0);
}
#[test]
fn test_avx512_vs_avx2_cost() {
let tti_skl = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
let tti_icx = X86TTIDeep::for_kind(X86SchedModelKind::IceLake);
let skl_cost = tti_skl.get_vectorized_loop_cost(ElementType::F64, 256, true, true, 1);
let icx_cost = tti_icx.get_vectorized_loop_cost(ElementType::F64, 256, true, true, 1);
assert!(skl_cost.cost > 0);
assert!(icx_cost.cost > 0);
}
#[test]
fn test_scalar_vs_vector_cost() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let vec_cost = tti.get_vectorized_loop_cost(ElementType::F32, 64, true, true, 2);
let scalar_per_iter = InstructionCost::new(
tti.model.l1_load_latency + tti.model.simd_alu_latency * 2 + tti.model.l1_store_latency,
);
let scalar_cost =
InstructionCost::new(scalar_per_iter.cost * 64 + tti.model.branch_cost * 64);
assert!(vec_cost.cost < scalar_cost.cost);
}
#[test]
fn test_memory_bound_loop_cost() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vectorized_loop_cost(ElementType::F32, 1024, true, true, 0);
assert!(cost.cost > 0);
}
#[test]
fn test_compute_bound_loop_cost() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
let cost = tti.get_vectorized_loop_cost(ElementType::F64, 256, false, false, 10);
assert!(cost.cost > 0);
}
#[test]
fn test_uarch_upgrade_improves_cost() {
let z3 = X86CostModel::new(X86SchedModelKind::Zen3);
let z5 = X86CostModel::new(X86SchedModelKind::Zen5);
assert!(z5.idiv_latency <= z3.idiv_latency);
assert!(z5.l1_load_latency <= z3.l1_load_latency);
}
#[test]
fn test_dense_vs_performance_core() {
let pcore = X86CostModel::new(X86SchedModelKind::Zen5);
let dense = X86CostModel::new(X86SchedModelKind::Zen5c);
assert!(pcore.idiv_latency <= dense.idiv_latency);
assert!(pcore.mem_load_latency <= dense.mem_load_latency);
}
#[test]
fn test_all_models_return_valid_gather_cost() {
let kinds = [
X86SchedModelKind::SkylakeClient,
X86SchedModelKind::IceLake,
X86SchedModelKind::GraniteRapids,
X86SchedModelKind::Zen3,
X86SchedModelKind::Zen5,
X86SchedModelKind::MeteorLakeEcore,
];
for kind in &kinds {
let model = X86CostModel::new(*kind);
let cost = model.get_gather_cost(ElementType::F32, VectorWidth::V256);
assert!(cost.cost > 0, "{:?} gather cost invalid", kind);
}
}
#[test]
fn test_all_models_return_valid_scatter_cost() {
let kinds = [
X86SchedModelKind::SkylakeClient,
X86SchedModelKind::IceLake,
X86SchedModelKind::GraniteRapids,
X86SchedModelKind::Zen3,
X86SchedModelKind::Zen5,
X86SchedModelKind::MeteorLakeEcore,
];
for kind in &kinds {
let model = X86CostModel::new(*kind);
let cost = model.get_scatter_cost(ElementType::F32, VectorWidth::V256);
assert!(cost.cost > 0, "{:?} scatter cost invalid", kind);
}
}
#[test]
fn test_vectorization_decision_fp16() {
let tti_zen5 = X86TTIDeep::for_kind(X86SchedModelKind::Zen5);
assert!(tti_zen5.should_vectorize(ElementType::F16, 32));
let tti_skl = X86TTIDeep::for_kind(X86SchedModelKind::SkylakeClient);
assert!(tti_skl.should_vectorize(ElementType::F16, 16));
}
#[test]
fn test_vectorization_decision_bf16() {
let tti = X86TTIDeep::for_kind(X86SchedModelKind::GraniteRapids);
assert!(tti.should_vectorize(ElementType::BF16, 32));
}
#[test]
fn test_make_x86_tti_all_kinds() {
let kinds = [
X86SchedModelKind::SkylakeClient,
X86SchedModelKind::IceLake,
X86SchedModelKind::AlderLakePcore,
X86SchedModelKind::GraniteRapids,
X86SchedModelKind::Zen3,
X86SchedModelKind::Zen4,
X86SchedModelKind::Zen5,
X86SchedModelKind::MeteorLakePcore,
X86SchedModelKind::MeteorLakeEcore,
X86SchedModelKind::LunarLakePcore,
X86SchedModelKind::LunarLakeEcore,
X86SchedModelKind::ArrowLakePcore,
X86SchedModelKind::SierraForest,
X86SchedModelKind::ClearwaterForest,
X86SchedModelKind::Zen5c,
X86SchedModelKind::Zen6,
X86SchedModelKind::Zen6c,
];
for kind in &kinds {
let tti = make_x86_tti(*kind);
assert_eq!(tti.model.kind, *kind);
}
}
}