hyperlight_guest_bin/memory.rs
1/*
2Copyright 2025 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use core::alloc::Layout;
18use core::ffi::c_void;
19use core::mem::{align_of, size_of};
20use core::ptr;
21
22use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
23use hyperlight_guest::exit::abort_with_code;
24
25/*
26 C-wrappers for Rust's registered global allocator.
27
28 Each memory allocation via `malloc/calloc/realloc` is stored together with a `alloc::Layout` describing
29 the size and alignment of the allocation. This layout is stored just before the actual raw memory returned to the caller.
30
31 Example: A call to malloc(64) will allocate space for both an `alloc::Layout` and 64 bytes of memory:
32
33 ----------------------------------------------------------------------------------------
34 | Layout { size: 64 + size_of::<Layout>(), ... } | 64 bytes of memory | ...
35 ----------------------------------------------------------------------------------------
36 ^
37 |
38 |
39 ptr returned to caller
40*/
41
42// We assume the maximum alignment for any value is the alignment of u128.
43const DEFAULT_ALIGN: usize = align_of::<u128>();
44const HEADER_LEN: usize = size_of::<Header>();
45
46#[repr(transparent)]
47// A header that stores the layout information for the allocated memory block.
48struct Header(Layout);
49
50/// Allocates a block of memory with the given size. The memory is only guaranteed to be initialized to 0s if `zero` is true, otherwise
51/// it may or may not be initialized.
52///
53/// # Invariants
54/// `alignment` must be non-zero and a power of two
55///
56/// # Safety
57/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
58unsafe fn alloc_helper(size: usize, alignment: usize, zero: bool) -> *mut c_void {
59 if size == 0 {
60 return ptr::null_mut();
61 }
62
63 let actual_align = alignment.max(align_of::<Header>());
64 let data_offset = HEADER_LEN.next_multiple_of(actual_align);
65
66 let Some(total_size) = data_offset.checked_add(size) else {
67 abort_with_code(&[ErrorCode::MallocFailed as u8]);
68 };
69
70 // Create layout for entire allocation
71 let layout =
72 Layout::from_size_align(total_size, actual_align).expect("Invalid layout parameters");
73
74 unsafe {
75 let raw_ptr = match zero {
76 true => alloc::alloc::alloc_zeroed(layout),
77 false => alloc::alloc::alloc(layout),
78 };
79
80 if raw_ptr.is_null() {
81 abort_with_code(&[ErrorCode::MallocFailed as u8]);
82 }
83
84 // Place Header immediately before the user data region
85 let header_ptr = raw_ptr.add(data_offset - HEADER_LEN).cast::<Header>();
86 header_ptr.write(Header(layout));
87 raw_ptr.add(data_offset) as *mut c_void
88 }
89}
90
91/// Allocates a block of memory with the given size.
92/// The memory is not guaranteed to be initialized to 0s.
93///
94/// # Safety
95/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
96#[unsafe(no_mangle)]
97pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
98 unsafe { alloc_helper(size, DEFAULT_ALIGN, false) }
99}
100
101/// Allocates a block of memory for an array of `nmemb` elements, each of `size` bytes.
102/// The memory is initialized to 0s.
103///
104/// # Safety
105/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
106#[unsafe(no_mangle)]
107pub unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
108 unsafe {
109 let total_size = nmemb
110 .checked_mul(size)
111 .expect("nmemb * size should not overflow in calloc");
112
113 alloc_helper(total_size, DEFAULT_ALIGN, true)
114 }
115}
116
117/// Allocates aligned memory.
118///
119/// # Safety
120/// The returned pointer must be freed with `free` when it is no longer needed.
121#[unsafe(no_mangle)]
122pub unsafe extern "C" fn aligned_alloc(alignment: usize, size: usize) -> *mut c_void {
123 // Validate alignment
124 if alignment == 0 || (alignment & (alignment - 1)) != 0 {
125 return ptr::null_mut();
126 }
127
128 unsafe { alloc_helper(size, alignment, false) }
129}
130
131/// Frees the memory block pointed to by `ptr`.
132///
133/// # Safety
134/// `ptr` must be a pointer to a memory block previously allocated by `memory::malloc`, `memory::calloc`, or `memory::realloc`.
135#[unsafe(no_mangle)]
136pub unsafe extern "C" fn free(ptr: *mut c_void) {
137 if ptr.is_null() {
138 return;
139 }
140
141 let user_ptr = ptr as *const u8;
142
143 unsafe {
144 // Read the Header just before the user data
145 let header_ptr = user_ptr.sub(HEADER_LEN).cast::<Header>();
146 let layout = header_ptr.read().0;
147
148 // Deallocate from the original base pointer
149 let offset = HEADER_LEN.next_multiple_of(layout.align());
150 let raw_ptr = user_ptr.sub(offset) as *mut u8;
151 alloc::alloc::dealloc(raw_ptr, layout);
152 }
153}
154
155/// Changes the size of the memory block pointed to by `ptr` to `size` bytes. If the returned ptr is non-null,
156/// any usage of the old memory block is immediately undefined behavior.
157///
158/// # Safety
159/// `ptr` must be a pointer to a memory block previously allocated by `memory::malloc`, `memory::calloc`, or `memory::realloc`.
160#[unsafe(no_mangle)]
161pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
162 if ptr.is_null() {
163 // If the pointer is null, treat as a malloc
164 return unsafe { malloc(size) };
165 }
166
167 if size == 0 {
168 // If the size is 0, treat as a free and return null
169 unsafe {
170 free(ptr);
171 }
172 return ptr::null_mut();
173 }
174
175 let user_ptr = ptr as *const u8;
176
177 unsafe {
178 let header_ptr = user_ptr.sub(HEADER_LEN).cast::<Header>();
179
180 let old_layout = header_ptr.read().0;
181 let old_offset = HEADER_LEN.next_multiple_of(old_layout.align());
182 let old_user_size = old_layout.size() - old_offset;
183
184 let new_ptr = alloc_helper(size, old_layout.align(), false);
185 if new_ptr.is_null() {
186 return ptr::null_mut();
187 }
188
189 let copy_size = old_user_size.min(size);
190 ptr::copy_nonoverlapping(user_ptr, new_ptr as *mut u8, copy_size);
191
192 free(ptr);
193 new_ptr
194 }
195}