use crate::execution_kernel::ExecutionTaskState;
use crate::guest_call::{ExecutionTaskId, SharedGuestCallStack, ThreadStorage};
use crate::guest_procedure::GuestIsa;
pub(crate) const DEFAULT_COOPERATIVE_THREAD_STACK_SIZE: u32 = 32 * 1024;
pub(crate) const THREAD_NOT_FOUND_ERR: i16 = -618;
pub(crate) const THREAD_PROTOCOL_ERR: i16 = -619;
pub(crate) struct ThreadManager<'a> {
execution: &'a SharedGuestCallStack,
}
impl<'a> ThreadManager<'a> {
pub(crate) fn new(execution: &'a SharedGuestCallStack) -> Self {
Self { execution }
}
pub(crate) fn stack_size(isa: GuestIsa, style: u32, requested: u32) -> Result<u32, i16> {
let size = if requested == 0 {
DEFAULT_COOPERATIVE_THREAD_STACK_SIZE
} else {
requested
};
let minimum = match isa {
GuestIsa::M68k => 8,
GuestIsa::PowerPc => 256,
};
if style != 1 || size < minimum || size > i32::MAX as u32 {
Err(-50)
} else {
Ok(size)
}
}
pub(crate) fn create_pool(
&self,
isa: GuestIsa,
style: u32,
count: i16,
requested: u32,
mut allocate: impl FnMut(u32) -> Option<ThreadStorage>,
) -> Result<(), (i16, Vec<ThreadStorage>)> {
let size = Self::stack_size(isa, style, requested).map_err(|error| (error, Vec::new()))?;
if count < 0 {
return Err((-50, Vec::new()));
}
let mut prepared = Vec::new();
for _ in 0..count {
let Some(mut storage) = allocate(size) else {
return Err((-108, prepared));
};
storage.result_destination = 0;
prepared.push(storage);
if storage.stack_base == 0
|| storage.stack_limit.checked_sub(storage.stack_base) != Some(size)
{
return Err((-108, prepared));
}
}
self.execution.publish_thread_pool(isa, prepared);
Ok(())
}
pub(crate) fn free_count(
&self,
isa: GuestIsa,
style: u32,
minimum_size: u32,
) -> Result<u16, i16> {
if style != 1 || minimum_size > i32::MAX as u32 {
return Err(-50);
}
u16::try_from(self.execution.thread_pool_count(isa, minimum_size)).map_err(|_| -617)
}
pub(crate) fn current_thread(&self) -> u32 {
self.execution.current_task().thread_id()
}
pub(crate) fn stack_space(
&self,
thread: u32,
live_isa: GuestIsa,
live_sp: u32,
application_limit: impl FnOnce(GuestIsa) -> u32,
) -> Result<u32, i16> {
let task = ExecutionTaskId::from_thread_id(self.resolve_thread(thread));
let storage = self
.execution
.thread_storage(task)
.ok_or(THREAD_NOT_FOUND_ERR)?;
let (isa, sp) = self
.execution
.thread_stack_pointer(task, live_isa, live_sp)
.ok_or(THREAD_PROTOCOL_ERR)?;
let base = if task == ExecutionTaskId::APPLICATION {
let application_limit = application_limit(isa);
if application_limit == 0 {
return Err(THREAD_PROTOCOL_ERR);
}
application_limit
} else {
if storage.stack_base == 0
|| storage.stack_limit < storage.stack_base
|| sp > storage.stack_limit
{
return Err(THREAD_PROTOCOL_ERR);
}
storage.stack_base
};
Ok(sp.saturating_sub(base))
}
pub(crate) fn resolve_thread(&self, thread: u32) -> u32 {
if thread <= 1 {
self.current_thread()
} else {
thread
}
}
pub(crate) fn state(&self, thread: u32) -> Result<u16, i16> {
self.execution
.scheduling_state(ExecutionTaskId::from_thread_id(self.resolve_thread(thread)))
.map(|state| match state {
ExecutionTaskState::Ready => 0,
ExecutionTaskState::Stopped => 1,
ExecutionTaskState::Running => 2,
})
.ok_or(THREAD_NOT_FOUND_ERR)
}
pub(crate) fn task_reference(&self) -> u32 {
ExecutionTaskId::APPLICATION.thread_id()
}
pub(crate) fn state_given_task(&self, reference: u32, thread: u32) -> Result<u16, i16> {
if reference != self.task_reference() {
return Err(THREAD_PROTOCOL_ERR);
}
self.state(thread)
}
pub(crate) fn ready_given_task(&self, reference: u32, thread: u32) -> i16 {
if reference != self.task_reference() {
return THREAD_PROTOCOL_ERR;
}
let task = ExecutionTaskId::from_thread_id(self.resolve_thread(thread));
match self.execution.scheduling_state(task) {
None => THREAD_NOT_FOUND_ERR,
Some(ExecutionTaskState::Stopped) => {
if self
.execution
.set_scheduling_state(task, ExecutionTaskState::Ready)
{
0
} else {
THREAD_PROTOCOL_ERR
}
}
Some(_) => THREAD_PROTOCOL_ERR,
}
}
pub(crate) fn begin_critical(&self) -> i16 {
self.execution.begin_critical();
0
}
pub(crate) fn end_critical(&self) -> i16 {
if self.execution.end_critical() {
0
} else {
THREAD_PROTOCOL_ERR
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thread_pool_preparation_preserves_existing_entries_and_returns_every_reserved_stack() {
for isa in [GuestIsa::M68k, GuestIsa::PowerPc] {
let execution = SharedGuestCallStack::default();
let manager = ThreadManager::new(&execution);
let storage = |base| ThreadStorage {
stack_base: base,
stack_limit: base + 1024,
result_destination: 42,
managed_pointer: isa == GuestIsa::PowerPc,
};
manager
.create_pool(isa, 1, 1, 1024, |_| Some(storage(0x1000)))
.unwrap();
let mut allocations = 0;
let failure = manager
.create_pool(isa, 1, 3, 1024, |_| {
allocations += 1;
if allocations == 3 {
None
} else {
Some(storage(0x2000 + allocations * 1024))
}
})
.unwrap_err();
assert_eq!(failure.0, -108);
assert_eq!(failure.1.len(), 2);
assert!(failure.1.iter().all(|stack| stack.result_destination == 0));
assert_eq!(manager.free_count(isa, 1, 0), Ok(1));
assert_eq!(manager.free_count(isa, 1, 2048), Ok(0));
assert!(manager
.create_pool(isa, 0, 1, 1024, |_| panic!(
"invalid style must not allocate"
))
.is_err());
assert!(manager
.create_pool(isa, 1, -1, 1024, |_| panic!(
"negative count must not allocate"
))
.is_err());
assert_eq!(execution.create_task().unwrap().thread_id(), 3);
}
}
}