use synth_core::wcet::{WcetDecline, WcetFunction};
use synth_synthesis::{ArmInstruction, ArmOp};
const STRAIGHTLINE_CEIL_PER_HALFWORD: u64 = 5;
fn straightline_expansion(op: &ArmOp) -> u64 {
let byte_len = synth_synthesis::estimate_arm_byte_size(op) as u64;
(byte_len / 2) * STRAIGHTLINE_CEIL_PER_HALFWORD
}
enum OpCost {
Cycles(u64),
LoopedExpansion,
Unmodeled,
}
#[allow(clippy::match_same_arms)] fn op_cost(op: &ArmOp) -> OpCost {
use ArmOp::*;
use OpCost::{Cycles, LoopedExpansion, Unmodeled};
match op {
Add { .. }
| Sub { .. }
| Adds { .. }
| Subs { .. }
| Adc { .. }
| Sbc { .. }
| And { .. }
| Orr { .. }
| Eor { .. }
| Rsb { .. }
| Mvn { .. } => Cycles(1),
Mov { .. } | Movw { .. } | Movt { .. } | MovwSym { .. } | MovtSym { .. } => Cycles(1),
Clz { .. } | Rbit { .. } => Cycles(1),
Sxtb { .. } | Sxth { .. } | Uxtb { .. } | Uxth { .. } => Cycles(1),
Cmp { .. } | Cmn { .. } => Cycles(1),
Lsl { .. }
| Lsr { .. }
| Asr { .. }
| Ror { .. }
| LslReg { .. }
| LsrReg { .. }
| AsrReg { .. }
| RorReg { .. } => Cycles(1),
Mul { .. } => Cycles(1),
Mls { .. } | Mla { .. } => Cycles(2),
Umull { .. } => Cycles(5),
Sdiv { .. } | Udiv { .. } => Cycles(12),
Ldr { .. }
| Str { .. }
| Ldrb { .. }
| Ldrh { .. }
| Ldrsb { .. }
| Ldrsh { .. }
| Strb { .. }
| Strh { .. }
| LdrSym { .. } => Cycles(2),
BOffset { .. } | BCondOffset { .. } | Bx { .. } => Cycles(4),
Bl { .. } | Blx { .. } => Unmodeled,
SetCond { .. } => Cycles(3),
SelectMove { .. } => Cycles(2),
Select { .. } => Unmodeled,
Popcnt { .. } => Cycles(straightline_expansion(op)),
Udf { .. } => Cycles(1),
I64DivU { .. } | I64DivS { .. } | I64RemU { .. } | I64RemS { .. } => LoopedExpansion,
I64SetCond { .. }
| I64SetCondZ { .. }
| I64Mul { .. }
| I64Shl { .. }
| I64ShrU { .. }
| I64ShrS { .. }
| I64Rotl { .. }
| I64Rotr { .. }
| I64Clz { .. }
| I64Ctz { .. }
| I64Popcnt { .. }
| I64Extend8S { .. }
| I64Extend16S { .. }
| I64Extend32S { .. } => Cycles(straightline_expansion(op)),
Push { regs } => Cycles(1 + regs.len() as u64 + 3),
Pop { regs } => Cycles(1 + regs.len() as u64 + 3),
Label { .. } | Nop => Cycles(0),
B { .. } | Bcc { .. } | Bhs { .. } | Blo { .. } => Unmodeled, LocalGet { .. }
| LocalSet { .. }
| LocalTee { .. }
| GlobalGet { .. }
| GlobalSet { .. } => Unmodeled,
Call { .. } | CallIndirect { .. } | BrTable { .. } => Unmodeled,
MemorySize { .. } | MemoryGrow { .. } => Unmodeled,
I64Add { .. }
| I64Sub { .. }
| I64And { .. }
| I64Or { .. }
| I64Xor { .. }
| I64Eqz { .. }
| I64Eq { .. }
| I64Ne { .. }
| I64LtS { .. }
| I64LtU { .. }
| I64LeS { .. }
| I64LeU { .. }
| I64GtS { .. }
| I64GtU { .. }
| I64GeS { .. }
| I64GeU { .. }
| I64Const { .. }
| I64Ldr { .. }
| I64Str { .. }
| I64ExtendI32S { .. }
| I64ExtendI32U { .. }
| I32WrapI64 { .. } => Unmodeled,
F32Add { .. }
| F32Sub { .. }
| F32Mul { .. }
| F32Div { .. }
| F32Abs { .. }
| F32Neg { .. }
| F32Sqrt { .. }
| F32Ceil { .. }
| F32Floor { .. }
| F32Trunc { .. }
| F32Nearest { .. }
| F32Min { .. }
| F32Max { .. }
| F32Copysign { .. }
| F32Eq { .. }
| F32Ne { .. }
| F32Lt { .. }
| F32Le { .. }
| F32Gt { .. }
| F32Ge { .. }
| F32Const { .. }
| F32Load { .. }
| F32Store { .. }
| F32ConvertI32S { .. }
| F32ConvertI32U { .. }
| F32ConvertI64S { .. }
| F32ConvertI64U { .. }
| F32ReinterpretI32 { .. }
| I32ReinterpretF32 { .. }
| I32TruncF32S { .. }
| I32TruncF32U { .. }
| F64Add { .. }
| F64Sub { .. }
| F64Mul { .. }
| F64Div { .. }
| F64Abs { .. }
| F64Neg { .. }
| F64Sqrt { .. }
| F64Ceil { .. }
| F64Floor { .. }
| F64Trunc { .. }
| F64Nearest { .. }
| F64Min { .. }
| F64Max { .. }
| F64Copysign { .. }
| F64Eq { .. }
| F64Ne { .. }
| F64Lt { .. }
| F64Le { .. }
| F64Gt { .. }
| F64Ge { .. }
| F64Const { .. }
| F64Load { .. }
| F64Store { .. }
| F64ConvertI32S { .. }
| F64ConvertI32U { .. }
| F64ConvertI64S { .. }
| F64ConvertI64U { .. }
| F64PromoteF32 { .. }
| F32DemoteF64 { .. }
| F64ReinterpretI64 { .. }
| I64ReinterpretF64 { .. }
| I64TruncF64S { .. }
| I64TruncF64U { .. }
| I32TruncF64S { .. }
| I32TruncF64U { .. }
| MveLoad { .. }
| MveStore { .. }
| MveConst { .. }
| MveAnd { .. }
| MveOrr { .. }
| MveEor { .. }
| MveMvn { .. }
| MveBic { .. }
| MveAddI { .. }
| MveSubI { .. }
| MveMulI { .. }
| MveNegI { .. }
| MveCmpEqI { .. }
| MveCmpNeI { .. }
| MveCmpLtS { .. }
| MveCmpLtU { .. }
| MveCmpGtS { .. }
| MveCmpGtU { .. }
| MveCmpLeS { .. }
| MveCmpLeU { .. }
| MveCmpGeS { .. }
| MveCmpGeU { .. }
| MveDup { .. }
| MveExtractLane { .. }
| MveInsertLane { .. }
| MveAddF32 { .. }
| MveSubF32 { .. }
| MveMulF32 { .. }
| MveNegF32 { .. }
| MveAbsF32 { .. }
| MveCmpEqF32 { .. }
| MveCmpNeF32 { .. }
| MveCmpLtF32 { .. }
| MveCmpLeF32 { .. }
| MveCmpGtF32 { .. }
| MveCmpGeF32 { .. }
| MveDupF32 { .. }
| MveExtractLaneF32 { .. }
| MveReplaceLaneF32 { .. }
| MveDivF32 { .. }
| MveSqrtF32 { .. } => Unmodeled,
}
}
pub fn op_worst_case_cycles(op: &ArmOp) -> u64 {
match op_cost(op) {
OpCost::Cycles(c) => c,
OpCost::LoopedExpansion | OpCost::Unmodeled => {
panic!("op_worst_case_cycles called on a non-straight-line op: {op:?}")
}
}
}
pub fn sound_core_class(triple: &str) -> Option<&'static str> {
match triple {
"thumbv7m-none-eabi" | "cortex-m3" => Some("cortex-m3"),
"thumbv7em-none-eabi" | "cortex-m4" => Some("cortex-m4"),
_ => None,
}
}
fn scan_for_decline(instrs: &[ArmInstruction]) -> Option<WcetDecline> {
use synth_synthesis::estimate_arm_byte_size;
let mut positions = Vec::with_capacity(instrs.len());
let mut pos: i64 = 0;
for instr in instrs {
positions.push(pos);
pos += estimate_arm_byte_size(&instr.op) as i64;
}
for (i, instr) in instrs.iter().enumerate() {
match &instr.op {
ArmOp::Bl { .. }
| ArmOp::Blx { .. }
| ArmOp::Call { .. }
| ArmOp::CallIndirect { .. } => {
return Some(WcetDecline::Call);
}
ArmOp::B { .. } | ArmOp::Bcc { .. } | ArmOp::Bhs { .. } | ArmOp::Blo { .. } => {
return Some(WcetDecline::UnresolvedBranch);
}
_ => {}
}
match op_cost(&instr.op) {
OpCost::LoopedExpansion => return Some(WcetDecline::LoopedExpansion),
OpCost::Unmodeled
if !matches!(
&instr.op,
ArmOp::Bl { .. }
| ArmOp::Blx { .. }
| ArmOp::B { .. }
| ArmOp::Bcc { .. }
| ArmOp::Bhs { .. }
| ArmOp::Blo { .. }
) =>
{
return Some(WcetDecline::UnmodeledOp);
}
_ => {}
}
if let ArmOp::BOffset { offset } | ArmOp::BCondOffset { offset, .. } = &instr.op {
let target = positions[i] + 4 + (*offset as i64) * 2;
if target <= positions[i] {
return Some(WcetDecline::Loop);
}
}
}
None
}
pub fn function_wcet(name: &str, instrs: &[ArmInstruction], triple: &str) -> WcetFunction {
if sound_core_class(triple).is_none() {
return WcetFunction::declined(name, WcetDecline::UnsupportedCore);
}
if let Some(reason) = scan_for_decline(instrs) {
return WcetFunction::declined(name, reason);
}
let cycles: u64 = instrs.iter().map(|i| op_worst_case_cycles(&i.op)).sum();
WcetFunction::Bounded {
name: name.to_string(),
cycles,
instr_count: instrs.len(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use synth_synthesis::{Condition, Operand2, Reg};
fn insn(op: ArmOp) -> ArmInstruction {
ArmInstruction {
op,
source_line: None,
}
}
#[test]
fn loop_free_sum_is_exact() {
let instrs = vec![
insn(ArmOp::Mov {
rd: Reg::R0,
op2: Operand2::Imm(1),
}), insn(ArmOp::Add {
rd: Reg::R0,
rn: Reg::R0,
op2: Operand2::Reg(Reg::R1),
}), insn(ArmOp::Ldr {
rd: Reg::R2,
addr: synth_synthesis::MemAddr {
base: Reg::R1,
offset: 0,
offset_reg: None,
},
}), insn(ArmOp::Bx { rm: Reg::LR }), ];
match function_wcet("leaf", &instrs, "cortex-m4") {
WcetFunction::Bounded { cycles, .. } => assert_eq!(cycles, 1 + 1 + 2 + 4),
other => panic!("expected bounded, got {other:?}"),
}
}
#[test]
fn backward_branch_declines_loop() {
let instrs = vec![
insn(ArmOp::Cmp {
rn: Reg::R0,
op2: Operand2::Imm(0),
}),
insn(ArmOp::BCondOffset {
cond: Condition::NE,
offset: -4,
}),
];
assert!(matches!(
function_wcet("spin", &instrs, "cortex-m4"),
WcetFunction::Declined {
reason: WcetDecline::Loop,
..
}
));
}
#[test]
fn forward_branch_is_loop_free() {
let instrs = vec![
insn(ArmOp::BCondOffset {
cond: Condition::EQ,
offset: -1,
}),
insn(ArmOp::Bx { rm: Reg::LR }),
];
assert!(matches!(
function_wcet("fwd", &instrs, "cortex-m4"),
WcetFunction::Bounded { .. }
));
}
#[test]
fn call_declines() {
let instrs = vec![insn(ArmOp::Bl {
label: "callee".into(),
})];
assert!(matches!(
function_wcet("caller", &instrs, "cortex-m4"),
WcetFunction::Declined {
reason: WcetDecline::Call,
..
}
));
}
#[test]
fn looped_i64_div_declines() {
let instrs = vec![insn(ArmOp::I64DivU {
rdlo: Reg::R0,
rdhi: Reg::R1,
rnlo: Reg::R2,
rnhi: Reg::R3,
rmlo: Reg::R4,
rmhi: Reg::R5,
elide_zero_guard: false,
})];
assert!(matches!(
function_wcet("div", &instrs, "cortex-m4"),
WcetFunction::Declined {
reason: WcetDecline::LoopedExpansion,
..
}
));
}
#[test]
fn m7_declines_unsupported_core() {
let instrs = vec![insn(ArmOp::Bx { rm: Reg::LR })];
assert!(matches!(
function_wcet("f", &instrs, "cortex-m7"),
WcetFunction::Declined {
reason: WcetDecline::UnsupportedCore,
..
}
));
}
}