blob: e222d55aa99852520432e111614bd36150486f5c [file] [log] [blame] [edit]
//! Global Allocator Bridge
//!
//! This module provides a bridge between the global-allocator interface of
//! the rust standard library and the allocators of this crate. The stabilized
//! interface of the rust compiler and standard-library to the global allocator
//! is provided by the `core::alloc::GlobalAlloc` trait and the
//! `global_allocator` attribute. The types provided by this module implement
//! this trait and can be used to register a global allocator.
//!
//! Only one crate in every dependency graph can use the `global_allocator`
//! attribute to mark one static variable as the global allocator of the entire
//! application. The type of it must implement `GlobalAlloc`. Note that this
//! attribute can only be used in the crate-root, not in sub-modules.
//!
//! UEFI is, however, not a natural fit for the global-allocator trait. On UEFI
//! systems, access to all system APIs is done through the system table, which
//! is passed as argument to the application entry-point. Therefore, it is up
//! to the implementor of the entry-point to set up the global state inherent
//! to rust's global allocator.
//!
//! # Examples
//!
//! The following UEFI application simply registers an allocator with its
//! system-table and then invokes `uefi_run()`. The latter can then operate
//! under the assumption that an allocator is available and ready. Once the
//! function returns, the allocator is automatically torn down.
//!
//! This is a typical use of the `r-efi-alloc` crate. Only applications that
//! actually exit the boot-services, or access UEFI outside of regular UEFI
//! application and driver environments will have to use the custom allocator
//! interfaces.
//!
//! ```ignore
//! #![no_main]
//! #![no_std]
//!
//! use r_efi::efi;
//! use r_efi_alloc::{alloc::Allocator, global::Bridge};
//!
//! #[global_allocator]
//! static GLOBAL_ALLOCATOR: Bridge = Bridge::new();
//!
//! #[no_mangle]
//! pub extern "C" fn efi_main(
//! h: efi::Handle,
//! st: *mut efi::SystemTable,
//! ) -> efi::Status {
//! unsafe {
//! let mut allocator = Allocator::from_system_table(st, efi::LOADER_DATA);
//! let _attachment = GLOBAL_ALLOCATOR.attach(&mut allocator);
//!
//! efi_run(h, st)
//! }
//! }
//!
//! pub fn efi_run(h: efi::Handle, st: *mut efi::SystemTable) -> efi::Status {
//! ...
//! }
//! ```
use core::sync::atomic;
/// Bridge for Global Allocators
///
/// This bridge connects static allocator variables to the dynamic UEFI
/// allocator interfaces. The bridge object implements the `GlobalAlloc`
/// interface and can thus be marked as `global_allocator`.
///
/// The need for a bridge arises from the fact that UEFI requires access to
/// the system-table to allocate memory, and the system-table is only available
/// as argument to the entry-point. Hence, this bridge represents a dynamic
/// link between the global allocator and a runtime allocator created by the
/// application.
///
/// The main API of the bridge is the `attach()` function, which allows to
/// attach an allocator to the bridge, which is thereon used for allocations.
/// Only a single allocator can be attached to a bridge at a time, and any
/// global allocations will fail if no allocator is attached.
///
/// The `attach()` operation returns an object that represents the attachment.
/// To release it, the attachment object has to be dropped. Note that the
/// caller must ensure that any global allocator is released before an
/// allocator attachment is released.
pub struct Bridge {
attachment: atomic::AtomicPtr<crate::alloc::Allocator>,
}
/// Bridge Attachment
///
/// This type represents the attachment of an allocator to a bridge. It is
/// returned by the `attach()` operation of a bridge. This type has no exposed
/// API other than a custom `drop()` implementation, which releases the
/// attachment.
pub struct Attachment<'alloc, 'bridge> {
allocator: &'alloc mut crate::alloc::Allocator,
bridge: &'bridge Bridge,
}
impl Bridge {
/// Create Bridge
///
/// The Bridge type represents the global allocator. Since the latter
/// cannot be instantiated at compile-time (on UEFI the system-table
/// address can only be resolved at runtime, since it is passed as argument
/// to the entry point), it is implemented as a bridge between the actual
/// allocator object and the global allocator. By default, the bridge
/// object has no allocator linked. Any allocation requests will thusly
/// yield an allocation error.
///
/// To make use of a bridge, you have to instantiate an allocator object
/// and attach it via the `attach()` method.
///
/// You can create as many bridges as you like. However, to mark a bridge
/// as global allocator, you have to make it a global, static variable and
/// annotate it with `#[global_allocator]`. Only one such variable is
/// allowed to exist in any crate tree, and it must be declared in the root
/// module of a given crate.
pub const fn new() -> Bridge {
Bridge {
attachment: atomic::AtomicPtr::new(core::ptr::null_mut()),
}
}
unsafe fn raw_attach(&self, ptr: *mut crate::alloc::Allocator) -> Option<()> {
// Set @ptr as the attachment on this bridge. This only succeeds if
// there is not already an attachment set.
// We use a compare_exchange() to change the attachment if it was NULL.
// We use Release semantics, so any stores to your allocator are
// visible once the attachment is written. On error, no ordering
// guarantees are given, since this interface is not meant to be a
// programmatic query.
// Note that the Release pairs with the Acquire in the GlobalAlloc
// trait below.
//
// This interface is unsafe since the caller must guarantee to detach
// the bridge before it is destroyed. There are no runtime guarantees
// given by this interface, it is all left to the caller.
let p = self.attachment.compare_exchange(
core::ptr::null_mut(),
ptr,
atomic::Ordering::Release,
atomic::Ordering::Relaxed,
);
if p.is_ok() {
Some(())
} else {
None
}
}
unsafe fn raw_detach(&self, ptr: *mut crate::alloc::Allocator) {
// Detach @ptr from this bridge. The caller must guarantee @ptr is
// already attached to the bridge. This function will panic if @ptr is
// not the current attachment.
//
// We use compare_exchange() to replace the old attachment with NULL.
// If it was not NULL, we panic. No ordering guarantees are required,
// since there is no dependent state.
let p = self.attachment.compare_exchange(
ptr,
core::ptr::null_mut(),
atomic::Ordering::Relaxed,
atomic::Ordering::Relaxed,
);
assert!(p.is_ok());
}
/// Attach an allocator
///
/// This attaches the allocator given as @allocator to the bridge. If there
/// is an allocator attached already, this will yield `None`. Otherwise, an
/// attachment is returned that represents this link. Dropping the
/// attachment will detach the allocator from the bridge.
///
/// As long as an allocator is attached to a bridge, allocations through
/// this bridge (via rust's `GlobalAlloc` trait) will be served by this
/// allocator.
///
/// This is an unsafe interface. It is the caller's responsibility to
/// guarantee that the attachment survives all outstanding allocations.
/// That is, any allocated memory must be released before detaching the
/// allocator.
pub unsafe fn attach<'alloc, 'bridge>(
&'bridge self,
allocator: &'alloc mut crate::alloc::Allocator,
) -> Option<Attachment<'alloc, 'bridge>> {
match self.raw_attach(allocator) {
None => None,
Some(()) => Some(Attachment {
allocator: allocator,
bridge: self,
}),
}
}
}
impl<'alloc, 'bridge> Drop for Attachment<'alloc, 'bridge> {
fn drop(&mut self) {
unsafe {
self.bridge.raw_detach(self.allocator);
}
}
}
// This implements GlobalAlloc for our bridge. This trait is used by the rust
// ecosystem to serve global memory allocations. For this to work, you must
// have a bridge as static variable annotated as `#[global_allocator]`.
//
// We simply forward all allocation requests to the attached allocator. If the
// allocator is NULL, we fail the allocations.
//
// Note that the bridge interface must guarantee that an attachment survives
// all allocations. That is, you must drop/deallocate all memory before
// dropping your attachment. See the description of the bridge interface for
// details.
unsafe impl core::alloc::GlobalAlloc for Bridge {
unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
let allocator = self.attachment.load(atomic::Ordering::Acquire);
if allocator.is_null() {
return core::ptr::null_mut();
}
(&*allocator).alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
let allocator = self.attachment.load(atomic::Ordering::Acquire);
assert!(!allocator.is_null());
(&*allocator).dealloc(ptr, layout)
}
}