Skip to main content

ferrum_interfaces/vnext/operation/
storage_profile.rs

1use serde::{Deserialize, Deserializer, Serialize};
2
3use super::super::VNextError;
4use super::foundation::invalid_operation;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ElementType {
9    Bool,
10    U8,
11    U32,
12    I8,
13    I32,
14    F16,
15    Bf16,
16    F32,
17}
18
19impl ElementType {
20    pub const fn size_bytes(self) -> u64 {
21        match self {
22            Self::Bool | Self::U8 | Self::I8 => 1,
23            Self::F16 | Self::Bf16 => 2,
24            Self::U32 | Self::I32 | Self::F32 => 4,
25        }
26    }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum DynamicStorageAllocator {
32    LinearArena,
33    FixedBlockArena { block_bytes: u64 },
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
37#[serde(rename_all = "snake_case")]
38pub enum DynamicStorageView {
39    Contiguous,
40    PagedRegions { block_bytes: u64 },
41}
42
43/// Backend-neutral physical addressability offered by a runtime and accepted
44/// by an operation provider. This is independent from capacity formulas.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
46pub struct DynamicStorageProfile {
47    allocator: DynamicStorageAllocator,
48    view: DynamicStorageView,
49}
50
51impl DynamicStorageProfile {
52    pub fn new(
53        allocator: DynamicStorageAllocator,
54        view: DynamicStorageView,
55    ) -> Result<Self, VNextError> {
56        let valid = match (allocator, view) {
57            (DynamicStorageAllocator::LinearArena, DynamicStorageView::Contiguous) => true,
58            (
59                DynamicStorageAllocator::FixedBlockArena { block_bytes },
60                DynamicStorageView::Contiguous,
61            ) => block_bytes.is_power_of_two(),
62            (
63                DynamicStorageAllocator::FixedBlockArena {
64                    block_bytes: allocator_block,
65                },
66                DynamicStorageView::PagedRegions {
67                    block_bytes: view_block,
68                },
69            ) => allocator_block.is_power_of_two() && allocator_block == view_block,
70            (DynamicStorageAllocator::LinearArena, DynamicStorageView::PagedRegions { .. }) => {
71                false
72            }
73        };
74        if !valid {
75            return Err(invalid_operation(
76                "dynamic storage allocator/view profile is incompatible or invalid",
77            ));
78        }
79        Ok(Self { allocator, view })
80    }
81
82    pub const fn allocator(self) -> DynamicStorageAllocator {
83        self.allocator
84    }
85
86    pub const fn view(self) -> DynamicStorageView {
87        self.view
88    }
89}
90
91#[derive(Deserialize)]
92#[serde(deny_unknown_fields)]
93struct DynamicStorageProfileWire {
94    allocator: DynamicStorageAllocator,
95    view: DynamicStorageView,
96}
97
98impl<'de> Deserialize<'de> for DynamicStorageProfile {
99    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100    where
101        D: Deserializer<'de>,
102    {
103        let wire = DynamicStorageProfileWire::deserialize(deserializer)?;
104        Self::new(wire.allocator, wire.view).map_err(serde::de::Error::custom)
105    }
106}
107
108/// Canonical non-empty set of profiles accepted by a provider binding or one
109/// provider-owned workspace. The planner intersects this with runtime offers
110/// and the ordered runtime-policy allowlist.
111#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
112pub struct DynamicStorageRequirement {
113    accepted_profiles: Vec<DynamicStorageProfile>,
114}
115
116impl DynamicStorageRequirement {
117    pub fn new(mut accepted_profiles: Vec<DynamicStorageProfile>) -> Result<Self, VNextError> {
118        accepted_profiles.sort_unstable();
119        accepted_profiles.dedup();
120        if accepted_profiles.is_empty() {
121            return Err(invalid_operation(
122                "dynamic storage requirement has no accepted profile",
123            ));
124        }
125        Ok(Self { accepted_profiles })
126    }
127
128    pub fn contiguous() -> Self {
129        Self {
130            accepted_profiles: vec![DynamicStorageProfile {
131                allocator: DynamicStorageAllocator::LinearArena,
132                view: DynamicStorageView::Contiguous,
133            }],
134        }
135    }
136
137    pub fn accepted_profiles(&self) -> &[DynamicStorageProfile] {
138        &self.accepted_profiles
139    }
140
141    pub fn accepts(&self, profile: DynamicStorageProfile) -> bool {
142        self.accepted_profiles.binary_search(&profile).is_ok()
143    }
144}
145
146#[derive(Deserialize)]
147#[serde(deny_unknown_fields)]
148struct DynamicStorageRequirementWire {
149    accepted_profiles: Vec<DynamicStorageProfile>,
150}
151
152impl<'de> Deserialize<'de> for DynamicStorageRequirement {
153    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154    where
155        D: Deserializer<'de>,
156    {
157        let wire = DynamicStorageRequirementWire::deserialize(deserializer)?;
158        let original = wire.accepted_profiles.clone();
159        let requirement = Self::new(wire.accepted_profiles).map_err(serde::de::Error::custom)?;
160        if requirement.accepted_profiles != original {
161            return Err(serde::de::Error::custom(
162                "dynamic storage requirement profiles are not canonical and unique",
163            ));
164        }
165        Ok(requirement)
166    }
167}