use alloc::{sync::Arc, vec, vec::Vec};
use crate::{
runtime::{
resource::{
AddressSpaceHandle, AddressSpaceToken, ExecutionContextHandle, StackHandle, TlsHandle,
},
service::{
SchedulerTickCpuTime, SchedulerTickGate, SchedulerTickTaskWork,
SchedulerTickWorkDisposition,
},
task_runtime,
},
sched::{CpuId, SchedulePolicy},
thread::{SchedulerTickWork, TaskError, ThreadHandle, ThreadId},
};
#[repr(C)]
#[derive(Debug, Eq, PartialEq)]
pub struct ThreadResources {
context: ExecutionContextHandle,
stack: StackHandle,
tls: TlsHandle,
address_space: AddressSpaceToken,
}
impl ThreadResources {
pub const NONE: Self = Self {
context: ExecutionContextHandle::NONE,
stack: StackHandle::NONE,
tls: TlsHandle::NONE,
address_space: AddressSpaceToken::NONE,
};
pub const unsafe fn new(
context: ExecutionContextHandle,
stack: StackHandle,
tls: TlsHandle,
address_space: AddressSpaceToken,
) -> Self {
Self {
context,
stack,
tls,
address_space,
}
}
pub const fn context(&self) -> ExecutionContextHandle {
self.context
}
pub const fn stack(&self) -> StackHandle {
self.stack
}
pub const fn tls(&self) -> TlsHandle {
self.tls
}
pub const fn address_space(&self) -> AddressSpaceHandle {
self.address_space.handle()
}
pub(crate) fn replace_address_space(
&mut self,
address_space: AddressSpaceToken,
) -> AddressSpaceToken {
core::mem::replace(&mut self.address_space, address_space)
}
pub(crate) fn take_address_space(&mut self) -> AddressSpaceToken {
core::mem::replace(&mut self.address_space, AddressSpaceToken::NONE)
}
pub(crate) fn release(mut self) -> AddressSpaceToken {
if !self.context.is_none() {
task_runtime::destroy_context(self.context);
self.context = ExecutionContextHandle::NONE;
}
if !self.tls.is_none() {
task_runtime::deallocate_tls(self.tls);
self.tls = TlsHandle::NONE;
}
if !self.stack.is_none() {
task_runtime::deallocate_stack(self.stack);
self.stack = StackHandle::NONE;
}
core::mem::replace(&mut self.address_space, AddressSpaceToken::NONE)
}
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SwitchReason {
Preempted = 1,
Yield = 2,
Blocked = 3,
Exited = 4,
Migrated = 5,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CpuSet {
words: Vec<usize>,
topology_len: usize,
allowed_count: usize,
}
impl CpuSet {
const BITS_PER_WORD: usize = usize::BITS as usize;
pub fn all(cpu_count: usize) -> Self {
let mut words = vec![usize::MAX; cpu_count.div_ceil(Self::BITS_PER_WORD)];
if let Some(last) = words.last_mut()
&& !cpu_count.is_multiple_of(Self::BITS_PER_WORD)
{
*last = (1usize << (cpu_count % Self::BITS_PER_WORD)) - 1;
}
Self {
words,
topology_len: cpu_count,
allowed_count: cpu_count,
}
}
pub fn empty(cpu_count: usize) -> Self {
Self {
words: vec![0; cpu_count.div_ceil(Self::BITS_PER_WORD)],
topology_len: cpu_count,
allowed_count: 0,
}
}
pub fn insert(&mut self, cpu: CpuId) -> bool {
let index = cpu.as_usize();
if index >= self.topology_len {
return false;
}
let mask = 1usize << (index % Self::BITS_PER_WORD);
let word = &mut self.words[index / Self::BITS_PER_WORD];
let changed = *word & mask == 0;
*word |= mask;
if changed {
self.allowed_count += 1;
}
changed
}
pub fn remove(&mut self, cpu: CpuId) -> bool {
let index = cpu.as_usize();
if index >= self.topology_len {
return false;
}
let mask = 1usize << (index % Self::BITS_PER_WORD);
let word = &mut self.words[index / Self::BITS_PER_WORD];
let changed = *word & mask != 0;
*word &= !mask;
if changed {
self.allowed_count -= 1;
}
changed
}
pub(crate) fn clear(&mut self) {
self.words.fill(0);
self.allowed_count = 0;
}
pub fn contains(&self, cpu: CpuId) -> bool {
let index = cpu.as_usize();
index < self.topology_len
&& self.words[index / Self::BITS_PER_WORD] & (1usize << (index % Self::BITS_PER_WORD))
!= 0
}
pub fn topology_len(&self) -> usize {
self.topology_len
}
pub(crate) fn count(&self) -> usize {
self.allowed_count
}
pub fn iter(&self) -> impl Iterator<Item = CpuId> + '_ {
(0..self.topology_len)
.map(|index| CpuId::new(index as u32))
.filter(|cpu| self.contains(*cpu))
}
pub(crate) fn sole_cpu(&self) -> Option<CpuId> {
if self.allowed_count != 1 {
return None;
}
let (word_index, word) = self
.words
.iter()
.copied()
.enumerate()
.find(|(_, word)| *word != 0)?;
let index = word_index * Self::BITS_PER_WORD + word.trailing_zeros() as usize;
(index < self.topology_len).then_some(CpuId::new(index as u32))
}
pub(crate) fn is_migration_capable(&self) -> bool {
self.allowed_count > 1
}
pub fn covers(&self, required: &Self) -> bool {
self.topology_len == required.topology_len
&& self
.words
.iter()
.zip(&required.words)
.all(|(allowed, is_required)| allowed & is_required == *is_required)
}
pub(crate) fn copy_from_set(&mut self, source: &Self) -> Result<(), TaskError> {
if self.topology_len != source.topology_len {
return Err(TaskError::InvalidConfiguration);
}
self.words.copy_from_slice(&source.words);
self.allowed_count = source.allowed_count;
Ok(())
}
pub(crate) fn first_intersection(
&self,
other: &Self,
mut accepts: impl FnMut(CpuId) -> bool,
) -> Option<CpuId> {
if self.topology_len != other.topology_len {
return None;
}
for (word_index, (left, right)) in self.words.iter().zip(&other.words).enumerate() {
let mut candidates = left & right;
while candidates != 0 {
let bit = candidates.trailing_zeros() as usize;
candidates &= candidates - 1;
let index = word_index * Self::BITS_PER_WORD + bit;
if index >= self.topology_len {
break;
}
let cpu = CpuId::new(index as u32);
if accepts(cpu) {
return Some(cpu);
}
}
}
None
}
pub(crate) fn word(&self, word_index: usize) -> usize {
self.words.get(word_index).copied().unwrap_or(0)
}
}
#[repr(C)]
#[derive(Debug)]
pub struct ThreadExtensionOps {
pub on_switch_in: unsafe extern "Rust" fn(
data: usize,
thread: ThreadId,
policy: SchedulePolicy,
charged_runtime_ns: u64,
),
pub on_switch_out: unsafe extern "Rust" fn(data: usize, thread: ThreadId, reason: SwitchReason),
pub on_exit: unsafe extern "Rust" fn(data: usize, thread: ThreadId),
pub on_deadline_overrun: unsafe extern "Rust" fn(data: usize, thread: ThreadId),
pub drop: unsafe extern "Rust" fn(data: usize),
}
pub type RunningPolicyAppliedHook = unsafe extern "Rust" fn(
data: usize,
thread: ThreadId,
base_policy: SchedulePolicy,
observed_ns: u64,
);
#[derive(Debug)]
pub struct ThreadExtension {
data: usize,
ops: &'static ThreadExtensionOps,
running_policy_applied_hook: Option<RunningPolicyAppliedHook>,
scheduler_tick_cpu_time: Option<Arc<SchedulerTickCpuTime>>,
scheduler_tick_work: Option<SchedulerTickWork>,
}
impl ThreadExtension {
pub const unsafe fn new(data: usize, ops: &'static ThreadExtensionOps) -> Self {
Self {
data,
ops,
running_policy_applied_hook: None,
scheduler_tick_cpu_time: None,
scheduler_tick_work: None,
}
}
pub fn with_scheduler_tick_cpu_time(mut self, accounting: Arc<SchedulerTickCpuTime>) -> Self {
self.scheduler_tick_cpu_time = Some(accounting);
self
}
pub unsafe fn with_running_policy_applied_hook(
mut self,
callback: RunningPolicyAppliedHook,
) -> Self {
self.running_policy_applied_hook = Some(callback);
self
}
pub unsafe fn with_scheduler_tick_work(
mut self,
gate: Arc<SchedulerTickGate>,
callback: SchedulerTickTaskWork,
) -> Self {
self.scheduler_tick_work = Some(SchedulerTickWork::new(gate, callback));
self
}
pub const fn data(&self) -> usize {
self.data
}
pub const fn ops(&self) -> &'static ThreadExtensionOps {
self.ops
}
pub const fn running_policy_applied_hook(&self) -> Option<RunningPolicyAppliedHook> {
self.running_policy_applied_hook
}
pub unsafe fn forward_running_policy_applied(
&self,
thread: ThreadId,
base_policy: SchedulePolicy,
observed_ns: u64,
) -> bool {
let Some(callback) = self.running_policy_applied_hook else {
return false;
};
unsafe { callback(self.data, thread, base_policy, observed_ns) };
true
}
pub fn scheduler_tick_work_gate(&self) -> Option<Arc<SchedulerTickGate>> {
self.scheduler_tick_work
.as_ref()
.map(SchedulerTickWork::gate)
}
pub fn scheduler_tick_cpu_time(&self) -> Option<Arc<SchedulerTickCpuTime>> {
self.scheduler_tick_cpu_time.as_ref().map(Arc::clone)
}
pub unsafe fn forward_scheduler_tick_work(
&self,
thread: ThreadId,
observed_ns: u64,
) -> Option<SchedulerTickWorkDisposition> {
let work = self.scheduler_tick_work.as_ref()?;
Some(unsafe { work.invoke(self.data, thread, observed_ns) })
}
pub(crate) const fn as_view(&self) -> ThreadExtensionView {
ThreadExtensionView {
data: self.data,
ops: self.ops,
running_policy_applied_hook: self.running_policy_applied_hook,
}
}
pub(crate) fn scheduler_tick_work(&self) -> Option<SchedulerTickWork> {
self.scheduler_tick_work.clone()
}
}
impl Drop for ThreadExtension {
fn drop(&mut self) {
unsafe { (self.ops.drop)(self.data) };
}
}
#[derive(Clone, Copy, Debug)]
pub struct ThreadExtensionView {
data: usize,
ops: &'static ThreadExtensionOps,
running_policy_applied_hook: Option<RunningPolicyAppliedHook>,
}
#[derive(Debug)]
pub struct ThreadExtensionBorrow<'thread> {
view: ThreadExtensionView,
_thread: &'thread ThreadHandle,
}
impl<'thread> ThreadExtensionBorrow<'thread> {
pub(crate) const fn new(view: ThreadExtensionView, thread: &'thread ThreadHandle) -> Self {
Self {
view,
_thread: thread,
}
}
pub const fn data(&self) -> usize {
self.view.data()
}
pub const fn ops(&self) -> &'static ThreadExtensionOps {
self.view.ops()
}
}
#[derive(Debug)]
pub struct ThreadExtensionLease {
view: ThreadExtensionView,
thread: ThreadHandle,
}
impl ThreadExtensionLease {
pub(crate) const fn new(view: ThreadExtensionView, thread: ThreadHandle) -> Self {
Self { view, thread }
}
pub fn thread_id(&self) -> ThreadId {
self.thread.id()
}
pub const fn data(&self) -> usize {
self.view.data()
}
pub const fn ops(&self) -> &'static ThreadExtensionOps {
self.view.ops()
}
pub unsafe fn release_for_current_thread_entry(self) -> ThreadExtensionView {
let view = self.view;
drop(self);
view
}
}
impl ThreadExtensionView {
pub const fn data(self) -> usize {
self.data
}
pub const fn ops(self) -> &'static ThreadExtensionOps {
self.ops
}
pub(crate) unsafe fn notify_running_policy_applied(
self,
thread: ThreadId,
base_policy: SchedulePolicy,
observed_ns: u64,
) {
if let Some(callback) = self.running_policy_applied_hook {
unsafe { callback(self.data, thread, base_policy, observed_ns) };
}
}
}
#[derive(Debug)]
pub struct ThreadSpec {
policy: SchedulePolicy,
affinity: Option<CpuSet>,
resources: ThreadResources,
extension: Option<ThreadExtension>,
}
impl ThreadSpec {
pub const fn new(policy: SchedulePolicy) -> Self {
Self {
policy,
affinity: None,
resources: ThreadResources::NONE,
extension: None,
}
}
pub fn with_affinity(mut self, affinity: CpuSet) -> Self {
self.affinity = Some(affinity);
self
}
pub fn with_extension(mut self, extension: ThreadExtension) -> Self {
self.extension = Some(extension);
self
}
pub unsafe fn with_resources(mut self, resources: ThreadResources) -> Self {
self.resources = resources;
self
}
pub const fn policy(&self) -> SchedulePolicy {
self.policy
}
pub fn affinity(&self) -> Option<&CpuSet> {
self.affinity.as_ref()
}
pub(crate) fn into_owned_parts(mut self) -> (Option<ThreadExtension>, ThreadResources) {
let extension = self.extension.take();
let resources = core::mem::replace(&mut self.resources, ThreadResources::NONE);
(extension, resources)
}
}