77//!
88//! [`helpers`]: uefi::helpers
99
10- use crate :: boot;
10+ use crate :: boot:: { self , AllocateType } ;
1111use crate :: mem:: memory_map:: MemoryType ;
1212use crate :: proto:: loaded_image:: LoadedImage ;
1313use core:: alloc:: { GlobalAlloc , Layout } ;
1414use core:: ptr:: { self , NonNull } ;
1515use core:: sync:: atomic:: { AtomicU32 , Ordering } ;
16+ use uefi_raw:: table:: boot:: PAGE_SIZE ;
1617
1718/// Get the memory type to use for allocation.
1819///
@@ -75,6 +76,20 @@ fn alloc_pool_aligned(memory_type: MemoryType, size: usize, align: usize) -> *mu
7576 }
7677}
7778
79+ /// Returns whether the allocation is a multiple of a [`PAGE_SIZE`] and is
80+ /// aligned to [`PAGE_SIZE`].
81+ ///
82+ /// This does not only check the alignment but also the size. For types
83+ /// allocated by Rust itself (e.g., `Box<T>`), the size is always at least the
84+ /// alignment, as specified in the [Rust type layout]. However, to be also safe
85+ /// when it comes to manual invocations, we additionally check if the size is
86+ /// a multiple of [`PAGE_SIZE`].
87+ ///
88+ /// [Rust type layout]: https://doc.rust-lang.org/reference/type-layout.html
89+ fn layout_allows_page_alloc_shortcut ( layout : & Layout ) -> bool {
90+ layout. size ( ) % PAGE_SIZE == 0 && layout. align ( ) == PAGE_SIZE
91+ }
92+
7893/// Allocator using UEFI boot services.
7994///
8095/// This type implements [`GlobalAlloc`] and can be marked with the
@@ -86,8 +101,9 @@ fn alloc_pool_aligned(memory_type: MemoryType, size: usize, align: usize) -> *mu
86101pub struct Allocator ;
87102
88103unsafe impl GlobalAlloc for Allocator {
89- /// Allocate memory using [`boot::allocate_pool`]. The allocation's [memory
90- /// type] matches the current image's [data type].
104+ /// Allocate memory using the UEFI boot services.
105+ ///
106+ /// The allocation's [memory type] matches the current image's [data type].
91107 ///
92108 /// [memory type]: MemoryType
93109 /// [data type]: LoadedImage::data_type
@@ -96,40 +112,58 @@ unsafe impl GlobalAlloc for Allocator {
96112 return ptr:: null_mut ( ) ;
97113 }
98114
99- let size = layout. size ( ) ;
100- let align = layout. align ( ) ;
101115 let memory_type = get_memory_type ( ) ;
116+ let use_page_shortcut = layout_allows_page_alloc_shortcut ( & layout) ;
102117
103- match align {
104- 0 ..=8 /* UEFI default alignment */ => {
118+ match ( use_page_shortcut, layout. align ( ) ) {
119+ // Allocating pages is actually very expected in UEFI OS loaders, so
120+ // it makes sense to provide this optimization.
121+ ( true , _) => {
122+ // To spammy, but useful for manual testing.
123+ // log::trace!("Taking PAGE_SIZE shortcut for layout={layout:?}");
124+ let count = layout. size ( ) . div_ceil ( PAGE_SIZE ) ;
125+ boot:: allocate_pages ( AllocateType :: AnyPages , memory_type, count)
126+ . map ( |ptr| ptr. as_ptr ( ) )
127+ . unwrap_or ( ptr:: null_mut ( ) )
128+ }
129+ ( false , 0 ..=8 /* UEFI default alignment */ ) => {
105130 // The requested alignment is less than or equal to eight, and
106131 // `allocate_pool` always provides eight-byte alignment, so we can
107132 // use `allocate_pool` directly.
108- boot:: allocate_pool ( memory_type, size)
133+ boot:: allocate_pool ( memory_type, layout . size ( ) )
109134 . map ( |ptr| ptr. as_ptr ( ) )
110135 . unwrap_or ( ptr:: null_mut ( ) )
111136 }
112- 9 .. => {
113- alloc_pool_aligned ( memory_type, size, align)
114- }
137+ ( false , 9 ..) => alloc_pool_aligned ( memory_type, layout. size ( ) , layout. align ( ) ) ,
115138 }
116139 }
117140
118- /// Deallocate memory using [` boot::free_pool`] .
141+ /// Deallocate memory using the UEFI boot services .
119142 ///
120143 /// This will panic after exiting boot services.
121144 unsafe fn dealloc ( & self , ptr : * mut u8 , layout : Layout ) {
122- match layout. align ( ) {
123- 0 ..=8 => {
124- // OK to unwrap: `ptr` is required to be a valid allocation by the trait API.
125- let ptr = NonNull :: new ( ptr) . unwrap ( ) ;
145+ let ptr = NonNull :: new ( ptr) . unwrap ( ) ;
146+
147+ let use_page_shortcut = layout_allows_page_alloc_shortcut ( & layout) ;
148+
149+ match ( use_page_shortcut, layout. align ( ) ) {
150+ ( true , _) => {
151+ // To spammy, but useful for manual testing.
152+ // log::trace!("Taking PAGE_SIZE shortcut for layout={layout:?}");
153+ let count = layout. size ( ) . div_ceil ( PAGE_SIZE ) ;
154+ unsafe { boot:: free_pages ( ptr, count) . unwrap ( ) }
155+ }
156+ ( false , 0 ..=8 /* UEFI default alignment */ ) => {
157+ // Warning: this will panic after exiting boot services.
126158 unsafe { boot:: free_pool ( ptr) } . unwrap ( ) ;
127159 }
128- 9 .. => {
160+ ( false , 9 ..) => {
161+ let ptr = ptr. as_ptr ( ) . cast :: < * mut u8 > ( ) ;
129162 // Retrieve the pointer to the full allocation that was packed right
130163 // before the aligned allocation in `alloc`.
131- let ptr = unsafe { ( ptr as * const * mut u8 ) . sub ( 1 ) . read ( ) } ;
132- let ptr = NonNull :: new ( ptr) . unwrap ( ) ;
164+ let actual_alloc_ptr = unsafe { ptr. sub ( 1 ) . read ( ) } ;
165+ let ptr = NonNull :: new ( actual_alloc_ptr) . unwrap ( ) ;
166+ // Warning: this will panic after exiting boot services.
133167 unsafe { boot:: free_pool ( ptr) } . unwrap ( ) ;
134168 }
135169 }
0 commit comments