Skip to main content

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}