use crate::{os, page, util, Error, Protection, Result};
#[allow(clippy::len_without_is_empty)]
pub struct Allocation {
base: *const (),
size: usize,
}
impl Allocation {
#[inline(always)]
pub fn as_ptr<T>(&self) -> *const T {
self.base.cast()
}
#[inline(always)]
pub fn as_mut_ptr<T>(&mut self) -> *mut T {
self.base as *mut T
}
#[inline(always)]
pub fn as_ptr_range<T>(&self) -> std::ops::Range<*const T> {
let range = self.as_range();
(range.start as *const T)..(range.end as *const T)
}
#[inline(always)]
pub fn as_mut_ptr_range<T>(&mut self) -> std::ops::Range<*mut T> {
let range = self.as_range();
(range.start as *mut T)..(range.end as *mut T)
}
#[inline(always)]
pub fn as_range(&self) -> std::ops::Range<usize> {
(self.base as usize)..(self.base as usize).saturating_add(self.size)
}
#[inline(always)]
pub fn len(&self) -> usize {
self.size
}
}
impl Drop for Allocation {
#[inline]
fn drop(&mut self) {
let result = unsafe { os::free(self.base, self.size) };
debug_assert!(result.is_ok(), "freeing region: {:?}", result);
}
}
#[inline]
pub fn alloc(size: usize, protection: Protection) -> Result<Allocation> {
if size == 0 {
return Err(Error::InvalidParameter("size"));
}
let size = page::ceil(size as *const ()) as usize;
unsafe {
let base = os::alloc(std::ptr::null::<()>(), size, protection)?;
Ok(Allocation { base, size })
}
}
#[inline]
pub fn alloc_at<T>(address: *const T, size: usize, protection: Protection) -> Result<Allocation> {
let (address, size) = util::round_to_page_boundaries(address, size)?;
unsafe {
let base = os::alloc(address.cast(), size, protection)?;
Ok(Allocation { base, size })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_size_is_aligned_to_page_size() -> Result<()> {
let memory = alloc(1, Protection::NONE)?;
assert_eq!(memory.len(), page::size());
Ok(())
}
#[test]
fn alloc_rejects_empty_allocation() {
assert!(matches!(
alloc(0, Protection::NONE),
Err(Error::InvalidParameter(_))
));
}
#[test]
fn alloc_obtains_correct_properties() -> Result<()> {
let memory = alloc(1, Protection::READ_WRITE)?;
let region = crate::query(memory.as_ptr::<()>())?;
assert_eq!(region.protection(), Protection::READ_WRITE);
assert!(region.len() >= memory.len());
assert!(!region.is_guarded());
assert!(!region.is_shared());
assert!(region.is_committed());
Ok(())
}
#[test]
fn alloc_frees_memory_when_dropped() -> Result<()> {
let base = alloc(1, Protection::READ_WRITE)?.as_ptr::<()>();
let query = crate::query(base);
assert!(matches!(query, Err(Error::UnmappedRegion)));
Ok(())
}
#[test]
fn alloc_can_allocate_unused_region() -> Result<()> {
let base = alloc(1, Protection::NONE)?.as_ptr::<()>();
let memory = alloc_at(base, 1, Protection::READ_WRITE)?;
assert_eq!(memory.as_ptr(), base);
Ok(())
}
}