bump_scope/alloc/
system.rs

1use core::{
2    alloc::{GlobalAlloc, Layout},
3    hint,
4    ptr::{self, NonNull},
5};
6use std::alloc::System;
7
8use crate::polyfill;
9
10use super::{AllocError, Allocator};
11
12// The Allocator impl checks the layout size to be non-zero and forwards to the GlobalAlloc impl,
13// which is in `std::sys::*::alloc`.
14unsafe impl Allocator for System {
15    #[inline]
16    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
17        alloc_impl(layout, false)
18    }
19
20    #[inline]
21    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
22        alloc_impl(layout, true)
23    }
24
25    #[inline]
26    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
27        if layout.size() != 0 {
28            // SAFETY: `layout` is non-zero in size,
29            // other conditions must be upheld by the caller
30            unsafe { GlobalAlloc::dealloc(self, ptr.as_ptr(), layout) }
31        }
32    }
33
34    #[inline]
35    unsafe fn grow(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
36        // SAFETY: all conditions must be upheld by the caller
37        unsafe { grow_impl(ptr, old_layout, new_layout, false) }
38    }
39
40    #[inline]
41    unsafe fn grow_zeroed(
42        &self,
43        ptr: NonNull<u8>,
44        old_layout: Layout,
45        new_layout: Layout,
46    ) -> Result<NonNull<[u8]>, AllocError> {
47        // SAFETY: all conditions must be upheld by the caller
48        unsafe { grow_impl(ptr, old_layout, new_layout, true) }
49    }
50
51    #[inline]
52    unsafe fn shrink(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
53        debug_assert!(
54            new_layout.size() <= old_layout.size(),
55            "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
56        );
57
58        match new_layout.size() {
59            // SAFETY: conditions must be upheld by the caller
60            0 => unsafe {
61                Allocator::deallocate(self, ptr, old_layout);
62                Ok(NonNull::slice_from_raw_parts(polyfill::layout::dangling(new_layout), 0))
63            },
64
65            // SAFETY: `new_size` is non-zero. Other conditions must be upheld by the caller
66            new_size if old_layout.align() == new_layout.align() => unsafe {
67                // `realloc` probably checks for `new_size <= old_layout.size()` or something similar.
68                hint::assert_unchecked(new_size <= old_layout.size());
69
70                let raw_ptr = GlobalAlloc::realloc(self, ptr.as_ptr(), old_layout, new_size);
71                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
72                Ok(NonNull::slice_from_raw_parts(ptr, new_size))
73            },
74
75            // SAFETY: because `new_size` must be smaller than or equal to `old_layout.size()`,
76            // both the old and new memory allocation are valid for reads and writes for `new_size`
77            // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
78            // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
79            // for `dealloc` must be upheld by the caller.
80            new_size => unsafe {
81                let new_ptr = Allocator::allocate(self, new_layout)?;
82                ptr::copy_nonoverlapping(ptr.as_ptr(), polyfill::non_null::as_mut_ptr(new_ptr), new_size);
83                Allocator::deallocate(self, ptr, old_layout);
84                Ok(new_ptr)
85            },
86        }
87    }
88}
89
90#[inline]
91fn alloc_impl(layout: Layout, zeroed: bool) -> Result<NonNull<[u8]>, AllocError> {
92    match layout.size() {
93        0 => Ok(NonNull::slice_from_raw_parts(polyfill::layout::dangling(layout), 0)),
94        // SAFETY: `layout` is non-zero in size,
95        size => unsafe {
96            let raw_ptr = if zeroed {
97                GlobalAlloc::alloc_zeroed(&System, layout)
98            } else {
99                GlobalAlloc::alloc(&System, layout)
100            };
101            let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
102            Ok(NonNull::slice_from_raw_parts(ptr, size))
103        },
104    }
105}
106
107// SAFETY: Same as `Allocator::grow`
108#[inline]
109unsafe fn grow_impl(
110    ptr: NonNull<u8>,
111    old_layout: Layout,
112    new_layout: Layout,
113    zeroed: bool,
114) -> Result<NonNull<[u8]>, AllocError> {
115    debug_assert!(
116        new_layout.size() >= old_layout.size(),
117        "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
118    );
119
120    match old_layout.size() {
121        0 => alloc_impl(new_layout, zeroed),
122
123        // SAFETY: `new_size` is non-zero as `new_size` is greater than or equal to `old_size`
124        // as required by safety conditions and the `old_size == 0` case was handled in the
125        // previous match arm. Other conditions must be upheld by the caller
126        old_size if old_layout.align() == new_layout.align() => unsafe {
127            let new_size = new_layout.size();
128
129            // `realloc` probably checks for `new_size >= old_layout.size()` or something similar.
130            hint::assert_unchecked(new_size >= old_layout.size());
131
132            let raw_ptr = GlobalAlloc::realloc(&System, ptr.as_ptr(), old_layout, new_size);
133            let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
134            if zeroed {
135                raw_ptr.add(old_size).write_bytes(0, new_size - old_size);
136            }
137            Ok(NonNull::slice_from_raw_parts(ptr, new_size))
138        },
139
140        // SAFETY: because `new_layout.size()` must be greater than or equal to `old_size`,
141        // both the old and new memory allocation are valid for reads and writes for `old_size`
142        // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
143        // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
144        // for `dealloc` must be upheld by the caller.
145        old_size => unsafe {
146            let new_ptr = alloc_impl(new_layout, zeroed)?;
147            ptr::copy_nonoverlapping(ptr.as_ptr(), polyfill::non_null::as_mut_ptr(new_ptr), old_size);
148            Allocator::deallocate(&System, ptr, old_layout);
149            Ok(new_ptr)
150        },
151    }
152}