use serde::{Deserialize, Serialize};
pub const SCHEMA: &str = "synth-wcet-v1";
pub const HINTS_SCHEMA: &str = "synth-wcet-hints-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) without a statically-proven trip count — \
canonical const-bound counted loops are proven automatically; \
equality-exit shapes need a verified --wcet-hints entry; \
data-dependent bounds are the 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, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum WcetLoopBoundSource {
Static,
HintVerified,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WcetLoopBound {
pub head_offset: u64,
pub trip_count: u64,
pub region_instr_count: usize,
pub source: WcetLoopBoundSource,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hint: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum WcetHintReject {
HintBelowDerivedTrip,
HintUnverifiableInduction,
HintUnknownLoop,
}
impl WcetHintReject {
pub fn note(&self) -> &'static str {
match self {
WcetHintReject::HintBelowDerivedTrip => {
"hint is below synth's derived trip count — a wrong hint; \
trusting it would emit an unsound bound"
}
WcetHintReject::HintUnverifiableInduction => {
"loop induction not verifiable against the hint (counter not \
provably monotonic toward a statically-known bound ≤ hint) — \
an unverifiable hint is never trusted into a bound"
}
WcetHintReject::HintUnknownLoop => {
"hint indexes a loop that does not exist in the final \
instruction stream"
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WcetHintRejection {
pub loop_index: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_offset: Option<u64>,
pub hint: u64,
pub reason: WcetHintReject,
pub note: String,
}
#[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,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
loops: Vec<WcetLoopBound>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
hint_rejections: Vec<WcetHintRejection>,
},
Declined {
name: String,
reason: WcetDecline,
note: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
hint_rejections: Vec<WcetHintRejection>,
},
}
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,
hint_rejections: Vec::new(),
}
}
pub fn declined_with_rejections(
name: impl Into<String>,
reason: WcetDecline,
hint_rejections: Vec<WcetHintRejection>,
) -> Self {
let note = reason.note().to_string();
WcetFunction::Declined {
name: name.into(),
reason,
note,
hint_rejections,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WcetHints {
pub schema: String,
#[serde(default)]
pub functions: std::collections::BTreeMap<String, WcetFunctionHints>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WcetFunctionHints {
#[serde(default)]
pub loop_bounds: Vec<Option<u64>>,
}
#[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,
loops: Vec::new(),
hint_rejections: Vec::new(),
});
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"));
}
}