Skip to content

Commit 6e9f5ab

Browse files
avrabeclaude
andauthored
feat(rta): Context_Switch_Time folded into recurrence (v0.9.2) (#198)
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 ca22970 commit 6e9f5ab

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
- id: REQ-RTA-009
15741590
type: requirement
15751591
title: Stop_For_Lock without explicit blocking emits warning

artifacts/verification.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,27 @@ artifacts:
21322132
- type: satisfies
21332133
target: REQ-TSN-005
21342134

2135+
- id: TEST-RTA-CONTEXT-SWITCH
2136+
type: feature
2137+
title: Context_Switch_Time inflates WCET in RTA recurrence
2138+
description: >
2139+
Verifies the v0.9.2 RTA Context_Switch_Time fix.
2140+
`context_switch_inflates_wcet` builds a one-thread model with
2141+
Context_Switch_Time = 100 µs and asserts WCET 1 ms → 1.2 ms
2142+
with an `OverheadInflation` Info diagnostic naming the thread
2143+
and the correction. `no_context_switch_byte_identical_to_v091`
2144+
confirms unset Context_Switch_Time produces byte-identical
2145+
output (no OverheadInflation diagnostic).
2146+
fields:
2147+
method: automated-test
2148+
steps:
2149+
- run: cargo test -p spar-analysis --lib -- context_switch
2150+
status: passing
2151+
tags: [v0.9.2, analysis, rta, soundness, scheduling]
2152+
links:
2153+
- type: satisfies
2154+
target: REQ-RTA-008
2155+
21352156
- id: TEST-INSIGHT-DISCREPANCY
21362157
type: feature
21372158
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);
@@ -1500,6 +1526,57 @@ mod tests {
15001526
RtaAnalysis.analyze(&inst)
15011527
}
15021528

1529+
// ── O1 (v0.9.2): Context_Switch_Time inflates WCET ────────────
1530+
#[test]
1531+
fn context_switch_inflates_wcet() {
1532+
// With Context_Switch_Time = 100 us, the WCET inflation is
1533+
// 2 × 100 us = 200 us (one preemption-in + one preemption-out
1534+
// per Buttazzo §7). The diagnostic must reflect the new value.
1535+
let (mut b, root, proc) = make_base();
1536+
let t1 = b.add_component("t1", ComponentCategory::Thread, Some(proc));
1537+
b.set_children(
1538+
root,
1539+
vec![
1540+
ComponentInstanceIdx::from_raw(la_arena::RawIdx::from_u32(1)),
1541+
proc,
1542+
],
1543+
);
1544+
b.set_children(proc, vec![t1]);
1545+
bind_thread(&mut b, t1, "10 ms", "1 ms", None);
1546+
b.set_property(t1, "Timing_Properties", "Context_Switch_Time", "100 us");
1547+
1548+
let inst = b.build(root);
1549+
let diags = RtaAnalysis.analyze(&inst);
1550+
1551+
let inflation = diags
1552+
.iter()
1553+
.find(|d| {
1554+
d.severity == Severity::Info
1555+
&& d.message.starts_with("OverheadInflation")
1556+
&& d.message.contains("'t1'")
1557+
})
1558+
.unwrap_or_else(|| panic!("expected OverheadInflation for t1, got: {:#?}", diags));
1559+
assert!(
1560+
inflation.message.contains("WCET 1.00 ms → 1.20 ms"),
1561+
"expected WCET 1 ms → 1.2 ms after 2x100us context-switch, got: {}",
1562+
inflation.message,
1563+
);
1564+
}
1565+
1566+
#[test]
1567+
fn no_context_switch_byte_identical_to_v091() {
1568+
// Context_Switch_Time unset = no OverheadInflation diagnostic
1569+
// and v0.8.x/v0.9.1 byte-identical RTA output.
1570+
let diags = make_two_thread_model_diags(None, None);
1571+
assert!(
1572+
!diags
1573+
.iter()
1574+
.any(|d| d.message.starts_with("OverheadInflation")),
1575+
"no Context_Switch_Time must not emit OverheadInflation: {:#?}",
1576+
diags,
1577+
);
1578+
}
1579+
15031580
// ── B1: non-regression — no Locking_Protocol must match v0.7.0 byte-for-byte ─
15041581
#[test]
15051582
fn no_locking_matches_v070() {

0 commit comments

Comments
 (0)