Skip to main content

gpu_async_reactor/core/
declared.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Declared settings: a setting whose value may be unavailable on a host resolves once, at
4//! startup, through a DECLARED fallback chain (never a guess), and reports what it did:
5//! - available: the requested value runs;
6//! - unavailable, not strict: a coded WARNING names the requested value, why it is unavailable
7//!   and the fallback that runs;
8//! - unavailable, strict: a coded refusal.
9//!
10//! [`Declared`] is the shape any such setting implements; [`ExecutorKind`] is the first.
11
12use alloc::format;
13use alloc::string::String;
14use alloc::vec::Vec;
15
16use crate::core::error::{Error, ErrorCode, Result};
17
18/// Whether a setting may fall back when its value is unavailable.
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
20pub enum Strictness {
21    /// Fall back along the declared chain, with a coded warning.
22    #[default]
23    Fallback,
24    /// Refuse: no fallback is acceptable.
25    Strict,
26}
27
28/// A setting value with a declared fallback chain.
29pub trait Declared: Copy + Eq + 'static {
30    /// What the host offers, probed once by the host (never by the core).
31    type Caps;
32
33    /// The value's spelling.
34    fn name(self) -> &'static str;
35
36    /// Why `self` cannot run on `caps`, or `None` when it can.
37    fn lacks(self, caps: &Self::Caps) -> Option<String>;
38
39    /// The declared fallback of `self` (`None`: the end of the chain, which must run anywhere).
40    fn fallback(self) -> Option<Self>;
41
42    /// The code of the warning a fallback emits: (requested, why, running).
43    const FALLBACK_CODE: ErrorCode;
44
45    /// The code of the refusal a strict setting emits: (requested, why).
46    const REFUSED_CODE: ErrorCode;
47}
48
49/// A resolved setting: what was asked, what runs, and the warning when they differ.
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct Resolution<K> {
52    /// The value as requested.
53    pub requested: K,
54    /// The value that runs.
55    pub running: K,
56    /// The coded warning when `running != requested`.
57    pub warning: Option<Error>,
58}
59
60impl<K: Declared> Resolution<K> {
61    /// `running`, naming the fallback when one was taken (boot report, metrics, bench record).
62    #[must_use]
63    pub fn label(&self) -> String {
64        if self.running == self.requested {
65            String::from(self.running.name())
66        } else {
67            format!(
68                "{} (fallback from {})",
69                self.running.name(),
70                self.requested.name()
71            )
72        }
73    }
74}
75
76/// Resolve `requested` against `caps`: walk the declared chain while the value cannot run.
77///
78/// # Errors
79/// The setting's refusal code when `strictness` is [`Strictness::Strict`] and `requested`
80/// cannot run.
81pub fn resolve<K: Declared>(
82    requested: K,
83    strictness: Strictness,
84    caps: &K::Caps,
85) -> Result<Resolution<K>> {
86    let mut running = requested;
87    let mut why: Vec<String> = Vec::new();
88    while let Some(reason) = running.lacks(caps) {
89        if strictness == Strictness::Strict {
90            return Err(Error::new(
91                K::REFUSED_CODE,
92                alloc::vec![String::from(requested.name()), reason],
93            ));
94        }
95        why.push(format!("{}: {reason}", running.name()));
96        match running.fallback() {
97            Some(next) => running = next,
98            None => break,
99        }
100    }
101    let warning = (running != requested).then(|| {
102        Error::new(
103            K::FALLBACK_CODE,
104            alloc::vec![
105                String::from(requested.name()),
106                why.join("; "),
107                String::from(running.name()),
108            ],
109        )
110    });
111    Ok(Resolution {
112        requested,
113        running,
114        warning,
115    })
116}
117
118/// How a waiting thread runs a wait.
119#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
120pub enum ExecutorKind {
121    /// The wait runs inline on the waiting thread: a blocking sleep, then a blocking park or
122    /// poll. Other work proceeds only on other threads or through issue-ahead.
123    Naive,
124    /// The wait is a task of this crate's [`Executor`](crate::Executor): its idle sleeps to the
125    /// earliest predicted deadline, then parks on the completion line; ready tasks run between.
126    GpuReactor,
127    /// As `GpuReactor`, with the deadline sleep an `IORING_OP_TIMEOUT` (ns) on an io_uring.
128    IoUring,
129}
130
131/// What a host offers the executors, probed once.
132#[derive(Clone, Debug, PartialEq, Eq)]
133pub struct ExecutorCaps {
134    /// `Ok` when a user-mode word wait runs (aarch64 `wfe`, x86 `mwaitx`/`umwait`), else why not.
135    pub word_wait: core::result::Result<(), String>,
136    /// `Ok` when an io_uring can be set up, else why not.
137    pub io_uring: core::result::Result<(), String>,
138}
139
140impl ExecutorKind {
141    /// Every kind, in declared order.
142    pub const ALL: [ExecutorKind; 3] = [Self::Naive, Self::GpuReactor, Self::IoUring];
143
144    /// Parse a spelling (`naive`, `gpu-reactor`, `io-uring`).
145    ///
146    /// # Errors
147    /// [`InvalidExecutor`](crate::ErrorCode::InvalidExecutor).
148    pub fn parse(text: &str) -> Result<Self> {
149        Self::ALL
150            .into_iter()
151            .find(|k| k.name() == text)
152            .ok_or_else(|| {
153                let names: Vec<&str> = Self::ALL.iter().map(|k| k.name()).collect();
154                crate::error!(InvalidExecutor, text, names.join(", "))
155            })
156    }
157}
158
159impl Declared for ExecutorKind {
160    type Caps = ExecutorCaps;
161
162    fn name(self) -> &'static str {
163        match self {
164            Self::Naive => "naive",
165            Self::GpuReactor => "gpu-reactor",
166            Self::IoUring => "io-uring",
167        }
168    }
169
170    fn lacks(self, caps: &ExecutorCaps) -> Option<String> {
171        match self {
172            Self::Naive => None,
173            Self::GpuReactor => caps.word_wait.as_ref().err().cloned(),
174            Self::IoUring => caps
175                .io_uring
176                .as_ref()
177                .err()
178                .or(caps.word_wait.as_ref().err())
179                .cloned(),
180        }
181    }
182
183    fn fallback(self) -> Option<Self> {
184        match self {
185            Self::IoUring => Some(Self::GpuReactor),
186            Self::GpuReactor => Some(Self::Naive),
187            Self::Naive => None,
188        }
189    }
190
191    const FALLBACK_CODE: ErrorCode = ErrorCode::ExecutorFallback;
192    const REFUSED_CODE: ErrorCode = ErrorCode::ExecutorRefused;
193}
194
195#[cfg(test)]
196#[path = "declared_tests.rs"]
197mod tests;