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