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	pub fn allocate(
107		&mut self,
108		size: u64,
109		alignment: u64,
110		strategy: PlacementStrategy,
111		upper: bool,
112	) -> Result<Placement, AllocError> {
113		match self {
114			Self::Tlsf(metadata) => {
115				if upper {
116					return Err(AllocError::UnsupportedCapability(
117						"upper address requires linear placement",
118					));
119				}
120				metadata
121					.allocate(size, alignment, strategy)
122					.map(|(index, serial)| Placement::Tlsf(index, serial))
123			}
124			Self::Linear(metadata) => metadata.allocate(size, alignment, upper),
125		}
126	}
127
128	pub fn allocate_resource(
129		&mut self,
130		size: u64,
131		alignment: u64,
132		strategy: PlacementStrategy,
133		upper: bool,
134		class: ResourceClass,
135	) -> Result<Placement, AllocError> {
136		match self {
137			Self::Tlsf(metadata) => {
138				if upper {
139					return Err(AllocError::UnsupportedCapability(
140						"upper address requires linear placement",
141					));
142				}
143				metadata
144					.allocate_resource(size, alignment, strategy, class)
145					.map(|(index, serial)| Placement::Tlsf(index, serial))
146			}
147			Self::Linear(metadata) => metadata.allocate_resource(size, alignment, upper, class),
148		}
149	}
150
151	pub fn info(&self, placement: Placement) -> Result<VirtualAllocationInfo, AllocError> {
152		match (self, placement) {
153			(Self::Tlsf(metadata), Placement::Tlsf(index, serial)) => metadata.info(index, serial),
154			(Self::Linear(metadata), Placement::Linear { offset, serial }) => {
155				metadata.info(offset, serial)
156			}
157			_ => Err(AllocError::UnknownAllocation),
158		}
159	}
160
161	pub fn free(&mut self, placement: Placement) -> Result<(), AllocError> {
162		match (self, placement) {
163			(Self::Tlsf(metadata), Placement::Tlsf(index, serial)) => metadata.free(index, serial),
164			(Self::Linear(metadata), Placement::Linear { offset, serial }) => {
165				metadata.free(offset, serial)
166			}
167			_ => Err(AllocError::UnknownAllocation),
168		}
169	}
170
171	pub fn clear(&mut self) {
172		match self {
173			Self::Tlsf(metadata) => metadata.clear(),
174			Self::Linear(metadata) => metadata.clear(),
175		}
176	}
177
178	pub fn set_user_data(&mut self, placement: Placement, data: usize) -> Result<(), AllocError> {
179		match (self, placement) {
180			(Self::Tlsf(metadata), Placement::Tlsf(index, serial)) => {
181				metadata.set_user_data(index, serial, data)
182			}
183			(Self::Linear(metadata), Placement::Linear { offset, serial }) => {
184				metadata.set_user_data(offset, serial, data)
185			}
186			_ => Err(AllocError::UnknownAllocation),
187		}
188	}
189
190	pub fn for_each_allocation(&self, visit: impl FnMut(u64, u64, usize)) {
191		match self {
192			Self::Tlsf(metadata) => metadata.for_each_allocation(visit),
193			Self::Linear(metadata) => metadata.for_each_allocation(visit),
194		}
195	}
196
197	pub fn for_each_free(&self, visit: impl FnMut(u64, u64)) {
198		match self {
199			Self::Tlsf(metadata) => metadata.for_each_free(visit),
200			Self::Linear(metadata) => metadata.for_each_free(visit),
201		}
202	}
203
204	pub fn statistics(&self) -> VirtualBlockStats {
205		match self {
206			Self::Tlsf(metadata) => metadata.statistics(),
207			Self::Linear(metadata) => metadata.statistics(),
208		}
209	}
210}
211
212#[cfg(test)]
213#[path = "../../../test/rs/mem/test_granularity.rs"]
214mod tests;