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