use core::{ cell::UnsafeCell, fmt::Debug, mem::{self, ManuallyDrop}, ptr::NonNull, }; use std::cell::Cell; use alloc::boxed::Box; use crate::{ WorkerThread, channel::{Parker, Sender}, }; #[repr(transparent)] pub struct StackJob { f: UnsafeCell>, } impl StackJob { pub fn new(f: F) -> Self { Self { f: UnsafeCell::new(ManuallyDrop::new(f)), } } pub unsafe fn unwrap(&self) -> F { unsafe { ManuallyDrop::take(&mut *self.f.get()) } } } pub struct HeapJob { f: F, } impl HeapJob { pub fn new(f: F) -> Box { Box::new(Self { f }) } pub fn into_inner(self) -> F { self.f } } type JobHarness = unsafe fn(&WorkerThread, this: NonNull<()>, sender: Option); #[repr(C)] pub struct Job2 { harness: JobHarness, this: NonNull<()>, receiver: Cell>>, } impl Debug for Job2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Job2") .field("harness", &self.harness) .field("this", &self.this) .finish_non_exhaustive() } } #[derive(Debug)] pub struct SharedJob { harness: JobHarness, this: NonNull<()>, sender: Option, } impl Job2 { fn new(harness: JobHarness, this: NonNull<()>) -> Self { let this = Self { harness, this, receiver: Cell::new(None), }; #[cfg(feature = "tracing")] tracing::trace!("new job: {:?}", this); this } pub fn share(&self, parker: Option<&Parker>) -> SharedJob { #[cfg(feature = "tracing")] tracing::trace!("sharing job: {:?}", self); let (sender, receiver) = parker .map(|parker| crate::channel::channel::(parker.into())) .unzip(); self.receiver.set(receiver); SharedJob { harness: self.harness, this: self.this, sender: unsafe { mem::transmute(sender) }, } } pub fn take_receiver(&self) -> Option> { self.receiver.take() } pub fn from_stackjob(job: &StackJob) -> Self where F: FnOnce(&WorkerThread) -> T + Send, { #[align(8)] #[cfg_attr( feature = "tracing", tracing::instrument(level = "trace", skip_all, name = "stack_job_harness") )] unsafe fn harness(worker: &WorkerThread, this: NonNull<()>, sender: Option) where F: FnOnce(&WorkerThread) -> T + Send, T: Send, { use std::panic::{AssertUnwindSafe, catch_unwind}; let f = unsafe { this.cast::>().as_ref().unwrap() }; let sender: Sender = unsafe { mem::transmute(sender) }; // #[cfg(feature = "metrics")] // if worker.heartbeat.parker() == mutex { // worker // .metrics // .num_sent_to_self // .fetch_add(1, Ordering::Relaxed); // tracing::trace!("job sent to self"); // } sender.send(catch_unwind(AssertUnwindSafe(|| f(worker)))); } Self::new(harness::, NonNull::from(job).cast()) } pub fn from_heapjob(job: Box>) -> Self where F: FnOnce(&WorkerThread) -> T + Send, { #[align(8)] #[cfg_attr( feature = "tracing", tracing::instrument(level = "trace", skip_all, name = "heap_job_harness") )] unsafe fn harness(worker: &WorkerThread, this: NonNull<()>, sender: Option) where F: FnOnce(&WorkerThread) -> T + Send, T: Send, { use std::panic::{AssertUnwindSafe, catch_unwind}; // expect MIRI to complain about this, but it is actually correct. // because I am so much smarter than MIRI, naturally, obviously. // unbox the job, which was allocated at (2) let f = unsafe { (*Box::from_non_null(this.cast::>())).into_inner() }; let result = catch_unwind(AssertUnwindSafe(|| f(worker))); let sender: Option> = unsafe { mem::transmute(sender) }; if let Some(sender) = sender { sender.send(result); } } // (1) allocate box for job Self::new( harness::, // (2) convert job into a pointer Box::into_non_null(job).cast(), ) } pub fn from_harness(harness: JobHarness, this: NonNull<()>) -> Self { Self::new(harness, this) } pub fn is_shared(&self) -> bool { unsafe { (&*self.receiver.as_ptr()).is_some() } } } impl SharedJob { pub unsafe fn execute(self, worker: &WorkerThread) { #[cfg(feature = "tracing")] tracing::trace!("executing shared job: {:?}", self); let Self { harness, this, sender, } = self; unsafe { (harness)(worker, this, sender); } #[cfg(feature = "tracing")] tracing::trace!("finished executing shared job: {:?}", this); } } pub use queuedjobqueue::JobQueue; mod queuedjobqueue { //! Basically `JobVec`, but for `QueuedJob`s. // TODO: use non-null's here and rely on Into/From for &T use std::{collections::VecDeque, ptr::NonNull}; use super::Job2 as Job; #[derive(Debug)] pub struct JobQueue { jobs: VecDeque>, } impl JobQueue { pub fn new() -> Self { Self { jobs: VecDeque::new(), } } pub fn push_front(&mut self, job: *const Job) { self.jobs .push_front(unsafe { NonNull::new_unchecked(job as *mut _) }); } pub fn push_back(&mut self, job: *const Job) { self.jobs .push_back(unsafe { NonNull::new_unchecked(job as *mut _) }); } pub fn pop_front(&mut self) -> Option> { self.jobs.pop_front() } pub fn pop_back(&mut self) -> Option> { self.jobs.pop_back() } pub fn is_empty(&self) -> bool { self.jobs.is_empty() } pub fn len(&self) -> usize { self.jobs.len() } } } pub mod traits { use std::{cell::UnsafeCell, mem::ManuallyDrop}; use crate::WorkerThread; use super::{HeapJob, Job2, StackJob}; pub trait IntoJob { fn into_job(self) -> Job2; } pub trait InlineJob: IntoJob { fn run_inline(self, worker: &WorkerThread) -> T; } impl IntoJob for F where F: FnOnce(&WorkerThread) -> T + Send, T: Send, { fn into_job(self) -> Job2 { Job2::from_heapjob(HeapJob::new(self)) } } impl IntoJob for &UnsafeCell> where F: FnOnce(&WorkerThread) -> T + Send, T: Send, { fn into_job(self) -> Job2 { Job2::from_stackjob(unsafe { std::mem::transmute::>(self) }) } } impl InlineJob for &UnsafeCell> where F: FnOnce(&WorkerThread) -> T + Send, T: Send, { fn run_inline(self, worker: &WorkerThread) -> T { unsafe { ManuallyDrop::take(&mut *self.get())(worker) } } } impl IntoJob for &StackJob where F: FnOnce(&WorkerThread) -> T + Send, T: Send, { fn into_job(self) -> Job2 { Job2::from_stackjob(self) } } impl InlineJob for &StackJob where F: FnOnce(&WorkerThread) -> T + Send, T: Send, { fn run_inline(self, worker: &WorkerThread) -> T { unsafe { self.unwrap()(worker) } } } }