use std::cell::Cell;
use std::sync::atomic::{AtomicU32, Ordering::*};
use super::m::M;
pub(crate) const GIDLE: u32 = 0;
pub(crate) const GRUNNABLE: u32 = 1;
pub(crate) const GRUNNING: u32 = 2;
pub(crate) const GSYSCALL: u32 = 3;
pub(crate) const GWAITING: u32 = 4;
pub(crate) const GDEAD: u32 = 6;
pub(crate) const GCOPYSTACK: u32 = 8;
pub(crate) const GPREEMPTED: u32 = 9;
pub(crate) const GSCAN: u32 = 0x1000;
pub(crate) const STACK_PREEMPT: usize = usize::MAX;
pub(crate) const STACK_GUARD: usize = 928;
#[repr(C)]
pub(crate) struct Stack {
pub lo: usize,
pub hi: usize,
}
#[repr(C)]
pub(crate) struct Gobuf {
pub sp: usize,
pub pc: usize,
pub g: *mut G,
pub ctxt: *mut u8,
pub ret: usize,
pub lr: usize,
pub bp: usize,
}
pub(crate) const GOBUF_SP_OFFSET: usize = 0;
pub(crate) const GOBUF_PC_OFFSET: usize = 8;
pub(crate) const GOBUF_G_OFFSET: usize = 16;
pub(crate) const GOBUF_CTXT_OFFSET: usize = 24;
pub(crate) const GOBUF_RET_OFFSET: usize = 32;
pub(crate) const GOBUF_LR_OFFSET: usize = 40;
pub(crate) const GOBUF_BP_OFFSET: usize = 48;
const _: () = {
use std::mem::offset_of;
assert!(offset_of!(Gobuf, sp) == GOBUF_SP_OFFSET);
assert!(offset_of!(Gobuf, pc) == GOBUF_PC_OFFSET);
assert!(offset_of!(Gobuf, g) == GOBUF_G_OFFSET);
assert!(offset_of!(Gobuf, ctxt) == GOBUF_CTXT_OFFSET);
assert!(offset_of!(Gobuf, ret) == GOBUF_RET_OFFSET);
assert!(offset_of!(Gobuf, lr) == GOBUF_LR_OFFSET);
assert!(offset_of!(Gobuf, bp) == GOBUF_BP_OFFSET);
};
unsafe impl Send for Gobuf {}
unsafe impl Sync for Gobuf {}
impl Default for Gobuf {
fn default() -> Self {
Self {
sp: 0,
pc: 0,
g: std::ptr::null_mut(),
ctxt: std::ptr::null_mut(),
ret: 0,
lr: 0,
bp: 0,
}
}
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub(crate) enum WaitReason {
#[default]
Zero = 0,
Select = 9, ChanReceive = 14, ChanSend = 15, Semacquire = 18, Sleep = 19, CondVar = 20, IOWait = 23, }
impl WaitReason {
#[allow(dead_code)]
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Zero => "",
Self::Select => "select",
Self::ChanReceive => "chan receive",
Self::ChanSend => "chan send",
Self::Semacquire => "semacquire",
Self::Sleep => "sleep",
Self::CondVar => "condvar wait",
Self::IOWait => "IO wait",
}
}
}
#[cfg_attr(not(windows), allow(dead_code))]
pub(crate) const G_STACK_LO_OFFSET: usize = 0;
#[cfg_attr(not(windows), allow(dead_code))]
pub(crate) const G_STACK_HI_OFFSET: usize = 8;
const _: () = {
use std::mem::offset_of;
assert!(offset_of!(G, stack) == G_STACK_LO_OFFSET,
"G.stack must be the first field (offset 0) for asm access");
};
#[repr(C)]
pub(crate) struct G {
pub stack: Stack,
pub stackguard0: usize,
pub m: *mut M,
pub sched: Gobuf,
pub atomicstatus: AtomicU32,
pub goid: u64,
pub schedlink: *mut G,
#[allow(dead_code)]
pub waitsince: i64,
pub waitreason: WaitReason,
pub param: *mut u8,
pub preempt: bool,
pub selectdone: AtomicU32,
#[allow(dead_code)]
pub timer: *mut u8,
}
unsafe impl Send for G {}
unsafe impl Sync for G {}
impl G {
pub(crate) fn new(stack: Stack, goid: u64) -> Box<G> {
let stackguard0 = stack.lo + STACK_GUARD;
let mut g = Box::new(G {
stackguard0,
stack,
m: std::ptr::null_mut(),
sched: Gobuf::default(),
atomicstatus: AtomicU32::new(GIDLE),
goid,
schedlink: std::ptr::null_mut(),
waitsince: 0,
waitreason: WaitReason::Zero,
param: std::ptr::null_mut(),
preempt: false,
selectdone: AtomicU32::new(0),
timer: std::ptr::null_mut(),
});
g.sched.g = std::ptr::addr_of_mut!(*g);
g
}
}
thread_local! {
pub(crate) static CURRENT_G: Cell<*mut G> =
const { Cell::new(std::ptr::null_mut()) };
pub(crate) static G0_SCHED: Cell<*mut Gobuf> =
const { Cell::new(std::ptr::null_mut()) };
}
#[inline]
pub(crate) fn current_g() -> *mut G {
CURRENT_G.with(|c| c.get())
}
#[inline]
pub(crate) unsafe fn set_current_g(g: *mut G) {
CURRENT_G.with(|c| c.set(g));
}
#[inline]
pub(crate) unsafe fn set_g0_sched(buf: *mut Gobuf) {
G0_SCHED.with(|c| c.set(buf));
}
#[allow(dead_code)] #[inline]
pub(crate) fn g0_sched() -> *mut Gobuf {
G0_SCHED.with(|c| c.get())
}
fn is_valid_transition(from: u32, to: u32) -> bool {
let f = from & !GSCAN;
let t = to & !GSCAN;
matches!((f, t),
(GIDLE, GRUNNABLE) | (GRUNNABLE, GRUNNING) | (GRUNNING, GRUNNABLE) | (GRUNNING, GWAITING) | (GWAITING, GRUNNABLE) | (GRUNNING, GSYSCALL) | (GSYSCALL, GRUNNING) | (GSYSCALL, GRUNNABLE) | (GRUNNING, GCOPYSTACK) | (GCOPYSTACK, GRUNNING) | (GRUNNING, GPREEMPTED) | (GPREEMPTED, GRUNNABLE) | (GRUNNING, GDEAD) )
}
pub(crate) unsafe fn casgstatus(gp: *mut G, old_val: u32, new_val: u32) {
debug_assert!(
is_valid_transition(old_val, new_val),
"casgstatus: invalid transition {old_val} → {new_val}",
);
loop {
let s = unsafe { (*gp).atomicstatus.load(Acquire) };
if s & GSCAN != 0 {
std::hint::spin_loop();
continue;
}
if unsafe {
(*gp).atomicstatus
.compare_exchange(old_val, new_val, AcqRel, Relaxed)
.is_ok()
} {
return;
}
std::hint::spin_loop();
}
}
#[cfg_attr(not(test), allow(dead_code))] pub(crate) unsafe fn castogscanstatus(gp: *mut G, base_status: u32) {
let result = unsafe {
(*gp).atomicstatus
.compare_exchange(base_status, GSCAN | base_status, AcqRel, Relaxed)
};
debug_assert!(
result.is_ok(),
"castogscanstatus: G not in expected status {base_status}",
);
}
#[cfg_attr(not(test), allow(dead_code))] pub(crate) unsafe fn casfrom_gscanstatus(gp: *mut G, scan_status: u32, new_val: u32) {
let result = unsafe {
(*gp).atomicstatus
.compare_exchange(scan_status, new_val, AcqRel, Relaxed)
};
debug_assert!(
result.is_ok(),
"casfrom_gscanstatus: G not in expected scan status {scan_status}",
);
}
#[inline]
pub(crate) unsafe fn readgstatus(gp: *mut G) -> u32 {
unsafe { (*gp).atomicstatus.load(Acquire) & !GSCAN }
}
#[cfg_attr(not(test), allow(dead_code))] pub(crate) unsafe fn scan_stack(gp: *mut G, scanner: impl FnOnce()) {
let base = unsafe { readgstatus(gp) };
unsafe { castogscanstatus(gp, base) }; scanner(); unsafe { casfrom_gscanstatus(gp, GSCAN | base, base) }; }
#[cfg(test)]
mod tests {
use std::sync::atomic::Ordering::Relaxed;
use super::*;
use crate::runtime::stack::{stack_alloc, stack_free};
#[test]
fn gscan_round_trip() {
let stack = unsafe { stack_alloc().expect("stack_alloc failed") };
let stack_bounds = Stack { lo: stack.lo, hi: stack.hi };
let mut g = G::new(stack, 999);
let gp: *mut G = &mut *g;
unsafe { (*gp).atomicstatus.store(GWAITING, Relaxed) };
unsafe {
scan_stack(gp, || {
assert_eq!(
(*gp).atomicstatus.load(Relaxed),
GSCAN | GWAITING,
"GSCAN bit should be set during scan"
);
});
}
assert_eq!(
unsafe { (*gp).atomicstatus.load(Relaxed) },
GWAITING,
"GSCAN bit should be cleared after scan"
);
unsafe { stack_free(&stack_bounds) };
}
}