use crate::heuristics::{INLINE_FREQUENCY_CLAMP, INLINE_GROWTH_SQUARING_BOUND};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Hints {
pub enables: bool,
pub asks: bool,
pub declared: bool,
}
impl Hints {
#[must_use]
pub fn limit(self, base: u32, percent: u32) -> u64 {
let base = u64::from(base);
let percent = u64::from(percent);
match (self.enables, self.asks) {
(true, true) => base * (percent * percent).min(1_000_000) / 100,
(true, false) | (false, true) => base * percent / 100,
(false, false) => base,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Call {
pub growth: i64,
pub saved: f64,
pub frequency: Option<f64>,
pub depth: u32,
pub overall: i64,
pub caller: i64,
pub hints: Hints,
}
const SHRINKS: f64 = -1.0e12;
#[must_use]
pub fn overall(growth: i64) -> f64 {
let bound = i64::from(crate::param!(INLINE_GROWTH_SQUARING_BOUND));
if growth < bound { (growth * growth) as f64 } else { (growth + bound * bound - bound) as f64 }
}
#[must_use]
pub fn badness(call: &Call) -> f64 {
let base = if call.growth <= 0 {
SHRINKS + call.growth as f64
} else if let Some(frequency) = call.frequency {
let frequency = frequency.clamp(1.0, most_frequent());
let saved = if call.saved > 0.0 { call.saved } else { 1.0 / 256.0 };
let mut denominator = call.growth as f64 * (call.caller + call.growth).max(1) as f64;
if call.overall > 0 {
denominator *= overall(call.overall);
}
-(saved * frequency) / denominator
} else {
call.growth as f64 / f64::from(1u32 << call.depth.min(8))
};
let mut badness = base;
if call.hints.enables || call.overall <= 0 {
badness = favour(badness, 2);
}
if call.hints.asks {
badness = favour(badness, 4);
}
if call.hints.declared {
badness = favour(badness, 3);
}
badness
}
#[must_use]
pub fn big_speedup(
callee: f64,
specialized: f64,
call: f64,
frequency: f64,
caller: f64,
percent: u32,
) -> bool {
let frequency = frequency.clamp(1.0, most_frequent());
let before = callee * frequency + caller;
let after = ((specialized - call) * frequency + caller).max(1.0 / 256.0);
(before - after) * 100.0 > before * f64::from(percent)
}
fn most_frequent() -> f64 {
f64::from(crate::param!(INLINE_FREQUENCY_CLAMP)).max(1.0)
}
fn favour(badness: f64, shift: u32) -> f64 {
let by = f64::from(1u32 << shift);
if badness > 0.0 { badness / by } else { badness * by }
}
#[cfg(test)]
mod tests {
use super::{Call, Hints, badness, big_speedup, overall};
use crate::heuristics::{INLINE_GROWTH_SQUARING_BOUND, INLINE_HINT_PERCENT};
fn call(growth: i64) -> Call {
Call {
growth,
saved: 4.0,
frequency: Some(1.0),
depth: 0,
overall: growth * 2,
caller: 40,
hints: Hints::default(),
}
}
#[test]
fn the_two_pieces_of_the_overall_growth_term_meet_at_the_bound() {
let bound = i64::from(INLINE_GROWTH_SQUARING_BOUND);
assert_eq!(overall(bound - 1), ((bound - 1) * (bound - 1)) as f64);
assert_eq!(overall(bound), (bound * bound) as f64);
assert_eq!(overall(bound + 1), (bound * bound + 1) as f64);
}
#[test]
fn a_call_that_shrinks_the_program_comes_before_any_that_grows_it() {
let mut hot = call(1);
hot.saved = 1000.0;
hot.frequency = Some(100.0);
assert!(badness(&call(0)) < badness(&hot));
assert!(badness(&call(-3)) < badness(&call(0)));
}
#[test]
fn a_call_in_a_loop_comes_before_the_same_call_outside_one() {
let mut inside = call(10);
inside.frequency = Some(10.0);
assert!(badness(&inside) < badness(&call(10)));
}
#[test]
fn a_frequency_is_believed_only_up_to_the_clamp() {
let mut guessed = call(10);
guessed.frequency = Some(100.0);
let mut nested = call(10);
nested.frequency = Some(10_000.0);
assert_eq!(badness(&guessed), badness(&nested));
}
#[test]
fn a_callee_with_one_caller_comes_before_one_with_many() {
let mut one = call(10);
one.overall = 10;
let mut many = call(10);
many.overall = 500;
assert!(badness(&one) < badness(&many));
}
#[test]
fn with_no_frequency_a_loop_halves_the_growth() {
let mut outside = call(16);
outside.frequency = None;
let mut inside = outside;
inside.depth = 2;
assert_eq!(badness(&outside), 16.0);
assert_eq!(badness(&inside), 16.0 / 4.0);
}
#[test]
fn each_hint_moves_a_call_toward_the_front() {
let plain = badness(&call(10));
for hints in [
Hints { enables: true, ..Hints::default() },
Hints { asks: true, ..Hints::default() },
Hints { declared: true, ..Hints::default() },
] {
assert!(badness(&Call { hints, ..call(10) }) < plain, "{hints:?}");
}
}
#[test]
fn a_hint_raises_a_limit_as_gcc_raises_it() {
let none = Hints::default();
let one = Hints { enables: true, ..none };
let both = Hints { enables: true, asks: true, ..none };
assert_eq!(none.limit(15, INLINE_HINT_PERCENT), 15);
assert_eq!(one.limit(15, INLINE_HINT_PERCENT), 30);
assert_eq!(both.limit(15, INLINE_HINT_PERCENT), 6000);
assert_eq!(both.limit(70, 600), 252_000);
}
#[test]
fn a_big_speedup_is_measured_against_the_caller_too() {
assert!(big_speedup(40.0, 10.0, 3.0, 1.0, 10.0, 30));
assert!(!big_speedup(40.0, 10.0, 3.0, 1.0, 1000.0, 30));
}
}