Skip to main content

vk/mem/
metadata.rs

1//! Shared, private placement dispatch for native and virtual blocks.
2
3use super::{
4	AllocError,
5	granularity::{Granularity, ResourceClass},
6	host::HostMemory,
7	linear::Linear,
8	tlsf::Tlsf,
9	virtual_block::{PlacementStrategy, VirtualAllocationInfo, VirtualBlockStats},
10};
11
12/// Placement metadata for custom pools and host-only virtual blocks.
13///
14/// Linear placement appends after the current tail; middle holes are not reused.
15/// Releasing all regions restarts the range. Last-in-first-out release rewinds
16/// a stack; a single-block pool also supports ring and double-stack lifetimes.
17/// Strategy flags affect TLSF only. These are placement rules, not GPU lifetime
18/// tracking or latency guarantees.
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20pub enum AllocationAlgorithm {
21	#[default]
22	Tlsf,
23	Linear,
24}
25
26#[derive(Clone, Copy)]
27pub(super) enum Placement {
28	Tlsf(usize, u64),
29	Linear { offset: u64, serial: u64 },
30}
31
32// Keep the existing TLSF block header inline and host acquisition fallible.
33// Boxing it would add an unrelated, infallible allocation per native block.
34#[expect(
35	clippy::large_enum_variant,
36	reason = "inline metadata preserves fallible block acquisition"
37)]
38pub(super) enum Metadata<'callbacks> {
39	Tlsf(Tlsf<'callbacks>),
40	Linear(Linear<'callbacks>),
41}
42
43impl<'callbacks> Metadata<'callbacks> {
44	/// Total free extent in constant time, including currently unusable holes.
45	pub fn free_bytes(&self) -> u64 {
46		match self {
47			Self::Tlsf(metadata) => metadata.free_bytes(),
48			Self::Linear(metadata) => metadata.free_bytes(),
49		}
50	}
51
52	pub fn is_empty(&self) -> bool {
53		match self {
54			Self::Tlsf(metadata) => metadata.is_empty(),
55			Self::Linear(metadata) => metadata.is_empty(),
56		}
57	}
58
59	pub fn new_native(
60		size: u64,
61		algorithm: AllocationAlgorithm,
62		allow_ring: bool,
63		granularity: Granularity,
64		host: HostMemory<'callbacks>,
65	) -> Result<Self, AllocError> {
66		let mut metadata = Self::new_with_host(size, algorithm, allow_ring, host)?;
67		match &mut metadata {
68			Self::Tlsf(metadata) => metadata.granularity = granularity,
69			Self::Linear(metadata) => metadata.granularity = granularity,
70		}
71		Ok(metadata)
72	}
73
74	pub fn new(
75		size: u64,
76		algorithm: AllocationAlgorithm,
77		allow_ring: bool,
78	) -> Result<Self, AllocError> {
79		match algorithm {
80			AllocationAlgorithm::Tlsf => Tlsf::new(size).map(Self::Tlsf),
81			AllocationAlgorithm::Linear => Linear::new(size, allow_ring).map(Self::Linear),
82		}
83	}
84
85	pub fn host_memory(&self) -> HostMemory<'callbacks> {
86		match self {
87			Self::Tlsf(metadata) => metadata.host_memory(),
88			Self::Linear(metadata) => metadata.host_memory(),
89		}
90	}
91
92	pub fn new_with_host(
93		size: u64,
94		algorithm: AllocationAlgorithm,
95		allow_ring: bool,
96		host: HostMemory<'callbacks>,
97	) -> Result<Self, AllocError> {
98		match algorithm {
99			AllocationAlgorithm::Tlsf => Tlsf::new_with_host(size, host).map(Self::Tlsf),
100			AllocationAlgorithm::Linear => {
101				Linear::new_with_host(size, allow_ring, host).map(Self::Linear)
102			}
103		}
104	}
105
106	#[inline]
107	pub fn allocate(
108		&mut self,
109		size: u64,
110		alignment: u64,
111		strategy: PlacementStrategy,
112		upper: bool,
113	) -> Result<Placement, AllocError> {
114		match self {
115			Self::Tlsf(metadata) => {
116				if upper {
117					return Err(AllocError::UnsupportedCapability(
118						"upper address requires linear placement",
119					));
120				}
121				metadata
122					.allocate(size, alignment, strategy)
123					.map(|(index, serial)| Placement::Tlsf(index, serial))
124			}
125			Self::Linear(metadata) => metadata.allocate(size, alignment, upper),
126		}
127	}
128
129	/// Place a native region and return it with its aligned offset, so callers
130	/// need not re-validate the placement they just received.
131	#[inline]
132	pub fn allocate_resource(
133		&mut self,
134		size: u64,
135		alignment: u64,
136		strategy: PlacementStrategy,
137		upper: bool,
138		class: ResourceClass,
139	) -> Result<(Placement, u64), AllocError> {
140		match self {
141			Self::Tlsf(metadata) => {
142				if upper {
143					return Err(AllocError::UnsupportedCapability(
144						"upper address requires linear placement",
145					));
146				}
147				metadata
148					.allocate_resource(size, alignment, strategy, class)
149					.map(|(index, serial)| (Placement::Tlsf(index, serial), metadata.offset(index)))
150			}
151			Self::Linear(metadata) => metadata.allocate_resource(size, alignment, upper, class),
152		}
153	}
154
155	pub fn info(&self, placement: Placement) -> Result<VirtualAllocationInfo, AllocError> {
156		match (self, placement) {
157			(Self::Tlsf(metadata), Placement::Tlsf(index, serial)) => metadata.info(index, serial),
158			(Self::Linear(metadata), Placement::Linear { offset, serial }) => {
159				metadata.info(offset, serial)
160			}
161			_ => Err(AllocError::UnknownAllocation),
162		}
163	}
164
165	#[inline]
166	pub fn free(&mut self, placement: Placement) -> Result<(), AllocError> {
167		match (self, placement) {
168			(Self::Tlsf(metadata), Placement::Tlsf(index, serial)) => metadata.free(index, serial),
169			(Self::Linear(metadata), Placement::Linear { offset, serial }) => {
170				metadata.free(offset, serial)
171			}
172			_ => Err(AllocError::UnknownAllocation),
173		}
174	}
175
176	pub fn clear(&mut self) {
177		match self {
178			Self::Tlsf(metadata) => metadata.clear(),
179			Self::Linear(metadata) => metadata.clear(),
180		}
181	}
182
183	pub fn set_user_data(&mut self, placement: Placement, data: usize) -> Result<(), AllocError> {
184		match (self, placement) {
185			(Self::Tlsf(metadata), Placement::Tlsf(index, serial)) => {
186				metadata.set_user_data(index, serial, data)
187			}
188			(Self::Linear(metadata), Placement::Linear { offset, serial }) => {
189				metadata.set_user_data(offset, serial, data)
190			}
191			_ => Err(AllocError::UnknownAllocation),
192		}
193	}
194
195	pub fn for_each_allocation(&self, visit: impl FnMut(u64, u64, usize)) {
196		match self {
197			Self::Tlsf(metadata) => metadata.for_each_allocation(visit),
198			Self::Linear(metadata) => metadata.for_each_allocation(visit),
199		}
200	}
201
202	pub fn for_each_free(&self, visit: impl FnMut(u64, u64)) {
203		match self {
204			Self::Tlsf(metadata) => metadata.for_each_free(visit),
205			Self::Linear(metadata) => metadata.for_each_free(visit),
206		}
207	}
208
209	pub fn statistics(&self) -> VirtualBlockStats {
210		match self {
211			Self::Tlsf(metadata) => metadata.statistics(),
212			Self::Linear(metadata) => metadata.statistics(),
213		}
214	}
215}
216
217#[cfg(test)]
218#[path = "../../../test/rs/mem/test_granularity.rs"]
219mod tests;