llam 0.1.1

Safe, Go-style Rust bindings for the LLAM runtime
use crate::error::{Error, Result};
use crate::sys;
use crate::task::{self, JoinError};
use std::mem;
use std::time::Duration;

#[derive(Clone, Copy, Debug)]
pub enum Profile {
    Balanced,
    ReleaseFast,
    DebugSafe,
    IoLatency,
}

impl Profile {
    pub(crate) fn raw(self) -> u32 {
        match self {
            Self::Balanced => sys::LLAM_RUNTIME_PROFILE_BALANCED,
            Self::ReleaseFast => sys::LLAM_RUNTIME_PROFILE_RELEASE_FAST,
            Self::DebugSafe => sys::LLAM_RUNTIME_PROFILE_DEBUG_SAFE,
            Self::IoLatency => sys::LLAM_RUNTIME_PROFILE_IO_LATENCY,
        }
    }
}

#[derive(Clone, Debug)]
pub struct RuntimeBuilder {
    opts: sys::llam_runtime_opts_t,
}

impl RuntimeBuilder {
    pub fn new() -> Self {
        let mut opts = unsafe { mem::zeroed::<sys::llam_runtime_opts_t>() };
        let rc = unsafe { sys::llam_runtime_opts_init(&mut opts, mem::size_of_val(&opts)) };
        assert_eq!(rc, 0, "llam_runtime_opts_init failed");
        Self { opts }
    }

    pub fn profile(mut self, profile: Profile) -> Self {
        self.opts.profile = profile.raw();
        self
    }

    pub fn deterministic(mut self, enabled: bool) -> Self {
        self.opts.deterministic = u32::from(enabled);
        self
    }

    pub fn forced_yield_every(mut self, every: u32) -> Self {
        self.opts.forced_yield_every = every;
        self
    }

    pub fn idle_spin(mut self, duration: Duration) -> Self {
        self.opts.idle_spin_ns = duration.as_nanos().min(u128::from(u64::MAX)) as u64;
        self
    }

    pub fn idle_spin_max_iters(mut self, iters: u32) -> Self {
        self.opts.idle_spin_max_iters = iters;
        self
    }

    pub fn sqpoll_cpu(mut self, cpu: i32) -> Self {
        self.opts.sqpoll_cpu = cpu;
        self
    }

    pub fn dynamic_workers(mut self, enabled: bool) -> Self {
        self.set_flag(sys::LLAM_RUNTIME_EXPERIMENTAL_F_DYNAMIC_WORKERS, enabled);
        self
    }

    pub fn worker_rings(mut self, enabled: bool) -> Self {
        self.set_flag(sys::LLAM_RUNTIME_EXPERIMENTAL_F_WORKER_RINGS, enabled);
        self
    }

    pub fn worker_rings_multishot(mut self, enabled: bool) -> Self {
        self.set_flag(
            sys::LLAM_RUNTIME_EXPERIMENTAL_F_WORKER_RINGS_MULTISHOT,
            enabled,
        );
        self
    }

    pub fn lockfree_normq(mut self, enabled: bool) -> Self {
        self.set_flag(sys::LLAM_RUNTIME_EXPERIMENTAL_F_LOCKFREE_NORMQ, enabled);
        self
    }

    pub fn huge_alloc(mut self, enabled: bool) -> Self {
        self.set_flag(sys::LLAM_RUNTIME_EXPERIMENTAL_F_HUGE_ALLOC, enabled);
        self
    }

    pub fn sqpoll(mut self, enabled: bool) -> Self {
        self.set_flag(sys::LLAM_RUNTIME_EXPERIMENTAL_F_SQPOLL, enabled);
        self
    }

    pub fn experimental_flags(mut self, flags: u64) -> Self {
        self.opts.experimental_flags = flags;
        self
    }

    pub fn raw_options(&self) -> &sys::llam_runtime_opts_t {
        &self.opts
    }

    pub fn init(self) -> Result<Runtime> {
        let rc = unsafe { sys::llam_runtime_init_ex(&self.opts, mem::size_of_val(&self.opts)) };
        if rc == 0 {
            Ok(Runtime { active: true })
        } else {
            Err(Error::last())
        }
    }

    pub fn create_handle(self) -> Result<RuntimeHandle> {
        let mut raw = std::ptr::null_mut();
        let rc =
            unsafe { sys::llam_runtime_create(&self.opts, mem::size_of_val(&self.opts), &mut raw) };
        if rc == 0 {
            Ok(RuntimeHandle { raw, active: true })
        } else {
            Err(Error::last())
        }
    }

    pub fn run<F>(self, f: F) -> Result<()>
    where
        F: FnOnce() -> Result<()> + Send + 'static,
    {
        let runtime = self.init()?;
        runtime.run(f)
    }

    fn set_flag(&mut self, flag: u64, enabled: bool) {
        if enabled {
            self.opts.experimental_flags |= flag;
        } else {
            self.opts.experimental_flags &= !flag;
        }
    }
}

impl Default for RuntimeBuilder {
    fn default() -> Self {
        Self::new()
    }
}

pub struct Runtime {
    active: bool,
}

impl Runtime {
    pub fn builder() -> RuntimeBuilder {
        RuntimeBuilder::new()
    }

    pub fn init() -> Result<Self> {
        RuntimeBuilder::new().init()
    }

    pub fn run<F>(self, f: F) -> Result<()>
    where
        F: FnOnce() -> Result<()> + Send + 'static,
    {
        let handle = task::try_spawn(f)?;
        let run_rc = unsafe { sys::llam_run() };
        let join_result = handle.join();
        self.shutdown();
        if run_rc != 0 {
            return Err(Error::last());
        }
        match join_result {
            Ok(result) => result,
            Err(JoinError::Runtime(error)) => Err(error),
            Err(JoinError::Panic(payload)) => std::panic::resume_unwind(payload),
            Err(JoinError::MissingResult) => Err(Error::from_errno(libc::EIO)),
        }
    }

    pub fn request_stop(&self) -> Result<()> {
        let rc = unsafe { sys::llam_runtime_request_stop() };
        if rc == 0 {
            Ok(())
        } else {
            Err(Error::last())
        }
    }

    pub fn stats(&self) -> Result<RuntimeStats> {
        let mut raw = unsafe { mem::zeroed::<sys::llam_runtime_stats_t>() };
        let rc = unsafe { sys::llam_runtime_collect_stats_ex(&mut raw, mem::size_of_val(&raw)) };
        if rc == 0 {
            Ok(RuntimeStats(raw))
        } else {
            Err(Error::last())
        }
    }

    pub fn shutdown(mut self) {
        self.shutdown_inner();
    }

    fn shutdown_inner(&mut self) {
        if self.active {
            unsafe { sys::llam_runtime_shutdown() };
            self.active = false;
        }
    }
}

impl Drop for Runtime {
    fn drop(&mut self) {
        self.shutdown_inner();
    }
}

#[derive(Clone, Copy, Debug)]
pub struct RuntimeStats(pub(crate) sys::llam_runtime_stats_t);

impl RuntimeStats {
    pub fn raw(&self) -> &sys::llam_runtime_stats_t {
        &self.0
    }

    pub fn ctx_switches(&self) -> u64 {
        self.0.ctx_switches
    }

    pub fn yields(&self) -> u64 {
        self.0.yields
    }

    pub fn parks(&self) -> u64 {
        self.0.parks
    }

    pub fn wakes(&self) -> u64 {
        self.0.wakes
    }

    pub fn steals(&self) -> u64 {
        self.0.steals
    }

    pub fn migrations(&self) -> u64 {
        self.0.migrations
    }

    pub fn blocking_calls(&self) -> u64 {
        self.0.blocking_calls
    }

    pub fn blocking_completions(&self) -> u64 {
        self.0.blocking_completions
    }

    pub fn io_submits(&self) -> u64 {
        self.0.io_submits
    }

    pub fn io_submit_calls(&self) -> u64 {
        self.0.io_submit_calls
    }

    pub fn io_submit_syscalls(&self) -> u64 {
        self.0.io_submit_syscalls
    }

    pub fn io_completions(&self) -> u64 {
        self.0.io_completions
    }

    pub fn active_workers(&self) -> u32 {
        self.0.active_workers
    }

    pub fn online_workers(&self) -> u32 {
        self.0.online_workers
    }

    pub fn active_nodes(&self) -> u32 {
        self.0.active_nodes
    }

    pub fn queue_overflows(&self) -> u64 {
        self.0.queue_overflows
    }

    pub fn yield_direct_attempts(&self) -> u64 {
        self.0.yield_direct_attempts
    }

    pub fn yield_direct_fast_hits(&self) -> u64 {
        self.0.yield_direct_fast_hits
    }
}

pub fn current_runtime_raw() -> *mut sys::llam_runtime_t {
    unsafe { sys::llam_runtime_default() }
}

pub struct RuntimeHandle {
    raw: *mut sys::llam_runtime_t,
    active: bool,
}

unsafe impl Send for RuntimeHandle {}

impl RuntimeHandle {
    pub fn run(&self) -> Result<()> {
        let rc = unsafe { sys::llam_runtime_run_handle(self.raw) };
        if rc == 0 {
            Ok(())
        } else {
            Err(Error::last())
        }
    }

    pub fn raw(&self) -> *mut sys::llam_runtime_t {
        self.raw
    }

    pub fn destroy(mut self) {
        self.destroy_inner();
    }

    fn destroy_inner(&mut self) {
        if self.active {
            unsafe { sys::llam_runtime_destroy(self.raw) };
            self.active = false;
            self.raw = std::ptr::null_mut();
        }
    }
}

impl Drop for RuntimeHandle {
    fn drop(&mut self) {
        self.destroy_inner();
    }
}