1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use core::fmt;
use core::marker::PhantomData;
use allocator_api2::alloc::{AllocError, Allocator, Global};
use crate::Arena;
use crate::internal::constants::{MAX_NORMAL_ALLOC, MIN_CHUNK_BYTES, SizeClass};
/// Minimum value accepted for the `max_normal_alloc` knob.
const MIN_MAX_NORMAL_ALLOC: usize = 4096;
/// Fluent builder for [`Arena`].
///
/// All knobs have sensible defaults. The defaults reproduce
/// `Arena::new()` exactly.
pub struct ArenaBuilder<A: Allocator + Clone = Global> {
allocator: A,
max_normal_alloc: usize,
byte_budget: Option<usize>,
capacity: usize,
_phantom: PhantomData<A>,
}
impl ArenaBuilder<Global> {
/// Start a new builder with default knobs and the [`Global`] allocator.
///
/// Crate-internal: the public entry point is
/// [`Arena::builder`](crate::Arena::builder), per the builder convention
/// that a builder is obtained from its target type, not constructed
/// directly.
#[must_use]
#[inline]
pub(crate) fn new() -> Self {
Self::new_in(Global)
}
}
impl<A: Allocator + Clone> ArenaBuilder<A> {
/// Start a new builder with default knobs and a custom backing
/// allocator.
///
/// Crate-internal: the public entry point is
/// [`Arena::builder_in`](crate::Arena::builder_in).
#[must_use]
#[inline]
pub(crate) fn new_in(allocator: A) -> Self {
Self {
allocator,
max_normal_alloc: MAX_NORMAL_ALLOC,
byte_budget: None,
capacity: 0,
_phantom: PhantomData,
}
}
/// Set the oversized-allocation cutover threshold.
///
/// Requests that need a fresh chunk and exceed this size get their own
/// oversized chunk. Must be in `[4096, MAX_CHUNK_BYTES - chunk_header_size]`;
/// out-of-range values cause [`Self::build`] / [`Self::try_build`] to panic
/// with the resolved bounds in the panic message.
#[must_use]
#[inline]
pub const fn max_normal_alloc(mut self, bytes: usize) -> Self {
self.max_normal_alloc = bytes;
self
}
/// Set a cap on outstanding chunk-capacity bytes.
///
/// Limits the total bytes of chunk capacity (live + cached) that may be
/// outstanding at any one time.
#[must_use]
#[inline]
pub const fn byte_budget(mut self, bytes: usize) -> Self {
self.byte_budget = Some(bytes);
self
}
/// Preallocate `bytes` bytes of total chunk allocation up front
/// (header + payload), warming the arena's chunk cache. `bytes` must be
/// `0` or at least 512. One capacity knob covers references and smart
/// pointers alike.
#[must_use]
#[inline]
pub const fn with_capacity(mut self, bytes: usize) -> Self {
self.capacity = bytes;
self
}
/// Replace the backing allocator. Returns a builder over the new
/// allocator type with all other settings preserved.
#[must_use]
#[inline]
pub fn allocator_in<A2: Allocator + Clone>(self, allocator: A2) -> ArenaBuilder<A2> {
ArenaBuilder {
allocator,
max_normal_alloc: self.max_normal_alloc,
byte_budget: self.byte_budget,
capacity: self.capacity,
_phantom: PhantomData,
}
}
/// Validate this builder's configuration. Panics if any knob is
/// out of range.
#[cold]
fn validate(&self) {
let upper = crate::internal::chunk::max_bump_extent::<A>();
assert!(
(MIN_MAX_NORMAL_ALLOC..=upper).contains(&self.max_normal_alloc),
"max_normal_alloc must be in [{MIN_MAX_NORMAL_ALLOC}, {upper}], got {}",
self.max_normal_alloc,
);
assert!(
self.capacity == 0 || self.capacity >= MIN_CHUNK_BYTES,
"with_capacity(bytes) must be either 0 or at least {MIN_CHUNK_BYTES}, got {}",
self.capacity,
);
}
/// Resolve a desired preallocation `capacity` (total chunk-allocation
/// bytes) into a `(target_class, chunk_count)` pair.
#[cfg_attr(test, mutants::skip)] // belt-and-suspenders cap; inner helper already saturates
fn resolve_capacity(capacity: usize) -> Option<(SizeClass, usize)> {
if capacity == 0 {
return None;
}
let target_class = SizeClass::min_for_bytes(capacity).min(SizeClass::MAX);
let class_total = target_class.bytes();
let count = capacity.div_ceil(class_total);
Some((target_class, count))
}
/// Consume this builder and produce a configured [`Arena`].
///
/// # Panics
///
/// Panics if any builder knob is out of range, or if the backing
/// allocator fails while preallocating chunks.
#[must_use]
#[cold]
pub fn build(self) -> Arena<A>
where
A: 'static,
{
match self.try_build() {
Ok(a) => a,
Err(_) => panic_build(),
}
}
/// Fallible variant of [`Self::build`].
///
/// # Panics
///
/// Panics if any builder knob is out of range. Allocator failures during
/// preallocation are returned as [`AllocError`].
///
/// # Errors
///
/// Returns [`AllocError`] if the backing allocator fails while
/// preallocating chunks.
#[cold]
pub fn try_build(self) -> Result<Arena<A>, AllocError>
where
A: 'static,
{
self.validate();
let capacity = Self::resolve_capacity(self.capacity);
let arena = Arena::try_from_config(self.allocator, self.max_normal_alloc, self.byte_budget)?;
if let Some((class, n)) = capacity {
for _ in 0..n {
arena.preallocate_one(class)?;
}
}
Ok(arena)
}
}
#[expect(
clippy::missing_fields_in_debug,
reason = "Allocator and PhantomData fields are not useful in debug output"
)]
impl<A: Allocator + Clone> fmt::Debug for ArenaBuilder<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ArenaBuilder")
.field("max_normal_alloc", &self.max_normal_alloc)
.field("byte_budget", &self.byte_budget)
.field("capacity", &self.capacity)
.finish()
}
}
#[cold]
#[inline(never)]
#[expect(clippy::panic, reason = "panicking constructor matches Arena's `panic_alloc` style")]
fn panic_build() -> ! {
panic!("multitude::ArenaBuilder::build: backing allocator failed");
}