Skip to content

Commit db068c2

Browse files
avrabeclaude
andcommitted
feat(rta): Context_Switch_Time folded into recurrence (v0.9.2)
Tier A #5 from the post-v0.9.0 reviewer audit (partial — only Context_Switch_Time; Interrupt_Overhead per ISR firing is the companion follow-up). v0.8.x emitted a STPA-REQ-022 advisory if Context_Switch_Time was unset, but never folded the value into the Tindell-Clark / Joseph-Pandya recurrence when it WAS set — silently optimistic. Now: each thread's WCET is inflated by `2 × Context_Switch_Time` (one preemption-in + one preemption-out per Buttazzo §7) before entering the recurrence. The recurrence math itself (`compute_response_time_jittered_blocking`) is unchanged, so the existing Lean monotonicity / convergence proofs still hold. New `OverheadInflation` Info diagnostic per thread when CS > 0. Default unset = 0 → byte-identical to v0.8.x / v0.9.1. REQ-RTA-008 + TEST-RTA-CONTEXT-SWITCH. 2 new RTA tests (context_switch_inflates_wcet, no_context_switch_byte_identical_to_v091). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 994aa7f commit db068c2

3 files changed

Lines changed: 115 additions & 1 deletion

File tree

artifacts/requirements.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,22 @@ artifacts:
15701570
status: implemented
15711571
tags: [network, wctt, soundness, v091]
15721572

1573+
- id: REQ-RTA-008
1574+
type: requirement
1575+
title: Context_Switch_Time folded into RTA recurrence
1576+
description: >
1577+
v0.8.x emitted a STPA-REQ-022 advisory if `Context_Switch_Time`
1578+
was unset, but never folded the value into the Tindell-Clark /
1579+
Joseph-Pandya recurrence when it *was* set — silently optimistic.
1580+
v0.9.2 inflates each thread's WCET by `2 × Context_Switch_Time`
1581+
(one preemption-in + one preemption-out per Buttazzo §7). New
1582+
`OverheadInflation` Info diagnostic per thread when CS > 0.
1583+
Default unset = 0 (preserves v0.8.x byte-identical output).
1584+
Per the post-v0.9.0 reviewer's Tier A #5 (partial —
1585+
`Interrupt_Overhead` per ISR firing still deferred).
1586+
status: implemented
1587+
tags: [analysis, scheduling, rta, soundness, v092]
1588+
15731589
# ── Track G: spar-insight discrepancy assistant (v0.9.0) ──────────
15741590

15751591
- id: REQ-INSIGHT-001

artifacts/verification.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,6 +2078,27 @@ artifacts:
20782078
- type: satisfies
20792079
target: REQ-NETWORK-010
20802080

2081+
- id: TEST-RTA-CONTEXT-SWITCH
2082+
type: feature
2083+
title: Context_Switch_Time inflates WCET in RTA recurrence
2084+
description: >
2085+
Verifies the v0.9.2 RTA Context_Switch_Time fix.
2086+
`context_switch_inflates_wcet` builds a one-thread model with
2087+
Context_Switch_Time = 100 µs and asserts WCET 1 ms → 1.2 ms
2088+
with an `OverheadInflation` Info diagnostic naming the thread
2089+
and the correction. `no_context_switch_byte_identical_to_v091`
2090+
confirms unset Context_Switch_Time produces byte-identical
2091+
output (no OverheadInflation diagnostic).
2092+
fields:
2093+
method: automated-test
2094+
steps:
2095+
- run: cargo test -p spar-analysis --lib -- context_switch
2096+
status: passing
2097+
tags: [v0.9.2, analysis, rta, soundness, scheduling]
2098+
links:
2099+
- type: satisfies
2100+
target: REQ-RTA-008
2101+
20812102
- id: TEST-INSIGHT-DISCREPANCY
20822103
type: feature
20832104
title: spar-insight CTF parser + 5-kind discrepancy detection

crates/spar-analysis/src/rta.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,37 @@ impl Analysis for RtaAnalysis {
203203
continue;
204204
};
205205

206-
let Some(exec_ps) = get_execution_time(props) else {
206+
let Some(exec_base_ps) = get_execution_time(props) else {
207207
// No execution time — skip; the scheduling pass already warns.
208208
continue;
209209
};
210210

211+
// v0.9.2 (Tier A #5): inflate WCET by 2·Context_Switch_Time
212+
// (one preemption-in + one preemption-out per dispatch) per
213+
// Buttazzo, *Hard Real-Time Computing Systems* §7. v0.8.x
214+
// emitted a STPA-REQ-022 advisory if Context_Switch_Time was
215+
// absent but never folded the value into the recurrence —
216+
// silently optimistic. Default unset = 0 (byte-identical to
217+
// v0.8.x). The property is read from the thread's props for
218+
// compatibility with the existing scheduling.rs pattern.
219+
let context_switch_ps = get_timing_property(props, "Context_Switch_Time").unwrap_or(0);
220+
let exec_ps = exec_base_ps.saturating_add(context_switch_ps.saturating_mul(2));
221+
if context_switch_ps > 0 {
222+
diags.push(AnalysisDiagnostic {
223+
severity: Severity::Info,
224+
message: format!(
225+
"OverheadInflation: thread '{}' WCET {} → {} (added 2 × {} \
226+
Context_Switch_Time per Buttazzo §7)",
227+
comp.name.as_str(),
228+
format_time(exec_base_ps),
229+
format_time(exec_ps),
230+
format_time(context_switch_ps),
231+
),
232+
path: component_path(instance, idx),
233+
analysis: self.name().to_string(),
234+
});
235+
}
236+
211237
let binding = get_processor_binding(props).unwrap_or("__unbound__".to_string());
212238

213239
let deadline_ps = get_timing_property(props, "Deadline").unwrap_or(period_ps);
@@ -1475,6 +1501,57 @@ mod tests {
14751501
RtaAnalysis.analyze(&inst)
14761502
}
14771503

1504+
// ── O1 (v0.9.2): Context_Switch_Time inflates WCET ────────────
1505+
#[test]
1506+
fn context_switch_inflates_wcet() {
1507+
// With Context_Switch_Time = 100 us, the WCET inflation is
1508+
// 2 × 100 us = 200 us (one preemption-in + one preemption-out
1509+
// per Buttazzo §7). The diagnostic must reflect the new value.
1510+
let (mut b, root, proc) = make_base();
1511+
let t1 = b.add_component("t1", ComponentCategory::Thread, Some(proc));
1512+
b.set_children(
1513+
root,
1514+
vec![
1515+
ComponentInstanceIdx::from_raw(la_arena::RawIdx::from_u32(1)),
1516+
proc,
1517+
],
1518+
);
1519+
b.set_children(proc, vec![t1]);
1520+
bind_thread(&mut b, t1, "10 ms", "1 ms", None);
1521+
b.set_property(t1, "Timing_Properties", "Context_Switch_Time", "100 us");
1522+
1523+
let inst = b.build(root);
1524+
let diags = RtaAnalysis.analyze(&inst);
1525+
1526+
let inflation = diags
1527+
.iter()
1528+
.find(|d| {
1529+
d.severity == Severity::Info
1530+
&& d.message.starts_with("OverheadInflation")
1531+
&& d.message.contains("'t1'")
1532+
})
1533+
.unwrap_or_else(|| panic!("expected OverheadInflation for t1, got: {:#?}", diags));
1534+
assert!(
1535+
inflation.message.contains("WCET 1.00 ms → 1.20 ms"),
1536+
"expected WCET 1 ms → 1.2 ms after 2x100us context-switch, got: {}",
1537+
inflation.message,
1538+
);
1539+
}
1540+
1541+
#[test]
1542+
fn no_context_switch_byte_identical_to_v091() {
1543+
// Context_Switch_Time unset = no OverheadInflation diagnostic
1544+
// and v0.8.x/v0.9.1 byte-identical RTA output.
1545+
let diags = make_two_thread_model_diags(None, None);
1546+
assert!(
1547+
!diags
1548+
.iter()
1549+
.any(|d| d.message.starts_with("OverheadInflation")),
1550+
"no Context_Switch_Time must not emit OverheadInflation: {:#?}",
1551+
diags,
1552+
);
1553+
}
1554+
14781555
// ── B1: non-regression — no Locking_Protocol must match v0.7.0 byte-for-byte ─
14791556
#[test]
14801557
fn no_locking_matches_v070() {

0 commit comments

Comments
 (0)