Skip to content

Commit d6699b7

Browse files
committed
test(test_suite): add a new test compute_round_robin
> Performs a compute-intensive task in multiple tasks that run in a > round-robin fashion, and validates the output of each run. > > The intent of this test to detect bugs in the saving and restoring of > registers during a context switch. To this end, the task is designed > to utilize as many floating-point registers as possible. As expected, this test fails on the Arm-M port when hardware FP is enabled (this configuration is not supported yet).
1 parent de707e5 commit d6699b7

File tree

7 files changed

+640
-0
lines changed

7 files changed

+640
-0
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/constance_test_suite/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ tests_selective = []
1818
constance = { path = "../constance" }
1919
wyhash = "0.3.0"
2020
log = "0.4.8"
21+
22+
[dev-dependencies]
23+
env_logger = "0.7.1"
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
//! Performs a compute-intensive task in multiple tasks that run in a
2+
//! round-robin fashion, and validates the output of each run.
3+
//!
4+
//! The intent of this test to detect bugs in the saving and restoring of
5+
//! registers during a context switch. To this end, the task is designed to
6+
//! utilize as many floating-point registers as possible.
7+
use constance::{
8+
kernel::{cfg::CfgBuilder, Hunk, StartupHook, Task, Timer},
9+
prelude::*,
10+
time::Duration,
11+
utils::Init,
12+
};
13+
use core::{
14+
cell::UnsafeCell,
15+
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
16+
};
17+
18+
use super::Driver;
19+
use crate::utils::compute;
20+
21+
const NUM_TASKS: usize = 4;
22+
23+
pub struct App<System> {
24+
timer: Timer<System>,
25+
tasks: [Task<System>; NUM_TASKS],
26+
state: Hunk<System, State>,
27+
}
28+
29+
impl<System: Kernel> App<System> {
30+
pub const fn new<D: Driver<Self>>(b: &mut CfgBuilder<System>) -> Self {
31+
StartupHook::build().start(hook_body::<System, D>).finish(b);
32+
33+
let timer = Timer::build()
34+
.delay(Duration::from_millis(0))
35+
.period(Duration::from_millis(10))
36+
.start(timer_body::<System, D>)
37+
.active(true)
38+
.finish(b);
39+
40+
let mut tasks = [None; NUM_TASKS];
41+
42+
// FIXME: Work-around for `for` being unsupported in `const fn`
43+
let mut i = 0;
44+
while i < NUM_TASKS {
45+
tasks[i] = Some(
46+
Task::build()
47+
.active(true)
48+
.start(worker_body::<System, D>)
49+
.priority(3)
50+
.param(i)
51+
.finish(b),
52+
);
53+
i += 1;
54+
}
55+
56+
// FIXME: Work-around for `Option::unwrap` being not `const fn`
57+
let tasks = if let [Some(t0), Some(t1), Some(t2), Some(t3)] = tasks {
58+
[t0, t1, t2, t3]
59+
} else {
60+
unreachable!()
61+
};
62+
63+
let state = Hunk::<_, State>::build().finish(b);
64+
65+
App {
66+
timer,
67+
tasks,
68+
state,
69+
}
70+
}
71+
}
72+
73+
struct State {
74+
ref_output: UnsafeCell<compute::KernelOutput>,
75+
task_state: [UnsafeCell<TaskState>; NUM_TASKS],
76+
77+
/// The number of times the workload was completed by each task.
78+
run_count: [AtomicUsize; NUM_TASKS],
79+
stop: AtomicBool,
80+
81+
sched_state: UnsafeCell<SchedState>,
82+
}
83+
84+
struct TaskState {
85+
kernel_state: compute::KernelState,
86+
output: compute::KernelOutput,
87+
}
88+
89+
struct SchedState {
90+
cur_task: usize,
91+
time: usize,
92+
}
93+
94+
unsafe impl Sync for State {}
95+
96+
impl Init for State {
97+
const INIT: Self = Self {
98+
ref_output: Init::INIT,
99+
task_state: Init::INIT,
100+
run_count: Init::INIT,
101+
stop: Init::INIT,
102+
sched_state: Init::INIT,
103+
};
104+
}
105+
106+
impl Init for TaskState {
107+
const INIT: Self = Self {
108+
kernel_state: Init::INIT,
109+
output: Init::INIT,
110+
};
111+
}
112+
113+
impl Init for SchedState {
114+
const INIT: Self = Self {
115+
cur_task: 0,
116+
time: 0,
117+
};
118+
}
119+
120+
fn hook_body<System: Kernel, D: Driver<App<System>>>(_: usize) {
121+
let state = &*D::app().state;
122+
123+
// Safety: These are unique references to the contents of respective cells
124+
let ref_output = unsafe { &mut *state.ref_output.get() };
125+
let task_state = unsafe { &mut *state.task_state[0].get() };
126+
127+
// Obtain the refernce output
128+
task_state.kernel_state.run(ref_output);
129+
}
130+
131+
fn worker_body<System: Kernel, D: Driver<App<System>>>(worker_id: usize) {
132+
let App { state, .. } = D::app();
133+
134+
// Safety: This is a unique reference
135+
let task_state = unsafe { &mut *state.task_state[worker_id].get() };
136+
137+
// Safety: A mutable reference to `ref_output` doesn't exist at this point
138+
let ref_output = unsafe { &*state.ref_output.get() };
139+
140+
let mut i = 0;
141+
142+
while !state.stop.load(Ordering::Relaxed) {
143+
i += 1;
144+
log::trace!("[{}] Iteration {}: starting", worker_id, i);
145+
task_state.output = Init::INIT;
146+
147+
// Run the computation
148+
task_state.kernel_state.run(&mut task_state.output);
149+
150+
// Validate the output
151+
log::trace!("[{}] Iteration {}: validating", worker_id, i);
152+
let valid = task_state.output == *ref_output;
153+
if !valid {
154+
stop::<System, D>();
155+
panic!("Output validation failed");
156+
}
157+
158+
log::trace!("[{}] Iteration {}: complete", worker_id, i);
159+
state.run_count[worker_id].fetch_add(1, Ordering::Relaxed);
160+
}
161+
}
162+
163+
fn timer_body<System: Kernel, D: Driver<App<System>>>(_: usize) {
164+
let App { state, tasks, .. } = D::app();
165+
166+
// Safety: This is a unique reference
167+
let sched_state = unsafe { &mut *state.sched_state.get() };
168+
169+
sched_state.time += 1;
170+
171+
// Switch the running task
172+
let new_task = (sched_state.cur_task + 1) % NUM_TASKS;
173+
log::trace!("scheduing tasks[{}]", new_task);
174+
tasks[sched_state.cur_task].set_priority(3).unwrap();
175+
tasks[new_task].set_priority(2).unwrap();
176+
sched_state.cur_task = new_task;
177+
178+
// Wait for several ticks to catch any bugs in context switching
179+
if sched_state.time < 100 {
180+
return;
181+
}
182+
183+
// Check the run count of tasks
184+
let mut run_count = [0; NUM_TASKS];
185+
for (x, y) in state.run_count.iter().zip(run_count.iter_mut()) {
186+
*y = x.load(Ordering::Relaxed);
187+
}
188+
189+
if sched_state.time % 32 == 0 {
190+
log::debug!("run_count = {:?}", run_count);
191+
}
192+
193+
let min_run_count: usize = *run_count.iter().min().unwrap();
194+
let max_run_count: usize = *run_count.iter().max().unwrap();
195+
196+
// Ensure the workload runs for sufficient times
197+
if max_run_count < 3 {
198+
if sched_state.time > 4000 {
199+
// Timeout
200+
stop::<System, D>();
201+
panic!("Timeout");
202+
}
203+
return;
204+
}
205+
206+
// Tasks are scheduled in a round-robin fashion. If there's a task that has
207+
// never completed the workload, something is wrong.
208+
if min_run_count == 0 {
209+
stop::<System, D>();
210+
panic!(
211+
"Too much inbalance between tasks - round-robin scheduling \
212+
might not be working"
213+
);
214+
}
215+
216+
log::info!("Success!");
217+
stop::<System, D>();
218+
D::success();
219+
}
220+
221+
fn stop<System: Kernel, D: Driver<App<System>>>() {
222+
let App { state, timer, .. } = D::app();
223+
224+
log::debug!("Stopping the workers");
225+
timer.stop().unwrap();
226+
state.stop.store(true, Ordering::Relaxed);
227+
}

src/constance_test_suite/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![feature(external_doc)] // `#[doc(include = ...)]`
22
#![feature(const_fn)]
3+
#![feature(const_panic)]
34
#![feature(unsafe_block_in_unsafe_fn)] // `unsafe fn` doesn't imply `unsafe {}`
45
#![deny(unsafe_op_in_unsafe_fn)]
56
#![doc(include = "./lib.md")]
@@ -137,6 +138,7 @@ pub mod kernel_tests {
137138
define_kernel_tests! {
138139
[$]
139140
(mod basic {}, "basic"),
141+
(mod compute_round_robin {}, "compute_round_robin"),
140142
(mod cpu_lock {}, "cpu_lock"),
141143
(mod event_group_interrupt {}, "event_group_interrupt"),
142144
(mod event_group_misc {}, "event_group_misc"),

src/constance_test_suite/src/utils.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
use constance::utils::Init;
33
use core::sync::atomic::{AtomicUsize, Ordering};
44

5+
pub(crate) mod compute;
6+
mod trig;
7+
58
/// An atomic counter for checking an execution sequence.
69
pub(crate) struct SeqTracker {
710
counter: AtomicUsize,

0 commit comments

Comments
 (0)