commonlibsse_ng\re\m\MemoryManager\alloc\tes_global/
impl_selfless.rs

1// SPDX-FileCopyrightText: (c) The Rust Project Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// - https://github.com/rust-lang/rust/blob/master/LICENSE-MIT
4//
5//! Rust's Allocator compatible memory allocator for Skyrim.
6use core::ptr;
7use core::{alloc::Layout, hint, ptr::NonNull};
8
9use stdx::alloc::{AllocError, non_null_empty_slice};
10
11use crate::re::MemoryManager::{
12    TESGlobalAlloc,
13    alloc::{alloc, alloc_zeroed, dealloc, realloc},
14};
15
16use std_fork::alloc::SelflessAllocator;
17
18impl TESGlobalAlloc {
19    #[inline]
20    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
21    pub(crate) fn alloc_impl(layout: Layout, zeroed: bool) -> Result<NonNull<[u8]>, AllocError> {
22        match layout.size() {
23            0 => Ok(non_null_empty_slice(layout)),
24            // SAFETY: `layout` is non-zero in size,
25            size => unsafe {
26                let raw_ptr = if zeroed { alloc_zeroed(layout) } else { alloc(layout) };
27                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
28                Ok(NonNull::slice_from_raw_parts(ptr, size))
29            },
30        }
31    }
32
33    // SAFETY: Same as `Allocator::grow`
34    #[inline]
35    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
36    pub(crate) unsafe fn grow_impl(
37        ptr: NonNull<u8>,
38        old_layout: Layout,
39        new_layout: Layout,
40        zeroed: bool,
41    ) -> Result<NonNull<[u8]>, AllocError> {
42        debug_assert!(
43            new_layout.size() >= old_layout.size(),
44            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
45        );
46
47        match old_layout.size() {
48            0 => Self::alloc_impl(new_layout, zeroed),
49
50            // SAFETY: `new_size` is non-zero as `old_size` is greater than or equal to `new_size`
51            // as required by safety conditions. Other conditions must be upheld by the caller
52            old_size if old_layout.align() == new_layout.align() => unsafe {
53                let new_size = new_layout.size();
54
55                // `realloc` probably checks for `new_size >= old_layout.size()` or something similar.
56                hint::assert_unchecked(new_size >= old_layout.size());
57
58                let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
59                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
60                if zeroed {
61                    raw_ptr.add(old_size).write_bytes(0, new_size - old_size);
62                }
63                Ok(NonNull::slice_from_raw_parts(ptr, new_size))
64            },
65
66            // SAFETY: because `new_layout.size()` must be greater than or equal to `old_size`,
67            // both the old and new memory allocation are valid for reads and writes for `old_size`
68            // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
69            // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
70            // for `dealloc` must be upheld by the caller.
71            old_size => unsafe {
72                let new_ptr = Self::alloc_impl(new_layout, zeroed)?;
73                ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.cast().as_ptr(), old_size);
74                Self::deallocate(ptr, old_layout);
75                Ok(new_ptr)
76            },
77        }
78    }
79}
80
81unsafe impl SelflessAllocator for TESGlobalAlloc {
82    #[inline]
83    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
84    fn allocate(layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
85        Self::alloc_impl(layout, false)
86    }
87
88    #[inline]
89    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
90    fn allocate_zeroed(layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
91        Self::alloc_impl(layout, true)
92    }
93
94    #[inline]
95    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
96    unsafe fn deallocate(ptr: NonNull<u8>, layout: Layout) {
97        if layout.size() != 0 {
98            // SAFETY: `layout` is non-zero in size,
99            // other conditions must be upheld by the caller
100            unsafe { dealloc(ptr.as_ptr(), layout) }
101        }
102    }
103
104    #[inline]
105    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
106    unsafe fn grow(
107        ptr: NonNull<u8>,
108        old_layout: Layout,
109        new_layout: Layout,
110    ) -> Result<NonNull<[u8]>, AllocError> {
111        // SAFETY: all conditions must be upheld by the caller
112        unsafe { Self::grow_impl(ptr, old_layout, new_layout, false) }
113    }
114
115    #[inline]
116    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
117    unsafe fn grow_zeroed(
118        ptr: NonNull<u8>,
119        old_layout: Layout,
120        new_layout: Layout,
121    ) -> Result<NonNull<[u8]>, AllocError> {
122        // SAFETY: all conditions must be upheld by the caller
123        unsafe { Self::grow_impl(ptr, old_layout, new_layout, true) }
124    }
125
126    #[inline]
127    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
128    unsafe fn shrink(
129        ptr: NonNull<u8>,
130        old_layout: Layout,
131        new_layout: Layout,
132    ) -> Result<NonNull<[u8]>, AllocError> {
133        debug_assert!(
134            new_layout.size() <= old_layout.size(),
135            "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
136        );
137
138        match new_layout.size() {
139            // SAFETY: conditions must be upheld by the caller
140            0 => {
141                unsafe { Self::deallocate(ptr, old_layout) };
142                Ok(non_null_empty_slice(new_layout))
143            }
144
145            // SAFETY: `new_size` is non-zero. Other conditions must be upheld by the caller
146            new_size if old_layout.align() == new_layout.align() => unsafe {
147                // `realloc` probably checks for `new_size <= old_layout.size()` or something similar.
148                hint::assert_unchecked(new_size <= old_layout.size());
149
150                let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
151                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
152                Ok(NonNull::slice_from_raw_parts(ptr, new_size))
153            },
154
155            // SAFETY: because `new_size` must be smaller than or equal to `old_layout.size()`,
156            // both the old and new memory allocation are valid for reads and writes for `new_size`
157            // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
158            // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
159            // for `dealloc` must be upheld by the caller.
160            new_size => unsafe {
161                let new_ptr = Self::allocate(new_layout)?;
162                ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.cast().as_ptr(), new_size);
163                Self::deallocate(ptr, old_layout);
164                Ok(new_ptr)
165            },
166        }
167    }
168}