#![cfg_attr(not(test), no_std)]
#![cfg_attr(test, feature(test))]
#![feature(alloc, allocator_api)]
#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
compile_error!("mmap-alloc only supports Windows, Linux, and Mac");
#[cfg(test)]
mod tests;
#[cfg(test)]
extern crate core;
extern crate alloc;
#[cfg(not(windows))]
extern crate libc;
extern crate object_alloc;
extern crate sysconf;
#[cfg(any(target_os = "linux", target_os = "macos"))]
extern crate errno;
#[cfg(windows)]
extern crate kernel32;
#[cfg(windows)]
extern crate winapi;
use self::alloc::allocator::{Alloc, AllocErr, CannotReallocInPlace, Excess, Layout};
use self::object_alloc::{Exhausted, UntypedObjectAlloc};
use core::ptr;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use errno::errno;
pub struct MapAllocBuilder {
read: bool,
write: bool,
exec: bool,
commit: bool,
pagesize: usize,
obj_size: Option<usize>,
}
impl MapAllocBuilder {
pub fn build(&self) -> MapAlloc {
let obj_size = if let Some(obj_size) = self.obj_size {
assert_eq!(
obj_size % self.pagesize,
0,
"object size ({}) is not a multiple of the page size ({})",
obj_size,
self.pagesize
);
obj_size
} else {
self.pagesize
};
MapAlloc {
pagesize: self.pagesize,
read: self.read,
write: self.write,
exec: self.exec,
perms: perms::get_perm(self.read, self.write, self.exec),
commit: self.commit,
obj_size: obj_size,
}
}
pub fn read(mut self, read: bool) -> MapAllocBuilder {
self.read = read;
self
}
pub fn write(mut self, write: bool) -> MapAllocBuilder {
self.write = write;
self
}
pub fn exec(mut self, exec: bool) -> MapAllocBuilder {
self.exec = exec;
self
}
pub fn no_write(mut self) -> MapAllocBuilder {
self.write = false;
self
}
#[cfg(any(target_os = "linux", windows))]
pub fn commit(mut self, commit: bool) -> MapAllocBuilder {
self.commit = commit;
self
}
pub fn obj_size(mut self, obj_size: usize) -> MapAllocBuilder {
self.obj_size = Some(obj_size);
self
}
}
impl Default for MapAllocBuilder {
fn default() -> MapAllocBuilder {
MapAllocBuilder {
read: true,
write: true,
exec: false,
commit: false,
pagesize: sysconf::page::pagesize(),
obj_size: None,
}
}
}
pub struct MapAlloc {
pagesize: usize,
#[cfg_attr(target_os = "linux", allow(unused))] read: bool,
#[cfg_attr(target_os = "linux", allow(unused))] write: bool,
#[cfg_attr(target_os = "linux", allow(unused))] exec: bool,
perms: perms::Perm,
commit: bool,
obj_size: usize,
}
impl Default for MapAlloc {
fn default() -> MapAlloc {
MapAllocBuilder::default().build()
}
}
impl MapAlloc {
#[cfg(windows)]
pub unsafe fn commit(&self, ptr: *mut u8, layout: Layout) {
debug_assert!(layout.size() > 0, "commit: size of layout must be non-zero");
#[cfg(debug_assertions)]
self.debug_verify_ptr(ptr, layout.clone());
commit(ptr, layout.size(), self.perms);
}
#[cfg(any(target_os = "linux", target_os = "macos", windows))]
pub unsafe fn uncommit(&self, ptr: *mut u8, layout: Layout) {
debug_assert!(
layout.size() > 0,
"uncommit: size of layout must be non-zero"
);
#[cfg(debug_assertions)]
self.debug_verify_ptr(ptr, layout.clone());
uncommit(ptr, layout.size());
}
#[cfg(target_os = "linux")]
unsafe fn resize_in_place(
&self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<(), CannotReallocInPlace> {
if new_layout.align() > self.pagesize {
return Err(CannotReallocInPlace);
}
let old_size = next_multiple(layout.size(), self.pagesize);
let new_size = next_multiple(new_layout.size(), self.pagesize);
if old_size == new_size {
return Ok(());
}
match remap(ptr, old_size, new_size, true) {
Some(new_ptr) => {
debug_assert_eq!(new_ptr, ptr);
Ok(())
}
None => Err(CannotReallocInPlace),
}
}
fn debug_verify_ptr(&self, ptr: *mut u8, layout: Layout) {
debug_assert_eq!(
ptr as usize % self.pagesize,
0,
"ptr {:?} not aligned to page size {}",
ptr,
self.pagesize
);
debug_assert!(layout.align() <= self.pagesize);
}
}
unsafe impl<'a> Alloc for &'a MapAlloc {
unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> {
debug_assert!(layout.size() > 0, "alloc: size of layout must be non-zero");
if layout.align() > self.pagesize {
return Err(AllocErr::invalid_input(
"cannot support alignment greater than a page",
));
}
let size = next_multiple(layout.size(), self.pagesize);
map(size, self.perms, self.commit).ok_or(AllocErr::Exhausted { request: layout })
}
unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) {
debug_assert!(
layout.size() > 0,
"dealloc: size of layout must be non-zero"
);
unmap(ptr, layout.size());
}
fn usable_size(&self, layout: &Layout) -> (usize, usize) {
debug_assert!(
layout.size() > 0,
"usable_size: size of layout must be non-zero"
);
let max_size = next_multiple(layout.size(), self.pagesize);
(max_size - self.pagesize + 1, max_size)
}
unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> {
debug_assert!(
layout.size() > 0,
"alloc_zeroed: size of layout must be non-zero"
);
<&'a MapAlloc as Alloc>::alloc(self, layout)
}
#[cfg(target_os = "linux")]
unsafe fn realloc(
&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<*mut u8, AllocErr> {
debug_assert!(
layout.size() > 0,
"realloc: size of layout must be non-zero"
);
debug_assert!(
new_layout.size() > 0,
"realloc: size of new_layout must be non-zero"
);
if new_layout.align() > self.pagesize {
return Err(AllocErr::invalid_input(
"cannot support alignment greater than a page",
));
}
let old_size = next_multiple(layout.size(), self.pagesize);
let new_size = next_multiple(new_layout.size(), self.pagesize);
if old_size == new_size {
return Ok(ptr);
}
remap(ptr, old_size, new_layout.size(), false).ok_or(AllocErr::Exhausted {
request: new_layout,
})
}
#[cfg(any(target_os = "macos", windows))]
unsafe fn realloc(
&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<*mut u8, AllocErr> {
debug_assert!(
layout.size() > 0,
"realloc: size of layout must be non-zero"
);
debug_assert!(
new_layout.size() > 0,
"realloc: size of new_layout must be non-zero"
);
if new_layout.align() > self.pagesize {
return Err(AllocErr::invalid_input(
"cannot support alignment greater than a page",
));
}
let old_size = next_multiple(layout.size(), self.pagesize);
let new_size = next_multiple(new_layout.size(), self.pagesize);
if old_size == new_size {
return Ok(ptr);
} else if !cfg!(windows) && new_size < old_size {
if let Ok(()) = self.shrink_in_place(ptr, layout.clone(), new_layout.clone()) {
return Ok(ptr);
}
}
let result = Alloc::alloc(self, new_layout);
if let Ok(new_ptr) = result {
use core::cmp;
let fix_old_perms = !self.read;
let fix_new_perms = !self.write;
if fix_old_perms {
protect(ptr, old_size, perms::get_perm(true, self.write, self.exec));
}
if fix_new_perms {
protect(
new_ptr,
new_size,
perms::get_perm(self.read, true, self.exec),
);
}
ptr::copy_nonoverlapping(ptr as *const u8, new_ptr, cmp::min(old_size, new_size));
Alloc::dealloc(self, ptr, layout);
if fix_new_perms {
protect(new_ptr, new_size, self.perms);
}
}
result
}
#[cfg(target_os = "linux")]
unsafe fn grow_in_place(
&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<(), CannotReallocInPlace> {
debug_assert!(
layout.size() > 0,
"grow_in_place: size of layout must be non-zero"
);
debug_assert!(
new_layout.size() > 0,
"grow_in_place: size of new_layout must be non-zero"
);
debug_assert!(new_layout.size() >= layout.size());
debug_assert_eq!(new_layout.align(), layout.align());
self.resize_in_place(ptr, layout, new_layout)
}
#[cfg(target_os = "linux")]
unsafe fn shrink_in_place(
&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<(), CannotReallocInPlace> {
debug_assert!(
layout.size() > 0,
"shrink_in_place: size of layout must be non-zero"
);
debug_assert!(
new_layout.size() > 0,
"shrink_in_place: size of new_layout must be non-zero"
);
debug_assert!(new_layout.size() <= layout.size());
debug_assert_eq!(new_layout.align(), layout.align());
self.resize_in_place(ptr, layout, new_layout)
}
#[cfg(target_os = "macos")]
unsafe fn shrink_in_place(
&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<(), CannotReallocInPlace> {
debug_assert!(
layout.size() > 0,
"shrink_in_place: size of layout must be non-zero"
);
debug_assert!(
new_layout.size() > 0,
"shrink_in_place: size of new_layout must be non-zero"
);
debug_assert!(new_layout.size() <= layout.size());
debug_assert_eq!(new_layout.align(), layout.align());
let old_size = next_multiple(layout.size(), self.pagesize);
let new_size = next_multiple(new_layout.size(), self.pagesize);
if new_size < old_size {
let diff = old_size - new_size;
let ptr = (ptr as usize + new_size) as *mut u8;
unmap(ptr, diff);
}
Ok(())
}
}
unsafe impl<'a> UntypedObjectAlloc for &'a MapAlloc {
fn layout(&self) -> Layout {
if cfg!(debug_assertions) {
Layout::from_size_align(self.obj_size, self.pagesize).unwrap()
} else {
unsafe { Layout::from_size_align_unchecked(self.obj_size, self.pagesize) }
}
}
unsafe fn alloc(&mut self) -> Result<*mut u8, Exhausted> {
match self.alloc_excess(self.layout()) {
Ok(Excess(ptr, _)) => Ok(ptr),
Err(AllocErr::Exhausted { .. }) => Err(Exhausted),
Err(AllocErr::Unsupported { .. }) => unreachable!(),
}
}
unsafe fn dealloc(&mut self, ptr: *mut u8) {
unmap(ptr, self.obj_size);
}
}
unsafe impl Alloc for MapAlloc {
unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> {
<&MapAlloc as Alloc>::alloc(&mut (&*self), layout)
}
fn usable_size(&self, layout: &Layout) -> (usize, usize) {
<&MapAlloc as Alloc>::usable_size(&(&*self), layout)
}
unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) {
<&MapAlloc as Alloc>::dealloc(&mut (&*self), ptr, layout)
}
unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> {
<&MapAlloc as Alloc>::alloc_zeroed(&mut (&*self), layout)
}
unsafe fn alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr> {
<&MapAlloc as Alloc>::alloc_excess(&mut (&*self), layout)
}
unsafe fn realloc(
&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<*mut u8, AllocErr> {
<&MapAlloc as Alloc>::realloc(&mut (&*self), ptr, layout, new_layout)
}
unsafe fn grow_in_place(
&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<(), CannotReallocInPlace> {
<&MapAlloc as Alloc>::grow_in_place(&mut (&*self), ptr, layout, new_layout)
}
unsafe fn shrink_in_place(
&mut self,
ptr: *mut u8,
layout: Layout,
new_layout: Layout,
) -> Result<(), CannotReallocInPlace> {
<&MapAlloc as Alloc>::shrink_in_place(&mut (&*self), ptr, layout, new_layout)
}
}
unsafe impl UntypedObjectAlloc for MapAlloc {
fn layout(&self) -> Layout {
<&MapAlloc as UntypedObjectAlloc>::layout(&(&*self))
}
unsafe fn alloc(&mut self) -> Result<*mut u8, Exhausted> {
<&MapAlloc as UntypedObjectAlloc>::alloc(&mut (&*self))
}
unsafe fn dealloc(&mut self, ptr: *mut u8) {
<&MapAlloc as UntypedObjectAlloc>::dealloc(&mut (&*self), ptr);
}
}
fn next_multiple(size: usize, unit: usize) -> usize {
let remainder = size % unit;
if remainder == 0 {
size
} else {
size + (unit - remainder)
}
}
#[cfg(target_os = "linux")]
unsafe fn map(size: usize, perms: i32, commit: bool) -> Option<*mut u8> {
use libc::{ENOMEM, MAP_ANONYMOUS, MAP_FAILED, MAP_POPULATE, MAP_PRIVATE};
let flags = if commit { MAP_POPULATE } else { 0 };
let ptr = libc::mmap(
ptr::null_mut(),
size,
perms,
MAP_ANONYMOUS | MAP_PRIVATE | flags,
-1,
0,
);
if ptr == MAP_FAILED {
if errno().0 == ENOMEM {
None
} else {
panic!("mmap failed: {}", errno())
}
} else {
assert_ne!(ptr, ptr::null_mut(), "mmap returned NULL");
Some(ptr as *mut u8)
}
}
#[cfg(target_os = "macos")]
unsafe fn map(size: usize, perms: i32, commit: bool) -> Option<*mut u8> {
use libc::{ENOMEM, MAP_ANON, MAP_FAILED, MAP_PRIVATE};
debug_assert!(!commit);
let ptr = libc::mmap(ptr::null_mut(), size, perms, MAP_ANON | MAP_PRIVATE, -1, 0);
if ptr == MAP_FAILED {
if errno().0 == ENOMEM {
None
} else {
panic!("mmap failed: {}", errno())
}
} else {
assert_ne!(ptr, ptr::null_mut(), "mmap returned NULL");
Some(ptr as *mut u8)
}
}
#[cfg(all(windows, target_pointer_width = "32"))]
type WindowsSize = u32;
#[cfg(all(windows, target_pointer_width = "64"))]
type WindowsSize = u64;
#[cfg(windows)]
unsafe fn map(size: usize, perms: u32, commit: bool) -> Option<*mut u8> {
use kernel32::VirtualAlloc;
use winapi::winnt::{MEM_COMMIT, MEM_RESERVE};
let typ = MEM_RESERVE | if commit { MEM_COMMIT } else { 0 };
let ptr = VirtualAlloc(ptr::null_mut(), size as WindowsSize, typ, perms) as *mut u8;
if ptr.is_null() {
None
} else {
Some(ptr)
}
}
#[cfg(target_os = "linux")]
unsafe fn remap(ptr: *mut u8, old_size: usize, new_size: usize, in_place: bool) -> Option<*mut u8> {
let flags = if !in_place { libc::MREMAP_MAYMOVE } else { 0 };
let result = libc::mremap(ptr as *mut _, old_size, new_size, flags);
if result == libc::MAP_FAILED {
let err = errno();
if err.0 == libc::ENOMEM {
None
} else {
panic!("mremap failed: {}", err)
}
} else {
assert_ne!(ptr, ptr::null_mut(), "mremap returned NULL");
Some(result as *mut u8)
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
unsafe fn unmap(ptr: *mut u8, size: usize) {
let ret = libc::munmap(ptr as *mut _, size);
assert_eq!(ret, 0, "munmap failed: {}", errno());
}
#[cfg(windows)]
unsafe fn unmap(ptr: *mut u8, _size: usize) {
use kernel32::{GetLastError, VirtualFree};
use winapi::winnt::MEM_RELEASE;
let ret = VirtualFree(ptr as *mut _, 0, MEM_RELEASE);
assert_ne!(
ret,
0,
"Call to VirtualFree failed with error code {}.",
GetLastError()
);
}
#[cfg_attr(target_os = "linux", allow(unused))]
#[cfg(any(target_os = "linux", target_os = "macos"))]
unsafe fn protect(ptr: *mut u8, size: usize, perm: perms::Perm) {
let ret = libc::mprotect(ptr as *mut _, size, perm);
assert_eq!(ret, 0, "mprotect failed: {}", errno());
}
#[cfg(windows)]
unsafe fn protect(ptr: *mut u8, size: usize, perm: perms::Perm) {
use kernel32::{GetLastError, VirtualProtect};
let mut _old_perm: winapi::DWORD = 0;
#[cfg(target_pointer_width = "64")]
type U = u64;
#[cfg(target_pointer_width = "32")]
type U = u32;
let ret = VirtualProtect(ptr as *mut _, size as U, perm, &mut _old_perm as *mut _);
assert_ne!(
ret,
0,
"Call to VirtualProtect failed with error code {}.",
GetLastError()
);
}
#[cfg(windows)]
unsafe fn commit(ptr: *mut u8, size: usize, perms: u32) {
use kernel32::VirtualAlloc;
use winapi::winnt::MEM_COMMIT;
let ret = VirtualAlloc(ptr as *mut _, size as WindowsSize, MEM_COMMIT, perms);
assert_eq!(ret as *mut u8, ptr);
}
#[cfg(target_os = "linux")]
unsafe fn uncommit(ptr: *mut u8, size: usize) {
use libc::MADV_DONTNEED;
libc::madvise(ptr as *mut _, size, MADV_DONTNEED);
}
#[cfg(target_os = "macos")]
unsafe fn uncommit(ptr: *mut u8, size: usize) {
use libc::MADV_FREE;
libc::madvise(ptr as *mut _, size, MADV_FREE);
}
#[cfg(windows)]
unsafe fn uncommit(ptr: *mut u8, size: usize) {
use kernel32::{GetLastError, VirtualFree};
use winapi::winnt::MEM_DECOMMIT;
let ret = VirtualFree(ptr as *mut _, size as WindowsSize, MEM_DECOMMIT);
assert_ne!(
ret,
0,
"Call to VirtualFree failed with error code {}.",
GetLastError()
);
}
mod perms {
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub use self::unix::*;
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub type Perm = i32;
#[cfg(windows)]
pub use self::windows::*;
#[cfg(windows)]
pub type Perm = u32;
pub fn get_perm(read: bool, write: bool, exec: bool) -> Perm {
match (read, write, exec) {
(false, false, false) => PROT_NONE,
(true, false, false) => PROT_READ,
(false, true, false) => PROT_WRITE,
(false, false, true) => PROT_EXEC,
(true, true, false) => PROT_READ_WRITE,
(true, false, true) => PROT_READ_EXEC,
(false, true, true) => PROT_WRITE_EXEC,
(true, true, true) => PROT_READ_WRITE_EXEC,
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
mod unix {
extern crate libc;
pub const PROT_NONE: i32 = libc::PROT_NONE;
pub const PROT_READ: i32 = libc::PROT_READ;
pub const PROT_WRITE: i32 = libc::PROT_WRITE;
pub const PROT_EXEC: i32 = libc::PROT_EXEC;
pub const PROT_READ_WRITE: i32 = libc::PROT_READ | libc::PROT_WRITE;
pub const PROT_READ_EXEC: i32 = libc::PROT_READ | libc::PROT_EXEC;
pub const PROT_WRITE_EXEC: i32 = libc::PROT_WRITE | libc::PROT_EXEC;
pub const PROT_READ_WRITE_EXEC: i32 = libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC;
}
#[cfg(windows)]
mod windows {
extern crate winapi;
use self::winapi::winnt;
pub const PROT_NONE: u32 = winnt::PAGE_NOACCESS;
pub const PROT_READ: u32 = winnt::PAGE_READONLY;
pub const PROT_WRITE: u32 = winnt::PAGE_READWRITE;
pub const PROT_EXEC: u32 = winnt::PAGE_EXECUTE;
pub const PROT_READ_WRITE: u32 = winnt::PAGE_READWRITE;
pub const PROT_READ_EXEC: u32 = winnt::PAGE_EXECUTE_READ;
pub const PROT_WRITE_EXEC: u32 = winnt::PAGE_EXECUTE_READWRITE;
pub const PROT_READ_WRITE_EXEC: u32 = winnt::PAGE_EXECUTE_READWRITE;
}
}