xref: /drstd/src/std/sys/windows/alloc.rs (revision 86982c5e9b2eaa583327251616ee822c36288824)
1 #![deny(unsafe_op_in_unsafe_fn)]
2 
3 use crate::std::alloc::{GlobalAlloc, Layout, System};
4 use crate::std::ffi::c_void;
5 use crate::std::ptr;
6 use crate::std::sync::atomic::{AtomicPtr, Ordering};
7 use crate::std::sys::c;
8 use crate::std::sys::common::alloc::{realloc_fallback, MIN_ALIGN};
9 
10 #[cfg(test)]
11 mod tests;
12 
13 // Heap memory management on Windows is done by using the system Heap API (heapapi.h)
14 // See https://docs.microsoft.com/windows/win32/api/heapapi/
15 
16 // Flag to indicate that the memory returned by `HeapAlloc` should be zeroed.
17 const HEAP_ZERO_MEMORY: c::DWORD = 0x00000008;
18 
19 #[link(name = "kernel32")]
20 extern "system" {
21     // Get a handle to the default heap of the current process, or null if the operation fails.
22     //
23     // SAFETY: Successful calls to this function within the same process are assumed to
24     // always return the same handle, which remains valid for the entire lifetime of the process.
25     //
26     // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-getprocessheap
27     fn GetProcessHeap() -> c::HANDLE;
28 
29     // Allocate a block of `dwBytes` bytes of memory from a given heap `hHeap`.
30     // The allocated memory may be uninitialized, or zeroed if `dwFlags` is
31     // set to `HEAP_ZERO_MEMORY`.
32     //
33     // Returns a pointer to the newly-allocated memory or null if the operation fails.
34     // The returned pointer will be aligned to at least `MIN_ALIGN`.
35     //
36     // SAFETY:
37     //  - `hHeap` must be a non-null handle returned by `GetProcessHeap`.
38     //  - `dwFlags` must be set to either zero or `HEAP_ZERO_MEMORY`.
39     //
40     // Note that `dwBytes` is allowed to be zero, contrary to some other allocators.
41     //
42     // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heapalloc
43     fn HeapAlloc(hHeap: c::HANDLE, dwFlags: c::DWORD, dwBytes: c::SIZE_T) -> c::LPVOID;
44 
45     // Reallocate a block of memory behind a given pointer `lpMem` from a given heap `hHeap`,
46     // to a block of at least `dwBytes` bytes, either shrinking the block in place,
47     // or allocating at a new location, copying memory, and freeing the original location.
48     //
49     // Returns a pointer to the reallocated memory or null if the operation fails.
50     // The returned pointer will be aligned to at least `MIN_ALIGN`.
51     // If the operation fails the given block will never have been freed.
52     //
53     // SAFETY:
54     //  - `hHeap` must be a non-null handle returned by `GetProcessHeap`.
55     //  - `dwFlags` must be set to zero.
56     //  - `lpMem` must be a non-null pointer to an allocated block returned by `HeapAlloc` or
57     //     `HeapReAlloc`, that has not already been freed.
58     // If the block was successfully reallocated at a new location, pointers pointing to
59     // the freed memory, such as `lpMem`, must not be dereferenced ever again.
60     //
61     // Note that `dwBytes` is allowed to be zero, contrary to some other allocators.
62     //
63     // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heaprealloc
64     fn HeapReAlloc(
65         hHeap: c::HANDLE,
66         dwFlags: c::DWORD,
67         lpMem: c::LPVOID,
68         dwBytes: c::SIZE_T,
69     ) -> c::LPVOID;
70 
71     // Free a block of memory behind a given pointer `lpMem` from a given heap `hHeap`.
72     // Returns a nonzero value if the operation is successful, and zero if the operation fails.
73     //
74     // SAFETY:
75     //  - `hHeap` must be a non-null handle returned by `GetProcessHeap`.
76     //  - `dwFlags` must be set to zero.
77     //  - `lpMem` must be a pointer to an allocated block returned by `HeapAlloc` or `HeapReAlloc`,
78     //     that has not already been freed.
79     // If the block was successfully freed, pointers pointing to the freed memory, such as `lpMem`,
80     // must not be dereferenced ever again.
81     //
82     // Note that `lpMem` is allowed to be null, which will not cause the operation to fail.
83     //
84     // See https://docs.microsoft.com/windows/win32/api/heapapi/nf-heapapi-heapfree
85     fn HeapFree(hHeap: c::HANDLE, dwFlags: c::DWORD, lpMem: c::LPVOID) -> c::BOOL;
86 }
87 
88 // Cached handle to the default heap of the current process.
89 // Either a non-null handle returned by `GetProcessHeap`, or null when not yet initialized or `GetProcessHeap` failed.
90 static HEAP: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
91 
92 // Get a handle to the default heap of the current process, or null if the operation fails.
93 // If this operation is successful, `HEAP` will be successfully initialized and contain
94 // a non-null handle returned by `GetProcessHeap`.
95 #[inline]
96 fn init_or_get_process_heap() -> c::HANDLE {
97     let heap = HEAP.load(Ordering::Relaxed);
98     if heap.is_null() {
99         // `HEAP` has not yet been successfully initialized
100         let heap = unsafe { GetProcessHeap() };
101         if !heap.is_null() {
102             // SAFETY: No locking is needed because within the same process,
103             // successful calls to `GetProcessHeap` will always return the same value, even on different threads.
104             HEAP.store(heap, Ordering::Release);
105 
106             // SAFETY: `HEAP` contains a non-null handle returned by `GetProcessHeap`
107             heap
108         } else {
109             // Could not get the current process heap.
110             ptr::null_mut()
111         }
112     } else {
113         // SAFETY: `HEAP` contains a non-null handle returned by `GetProcessHeap`
114         heap
115     }
116 }
117 
118 // Get a non-null handle to the default heap of the current process.
119 // SAFETY: `HEAP` must have been successfully initialized.
120 #[inline]
121 unsafe fn get_process_heap() -> c::HANDLE {
122     HEAP.load(Ordering::Acquire)
123 }
124 
125 // Header containing a pointer to the start of an allocated block.
126 // SAFETY: Size and alignment must be <= `MIN_ALIGN`.
127 #[repr(C)]
128 struct Header(*mut u8);
129 
130 // Allocate a block of optionally zeroed memory for a given `layout`.
131 // SAFETY: Returns a pointer satisfying the guarantees of `System` about allocated pointers,
132 // or null if the operation fails. If this returns non-null `HEAP` will have been successfully
133 // initialized.
134 #[inline]
135 unsafe fn allocate(layout: Layout, zeroed: bool) -> *mut u8 {
136     let heap = init_or_get_process_heap();
137     if heap.is_null() {
138         // Allocation has failed, could not get the current process heap.
139         return ptr::null_mut();
140     }
141 
142     // Allocated memory will be either zeroed or uninitialized.
143     let flags = if zeroed { HEAP_ZERO_MEMORY } else { 0 };
144 
145     if layout.align() <= MIN_ALIGN {
146         // SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`.
147         // The returned pointer points to the start of an allocated block.
148         unsafe { HeapAlloc(heap, flags, layout.size()) as *mut u8 }
149     } else {
150         // Allocate extra padding in order to be able to satisfy the alignment.
151         let total = layout.align() + layout.size();
152 
153         // SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`.
154         let ptr = unsafe { HeapAlloc(heap, flags, total) as *mut u8 };
155         if ptr.is_null() {
156             // Allocation has failed.
157             return ptr::null_mut();
158         }
159 
160         // Create a correctly aligned pointer offset from the start of the allocated block,
161         // and write a header before it.
162 
163         let offset = layout.align() - (ptr.addr() & (layout.align() - 1));
164         // SAFETY: `MIN_ALIGN` <= `offset` <= `layout.align()` and the size of the allocated
165         // block is `layout.align() + layout.size()`. `aligned` will thus be a correctly aligned
166         // pointer inside the allocated block with at least `layout.size()` bytes after it and at
167         // least `MIN_ALIGN` bytes of padding before it.
168         let aligned = unsafe { ptr.add(offset) };
169         // SAFETY: Because the size and alignment of a header is <= `MIN_ALIGN` and `aligned`
170         // is aligned to at least `MIN_ALIGN` and has at least `MIN_ALIGN` bytes of padding before
171         // it, it is safe to write a header directly before it.
172         unsafe { ptr::write((aligned as *mut Header).sub(1), Header(ptr)) };
173 
174         // SAFETY: The returned pointer does not point to the to the start of an allocated block,
175         // but there is a header readable directly before it containing the location of the start
176         // of the block.
177         aligned
178     }
179 }
180 
181 // All pointers returned by this allocator have, in addition to the guarantees of `GlobalAlloc`, the
182 // following properties:
183 //
184 // If the pointer was allocated or reallocated with a `layout` specifying an alignment <= `MIN_ALIGN`
185 // the pointer will be aligned to at least `MIN_ALIGN` and point to the start of the allocated block.
186 //
187 // If the pointer was allocated or reallocated with a `layout` specifying an alignment > `MIN_ALIGN`
188 // the pointer will be aligned to the specified alignment and not point to the start of the allocated block.
189 // Instead there will be a header readable directly before the returned pointer, containing the actual
190 // location of the start of the block.
191 unsafe impl GlobalAlloc for System {
192     #[inline]
193     unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
194         // SAFETY: Pointers returned by `allocate` satisfy the guarantees of `System`
195         let zeroed = false;
196         unsafe { allocate(layout, zeroed) }
197     }
198 
199     #[inline]
200     unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
201         // SAFETY: Pointers returned by `allocate` satisfy the guarantees of `System`
202         let zeroed = true;
203         unsafe { allocate(layout, zeroed) }
204     }
205 
206     #[inline]
207     unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
208         let block = {
209             if layout.align() <= MIN_ALIGN {
210                 ptr
211             } else {
212                 // The location of the start of the block is stored in the padding before `ptr`.
213 
214                 // SAFETY: Because of the contract of `System`, `ptr` is guaranteed to be non-null
215                 // and have a header readable directly before it.
216                 unsafe { ptr::read((ptr as *mut Header).sub(1)).0 }
217             }
218         };
219 
220         // SAFETY: because `ptr` has been successfully allocated with this allocator,
221         // `HEAP` must have been successfully initialized.
222         let heap = unsafe { get_process_heap() };
223 
224         // SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`,
225         // `block` is a pointer to the start of an allocated block.
226         unsafe { HeapFree(heap, 0, block as c::LPVOID) };
227     }
228 
229     #[inline]
230     unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
231         if layout.align() <= MIN_ALIGN {
232             // SAFETY: because `ptr` has been successfully allocated with this allocator,
233             // `HEAP` must have been successfully initialized.
234             let heap = unsafe { get_process_heap() };
235 
236             // SAFETY: `heap` is a non-null handle returned by `GetProcessHeap`,
237             // `ptr` is a pointer to the start of an allocated block.
238             // The returned pointer points to the start of an allocated block.
239             unsafe { HeapReAlloc(heap, 0, ptr as c::LPVOID, new_size) as *mut u8 }
240         } else {
241             // SAFETY: `realloc_fallback` is implemented using `dealloc` and `alloc`, which will
242             // correctly handle `ptr` and return a pointer satisfying the guarantees of `System`
243             unsafe { realloc_fallback(self, ptr, layout, new_size) }
244         }
245     }
246 }
247