use serde::{Deserialize, Serialize};
pub const SCHEMA: &str = "synth-wcet-v1";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum WcetDecline {
Loop,
Call,
UnresolvedBranch,
LoopedExpansion,
UnsupportedCore,
UnmodeledOp,
}
impl WcetDecline {
pub fn note(&self) -> &'static str {
match self {
WcetDecline::Loop => {
"backward branch (loop) — a sound bound needs a trip count \
(scry loop-bound-inference follow-up)"
}
WcetDecline::Call => {
"call (Bl/Blx) — per-function bound is intra-procedural \
(spar inter-procedural-composition follow-up)"
}
WcetDecline::UnresolvedBranch => {
"residual external/unresolved label branch — direction not \
statically known, cannot prove loop-free"
}
WcetDecline::LoopedExpansion => {
"op expands to an internal runtime loop (i64 software div/rem, \
executed 64×) — straight sum would undercount"
}
WcetDecline::UnsupportedCore => {
"core class not soundly summable with a zero-wait per-op table \
(Cortex-M7 dual-issue + cache wait-states)"
}
WcetDecline::UnmodeledOp => "op not classified by the cycle model",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "kebab-case")]
pub enum WcetFunction {
Bounded {
name: String,
cycles: u64,
instr_count: usize,
},
Declined {
name: String,
reason: WcetDecline,
note: String,
},
}
impl WcetFunction {
pub fn declined(name: impl Into<String>, reason: WcetDecline) -> Self {
let note = reason.note().to_string();
WcetFunction::Declined {
name: name.into(),
reason,
note,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WcetReport {
pub schema: String,
pub module: String,
pub core_class: String,
pub wait_states: u32,
pub memory_assumption: String,
pub functions: Vec<WcetFunction>,
}
impl WcetReport {
pub fn new(module: impl Into<String>, core_class: impl Into<String>) -> Self {
WcetReport {
schema: SCHEMA.to_string(),
module: module.into(),
core_class: core_class.into(),
wait_states: 0,
memory_assumption:
"zero-wait-state instruction memory (flash accelerator / I-cache hit); \
in-order single-issue pipeline; documented per-instruction worst-case cycles"
.to_string(),
functions: Vec::new(),
}
}
pub fn to_json(&self) -> serde_json::Result<String> {
serde_json::to_string_pretty(self)
}
pub fn sidecar_path(output: &std::path::Path) -> std::path::PathBuf {
let mut s = output.as_os_str().to_os_string();
s.push(".wcet.json");
std::path::PathBuf::from(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bounded_and_declined_roundtrip() {
let mut r = WcetReport::new("m", "cortex-m4");
r.functions.push(WcetFunction::Bounded {
name: "leaf".into(),
cycles: 42,
instr_count: 7,
});
r.functions
.push(WcetFunction::declined("spins", WcetDecline::Loop));
let json = r.to_json().unwrap();
let back: WcetReport = serde_json::from_str(&json).unwrap();
assert_eq!(r, back);
assert!(json.contains("\"reason\": \"loop\""));
assert!(json.contains("synth-wcet-v1"));
}
#[test]
fn sidecar_path_appends_suffix() {
let p = WcetReport::sidecar_path(std::path::Path::new("out/app.elf"));
assert_eq!(p, std::path::PathBuf::from("out/app.elf.wcet.json"));
}
}