Skip to content

Commit 2a629c8

Browse files
committed
uefi: allocator: use shortcut for PAGE_SIZE + add test
Allocating page-aligned memory via the global allocator is not uncommon for UEFI OS loaders. Therefore, it is feasible to use a shortcut in the allocator, and directly use boot::allocate_pages() rather than boot::allocate_pool(). We can look at the TRACE messages of `cargo xtask run` to verify that the shortcut is taken.
1 parent bd3e22a commit 2a629c8

File tree

3 files changed

+78
-29
lines changed

3 files changed

+78
-29
lines changed

Diff for: uefi-test-runner/src/boot/memory.rs

+23-10
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,12 @@ mod bootservices {
5858
/// Tests that use [`uefi::allocator::Allocator`], which is configured as the
5959
/// global allocator.
6060
mod global {
61+
use alloc::boxed::Box;
62+
use uefi_raw::table::boot::PAGE_SIZE;
63+
6164
/// Simple test to ensure our custom allocator works with the `alloc` crate.
6265
pub fn alloc_vec() {
63-
info!("Allocating a vector through the `alloc` crate");
66+
info!("Allocating a vector using the global allocator");
6467

6568
#[allow(clippy::useless_vec)]
6669
let mut values = vec![-5, 16, 23, 4, 0];
@@ -71,17 +74,27 @@ mod global {
7174
}
7275

7376
/// Simple test to ensure our custom allocator works with correct alignment.
77+
#[allow(dead_code)] // Ignore warning due to field not being read.
7478
pub fn alloc_alignment() {
75-
info!("Allocating a structure with alignment to 0x100");
76-
77-
#[repr(align(0x100))]
78-
struct Block(
79-
// Ignore warning due to field not being read.
80-
#[allow(dead_code)] [u8; 0x100],
81-
);
79+
{
80+
info!("Allocating a structure with alignment of 0x100 using the global allocator");
81+
#[repr(align(0x100))]
82+
struct Block([u8; 0x100]);
8283

83-
let value = vec![Block([1; 0x100])];
84-
assert_eq!(value.as_ptr() as usize % 0x100, 0, "Wrong alignment");
84+
let value = vec![Block([1; 0x100])];
85+
assert_eq!(value.as_ptr() as usize % 0x100, 0, "Wrong alignment");
86+
}
87+
{
88+
info!("Allocating a memory page ({PAGE_SIZE}) using the global allocator");
89+
#[repr(align(4096))]
90+
struct Page([u8; PAGE_SIZE]);
91+
let value = Box::new(Page([0; PAGE_SIZE]));
92+
assert_eq!(
93+
value.0.as_ptr().align_offset(PAGE_SIZE),
94+
0,
95+
"Wrong alignment"
96+
);
97+
}
8598
}
8699
}
87100

Diff for: uefi/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
- The `Display` impl for `CStr8` now excludes the trailing null character.
3838
- `VariableKeys` initializes with a larger name buffer to work around firmware
3939
bugs on some devices.
40+
- The UEFI `allocator::Allocator` has been optimized for page-aligned
41+
allocations.
4042

4143

4244
# uefi - 0.34.1 (2025-02-07)

Diff for: uefi/src/allocator.rs

+53-19
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
//!
88
//! [`helpers`]: uefi::helpers
99
10-
use crate::boot;
10+
use crate::boot::{self, AllocateType};
1111
use crate::mem::memory_map::MemoryType;
1212
use crate::proto::loaded_image::LoadedImage;
1313
use core::alloc::{GlobalAlloc, Layout};
1414
use core::ptr::{self, NonNull};
1515
use 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+
const 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
86101
pub struct Allocator;
87102

88103
unsafe 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

Comments
 (0)