Skip to main content

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}