diff --git a/.cargo/config.toml b/.cargo/config.toml index d9fa1ac..ae7a271 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,7 @@ [target.riscv64gc-unknown-none-elf] runner = "qemu-system-riscv64 -machine virt -nographic -bios default -kernel" rustflags = ["-C", "panic=abort"] + +[env] +# Kernel tables are large fixed arrays; tests build `Kernel` by value. +RUST_MIN_STACK = "33554432" diff --git a/Makefile b/Makefile index e679cfc..fb2cf7e 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,24 @@ -.PHONY: check fmt clippy test kani build all image run +.PHONY: check fmt clippy test kani build all image run ci kernel-fmt kernel-clippy kernel-test RISCV_TARGET = riscv64gc-unknown-none-elf KERNEL_BIN = target/$(RISCV_TARGET)/release/adapter-riscv +KERNEL_CRATES = -p types -p domain -p kernel -p platform + all: check fmt clippy test +# Gate for kernel work: formatting, lints, unit tests, Kani proofs, RISC-V build. +ci: kernel-fmt kernel-clippy kernel-test kani build + +kernel-fmt: + cargo fmt $(KERNEL_CRATES) -p adapter-riscv -- --check + +kernel-clippy: + cargo clippy $(KERNEL_CRATES) --all-targets -- -D warnings + +kernel-test: + cargo test $(KERNEL_CRATES) + check: cargo check --workspace diff --git a/crates/apps/snake/src/renderer.rs b/crates/apps/snake/src/renderer.rs index c90340b..7303b67 100644 --- a/crates/apps/snake/src/renderer.rs +++ b/crates/apps/snake/src/renderer.rs @@ -4,7 +4,7 @@ const CELL_SIZE: u16 = 24; const BG_COLOR: u32 = 0xFF202040; const SNAKE_COLOR: u32 = 0xFF00FF00; const HEAD_COLOR: u32 = 0xFF00CC00; -const FOOD_COLOR: u32 = 0xFFFF0000; +pub(crate) const FOOD_COLOR: u32 = 0xFFFF0000; const BORDER_COLOR: u32 = 0xFF505080; #[derive(Clone, Copy)] diff --git a/crates/apps/snake/src/tests/renderer.rs b/crates/apps/snake/src/tests/renderer.rs index 2f118f7..95e25cc 100644 --- a/crates/apps/snake/src/tests/renderer.rs +++ b/crates/apps/snake/src/tests/renderer.rs @@ -28,5 +28,5 @@ fn food_is_rendered() { let count = render(&snake, &mut commands); // Food is command index 2 (after bg and border) assert!(count > 2); - assert_eq!(commands[2].color, 0xFF0040); + assert_eq!(commands[2].color, crate::renderer::FOOD_COLOR); } diff --git a/crates/domain/Cargo.toml b/crates/domain/Cargo.toml index 15a24d6..846a6e1 100644 --- a/crates/domain/Cargo.toml +++ b/crates/domain/Cargo.toml @@ -5,3 +5,6 @@ edition = "2024" [dependencies] types = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] } diff --git a/crates/domain/src/capability/mod.rs b/crates/domain/src/capability/mod.rs index 88ee80c..3bbe6f9 100644 --- a/crates/domain/src/capability/mod.rs +++ b/crates/domain/src/capability/mod.rs @@ -8,7 +8,8 @@ use crate::{error::KernelError, resource::Resource}; #[path = "tests.rs"] mod tests; -const MAX_CAPS_PER_PROCESS: usize = 16; +// Sized so that every representable `CapSlot` indexes a real slot. +pub const MAX_CAPS_PER_PROCESS: usize = CapSlot::MAX as usize + 1; pub struct Capability { resource: Resource, @@ -76,3 +77,9 @@ impl CapabilityTable { Ok(()) } } + +impl Default for CapabilityTable { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/domain/src/endpoint/mod.rs b/crates/domain/src/endpoint/mod.rs index 948ff41..8de00cc 100644 --- a/crates/domain/src/endpoint/mod.rs +++ b/crates/domain/src/endpoint/mod.rs @@ -113,15 +113,19 @@ impl EndpointTable { } pub fn remove_thread_from_all(&mut self, thread: ThreadId) { - for slot in &mut self.endpoints { - if let Some(ep) = slot { - ep.remove_sender(thread); - ep.remove_receiver(thread); - } + for ep in self.endpoints.iter_mut().flatten() { + ep.remove_sender(thread); + ep.remove_receiver(thread); } } } +impl Default for EndpointTable { + fn default() -> Self { + Self::new() + } +} + pub trait MessagePassing { fn send( &mut self, diff --git a/crates/domain/src/endpoint/wait_queue.rs b/crates/domain/src/endpoint/wait_queue.rs index 80d5ead..833b217 100644 --- a/crates/domain/src/endpoint/wait_queue.rs +++ b/crates/domain/src/endpoint/wait_queue.rs @@ -44,3 +44,9 @@ impl WaitQueue { self.count == 0 } } + +impl Default for WaitQueue { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/domain/src/irq/mod.rs b/crates/domain/src/irq/mod.rs index 335d4c4..d8530ce 100644 --- a/crates/domain/src/irq/mod.rs +++ b/crates/domain/src/irq/mod.rs @@ -36,3 +36,9 @@ impl IrqRoutingTable { self.owners[irq.raw() as usize] } } + +impl Default for IrqRoutingTable { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/domain/src/memory/mod.rs b/crates/domain/src/memory/mod.rs index a5164a7..6702fa9 100644 --- a/crates/domain/src/memory/mod.rs +++ b/crates/domain/src/memory/mod.rs @@ -104,10 +104,11 @@ impl RegionTable { base: PhysicalAddress, ) -> Option<(RegionId, &MemoryRegion)> { for (i, slot) in self.regions.iter().enumerate() { - if let Some(region) = slot { - if region.owner() == owner && region.base() == base { - return Some((RegionId::from_raw(i as u16), region)); - } + if let Some(region) = slot + && region.owner() == owner + && region.base() == base + { + return Some((RegionId::from_raw(i as u16), region)); } } None @@ -120,3 +121,9 @@ impl RegionTable { .filter(move |r| r.owner() == process) } } + +impl Default for RegionTable { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/domain/src/memory/page_pool.rs b/crates/domain/src/memory/page_pool.rs index 23c077a..98a0c5b 100644 --- a/crates/domain/src/memory/page_pool.rs +++ b/crates/domain/src/memory/page_pool.rs @@ -12,9 +12,7 @@ pub struct PagePool { impl PagePool { pub fn new(base: PhysicalAddress, total_pages: usize) -> Self { let mut pages = [false; MAX_PAGES]; - for i in 0..total_pages { - pages[i] = true; - } + pages[..total_pages].fill(true); Self { pages, base, diff --git a/crates/domain/src/process/mod.rs b/crates/domain/src/process/mod.rs index c717ed8..302abfa 100644 --- a/crates/domain/src/process/mod.rs +++ b/crates/domain/src/process/mod.rs @@ -124,3 +124,9 @@ impl ProcessTable { .ok_or(KernelError::InvalidProcess(id)) } } + +impl Default for ProcessTable { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/domain/src/scheduler/multi_core.rs b/crates/domain/src/scheduler/multi_core.rs index 50b99ea..58154ce 100644 --- a/crates/domain/src/scheduler/multi_core.rs +++ b/crates/domain/src/scheduler/multi_core.rs @@ -47,13 +47,13 @@ impl MultiCoreScheduler { fn least_loaded_core(&self) -> Option { (0..self.num_cores) - .map(|i| CoreId::from_raw(i)) + .map(CoreId::from_raw) .min_by_key(|&c| self.thread_count_on(c)) } fn most_loaded_core(&self) -> Option { (0..self.num_cores) - .map(|i| CoreId::from_raw(i)) + .map(CoreId::from_raw) .max_by_key(|&c| self.thread_count_on(c)) } } @@ -69,7 +69,7 @@ impl Scheduler for MultiCoreScheduler { let idx = thread.raw() as usize; if let Some(placement) = self.placements[idx].take() { let core_idx = placement.core.raw() as usize; - self.cores[core_idx].remove(thread, placement.priority); + self.cores[core_idx].remove(thread); if self.cores[core_idx].current() == Some(thread) { self.cores[core_idx].clear_current(); } @@ -85,13 +85,12 @@ impl Scheduler for MultiCoreScheduler { if let Some(placement) = &self.placements[idx] { let current_priority = placement.priority; let core_idx = core.raw() as usize; - if let Some(highest) = self.cores[core_idx].highest_waiting_priority() { - if highest > current_priority { - if let Some(next) = self.cores[core_idx].pick_next() { - self.cores[core_idx].add(current, current_priority); - return SchedulerDecision::Preempt(next); - } - } + if let Some(highest) = self.cores[core_idx].highest_waiting_priority() + && highest > current_priority + && let Some(next) = self.cores[core_idx].pick_next() + { + self.cores[core_idx].add(current, current_priority); + return SchedulerDecision::Preempt(next); } } SchedulerDecision::Continue diff --git a/crates/domain/src/scheduler/per_core.rs b/crates/domain/src/scheduler/per_core.rs index 7706bc6..34ac853 100644 --- a/crates/domain/src/scheduler/per_core.rs +++ b/crates/domain/src/scheduler/per_core.rs @@ -29,12 +29,12 @@ impl PerCoreScheduler { self.queue.add(thread, priority); } - pub fn remove(&mut self, thread: ThreadId, priority: Priority) { - self.queue.remove(thread, priority); + pub fn remove(&mut self, thread: ThreadId) { + self.queue.remove(thread); } pub fn pick_next(&mut self) -> Option { - let (thread, _priority) = self.queue.next()?; + let (thread, _priority) = self.queue.pop_highest()?; self.current = Some(thread); Some(thread) } @@ -56,6 +56,10 @@ impl PerCoreScheduler { } pub fn len(&self) -> usize { - self.queue.bitmap().count_ones() as usize + self.queue.len() + } + + pub fn is_empty(&self) -> bool { + self.queue.is_empty() } } diff --git a/crates/domain/src/scheduler/priority_queue.rs b/crates/domain/src/scheduler/priority_queue.rs index 9fc84cb..20f5973 100644 --- a/crates/domain/src/scheduler/priority_queue.rs +++ b/crates/domain/src/scheduler/priority_queue.rs @@ -2,71 +2,42 @@ use types::bounded::Priority; use types::id::ThreadId; const NUM_PRIORITIES: usize = 32; -const MAX_THREADS_PER_PRIORITY: usize = 16; +const MAX_THREADS: usize = crate::thread::MAX_THREADS; -struct RunList { - threads: [Option; MAX_THREADS_PER_PRIORITY], - head: usize, - tail: usize, - count: u8, +#[derive(Clone, Copy)] +struct Links { + prev: Option, + next: Option, + queued_at: Option, } -impl RunList { - fn new() -> Self { - Self { - threads: [None; MAX_THREADS_PER_PRIORITY], - head: 0, - tail: 0, - count: 0, - } - } - - fn push(&mut self, thread: ThreadId) -> bool { - match self.threads.iter_mut().find(|s| s.is_none()) { - Some(slot) => { - *slot = Some(thread); - self.count += 1; - true - } - None => false, - } - } - - fn pop(&mut self) -> Option { - if self.count == 0 { - return None; - } - let slot = self.threads.iter_mut().find(|s| s.is_some())?; - let thread = slot.take(); - self.count -= 1; - thread - } - - fn remove(&mut self, thread: ThreadId) -> bool { - for slot in &mut self.threads { - if *slot == Some(thread) { - *slot = None; - self.count -= 1; - return true; - } - } - false - } - - fn is_empty(&self) -> bool { - self.count == 0 - } +#[derive(Clone, Copy)] +struct ListEnds { + head: Option, + tail: Option, } +/// Ready queue: one FIFO per priority level, threaded through an +/// intrusive doubly linked list indexed by thread id. A thread is in at +/// most one list, so capacity never runs out and every op is O(1). pub struct PriorityQueue { - queues: [RunList; NUM_PRIORITIES], + lists: [ListEnds; NUM_PRIORITIES], + links: [Links; MAX_THREADS], bitmap: u32, } impl PriorityQueue { pub fn new() -> Self { Self { - queues: core::array::from_fn(|_| RunList::new()), + lists: [ListEnds { + head: None, + tail: None, + }; NUM_PRIORITIES], + links: [Links { + prev: None, + next: None, + queued_at: None, + }; MAX_THREADS], bitmap: 0, } } @@ -75,34 +46,84 @@ impl PriorityQueue { self.bitmap } - pub fn add(&mut self, thread: ThreadId, priority: Priority) { - let idx = priority.value() as usize; - let added = self.queues[idx].push(thread); - debug_assert!(added, "scheduler run list full for priority {}", idx); - if added { - self.bitmap |= 1 << idx; - } + pub fn len(&self) -> usize { + self.links.iter().filter(|l| l.queued_at.is_some()).count() } - pub fn remove(&mut self, thread: ThreadId, priority: Priority) { + pub fn contains(&self, thread: ThreadId) -> bool { + self.link(thread).is_some_and(|l| l.queued_at.is_some()) + } + + fn link(&self, thread: ThreadId) -> Option<&Links> { + self.links.get(thread.raw() as usize) + } + + /// Appends `thread` at `priority`. A thread that is already queued is + /// left where it is; returns whether it was added. + pub fn add(&mut self, thread: ThreadId, priority: Priority) -> bool { let idx = priority.value() as usize; - self.queues[idx].remove(thread); - if self.queues[idx].is_empty() { + let t = thread.raw() as usize; + if t >= MAX_THREADS || idx >= NUM_PRIORITIES || self.links[t].queued_at.is_some() { + return false; + } + let old_tail = self.lists[idx].tail; + self.links[t] = Links { + prev: old_tail, + next: None, + queued_at: Some(idx as u8), + }; + match old_tail { + Some(tail) => self.links[tail.raw() as usize].next = Some(thread), + None => self.lists[idx].head = Some(thread), + } + self.lists[idx].tail = Some(thread); + self.bitmap |= 1 << idx; + true + } + + /// Unlinks `thread` from whichever level it is queued at. + pub fn remove(&mut self, thread: ThreadId) -> bool { + let t = thread.raw() as usize; + let Some(Links { + prev, + next, + queued_at: Some(level), + }) = self.links.get(t).copied() + else { + return false; + }; + let idx = level as usize; + match prev { + Some(p) => self.links[p.raw() as usize].next = next, + None => self.lists[idx].head = next, + } + match next { + Some(n) => self.links[n.raw() as usize].prev = prev, + None => self.lists[idx].tail = prev, + } + self.links[t] = Links { + prev: None, + next: None, + queued_at: None, + }; + if self.lists[idx].head.is_none() { self.bitmap &= !(1 << idx); } + true } - pub fn next(&mut self) -> Option<(ThreadId, Priority)> { + fn pop_level(&mut self, idx: usize) -> Option<(ThreadId, Priority)> { + let thread = self.lists[idx].head?; + self.remove(thread); + Some((thread, Priority::new(idx as u8)?)) + } + + pub fn pop_highest(&mut self) -> Option<(ThreadId, Priority)> { if self.bitmap == 0 { return None; } // highest set bit = highest priority - let idx = 31 - self.bitmap.leading_zeros() as usize; - let thread = self.queues[idx].pop()?; - if self.queues[idx].is_empty() { - self.bitmap &= !(1 << idx); - } - Some((thread, Priority::new(idx as u8)?)) + self.pop_level(31 - self.bitmap.leading_zeros() as usize) } pub fn highest_priority(&self) -> Option { @@ -117,15 +138,16 @@ impl PriorityQueue { if self.bitmap == 0 { return None; } - let idx = self.bitmap.trailing_zeros() as usize; - let thread = self.queues[idx].pop()?; - if self.queues[idx].is_empty() { - self.bitmap &= !(1 << idx); - } - Some((thread, Priority::new(idx as u8)?)) + self.pop_level(self.bitmap.trailing_zeros() as usize) } pub fn is_empty(&self) -> bool { self.bitmap == 0 } } + +impl Default for PriorityQueue { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/domain/src/scheduler/tests.rs b/crates/domain/src/scheduler/tests.rs index ea3bb03..fb5113a 100644 --- a/crates/domain/src/scheduler/tests.rs +++ b/crates/domain/src/scheduler/tests.rs @@ -22,7 +22,7 @@ fn prio(value: u8) -> Priority { #[test] fn empty_queue_returns_none() { let mut q = PriorityQueue::new(); - assert!(q.next().is_none()); + assert!(q.pop_highest().is_none()); assert!(q.is_empty()); } @@ -31,7 +31,7 @@ fn single_thread() { let mut q = PriorityQueue::new(); q.add(thread(1), prio(10)); - let (t, p) = q.next().unwrap(); + let (t, p) = q.pop_highest().unwrap(); assert_eq!(t, thread(1)); assert_eq!(p, prio(10)); } @@ -43,7 +43,7 @@ fn highest_priority_runs_first() { q.add(thread(2), prio(20)); q.add(thread(3), prio(10)); - let (t, _) = q.next().unwrap(); + let (t, _) = q.pop_highest().unwrap(); assert_eq!(t, thread(2)); } @@ -54,10 +54,10 @@ fn same_priority_is_fifo() { q.add(thread(2), prio(10)); q.add(thread(3), prio(10)); - assert_eq!(q.next().unwrap().0, thread(1)); - assert_eq!(q.next().unwrap().0, thread(2)); - assert_eq!(q.next().unwrap().0, thread(3)); - assert!(q.next().is_none()); + assert_eq!(q.pop_highest().unwrap().0, thread(1)); + assert_eq!(q.pop_highest().unwrap().0, thread(2)); + assert_eq!(q.pop_highest().unwrap().0, thread(3)); + assert!(q.pop_highest().is_none()); } #[test] @@ -66,18 +66,18 @@ fn remove_thread() { q.add(thread(1), prio(10)); q.add(thread(2), prio(10)); - q.remove(thread(1), prio(10)); + q.remove(thread(1)); - let (t, _) = q.next().unwrap(); + let (t, _) = q.pop_highest().unwrap(); assert_eq!(t, thread(2)); - assert!(q.next().is_none()); + assert!(q.pop_highest().is_none()); } #[test] fn remove_clears_bitmap_when_empty() { let mut q = PriorityQueue::new(); q.add(thread(1), prio(10)); - q.remove(thread(1), prio(10)); + q.remove(thread(1)); assert!(q.is_empty()); } @@ -89,9 +89,9 @@ fn drain_all_priorities() { q.add(thread(2), prio(15)); q.add(thread(3), prio(31)); - assert_eq!(q.next().unwrap().0, thread(3)); // highest first - assert_eq!(q.next().unwrap().0, thread(2)); - assert_eq!(q.next().unwrap().0, thread(1)); // lowest last + assert_eq!(q.pop_highest().unwrap().0, thread(3)); // highest first + assert_eq!(q.pop_highest().unwrap().0, thread(2)); + assert_eq!(q.pop_highest().unwrap().0, thread(1)); // lowest last assert!(q.is_empty()); } @@ -137,7 +137,7 @@ fn highest_priority_peeks_without_popping() { q.add(thread(1), prio(5)); q.add(thread(2), prio(20)); assert_eq!(q.highest_priority(), Some(prio(20))); - assert_eq!(q.next().unwrap().0, thread(2)); + assert_eq!(q.pop_highest().unwrap().0, thread(2)); } #[test] @@ -154,7 +154,7 @@ fn steal_lowest_takes_lowest_priority_thread() { let (t, p) = q.steal_lowest().unwrap(); assert_eq!(t, thread(1)); assert_eq!(p, prio(5)); - assert_eq!(q.next().unwrap().0, thread(2)); + assert_eq!(q.pop_highest().unwrap().0, thread(2)); } #[test] @@ -240,3 +240,47 @@ fn multi_threads_on_different_cores() { assert_eq!(s.next(core(0)), Some(thread(1))); assert_eq!(s.next(core(1)), Some(thread(2))); } + +#[test] +fn requeued_thread_goes_behind_same_priority_peers() { + let mut q = PriorityQueue::new(); + let p = Priority::new(5).unwrap(); + q.add(thread(1), p); + q.add(thread(2), p); + + assert_eq!(q.pop_highest().unwrap().0, thread(1)); + q.add(thread(1), p); // preempted, back in the run list + + assert_eq!(q.pop_highest().unwrap().0, thread(2)); + assert_eq!(q.pop_highest().unwrap().0, thread(1)); +} + +#[test] +fn round_robin_survives_wraparound() { + let mut q = PriorityQueue::new(); + let p = Priority::new(5).unwrap(); + q.add(thread(1), p); + q.add(thread(2), p); + q.add(thread(3), p); + for _ in 0..40 { + let (a, _) = q.pop_highest().unwrap(); + q.add(a, p); + } + // 40 rotations of 3 threads: 40 % 3 == 1, so thread 2 is at the head + assert_eq!(q.pop_highest().unwrap().0, thread(2)); + assert_eq!(q.pop_highest().unwrap().0, thread(3)); + assert_eq!(q.pop_highest().unwrap().0, thread(1)); +} + +#[test] +fn removed_thread_is_skipped_and_order_kept() { + let mut q = PriorityQueue::new(); + let p = Priority::new(5).unwrap(); + q.add(thread(1), p); + q.add(thread(2), p); + q.add(thread(3), p); + q.remove(thread(2)); + assert_eq!(q.pop_highest().unwrap().0, thread(1)); + assert_eq!(q.pop_highest().unwrap().0, thread(3)); + assert!(q.pop_highest().is_none()); +} diff --git a/crates/domain/src/thread/mod.rs b/crates/domain/src/thread/mod.rs index 28acf33..bacbdb0 100644 --- a/crates/domain/src/thread/mod.rs +++ b/crates/domain/src/thread/mod.rs @@ -303,3 +303,9 @@ impl ThreadTable { .filter(move |t| t.process() == process) } } + +impl Default for ThreadTable { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/elf/src/lib.rs b/crates/elf/src/lib.rs index d4fb188..25ecf48 100644 --- a/crates/elf/src/lib.rs +++ b/crates/elf/src/lib.rs @@ -134,7 +134,7 @@ impl ElfInfo { pub fn total_pages(&self) -> usize { let mut total = 0; for seg in self.segments() { - total += (seg.mem_size + 4095) / 4096; + total += seg.mem_size.div_ceil(4096); } total } diff --git a/crates/kernel/Cargo.toml b/crates/kernel/Cargo.toml index 06ecabe..45fa743 100644 --- a/crates/kernel/Cargo.toml +++ b/crates/kernel/Cargo.toml @@ -7,3 +7,6 @@ edition = "2024" types = { workspace = true } domain = { workspace = true } platform = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] } diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index adb7953..a515529 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -196,20 +196,20 @@ impl Kernel

{ if current == tid { return; } - if let Ok(t) = self.threads.get_mut(current) { - if matches!(t.state(), ThreadState::Running(_)) { - let priority = t.priority(); - let _ = t.preempt(); - self.scheduler.add(current, priority, core); - } + if let Ok(t) = self.threads.get_mut(current) + && matches!(t.state(), ThreadState::Running(_)) + { + let priority = t.priority(); + let _ = t.preempt(); + self.scheduler.add(current, priority, core); } } - if let Ok(t) = self.threads.get(tid) { - if matches!(t.state(), ThreadState::Ready) { - self.scheduler.next(core); - let _ = self.threads.get_mut(tid).map(|t| t.set_running(core)); - } + if let Ok(t) = self.threads.get(tid) + && matches!(t.state(), ThreadState::Ready) + { + self.scheduler.next(core); + let _ = self.threads.get_mut(tid).map(|t| t.set_running(core)); } } @@ -265,26 +265,19 @@ impl Kernel

{ pub(crate) fn cleanup_threads(&mut self, thread_ids: &[Option]) { use domain::scheduler::Scheduler; - for slot in thread_ids { - if let Some(tid) = slot { - self.endpoints.remove_thread_from_all(*tid); - self.cleanup_wait_any(*tid); - self.scheduler.remove(*tid); - self.pending_messages[tid.raw() as usize] = None; - self.pending_results[tid.raw() as usize] = None; - let _ = self.threads.destroy(*tid); - } + for tid in thread_ids.iter().flatten() { + self.endpoints.remove_thread_from_all(*tid); + self.cleanup_wait_any(*tid); + self.scheduler.remove(*tid); + self.pending_messages[tid.raw() as usize] = None; + self.pending_results[tid.raw() as usize] = None; + let _ = self.threads.destroy(*tid); } } pub(crate) fn cleanup_wait_any(&mut self, tid: ThreadId) { - if let Some(wait_set) = self.wait_sets[tid.raw() as usize].take() { - for ep_slot in wait_set.endpoints() { - if let Some(_slot) = ep_slot { - self.endpoints.remove_thread_from_all(tid); - break; - } - } + if self.wait_sets[tid.raw() as usize].take().is_some() { + self.endpoints.remove_thread_from_all(tid); } } @@ -312,12 +305,12 @@ impl Kernel

{ .endpoints .get(*id) .map(|ep| *ep.generation()) - .unwrap_or_else(|| Generation::initial()), + .unwrap_or_else(Generation::initial), Resource::Memory(id) => self .regions .get(*id) .map(|r| *r.generation()) - .unwrap_or_else(|| Generation::initial()), + .unwrap_or_else(Generation::initial), _ => Generation::initial(), } } diff --git a/crates/kernel/src/syscall/capability.rs b/crates/kernel/src/syscall/capability.rs index 06faa28..fad7e3e 100644 --- a/crates/kernel/src/syscall/capability.rs +++ b/crates/kernel/src/syscall/capability.rs @@ -42,11 +42,11 @@ impl Kernel

{ return SyscallOutcome::Complete(SyscallResult::Error(e)); } - if let domain::resource::Resource::Memory(region_id) = &resource { - if let Some(region) = self.regions.get(*region_id) { - self.platform - .map_shared(target_pid, region.base(), region.size(), restricted); - } + if let domain::resource::Resource::Memory(region_id) = &resource + && let Some(region) = self.regions.get(*region_id) + { + self.platform + .map_shared(target_pid, region.base(), region.size(), restricted); } SyscallOutcome::Complete(SyscallResult::Ok) diff --git a/crates/kernel/src/syscall/fault.rs b/crates/kernel/src/syscall/fault.rs index 0af185b..d223822 100644 --- a/crates/kernel/src/syscall/fault.rs +++ b/crates/kernel/src/syscall/fault.rs @@ -15,34 +15,31 @@ impl Kernel

{ }; // Try to deliver fault to a fault handler endpoint (if configured) - if let Ok(process) = self.processes.get(pid) { - if let Some(handler_slot) = process.fault_handler() { - if let Ok(cap) = process.capabilities().get(handler_slot) { - if let domain::resource::Resource::Endpoint(ep_id) = cap.resource() { - let msg = domain::message::Message::from_words([ - pid.raw() as usize, - fault.code(), - fault.address(), - fault_pc, - ]); + if let Ok(process) = self.processes.get(pid) + && let Some(handler_slot) = process.fault_handler() + && let Ok(cap) = process.capabilities().get(handler_slot) + && let domain::resource::Resource::Endpoint(ep_id) = cap.resource() + { + let msg = domain::message::Message::from_words([ + pid.raw() as usize, + fault.code(), + fault.address(), + fault_pc, + ]); - if let Some(ep) = self.endpoints.get_mut(*ep_id) { - if ep.has_waiting_receiver() { - if let Some(receiver_tid) = ep.dequeue_receiver() { - self.pending_results[receiver_tid.raw() as usize] = - Some(crate::SyscallResult::Message(msg)); + if let Some(ep) = self.endpoints.get_mut(*ep_id) + && ep.has_waiting_receiver() + && let Some(receiver_tid) = ep.dequeue_receiver() + { + self.pending_results[receiver_tid.raw() as usize] = + Some(crate::SyscallResult::Message(msg)); - if let Ok(receiver) = self.threads.get_mut(receiver_tid) { - let priority = receiver.priority(); - let _ = receiver.unblock(); - let core = self.platform.core_id(); - use domain::scheduler::Scheduler; - self.scheduler.add(receiver_tid, priority, core); - } - } - } - } - } + if let Ok(receiver) = self.threads.get_mut(receiver_tid) { + let priority = receiver.priority(); + let _ = receiver.unblock(); + let core = self.platform.core_id(); + use domain::scheduler::Scheduler; + self.scheduler.add(receiver_tid, priority, core); } } } diff --git a/crates/kernel/src/syscall/ipc.rs b/crates/kernel/src/syscall/ipc.rs index 51b4e9c..c659a7a 100644 --- a/crates/kernel/src/syscall/ipc.rs +++ b/crates/kernel/src/syscall/ipc.rs @@ -180,7 +180,7 @@ impl Kernel

{ message.is_some(), "sender in wait queue had no pending message" ); - let message = message.unwrap_or_else(|| domain::message::Message::empty()); + let message = message.unwrap_or_else(domain::message::Message::empty); self.pending_results[sender_tid.raw() as usize] = Some(SyscallResult::Ok); @@ -289,54 +289,46 @@ impl Kernel

{ )); } - for ep_slot in sources.endpoints() { - if let Some(slot) = ep_slot { - let ep_id = - match self.check_cap(caller_pid, *slot, types::permission::Permission::Read) { - Ok(cap) => match cap.resource() { - domain::resource::Resource::Endpoint(id) => *id, - _ => continue, - }, - Err(_) => continue, - }; + for slot in sources.endpoints().iter().flatten() { + let ep_id = match self.check_cap(caller_pid, *slot, types::permission::Permission::Read) + { + Ok(cap) => match cap.resource() { + domain::resource::Resource::Endpoint(id) => *id, + _ => continue, + }, + Err(_) => continue, + }; - if let Some(ep) = self.endpoints.get_mut(ep_id) { - if ep.has_waiting_sender() { - return SyscallOutcome::Complete(SyscallResult::WakeReason( - domain::syscall::WakeReason::MessageReady(*slot), - )); - } - } + if let Some(ep) = self.endpoints.get_mut(ep_id) + && ep.has_waiting_sender() + { + return SyscallOutcome::Complete(SyscallResult::WakeReason( + domain::syscall::WakeReason::MessageReady(*slot), + )); } } for irq_slot in sources.interrupts() { - if let Some(slot) = irq_slot { - if let Ok(cap) = + if let Some(slot) = irq_slot + && let Ok(cap) = self.check_cap(caller_pid, *slot, types::permission::Permission::Read) - { - if let domain::resource::Resource::Interrupt(irq_id) = cap.resource() { - if self.irqs.owner(*irq_id).is_some() { - return SyscallOutcome::Complete(SyscallResult::WakeReason( - domain::syscall::WakeReason::InterruptFired(*slot), - )); - } - } - } + && let domain::resource::Resource::Interrupt(irq_id) = cap.resource() + && self.irqs.owner(*irq_id).is_some() + { + return SyscallOutcome::Complete(SyscallResult::WakeReason( + domain::syscall::WakeReason::InterruptFired(*slot), + )); } } for ep_slot in sources.endpoints() { - if let Some(slot) = ep_slot { - if let Ok(cap) = + if let Some(slot) = ep_slot + && let Ok(cap) = self.check_cap(caller_pid, *slot, types::permission::Permission::Read) - { - if let domain::resource::Resource::Endpoint(id) = cap.resource() { - if let Some(ep) = self.endpoints.get_mut(*id) { - let _ = ep.enqueue_receiver(caller_tid); - } - } - } + && let domain::resource::Resource::Endpoint(id) = cap.resource() + && let Some(ep) = self.endpoints.get_mut(*id) + { + let _ = ep.enqueue_receiver(caller_tid); } } diff --git a/crates/kernel/src/syscall/lifecycle.rs b/crates/kernel/src/syscall/lifecycle.rs index a2f4264..4ed15ee 100644 --- a/crates/kernel/src/syscall/lifecycle.rs +++ b/crates/kernel/src/syscall/lifecycle.rs @@ -1,4 +1,3 @@ -use domain::process::MAX_THREADS_PER_PROCESS; use platform::Platform; use super::{SyscallOutcome, SyscallResult}; diff --git a/crates/kernel/src/tests/capability.rs b/crates/kernel/src/tests/capability.rs index 0a61f78..23cba6b 100644 --- a/crates/kernel/src/tests/capability.rs +++ b/crates/kernel/src/tests/capability.rs @@ -197,3 +197,18 @@ fn revoke_syscall_without_revoke_permission_fails() { SyscallOutcome::Complete(SyscallResult::Error(_)) )); } + +#[test] +fn every_representable_slot_is_rejected_not_panicking() { + let (mut k, _) = kernel_with_process(); + for raw in 1..=CapSlot::MAX { + let outcome = k.handle_syscall(Syscall::Send { + endpoint: CapSlot::new(raw).unwrap(), + message: domain::message::Message::empty(), + }); + assert!( + matches!(outcome, SyscallOutcome::Complete(SyscallResult::Error(_))), + "slot {raw} should be an error" + ); + } +} diff --git a/crates/kernel/src/tests/exit.rs b/crates/kernel/src/tests/exit.rs index 423d134..7ca2c94 100644 --- a/crates/kernel/src/tests/exit.rs +++ b/crates/kernel/src/tests/exit.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{SyscallOutcome, SyscallResult}; +use crate::SyscallOutcome; use domain::syscall::Syscall; #[test] @@ -21,5 +21,5 @@ fn exit_thread_last_thread_destroys_process() { let (mut k, pid) = kernel_with_process(); k.handle_syscall(Syscall::ExitThread); // Process should be gone since it had only one thread - assert!(k.process_exists(pid) == false); + assert!(!k.process_exists(pid)); } diff --git a/crates/kernel/src/tests/helpers.rs b/crates/kernel/src/tests/helpers.rs index 7e3153e..2704902 100644 --- a/crates/kernel/src/tests/helpers.rs +++ b/crates/kernel/src/tests/helpers.rs @@ -65,20 +65,20 @@ impl Kernel { if current == tid { return; } - if let Ok(t) = self.threads.get_mut(current) { - if matches!(t.state(), ThreadState::Running(_)) { - let priority = t.priority(); - let _ = t.preempt(); - self.scheduler.add(current, priority, core); - } + if let Ok(t) = self.threads.get_mut(current) + && matches!(t.state(), ThreadState::Running(_)) + { + let priority = t.priority(); + let _ = t.preempt(); + self.scheduler.add(current, priority, core); } } - if let Ok(t) = self.threads.get(tid) { - if matches!(t.state(), ThreadState::Ready) { - self.scheduler.next(core); - let _ = self.threads.get_mut(tid).map(|t| t.set_running(core)); - } + if let Ok(t) = self.threads.get(tid) + && matches!(t.state(), ThreadState::Ready) + { + self.scheduler.next(core); + let _ = self.threads.get_mut(tid).map(|t| t.set_running(core)); } } diff --git a/crates/kernel/src/tests/ipc.rs b/crates/kernel/src/tests/ipc.rs index 882298a..2602f96 100644 --- a/crates/kernel/src/tests/ipc.rs +++ b/crates/kernel/src/tests/ipc.rs @@ -69,7 +69,7 @@ fn send_without_receiver_blocks_caller() { #[test] fn receive_without_sender_blocks_caller() { - let (mut k, _, pid_b, _, cap_b, tid_b) = setup_ipc_kernel(); + let (mut k, _, _pid_b, _, cap_b, tid_b) = setup_ipc_kernel(); // Switch to thread B to call Receive k.switch_to_thread_for_test(tid_b); @@ -83,7 +83,7 @@ fn receive_without_sender_blocks_caller() { #[test] fn send_with_waiting_receiver_delivers_and_switches() { - let (mut k, pid_a, pid_b, cap_a, cap_b, tid_b) = setup_ipc_kernel(); + let (mut k, _pid_a, _pid_b, cap_a, cap_b, tid_b) = setup_ipc_kernel(); let tid_a = ThreadId::from_raw(0); // Thread B calls Receive first (blocks) @@ -108,8 +108,8 @@ fn send_with_waiting_receiver_delivers_and_switches() { #[test] fn receive_with_waiting_sender_delivers_immediately() { - let (mut k, pid_a, pid_b, cap_a, cap_b, tid_b) = setup_ipc_kernel(); - let tid_a = ThreadId::from_raw(0); + let (mut k, _pid_a, _pid_b, cap_a, cap_b, tid_b) = setup_ipc_kernel(); + let _tid_a = ThreadId::from_raw(0); // Thread A sends first (blocks) let msg = Message::from_words([99, 0, 0, 0]); @@ -184,7 +184,7 @@ fn ack_interrupt_returns_ok() { #[test] fn wait_any_with_ready_source_returns_immediately() { - let (mut k, pid_a, _, cap_a, cap_b, tid_b) = setup_ipc_kernel(); + let (mut k, _pid_a, _, cap_a, cap_b, tid_b) = setup_ipc_kernel(); let tid_a = ThreadId::from_raw(0); // Thread B sends a message (blocks since no receiver) @@ -206,7 +206,7 @@ fn wait_any_with_ready_source_returns_immediately() { #[test] fn wait_any_with_no_ready_source_blocks() { - let (mut k, pid_a, _, cap_a, _, _) = setup_ipc_kernel(); + let (mut k, _pid_a, _, cap_a, _, _) = setup_ipc_kernel(); let wait_set = WaitSet::new([Some(cap_a), None, None, None], [None, None, None, None]); let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set }); assert!(matches!( diff --git a/crates/kernel/src/tests/mock_platform.rs b/crates/kernel/src/tests/mock_platform.rs index e3e3cd6..a357675 100644 --- a/crates/kernel/src/tests/mock_platform.rs +++ b/crates/kernel/src/tests/mock_platform.rs @@ -41,7 +41,7 @@ impl MockPlatform { impl Platform for MockPlatform { fn configure_protection(&mut self, _process: ProcessId, _regions: &[MemoryRegion]) {} fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! { - loop {} + panic!("enable_user_mode is not supported in tests") } fn set_timer(&mut self, _duration: TimerDuration) {} fn current_ticks(&self) -> Ticks { @@ -65,16 +65,17 @@ impl Platform for MockPlatform { ) { } fn halt(&self) -> ! { - loop {} + panic!("halt") } fn debug_putchar(&self, _byte: u8) {} fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option { for m in &self.mappings { - if let Some(m) = m { - if m.process.raw() == process.raw() && m.va == va.raw() { - return Some(PhysicalAddress::from_raw(m.pa)); - } + if let Some(m) = m + && m.process.raw() == process.raw() + && m.va == va.raw() + { + return Some(PhysicalAddress::from_raw(m.pa)); } } None diff --git a/crates/kernel/src/tests/tick.rs b/crates/kernel/src/tests/tick.rs index ad5d92f..407faa0 100644 --- a/crates/kernel/src/tests/tick.rs +++ b/crates/kernel/src/tests/tick.rs @@ -12,16 +12,16 @@ fn tick_continues_when_time_remaining() { #[test] fn tick_preempts_when_time_expired_and_other_thread_ready() { - let mut k = test_kernel(); + let _k = test_kernel(); let mock = MockPlatform::new(1); let pool = PagePool::new(PhysicalAddress::from_raw(0x8000_0000), 1024); let mut k = Kernel::new(mock, pool); // Boot with quantum=2 so it expires after 2 ticks - let (pid, tid1) = k.boot(Priority::DEFAULT, TimeSlice::new(2)).unwrap(); + let (pid, _tid1) = k.boot(Priority::DEFAULT, TimeSlice::new(2)).unwrap(); // Spawn a second thread so scheduler has someone to switch to - let tid2 = k.spawn_thread_for_test(pid, Priority::DEFAULT, TimeSlice::DEFAULT); + let _tid2 = k.spawn_thread_for_test(pid, Priority::DEFAULT, TimeSlice::DEFAULT); // First tick: time remaining let d1 = k.handle_tick(CoreId::from_raw(0)); diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 616db51..fa3d272 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(kani)"] } diff --git a/crates/types/src/macros.rs b/crates/types/src/macros.rs index c3bd861..118a0b7 100644 --- a/crates/types/src/macros.rs +++ b/crates/types/src/macros.rs @@ -25,7 +25,7 @@ macro_rules! bounded { pub const MAX: $inner = $max; pub fn new(value: $inner) -> Option { - if value >= Self::MIN && value <= Self::MAX { + if (Self::MIN..=Self::MAX).contains(&value) { Some(Self(value)) } else { None