use std::collections::HashMap;
use synth_core::wcet::{WcetDecline, WcetFunction, WcetIntermediate};
pub fn compose(
intermediates: &[WcetIntermediate],
index_by_func_label: &HashMap<String, usize>,
) -> Vec<WcetFunction> {
let n = intermediates.len();
#[derive(Clone)]
enum State {
Pending,
OnStack,
Bounded(u64),
Declined(WcetDecline),
}
let mut state = vec![State::Pending; n];
fn resolve(
start: usize,
intermediates: &[WcetIntermediate],
index_by_func_label: &HashMap<String, usize>,
state: &mut [State],
recursion_hits: &mut [bool],
) {
let mut work: Vec<(usize, bool)> = vec![(start, false)];
while let Some((i, second)) = work.pop() {
match &state[i] {
State::Bounded(_) | State::Declined(_) => continue,
_ => {}
}
let inter = &intermediates[i];
let WcetIntermediate::Composable {
own_cycles,
call_sites,
recursion_cert,
..
} = inter
else {
if let WcetIntermediate::Declined { reason, .. } = inter {
state[i] = State::Declined(reason.clone());
}
continue;
};
let self_label: Option<&str> = recursion_cert.as_ref().map(|c| c.self_label.as_str());
let is_self_site = |site: &synth_core::wcet::WcetCallSite| {
Some(site.callee_label.as_str()) == self_label
};
if !second {
state[i] = State::OnStack;
work.push((i, true));
for site in call_sites {
if is_self_site(site) {
continue;
}
match index_by_func_label.get(&site.callee_label) {
None => {} Some(&callee) => match &state[callee] {
State::OnStack => {
recursion_hits[callee] = true;
}
State::Pending => work.push((callee, false)),
State::Bounded(_) | State::Declined(_) => {}
},
}
}
continue;
}
if recursion_hits[i] && recursion_cert.is_none() {
state[i] = State::Declined(WcetDecline::Recursion);
continue;
}
let mut frame_cost: u128 = *own_cycles as u128;
let mut verdict: Option<WcetDecline> = None;
for site in call_sites {
if is_self_site(site) {
continue; }
let callee = match index_by_func_label.get(&site.callee_label) {
None => {
verdict = Some(WcetDecline::Call);
break;
}
Some(&c) => c,
};
match &state[callee] {
State::Bounded(c) => {
frame_cost =
frame_cost.saturating_add(site.multiplier.saturating_mul(*c as u128));
}
State::Declined(WcetDecline::Recursion) => {
verdict = Some(WcetDecline::Recursion);
break;
}
State::Declined(_) => {
verdict = Some(WcetDecline::CalleeUnbounded);
break;
}
State::OnStack | State::Pending => {
verdict = Some(WcetDecline::Recursion);
break;
}
}
}
let total: u128 = match (&verdict, recursion_cert.as_ref()) {
(None, Some(cert)) => {
let frames = (cert.max_depth as u128).saturating_add(1);
frame_cost.saturating_mul(frames)
}
_ => frame_cost,
};
state[i] = match verdict {
Some(reason) => State::Declined(reason),
None => match u64::try_from(total) {
Ok(c) => State::Bounded(c),
Err(_) => State::Declined(WcetDecline::Recursion),
},
};
}
}
let mut recursion_hits = vec![false; n];
for i in 0..n {
if matches!(state[i], State::Pending) {
resolve(
i,
intermediates,
index_by_func_label,
&mut state,
&mut recursion_hits,
);
}
}
intermediates
.iter()
.enumerate()
.map(|(i, inter)| match &state[i] {
State::Bounded(cycles) => {
let (name, instr_count, loops, recursion, hint_rejections) = match inter {
WcetIntermediate::Composable {
name,
instr_count,
loops,
recursion_cert,
hint_rejections,
..
} => (
name.clone(),
*instr_count,
loops.clone(),
recursion_cert
.as_ref()
.map(|c| synth_core::wcet::WcetRecursionBound {
max_depth: c.max_depth,
frame_count: c.max_depth.saturating_add(1),
hint: c.hint,
}),
hint_rejections.clone(),
),
WcetIntermediate::Declined { name, .. } => {
(name.clone(), 0, Vec::new(), None, Vec::new())
}
};
WcetFunction::Bounded {
name,
cycles: *cycles,
instr_count,
loops,
recursion,
hint_rejections,
hint_key: None,
}
}
State::Declined(reason) => {
let hint_rejections = match inter {
WcetIntermediate::Composable {
hint_rejections, ..
}
| WcetIntermediate::Declined {
hint_rejections, ..
} => hint_rejections.clone(),
};
let site = match inter {
WcetIntermediate::Declined { site, .. } => site.clone(),
WcetIntermediate::Composable { .. } => None,
};
match site {
Some(s) if hint_rejections.is_empty() => {
WcetFunction::declined_at(inter.name(), reason.clone(), s.op, s.offset)
}
_ => WcetFunction::declined_with_rejections(
inter.name(),
reason.clone(),
hint_rejections,
),
}
}
State::Pending | State::OnStack => {
WcetFunction::declined(inter.name(), WcetDecline::Recursion)
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use synth_core::wcet::{WcetCallSite, WcetIntermediate};
fn composable(name: &str, own: u64, sites: &[(&str, u128)]) -> WcetIntermediate {
WcetIntermediate::Composable {
name: name.to_string(),
own_cycles: own,
instr_count: 1,
call_sites: sites
.iter()
.map(|(l, m)| WcetCallSite {
callee_label: (*l).to_string(),
multiplier: *m,
})
.collect(),
loops: Vec::new(),
recursion_cert: None,
hint_rejections: Vec::new(),
}
}
fn recursive(
name: &str,
own: u64,
sites: &[(&str, u128)],
self_label: &str,
max_depth: u64,
) -> WcetIntermediate {
let WcetIntermediate::Composable {
name,
own_cycles,
instr_count,
call_sites,
loops,
hint_rejections,
..
} = composable(name, own, sites)
else {
unreachable!()
};
WcetIntermediate::Composable {
name,
own_cycles,
instr_count,
call_sites,
loops,
recursion_cert: Some(synth_core::wcet::WcetRecursionCert {
self_label: self_label.to_string(),
max_depth,
hint: max_depth,
}),
hint_rejections,
}
}
fn declined(name: &str, reason: WcetDecline) -> WcetIntermediate {
WcetIntermediate::Declined {
name: name.to_string(),
reason,
site: None,
hint_rejections: Vec::new(),
}
}
fn labels(names: &[&str]) -> HashMap<String, usize> {
names
.iter()
.enumerate()
.map(|(i, n)| ((*n).to_string(), i))
.collect()
}
fn cycles_of(f: &WcetFunction) -> u64 {
match f {
WcetFunction::Bounded { cycles, .. } => *cycles,
other => panic!("expected bounded, got {other:?}"),
}
}
fn reason_of(f: &WcetFunction) -> &WcetDecline {
match f {
WcetFunction::Declined { reason, .. } => reason,
other => panic!("expected declined, got {other:?}"),
}
}
#[test]
fn leaf_mid_root_chain_composes_tightly() {
let inters = vec![
composable("func_0", 10, &[]),
composable("func_1", 20, &[("func_0", 1)]),
composable("func_2", 30, &[("func_1", 1)]),
];
let out = compose(&inters, &labels(&["func_0", "func_1", "func_2"]));
assert_eq!(cycles_of(&out[0]), 10); assert_eq!(cycles_of(&out[1]), 30); assert_eq!(cycles_of(&out[2]), 60); }
#[test]
fn call_inside_loop_multiplies_callee() {
let inters = vec![
composable("func_0", 10, &[]),
composable("func_1", 7, &[("func_0", 5)]),
];
let out = compose(&inters, &labels(&["func_0", "func_1"]));
assert_eq!(cycles_of(&out[1]), 7 + 5 * 10); }
#[test]
fn diamond_counts_each_path() {
let inters = vec![
composable("func_0", 4, &[]),
composable("func_1", 5, &[("func_0", 1)]),
composable("func_2", 6, &[("func_0", 1)]),
composable("func_3", 7, &[("func_1", 1), ("func_2", 1)]),
];
let out = compose(&inters, &labels(&["func_0", "func_1", "func_2", "func_3"]));
assert_eq!(cycles_of(&out[3]), 26);
}
#[test]
fn self_recursion_declines() {
let inters = vec![composable("func_0", 10, &[("func_0", 1)])];
let out = compose(&inters, &labels(&["func_0"]));
assert_eq!(reason_of(&out[0]), &WcetDecline::Recursion);
}
#[test]
fn certified_self_recursion_folds_depth_frames() {
let inters = vec![recursive("func_0", 10, &[("func_0", 1)], "func_0", 3)];
let out = compose(&inters, &labels(&["func_0"]));
assert_eq!(cycles_of(&out[0]), 4 * 10); }
#[test]
fn certified_self_recursion_folds_nonself_callee_per_frame() {
let inters = vec![
composable("func_0", 10, &[]),
recursive("func_1", 5, &[("func_1", 1), ("func_0", 1)], "func_1", 2),
];
let out = compose(&inters, &labels(&["func_0", "func_1"]));
assert_eq!(cycles_of(&out[0]), 10);
assert_eq!(cycles_of(&out[1]), 3 * (5 + 10)); }
#[test]
fn certified_self_still_declines_a_mutual_cycle() {
let inters = vec![
recursive("func_0", 10, &[("func_0", 1), ("func_1", 1)], "func_0", 3),
composable("func_1", 10, &[("func_0", 1)]),
];
let out = compose(&inters, &labels(&["func_0", "func_1"]));
assert_eq!(reason_of(&out[0]), &WcetDecline::Recursion);
assert_eq!(reason_of(&out[1]), &WcetDecline::Recursion);
}
#[test]
fn mutual_recursion_declines_both() {
let inters = vec![
composable("func_0", 10, &[("func_1", 1)]),
composable("func_1", 10, &[("func_0", 1)]),
];
let out = compose(&inters, &labels(&["func_0", "func_1"]));
assert_eq!(reason_of(&out[0]), &WcetDecline::Recursion);
assert_eq!(reason_of(&out[1]), &WcetDecline::Recursion);
}
#[test]
fn declined_callee_propagates_up() {
let inters = vec![
composable("func_0", 10, &[]),
declined("func_1", WcetDecline::Loop),
composable("func_2", 5, &[("func_1", 1)]),
];
let out = compose(&inters, &labels(&["func_0", "func_1", "func_2"]));
assert_eq!(cycles_of(&out[0]), 10);
assert_eq!(reason_of(&out[1]), &WcetDecline::Loop);
assert_eq!(reason_of(&out[2]), &WcetDecline::CalleeUnbounded);
}
#[test]
fn external_callee_declines_call() {
let inters = vec![composable("func_0", 10, &[("func_99", 1)])];
let out = compose(&inters, &labels(&["func_0"]));
assert_eq!(reason_of(&out[0]), &WcetDecline::Call);
}
#[test]
fn transitive_recursion_caller_also_declines() {
let inters = vec![
composable("func_1", 10, &[("func_1", 1)]),
composable("func_2", 5, &[("func_1", 1)]),
];
let out = compose(&inters, &labels(&["func_1", "func_2"]));
assert_eq!(reason_of(&out[0]), &WcetDecline::Recursion);
assert_eq!(reason_of(&out[1]), &WcetDecline::Recursion);
}
}