use core::ffi::c_void;
#[repr(C, align(4))]
pub struct FunctionPointer {
function: i32,
context_size: u32,
}
impl FunctionPointer {
pub const fn new() -> Self {
FunctionPointer { function: 0, context_size: 0 }
}
}
impl Default for FunctionPointer {
fn default() -> Self {
Self::new()
}
}
const EMPTY_CONTEXT: u32 = 32;
pub unsafe fn prepare(
record: &'static mut FunctionPointer,
slot: usize,
action: impl Fn() + Send + Sync + 'static,
) -> [u64; 2] {
unsafe {
let (code, target, shim) = thunks(slot);
ACTIONS[slot].store(
Box::into_raw(Box::new(Box::new(action) as Box<dyn Fn() + Send + Sync>)),
Ordering::Release,
);
*target = shim;
let at = &record.function as *const i32 as *const u8;
let delta = code as isize - at as isize;
record.function = i32::try_from(delta).expect("async function pointer out of range");
record.context_size = EMPTY_CONTEXT;
[record as *const FunctionPointer as u64, 0]
}
}
macro_rules! async_thunks {
($($name:ident, $sym:literal, $target:ident;)*) => {$(
crate::thunk::runtime_thunk! {
fn $name = $sym via $target {
prologue: [
"orr x29, x29, #0x1000000000000000",
"sub sp, sp, #0x20",
"stp x29, x30, [sp, #0x10]",
"str x22, [sp, #0x8]",
"add x29, sp, #0x10",
],
branch: "blr",
epilogue: [
"ldr x22, [sp, #0x8]",
"ldp x29, x30, [sp, #0x10]",
"and x29, x29, #0xefffffffffffffff",
"add sp, sp, #0x20",
"ldr x0, [x22, #8]",
"br x0",
],
}
}
)*};
}
async_thunks! {
async0, "rswift_async0", RSWIFT_ASYNC0;
async1, "rswift_async1", RSWIFT_ASYNC1;
async2, "rswift_async2", RSWIFT_ASYNC2;
}
static ACTIONS: [AtomicPtr<Box<dyn Fn() + Send + Sync>>; SLOTS] =
[const { AtomicPtr::new(core::ptr::null_mut()) }; SLOTS];
use core::sync::atomic::{AtomicPtr, Ordering};
const SLOTS: usize = 3;
macro_rules! shims {
($($name:ident = $i:expr;)*) => {$(
unsafe extern "C" fn $name() {
let f = ACTIONS[$i].load(Ordering::Acquire);
if !f.is_null() {
unsafe { (**f)() };
}
}
)*};
}
shims! { call0 = 0; call1 = 1; call2 = 2; }
fn thunks(slot: usize) -> (*const c_void, *mut *const c_void, *const c_void) {
match slot {
0 => (async0 as *const c_void, &raw mut RSWIFT_ASYNC0, call0 as *const c_void),
1 => (async1 as *const c_void, &raw mut RSWIFT_ASYNC1, call1 as *const c_void),
_ => (async2 as *const c_void, &raw mut RSWIFT_ASYNC2, call2 as *const c_void),
}
}
crate::thunk::runtime_thunk! {
fn task_entry = "rswift_task_entry" via RSWIFT_TASK_ENTRY {
prologue: [
"orr x29, x29, #0x1000000000000000",
"sub sp, sp, #0x20",
"stp x29, x30, [sp, #0x10]",
"str x22, [sp, #0x8]",
"add x29, sp, #0x10",
"mov x0, x20",
],
branch: "blr",
epilogue: [
"ldr x22, [sp, #0x8]",
"ldp x29, x30, [sp, #0x10]",
"and x29, x29, #0xefffffffffffffff",
"add sp, sp, #0x20",
"ldr x0, [x22, #8]",
"br x0",
],
}
}
pub(crate) struct TaskState {
done: std::sync::Mutex<bool>,
woken: std::sync::Condvar,
}
impl TaskState {
pub(crate) fn new() -> Self {
Self { done: std::sync::Mutex::new(false), woken: std::sync::Condvar::new() }
}
pub(crate) fn is_finished(&self) -> bool {
*self.done.lock().expect("task state")
}
pub(crate) fn wait(&self, limit: Option<std::time::Duration>) -> bool {
let mut done = self.done.lock().expect("task state");
match limit {
None => {
while !*done {
done = self.woken.wait(done).expect("task state");
}
true
}
Some(limit) => {
let deadline = std::time::Instant::now() + limit;
while !*done {
let left = deadline.saturating_duration_since(std::time::Instant::now());
if left.is_zero() {
return false;
}
let (next, timeout) = self
.woken
.wait_timeout(done, left)
.expect("task state");
done = next;
if timeout.timed_out() && !*done {
return false;
}
}
true
}
}
}
fn finish(&self) {
*self.done.lock().expect("task state") = true;
self.woken.notify_all();
}
}
#[repr(C)]
struct TaskCtx {
isa: *const c_void,
refcount: u64,
body: *mut Box<dyn FnOnce() + Send>,
state: *const TaskState,
}
fn retired() -> &'static std::sync::Mutex<Vec<usize>> {
static R: std::sync::OnceLock<std::sync::Mutex<Vec<usize>>> = std::sync::OnceLock::new();
R.get_or_init(|| std::sync::Mutex::new(Vec::new()))
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_task_body(ctx: *mut c_void) {
let c = unsafe { &mut *(ctx as *mut TaskCtx) };
let body = core::mem::replace(&mut c.body, core::ptr::null_mut());
if !body.is_null() {
let body = unsafe { Box::from_raw(body) };
if std::panic::catch_unwind(std::panic::AssertUnwindSafe(body)).is_err() {
eprintln!("rswift: a detached task's closure panicked; the task completed anyway");
}
}
let state = core::mem::replace(&mut c.state, core::ptr::null());
if !state.is_null() {
let state = unsafe { std::sync::Arc::from_raw(state) };
state.finish();
}
retired().lock().expect("retired contexts").push(ctx as usize);
}
pub(crate) unsafe fn task_entry_point(
action: Box<dyn FnOnce() + Send>,
state: &std::sync::Arc<TaskState>,
) -> (*const c_void, *mut c_void) {
static mut RECORD: FunctionPointer = FunctionPointer::new();
static ONCE: std::sync::Once = std::sync::Once::new();
unsafe {
ONCE.call_once(|| {
RSWIFT_TASK_ENTRY = rswift_task_body as *const c_void;
let record = crate::static_mut(&raw mut RECORD);
let at = &record.function as *const i32 as *const u8;
let delta = task_entry as *const u8 as isize - at as isize;
record.function =
i32::try_from(delta).expect("async function pointer out of range");
record.context_size = EMPTY_CONTEXT;
});
let body = Box::into_raw(Box::new(action));
let state = std::sync::Arc::into_raw(std::sync::Arc::clone(state));
let reused = retired().lock().expect("retired contexts").pop();
let ctx = match reused {
Some(addr) => addr as *mut TaskCtx,
None => Box::leak(Box::new(TaskCtx {
isa: crate::abi::immortal_isa(),
refcount: crate::abi::IMMORTAL_REFCOUNT,
body: core::ptr::null_mut(),
state: core::ptr::null(),
})) as *mut TaskCtx,
};
(*ctx).body = body;
(*ctx).state = state;
(&raw const RECORD as *const c_void, ctx as *mut c_void)
}
}