@@ -36,17 +36,23 @@ impl IsolatedAlloc {
36
36
page_ptrs : Vec :: new ( ) ,
37
37
huge_ptrs : Vec :: new ( ) ,
38
38
page_infos : Vec :: new ( ) ,
39
+ // SAFETY: `sysconf(_SC_PAGESIZE)` is always safe to call at runtime
40
+ // See https://www.man7.org/linux/man-pages/man3/sysconf.3.html
39
41
page_size : unsafe { libc:: sysconf ( libc:: _SC_PAGESIZE) . try_into ( ) . unwrap ( ) } ,
40
42
}
41
43
}
42
44
43
45
/// Expands the available memory pool by adding one page.
44
- fn add_page ( & mut self , page_size : usize ) -> ( * mut u8 , & mut DenseBitSet < usize > ) {
45
- let page_layout = unsafe { Layout :: from_size_align_unchecked ( page_size, page_size) } ;
46
+ fn add_page ( & mut self ) -> ( * mut u8 , & mut DenseBitSet < usize > ) {
47
+ // SAFETY: the system page size must always be a power of 2 and nonzero,
48
+ // and cannot overflow an isize on the host.
49
+ let page_layout =
50
+ unsafe { Layout :: from_size_align_unchecked ( self . page_size , self . page_size ) } ;
51
+ // SAFETY: The system page size, which is the layout size, cannot be 0
46
52
let page_ptr = unsafe { alloc:: alloc ( page_layout) } ;
47
53
// `page_infos` has to have one bit for each `COMPRESSION_FACTOR`-sized chunk of bytes in the page.
48
- assert ! ( page_size % COMPRESSION_FACTOR == 0 ) ;
49
- self . page_infos . push ( DenseBitSet :: new_empty ( page_size / COMPRESSION_FACTOR ) ) ;
54
+ assert ! ( self . page_size % COMPRESSION_FACTOR == 0 ) ;
55
+ self . page_infos . push ( DenseBitSet :: new_empty ( self . page_size / COMPRESSION_FACTOR ) ) ;
50
56
self . page_ptrs . push ( page_ptr) ;
51
57
( page_ptr, self . page_infos . last_mut ( ) . unwrap ( ) )
52
58
}
@@ -85,13 +91,15 @@ impl IsolatedAlloc {
85
91
///
86
92
/// SAFETY: See `alloc::alloc()`
87
93
pub unsafe fn alloc ( & mut self , layout : Layout ) -> * mut u8 {
94
+ // SAFETY: Upheld by caller
88
95
unsafe { self . allocate ( layout, false ) }
89
96
}
90
97
91
98
/// Same as `alloc`, but zeroes out the memory.
92
99
///
93
100
/// SAFETY: See `alloc::alloc_zeroed()`
94
101
pub unsafe fn alloc_zeroed ( & mut self , layout : Layout ) -> * mut u8 {
102
+ // SAFETY: Upheld by caller
95
103
unsafe { self . allocate ( layout, true ) }
96
104
}
97
105
@@ -103,9 +111,13 @@ impl IsolatedAlloc {
103
111
unsafe fn allocate ( & mut self , layout : Layout , zeroed : bool ) -> * mut u8 {
104
112
let ( size, align) = IsolatedAlloc :: normalized_layout ( layout) ;
105
113
if IsolatedAlloc :: is_huge_alloc ( size, align, self . page_size ) {
114
+ // SAFETY: Validity of `layout` upheld by caller; we checked that
115
+ // the size and alignment are appropriate for being a huge alloc
106
116
unsafe { self . alloc_huge ( layout, zeroed) }
107
117
} else {
108
118
for ( & mut page, pinfo) in std:: iter:: zip ( & mut self . page_ptrs , & mut self . page_infos ) {
119
+ // SAFETY: The value in `self.page_size` is used to allocate
120
+ // `page`, with page alignment
109
121
if let Some ( ptr) =
110
122
unsafe { Self :: alloc_from_page ( self . page_size , layout, page, pinfo, zeroed) }
111
123
{
@@ -117,7 +129,9 @@ impl IsolatedAlloc {
117
129
let page_size = self . page_size ;
118
130
// Add another page and allocate from it; this cannot fail since the
119
131
// new page is empty and we already asserted it fits into a page
120
- let ( page, pinfo) = self . add_page ( page_size) ;
132
+ let ( page, pinfo) = self . add_page ( ) ;
133
+
134
+ // SAFETY: See comment on `alloc_from_page` above
121
135
unsafe { Self :: alloc_from_page ( page_size, layout, page, pinfo, zeroed) . unwrap ( ) }
122
136
}
123
137
}
@@ -149,6 +163,11 @@ impl IsolatedAlloc {
149
163
let range_avail = !( idx_pinfo..idx_pinfo + size_pinfo) . any ( |idx| pinfo. contains ( idx) ) ;
150
164
if range_avail {
151
165
pinfo. insert_range ( idx_pinfo..idx_pinfo + size_pinfo) ;
166
+ // SAFETY: We checked the available bytes after `idx` in the call
167
+ // to `domain_size` above and asserted there are at least `idx +
168
+ // layout.size()` bytes available and unallocated after it.
169
+ // `page` must point to the start of the page, so adding `idx`
170
+ // is safe per the above.
152
171
unsafe {
153
172
let ptr = page. add ( idx) ;
154
173
if zeroed {
@@ -168,6 +187,7 @@ impl IsolatedAlloc {
168
187
/// SAFETY: Same as `alloc()`.
169
188
unsafe fn alloc_huge ( & mut self , layout : Layout , zeroed : bool ) -> * mut u8 {
170
189
let layout = IsolatedAlloc :: huge_normalized_layout ( layout, self . page_size ) ;
190
+ // SAFETY: Upheld by caller
171
191
let ret =
172
192
unsafe { if zeroed { alloc:: alloc_zeroed ( layout) } else { alloc:: alloc ( layout) } } ;
173
193
self . huge_ptrs . push ( ( ret, layout. size ( ) ) ) ;
@@ -183,6 +203,8 @@ impl IsolatedAlloc {
183
203
let ( size, align) = IsolatedAlloc :: normalized_layout ( layout) ;
184
204
185
205
if IsolatedAlloc :: is_huge_alloc ( size, align, self . page_size ) {
206
+ // SAFETY: Partly upheld by caller, and we checked that the size
207
+ // and align mean this must have been allocated via `alloc_huge`
186
208
unsafe {
187
209
self . dealloc_huge ( ptr, layout) ;
188
210
}
@@ -195,8 +217,9 @@ impl IsolatedAlloc {
195
217
// Find the page this allocation belongs to.
196
218
// This could be made faster if the list was sorted -- the allocator isn't fully optimized at the moment.
197
219
let pinfo = std:: iter:: zip ( & mut self . page_ptrs , & mut self . page_infos )
198
- . find ( |( page, _) | page. addr ( ) == page_addr) ;
199
- let Some ( ( _, pinfo) ) = pinfo else {
220
+ . enumerate ( )
221
+ . find ( |( _, ( page, _) ) | page. addr ( ) == page_addr) ;
222
+ let Some ( ( idx_of_pinfo, ( _, pinfo) ) ) = pinfo else {
200
223
panic ! (
201
224
"Freeing in an unallocated page: {ptr:?}\n Holding pages {:?}" ,
202
225
self . page_ptrs
@@ -209,15 +232,18 @@ impl IsolatedAlloc {
209
232
pinfo. remove ( idx) ;
210
233
}
211
234
212
- // We allocated all the pages with this layout
213
- let page_layout =
214
- unsafe { Layout :: from_size_align_unchecked ( self . page_size , self . page_size ) } ;
215
235
// Only 1 page might have been freed after a dealloc, so if it exists,
216
236
// find it and free it (and adjust the vectors)
217
- if let Some ( free_idx) = self . page_infos . iter ( ) . position ( |pinfo| pinfo. is_empty ( ) ) {
218
- self . page_infos . remove ( free_idx) ;
237
+ if pinfo. is_empty ( ) {
238
+ // SAFETY: `self.page_size` is always a power of 2 and does not overflow an isize
239
+ let page_layout =
240
+ unsafe { Layout :: from_size_align_unchecked ( self . page_size , self . page_size ) } ;
241
+ self . page_infos . remove ( idx_of_pinfo) ;
242
+ // SAFETY: We checked that there are no outstanding allocations
243
+ // from us pointing to this page, and we know it was allocated
244
+ // with this layout
219
245
unsafe {
220
- alloc:: dealloc ( self . page_ptrs . remove ( free_idx ) , page_layout) ;
246
+ alloc:: dealloc ( self . page_ptrs . remove ( idx_of_pinfo ) , page_layout) ;
221
247
}
222
248
}
223
249
}
@@ -235,6 +261,7 @@ impl IsolatedAlloc {
235
261
. expect ( "Freeing unallocated pages" ) ;
236
262
// And kick it from the list
237
263
self . huge_ptrs . remove ( idx) ;
264
+ // SAFETY: Caller ensures validity of the layout
238
265
unsafe {
239
266
alloc:: dealloc ( ptr, layout) ;
240
267
}
@@ -247,7 +274,10 @@ mod tests {
247
274
248
275
/// Helper function to assert that all bytes from `ptr` to `ptr.add(layout.size())`
249
276
/// are zeroes.
250
- fn assert_zeroes ( ptr : * mut u8 , layout : Layout ) {
277
+ ///
278
+ /// SAFETY: `ptr` must have been allocated with `layout`.
279
+ unsafe fn assert_zeroes ( ptr : * mut u8 , layout : Layout ) {
280
+ // SAFETY: Caller ensures this is valid
251
281
unsafe {
252
282
for ofs in 0 ..layout. size ( ) {
253
283
assert_eq ! ( 0 , ptr. add( ofs) . read( ) ) ;
@@ -261,9 +291,11 @@ mod tests {
261
291
let mut alloc = IsolatedAlloc :: new ( ) ;
262
292
// 256 should be less than the pagesize on *any* system
263
293
let layout = Layout :: from_size_align ( 256 , 32 ) . unwrap ( ) ;
294
+ // SAFETY: layout size is the constant above, not 0
264
295
let ptr = unsafe { alloc. alloc_zeroed ( layout) } ;
265
- assert_zeroes ( ptr, layout) ;
296
+ // SAFETY: ` ptr` was just allocated with ` layout`
266
297
unsafe {
298
+ assert_zeroes ( ptr, layout) ;
267
299
alloc. dealloc ( ptr, layout) ;
268
300
}
269
301
}
@@ -274,9 +306,11 @@ mod tests {
274
306
let mut alloc = IsolatedAlloc :: new ( ) ;
275
307
// 16k is about as big as pages get e.g. on macos aarch64
276
308
let layout = Layout :: from_size_align ( 16 * 1024 , 128 ) . unwrap ( ) ;
309
+ // SAFETY: layout size is the constant above, not 0
277
310
let ptr = unsafe { alloc. alloc_zeroed ( layout) } ;
278
- assert_zeroes ( ptr, layout) ;
311
+ // SAFETY: ` ptr` was just allocated with ` layout`
279
312
unsafe {
313
+ assert_zeroes ( ptr, layout) ;
280
314
alloc. dealloc ( ptr, layout) ;
281
315
}
282
316
}
@@ -289,24 +323,21 @@ mod tests {
289
323
// Try both sub-pagesize allocs and those larger than / equal to a page
290
324
for sz in ( 1 ..=( 16 * 1024 ) ) . step_by ( 128 ) {
291
325
let layout = Layout :: from_size_align ( sz, 1 ) . unwrap ( ) ;
326
+ // SAFETY: all sizes in the range above are nonzero as we start from 1
292
327
let ptr = unsafe { alloc. alloc_zeroed ( layout) } ;
293
- assert_zeroes ( ptr, layout) ;
328
+ // SAFETY: `ptr` was just allocated with `layout`, which was used
329
+ // to bound the access size
294
330
unsafe {
331
+ assert_zeroes ( ptr, layout) ;
295
332
ptr. write_bytes ( 255 , sz) ;
296
333
alloc. dealloc ( ptr, layout) ;
297
334
}
298
335
}
299
336
}
300
337
301
- /// Checks that allocations of different sizes do not overlap.
302
- #[ test]
303
- fn no_overlaps ( ) {
304
- let mut alloc = IsolatedAlloc :: new ( ) ;
305
- no_overlaps_inner ( & mut alloc) ;
306
- }
307
-
308
- /// Allows us to reuse this bit for `no_overlaps` and `check_leaks`.
309
- fn no_overlaps_inner ( alloc : & mut IsolatedAlloc ) {
338
+ /// Checks that allocations of different sizes do not overlap. Not a freestanding
339
+ /// test because we use it as part of `check_leaks()` also.
340
+ fn no_overlaps ( alloc : & mut IsolatedAlloc ) {
310
341
// Some random sizes and aligns
311
342
let mut sizes = vec ! [ 32 ; 10 ] ;
312
343
sizes. append ( & mut vec ! [ 15 ; 4 ] ) ;
@@ -327,13 +358,18 @@ mod tests {
327
358
let layouts: Vec < _ > = std:: iter:: zip ( sizes, aligns)
328
359
. map ( |( sz, al) | Layout :: from_size_align ( sz, al) . unwrap ( ) )
329
360
. collect ( ) ;
361
+ // SAFETY: all sizes specified in `sizes` are nonzero
330
362
let ptrs: Vec < _ > =
331
363
layouts. iter ( ) . map ( |layout| unsafe { alloc. alloc_zeroed ( * layout) } ) . collect ( ) ;
332
364
333
365
for ( & ptr, & layout) in std:: iter:: zip ( & ptrs, & layouts) {
334
366
// We requested zeroed allocations, so check that that's true
335
367
// Then write to the end of the current size, so if the allocs
336
- // overlap (or the zeroing is wrong) then `assert_zeroes` will panic
368
+ // overlap (or the zeroing is wrong) then `assert_zeroes` will panic.
369
+ // Also check that the alignment we asked for was respected
370
+ assert_eq ! ( ptr. addr( ) . rem_euclid( layout. align( ) ) , 0 ) ;
371
+ // SAFETY: each `ptr` was allocated with its corresponding `layout`,
372
+ // which is used to bound the access size
337
373
unsafe {
338
374
assert_zeroes ( ptr, layout) ;
339
375
ptr. write_bytes ( 255 , layout. size ( ) ) ;
@@ -346,9 +382,9 @@ mod tests {
346
382
#[ test]
347
383
fn check_leaks ( ) {
348
384
let mut alloc = IsolatedAlloc :: new ( ) ;
349
-
350
385
// Generate some noise first so leaks can manifest
351
- no_overlaps_inner ( & mut alloc) ;
386
+ no_overlaps ( & mut alloc) ;
387
+
352
388
// And then verify that no memory was leaked
353
389
assert ! ( alloc. page_ptrs. is_empty( ) && alloc. huge_ptrs. is_empty( ) ) ;
354
390
}
0 commit comments