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,
Recursion,
IndirectCall,
CalleeUnbounded,
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 => {
"direct call to an external/imported/unresolvable callee with no \
per-function bound in this module — cannot compose an \
inter-procedural bound (local direct calls ARE composed, #778 phase 3)"
}
WcetDecline::Recursion => {
"cycle in the direct call graph (self- or mutual recursion) — an \
upper cycle bound cannot be composed from a recursive call graph"
}
WcetDecline::IndirectCall => {
"indirect call (Blx <reg> / call_indirect / function-pointer \
dispatch) — the callee is not statically known, cannot compose"
}
WcetDecline::CalleeUnbounded => {
"a directly-called callee is itself unbounded — the decline \
propagates up the call graph (a caller cannot be bounded while a \
callee it invokes is unbounded)"
}
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,
MaskCeiling,
}
#[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,
HintUnverifiableRecursion,
HintBelowDerivedDepth,
}
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"
}
WcetHintReject::HintUnverifiableRecursion => {
"recursion-depth hint not verifiable — the self-recursion is not a \
single-self-call chain whose controlling value is entry-independently \
bounded (masked-slot counter decreasing by a const step toward a base \
guard on the same masked quantity); depth is runtime-unbounded, so \
the hint is never trusted into a bound"
}
WcetHintReject::HintBelowDerivedDepth => {
"recursion-depth hint is below synth's derived maximum depth (the \
entry-independent ceiling proven from the masked-slot induction) — \
a wrong hint; trusting it would emit an unsound bound"
}
}
}
}
#[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)]
pub struct WcetRecursionBound {
pub max_depth: u64,
pub frame_count: u64,
pub hint: u64,
}
#[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 = "Option::is_none")]
recursion: Option<WcetRecursionBound>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
hint_rejections: Vec<WcetHintRejection>,
},
Declined {
name: String,
reason: WcetDecline,
note: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
op: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
offset: Option<u64>,
#[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,
op: None,
offset: None,
hint_rejections: Vec::new(),
}
}
pub fn declined_at(
name: impl Into<String>,
reason: WcetDecline,
op: impl Into<String>,
offset: Option<u64>,
) -> Self {
let note = reason.note().to_string();
WcetFunction::Declined {
name: name.into(),
reason,
note,
op: Some(op.into()),
offset,
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,
op: None,
offset: None,
hint_rejections,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WcetRecursionCert {
pub self_label: String,
pub max_depth: u64,
pub hint: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WcetCallSite {
pub callee_label: String,
pub multiplier: u128,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WcetDeclineSite {
pub op: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WcetIntermediate {
Declined {
site: Option<WcetDeclineSite>,
name: String,
reason: WcetDecline,
hint_rejections: Vec<WcetHintRejection>,
},
Composable {
name: String,
own_cycles: u64,
instr_count: usize,
call_sites: Vec<WcetCallSite>,
loops: Vec<WcetLoopBound>,
recursion_cert: Option<WcetRecursionCert>,
hint_rejections: Vec<WcetHintRejection>,
},
}
impl WcetIntermediate {
pub fn name(&self) -> &str {
match self {
WcetIntermediate::Declined { name, .. } | WcetIntermediate::Composable { name, .. } => {
name
}
}
}
}
#[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>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recursion_depth: 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(),
recursion: None,
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"));
}
}