Skip to content

Commit 1539771

Browse files
committed
address review pt 2
1 parent 3628f0e commit 1539771

File tree

1 file changed

+65
-29
lines changed

1 file changed

+65
-29
lines changed

src/alloc/isolated_alloc.rs

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,23 @@ impl IsolatedAlloc {
3636
page_ptrs: Vec::new(),
3737
huge_ptrs: Vec::new(),
3838
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
3941
page_size: unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() },
4042
}
4143
}
4244

4345
/// 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
4652
let page_ptr = unsafe { alloc::alloc(page_layout) };
4753
// `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));
5056
self.page_ptrs.push(page_ptr);
5157
(page_ptr, self.page_infos.last_mut().unwrap())
5258
}
@@ -85,13 +91,15 @@ impl IsolatedAlloc {
8591
///
8692
/// SAFETY: See `alloc::alloc()`
8793
pub unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 {
94+
// SAFETY: Upheld by caller
8895
unsafe { self.allocate(layout, false) }
8996
}
9097

9198
/// Same as `alloc`, but zeroes out the memory.
9299
///
93100
/// SAFETY: See `alloc::alloc_zeroed()`
94101
pub unsafe fn alloc_zeroed(&mut self, layout: Layout) -> *mut u8 {
102+
// SAFETY: Upheld by caller
95103
unsafe { self.allocate(layout, true) }
96104
}
97105

@@ -103,9 +111,13 @@ impl IsolatedAlloc {
103111
unsafe fn allocate(&mut self, layout: Layout, zeroed: bool) -> *mut u8 {
104112
let (size, align) = IsolatedAlloc::normalized_layout(layout);
105113
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
106116
unsafe { self.alloc_huge(layout, zeroed) }
107117
} else {
108118
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
109121
if let Some(ptr) =
110122
unsafe { Self::alloc_from_page(self.page_size, layout, page, pinfo, zeroed) }
111123
{
@@ -117,7 +129,9 @@ impl IsolatedAlloc {
117129
let page_size = self.page_size;
118130
// Add another page and allocate from it; this cannot fail since the
119131
// 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
121135
unsafe { Self::alloc_from_page(page_size, layout, page, pinfo, zeroed).unwrap() }
122136
}
123137
}
@@ -149,6 +163,11 @@ impl IsolatedAlloc {
149163
let range_avail = !(idx_pinfo..idx_pinfo + size_pinfo).any(|idx| pinfo.contains(idx));
150164
if range_avail {
151165
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.
152171
unsafe {
153172
let ptr = page.add(idx);
154173
if zeroed {
@@ -168,6 +187,7 @@ impl IsolatedAlloc {
168187
/// SAFETY: Same as `alloc()`.
169188
unsafe fn alloc_huge(&mut self, layout: Layout, zeroed: bool) -> *mut u8 {
170189
let layout = IsolatedAlloc::huge_normalized_layout(layout, self.page_size);
190+
// SAFETY: Upheld by caller
171191
let ret =
172192
unsafe { if zeroed { alloc::alloc_zeroed(layout) } else { alloc::alloc(layout) } };
173193
self.huge_ptrs.push((ret, layout.size()));
@@ -183,6 +203,8 @@ impl IsolatedAlloc {
183203
let (size, align) = IsolatedAlloc::normalized_layout(layout);
184204

185205
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`
186208
unsafe {
187209
self.dealloc_huge(ptr, layout);
188210
}
@@ -195,8 +217,9 @@ impl IsolatedAlloc {
195217
// Find the page this allocation belongs to.
196218
// This could be made faster if the list was sorted -- the allocator isn't fully optimized at the moment.
197219
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 {
200223
panic!(
201224
"Freeing in an unallocated page: {ptr:?}\nHolding pages {:?}",
202225
self.page_ptrs
@@ -209,15 +232,18 @@ impl IsolatedAlloc {
209232
pinfo.remove(idx);
210233
}
211234

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) };
215235
// Only 1 page might have been freed after a dealloc, so if it exists,
216236
// 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
219245
unsafe {
220-
alloc::dealloc(self.page_ptrs.remove(free_idx), page_layout);
246+
alloc::dealloc(self.page_ptrs.remove(idx_of_pinfo), page_layout);
221247
}
222248
}
223249
}
@@ -235,6 +261,7 @@ impl IsolatedAlloc {
235261
.expect("Freeing unallocated pages");
236262
// And kick it from the list
237263
self.huge_ptrs.remove(idx);
264+
// SAFETY: Caller ensures validity of the layout
238265
unsafe {
239266
alloc::dealloc(ptr, layout);
240267
}
@@ -247,7 +274,10 @@ mod tests {
247274

248275
/// Helper function to assert that all bytes from `ptr` to `ptr.add(layout.size())`
249276
/// 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
251281
unsafe {
252282
for ofs in 0..layout.size() {
253283
assert_eq!(0, ptr.add(ofs).read());
@@ -261,9 +291,11 @@ mod tests {
261291
let mut alloc = IsolatedAlloc::new();
262292
// 256 should be less than the pagesize on *any* system
263293
let layout = Layout::from_size_align(256, 32).unwrap();
294+
// SAFETY: layout size is the constant above, not 0
264295
let ptr = unsafe { alloc.alloc_zeroed(layout) };
265-
assert_zeroes(ptr, layout);
296+
// SAFETY: `ptr` was just allocated with `layout`
266297
unsafe {
298+
assert_zeroes(ptr, layout);
267299
alloc.dealloc(ptr, layout);
268300
}
269301
}
@@ -274,9 +306,11 @@ mod tests {
274306
let mut alloc = IsolatedAlloc::new();
275307
// 16k is about as big as pages get e.g. on macos aarch64
276308
let layout = Layout::from_size_align(16 * 1024, 128).unwrap();
309+
// SAFETY: layout size is the constant above, not 0
277310
let ptr = unsafe { alloc.alloc_zeroed(layout) };
278-
assert_zeroes(ptr, layout);
311+
// SAFETY: `ptr` was just allocated with `layout`
279312
unsafe {
313+
assert_zeroes(ptr, layout);
280314
alloc.dealloc(ptr, layout);
281315
}
282316
}
@@ -289,24 +323,21 @@ mod tests {
289323
// Try both sub-pagesize allocs and those larger than / equal to a page
290324
for sz in (1..=(16 * 1024)).step_by(128) {
291325
let layout = Layout::from_size_align(sz, 1).unwrap();
326+
// SAFETY: all sizes in the range above are nonzero as we start from 1
292327
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
294330
unsafe {
331+
assert_zeroes(ptr, layout);
295332
ptr.write_bytes(255, sz);
296333
alloc.dealloc(ptr, layout);
297334
}
298335
}
299336
}
300337

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) {
310341
// Some random sizes and aligns
311342
let mut sizes = vec![32; 10];
312343
sizes.append(&mut vec![15; 4]);
@@ -327,13 +358,18 @@ mod tests {
327358
let layouts: Vec<_> = std::iter::zip(sizes, aligns)
328359
.map(|(sz, al)| Layout::from_size_align(sz, al).unwrap())
329360
.collect();
361+
// SAFETY: all sizes specified in `sizes` are nonzero
330362
let ptrs: Vec<_> =
331363
layouts.iter().map(|layout| unsafe { alloc.alloc_zeroed(*layout) }).collect();
332364

333365
for (&ptr, &layout) in std::iter::zip(&ptrs, &layouts) {
334366
// We requested zeroed allocations, so check that that's true
335367
// 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
337373
unsafe {
338374
assert_zeroes(ptr, layout);
339375
ptr.write_bytes(255, layout.size());
@@ -346,9 +382,9 @@ mod tests {
346382
#[test]
347383
fn check_leaks() {
348384
let mut alloc = IsolatedAlloc::new();
349-
350385
// Generate some noise first so leaks can manifest
351-
no_overlaps_inner(&mut alloc);
386+
no_overlaps(&mut alloc);
387+
352388
// And then verify that no memory was leaked
353389
assert!(alloc.page_ptrs.is_empty() && alloc.huge_ptrs.is_empty());
354390
}

0 commit comments

Comments
 (0)