Skip to main content

dope_core/driver/settings/
mod.rs

1use std::num;
2
3pub use config::Config;
4pub(crate) use config::plan::{Plan, Ready};
5pub use demands::Schedule;
6use o3::{base::num::bounded, depot, depot::capacity};
7pub use receive::Receive;
8pub use resource::Resource;
9pub use routes::Routes;
10
11use crate::{
12    driver::route::{SLOT_MASK, indices::SlotIndex, table},
13    io::{datagram::payload, socket::option, transfer},
14};
15
16pub mod claim;
17mod config;
18pub mod demands;
19pub mod errors;
20mod receive;
21mod resource;
22mod routes;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct DomainSum {
26    current: Domain,
27    added: Domain,
28}
29
30impl DomainSum {
31    pub const fn parts(&self) -> (&Domain, &Domain) {
32        (&self.current, &self.added)
33    }
34}
35
36/// Power-of-two receive slots valid for one datagram pool.
37#[derive(Clone, Copy, PartialEq, Eq)]
38#[repr(transparent)]
39pub struct Ring(Buffers);
40
41impl Ring {
42    pub fn try_round(entries: u32) -> Result<Self, errors::Claim> {
43        if entries < 2 {
44            return Err(errors::Config::ReceiveEntriesOutOfRange {
45                actual: entries,
46                minimum: 2,
47                maximum: u16::MAX as u32,
48            }
49            .into());
50        }
51        let count = entries
52            .checked_next_power_of_two()
53            .ok_or(errors::Claim::ReceiveEntriesPowerOfTwoOverflow { requested: entries })?;
54        if count > u32::from(u16::MAX) {
55            return Err(errors::Claim::ReceiveEntriesTooLarge {
56                actual: count as usize,
57                maximum: u16::MAX as usize,
58            });
59        }
60        Ok(Self(Buffers(
61            num::NonZero::<u16>::MIN.saturating_add(count as u16 - 1),
62        )))
63    }
64
65    pub const fn buffers(self) -> Buffers {
66        self.0
67    }
68}
69
70impl TryFrom<u16> for Ring {
71    type Error = errors::Config;
72
73    fn try_from(entries: u16) -> Result<Self, Self::Error> {
74        if entries < 2 {
75            return Err(errors::Config::ReceiveEntriesOutOfRange {
76                actual: entries as u32,
77                minimum: 2,
78                maximum: u16::MAX as u32,
79            });
80        }
81        if !entries.is_power_of_two() {
82            return Err(errors::Config::ReceiveEntriesNotPowerOfTwo { actual: entries });
83        }
84        Ok(Self(Buffers(
85            num::NonZero::<u16>::MIN.saturating_add(entries - 1),
86        )))
87    }
88}
89
90const _: () = assert!(std::mem::size_of::<Ring>() == std::mem::size_of::<Buffers>());
91
92#[derive(Clone, Copy, PartialEq, Eq)]
93#[repr(transparent)]
94pub struct Datagram(Receive);
95
96impl Datagram {
97    /// Constructs a datagram layout whose bounds are checked during compilation.
98    ///
99    pub const fn fixed<const ENTRIES: u16, const PAYLOAD: u32>() -> Self {
100        const {
101            assert!(ENTRIES >= 2);
102            assert!(ENTRIES.is_power_of_two());
103            let len = transfer::NonZero::from_payload(payload::Len::fixed::<PAYLOAD>());
104            assert!((ENTRIES as u64) * (len.into_nonzero().get() as u64) <= isize::MAX as u64);
105            let entries = Buffers(num::NonZero::<u16>::MIN.saturating_add(ENTRIES - 1));
106            Self(Receive::from_parts(entries, len))
107        }
108    }
109
110    pub const fn new(entries: Ring, payload: payload::Len) -> Self {
111        let len = transfer::NonZero::from_payload(payload);
112        Self(Receive::from_parts(entries.buffers(), len))
113    }
114
115    pub const fn receive(self) -> Receive {
116        self.0
117    }
118}
119
120const _: () = {
121    assert!(std::mem::size_of::<Datagram>() == std::mem::size_of::<Receive>());
122    assert!(std::mem::align_of::<Datagram>() == std::mem::align_of::<Receive>());
123};
124
125const MIN_SUBMISSION_CAPACITY: u32 = (option::MAX_STREAM_OPTIONS + 1) as u32;
126const MAX_SUBMISSION_CAPACITY: u32 = 32_768;
127const MAX_COMPLETION_CAPACITY: u32 = 65_536;
128pub(crate) const MAX_FILE_CAPACITY: u32 = SLOT_MASK as u32 + 1;
129
130/// Backend-neutral intent for making completed work visible to the driver.
131#[derive(Clone, Copy, PartialEq, Eq)]
132#[repr(u8)]
133pub enum Progress {
134    /// Avoid voluntarily deferring backend completion work.
135    Prompt,
136    /// Permit a backend to coalesce completion work until the next driver entry.
137    BatchedWhenSupported,
138}
139
140/// Static driver policy with a validated receive-buffer layout.
141///
142/// Invalid layouts cannot become profile constants:
143///
144pub trait Profile {
145    const QUEUES: QueueLayout;
146    const SCHEDULER: Schedule = Schedule::DEFAULT;
147    const COMPLETION_PROGRESS: Progress = Progress::BatchedWhenSupported;
148    /// Backend descriptor baseline. Topology-owned accept and outbound demand
149    /// is composed separately through [`Resource`].
150    const FILES: FileSlots = FileSlots::fixed::<0, 256>();
151    const RECEIVE: Receive = Receive::DEFAULT;
152    /// Stable resident-accounting domains available to one driver scope.
153    const RESIDENT_DOMAINS: Domain = Domain::ZERO;
154}
155
156#[derive(Clone, Copy, PartialEq, Eq, Debug)]
157#[repr(transparent)]
158pub struct Buffers(num::NonZero<u16>);
159
160impl Buffers {
161    pub const fn into_nonzero(self) -> num::NonZero<u16> {
162        self.0
163    }
164
165    pub(crate) const fn get(self) -> u16 {
166        self.0.get()
167    }
168}
169
170#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub struct QueueLayout {
172    submissions: Submission,
173    completions: Completion,
174}
175
176impl QueueLayout {
177    /// Creates a queue layout validated during compilation.
178    pub const fn fixed<const SUBMISSIONS: u32, const COMPLETIONS: u32>() -> Self {
179        const {
180            assert!(SUBMISSIONS >= MIN_SUBMISSION_CAPACITY);
181            assert!(SUBMISSIONS <= MAX_SUBMISSION_CAPACITY);
182            assert!(SUBMISSIONS.is_power_of_two());
183            assert!(COMPLETIONS != 0);
184            assert!(COMPLETIONS <= MAX_COMPLETION_CAPACITY);
185            assert!(COMPLETIONS.is_power_of_two());
186            assert!(COMPLETIONS >= SUBMISSIONS);
187            QueueLayout {
188                submissions: Submission(bounded::U32::clamp(SUBMISSIONS as usize)),
189                completions: Completion(bounded::U32::clamp(COMPLETIONS as usize)),
190            }
191        }
192    }
193
194    /// Enlarges submission capacity and keeps the completion queue at least as large.
195    pub fn resolve(self, need: demands::Submit) -> Result<Self, errors::Claim> {
196        let wanted = self.submissions().max(need.get());
197        let submit = wanted
198            .checked_next_power_of_two()
199            .ok_or(errors::Claim::QueueSubmissionsPowerOfTwoOverflow { requested: wanted })?;
200        if submit > MAX_SUBMISSION_CAPACITY {
201            return Err(errors::Config::QueueSubmissionsOutOfRange {
202                actual: submit,
203                minimum: MIN_SUBMISSION_CAPACITY,
204                maximum: MAX_SUBMISSION_CAPACITY,
205            }
206            .into());
207        }
208
209        let complete = self.completions().max(submit);
210
211        Ok(Self {
212            submissions: Submission(bounded::U32::clamp(submit as usize)),
213            completions: Completion(bounded::U32::clamp(complete as usize)),
214        })
215    }
216
217    pub const fn submission(self) -> Submission {
218        self.submissions
219    }
220
221    pub(crate) const fn completion(self) -> Completion {
222        self.completions
223    }
224
225    pub(crate) const fn submissions(self) -> u32 {
226        self.submissions.get()
227    }
228
229    pub(crate) const fn completions(self) -> u32 {
230        self.completions.get()
231    }
232}
233
234/// A descriptor-slot layout representable by every backend target table.
235#[derive(Clone, Copy, PartialEq, Eq)]
236pub struct FileSlots {
237    accept: table::Capacity,
238    table: bounded::U32<1, MAX_FILE_CAPACITY>,
239}
240
241impl FileSlots {
242    /// Creates a descriptor-slot layout validated during compilation.
243    ///
244    ///
245    pub const fn fixed<const ACCEPT: u32, const OUTBOUND: u32>() -> Self {
246        const {
247            assert!(ACCEPT <= u32::MAX - OUTBOUND);
248            assert!(OUTBOUND != 0);
249            let total = ACCEPT + OUTBOUND;
250            assert!(total <= MAX_FILE_CAPACITY);
251            FileSlots {
252                accept: table::Capacity(capacity::Count::new(ACCEPT)),
253                table: bounded::U32::clamp(total as usize),
254            }
255        }
256    }
257
258    /// Resolves descriptor demand against this validated baseline.
259    pub fn resolve(self, need: demands::Files) -> Result<Self, errors::Config> {
260        let accept = self.accept().raw().max(need.accepted().get());
261        let outbound = self.outbound().raw().max(need.outbound().get());
262        let total = accept as u64 + outbound as u64;
263        let maximum = MAX_FILE_CAPACITY;
264        if total > maximum as u64 {
265            return Err(errors::Config::DescriptorCapacityOutOfRange {
266                accepted: accept,
267                outbound,
268                actual: total,
269                minimum: 1,
270                maximum,
271            });
272        }
273        Ok(Self {
274            accept: table::Capacity(capacity::Count::new(accept)),
275            table: bounded::U32::clamp(total as usize),
276        })
277    }
278
279    #[must_use]
280    pub const fn capacity(self) -> table::Capacity {
281        table::Capacity(capacity::Count::new(self.table.get()))
282    }
283
284    #[must_use]
285    pub const fn accept(self) -> table::Capacity {
286        self.accept
287    }
288
289    #[must_use]
290    pub const fn outbound(self) -> table::Capacity {
291        table::Capacity(capacity::Count::new(self.table.get() - self.accept.raw()))
292    }
293
294    pub(crate) fn slots(self) -> impl DoubleEndedIterator<Item = SlotIndex> + ExactSizeIterator {
295        self.capacity()
296            .into_iter()
297            .rev()
298            .take(self.outbound().count().get())
299            .rev()
300    }
301
302    pub(crate) fn accept_slot(self) -> Option<SlotIndex> {
303        let span = self.accept.span()?;
304        Some(SlotIndex::from_field(u64::from(span.get())))
305    }
306
307    pub(crate) const fn bound(self) -> bounded::U32<1, MAX_FILE_CAPACITY> {
308        self.table
309    }
310}
311
312/// Representable number of resident-accounting domains in one driver scope.
313///
314#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
315#[repr(transparent)]
316pub struct Domain(u32);
317
318impl Domain {
319    pub const ZERO: Self = Self(0);
320    pub const ONE: Self = Self(1);
321    pub const MAX: Self = Self(u32::MAX);
322
323    /// Creates a count across the complete `u32` representation.
324    pub const fn fixed<const COUNT: u32>() -> Self {
325        Self(COUNT)
326    }
327
328    /// Resolves additive resident-domain demand against this baseline.
329    #[must_use]
330    pub const fn resolve(self, need: Self) -> Self {
331        if self.0 >= need.0 { self } else { need }
332    }
333
334    pub const fn combine(self, next: Self) -> Result<Self, DomainSum> {
335        match self.0.checked_add(next.0) {
336            Some(total) => Ok(Self(total)),
337            None => Err(DomainSum {
338                current: self,
339                added: next,
340            }),
341        }
342    }
343
344    #[must_use]
345    pub const fn get(self) -> u32 {
346        self.0
347    }
348}
349
350const _: () = {
351    assert!(std::mem::size_of::<Domain>() == std::mem::size_of::<u32>());
352    assert!(std::mem::align_of::<Domain>() == std::mem::align_of::<u32>());
353};
354
355#[derive(Clone, Copy, PartialEq, Eq, Debug)]
356#[repr(transparent)]
357pub struct Submission(bounded::U32<MIN_SUBMISSION_CAPACITY, MAX_SUBMISSION_CAPACITY>);
358
359impl Submission {
360    pub(crate) const fn get(self) -> u32 {
361        self.0.get()
362    }
363}
364
365#[derive(Clone, Copy, PartialEq, Eq, Debug)]
366#[repr(transparent)]
367pub(crate) struct Completion(pub(crate) bounded::U32<1, MAX_COMPLETION_CAPACITY>);
368
369impl Completion {
370    pub(crate) const fn get(self) -> u32 {
371        self.0.get()
372    }
373}
374
375const _: () = {
376    assert!(std::mem::size_of::<Completion>() == std::mem::size_of::<u32>());
377    assert!(std::mem::align_of::<Completion>() == std::mem::align_of::<u32>());
378};
379
380impl From<FileSlots> for (table::Capacity, table::Capacity) {
381    fn from(layout: FileSlots) -> Self {
382        (layout.capacity(), layout.accept)
383    }
384}
385
386// SAFETY: Completion retains one immutable bounded queue capacity in every copy.
387unsafe impl depot::raw::Count for Completion {
388    const MAX: u32 = MAX_COMPLETION_CAPACITY;
389
390    fn count(self) -> capacity::Count {
391        capacity::Count::new(self.0.get())
392    }
393}