use offset_of! for ptr offsets
This commit is contained in:
parent
d92eda3ea4
commit
53441e014e
@ -7,7 +7,8 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memmap = "0.7.0"
|
memmap = "0.7.0"
|
||||||
zerocopy = "0.7.0-alpha.5"
|
memoffset = "0.9.0"
|
||||||
|
zerocopy = { version = "0.7.0-alpha.5", features = ["alloc"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use memoffset::offset_of;
|
||||||
use zerocopy::{AsBytes, FromBytes, FromZeroes, Unaligned};
|
use zerocopy::{AsBytes, FromBytes, FromZeroes, Unaligned};
|
||||||
|
|
||||||
use crate::{Db, FilePointer, FileRange, PagePointer, RawFilePointer, PAGE_SIZE, U16, U32, U64};
|
use crate::{Db, FilePointer, FileRange, PagePointer, RawFilePointer, PAGE_SIZE, U16, U32, U64};
|
||||||
@ -31,14 +32,24 @@ impl SlabKind {
|
|||||||
#[derive(Clone, Copy, FromBytes, FromZeroes, AsBytes, Unaligned)]
|
#[derive(Clone, Copy, FromBytes, FromZeroes, AsBytes, Unaligned)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct AllocatorState {
|
pub struct AllocatorState {
|
||||||
pub general: RawFilePointer,
|
pub general: FilePointer<FreeListBlock>,
|
||||||
pub slabs: SlabListPointer,
|
pub slabs: SlabListPointer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FilePointer<AllocatorState> {
|
||||||
|
pub fn general_ptr(self) -> FilePointer<FilePointer<FreeListBlock>> {
|
||||||
|
field_ptr!(self, AllocatorState, general)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slabs_ptr(self) -> FilePointer<SlabListPointer> {
|
||||||
|
field_ptr!(self, AllocatorState, slabs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, FromBytes, FromZeroes, AsBytes, Unaligned)]
|
#[derive(Clone, Copy, FromBytes, FromZeroes, AsBytes, Unaligned)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct GeneralPurposeAllocator {
|
pub struct GeneralPurposeAllocator {
|
||||||
pub head_ptr: FilePointer<FreeListBlock>,
|
pub head_ptr: FilePointer<FilePointer<FreeListBlock>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, FromBytes, FromZeroes, AsBytes, Unaligned)]
|
#[derive(Clone, Copy, FromBytes, FromZeroes, AsBytes, Unaligned)]
|
||||||
@ -46,36 +57,37 @@ pub struct GeneralPurposeAllocator {
|
|||||||
pub struct FreeListBlock {
|
pub struct FreeListBlock {
|
||||||
next: FilePointer<FreeListBlock>,
|
next: FilePointer<FreeListBlock>,
|
||||||
size: u8,
|
size: u8,
|
||||||
|
full_size: U64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilePointer<FreeListBlock> {
|
impl FilePointer<FreeListBlock> {
|
||||||
pub fn next_ptr(self) -> FilePointer<FilePointer<FreeListBlock>> {
|
pub fn next_ptr(self) -> FilePointer<FilePointer<FreeListBlock>> {
|
||||||
FilePointer::new(self.into_raw())
|
FilePointer::new(self.into_raw() + offset_of!(FreeListBlock, next) as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size_start_ptr(self) -> FilePointer<u8> {
|
pub fn size_ptr(self) -> FilePointer<u8> {
|
||||||
FilePointer::new(self.into_raw() + size_of::<FilePointer<FreeListBlock>>() as u64)
|
FilePointer::new(self.into_raw() + offset_of!(FreeListBlock, size) as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size_end_ptr(self) -> FilePointer<U64> {
|
pub fn full_size_ptr(self) -> FilePointer<U64> {
|
||||||
FilePointer::new(self.into_raw() + size_of::<FreeListBlock>() as u64)
|
FilePointer::new(self.into_raw() + offset_of!(FreeListBlock, full_size) as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GeneralPurposeAllocator {
|
impl GeneralPurposeAllocator {
|
||||||
const SIZE_MASK: u8 = 0b1000_0000;
|
const SIZE_MASK: u8 = 0b1000_0000;
|
||||||
const MIN_ALLOCATION_SIZE: u64 = size_of::<FreeListBlock>() as u64;
|
const MIN_ALLOCATION_SIZE: u64 = size_of::<FreeListBlock>() as u64 - size_of::<U64>() as u64;
|
||||||
|
|
||||||
pub fn size<R>(db: &Db<R>, head: FilePointer<FreeListBlock>) -> u64 {
|
pub fn size<R>(db: &Db<R>, head: FilePointer<FreeListBlock>) -> u64 {
|
||||||
// println!("get size({head:?})");
|
// println!("get size({head:?})");
|
||||||
let first_byte: u8 = unsafe { db.read(head.size_start_ptr()) };
|
let first_byte: u8 = unsafe { db.read(head.size_ptr()) };
|
||||||
|
|
||||||
let size = if first_byte & Self::SIZE_MASK == 0 {
|
let size = if first_byte & Self::SIZE_MASK == 0 {
|
||||||
// small size (can fit in 7bits)
|
// small size (can fit in 7bits)
|
||||||
first_byte as u64
|
first_byte as u64
|
||||||
} else {
|
} else {
|
||||||
// large size
|
// large size
|
||||||
unsafe { db.read::<U64>(head.size_end_ptr()) }.get()
|
unsafe { db.read(head.full_size_ptr()) }.get()
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::MIN_ALLOCATION_SIZE + size
|
Self::MIN_ALLOCATION_SIZE + size
|
||||||
@ -87,11 +99,11 @@ impl GeneralPurposeAllocator {
|
|||||||
if size <= (u8::MAX & !Self::SIZE_MASK) as u64 {
|
if size <= (u8::MAX & !Self::SIZE_MASK) as u64 {
|
||||||
// small size (can fit in 7bits)
|
// small size (can fit in 7bits)
|
||||||
debug_assert_eq!(size as u8 & Self::SIZE_MASK, 0);
|
debug_assert_eq!(size as u8 & Self::SIZE_MASK, 0);
|
||||||
unsafe { db.write(head.size_start_ptr(), size as u8) };
|
unsafe { db.write(head.size_ptr(), size as u8) };
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
unsafe {
|
||||||
db.write(head.size_start_ptr(), Self::SIZE_MASK);
|
db.write(head.size_ptr(), Self::SIZE_MASK);
|
||||||
db.write::<U64>(head.size_end_ptr(), size.into());
|
db.write(head.full_size_ptr(), size.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,13 +111,13 @@ impl GeneralPurposeAllocator {
|
|||||||
fn clear<R>(db: &mut Db<R>, ptr: FilePointer<FreeListBlock>) -> RawFilePointer {
|
fn clear<R>(db: &mut Db<R>, ptr: FilePointer<FreeListBlock>) -> RawFilePointer {
|
||||||
unsafe {
|
unsafe {
|
||||||
db.write(ptr.next_ptr(), FilePointer::null());
|
db.write(ptr.next_ptr(), FilePointer::null());
|
||||||
let first_byte: u8 = db.read(ptr.size_start_ptr());
|
let first_byte: u8 = db.read(ptr.size_ptr());
|
||||||
|
|
||||||
// clear first size byte
|
// clear first size byte
|
||||||
db.write(ptr.size_start_ptr(), 0);
|
db.write(ptr.size_ptr(), 0);
|
||||||
if first_byte & Self::SIZE_MASK != 0 {
|
if first_byte & Self::SIZE_MASK != 0 {
|
||||||
// larger block. clear full size field
|
// larger block. clear full size field
|
||||||
db.write(ptr.size_end_ptr(), 0.into());
|
db.write(ptr.full_size_ptr(), 0.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +152,11 @@ impl GeneralPurposeAllocator {
|
|||||||
// we need space to store the free list entry
|
// we need space to store the free list entry
|
||||||
let needed_size = expected_size.max(Self::MIN_ALLOCATION_SIZE);
|
let needed_size = expected_size.max(Self::MIN_ALLOCATION_SIZE);
|
||||||
|
|
||||||
let head = self.head_ptr;
|
// while this pointet doesn't technically point to a `FreeListBlock` (it points to the head pointer)
|
||||||
|
// as long as it's at most in the `prev` position it will never be allocated and only ever be
|
||||||
|
// written to to set the pointer to the first element. We use `empty_list` to make sure we never
|
||||||
|
// write into it incorrectly.
|
||||||
|
let head = self.head_ptr.cast::<FreeListBlock>();
|
||||||
|
|
||||||
// if the first element is replaced update the head pointer
|
// if the first element is replaced update the head pointer
|
||||||
let mut prevprev = FilePointer::<FreeListBlock>::null();
|
let mut prevprev = FilePointer::<FreeListBlock>::null();
|
||||||
@ -152,7 +168,7 @@ impl GeneralPurposeAllocator {
|
|||||||
while !next.is_null() && !Self::can_allocate_into(needed_size, Self::size(db, next)) {
|
while !next.is_null() && !Self::can_allocate_into(needed_size, Self::size(db, next)) {
|
||||||
prevprev = prev;
|
prevprev = prev;
|
||||||
prev = next;
|
prev = next;
|
||||||
next = unsafe { db.read(next) }.next;
|
next = unsafe { db.read(next.next_ptr()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// dbg!(next, Self::size(db, next));
|
// dbg!(next, Self::size(db, next));
|
||||||
@ -248,7 +264,8 @@ impl GeneralPurposeAllocator {
|
|||||||
let mut size = range.len().max(Self::MIN_ALLOCATION_SIZE);
|
let mut size = range.len().max(Self::MIN_ALLOCATION_SIZE);
|
||||||
let mut start = FilePointer::<FreeListBlock>::new(range.start);
|
let mut start = FilePointer::<FreeListBlock>::new(range.start);
|
||||||
|
|
||||||
let head = self.head_ptr;
|
// see `allocate` for reasoning why this is okay
|
||||||
|
let head = self.head_ptr.cast::<FreeListBlock>();
|
||||||
|
|
||||||
let mut prevprev = FilePointer::null();
|
let mut prevprev = FilePointer::null();
|
||||||
let mut prev = head;
|
let mut prev = head;
|
||||||
|
72
src/lib.rs
72
src/lib.rs
@ -10,6 +10,22 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
macro_rules! field_ptr {
|
||||||
|
($base_ptr: expr, $type: ty, $field: ident) => {{
|
||||||
|
let base: FilePointer<$type> = $base_ptr;
|
||||||
|
|
||||||
|
let res = FilePointer::new(base.into_raw() + offset_of!($type, $field) as u64);
|
||||||
|
|
||||||
|
if false {
|
||||||
|
let ptr: Box<$type> = <$type as ::zerocopy::FromZeroes>::new_box_zeroed();
|
||||||
|
let mut addr = ::core::ptr::addr_of!(ptr.$field);
|
||||||
|
addr = res.typed_null_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
mod allocator;
|
mod allocator;
|
||||||
mod atomic_arc;
|
mod atomic_arc;
|
||||||
mod mapped;
|
mod mapped;
|
||||||
@ -18,6 +34,7 @@ mod transaction;
|
|||||||
use allocator::{AllocatorState, GeneralPurposeAllocator, SlabListPointer, SlabPointer};
|
use allocator::{AllocatorState, GeneralPurposeAllocator, SlabListPointer, SlabPointer};
|
||||||
use atomic_arc::AtomicArc;
|
use atomic_arc::AtomicArc;
|
||||||
use memmap::{Mmap, MmapMut};
|
use memmap::{Mmap, MmapMut};
|
||||||
|
use memoffset::offset_of;
|
||||||
use transaction::TransactionHandle;
|
use transaction::TransactionHandle;
|
||||||
use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref, Unaligned, LE};
|
use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref, Unaligned, LE};
|
||||||
|
|
||||||
@ -107,6 +124,15 @@ impl<T> FilePointer<T> {
|
|||||||
pub fn into_raw(self) -> RawFilePointer {
|
pub fn into_raw(self) -> RawFilePointer {
|
||||||
self.inner
|
self.inner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cast<U>(self) -> FilePointer<U> {
|
||||||
|
FilePointer::new(self.into_raw())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn typed_null_ptr(self) -> *const T {
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, FromBytes, FromZeroes, AsBytes, Unaligned, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, FromBytes, FromZeroes, AsBytes, Unaligned, Hash, PartialEq, Eq)]
|
||||||
@ -226,7 +252,7 @@ impl Default for Header {
|
|||||||
magic: *b"cool db format 1",
|
magic: *b"cool db format 1",
|
||||||
root: RawFilePointer::null(),
|
root: RawFilePointer::null(),
|
||||||
allocator_state: AllocatorState {
|
allocator_state: AllocatorState {
|
||||||
general: RawFilePointer::null(),
|
general: FilePointer::null(),
|
||||||
slabs: SlabListPointer(FilePointer::new(
|
slabs: SlabListPointer(FilePointer::new(
|
||||||
RawFilePointer::null() + size_of::<Header>() as u64,
|
RawFilePointer::null() + size_of::<Header>() as u64,
|
||||||
)),
|
)),
|
||||||
@ -274,11 +300,31 @@ struct SnapshotAndFreeList<R> {
|
|||||||
to_free: Vec<FileRange>,
|
to_free: Vec<FileRange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FilePointer<Header> {
|
||||||
|
fn root_ptr<R>(self) -> FilePointer<FilePointer<R>> {
|
||||||
|
field_ptr!(self, Header, root).cast::<FilePointer<R>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocator_state_ptr(self) -> FilePointer<AllocatorState> {
|
||||||
|
field_ptr!(self, Header, allocator_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<R> Db<R> {
|
impl<R> Db<R> {
|
||||||
fn root(&self) -> FilePointer<R> {
|
fn root(&self) -> FilePointer<R> {
|
||||||
FilePointer::new(self.header().root)
|
FilePointer::new(self.header().root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn header_ptr() -> FilePointer<Header> {
|
||||||
|
FilePointer::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn general_purpose_allocator() -> GeneralPurposeAllocator {
|
||||||
|
GeneralPurposeAllocator {
|
||||||
|
head_ptr: Self::header_ptr().allocator_state_ptr().general_ptr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn header(&self) -> &Header {
|
fn header(&self) -> &Header {
|
||||||
unsafe { self.reference_range_unchecked(Self::header_ptr().range()) }
|
unsafe { self.reference_range_unchecked(Self::header_ptr().range()) }
|
||||||
}
|
}
|
||||||
@ -337,24 +383,6 @@ impl<R> Db<R> {
|
|||||||
self.snapshots = snapshots;
|
self.snapshots = snapshots;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_ptr() -> FilePointer<Header> {
|
|
||||||
FilePointer::new(RawFilePointer(0.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn root_ptr() -> FilePointer<FilePointer<R>> {
|
|
||||||
FilePointer::new(RawFilePointer(16.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn allocator_state_ptr() -> RawFilePointer {
|
|
||||||
RawFilePointer((size_of::<Header>() as u64 - size_of::<AllocatorState>() as u64).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn general_purpose_allocator() -> GeneralPurposeAllocator {
|
|
||||||
GeneralPurposeAllocator {
|
|
||||||
head_ptr: FilePointer::new(Self::allocator_state_ptr()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_reader(&self) -> Reader<R> {
|
pub fn create_reader(&self) -> Reader<R> {
|
||||||
Reader {
|
Reader {
|
||||||
state: self.state.clone(),
|
state: self.state.clone(),
|
||||||
@ -370,12 +398,12 @@ impl<R> Db<R> {
|
|||||||
|
|
||||||
// update root pointer and immediately flush
|
// update root pointer and immediately flush
|
||||||
unsafe {
|
unsafe {
|
||||||
self.write(Self::root_ptr(), new_root);
|
self.write(Self::header_ptr().root_ptr(), new_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.map
|
self.map
|
||||||
.flush_range(
|
.flush_range(
|
||||||
Self::root_ptr().into_raw().0.get() as usize,
|
Self::header_ptr().root_ptr::<R>().into_raw().0.get() as usize,
|
||||||
size_of::<RawFilePointer>(),
|
size_of::<RawFilePointer>(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -626,7 +654,7 @@ mod tests {
|
|||||||
fn fragmentation<R>(db: &mut Db<R>, print: bool) -> usize {
|
fn fragmentation<R>(db: &mut Db<R>, print: bool) -> usize {
|
||||||
let allocator = Db::<()>::general_purpose_allocator();
|
let allocator = Db::<()>::general_purpose_allocator();
|
||||||
|
|
||||||
let mut next = unsafe { db.read(allocator.head_ptr.next_ptr()) };
|
let mut next = unsafe { db.read(allocator.head_ptr) };
|
||||||
|
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
while !next.is_null() {
|
while !next.is_null() {
|
||||||
|
Loading…
Reference in New Issue
Block a user