use std::any::Any;
use std::ptr;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU64, Ordering::*};
use std::sync::{Arc, Mutex, OnceLock};
use super::g::{casgstatus, current_g, readgstatus, set_current_g, G, GDEAD, GPREEMPTED, GRUNNABLE, GRUNNING, STACK_GUARD};
use super::m::{current_m, M};
use super::p::{GlobalRunQueue, P, PIDLE, PRUNNING};
use super::stack::{grow_stack_if_needed, stack_alloc};
#[cfg(not(windows))]
use super::stack::install_sigsegv_handler;
use super::sysmon::start_sysmon;
use super::time::start_timer_thread;
#[cfg(all(target_arch = "x86_64", not(windows)))]
use super::asm_amd64::{async_preempt_trampoline, gogo, mcall};
#[cfg(all(target_arch = "x86_64", windows))]
use super::asm_amd64::{gogo, mcall};
#[cfg(target_arch = "aarch64")]
use super::asm_arm64::{async_preempt_trampoline, gogo, mcall};
pub(crate) struct SchedInner {
pub idle_p: *mut P,
pub idle_m: *mut M,
pub nmidle: i32,
pub allp: Vec<*mut P>,
pub gomaxprocs: i32,
}
unsafe impl Send for SchedInner {}
pub(crate) struct Sched {
pub global_run_q: GlobalRunQueue,
pub nmspinning: AtomicI32,
pub gomaxprocs: AtomicI32,
pub inner: Mutex<SchedInner>,
}
unsafe impl Sync for Sched {}
static SCHED: OnceLock<Sched> = OnceLock::new();
pub(crate) fn sched() -> &'static Sched {
SCHED.get_or_init(|| Sched {
global_run_q: GlobalRunQueue::new(),
nmspinning: AtomicI32::new(0),
gomaxprocs: AtomicI32::new(1),
inner: Mutex::new(SchedInner {
idle_p: ptr::null_mut(),
idle_m: ptr::null_mut(),
nmidle: 0,
allp: Vec::new(),
gomaxprocs: 1,
}),
})
}
#[allow(clippy::never_loop)] pub(crate) unsafe fn schedule() -> ! {
let m = current_m();
debug_assert!(!m.is_null(), "schedule: CURRENT_M is null — call set_current_m first");
loop {
let p = unsafe { (*m).p };
debug_assert!(!p.is_null(), "schedule: M has no P attached");
let tick = unsafe { (*p).schedtick.load(Relaxed) };
if tick % 61 == 0 && sched().global_run_q.len() > 0 {
let gp = unsafe { sched().global_run_q.pop() };
if !gp.is_null() {
unsafe { execute(gp) }; }
}
let (gp, _inherit) = unsafe { (*p).runqget() };
let gp = if !gp.is_null() {
gp
} else {
unsafe { findrunnable() } };
unsafe { execute(gp) }; }
}
pub(crate) unsafe fn findrunnable() -> *mut G {
let m = current_m();
let sc = sched();
loop {
let p = unsafe { (*m).p };
if !p.is_null() {
let (gp, _) = unsafe { (*p).runqget() };
if !gp.is_null() {
return gp;
}
}
{
let gp = unsafe { sc.global_run_q.pop() };
if !gp.is_null() {
return gp;
}
}
{
let inner = sc.inner.lock().unwrap();
let allp = &inner.allp;
let np = allp.len();
if np > 1 && !p.is_null() {
let start = (unsafe { (*m).id as usize }).wrapping_mul(0x9e3779b9) % np;
let victim_ptrs: Vec<*mut P> = (0..4)
.map(|i| allp[(start.wrapping_add(i)) % np])
.collect();
drop(inner);
for (i, victim_ptr) in victim_ptrs.iter().enumerate() {
if *victim_ptr == p {
continue;
}
let stolen = unsafe {
(*p).runqsteal(&**victim_ptr, i == 3)
};
if !stolen.is_null() {
return stolen;
}
}
}
}
{
let ready = unsafe { super::netpoll::netpoll_wait(0) };
for gp in ready {
unsafe { super::park::goready(gp) };
}
}
unsafe { stopm() };
}
}
unsafe fn stopm() {
let m = current_m();
let sc = sched();
{
let mut inner = sc.inner.lock().unwrap();
let p = unsafe { (*m).p };
if !p.is_null() {
unsafe {
(*m).p = ptr::null_mut();
(*p).status.store(PIDLE, Release);
(*p).link = inner.idle_p;
inner.idle_p = p;
}
}
unsafe {
(*m).schedlink = inner.idle_m;
inner.idle_m = m;
inner.nmidle += 1;
}
}
unsafe { (*m).park_m() };
}
pub(crate) unsafe fn startm(p: *mut P) {
let sc = sched();
let mut inner = sc.inner.lock().unwrap();
let m = inner.idle_m;
if m.is_null() {
if !p.is_null() {
unsafe {
(*p).status.store(PIDLE, Release);
(*p).link = inner.idle_p;
inner.idle_p = p;
}
}
return;
}
unsafe {
inner.idle_m = (*m).schedlink;
(*m).schedlink = ptr::null_mut();
inner.nmidle -= 1;
}
let use_p = if !p.is_null() {
p
} else if !inner.idle_p.is_null() {
let p2 = inner.idle_p;
unsafe {
inner.idle_p = (*p2).link;
(*p2).link = ptr::null_mut();
}
p2
} else {
unsafe {
(*m).schedlink = inner.idle_m;
inner.idle_m = m;
inner.nmidle += 1;
}
return;
};
unsafe {
(*use_p).status.store(PRUNNING, Release);
(*m).p = use_p;
}
drop(inner);
unsafe { (*m).unpark() };
}
pub(crate) unsafe fn execute(gp: *mut G) -> ! {
let m = current_m();
unsafe {
(*m).curg = gp;
(*gp).m = m;
casgstatus(gp, GRUNNABLE, GRUNNING);
}
let p = unsafe { (*m).p };
if !p.is_null() {
unsafe { (*p).schedtick.fetch_add(1, Relaxed) };
}
unsafe { grow_stack_if_needed(gp) };
unsafe {
set_current_g(gp);
gogo(gp)
}
}
pub(crate) unsafe extern "C" fn goexit0(gp: *mut G) {
let m = current_m();
unsafe {
casgstatus(gp, GRUNNING, GDEAD);
(*gp).m = ptr::null_mut();
(*m).curg = ptr::null_mut();
set_current_g(ptr::null_mut());
}
unsafe { schedule() }
}
pub(crate) unsafe fn gosched() {
let gp = current_g();
debug_assert!(!gp.is_null(), "gosched: called from g0 or uninitialised thread");
unsafe { mcall(gp, gosched_m) };
}
unsafe extern "C" fn gosched_m(gp: *mut G) {
let m = current_m();
unsafe {
casgstatus(gp, GRUNNING, GRUNNABLE);
(*gp).m = ptr::null_mut();
(*m).curg = ptr::null_mut();
set_current_g(ptr::null_mut());
}
unsafe {
(*gp).schedlink = ptr::null_mut();
sched().global_run_q.push_batch(gp, gp, 1);
}
unsafe { schedule() };
}
#[cfg(not(windows))]
static PREV_SIGURG: Mutex<Option<libc::sigaction>> = Mutex::new(None);
#[cfg(not(windows))]
pub(crate) unsafe fn install_sigurg_handler() {
let mut sa: libc::sigaction = unsafe { std::mem::zeroed() };
sa.sa_sigaction = sigurg_handler as *const () as usize;
sa.sa_flags = (libc::SA_SIGINFO | libc::SA_ONSTACK | libc::SA_RESTART) as _;
unsafe { libc::sigemptyset(&mut sa.sa_mask) };
let mut old: libc::sigaction = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::sigaction(libc::SIGURG, &sa, &mut old) };
assert_eq!(ret, 0, "install_sigurg_handler: sigaction failed");
*PREV_SIGURG.lock().unwrap() = Some(old);
}
#[cfg(not(windows))]
#[inline]
fn ucontext_sp(ctx: *mut libc::c_void) -> usize {
let uc = ctx as *mut libc::ucontext_t;
unsafe {
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
return (*uc).uc_mcontext.gregs[libc::REG_RSP as usize] as usize;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
return (*uc).uc_mcontext.sp as usize;
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
return (*(*uc).uc_mcontext).__ss.__rsp as usize;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
return (*(*uc).uc_mcontext).__ss.__sp as usize;
#[allow(unreachable_code)]
0_usize
}
}
#[cfg(not(windows))]
unsafe extern "C" fn sigurg_handler(
sig: libc::c_int,
info: *mut libc::siginfo_t,
ctx: *mut libc::c_void,
) {
let gp = current_g();
if !gp.is_null() && unsafe { (*gp).preempt } {
if unsafe { readgstatus(gp) } != GRUNNING {
return; }
let sp = ucontext_sp(ctx);
let (lo, hi) = unsafe { ((*gp).stack.lo, (*gp).stack.hi) };
if sp < lo || sp > hi {
return; }
unsafe { redirect_to_async_preempt(gp, ctx) };
return;
}
let prev = *PREV_SIGURG.lock().unwrap();
match prev {
Some(old) if old.sa_sigaction != libc::SIG_DFL
&& old.sa_sigaction != libc::SIG_IGN => {
type SaFn = unsafe extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void);
let f: SaFn = unsafe { std::mem::transmute(old.sa_sigaction) };
unsafe { f(sig, info, ctx) };
}
_ => {} }
}
#[cfg(not(windows))]
#[allow(unused_variables)]
unsafe fn redirect_to_async_preempt(gp: *mut G, ctx: *mut libc::c_void) {
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let rip = (*uc).uc_mcontext.gregs[libc::REG_RIP as usize] as usize;
let rsp = (*uc).uc_mcontext.gregs[libc::REG_RSP as usize] as usize;
let new_rsp = rsp - 8;
*(new_rsp as *mut usize) = rip;
(*uc).uc_mcontext.gregs[libc::REG_RSP as usize] = new_rsp as libc::greg_t;
(*uc).uc_mcontext.gregs[libc::REG_RIP as usize] =
async_preempt_trampoline as usize as libc::greg_t;
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
unsafe {
let uc = ctx as *mut libc::ucontext_t;
(*uc).uc_mcontext.regs[30] = (*uc).uc_mcontext.pc;
(*uc).uc_mcontext.pc = async_preempt_trampoline as u64;
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let ss = &mut (*(*uc).uc_mcontext).__ss;
let rip = ss.__rip as usize;
let rsp = ss.__rsp as usize;
let new_rsp = rsp - 8;
*(new_rsp as *mut usize) = rip;
ss.__rsp = new_rsp as u64;
ss.__rip = async_preempt_trampoline as *const () as u64;
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let ss = &mut (*(*uc).uc_mcontext).__ss;
ss.__lr = ss.__pc;
ss.__pc = async_preempt_trampoline as u64;
}
}
#[unsafe(no_mangle)]
pub(crate) unsafe extern "C" fn async_preempt2() {
let gp = current_g();
if gp.is_null() {
return;
}
if unsafe { readgstatus(gp) } != GRUNNING {
return;
}
unsafe {
(*gp).preempt = false;
(*gp).stackguard0 = (*gp).stack.lo + STACK_GUARD;
}
unsafe { mcall(gp, preemptm) };
}
unsafe extern "C" fn preemptm(gp: *mut G) {
let m = current_m();
unsafe {
casgstatus(gp, GRUNNING, GPREEMPTED);
casgstatus(gp, GPREEMPTED, GRUNNABLE);
(*gp).m = ptr::null_mut();
(*m).curg = ptr::null_mut();
set_current_g(ptr::null_mut());
(*gp).schedlink = ptr::null_mut();
sched().global_run_q.push_batch(gp, gp, 1);
}
unsafe { schedule() };
}
#[cfg(not(windows))]
static PREV_SIGBUS: Mutex<Option<libc::sigaction>> = Mutex::new(None);
#[cfg(not(windows))]
pub(crate) unsafe fn install_sigbus_handler() {
let mut sa: libc::sigaction = unsafe { std::mem::zeroed() };
sa.sa_sigaction = sigbus_handler as *const () as usize;
sa.sa_flags = (libc::SA_SIGINFO | libc::SA_ONSTACK) as _;
unsafe { libc::sigemptyset(&mut sa.sa_mask) };
let mut old: libc::sigaction = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::sigaction(libc::SIGBUS, &sa, &mut old) };
assert_eq!(ret, 0, "install_sigbus_handler: sigaction failed");
*PREV_SIGBUS.lock().unwrap() = Some(old);
}
#[cfg(not(windows))]
unsafe extern "C" fn sigbus_handler(
_sig: libc::c_int,
_info: *mut libc::siginfo_t,
ctx: *mut libc::c_void,
) {
let msg = b"[go-lib SIGBUS] crash detected\n";
unsafe { libc::write(2, msg.as_ptr() as *const libc::c_void, msg.len()) };
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
if !ctx.is_null() {
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let ss = &(*(*uc).uc_mcontext).__ss;
eprintln!("[go-lib SIGBUS] PC = {:#018x}", ss.__pc);
eprintln!("[go-lib SIGBUS] LR = {:#018x}", ss.__lr);
eprintln!("[go-lib SIGBUS] SP = {:#018x}", ss.__sp);
eprintln!("[go-lib SIGBUS] FP = {:#018x}", ss.__fp);
for (i, r) in ss.__x.iter().enumerate() {
if *r != 0 {
eprintln!("[go-lib SIGBUS] x{i:02} = {r:#018x}");
}
}
}
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
if !ctx.is_null() {
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let mc = &(*uc).uc_mcontext;
eprintln!("[go-lib SIGBUS] PC = {:#018x}", mc.pc);
eprintln!("[go-lib SIGBUS] SP = {:#018x}", mc.sp);
eprintln!("[go-lib SIGBUS] LR = {:#018x}", mc.regs[30]);
for (i, r) in mc.regs.iter().enumerate() {
if *r != 0 {
eprintln!("[go-lib SIGBUS] x{i:02} = {r:#018x}");
}
}
}
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
if !ctx.is_null() {
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let rip = (*uc).uc_mcontext.gregs[libc::REG_RIP as usize];
let rsp = (*uc).uc_mcontext.gregs[libc::REG_RSP as usize];
eprintln!("[go-lib SIGBUS] RIP = {rip:#018x}");
eprintln!("[go-lib SIGBUS] RSP = {rsp:#018x}");
}
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
if !ctx.is_null() {
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let ss = &(*(*uc).uc_mcontext).__ss;
eprintln!("[go-lib SIGBUS] RIP = {:#018x}", ss.__rip);
eprintln!("[go-lib SIGBUS] RSP = {:#018x}", ss.__rsp);
}
}
let bt = std::backtrace::Backtrace::force_capture();
eprintln!("[go-lib SIGBUS] backtrace:\n{bt}");
unsafe { libc::abort() };
}
#[cfg(windows)]
static VEH_HANDLING: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
#[cfg(windows)]
mod win_veh_sys {
#[link(name = "kernel32")]
unsafe extern "system" {
pub fn AddVectoredExceptionHandler(
FirstHandler: u32,
VectoredHandler: unsafe extern "system" fn(*mut u8) -> i32,
) -> *mut u8;
pub fn GetStdHandle(nStdHandle: u32) -> *mut u8;
pub fn WriteFile(
hFile: *mut u8,
lpBuffer: *const u8,
nNumberOfBytesToWrite: u32,
lpNumberOfBytesWritten: *mut u32,
lpOverlapped: *mut u8,
) -> i32;
pub fn FlushFileBuffers(hFile: *mut u8) -> i32;
pub fn GetCurrentProcess() -> *mut u8;
pub fn TerminateProcess(hProcess: *mut u8, uExitCode: u32) -> i32;
pub fn CreateFileW(
lpFileName: *const u16,
dwDesiredAccess: u32,
dwShareMode: u32,
lpSecurityAttributes: *mut u8,
dwCreationDisposition: u32,
dwFlagsAndAttributes: u32,
hTemplateFile: *mut u8,
) -> *mut u8;
pub fn CloseHandle(hObject: *mut u8) -> i32;
pub fn Sleep(dwMilliseconds: u32);
}
}
#[cfg(windows)]
unsafe fn veh_write(h: *mut u8, s: &[u8]) {
let mut n = 0u32;
unsafe {
win_veh_sys::WriteFile(h, s.as_ptr(), s.len() as u32, &mut n,
core::ptr::null_mut());
}
}
#[cfg(windows)]
unsafe fn veh_open_crash_file() -> *mut u8 {
const PATH: &[u16] = &[
b'.' as u16, b'\\' as u16,
b'g' as u16, b'o' as u16, b'-' as u16, b'l' as u16, b'i' as u16,
b'b' as u16, b'-' as u16, b'c' as u16, b'r' as u16, b'a' as u16,
b's' as u16, b'h' as u16, b'-' as u16, b'v' as u16, b'e' as u16,
b'h' as u16, b'.' as u16, b't' as u16, b'x' as u16, b't' as u16,
0u16, ];
const FILE_APPEND_DATA: u32 = 0x0000_0004;
const FILE_SHARE_READ: u32 = 0x0000_0001;
const OPEN_ALWAYS: u32 = 4; const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
unsafe {
win_veh_sys::CreateFileW(
PATH.as_ptr(),
FILE_APPEND_DATA,
FILE_SHARE_READ,
core::ptr::null_mut(),
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
core::ptr::null_mut(),
)
}
}
#[cfg(windows)]
unsafe fn veh_write_hex(h: *mut u8, val: u64) {
const HEX: &[u8] = b"0123456789abcdef";
let mut buf = [b'0'; 20];
buf[0] = b'0'; buf[1] = b'x';
for i in 0..16usize {
buf[17 - i] = HEX[((val >> (i * 4)) & 0xf) as usize];
}
buf[18] = b'\r'; buf[19] = b'\n';
let mut n = 0u32;
unsafe {
win_veh_sys::WriteFile(h, buf.as_ptr(), 20, &mut n,
core::ptr::null_mut());
}
}
#[cfg(windows)]
pub(crate) fn install_windows_veh() {
unsafe {
win_veh_sys::AddVectoredExceptionHandler(1, windows_veh_handler);
let stderr = win_veh_sys::GetStdHandle(0xFFFF_FFF4u32); let marker = b"[go-lib] VEH installed\r\n";
let mut n = 0u32;
win_veh_sys::WriteFile(stderr, marker.as_ptr(), marker.len() as u32,
&mut n, core::ptr::null_mut());
win_veh_sys::FlushFileBuffers(stderr);
let fh = veh_open_crash_file();
const INVALID_HANDLE: *mut u8 = usize::MAX as *mut u8;
if !fh.is_null() && fh != INVALID_HANDLE {
win_veh_sys::WriteFile(fh, marker.as_ptr(), marker.len() as u32,
&mut n, core::ptr::null_mut());
win_veh_sys::CloseHandle(fh);
}
}
}
#[cfg(windows)]
unsafe extern "system" fn windows_veh_handler(ep: *mut u8) -> i32 {
const STATUS_ACCESS_VIOLATION: u32 = 0xC000_0005;
const STATUS_STACK_OVERFLOW: u32 = 0xC000_00FD;
const EXCEPTION_CONTINUE_SEARCH: i32 = 0;
const STD_ERROR_HANDLE: u32 = 0xFFFF_FFF4;
if VEH_HANDLING.swap(true, std::sync::atomic::Ordering::AcqRel) {
return EXCEPTION_CONTINUE_SEARCH;
}
let h = unsafe { win_veh_sys::GetStdHandle(STD_ERROR_HANDLE) };
let ptrs = ep as *const usize;
let er_ptr = unsafe { *ptrs } as *const u8; let ctx_ptr = unsafe { *ptrs.add(1) } as *const u8;
let code: u32 = unsafe { (er_ptr as *const u32).read() };
if code != STATUS_ACCESS_VIOLATION && code != STATUS_STACK_OVERFLOW {
VEH_HANDLING.store(false, std::sync::atomic::Ordering::Release);
return EXCEPTION_CONTINUE_SEARCH;
}
unsafe {
veh_write(h, b"[go-lib VEH] exception code : ");
veh_write_hex(h, code as u64);
let instr: usize = *(er_ptr.add(16) as *const usize);
veh_write(h, b"[go-lib VEH] ExceptionAddress : ");
veh_write_hex(h, instr as u64);
let n_params: u32 = *(er_ptr.add(24) as *const u32);
if n_params >= 2 {
let ft: usize = *(er_ptr.add(32) as *const usize);
let fa: usize = *(er_ptr.add(40) as *const usize);
veh_write(h, b"[go-lib VEH] fault_type (0=rd,1=wr,8=DEP) : ");
veh_write_hex(h, ft as u64);
veh_write(h, b"[go-lib VEH] fault_addr : ");
veh_write_hex(h, fa as u64);
}
let rip: u64 = *(ctx_ptr.add(248) as *const u64);
let rsp: u64 = *(ctx_ptr.add(152) as *const u64);
let rbp: u64 = *(ctx_ptr.add(160) as *const u64);
let rax: u64 = *(ctx_ptr.add(120) as *const u64);
let rcx: u64 = *(ctx_ptr.add(128) as *const u64);
let rdx: u64 = *(ctx_ptr.add(136) as *const u64);
let rbx: u64 = *(ctx_ptr.add(144) as *const u64);
let rsi: u64 = *(ctx_ptr.add(168) as *const u64);
let rdi: u64 = *(ctx_ptr.add(176) as *const u64);
let r8: u64 = *(ctx_ptr.add(184) as *const u64);
let r9: u64 = *(ctx_ptr.add(192) as *const u64);
veh_write(h, b"[go-lib VEH] RIP : "); veh_write_hex(h, rip);
veh_write(h, b"[go-lib VEH] RSP : "); veh_write_hex(h, rsp);
veh_write(h, b"[go-lib VEH] RBP : "); veh_write_hex(h, rbp);
veh_write(h, b"[go-lib VEH] RAX : "); veh_write_hex(h, rax);
veh_write(h, b"[go-lib VEH] RCX : "); veh_write_hex(h, rcx);
veh_write(h, b"[go-lib VEH] RDX : "); veh_write_hex(h, rdx);
veh_write(h, b"[go-lib VEH] RBX : "); veh_write_hex(h, rbx);
veh_write(h, b"[go-lib VEH] RSI : "); veh_write_hex(h, rsi);
veh_write(h, b"[go-lib VEH] RDI : "); veh_write_hex(h, rdi);
veh_write(h, b"[go-lib VEH] R8 : "); veh_write_hex(h, r8);
veh_write(h, b"[go-lib VEH] R9 : "); veh_write_hex(h, r9);
let stdout = win_veh_sys::GetStdHandle(0xFFFF_FFF5u32); veh_write(stdout, b"[go-lib VEH] exception code : ");
veh_write_hex(stdout, code as u64);
let instr2: usize = *(er_ptr.add(16) as *const usize);
veh_write(stdout, b"[go-lib VEH] ExceptionAddress : ");
veh_write_hex(stdout, instr2 as u64);
let rip2: u64 = *(ctx_ptr.add(248) as *const u64);
let rsp2: u64 = *(ctx_ptr.add(152) as *const u64);
veh_write(stdout, b"[go-lib VEH] RIP : "); veh_write_hex(stdout, rip2);
veh_write(stdout, b"[go-lib VEH] RSP : "); veh_write_hex(stdout, rsp2);
win_veh_sys::FlushFileBuffers(stdout);
let fh = veh_open_crash_file();
const INVALID_HANDLE: *mut u8 = usize::MAX as *mut u8;
if !fh.is_null() && fh != INVALID_HANDLE {
veh_write(fh, b"[go-lib VEH] exception code : ");
veh_write_hex(fh, code as u64);
let instr3: usize = *(er_ptr.add(16) as *const usize);
veh_write(fh, b"[go-lib VEH] ExceptionAddress : ");
veh_write_hex(fh, instr3 as u64);
if n_params >= 2 {
let ft2: usize = *(er_ptr.add(32) as *const usize);
let fa2: usize = *(er_ptr.add(40) as *const usize);
veh_write(fh, b"[go-lib VEH] fault_type : ");
veh_write_hex(fh, ft2 as u64);
veh_write(fh, b"[go-lib VEH] fault_addr : ");
veh_write_hex(fh, fa2 as u64);
}
let rip3: u64 = *(ctx_ptr.add(248) as *const u64);
let rsp3: u64 = *(ctx_ptr.add(152) as *const u64);
let rbp3: u64 = *(ctx_ptr.add(160) as *const u64);
let rax3: u64 = *(ctx_ptr.add(120) as *const u64);
let rcx3: u64 = *(ctx_ptr.add(128) as *const u64);
let rdx3: u64 = *(ctx_ptr.add(136) as *const u64);
veh_write(fh, b"[go-lib VEH] RIP : "); veh_write_hex(fh, rip3);
veh_write(fh, b"[go-lib VEH] RSP : "); veh_write_hex(fh, rsp3);
veh_write(fh, b"[go-lib VEH] RBP : "); veh_write_hex(fh, rbp3);
veh_write(fh, b"[go-lib VEH] RAX : "); veh_write_hex(fh, rax3);
veh_write(fh, b"[go-lib VEH] RCX : "); veh_write_hex(fh, rcx3);
veh_write(fh, b"[go-lib VEH] RDX : "); veh_write_hex(fh, rdx3);
win_veh_sys::CloseHandle(fh);
}
win_veh_sys::FlushFileBuffers(h);
win_veh_sys::Sleep(500);
win_veh_sys::TerminateProcess(win_veh_sys::GetCurrentProcess(), code);
}
EXCEPTION_CONTINUE_SEARCH
}
struct GoFn(Box<dyn FnOnce() + Send + 'static>);
type PanicFn = Arc<dyn Fn(Box<dyn Any + Send + 'static>) + Send + Sync + 'static>;
static PANIC_HANDLER: Mutex<Option<PanicFn>> = Mutex::new(None);
pub fn set_panic_handler<F>(f: F)
where
F: Fn(Box<dyn Any + Send + 'static>) + Send + Sync + 'static,
{
*PANIC_HANDLER.lock().unwrap() = Some(Arc::new(f));
}
fn handle_goroutine_panic(payload: Box<dyn Any + Send + 'static>) {
let handler = PANIC_HANDLER.lock().unwrap_or_else(|e| e.into_inner()).clone();
match handler {
Some(f) => f(payload),
None => {
let msg = payload.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("(unknown panic payload)");
eprintln!("goroutine panicked: {msg}");
}
}
}
pub fn gomaxprocs() -> usize {
sched().gomaxprocs.load(Relaxed) as usize
}
pub fn set_gomaxprocs(n: usize) -> usize {
let n = n.clamp(1, 256) as i32;
let sc = sched();
let old = {
let mut inner = sc.inner.lock().unwrap();
let old = inner.gomaxprocs;
if n > old {
let new_ps: Vec<*mut P> = (old..n)
.map(|id| Box::into_raw(P::new(id)))
.collect();
inner.allp.extend_from_slice(&new_ps);
inner.gomaxprocs = n;
sc.gomaxprocs.store(n, Relaxed);
drop(inner);
for p_ptr in new_ps {
let id = NEXT_MID.fetch_add(1, Relaxed);
unsafe { spawn_m(id, p_ptr) };
}
} else {
inner.gomaxprocs = n;
sc.gomaxprocs.store(n, Relaxed);
}
old
};
old as usize
}
static NEXT_GOID: AtomicU64 = AtomicU64::new(1);
static NEXT_MID: AtomicI64 = AtomicI64::new(1);
static INITIALIZED: AtomicBool = AtomicBool::new(false);
unsafe extern "C" fn goroutine_entry() {
let gp = current_g();
let go_fn = unsafe {
let fn_ptr = (*gp).sched.ctxt as *mut GoFn;
(*gp).sched.ctxt = ptr::null_mut();
Box::from_raw(fn_ptr)
};
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (go_fn.0)()));
if let Err(payload) = result {
handle_goroutine_panic(payload);
}
}
#[cfg(target_arch = "x86_64")]
#[unsafe(naked)]
unsafe extern "C" fn goexit_trampoline() -> ! {
core::arch::naked_asm!(
"call {handler}",
"ud2", handler = sym goexit0_handler,
)
}
#[cfg(target_arch = "x86_64")]
unsafe extern "C" fn goexit0_handler() -> ! {
let gp = current_g();
unsafe { mcall(gp, goexit0) };
unsafe { std::hint::unreachable_unchecked() }
}
#[cfg(target_arch = "aarch64")]
unsafe extern "C" fn goexit_trampoline() -> ! {
let gp = current_g();
unsafe { mcall(gp, goexit0) };
unsafe { std::hint::unreachable_unchecked() }
}
pub(crate) fn new_goroutine(f: impl FnOnce() + Send + 'static) -> Box<G> {
let stack = unsafe { stack_alloc().expect("new_goroutine: stack_alloc failed") };
let goid = NEXT_GOID.fetch_add(1, Relaxed);
let mut g = G::new(stack, goid);
let go_fn = Box::new(GoFn(Box::new(f)));
g.sched.ctxt = Box::into_raw(go_fn) as *mut u8;
g.sched.pc = goroutine_entry as *const () as usize;
#[cfg(target_arch = "x86_64")]
{
let ret_slot = (g.stack.hi - 8) as *mut usize;
unsafe { ret_slot.write(goexit_trampoline as *const () as usize) };
g.sched.sp = g.stack.hi - 8;
g.sched.bp = 0; }
#[cfg(target_arch = "aarch64")]
{
g.sched.lr = goexit_trampoline as usize;
g.sched.sp = g.stack.hi;
g.sched.bp = 0;
}
g.atomicstatus.store(GRUNNABLE, Release);
g
}
pub(crate) fn spawn_goroutine(f: impl FnOnce() + Send + 'static) {
debug_assert!(
INITIALIZED.load(Acquire),
"spawn_goroutine called before schedinit; goroutine will never run"
);
let g_ptr = Box::into_raw(new_goroutine(f));
unsafe {
(*g_ptr).schedlink = ptr::null_mut();
sched().global_run_q.push_batch(g_ptr, g_ptr, 1);
startm(ptr::null_mut());
}
}
pub(crate) fn schedinit(nprocs: i32) {
if INITIALIZED.swap(true, AcqRel) {
return;
}
assert!(nprocs >= 1, "schedinit: nprocs must be ≥ 1");
let nprocs = std::env::var("GOMAXPROCS")
.ok()
.and_then(|s| s.parse::<i32>().ok())
.filter(|&n| (1..=256).contains(&n))
.unwrap_or(nprocs);
let sc = sched();
let ps: Vec<*mut P> = (0..nprocs)
.map(|id| Box::into_raw(P::new(id)))
.collect();
{
let mut inner = sc.inner.lock().unwrap();
inner.gomaxprocs = nprocs;
inner.allp = ps.clone();
}
sc.gomaxprocs.store(nprocs, Relaxed);
for p_ptr in ps {
let id = NEXT_MID.fetch_add(1, Relaxed);
unsafe { spawn_m(id, p_ptr) };
}
#[cfg(not(windows))]
unsafe { install_sigsegv_handler() };
#[cfg(not(windows))]
unsafe { install_sigurg_handler() };
#[cfg(not(windows))]
unsafe { install_sigbus_handler() };
#[cfg(windows)]
install_windows_veh();
start_sysmon();
start_timer_thread();
}
unsafe fn spawn_m(id: i64, p: *mut P) {
let m = Box::into_raw(unsafe { M::new(id) });
unsafe {
(*m).p = p;
(*p).m = m;
(*p).status.store(PRUNNING, Release);
}
let m_addr = m as usize;
std::thread::spawn(move || {
let m = m_addr as *mut M;
unsafe {
(*m).start();
schedule();
}
});
}
pub(crate) fn run_impl<F: FnOnce() + Send + 'static>(f: F) {
let nprocs = std::thread::available_parallelism()
.map(|n| n.get() as i32)
.unwrap_or(1);
schedinit(nprocs);
struct UnparkOnDrop(std::thread::Thread);
impl Drop for UnparkOnDrop {
fn drop(&mut self) { self.0.unpark(); }
}
let caller = std::thread::current();
let wrapper = move || {
let _guard = UnparkOnDrop(caller);
f();
};
spawn_goroutine(wrapper);
std::thread::park();
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
#[test]
fn sched_singleton() {
let s1 = sched() as *const Sched;
let s2 = sched() as *const Sched;
assert_eq!(s1, s2, "sched() must return the same singleton");
}
#[test]
fn global_run_queue_round_trip() {
use crate::runtime::g::{Stack, G};
use crate::runtime::p::GlobalRunQueue;
let q = GlobalRunQueue::new();
let lo = 0x200000usize;
let g1 = G::new(Stack { lo, hi: lo + 65536 }, 99);
let g1_ptr = Box::into_raw(g1);
unsafe {
(*g1_ptr).schedlink = ptr::null_mut();
q.push_batch(g1_ptr, g1_ptr, 1);
assert_eq!(q.len(), 1);
let got = q.pop();
assert_eq!(got, g1_ptr);
assert_eq!(q.len(), 0);
let _ = Box::from_raw(g1_ptr);
}
}
#[test]
fn new_goroutine_fields() {
use crate::runtime::g::GRUNNABLE;
use std::sync::atomic::Ordering::Relaxed;
let g = new_goroutine(|| {});
let g_ptr = Box::into_raw(g);
unsafe {
assert_eq!(
(*g_ptr).atomicstatus.load(Relaxed),
GRUNNABLE,
"new goroutine must start as Grunnable"
);
assert_ne!((*g_ptr).sched.pc, 0, "pc must be set to goroutine_entry");
assert!(!(*g_ptr).sched.ctxt.is_null(), "ctxt must hold the closure");
#[cfg(target_arch = "x86_64")]
{
assert_eq!((*g_ptr).sched.sp, (*g_ptr).stack.hi - 8);
let ret_addr = ((*g_ptr).sched.sp as *const usize).read();
assert_eq!(ret_addr, goexit_trampoline as *const () as usize);
}
#[cfg(target_arch = "aarch64")]
{
assert_eq!((*g_ptr).sched.sp, (*g_ptr).stack.hi);
assert_eq!((*g_ptr).sched.lr, goexit_trampoline as *const () as usize);
}
let _ = Box::from_raw(g_ptr);
}
}
#[test]
fn run_single_goroutine() {
use std::sync::atomic::{AtomicBool, Ordering};
static RAN: AtomicBool = AtomicBool::new(false);
run_impl(|| {
RAN.store(true, Ordering::Release);
});
assert!(RAN.load(Ordering::Acquire), "goroutine body did not execute");
}
#[test]
fn run_second_goroutine() {
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNT: AtomicUsize = AtomicUsize::new(0);
run_impl(|| { COUNT.fetch_add(1, Ordering::AcqRel); });
run_impl(|| { COUNT.fetch_add(1, Ordering::AcqRel); });
assert_eq!(COUNT.load(Ordering::Acquire), 2);
}
#[test]
fn gosched_returns_to_caller() {
use std::sync::atomic::{AtomicBool, Ordering};
static AFTER_YIELD: AtomicBool = AtomicBool::new(false);
run_impl(|| {
unsafe { gosched() };
AFTER_YIELD.store(true, Ordering::Release);
});
assert!(
AFTER_YIELD.load(Ordering::Acquire),
"execution must continue after gosched()"
);
}
#[test]
fn gosched_allows_other_goroutines_to_run() {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
let flag = Arc::new(AtomicBool::new(false));
let flag_setter = Arc::clone(&flag);
run_impl(move || {
spawn_goroutine(move || {
flag_setter.store(true, Ordering::Release);
});
while !flag.load(Ordering::Acquire) {
unsafe { gosched() };
}
});
}
}