initial macos porting work
This commit is contained in:
@@ -1178,7 +1178,11 @@ fn schedule_immediate_interval(owner: *const ThreadState, id: usize) {
|
||||
};
|
||||
(callback.borrow_mut())();
|
||||
// Re-enqueue for the next turn if still live.
|
||||
if current_thread().immediate_intervals.borrow().contains_key(&id) {
|
||||
if current_thread()
|
||||
.immediate_intervals
|
||||
.borrow()
|
||||
.contains_key(&id)
|
||||
{
|
||||
schedule_immediate_interval(owner, id);
|
||||
}
|
||||
}));
|
||||
@@ -1207,12 +1211,8 @@ fn dispatch_expired_timers() {
|
||||
.deadline
|
||||
.checked_add(interval)
|
||||
.unwrap_or(Duration::MAX);
|
||||
let next_timer = TimerNode::interval(
|
||||
timer.id,
|
||||
next_deadline,
|
||||
interval,
|
||||
Rc::clone(&callback),
|
||||
);
|
||||
let next_timer =
|
||||
TimerNode::interval(timer.id, next_deadline, interval, Rc::clone(&callback));
|
||||
current_thread().timers.borrow_mut().insert(next_timer);
|
||||
|
||||
push_local_macrotask(Box::new(move || {
|
||||
@@ -1394,8 +1394,7 @@ mod tests {
|
||||
// turns by awaiting a sleep between checks.
|
||||
let count = Rc::new(Cell::new(0usize));
|
||||
let count_clone = Rc::clone(&count);
|
||||
let handle_slot: Rc<RefCell<Option<super::IntervalHandle>>> =
|
||||
Rc::new(RefCell::new(None));
|
||||
let handle_slot: Rc<RefCell<Option<super::IntervalHandle>>> = Rc::new(RefCell::new(None));
|
||||
let handle_slot_clone = Rc::clone(&handle_slot);
|
||||
|
||||
let handle = set_interval(Duration::ZERO, move || {
|
||||
|
||||
340
lib/runtime/src/platform/macos_aarch64/driver.rs
Normal file
340
lib/runtime/src/platform/macos_aarch64/driver.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
//! Public runtime driver primitives for macOS.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::io;
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct NotifierInner {
|
||||
write_fd: RawFd,
|
||||
closed: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl NotifierInner {
|
||||
fn notify(&self) -> io::Result<()> {
|
||||
if self.closed.load(Ordering::Acquire) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::BrokenPipe,
|
||||
"target runtime driver is closed",
|
||||
));
|
||||
}
|
||||
|
||||
let byte = 1u8;
|
||||
let written = unsafe {
|
||||
libc::write(
|
||||
self.write_fd,
|
||||
&byte as *const u8 as *const libc::c_void,
|
||||
std::mem::size_of::<u8>(),
|
||||
)
|
||||
};
|
||||
if written < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() == io::ErrorKind::WouldBlock {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Cross-thread notifier for a runtime thread's driver.
|
||||
pub struct ThreadNotifier {
|
||||
inner: NotifierInner,
|
||||
}
|
||||
|
||||
impl ThreadNotifier {
|
||||
/// Sends a wake notification to the target runtime thread.
|
||||
pub fn notify(&self) -> io::Result<()> {
|
||||
self.inner.notify()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
|
||||
/// Readiness information returned by [`Driver::poll`].
|
||||
pub struct ReadyEvents {
|
||||
/// One or more timer expirations are pending.
|
||||
pub timer: bool,
|
||||
/// One or more cross-thread wake notifications are pending.
|
||||
pub wake: bool,
|
||||
}
|
||||
|
||||
/// Low-level macOS runtime driver backed by `kqueue` and a wake pipe.
|
||||
pub struct Driver {
|
||||
kqueue_fd: RawFd,
|
||||
wake_read_fd: RawFd,
|
||||
wake_write_fd: RawFd,
|
||||
closed: Arc<AtomicBool>,
|
||||
timer_deadline: Cell<Option<Duration>>,
|
||||
pending_wakes: Cell<u64>,
|
||||
pending_timers: Cell<u64>,
|
||||
}
|
||||
|
||||
/// Creates a new driver and its paired [`ThreadNotifier`].
|
||||
pub fn create() -> io::Result<(Driver, ThreadNotifier)> {
|
||||
create_driver()
|
||||
}
|
||||
|
||||
/// Creates a new driver and its paired [`ThreadNotifier`].
|
||||
pub fn create_driver() -> io::Result<(Driver, ThreadNotifier)> {
|
||||
let kqueue_fd = cvt(unsafe { libc::kqueue() })?;
|
||||
|
||||
let mut pipe_fds = [0; 2];
|
||||
cvt(unsafe { libc::pipe(pipe_fds.as_mut_ptr()) })?;
|
||||
let wake_read_fd = pipe_fds[0];
|
||||
let wake_write_fd = pipe_fds[1];
|
||||
|
||||
set_nonblocking(wake_read_fd)?;
|
||||
set_nonblocking(wake_write_fd)?;
|
||||
|
||||
let event = libc::kevent {
|
||||
ident: wake_read_fd as usize,
|
||||
filter: libc::EVFILT_READ,
|
||||
flags: libc::EV_ADD | libc::EV_ENABLE,
|
||||
fflags: 0,
|
||||
data: 0,
|
||||
udata: std::ptr::null_mut(),
|
||||
};
|
||||
|
||||
let submitted = unsafe {
|
||||
libc::kevent(
|
||||
kqueue_fd,
|
||||
&event,
|
||||
1,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if submitted < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
unsafe {
|
||||
libc::close(wake_read_fd);
|
||||
libc::close(wake_write_fd);
|
||||
libc::close(kqueue_fd);
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let closed = Arc::new(AtomicBool::new(false));
|
||||
let driver = Driver {
|
||||
kqueue_fd,
|
||||
wake_read_fd,
|
||||
wake_write_fd,
|
||||
closed: Arc::clone(&closed),
|
||||
timer_deadline: Cell::new(None),
|
||||
pending_wakes: Cell::new(0),
|
||||
pending_timers: Cell::new(0),
|
||||
};
|
||||
|
||||
let notifier = ThreadNotifier {
|
||||
inner: NotifierInner {
|
||||
write_fd: wake_write_fd,
|
||||
closed,
|
||||
},
|
||||
};
|
||||
|
||||
Ok((driver, notifier))
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
pub(crate) fn bind_current_thread(&self) {}
|
||||
|
||||
pub(crate) fn unbind_current_thread(&self) {}
|
||||
|
||||
/// Polls the driver without blocking.
|
||||
pub fn poll(&self) -> io::Result<Option<ReadyEvents>> {
|
||||
let mut pending = ReadyEvents::default();
|
||||
if self.pending_wakes.get() > 0 {
|
||||
pending.wake = true;
|
||||
}
|
||||
if self.pending_timers.get() > 0 {
|
||||
pending.timer = true;
|
||||
}
|
||||
if pending.wake || pending.timer {
|
||||
return Ok(Some(pending));
|
||||
}
|
||||
self.process(Some(Duration::ZERO))
|
||||
}
|
||||
|
||||
/// Blocks until at least one event is available.
|
||||
pub fn wait(&self) -> io::Result<()> {
|
||||
let now = monotonic_now()?;
|
||||
let timeout = self
|
||||
.timer_deadline
|
||||
.get()
|
||||
.map(|deadline| deadline.saturating_sub(now));
|
||||
let _ = self.process(timeout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the currently armed timer deadline.
|
||||
pub fn rearm_timer(&self, deadline: Option<Duration>) -> io::Result<()> {
|
||||
self.timer_deadline.set(deadline);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Drains the accumulated wake notification count.
|
||||
pub fn drain_wake(&self) -> io::Result<u64> {
|
||||
let wakes = self.pending_wakes.replace(0);
|
||||
if wakes == 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"no wake events are pending",
|
||||
))
|
||||
} else {
|
||||
Ok(wakes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Drains the accumulated timer-expiration count.
|
||||
pub fn drain_timer(&self) -> io::Result<u64> {
|
||||
let timers = self.pending_timers.replace(0);
|
||||
if timers == 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"no timer events are pending",
|
||||
))
|
||||
} else {
|
||||
Ok(timers)
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&self, timeout: Option<Duration>) -> io::Result<Option<ReadyEvents>> {
|
||||
let mut ready = ReadyEvents::default();
|
||||
|
||||
let mut events = [unsafe { std::mem::zeroed::<libc::kevent>() }; 16];
|
||||
let timeout_spec = timeout_to_timespec(timeout);
|
||||
let timeout_ptr = timeout_spec
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null(), |value| value as *const libc::timespec);
|
||||
|
||||
let result = unsafe {
|
||||
libc::kevent(
|
||||
self.kqueue_fd,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
events.as_mut_ptr(),
|
||||
events.len() as i32,
|
||||
timeout_ptr,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() != io::ErrorKind::Interrupted {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
let mut saw_any = false;
|
||||
let count = result.max(0) as usize;
|
||||
if count > 0 {
|
||||
saw_any = true;
|
||||
for event in events.iter().take(count) {
|
||||
if event.ident as RawFd == self.wake_read_fd {
|
||||
ready.wake = true;
|
||||
let wakes = drain_wake_pipe(self.wake_read_fd)?;
|
||||
self.pending_wakes
|
||||
.set(self.pending_wakes.get().saturating_add(wakes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(deadline) = self.timer_deadline.get()
|
||||
&& monotonic_now()? >= deadline
|
||||
{
|
||||
ready.timer = true;
|
||||
saw_any = true;
|
||||
self.timer_deadline.set(None);
|
||||
self.pending_timers
|
||||
.set(self.pending_timers.get().saturating_add(1));
|
||||
}
|
||||
|
||||
if saw_any { Ok(Some(ready)) } else { Ok(None) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Driver {
|
||||
fn drop(&mut self) {
|
||||
self.closed.store(true, Ordering::Release);
|
||||
unsafe {
|
||||
libc::close(self.wake_read_fd);
|
||||
libc::close(self.wake_write_fd);
|
||||
libc::close(self.kqueue_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current monotonic clock reading.
|
||||
pub fn monotonic_now() -> io::Result<Duration> {
|
||||
let mut now = std::mem::MaybeUninit::<libc::timespec>::uninit();
|
||||
let result = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()) };
|
||||
if result < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let now = unsafe { now.assume_init() };
|
||||
Ok(Duration::new(now.tv_sec as u64, now.tv_nsec as u32))
|
||||
}
|
||||
|
||||
fn cvt(value: libc::c_int) -> io::Result<libc::c_int> {
|
||||
if value < 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_nonblocking(fd: RawFd) -> io::Result<()> {
|
||||
let flags = cvt(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
|
||||
cvt(unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn timeout_to_timespec(timeout: Option<Duration>) -> Option<libc::timespec> {
|
||||
timeout.map(|value| libc::timespec {
|
||||
tv_sec: value.as_secs() as libc::time_t,
|
||||
tv_nsec: value.subsec_nanos() as libc::c_long,
|
||||
})
|
||||
}
|
||||
|
||||
fn drain_wake_pipe(fd: RawFd) -> io::Result<u64> {
|
||||
let mut wakes = 0u64;
|
||||
let mut buf = [0u8; 256];
|
||||
|
||||
loop {
|
||||
let read = unsafe {
|
||||
libc::read(
|
||||
fd,
|
||||
buf.as_mut_ptr() as *mut libc::c_void,
|
||||
buf.len() as libc::size_t,
|
||||
)
|
||||
};
|
||||
|
||||
if read > 0 {
|
||||
wakes = wakes.saturating_add(read as u64);
|
||||
continue;
|
||||
}
|
||||
|
||||
if read == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let error = io::Error::last_os_error();
|
||||
if error.kind() == io::ErrorKind::WouldBlock {
|
||||
break;
|
||||
}
|
||||
if error.kind() == io::ErrorKind::Interrupted {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(wakes.max(1))
|
||||
}
|
||||
2
lib/runtime/src/platform/macos_aarch64/mod.rs
Normal file
2
lib/runtime/src/platform/macos_aarch64/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod driver;
|
||||
pub mod runtime;
|
||||
1416
lib/runtime/src/platform/macos_aarch64/runtime.rs
Normal file
1416
lib/runtime/src/platform/macos_aarch64/runtime.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,9 @@
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub mod linux_x86_64;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub mod macos_aarch64;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
pub use linux_x86_64 as current;
|
||||
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
|
||||
pub use macos_aarch64 as current;
|
||||
|
||||
Reference in New Issue
Block a user