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