use synth_core::wcet::{WcetHintReject, WcetHintRejection, WcetRecursionCert};
use synth_synthesis::{ArmInstruction, ArmOp, Condition, Operand2, Reg};
use crate::wcet_loops::{Pred, Rel, exit_index};
pub(crate) enum RecursionAnalysis {
NotRecursive,
Unprovable {
hint_rejections: Vec<WcetHintRejection>,
},
Certified {
cert: WcetRecursionCert,
hint_rejections: Vec<WcetHintRejection>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Sym {
Top,
Const(i32),
Param(Reg),
Masked {
base: Reg,
mask: i32,
},
MaskedStep {
base: Reg,
mask: i32,
add: i64,
},
}
struct Walk {
regs: [Sym; 16],
slots: std::collections::BTreeMap<i64, Sym>,
flags: Option<(Sym, Sym)>,
}
impl Walk {
fn entry() -> Self {
let mut regs = [Sym::Top; 16];
regs[Reg::R0 as usize] = Sym::Param(Reg::R0);
regs[Reg::R1 as usize] = Sym::Param(Reg::R1);
regs[Reg::R2 as usize] = Sym::Param(Reg::R2);
regs[Reg::R3 as usize] = Sym::Param(Reg::R3);
Walk {
regs,
slots: std::collections::BTreeMap::new(),
flags: None,
}
}
fn reg(&self, r: Reg) -> Sym {
self.regs[r as usize]
}
fn set(&mut self, r: Reg, v: Sym) {
self.regs[r as usize] = v;
}
fn op2(&self, o: &Operand2) -> Sym {
match o {
Operand2::Imm(c) => Sym::Const(*c),
Operand2::Reg(r) => self.reg(*r),
Operand2::RegShift { .. } => Sym::Top,
}
}
}
#[derive(Debug, Clone, Copy)]
struct GuardPred {
base: Reg,
mask: i32,
add: i64,
rel: Rel,
rhs: i32,
}
pub(crate) fn analyze_recursion(
instrs: &[ArmInstruction],
self_label: Option<&str>,
mult_at: &dyn Fn(usize) -> u128,
hint: Option<u64>,
) -> RecursionAnalysis {
let Some(self_label) = self_label else {
return RecursionAnalysis::NotRecursive;
};
let self_calls: Vec<usize> = instrs
.iter()
.enumerate()
.filter_map(|(i, ins)| match &ins.op {
ArmOp::Bl { label } if label == self_label => Some(i),
_ => None,
})
.collect();
if self_calls.is_empty() {
return RecursionAnalysis::NotRecursive;
}
let reject = |reason: WcetHintReject| -> RecursionAnalysis {
let hint_rejections = hint
.map(|h| {
let note = reason.note().to_string();
vec![WcetHintRejection {
loop_index: 0,
head_offset: None,
hint: h,
reason,
note,
}]
})
.unwrap_or_default();
RecursionAnalysis::Unprovable { hint_rejections }
};
if self_calls.len() != 1 {
return reject(WcetHintReject::HintUnverifiableRecursion);
}
let self_idx = self_calls[0];
if mult_at(self_idx) != 1 {
return reject(WcetHintReject::HintUnverifiableRecursion);
}
let byte = match byte_positions(instrs) {
Some(b) => b,
None => return reject(WcetHintReject::HintUnverifiableRecursion),
};
let Some((base_pred, step)) = prove_masked_chain(instrs, self_idx, &byte) else {
return reject(WcetHintReject::HintUnverifiableRecursion);
};
let p = Pred {
off: 0, add: base_pred.add,
rel: base_pred.rel,
rhs: base_pred.rhs,
};
if matches!(base_pred.rel, Rel::Eq | Rel::Ne) && step.abs() != 1 {
return reject(WcetHintReject::HintUnverifiableRecursion);
}
let (Some((d_hi, _)), Some((d_lo, _))) = (
exit_index(base_pred.mask, step, &p),
exit_index(0, step, &p),
) else {
return reject(WcetHintReject::HintUnverifiableRecursion);
};
let derived_depth = d_hi.max(d_lo);
let Some(h) = hint else {
return RecursionAnalysis::Unprovable {
hint_rejections: Vec::new(),
};
};
if h < derived_depth {
return RecursionAnalysis::Unprovable {
hint_rejections: vec![WcetHintRejection {
loop_index: 0,
head_offset: None,
hint: h,
reason: WcetHintReject::HintBelowDerivedDepth,
note: WcetHintReject::HintBelowDerivedDepth.note().to_string(),
}],
};
}
RecursionAnalysis::Certified {
cert: WcetRecursionCert {
self_label: self_label.to_string(),
max_depth: derived_depth,
hint: h,
},
hint_rejections: Vec::new(),
}
}
fn byte_positions(instrs: &[ArmInstruction]) -> Option<Vec<i64>> {
let encoder = crate::arm_encoder::ArmEncoder::new_thumb2();
let mut pos = Vec::with_capacity(instrs.len());
let mut p: i64 = 0;
for ins in instrs {
pos.push(p);
let bytes = encoder.encode(&ins.op).ok()?;
p += bytes.len() as i64;
}
Some(pos)
}
fn prove_masked_chain(
instrs: &[ArmInstruction],
self_idx: usize,
byte: &[i64],
) -> Option<(GuardPred, i64)> {
let target_byte = |i: usize, offset: i32| byte[i] + 4 + (offset as i64) * 2;
let idx_at = |b: i64| -> Option<usize> { byte.iter().position(|&p| p == b) };
for (i, ins) in instrs.iter().enumerate() {
let off = match &ins.op {
ArmOp::BOffset { offset } => *offset,
ArmOp::BCondOffset { offset, .. } => *offset,
_ => continue,
};
if target_byte(i, off) <= byte[i] {
return None;
}
}
let mut st = Walk::entry();
let mut guard: Option<(GuardPred, usize, usize)> = None;
let mut arg_at_call: Option<Sym> = None;
for (i, ins) in instrs.iter().enumerate() {
if i == self_idx {
arg_at_call = Some(st.reg(Reg::R0));
}
match &ins.op {
ArmOp::BCondOffset { cond, offset } => {
if let Some(p) = eval_guard(*cond, &st.flags) {
let tgt = target_byte(i, *offset);
let ti = idx_at(tgt)?;
if guard.is_some() {
return None;
}
guard = Some((p, i, ti));
}
}
op => step_sym(op, &mut st),
}
}
let (gpred, guard_idx, taken_idx) = guard?;
let arg = arg_at_call?;
if !(guard_idx < self_idx && self_idx < taken_idx) {
return None;
}
if instrs[(guard_idx + 1)..=self_idx]
.iter()
.any(|ins| matches!(&ins.op, ArmOp::BOffset { .. } | ArmOp::BCondOffset { .. }))
{
return None; }
let region_lo = byte[guard_idx + 1];
let region_hi = byte[self_idx]; for (i, ins) in instrs.iter().enumerate() {
let off = match &ins.op {
ArmOp::BOffset { offset } => *offset,
ArmOp::BCondOffset { offset, .. } => *offset,
_ => continue,
};
let tgt = target_byte(i, off);
if tgt > region_lo && tgt <= region_hi {
return None; }
}
let (abase, amask, aadd) = match arg {
Sym::MaskedStep { base, mask, add } => (base, mask, add),
_ => return None, };
if abase != gpred.base || amask != gpred.mask {
return None; }
let step = aadd; if step == 0 {
return None;
}
Some((gpred, step))
}
fn step_sym(op: &ArmOp, st: &mut Walk) {
use ArmOp::*;
match op {
Mov { rd, op2 } => {
let v = st.op2(op2);
st.set(*rd, v);
st.flags = None;
}
And { rd, rn, op2 } => {
let v = match (st.reg(*rn), st.op2(op2)) {
(Sym::Param(base), Sym::Const(mask)) => Sym::Masked { base, mask },
_ => Sym::Top,
};
st.set(*rd, v);
st.flags = None;
}
Add { rd, rn, op2 } | Adds { rd, rn, op2 } => {
let v = masked_shift(st.reg(*rn), st.op2(op2), 1);
st.set(*rd, v);
st.flags = None;
}
Sub { rd, rn, op2 } | Subs { rd, rn, op2 } => {
let v = masked_shift(st.reg(*rn), st.op2(op2), -1);
st.set(*rd, v);
st.flags = None;
}
Str { rd, addr } => {
if addr.base == Reg::SP && addr.offset_reg.is_none() {
st.slots.insert(addr.offset as i64, st.reg(*rd));
}
}
Ldr { rd, addr } => {
let v = if addr.base == Reg::SP && addr.offset_reg.is_none() {
st.slots
.get(&(addr.offset as i64))
.copied()
.unwrap_or(Sym::Top)
} else {
Sym::Top
};
st.set(*rd, v);
}
Cmp { rn, op2 } => {
st.flags = Some((st.reg(*rn), st.op2(op2)));
}
SetCond { rd, .. } => {
st.set(*rd, Sym::Top);
}
Label { .. } | Nop => {}
_ => {
if let Some(rd) = dest_reg(op) {
st.set(rd, Sym::Top);
}
st.flags = None;
}
}
}
fn masked_shift(a: Sym, b: Sym, sign: i64) -> Sym {
match (a, b) {
(Sym::Const(x), Sym::Const(y)) => {
i32::try_from(x as i64 + sign * y as i64).map_or(Sym::Top, Sym::Const)
}
(Sym::Masked { base, mask }, Sym::Const(y)) => {
let add = sign * y as i64;
if add.abs() <= 1 << 20 {
Sym::MaskedStep { base, mask, add }
} else {
Sym::Top
}
}
(Sym::MaskedStep { base, mask, add }, Sym::Const(y)) => {
match add.checked_add(sign * y as i64) {
Some(na) if na.abs() <= 1 << 20 => Sym::MaskedStep {
base,
mask,
add: na,
},
_ => Sym::Top,
}
}
_ => Sym::Top,
}
}
fn eval_guard(cond: Condition, flags: &Option<(Sym, Sym)>) -> Option<GuardPred> {
let (a, b) = flags.as_ref()?;
match (a, b) {
(Sym::Masked { base, mask }, Sym::Const(c)) => Some(GuardPred {
base: *base,
mask: *mask,
add: 0,
rel: Rel::of(cond),
rhs: *c,
}),
(Sym::MaskedStep { base, mask, add }, Sym::Const(c)) => Some(GuardPred {
base: *base,
mask: *mask,
add: *add,
rel: Rel::of(cond),
rhs: *c,
}),
_ => None,
}
}
fn dest_reg(op: &ArmOp) -> Option<Reg> {
use ArmOp::*;
match op {
Add { rd, .. }
| Sub { rd, .. }
| Adds { rd, .. }
| Subs { rd, .. }
| And { rd, .. }
| Orr { rd, .. }
| Eor { rd, .. }
| Mov { rd, .. }
| Movw { rd, .. }
| Movt { rd, .. }
| Lsl { rd, .. }
| Lsr { rd, .. }
| Asr { rd, .. }
| Ror { rd, .. }
| Mul { rd, .. }
| Rsb { rd, .. }
| Mvn { rd, .. }
| Clz { rd, .. }
| Uxtb { rd, .. }
| Uxth { rd, .. }
| Sxtb { rd, .. }
| Sxth { rd, .. }
| SetCond { rd, .. } => Some(*rd),
_ => None,
}
}