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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
//! The `#[global_allocator]` shim.
//!
//! One [`Heap`] per thread, reached through thread-local storage. There
//! is no lock and no shared heap behind it: a thread allocates from its
//! own segments, and a free that arrives on the wrong thread is handed
//! back through the owning segment's push-only foreign list.
//!
//! # Two hazards this file exists to handle
//!
//! **Thread exit must not unmap live memory.** kevy shares values across
//! shards, so a segment can hold slots that outlive the thread that
//! allocated them. If the thread-local heap were dropped at thread exit
//! it would unmap those segments underneath their readers. The heap is
//! therefore held in a [`ManuallyDrop`], and its segments are
//! deliberately leaked when a thread ends — address space is given up,
//! never live memory. Handing abandoned segments to another heap the way
//! mimalloc does is the better answer and is not attempted here; leaking
//! is the answer that cannot be wrong.
//!
//! That also keeps the TLS block destructor-free, so access is a plain
//! static offset that cannot fail during teardown — a global allocator
//! that panics once TLS is gone is a bad way to end a process.
//!
//! **The allocator must not allocate.** Nothing on these paths uses
//! `Vec`, `Box` or formatting; segments are tracked through an intrusive
//! list threaded through their own headers, and the size-class table is
//! a `const` array.
use ;
use UnsafeCell;
use ManuallyDrop;
use NonNull;
use crateclass;
use crateHeap;
thread_local!
/// Run `f` against this thread's heap.
///
/// Returns `None` only when thread-local storage is unavailable, which
/// on a destructor-free block means the thread is past teardown. The
/// caller answers a null rather than panicking.
/// A `#[global_allocator]` backed by one [`Heap`] per thread.
///
/// ```no_run
/// #[global_allocator]
/// static ALLOC: kevy_alloc::KevyAlloc = kevy_alloc::KevyAlloc;
/// ```
;
/// Bytes reserved before an over-aligned block to remember its base.
const BASE_SLOT: usize = ;
/// Total to request so that an `align`-aligned address with room for a
/// base pointer in front of it fits inside.
/// Whether a layout needs the over-aligned dance at all.
// SAFETY: the four premises `GlobalAlloc` asks for, in order.
//
// 1. A returned block meets the layout. Alignments up to
// `class::MAX_NATIVE_ALIGN` are what the size classes are built on;
// anything stricter goes through `alloc_over_aligned`, which
// over-allocates and rounds up, so the address it returns is aligned
// by construction and has `layout.size()` bytes after it.
// 2. Failure is a null pointer, never an unwind. Every path out of
// `with_heap` is an `Option`: `try_with` yields `None` once the
// thread's `HEAP` is gone or not yet made, and the heap itself
// returns `None` when it cannot serve, and both land on
// `core::ptr::null_mut()`. Nothing here can panic on the failure
// path, which is what makes it usable as THE allocator.
// 3. It is callable from any thread, and a block may cross threads. Each
// thread has its own `Heap`, so there is no shared mutable state to
// race on. A free arriving on a thread that did not allocate the
// block is the case that would otherwise be unsound: `dealloc_small`
// compares `seg.owner` against `self.id` and, when they differ,
// pushes to the local outbound ring for the owner to drain rather
// than touching the owner's segment or its non-atomic counters
// (`heap_free.rs`). Ownership is read from the segment header, so it
// is a property of the block, not of who is asking.
// 4. Re-entrancy cannot occur. The closure `with_heap` runs holds the
// only reference to the thread's heap, and no path inside it
// allocates — which is the premise the reference in `with_heap`
// rests on in turn.
unsafe
/// Serve an alignment stricter than a size class can offer by
/// over-allocating and recording the base pointer just below the
/// aligned address.
///
/// The base has to be recorded because the aligned address is not
/// derivable from the layout alone: it depends on where the underlying
/// block landed. This is the one place the crate stores a header, and it
/// is confined to a path Rust programs take rarely.
/// # Safety
/// `ptr` must come from [`alloc_over_aligned`] with the same layout.
unsafe
/// This thread's heap statistics, or `None` past thread teardown.
///
/// Shards report separately; a process figure is [`crate::Stats::merge`]
/// over them.
/// # Examples
///
/// ```
/// // `None` past thread teardown — a caller cannot read that as
/// // "this thread allocated nothing".
/// if let Some(s) = kevy_alloc::thread_stats() {
/// // Every mapped byte lands in exactly one bucket, so the sum is
/// // comparable to `mapped` rather than derived from it.
/// assert!(s.accounted() <= s.mapped);
/// }
/// ```
/// Return this thread's empty spans to the OS.
///
/// Exposed rather than run automatically because how often to sweep is a
/// policy question the engine answers, not the allocator: kevy already
/// has a shard tick to hang it on.
/// # Examples
///
/// ```
/// // Idempotent and always safe to call: with nothing to return it
/// // does nothing, which is why the engine can hang it on a tick.
/// kevy_alloc::thread_reclaim();
/// kevy_alloc::thread_reclaim();
/// ```