#![feature(
allocator_api,
btree_cursors,
concat_bytes,
const_mut_refs,
alloc_layout_extra,
slice_ptr_get,
btreemap_alloc,
io_error_uncategorized
)]
use const_str;
use ctor;
use intrusive_collections::{RBTree, UnsafeRef};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::marker;
use std::mem::ManuallyDrop;
use std::ops;
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
mod allocator;
use allocator::new_allocator;
pub use allocator::{Allocator, ALLOCATION_QUANTUM};
use allocator::{ByAddressAdapter, BySizeAddressAdapter, FreeBlock};
mod fd_and_mem;
use fd_and_mem::{commit, rollback, extend_file, save_old_page, os};
use os::MapHolder;
#[derive(Debug)]
struct FileHolder(os::File);
impl FileHolder {
fn new(prefix: &str, extension: &str) -> std::io::Result<Self> {
let fname = std::format!("{}.{}\0", prefix, extension);
let r = os::open(&fname)?;
Ok(Self(r))
}
fn read_header_of_header(&self) -> Option<HeaderOfHeader> {
let mut h = std::mem::MaybeUninit::<HeaderOfHeader>::uninit();
const S: usize = std::mem::size_of::<HeaderOfHeader>();
let read =
unsafe { os::read(self.0, h.as_mut_ptr() as *mut os::Void, S) };
if read == S {
Some(unsafe { h.assume_init() })
} else {
None
}
}
}
impl ops::Deref for FileHolder {
type Target = os::File;
fn deref(&self) -> &os::File {
&self.0
}
}
impl Drop for FileHolder {
fn drop(&mut self) {
os::close(self.0);
}
}
#[derive(Debug)]
struct Arena {
mem: MapHolder,
fd: FileHolder, log_fd: FileHolder, readers: Mutex<u32>, page_size: usize,
}
unsafe impl Sync for MapHolder {}
unsafe impl Send for MapHolder {}
pub const KI: usize = 1024;
pub const MI: usize = 1024 * KI;
pub const GI: usize = 1024 * MI;
#[cfg(target_pointer_width = "64")]
pub const TI: usize = 1024 * GI;
#[derive(Debug, Clone)]
pub struct Holder<Root: Sync + Send> {
arena: Arc<RwLock<Arena>>,
phantom: marker::PhantomData<Root>,
}
#[derive(Debug)]
pub enum Error {
IoError(std::io::Error),
WrongFileType,
WrongMajorVersion,
WrongEndianBitness,
WrongMagick,
WrongAddress,
WrongSize,
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
pub type Result<T> = std::result::Result<T, Error>;
impl<Root: Sync + Send> Holder<Root> {
pub unsafe fn open(
file_pfx: &str,
arena_address: Option<usize>,
arena_size: usize,
magick: u64,
new_root: fn(Allocator) -> Root,
) -> Result<Self> {
let ps = os::page_size();
assert!(ps > 0);
let page_size = ps as usize;
assert!(page_size.is_power_of_two());
let fd = FileHolder::new(file_pfx, "odb")?;
os::flock_w(*fd);
let h = fd.read_header_of_header();
let ld = FileHolder::new(file_pfx, "log")?;
let aa = match arena_address {
None => match h {
None => 0,
Some(ha) => ha.address,
},
Some(a) => match h {
None => a,
Some(ha) => {
if a == ha.address {
a
} else {
return Err(Error::WrongAddress);
}
}
},
} as *mut os::Void;
let addr = MapHolder::new(*fd, aa, arena_size)?;
let shown = addr.arena as usize;
let arena = Arena {
mem: addr,
fd,
log_fd: ld,
readers: Mutex::new(0),
page_size,
};
let arena = Arc::new(RwLock::new(arena));
let s = Self { arena, phantom: marker::PhantomData };
prep_header(&s, magick, shown, arena_size, new_root)?;
Ok(s)
}
pub fn read(&self) -> Reader<Root> {
let guard = self.arena.read().unwrap();
{
let mut readers = guard.readers.lock().unwrap();
if *readers == 0 {
os::flock_r(*guard.fd);
if os::not_empty(*guard.log_fd) {
os::unflock(*guard.fd);
os::flock_w(*guard.fd);
unsafe {
rollback::<false>(
*guard.fd,
*guard.log_fd,
guard.mem.arena,
guard.page_size,
)
};
os::flock_r(*guard.fd);
}
}
*readers += 1;
}
Reader::<Root> {
guard,
arena: Arc::clone(&self.arena),
phantom: marker::PhantomData,
}
}
fn internal_write<const PAGES_WRITABLE_FILE_NOT_LOCKED: bool>(
&self,
) -> InternalWriter<Root, PAGES_WRITABLE_FILE_NOT_LOCKED> {
let guard = self.arena.write().unwrap();
if PAGES_WRITABLE_FILE_NOT_LOCKED {
os::flock_w(*guard.fd);
}
unsafe {
rollback::<false>(
*guard.fd,
*guard.log_fd,
guard.mem.arena,
guard.page_size,
)
};
let rv = InternalWriter::<Root, PAGES_WRITABLE_FILE_NOT_LOCKED> {
guard,
phantom: marker::PhantomData,
};
rv.setseg();
rv
}
pub fn write(&mut self) -> Writer<Root> {
self.internal_write::<true>()
}
pub fn address(&self) -> usize {
self.arena.read().unwrap().mem.arena as usize
}
pub fn size(&self) -> usize {
self.arena.read().unwrap().mem.size as usize
}
}
impl<Root, const PAGES_WRITABLE: bool>
InternalWriter<'_, Root, PAGES_WRITABLE>
{
fn setseg(&self) {
let g = &self.guard;
let s = g.mem.size;
let b = g.mem.arena as *const os::Void;
let e = unsafe { b.byte_add(s) };
MEM_MAP.with_borrow_mut(|mb| {
let c = mb.upper_bound(ops::Bound::Included(&b));
if let Some((l, ta)) = c.peek_prev() {
assert!(unsafe { l.byte_add(ta.size) } <= b);
if let Some((l, _)) = c.peek_next() {
assert!(*l >= e);
}
}
mb.insert(b, ThreadArena {
size: s,
odb_fd: *g.fd,
log_fd: *g.log_fd,
page_size: g.page_size,
});
});
}
fn remseg(&self) {
let g = &self.guard;
let b = g.mem.arena as *const os::Void;
MEM_MAP.with_borrow_mut(|mb| {
let r = mb.remove(&b).unwrap();
assert_eq!(r, ThreadArena {
size: g.mem.size,
odb_fd: *g.fd,
log_fd: *g.log_fd,
page_size: g.page_size
});
});
}
}
unsafe fn memory_violation_handler(
addr: *const os::Void,
extend: bool,
) -> bool {
MEM_MAP.with_borrow(|mb| {
let c = mb.upper_bound(ops::Bound::Included(&addr));
if let Some((l, ta)) = c.peek_prev() {
assert!(l <= &addr);
if addr < l.byte_add(ta.size) {
if extend {
extend_file(*l, ta.size, ta.odb_fd, ta.page_size, addr);
} else {
save_old_page(*l, ta.size, ta.log_fd, ta.page_size, addr);
}
true
} else {
false
}
} else {
false
}
})
}
#[derive(PartialEq, Debug)]
struct ThreadArena {
size: usize,
odb_fd: os::File,
log_fd: os::File,
page_size: usize,
}
thread_local! {
static MEM_MAP: RefCell<BTreeMap<*const os::Void, ThreadArena>> =
const { RefCell::new(BTreeMap::new()) };
}
#[ctor::ctor]
unsafe fn initialise_sigs() {
os::initialize_memory_violation_handler(memory_violation_handler);
}
#[ctor::dtor]
unsafe fn finalise_sigs() {
os::finalize_memory_violation_handler();
}
#[repr(C, align(8))]
struct HeaderOfHeader {
filetype: [u8; 8], version: [u8; 8], endian_bitness: u64, magick: u64, address: usize, size: usize, by_address: ManuallyDrop<RBTree<ByAddressAdapter>>,
by_size_address: ManuallyDrop<RBTree<BySizeAddressAdapter>>,
}
#[repr(C, align(8))]
struct Header<Root> {
h: HeaderOfHeader,
root: ManuallyDrop<Root>,
}
fn prep_header<Root: Sync + Send>(
holder: &Holder<Root>,
magick: u64,
addr: usize,
size: usize,
new_root: fn(Allocator) -> Root,
) -> Result<()> {
let w = &holder.internal_write::<false>();
let header_state = header_is_ok_state(magick, addr, size)?;
match header_state {
HeaderState::Fine => {}
HeaderState::NeedsToGrow => grow_up_free_block::<Root>(addr, size, &w),
HeaderState::Empty => {
initialize_header::<Root>(addr, size, magick, &w, new_root)
}
}
Ok(())
}
fn grow_up_free_block<Root>(
addr: usize,
size: usize,
w: &InternalWriter<Root, false>,
) {
let hp = addr as *mut Header<Root>;
let ptr = unsafe { hp.as_mut() }.unwrap();
let p = unsafe { hp.byte_add(ptr.h.size) } as *const FreeBlock;
let ph = &mut ptr.h;
let cl = ph.by_address.back_mut();
let old_size = ph.size;
match cl.get() {
None => unsafe {
FreeBlock::initialize(
ph,
hp.byte_add(old_size) as *const FreeBlock,
size - old_size,
)
},
Some(l) => unsafe {
let lp = l as *const FreeBlock;
if lp.byte_add(l.size) < p {
FreeBlock::initialize(
ph,
hp.byte_add(old_size) as *const FreeBlock,
size - old_size,
)
} else {
ph.by_size_address.cursor_mut_from_ptr(lp).remove();
let lr = lp.cast_mut().as_mut().unwrap();
lr.size += size - old_size;
ph.by_size_address.insert(UnsafeRef::from_raw(lp));
}
},
};
ph.size = size;
let wg = &w.guard;
unsafe { commit(*wg.fd, *wg.log_fd, wg.mem.arena, wg.mem.size) };
}
fn initialize_header<Root>(
addr: usize,
size: usize,
magick: u64,
w: &InternalWriter<Root, false>,
new_root: fn(Allocator) -> Root,
) {
let hp = addr as *mut Header<Root>;
let ptr = unsafe { hp.as_mut() }.unwrap();
let h = HeaderOfHeader {
filetype: FILETYPE,
version: VERSION,
endian_bitness: ENDIAN_BITNESS,
magick,
address: addr,
size,
by_address: ManuallyDrop::new(RBTree::new(ByAddressAdapter::new())),
by_size_address: ManuallyDrop::new(RBTree::new(
BySizeAddressAdapter::new(),
)),
};
ptr.h = h;
let fbraw = unsafe { hp.add(1) };
let fbaddr = fbraw as usize;
let fbraw = unsafe {
fbraw.byte_add(ALLOCATION_QUANTUM - (fbaddr % ALLOCATION_QUANTUM))
} as *mut FreeBlock;
unsafe {
FreeBlock::initialize(
&mut ptr.h,
fbraw,
hp.byte_add(size).byte_offset_from(fbraw).try_into().unwrap(),
)
};
ptr.root = ManuallyDrop::new(new_root(w.allocator()));
let wg = &w.guard;
unsafe { commit(*wg.fd, *wg.log_fd, wg.mem.arena, wg.mem.size) };
}
const FILETYPE: [u8; 8] = const_str::to_byte_array!(b"TMFALLOC");
const V: &str = env!("CARGO_PKG_VERSION_MAJOR");
const V_PREF: &str = const_str::repeat!(" ", 8 - V.len());
const V_STR: &str = const_str::concat!(V_PREF, V);
const VERSION: [u8; 8] = const_str::to_byte_array!(V_STR);
const ENDIAN_BITNESS: u64 = std::mem::size_of::<usize>() as u64;
enum HeaderState {
Empty,
NeedsToGrow,
Fine,
}
fn header_is_ok_state(
magick: u64,
address: usize,
size: usize,
) -> Result<HeaderState> {
let ptr = unsafe { (address as *const HeaderOfHeader).as_ref() }.unwrap();
if ptr.filetype == [0; 8] && ptr.magick == 0 && ptr.address == 0 {
Ok(HeaderState::Empty)
} else if ptr.filetype != FILETYPE {
Err(Error::WrongFileType)
} else if ptr.version != VERSION {
Err(Error::WrongMajorVersion)
} else if ptr.endian_bitness != ENDIAN_BITNESS {
Err(Error::WrongEndianBitness)
} else if ptr.magick != magick {
Err(Error::WrongMagick)
} else if ptr.address != address {
Err(Error::WrongAddress)
} else if ptr.size > size {
Err(Error::WrongSize)
} else if ptr.size < size {
Ok(HeaderState::NeedsToGrow)
} else {
Ok(HeaderState::Fine)
}
}
pub struct Reader<'a, Root> {
guard: RwLockReadGuard<'a, Arena>,
arena: Arc<RwLock<Arena>>,
phantom: marker::PhantomData<&'a Root>,
}
impl<Root> ops::Deref for Reader<'_, Root> {
type Target = Root;
fn deref(&self) -> &Root {
&unsafe { (self.guard.mem.arena as *const Header<Root>).as_ref() }
.unwrap()
.root
}
}
impl<Root> Drop for Reader<'_, Root> {
fn drop(&mut self) {
let arena = self.arena.read().unwrap();
let mut readers = arena.readers.lock().unwrap();
if *readers == 1 {
os::unflock(*arena.fd);
}
assert!(*readers > 0);
*readers -= 1;
}
}
pub struct InternalWriter<'a, Root, const PAGES_WRITABLE: bool> {
guard: RwLockWriteGuard<'a, Arena>,
phantom: marker::PhantomData<&'a Root>,
}
impl<'a, Root, const PAGES_WRITABLE: bool>
InternalWriter<'a, Root, PAGES_WRITABLE>
{
pub fn rollback(&self) {
let g = &self.guard;
unsafe {
rollback::<PAGES_WRITABLE>(
*g.fd,
*g.log_fd,
g.mem.arena,
g.page_size,
)
};
}
pub fn commit(&self) {
let g = &self.guard;
unsafe { commit(*g.fd, *g.log_fd, g.mem.arena, g.mem.size) };
}
pub fn allocator(&self) -> Allocator {
new_allocator(self.guard.mem.arena as usize)
}
}
impl<Root, const PAGES_WRITABLE: bool> ops::Deref
for InternalWriter<'_, Root, PAGES_WRITABLE>
{
type Target = Root;
fn deref(&self) -> &Root {
&unsafe { (self.guard.mem.arena as *const Header<Root>).as_ref() }
.unwrap()
.root
}
}
impl<Root, const PAGES_WRITABLE: bool> ops::DerefMut
for InternalWriter<'_, Root, PAGES_WRITABLE>
{
fn deref_mut(&mut self) -> &mut Root {
&mut unsafe { (self.guard.mem.arena as *mut Header<Root>).as_mut() }
.unwrap()
.root
}
}
impl<Root, const PAGES_WRITABLE: bool> Drop
for InternalWriter<'_, Root, PAGES_WRITABLE>
{
fn drop(&mut self) {
let a = &self.guard;
unsafe {
rollback::<PAGES_WRITABLE>(
*a.fd,
*a.log_fd,
a.mem.arena,
a.page_size,
)
};
self.remseg();
os::unflock(*a.fd);
}
}
pub type Writer<'a, Root> = InternalWriter<'a, Root, true>;
#[cfg(test)]
mod tests;