Skip to content

Commit

Permalink
cleanup + docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ielashi committed Aug 30, 2023
1 parent 3fcce86 commit dd59a84
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 69 deletions.
137 changes: 79 additions & 58 deletions src/btreemap/node/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use super::*;
use crate::btreemap::node::v2::PAGE_OVERFLOW_DATA_OFFSET;
use std::cmp::min;

/// A `NodeReader` simulates the node as a memory.
/// A `NodeReader` abstracts the `Node` as a single contiguous memory, even though internally
/// it can be composed of multiple pages.
pub struct NodeReader<'a, M: Memory> {
pub address: Address,
pub overflows: &'a [Address],
Expand All @@ -11,27 +12,19 @@ pub struct NodeReader<'a, M: Memory> {
}

impl<'a, M: Memory> Memory for NodeReader<'a, M> {
fn size(&self) -> u64 {
panic!("");
}

fn grow(&self, _: u64) -> i64 {
panic!("");
}

fn read(&self, offset: u64, dst: &mut [u8]) {
if (offset + dst.len() as u64) < self.page_size.get() as u64 {
self.memory.read(self.address.get() + offset, dst);
return;
}

let iter = NodeIterator {
virtual_segment: VirtualSegment {
let iter = NodeIterator::new(
VirtualSegment {
address: Address::from(offset),
length: Bytes::from(dst.len() as u64),
},
page_size: Bytes::from(self.page_size.get()),
};
Bytes::from(self.page_size.get()),
);

let mut bytes_read = 0;
for RealSegment {
Expand All @@ -57,7 +50,15 @@ impl<'a, M: Memory> Memory for NodeReader<'a, M> {
}

fn write(&self, _: u64, _: &[u8]) {
panic!("out of bounds")
unreachable!("NodeReader does not call write")
}

fn size(&self) -> u64 {
unreachable!("NodeReader does not call size")
}

fn grow(&self, _: u64) -> i64 {
unreachable!("NodeReader does not call grow")
}
}

Expand All @@ -73,9 +74,47 @@ struct RealSegment {
length: Bytes,
}

// An iterator that maps a segment of a node into segments of its underlying memory.
//
// A segment in a node can map to multiple segments of memory. Here's an example:
//
// Node
// --------------------------------------------------------
// (A) ---------- SEGMENT ---------- (B)
// --------------------------------------------------------
// ↑ ↑ ↑ ↑
// Page 0 Page 1 Page 2 Page 3
//
// The [`Node`] is internally divided into fixed-size pages. In the node's virtual
// address space, all these pages are consecutive, but in the underlying memory this may not
// be the case.
//
// A virtual segment would first be split at the page boundaries. The example virtual segment
// above would be split into the following segments:
//
// (A, end of page 0)
// (start of page 1, end of page 1)
// (start of page 2, B)
//
// Each of the segments above can then be translated into the real address space by looking up
// the pages' addresses in the underlying memory.
struct NodeIterator {
virtual_segment: VirtualSegment,
page_size: Bytes,

// The amount of data that can be stored in an overflow page.
overflow_page_capacity: Bytes,
}

impl NodeIterator {
#[inline]
fn new(virtual_segment: VirtualSegment, page_size: Bytes) -> Self {
Self {
virtual_segment,
page_size,
overflow_page_capacity: page_size - PAGE_OVERFLOW_DATA_OFFSET,
}
}
}

impl Iterator for NodeIterator {
Expand All @@ -87,58 +126,40 @@ impl Iterator for NodeIterator {
return None;
}

// Map the virtual segment's address to a real address.
let offset = Bytes::from(self.virtual_segment.address.get());

let segment = if offset < self.page_size {
// The address is in the initial page.

// Compute how many bytes are in this real segment.
let bytes_in_segment = {
let end_page_offset = self.page_size;

// Write up to either the end of the page, or the end of the segment.
min(end_page_offset - offset, self.virtual_segment.length)
};

RealSegment {
page_idx: 0,
offset,
length: bytes_in_segment,
}
// Compute the page where the segment begins.
let page_idx = if offset < self.page_size {
0 // The segment begins in in the initial page.
} else {
// The amount of data that can be stored in an overflow page.
let overflow_data_size = self.page_size - PAGE_OVERFLOW_DATA_OFFSET;

// The offset is in the overflows.
let overflow_idx =
((offset - self.page_size).get() as usize) / (overflow_data_size.get() as usize);

let offset_in_overflow =
((offset - self.page_size).get() as usize) % (overflow_data_size.get() as usize);

let overflow_page_end_offset =
self.page_size + Bytes::from((overflow_idx + 1) as u64) * overflow_data_size;

// Compute how many bytes are in this real segment.
let bytes_in_segment = {
let end_page_offset = overflow_page_end_offset;
// The segment begins in an overflow page.
((offset - self.page_size) / self.overflow_page_capacity).get() + 1
};

// Write up to either the end of the page, or the end of the segment.
min(end_page_offset - offset, self.virtual_segment.length)
};
// Compute the length of the next real segment.
// The length of the real segment is either up to the end of the page, or the end of the
// virtual segment, whichever is smaller.
let length = {
let page_end = self.page_size + self.overflow_page_capacity * page_idx;
min(page_end - offset, self.virtual_segment.length)
};

RealSegment {
page_idx: overflow_idx + 1,
offset: PAGE_OVERFLOW_DATA_OFFSET + Bytes::new(offset_in_overflow as u64),
length: bytes_in_segment,
}
// Compute the offset within the page.
let offset = if offset < self.page_size {
offset
} else {
// The offset is in the overflow pages.
PAGE_OVERFLOW_DATA_OFFSET + (offset - self.page_size) % self.overflow_page_capacity
};

// Update the virtual segment to exclude the portion we're about to return.
self.virtual_segment.length -= segment.length;
self.virtual_segment.address += segment.length;
self.virtual_segment.length -= length;
self.virtual_segment.address += length;

Some(segment)
Some(RealSegment {
page_idx: page_idx as usize,
offset,
length,
})
}
}
2 changes: 1 addition & 1 deletion src/btreemap/node/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const ENTRIES_OFFSET: Bytes = Bytes::new(15);
// Overflow page
const OVERFLOW_MAGIC: &[u8; 3] = b"NOF";
const PAGE_OVERFLOW_NEXT_OFFSET: Bytes = Bytes::new(3);
pub const PAGE_OVERFLOW_DATA_OFFSET: Bytes = Bytes::new(11);
pub(super) const PAGE_OVERFLOW_DATA_OFFSET: Bytes = Bytes::new(11);

// The minimum size a page can have.
// Rationale: a page size needs to at least store the header (15 bytes) + all the children
Expand Down
45 changes: 35 additions & 10 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use core::ops::{Add, AddAssign, Mul, Sub, SubAssign};
use core::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign, Rem};

pub const NULL: Address = Address(0);

Expand Down Expand Up @@ -48,18 +48,27 @@ impl AddAssign<Bytes> for Address {
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
pub struct Bytes(u64);

impl<I: Into<u64>> From<I> for Bytes {
fn from(bytes: I) -> Self {
Self(bytes.into())
impl From<u64> for Bytes {
fn from(bytes: u64) -> Self {
Self(bytes)
}
}

// `From<usize>` is unimplemented as it would conflict
// with the `From` trait above.
#[allow(clippy::from_over_into)]
impl Into<usize> for Bytes {
fn into(self) -> usize {
self.0 as usize
impl From<usize> for Bytes {
fn from(bytes: usize) -> Self {
Self(bytes as u64)
}
}

impl From<u32> for Bytes {
fn from(bytes: u32) -> Self {
Self(bytes as u64)
}
}

impl From<u16> for Bytes {
fn from(bytes: u16) -> Self {
Self(bytes as u64)
}
}

Expand Down Expand Up @@ -87,6 +96,22 @@ impl Mul<Bytes> for Bytes {
}
}

impl Div<Bytes> for Bytes {
type Output = Self;

fn div(self, bytes: Bytes) -> Self {
Self(self.0 / bytes.0)
}
}

impl Rem<Bytes> for Bytes {
type Output = Self;

fn rem(self, bytes: Bytes) -> Self {
Self(self.0 % bytes.0)
}
}

impl Mul<u64> for Bytes {
type Output = Self;

Expand Down

0 comments on commit dd59a84

Please sign in to comment.