use std::cell::Cell;
use std::ptr::addr_of_mut;
use std::sync::{Condvar, Mutex};
use super::g::{set_current_g, set_g0_sched, Stack, G};
use super::p::P;
use super::stack::{g0_stack_alloc, stack_free};
const ALT_STACK_SIZE: usize = 64 * 1024;
thread_local! {
pub(crate) static CURRENT_M: Cell<*mut M> = const { Cell::new(std::ptr::null_mut()) };
}
#[inline]
pub(crate) fn current_m() -> *mut M {
CURRENT_M.with(|c| c.get())
}
#[inline]
pub(crate) unsafe fn set_current_m(m: *mut M) {
CURRENT_M.with(|c| c.set(m));
}
pub(crate) struct Note {
flag: Mutex<bool>,
cond: Condvar,
}
impl Note {
fn new() -> Self {
Self { flag: Mutex::new(false), cond: Condvar::new() }
}
pub(crate) fn sleep(&self) {
let mut flag = self.flag.lock().unwrap();
while !*flag {
flag = self.cond.wait(flag).unwrap();
}
*flag = false; }
pub(crate) fn wakeup(&self) {
{
let mut flag = self.flag.lock().unwrap();
*flag = true;
} self.cond.notify_one();
}
pub(crate) fn clear(&self) {
*self.flag.lock().unwrap() = false;
}
}
pub(crate) struct M {
pub g0: *mut G,
pub curg: *mut G,
pub p: *mut P,
pub oldp: *mut P,
pub id: i64,
pub spinning: bool,
pub blocked: bool,
pub park: Note,
pub alllink: *mut M,
pub schedlink: *mut M,
pub pthread_id: u64,
}
unsafe impl Send for M {}
unsafe impl Sync for M {}
impl M {
pub(crate) unsafe fn new(id: i64) -> Box<M> {
let g0_stack = unsafe {
g0_stack_alloc().expect("M::new: failed to allocate g0 stack")
};
let mut g0 = G::new(g0_stack, 0);
g0.sched.sp = g0.stack.hi;
g0.sched.bp = g0.stack.hi;
let mut m = Box::new(M {
g0: std::ptr::null_mut(), curg: std::ptr::null_mut(),
p: std::ptr::null_mut(),
oldp: std::ptr::null_mut(),
id,
spinning: false,
blocked: false,
park: Note::new(),
alllink: std::ptr::null_mut(),
schedlink: std::ptr::null_mut(),
pthread_id: 0, });
let g0_ptr = Box::into_raw(g0);
m.g0 = g0_ptr;
unsafe { (*g0_ptr).m = addr_of_mut!(*m) };
m
}
pub(crate) unsafe fn start(&mut self) {
unsafe {
set_current_m(self as *mut M);
set_g0_sched(addr_of_mut!((*self.g0).sched));
set_current_g(std::ptr::null_mut());
#[cfg(not(windows))]
{
self.pthread_id = libc::pthread_self() as u64;
setup_sigaltstack();
}
}
}
pub(crate) fn park_m(&mut self) {
self.blocked = true;
self.park.sleep();
self.blocked = false;
}
pub(crate) fn unpark(&self) {
self.park.wakeup();
}
}
#[cfg(not(windows))]
unsafe fn setup_sigaltstack() {
let mem = unsafe {
libc::mmap(
std::ptr::null_mut(),
ALT_STACK_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_ANON | libc::MAP_PRIVATE,
-1,
0,
)
};
if mem == libc::MAP_FAILED {
return;
}
let ss = libc::stack_t {
ss_sp: mem,
ss_flags: 0,
ss_size: ALT_STACK_SIZE,
};
unsafe { libc::sigaltstack(&ss, std::ptr::null_mut()) };
}
impl Drop for M {
fn drop(&mut self) {
if !self.g0.is_null() {
unsafe {
let lo = (*self.g0).stack.lo;
let hi = (*self.g0).stack.hi;
stack_free(&Stack { lo, hi });
drop(Box::from_raw(self.g0));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn note_wakeup_before_sleep() {
let note = Note::new();
note.wakeup();
note.sleep(); }
#[test]
fn note_sleep_then_wakeup() {
use std::sync::Arc;
let note = Arc::new(Note::new());
let note2 = Arc::clone(¬e);
let handle = std::thread::spawn(move || note2.sleep());
std::thread::sleep(std::time::Duration::from_millis(20));
note.wakeup();
handle.join().unwrap();
}
#[test]
fn note_clear_resets_flag() {
let note = Note::new();
note.wakeup();
note.clear();
note.wakeup();
note.sleep();
}
#[test]
fn m_new_wires_g0() {
unsafe {
let m = M::new(1);
assert!(!m.g0.is_null(), "g0 must not be null");
let g0 = &*m.g0;
assert_eq!(
g0.m,
std::ptr::addr_of!(*m) as *mut M,
"g0.m must point back to M"
);
assert!(g0.stack.lo < g0.stack.hi, "g0 stack bounds invalid");
assert_eq!(g0.sched.sp, g0.stack.hi, "g0 sched.sp must equal stack.hi");
assert_eq!(g0.sched.bp, g0.stack.hi, "g0 sched.bp must equal stack.hi");
assert!(m.curg.is_null());
assert!(m.p.is_null());
assert_eq!(m.id, 1);
assert!(!m.spinning);
assert!(!m.blocked);
}
}
#[test]
fn m_drop_frees_stack() {
unsafe {
let m = M::new(2);
let stack_lo = (*m.g0).stack.lo;
let stack_hi = (*m.g0).stack.hi;
drop(m); let _ = (stack_lo, stack_hi); }
}
#[test]
fn m_park_unpark() {
use std::sync::Arc;
let note = Arc::new(Note::new());
let note2 = Arc::clone(¬e);
let handle = std::thread::spawn(move || note2.sleep());
std::thread::sleep(std::time::Duration::from_millis(20));
note.wakeup();
handle.join().unwrap();
}
}