Merge pull request #143 from urbit/eamsden/integrate-pma

Integrate PMA
This commit is contained in:
Edward Amsden 2023-12-20 09:35:31 -06:00 committed by GitHub
commit c6a45aed9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 5467 additions and 4386 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@ ships/
*.backup
urbit
*.jam.out
*.o
*.a

34
rust/ares/Cargo.lock generated
View File

@ -22,6 +22,7 @@ name = "ares"
version = "0.1.0"
dependencies = [
"ares_macros",
"ares_pma",
"assert_no_alloc",
"autotools",
"bitvec",
@ -50,6 +51,14 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "ares_pma"
version = "0.1.0"
dependencies = [
"bindgen 0.69.1",
"cc",
]
[[package]]
name = "assert_no_alloc"
version = "1.1.2"
@ -105,6 +114,29 @@ dependencies = [
"which",
]
[[package]]
name = "bindgen"
version = "0.69.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
dependencies = [
"bitflags 2.4.1",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.39",
"which",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -827,7 +859,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced751f95a527a3458eb67c75e4ae7093d41585edaa7565f5769101502473019"
dependencies = [
"bindgen",
"bindgen 0.68.1",
"pkg-config",
]

View File

@ -12,9 +12,12 @@ edition = "2018"
# Please keep these alphabetized
[dependencies]
ares_macros = { path = "../ares_macros" }
# assert_no_alloc = "1.1.2"
# Use this when debugging requires the debug printfs in the PMA
# ares_pma = { path = "../ares_pma", features=["debug_prints"] }
ares_pma = { path = "../ares_pma" }
assert_no_alloc = "1.1.2"
# use this when debugging requires allocation (e.g. eprintln)
assert_no_alloc = {version="1.1.2", features=["warn_debug"]}
# assert_no_alloc = {version="1.1.2", features=["warn_debug"]}
bitvec = "1.0.0"
criterion = "0.4"
either = "1.9.0"

View File

@ -1,8 +1,11 @@
use crate::mem::{unifying_equality, NockStack, Preserve};
use crate::mem::{NockStack, Preserve};
use crate::mug::mug_u32;
use crate::noun::Noun;
use crate::persist::{pma_contains, Persist};
use crate::unifying_equality::unifying_equality;
use either::Either::{self, *};
use std::ptr::{copy_nonoverlapping, null};
use std::mem::size_of;
use std::ptr::{copy_nonoverlapping, null_mut};
use std::slice;
type MutStemEntry<T> = Either<*mut MutStem<T>, Leaf<T>>;
@ -160,11 +163,23 @@ impl<T: Copy> MutHamt<T> {
}
}
/**
* This is the core memory structure of an immutable HAMT.
*
* The root Stem lives in its own memory allocation, addressed by the pointer wrapped by [Hamt].
* All other Stems and Leaves live in memory blocks pointed to by [buffer]. The memory pointed to
* by this field may be zero to 32 entries, depending on the *number of bits set* in bitmap.
*
* Addressing a chunk of the key's hash is done by counting the number of set bits in the bitmap
* before the chunk'th bit. The typemap is a parallel bitmap in which bits are set if the
* corresponding entry is a stem, and cleared if it is a leaf.
*/
#[repr(packed)]
#[repr(C)]
struct Stem<T: Copy> {
bitmap: u32,
typemap: u32,
buffer: *const Entry<T>,
buffer: *mut Entry<T>,
}
impl<T: Copy> Copy for Stem<T> {}
@ -218,6 +233,7 @@ impl<T: Copy> Stem<T> {
}
#[repr(packed)]
#[repr(C)]
struct Leaf<T: Copy> {
len: usize,
buffer: *mut (Noun, T), // mutable for unifying equality
@ -238,6 +254,8 @@ impl<T: Copy> Leaf<T> {
}
#[derive(Copy, Clone)]
#[repr(packed)]
#[repr(C)]
union Entry<T: Copy> {
stem: Stem<T>,
leaf: Leaf<T>,
@ -256,19 +274,23 @@ assert_eq_size!(&[(Noun, ())], Leaf<()>);
assert_eq_size!(&[Entry<()>], Stem<()>);
#[derive(Copy, Clone)]
pub struct Hamt<T: Copy>(Stem<T>);
pub struct Hamt<T: Copy>(*mut Stem<T>);
impl<T: Copy + Preserve> Hamt<T> {
pub fn is_null(&self) -> bool {
self.0.bitmap == 0
unsafe { (*self.0).bitmap == 0 }
}
// Make a new, empty HAMT
pub fn new() -> Self {
Hamt(Stem {
bitmap: 0,
typemap: 0,
buffer: null(),
})
pub fn new(stack: &mut NockStack) -> Self {
unsafe {
let stem_ptr = stack.struct_alloc::<Stem<T>>(1);
*stem_ptr = Stem {
bitmap: 0,
typemap: 0,
buffer: null_mut(),
};
Hamt(stem_ptr)
}
}
/**
@ -278,7 +300,7 @@ impl<T: Copy + Preserve> Hamt<T> {
* in the HAMT
*/
pub fn lookup(&self, stack: &mut NockStack, n: &mut Noun) -> Option<T> {
let mut stem = self.0;
let mut stem = unsafe { *self.0 };
let mut mug = mug_u32(stack, *n);
'lookup: loop {
let chunk = mug & 0x1F; // 5 bits
@ -309,9 +331,9 @@ impl<T: Copy + Preserve> Hamt<T> {
pub fn insert(&self, stack: &mut NockStack, n: &mut Noun, t: T) -> Hamt<T> {
let mut mug = mug_u32(stack, *n);
let mut depth = 0u8;
let mut stem = self.0;
let mut stem_ret = self.0;
let mut dest = &mut stem_ret as *mut Stem<T>;
let mut stem = unsafe { *self.0 };
let stem_ret = unsafe { stack.struct_alloc::<Stem<T>>(1) };
let mut dest = stem_ret;
unsafe {
'insert: loop {
let chunk = mug & 0x1F; // 5 bits
@ -439,17 +461,12 @@ impl<T: Copy + Preserve> Hamt<T> {
}
}
impl<T: Copy + Preserve> Default for Hamt<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Copy + Preserve> Preserve for Hamt<T> {
unsafe fn assert_in_stack(&self, stack: &NockStack) {
stack.assert_struct_is_in(self.0.buffer, self.0.size());
stack.assert_struct_is_in(self.0, 1);
stack.assert_struct_is_in((*self.0).buffer, (*self.0).size());
let mut traversal_stack: [Option<(Stem<T>, u32)>; 6] = [None; 6];
traversal_stack[0] = Some((self.0, 0));
traversal_stack[0] = Some(((*self.0), 0));
let mut traversal_depth = 1;
'check: loop {
if traversal_depth == 0 {
@ -491,78 +508,85 @@ impl<T: Copy + Preserve> Preserve for Hamt<T> {
}
unsafe fn preserve(&mut self, stack: &mut NockStack) {
if stack.is_in_frame(self.0.buffer) {
let dest_buffer = stack.struct_alloc_in_previous_frame(self.0.size());
copy_nonoverlapping(self.0.buffer, dest_buffer, self.0.size());
self.0.buffer = dest_buffer;
// Here we're using the Rust stack since the array is a fixed
// size. Thus it will be cleaned up if the Rust thread running
// this is killed, and is therefore not an issue vs. if it were allocated
// on the heap.
//
// In the past, this traversal stack was allocated in NockStack, but
// exactly the right way to do this is less clear with the split stack.
let mut traversal_stack: [Option<(Stem<T>, u32)>; 6] = [None; 6];
traversal_stack[0] = Some((self.0, 0));
let mut traversal_depth = 1;
'preserve: loop {
if traversal_depth == 0 {
break;
}
let (stem, mut position) = traversal_stack[traversal_depth - 1]
.expect("Attempted to access uninitialized array element");
// can we loop over the size and count leading 0s remaining in the bitmap?
'preserve_stem: loop {
if position >= 32 {
traversal_depth -= 1;
continue 'preserve;
if stack.is_in_frame(self.0) {
let dest_stem = stack.struct_alloc_in_previous_frame(1);
copy_nonoverlapping(self.0, dest_stem, 1);
self.0 = dest_stem;
if stack.is_in_frame((*dest_stem).buffer) {
let dest_buffer = stack.struct_alloc_in_previous_frame((*dest_stem).size());
copy_nonoverlapping((*dest_stem).buffer, dest_buffer, (*dest_stem).size());
(*dest_stem).buffer = dest_buffer;
// Here we're using the Rust stack since the array is a fixed
// size. Thus it will be cleaned up if the Rust thread running
// this is killed, and is therefore not an issue vs. if it were allocated
// on the heap.
//
// In the past, this traversal stack was allocated in NockStack, but
// exactly the right way to do this is less clear with the split stack.
let mut traversal_stack: [Option<(Stem<T>, u32)>; 6] = [None; 6];
traversal_stack[0] = Some(((*dest_stem), 0));
let mut traversal_depth = 1;
'preserve: loop {
if traversal_depth == 0 {
break;
}
match stem.entry(position) {
None => {
position += 1;
continue 'preserve_stem;
let (stem, mut position) = traversal_stack[traversal_depth - 1]
.expect("Attempted to access uninitialized array element");
// can we loop over the size and count leading 0s remaining in the bitmap?
'preserve_stem: loop {
if position >= 32 {
traversal_depth -= 1;
continue 'preserve;
}
Some((Left(next_stem), idx)) => {
if stack.is_in_frame(next_stem.buffer) {
let dest_buffer =
stack.struct_alloc_in_previous_frame(next_stem.size());
copy_nonoverlapping(
next_stem.buffer,
dest_buffer,
next_stem.size(),
);
let new_stem = Stem {
bitmap: next_stem.bitmap,
typemap: next_stem.typemap,
buffer: dest_buffer,
};
*(stem.buffer.add(idx) as *mut Entry<T>) = Entry { stem: new_stem };
assert!(traversal_depth <= 5); // will increment
traversal_stack[traversal_depth - 1] = Some((stem, position + 1));
traversal_stack[traversal_depth] = Some((new_stem, 0));
traversal_depth += 1;
continue 'preserve;
} else {
match stem.entry(position) {
None => {
position += 1;
continue 'preserve_stem;
}
}
Some((Right(leaf), idx)) => {
if stack.is_in_frame(leaf.buffer) {
let dest_buffer = stack.struct_alloc_in_previous_frame(leaf.len);
copy_nonoverlapping(leaf.buffer, dest_buffer, leaf.len);
let new_leaf = Leaf {
len: leaf.len,
buffer: dest_buffer,
};
for pair in new_leaf.to_mut_slice().iter_mut() {
pair.0.preserve(stack);
pair.1.preserve(stack);
Some((Left(next_stem), idx)) => {
if stack.is_in_frame(next_stem.buffer) {
let dest_buffer =
stack.struct_alloc_in_previous_frame(next_stem.size());
copy_nonoverlapping(
next_stem.buffer,
dest_buffer,
next_stem.size(),
);
let new_stem = Stem {
bitmap: next_stem.bitmap,
typemap: next_stem.typemap,
buffer: dest_buffer,
};
*stem.buffer.add(idx) = Entry { stem: new_stem };
assert!(traversal_depth <= 5); // will increment
traversal_stack[traversal_depth - 1] =
Some((stem, position + 1));
traversal_stack[traversal_depth] = Some((new_stem, 0));
traversal_depth += 1;
continue 'preserve;
} else {
position += 1;
continue 'preserve_stem;
}
*(stem.buffer.add(idx) as *mut Entry<T>) = Entry { leaf: new_leaf };
}
position += 1;
continue 'preserve_stem;
Some((Right(leaf), idx)) => {
if stack.is_in_frame(leaf.buffer) {
let dest_buffer =
stack.struct_alloc_in_previous_frame(leaf.len);
copy_nonoverlapping(leaf.buffer, dest_buffer, leaf.len);
let new_leaf = Leaf {
len: leaf.len,
buffer: dest_buffer,
};
for pair in new_leaf.to_mut_slice().iter_mut() {
pair.0.preserve(stack);
pair.1.preserve(stack);
}
*stem.buffer.add(idx) = Entry { leaf: new_leaf };
}
position += 1;
continue 'preserve_stem;
}
}
}
}
@ -570,3 +594,182 @@ impl<T: Copy + Preserve> Preserve for Hamt<T> {
}
}
}
impl<T: Copy + Persist> Persist for Hamt<T> {
unsafe fn space_needed(&mut self, stack: &mut NockStack) -> usize {
if pma_contains(self.0, 1) {
return 0;
}
let mut bytes: usize = size_of::<Stem<T>>();
if pma_contains((*self.0).buffer, (*self.0).size()) {
return bytes;
};
bytes += (*self.0).size() * size_of::<Entry<T>>();
let mut depth: usize = 0;
let mut traversal = [Stem {
bitmap: 0,
typemap: 0,
buffer: null_mut(),
}; 6];
traversal[0] = *self.0;
loop {
assert!(depth < 6);
if traversal[depth].bitmap == 0 {
if depth == 0 {
break bytes;
}
depth -= 1;
continue;
}
let next_chunk = traversal[depth].bitmap.trailing_zeros();
let next_type = traversal[depth].typemap & (1 << next_chunk) != 0;
let next_entry = *traversal[depth].buffer;
traversal[depth].bitmap >>= next_chunk + 1;
traversal[depth].typemap >>= next_chunk + 1;
traversal[depth].buffer = traversal[depth].buffer.add(1);
if next_type {
// true->stem false->leaf
// found another stem
traversal[depth + 1] = next_entry.stem;
if pma_contains(traversal[depth + 1].buffer, traversal[depth + 1].size()) {
continue;
}
// count the buffer for the next stem
bytes += traversal[depth + 1].size() * size_of::<Entry<T>>();
depth += 1;
} else {
let mut leaf = next_entry.leaf;
if leaf.len == 0 {
continue;
}
if pma_contains(leaf.buffer, leaf.len) {
continue;
}
bytes += size_of::<(Noun, T)>() * leaf.len;
while leaf.len > 0 {
bytes += (*leaf.buffer).0.space_needed(stack);
bytes += (*leaf.buffer).1.space_needed(stack);
leaf.buffer = leaf.buffer.add(1);
leaf.len -= 1;
}
}
}
}
unsafe fn copy_to_buffer(&mut self, stack: &mut NockStack, buffer: &mut *mut u8) {
if pma_contains(self.0, 1) {
return;
}
let stem_ptr = *buffer as *mut Stem<T>;
copy_nonoverlapping(self.0, stem_ptr, 1);
*buffer = stem_ptr.add(1) as *mut u8;
self.0 = stem_ptr;
let stem_buffer_size = (*stem_ptr).size();
if pma_contains((*stem_ptr).buffer, stem_buffer_size) {
return;
}
let stem_buffer_ptr = *buffer as *mut Entry<T>;
copy_nonoverlapping((*stem_ptr).buffer, stem_buffer_ptr, stem_buffer_size);
*buffer = stem_buffer_ptr.add(stem_buffer_size) as *mut u8;
(*stem_ptr).buffer = stem_buffer_ptr;
let mut depth: usize = 0;
let mut traversal = [Stem {
bitmap: 0,
typemap: 0,
buffer: null_mut(),
}; 6];
traversal[0] = *stem_ptr;
loop {
if traversal[depth].bitmap == 0 {
if depth == 0 {
break;
}
depth -= 1;
continue;
}
let next_chunk = traversal[depth].bitmap.trailing_zeros();
let next_type = traversal[depth].typemap & (1 << next_chunk) != 0;
let next_entry_ptr = traversal[depth].buffer;
traversal[depth].bitmap >>= next_chunk + 1;
traversal[depth].typemap >>= next_chunk + 1;
traversal[depth].buffer = traversal[depth].buffer.add(1);
if next_type {
// Stem case
assert!(depth < 5);
let stem_ptr: *mut Stem<T> = &mut (*next_entry_ptr).stem;
let stem_size = (*stem_ptr).size();
if pma_contains((*stem_ptr).buffer, stem_size) {
continue;
}
let stem_buffer_ptr = *buffer as *mut Entry<T>;
copy_nonoverlapping((*stem_ptr).buffer, stem_buffer_ptr, stem_size);
*buffer = stem_buffer_ptr.add(stem_size) as *mut u8;
(*stem_ptr).buffer = stem_buffer_ptr;
traversal[depth + 1] = *stem_ptr;
depth += 1;
} else {
// Leaf case
let leaf_ptr: *mut Leaf<T> = &mut (*next_entry_ptr).leaf;
if (*leaf_ptr).len == 0 {
continue;
}
if pma_contains((*leaf_ptr).buffer, (*leaf_ptr).len) {
continue;
}
let leaf_buffer_ptr = *buffer as *mut (Noun, T);
copy_nonoverlapping((*leaf_ptr).buffer, leaf_buffer_ptr, (*leaf_ptr).len);
*buffer = leaf_buffer_ptr.add((*leaf_ptr).len) as *mut u8;
(*leaf_ptr).buffer = leaf_buffer_ptr;
let mut leaf_idx = 0;
while leaf_idx < (*leaf_ptr).len {
(*(*leaf_ptr).buffer.add(leaf_idx))
.0
.copy_to_buffer(stack, buffer);
(*(*leaf_ptr).buffer.add(leaf_idx))
.1
.copy_to_buffer(stack, buffer);
leaf_idx += 1;
}
}
}
}
unsafe fn handle_to_u64(&self) -> u64 {
self.0 as u64
}
unsafe fn handle_from_u64(meta_handle: u64) -> Self {
Hamt(meta_handle as *mut Stem<T>)
}
}

View File

@ -7,7 +7,6 @@ use crate::jets::cold::Cold;
use crate::jets::hot::Hot;
use crate::jets::warm::Warm;
use crate::jets::JetErr;
use crate::mem::unifying_equality;
use crate::mem::NockStack;
use crate::mem::Preserve;
use crate::newt::Newt;
@ -15,6 +14,7 @@ use crate::noun;
use crate::noun::{Atom, Cell, IndirectAtom, Noun, Slots, D, T};
use crate::serf::TERMINATOR;
use crate::trace::{write_nock_trace, TraceInfo, TraceStack};
use crate::unifying_equality::unifying_equality;
use ares_macros::tas;
use assert_no_alloc::assert_no_alloc;
use bitvec::prelude::{BitSlice, Lsb0};
@ -1304,9 +1304,9 @@ mod hint {
use crate::jets;
use crate::jets::cold;
use crate::jets::nock::util::{mook, LEAF};
use crate::mem::unifying_equality;
use crate::noun::{tape, Atom, Cell, Noun, D, T};
use crate::serf::TERMINATOR;
use crate::unifying_equality::unifying_equality;
use ares_macros::tas;
use std::sync::atomic::Ordering;
use std::sync::Arc;

View File

@ -307,8 +307,9 @@ pub mod util {
pub mod test {
use super::*;
use crate::hamt::Hamt;
use crate::mem::{unifying_equality, NockStack};
use crate::mem::NockStack;
use crate::noun::{Atom, Noun, D, T};
use crate::unifying_equality::unifying_equality;
use assert_no_alloc::assert_no_alloc;
use ibig::UBig;
@ -316,9 +317,9 @@ pub mod util {
let mut stack = NockStack::new(8 << 10 << 10, 0);
let newt = Newt::new_mock();
let cold = Cold::new(&mut stack);
let warm = Warm::new();
let warm = Warm::new(&mut stack);
let hot = Hot::init(&mut stack, URBIT_HOT_STATE);
let cache = Hamt::<Noun>::new();
let cache = Hamt::<Noun>::new(&mut stack);
Context {
stack,

View File

@ -1,7 +1,10 @@
use crate::hamt::Hamt;
use crate::mem::{unifying_equality, NockStack, Preserve};
use crate::mem::{NockStack, Preserve};
use crate::noun;
use crate::noun::{Atom, DirectAtom, Noun, Slots, D, T};
use crate::persist::{pma_contains, Persist};
use crate::unifying_equality::unifying_equality;
use std::mem::size_of;
use std::ptr::copy_nonoverlapping;
use std::ptr::null_mut;
@ -31,6 +34,59 @@ struct BatteriesMem {
parent_batteries: Batteries,
}
impl Persist for Batteries {
unsafe fn space_needed(&mut self, stack: &mut NockStack) -> usize {
let mut bytes = 0;
let mut batteries = *self;
loop {
if batteries.0.is_null() {
break;
}
if pma_contains(batteries.0, 1) {
break;
}
bytes += size_of::<BatteriesMem>();
bytes += (*batteries.0).battery.space_needed(stack);
bytes += (*batteries.0).parent_axis.space_needed(stack);
batteries = (*batteries.0).parent_batteries;
}
bytes
}
unsafe fn copy_to_buffer(&mut self, stack: &mut NockStack, buffer: &mut *mut u8) {
let mut dest = self;
loop {
if dest.0.is_null() {
break;
}
if pma_contains(dest.0, 1) {
break;
}
let batteries_mem_ptr = *buffer as *mut BatteriesMem;
copy_nonoverlapping(dest.0, batteries_mem_ptr, 1);
*buffer = batteries_mem_ptr.add(1) as *mut u8;
(*batteries_mem_ptr).battery.copy_to_buffer(stack, buffer);
(*batteries_mem_ptr)
.parent_axis
.copy_to_buffer(stack, buffer);
dest.0 = batteries_mem_ptr;
dest = &mut (*dest.0).parent_batteries;
}
}
unsafe fn handle_to_u64(&self) -> u64 {
self.0 as u64
}
unsafe fn handle_from_u64(meta_handle: u64) -> Self {
Batteries(meta_handle as *mut BatteriesMem)
}
}
impl Preserve for Batteries {
unsafe fn assert_in_stack(&self, stack: &NockStack) {
if self.0.is_null() {
@ -143,6 +199,55 @@ struct BatteriesListMem {
next: BatteriesList,
}
impl Persist for BatteriesList {
unsafe fn space_needed(&mut self, stack: &mut NockStack) -> usize {
let mut bytes = 0;
let mut list = *self;
loop {
if list.0.is_null() {
break;
}
if pma_contains(list.0, 1) {
break;
}
bytes += size_of::<BatteriesListMem>();
bytes += (*list.0).batteries.space_needed(stack);
list = (*list.0).next;
}
bytes
}
unsafe fn copy_to_buffer(&mut self, stack: &mut NockStack, buffer: &mut *mut u8) {
let mut dest = self;
loop {
if dest.0.is_null() {
break;
}
if pma_contains(dest.0, 1) {
break;
}
let list_mem_ptr = *buffer as *mut BatteriesListMem;
copy_nonoverlapping(dest.0, list_mem_ptr, 1);
*buffer = list_mem_ptr.add(1) as *mut u8;
dest.0 = list_mem_ptr;
(*dest.0).batteries.copy_to_buffer(stack, buffer);
dest = &mut (*dest.0).next;
}
}
unsafe fn handle_to_u64(&self) -> u64 {
self.0 as u64
}
unsafe fn handle_from_u64(meta_handle: u64) -> Self {
BatteriesList(meta_handle as *mut BatteriesListMem)
}
}
impl Preserve for BatteriesList {
unsafe fn assert_in_stack(&self, stack: &NockStack) {
if self.0.is_null() {
@ -215,6 +320,58 @@ struct NounListMem {
next: NounList,
}
impl Persist for NounList {
unsafe fn space_needed(&mut self, stack: &mut NockStack) -> usize {
let mut bytes: usize = 0;
let mut list = *self;
loop {
if list.0.is_null() {
break;
}
if pma_contains(list.0, 1) {
break;
}
bytes += size_of::<NounListMem>();
bytes += (*list.0).element.space_needed(stack);
list = (*list.0).next;
}
bytes
}
unsafe fn copy_to_buffer(&mut self, stack: &mut NockStack, buffer: &mut *mut u8) {
let mut dest = self;
loop {
if dest.0.is_null() {
break;
}
if pma_contains(dest.0, 1) {
break;
}
let noun_list_mem_ptr = *buffer as *mut NounListMem;
copy_nonoverlapping(dest.0, noun_list_mem_ptr, 1);
*buffer = noun_list_mem_ptr.add(1) as *mut u8;
dest.0 = noun_list_mem_ptr;
(*dest.0).element.copy_to_buffer(stack, buffer);
dest = &mut (*dest.0).next;
}
}
unsafe fn handle_to_u64(&self) -> u64 {
self.0 as u64
}
unsafe fn handle_from_u64(meta_handle: u64) -> Self {
NounList(meta_handle as *mut NounListMem)
}
}
impl Preserve for NounList {
unsafe fn assert_in_stack(&self, stack: &NockStack) {
if self.0.is_null() {
@ -292,6 +449,44 @@ struct ColdMem {
path_to_batteries: Hamt<BatteriesList>,
}
impl Persist for Cold {
unsafe fn space_needed(&mut self, stack: &mut NockStack) -> usize {
if pma_contains(self.0, 1) {
return 0;
}
let mut bytes = size_of::<ColdMem>();
bytes += (*self.0).battery_to_paths.space_needed(stack);
bytes += (*self.0).root_to_paths.space_needed(stack);
bytes += (*self.0).path_to_batteries.space_needed(stack);
bytes
}
unsafe fn copy_to_buffer(&mut self, stack: &mut NockStack, buffer: &mut *mut u8) {
if pma_contains(self.0, 1) {
return;
}
let cold_mem_ptr = *buffer as *mut ColdMem;
copy_nonoverlapping(self.0, cold_mem_ptr, 1);
*buffer = cold_mem_ptr.add(1) as *mut u8;
self.0 = cold_mem_ptr;
(*self.0).battery_to_paths.copy_to_buffer(stack, buffer);
(*self.0).root_to_paths.copy_to_buffer(stack, buffer);
(*self.0).path_to_batteries.copy_to_buffer(stack, buffer);
}
unsafe fn handle_to_u64(&self) -> u64 {
self.0 as u64
}
unsafe fn handle_from_u64(meta_handle: u64) -> Self {
Cold(meta_handle as *mut ColdMem)
}
}
impl Preserve for Cold {
unsafe fn assert_in_stack(&self, stack: &NockStack) {
stack.assert_struct_is_in(self.0, 1);
@ -319,9 +514,9 @@ impl Cold {
}
pub fn new(stack: &mut NockStack) -> Self {
let battery_to_paths = Hamt::new();
let root_to_paths = Hamt::new();
let path_to_batteries = Hamt::new();
let battery_to_paths = Hamt::new(stack);
let root_to_paths = Hamt::new(stack);
let path_to_batteries = Hamt::new(stack);
unsafe {
let cold_mem_ptr: *mut ColdMem = stack.struct_alloc(1);
*cold_mem_ptr = ColdMem {

View File

@ -149,7 +149,7 @@ pub mod util {
let cache_snapshot = context.cache;
let scry_snapshot = context.scry_stack;
context.cache = Hamt::<Noun>::new();
context.cache = Hamt::<Noun>::new(&mut context.stack);
context.scry_stack = T(&mut context.stack, &[scry, context.scry_stack]);
match interpret(context, subject, formula) {

View File

@ -86,8 +86,8 @@ impl Iterator for WarmEntry {
impl Warm {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Warm(Hamt::new())
pub fn new(stack: &mut NockStack) -> Self {
Warm(Hamt::new(stack))
}
fn insert(
@ -112,7 +112,7 @@ impl Warm {
}
pub fn init(stack: &mut NockStack, cold: &mut Cold, hot: &Hot) -> Self {
let mut warm = Self::new();
let mut warm = Self::new(stack);
for (mut path, axis, jet) in *hot {
let batteries_list = cold.find(stack, &mut path);
for batteries in batteries_list {

View File

@ -12,8 +12,10 @@ pub mod newt;
pub mod noun;
pub mod serf;
//pub mod bytecode;
pub mod persist;
pub mod serialization;
pub mod trace;
pub mod unifying_equality;
/** Introduce useful functions for debugging
*

View File

@ -5,7 +5,6 @@ use crate::noun::{Atom, Cell, CellMemory, IndirectAtom, Noun, NounAllocator};
use assert_no_alloc::permit_alloc;
use either::Either::{self, Left, Right};
use ibig::Stack;
use libc::{c_void, memcmp};
use memmap::MmapMut;
use std::alloc::Layout;
use std::mem;
@ -50,6 +49,7 @@ pub struct NockStack {
alloc_pointer: *mut u64,
/** MMap which must be kept alive as long as this NockStack is */
memory: MmapMut,
/** PMA from which we will copy into the NockStack */
/** Whether or not pre_copy() has been called on the current stack frame. */
pc: bool,
}
@ -142,6 +142,26 @@ impl NockStack {
self.frame_pointer
}
/** Current stack pointer of this NockStack */
pub fn get_stack_pointer(&self) -> *const u64 {
self.stack_pointer
}
/** Current alloc pointer of this NockStack */
pub fn get_alloc_pointer(&self) -> *const u64 {
self.alloc_pointer
}
/** Start of the memory range for this NockStack */
pub fn get_start(&self) -> *const u64 {
self.start
}
/** End of the memory range for this NockStack */
pub fn get_size(&self) -> usize {
self.size
}
/** Checks if the current stack frame has West polarity */
#[inline]
pub fn is_west(&self) -> bool {
@ -227,7 +247,7 @@ impl NockStack {
}
/** Pointer to where the previous stack pointer is saved in a frame */
unsafe fn prev_stack_pointer_pointer(&self) -> *mut *mut u64 {
pub unsafe fn prev_stack_pointer_pointer(&self) -> *mut *mut u64 {
if !self.pc {
self.slot_pointer(STACK) as *mut *mut u64
} else {
@ -816,240 +836,6 @@ impl NockStack {
}
}
#[cfg(feature = "check_junior")]
#[macro_export]
macro_rules! assert_no_junior_pointers {
( $x:expr, $y:expr ) => {
assert_no_alloc::permit_alloc(|| {
assert!($x.no_junior_pointers($y));
})
};
}
#[cfg(not(feature = "check_junior"))]
#[macro_export]
macro_rules! assert_no_junior_pointers {
( $x:expr, $y:expr ) => {};
}
pub unsafe fn unifying_equality(stack: &mut NockStack, a: *mut Noun, b: *mut Noun) -> bool {
/* This version of unifying equality is not like that of vere.
* Vere does a tree comparison (accelerated by pointer equality and short-circuited by mug
* equality) and then unifies the nouns at the top level if they are equal.
*
* Here we recursively attempt to unify nouns. Pointer-equal nouns are already unified.
* Disequal mugs again short-circuit the unification and equality check.
*
* Since we expect atoms to be normalized, direct and indirect atoms do not unify with each
* other. For direct atoms, no unification is possible as there is no pointer involved in their
* representation. Equality is simply direct equality on the word representation. Indirect
* atoms require equality first of the size and then of the memory buffers' contents.
*
* Cell equality is tested (after mug and pointer equality) by attempting to unify the heads and tails,
* respectively, of cells, and then re-testing. If unification succeeds then the heads and
* tails will be pointer-wise equal and the cell itself can be unified. A failed unification of
* the head or the tail will already short-circuit the unification/equality test, so we will
* not return to re-test the pointer equality.
*
* When actually mutating references for unification, we must be careful to respect seniority.
* A reference to a more junior noun should always be replaced with a reference to a more
* senior noun, *never vice versa*, to avoid introducing references from more senior frames
* into more junior frames, which would result in incorrect operation of the copier.
*/
assert_acyclic!(*a);
assert_acyclic!(*b);
assert_no_forwarding_pointers!(*a);
assert_no_forwarding_pointers!(*b);
assert_no_junior_pointers!(stack, *a);
assert_no_junior_pointers!(stack, *b);
// If the nouns are already word-equal we have nothing to do
if (*a).raw_equals(*b) {
return true;
};
// If the nouns have cached mugs which are disequal we have nothing to do
if let (Ok(a_alloc), Ok(b_alloc)) = ((*a).as_allocated(), (*b).as_allocated()) {
if let (Some(a_mug), Some(b_mug)) = (a_alloc.get_cached_mug(), b_alloc.get_cached_mug()) {
if a_mug != b_mug {
return false;
};
};
};
stack.frame_push(0);
*(stack.push::<(*mut Noun, *mut Noun)>()) = (a, b);
loop {
if stack.stack_is_empty() {
break;
};
let (x, y): (*mut Noun, *mut Noun) = *(stack.top());
if (*x).raw_equals(*y) {
stack.pop::<(*mut Noun, *mut Noun)>();
continue;
};
if let (Ok(x_alloc), Ok(y_alloc)) = (
// equal direct atoms return true for raw_equals()
(*x).as_allocated(),
(*y).as_allocated(),
) {
if let (Some(x_mug), Some(y_mug)) = (x_alloc.get_cached_mug(), y_alloc.get_cached_mug())
{
if x_mug != y_mug {
break; // short-circuit, the mugs differ therefore the nouns must differ
}
};
match (x_alloc.as_either(), y_alloc.as_either()) {
(Left(x_indirect), Left(y_indirect)) => {
let x_as_ptr = x_indirect.to_raw_pointer();
let y_as_ptr = y_indirect.to_raw_pointer();
if x_indirect.size() == y_indirect.size()
&& memcmp(
x_indirect.data_pointer() as *const c_void,
y_indirect.data_pointer() as *const c_void,
x_indirect.size() << 3,
) == 0
{
let (_senior, junior) = senior_pointer_first(stack, x_as_ptr, y_as_ptr);
if x_as_ptr == junior {
*x = *y;
} else {
*y = *x;
}
stack.pop::<(*mut Noun, *mut Noun)>();
continue;
} else {
break;
}
}
(Right(x_cell), Right(y_cell)) => {
let x_as_ptr = x_cell.to_raw_pointer() as *const u64;
let y_as_ptr = y_cell.to_raw_pointer() as *const u64;
if x_cell.head().raw_equals(y_cell.head())
&& x_cell.tail().raw_equals(y_cell.tail())
{
let (_senior, junior) = senior_pointer_first(stack, x_as_ptr, y_as_ptr);
if x_as_ptr == junior {
*x = *y;
} else {
*y = *x;
}
stack.pop::<(*mut Noun, *mut Noun)>();
continue;
} else {
/* THIS ISN'T AN INFINITE LOOP
* If we discover a disequality in either side, we will
* short-circuit the entire loop and reset the work stack.
*
* If both sides are equal, then we will discover pointer
* equality when we return and unify the cell.
*/
*(stack.push::<(*mut Noun, *mut Noun)>()) =
(x_cell.tail_as_mut(), y_cell.tail_as_mut());
*(stack.push::<(*mut Noun, *mut Noun)>()) =
(x_cell.head_as_mut(), y_cell.head_as_mut());
continue;
}
}
(_, _) => {
break; // cells don't unify with atoms
}
}
} else {
break; // direct atom not raw equal, so short circuit
}
}
stack.frame_pop();
assert_acyclic!(*a);
assert_acyclic!(*b);
assert_no_forwarding_pointers!(*a);
assert_no_forwarding_pointers!(*b);
assert_no_junior_pointers!(stack, *a);
assert_no_junior_pointers!(stack, *b);
(*a).raw_equals(*b)
}
unsafe fn senior_pointer_first(
stack: &NockStack,
a: *const u64,
b: *const u64,
) -> (*const u64, *const u64) {
let mut frame_pointer: *const u64 = stack.frame_pointer;
let mut stack_pointer: *const u64 = stack.stack_pointer;
let mut alloc_pointer: *const u64 = stack.alloc_pointer;
let prev_stack_pointer = *(stack.prev_stack_pointer_pointer());
let (mut high_pointer, mut low_pointer): (*const u64, *const u64) = if stack.is_west() {
(prev_stack_pointer, alloc_pointer)
} else {
(alloc_pointer, prev_stack_pointer)
};
loop {
if low_pointer.is_null() || high_pointer.is_null() {
// we found the bottom of the stack; check entirety of the stack
low_pointer = stack.start;
high_pointer = stack.start.add(stack.size);
}
match (
a < high_pointer && a >= low_pointer,
b < high_pointer && b >= low_pointer,
) {
(true, true) => {
// both pointers are in the same frame, pick arbitrarily (lower in mem)
break lower_pointer_first(a, b);
}
(true, false) => break (b, a), // a is in the frame, b is not, so b is senior
(false, true) => break (a, b), // b is in the frame, a is not, so a is senior
(false, false) => {
// chase up the stack
#[allow(clippy::comparison_chain)]
// test to see if the frame under consideration is a west frame
if stack_pointer < alloc_pointer {
stack_pointer = *(frame_pointer.sub(STACK + 1)) as *const u64;
alloc_pointer = *(frame_pointer.sub(ALLOC + 1)) as *const u64;
frame_pointer = *(frame_pointer.sub(FRAME + 1)) as *const u64;
// both pointers are in the PMA, pick arbitrarily (lower in mem)
if frame_pointer.is_null() {
break lower_pointer_first(a, b);
};
// previous allocation pointer
high_pointer = alloc_pointer;
// "previous previous" stack pointer. this is the other boundary of the previous allocation arena
low_pointer = *(frame_pointer.add(STACK)) as *const u64;
} else if stack_pointer > alloc_pointer {
stack_pointer = *(frame_pointer.add(STACK)) as *const u64;
alloc_pointer = *(frame_pointer.add(ALLOC)) as *const u64;
frame_pointer = *(frame_pointer.add(FRAME)) as *const u64;
// both pointers are in the PMA, pick arbitrarily (lower in mem)
if frame_pointer.is_null() {
break lower_pointer_first(a, b);
};
// previous allocation pointer
low_pointer = alloc_pointer;
// "previous previous" stack pointer. this is the other boundary of the previous allocation arena
high_pointer = *(frame_pointer.sub(STACK + 1)) as *const u64;
} else {
panic!("senior_pointer_first: stack_pointer == alloc_pointer");
}
}
}
}
}
fn lower_pointer_first(a: *const u64, b: *const u64) -> (*const u64, *const u64) {
if a < b {
(a, b)
} else {
(b, a)
}
}
impl NounAllocator for NockStack {
unsafe fn alloc_indirect(&mut self, words: usize) -> *mut u64 {
self.indirect_alloc(words)

View File

@ -441,6 +441,11 @@ impl IndirectAtom {
unsafe { *(self.to_raw_pointer().add(1)) as usize }
}
/** Memory size of an indirect atom (including size + metadata fields) in 64-bit words */
pub fn raw_size(&self) -> usize {
self.size() + 2
}
pub fn bit_size(&self) -> usize {
unsafe {
((self.size() - 1) << 6) + 64
@ -889,6 +894,21 @@ impl Atom {
*self
}
}
/** Make an atom from a raw u64
*
* # Safety
*
* Note that the [u64] parameter is *not*, in general, the value of the atom!
*
* In particular, anything with the high bit set will be treated as a tagged pointer.
* This method is only to be used to restore an atom from the raw [u64] representation
* returned by [Noun::as_raw], and should only be used if we are sure the restored noun is in
* fact an atom.
*/
pub unsafe fn from_raw(raw: u64) -> Atom {
Atom { raw }
}
}
impl fmt::Display for Atom {

311
rust/ares/src/persist.rs Normal file
View File

@ -0,0 +1,311 @@
use crate::mem::NockStack;
use crate::noun::{Allocated, Atom, Cell, CellMemory, IndirectAtom, Noun};
use ares_pma::*;
use either::Either::{Left, Right};
use std::convert::TryInto;
use std::ffi::{c_void, CString};
use std::mem::size_of;
use std::path::PathBuf;
use std::ptr::copy_nonoverlapping;
use std::sync::OnceLock;
const PMA_MODE: mode_t = 0o600; // RW for user only
const PMA_FLAGS: ULONG = 0; // ignored for now
const NOUN_MARKED: u64 = 1 << 63;
/// Handle to a PMA
#[derive(Copy, Clone)]
struct PMAState(u64); // this is idiotic but necessary for Rust to let us put this in a oncelock
static PMA: OnceLock<PMAState> = OnceLock::new();
fn get_pma_state() -> Option<*mut BT_state> {
PMA.get().map(|r| r.0 as *mut BT_state)
}
fn pma_state_err() -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::AlreadyExists, "PMA")
}
#[cfg(unix)]
pub fn pma_open(path: PathBuf) -> Result<(), std::io::Error> {
let mut state: *mut BT_state = std::ptr::null_mut();
// correct for Unix thus cfg gated
let path_cstring = CString::new(path.into_os_string().as_encoded_bytes())?;
unsafe {
bt_state_new(&mut state);
let err = bt_state_open(state, path_cstring.as_ptr(), PMA_FLAGS, PMA_MODE);
if err == 0 {
PMA.set(PMAState(state as u64))
.map_err(|state| state.0 as *mut BT_state)
.expect("PMA state already initialized to:");
assert!(get_pma_state().is_some());
Ok(())
} else {
// XX need to free the state
Err(std::io::Error::from_raw_os_error(err))
}
}
}
#[cfg(windows)]
pub fn pma_open(path: PathBuf) -> Result<Self, std::io::Error> {
unimplemented!()
}
pub fn pma_close() -> Result<(), std::io::Error> {
// XX need a way to free the state after
let err = unsafe { bt_state_close(get_pma_state().ok_or_else(pma_state_err)?) };
if err == 0 {
Ok(())
} else {
Err(std::io::Error::from_raw_os_error(err))
}
}
#[inline]
pub fn pma_meta_get(field: usize) -> u64 {
unsafe { bt_meta_get(get_pma_state().unwrap(), field) }
}
#[inline]
pub fn pma_meta_set(field: usize, val: u64) {
unsafe { bt_meta_set(get_pma_state().unwrap(), field, val) };
}
pub unsafe fn pma_contains<T>(ptr: *const T, count: usize) -> bool {
if let Some(pma_state) = get_pma_state() {
bt_inbounds(pma_state, ptr as *mut c_void) != 0
&& bt_inbounds(pma_state, ptr.add(count) as *mut c_void) != 0
} else {
false
}
}
pub fn pma_sync() {
unsafe {
if bt_sync(get_pma_state().unwrap()) != 0 {
panic!("PMA sync failed but did not abort: this should never happen.");
}
}
}
pub unsafe fn pma_dirty<T>(ptr: *mut T, count: usize) {
let lo = bt_page_round_down(ptr);
let hi = bt_page_round_up(ptr.add(count));
let e = bt_dirty(get_pma_state().unwrap(), lo, hi);
assert!(e == 0);
}
/**
* This trait defines operations for copying a structure into the PMA.
*
* This is done in two phases. The [space_needed] phase counts how much space the structure needs in
* the PMA, not counting referenced structures already in the PMA. Then a buffer is allocated in
* the PMA of at least the computed size, and the [copy_to_buffer] phase copies the structure into
* this buffer.
*
* The phases are separated so that instances of the trait may compose, while still allocating a
* single buffer. Thus, in the instance for a HAMT, the [space_needed] method for the HAMT will
* call the [space_needed] method on each noun key, and on each value, as well as computing the
* size of the HAMT's own structures. Similarly, the [copy_to_buffer] method for the HAMT will call
* the [copy_to_buffer] method for the keys and values as it copies its own structures in.
*/
pub trait Persist {
/// Count how much space is needed, in bytes. May set marks so long as marks are cleaned up by
/// [copy_into_buffer]
unsafe fn space_needed(&mut self, stack: &mut NockStack) -> usize;
/// Copy into the provided buffer, which may be assumed to be at least as large as the size
/// returned by [space_needed] on the same structure.
unsafe fn copy_to_buffer(&mut self, stack: &mut NockStack, buffer: &mut *mut u8);
/// Persist an object into the PMA using [space_needed] and [copy_to_buffer], returning
/// a [u64] (probably a pointer or tagged pointer) that can be saved into metadata.
unsafe fn save_to_pma(&mut self, stack: &mut NockStack) -> u64 {
unsafe {
let space = self.space_needed(stack);
if space == 0 {
return self.handle_to_u64();
}
let space_as_pages = (space + (BT_PAGESIZE as usize - 1)) >> BT_PAGEBITS;
let mut buffer = bt_malloc(get_pma_state().unwrap(), space_as_pages) as *mut u8;
let orig_buffer = buffer;
self.copy_to_buffer(stack, &mut buffer);
let space_isize: isize = space.try_into().unwrap();
assert!(buffer.offset_from(orig_buffer) == space_isize);
self.handle_to_u64()
}
}
unsafe fn handle_to_u64(&self) -> u64;
unsafe fn handle_from_u64(meta_handle: u64) -> Self;
}
/// Ensure an allocated noun is marked and return if it was already marked
unsafe fn mark(a: Allocated) -> bool {
let metadata = a.get_metadata();
a.set_metadata(metadata | NOUN_MARKED);
metadata & NOUN_MARKED != 0
}
/// Unmark an allocated noun
unsafe fn unmark(a: Allocated) {
let metadata = a.get_metadata();
a.set_metadata(metadata & !NOUN_MARKED);
}
impl Persist for Atom {
unsafe fn space_needed(&mut self, _stack: &mut NockStack) -> usize {
if let Ok(indirect) = self.as_indirect() {
let count = indirect.raw_size();
if !pma_contains(indirect.to_raw_pointer(), count) && !mark(indirect.as_allocated()) {
return count * size_of::<u64>();
}
}
0
}
unsafe fn copy_to_buffer(&mut self, _stack: &mut NockStack, buffer: &mut *mut u8) {
if let Ok(mut indirect) = self.as_indirect() {
let count = indirect.raw_size();
if !pma_contains(indirect.to_raw_pointer(), count) {
if let Some(forward) = indirect.forwarding_pointer() {
*self = forward.as_atom();
} else {
let indirect_buffer_ptr = *buffer as *mut u64;
copy_nonoverlapping(indirect.to_raw_pointer(), indirect_buffer_ptr, count);
*buffer = indirect_buffer_ptr.add(count) as *mut u8;
indirect.set_forwarding_pointer(indirect_buffer_ptr);
*self = IndirectAtom::from_raw_pointer(indirect_buffer_ptr).as_atom();
}
}
}
}
unsafe fn handle_to_u64(&self) -> u64 {
self.as_noun().as_raw()
}
unsafe fn handle_from_u64(meta_handle: u64) -> Self {
Atom::from_raw(meta_handle)
}
}
impl Persist for Noun {
unsafe fn space_needed(&mut self, stack: &mut NockStack) -> usize {
let mut space = 0usize;
stack.frame_push(0);
*(stack.push::<Noun>()) = *self;
loop {
if stack.stack_is_empty() {
break;
}
let noun = *(stack.top::<Noun>());
stack.pop::<Noun>();
match noun.as_either_atom_cell() {
Left(mut atom) => {
space += atom.space_needed(stack);
}
Right(cell) => {
if !pma_contains(cell.to_raw_pointer(), 1) && !mark(cell.as_allocated()) {
space += size_of::<CellMemory>();
(*stack.push::<Noun>()) = cell.tail();
(*stack.push::<Noun>()) = cell.head();
}
}
}
}
stack.frame_pop();
space
}
unsafe fn copy_to_buffer(&mut self, stack: &mut NockStack, buffer: &mut *mut u8) {
let mut buffer_u64 = (*buffer) as *mut u64;
stack.frame_push(0);
*(stack.push::<*mut Noun>()) = self as *mut Noun;
loop {
if stack.stack_is_empty() {
break;
}
let dest = *(stack.top::<*mut Noun>());
stack.pop::<*mut Noun>();
match (*dest).as_either_direct_allocated() {
Left(_direct) => {}
Right(allocated) => {
if let Some(a) = allocated.forwarding_pointer() {
*dest = a.as_noun();
continue;
}
match allocated.as_either() {
Left(mut indirect) => {
let count = indirect.raw_size();
if pma_contains(indirect.to_raw_pointer(), count) {
continue;
}
unmark(allocated);
copy_nonoverlapping(indirect.to_raw_pointer(), buffer_u64, count);
indirect.set_forwarding_pointer(buffer_u64);
*dest = IndirectAtom::from_raw_pointer(buffer_u64).as_noun();
buffer_u64 = buffer_u64.add(count);
}
Right(mut cell) => {
if pma_contains(cell.to_raw_pointer(), 1) {
continue;
}
unmark(allocated);
let new_cell_mem = buffer_u64 as *mut CellMemory;
copy_nonoverlapping(cell.to_raw_pointer(), new_cell_mem, 1);
cell.set_forwarding_pointer(new_cell_mem);
*dest = Cell::from_raw_pointer(new_cell_mem).as_noun();
*(stack.push::<*mut Noun>()) = &mut (*new_cell_mem).tail;
*(stack.push::<*mut Noun>()) = &mut (*new_cell_mem).head;
buffer_u64 = new_cell_mem.add(1) as *mut u64;
}
}
}
}
}
*buffer = buffer_u64 as *mut u8;
stack.frame_pop();
}
unsafe fn handle_to_u64(&self) -> u64 {
self.as_raw()
}
unsafe fn handle_from_u64(meta_handle: u64) -> Self {
Noun::from_raw(meta_handle)
}
}
/** Mask to mask out pointer bits not aligned with a BT_PAGESIZE page */
const BT_PAGEBITS_MASK_OUT: u64 = !((1 << BT_PAGEBITS) - 1);
// round an address down to a page boundary
fn bt_page_round_down<T>(ptr: *mut T) -> *mut c_void {
((ptr as u64) & BT_PAGEBITS_MASK_OUT) as *mut c_void
}
// round an address up to a page boundary
fn bt_page_round_up<T>(ptr: *mut T) -> *mut c_void {
(((ptr as u64) + (BT_PAGESIZE as u64) - 1) & BT_PAGEBITS_MASK_OUT) as *mut c_void
}

View File

@ -1,8 +0,0 @@
## PMA - TODO
Ported from development in a
[separate repo](https://github.com/ashelkovnykov/pma_malloc). README will be
updated after the final implementation is complete, which replaces the
array-based page directory with a B+ Tree one. Until then, please refer to the
README in the above-linked directory.

File diff suppressed because it is too large Load Diff

View File

@ -1,118 +0,0 @@
/**
* Persistent Memory Arena for the New Mars Nock virtualization engine.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//==============================================================================
// PROTOTYPES
//==============================================================================
/**
* Struct returned from pma_load()
*/
typedef struct PMARootState PMARootState;
struct PMARootState {
uint64_t epoch; // Epoch ID of the most recently processed event
uint64_t event; // ID of the most recently processed event
uint64_t root; // Root after most recent event
};
/**
* Initialize a brand new PMA environment and event snapshot
*
* @param path File directory in which to create backing files for snapshot and
* page directory
*
* @return 0 success
* @return -1 failure; errno set to error code
*/
int
pma_init(const char *path);
/**
* Load an existing PMA environment and event snapshot
*
* @param path File directory from which to load the backing files for the
* snapshot and page directory
*
* @return 0 success
* @return -1 failure; errno set to error code
*/
PMARootState
pma_load(const char *path);
/**
* Safely unload the PMA after syncing changes to PMA state
*
* @param epoch Epoch of latest event successfully applied to state snapshot
* @param event Event number of latest event successfully applied to state
* snapshot
*
* @return 0 success
* @return -1 failure; errno set to error code
*/
int
pma_close(uint64_t epoch, uint64_t event, uint64_t root);
/**
* Allocate a new block of memory in the PMA
*
* @param size Size in bytes to allocate
*
* @return NULL failure; errno set to error code
* @return void* address of the newly allocated memory
*/
void *
pma_malloc(size_t size);
/**
* Deallocate an existing block of memory in the PMA
*
* @param address Address of block to deallocated
*
* @return 0 success
* @return -1 failure; errno set to error code
*/
int
pma_free(void *address);
/**
* Sync changes to PMA state
*
* @param epoch Epoch of latest event successfully applied to state snapshot
* @param event Event number of latest event successfully applied to state
* snapshot
*
* @return 0 success
* @return -1 failure; errno set to error code
*/
int
pma_sync(uint64_t epoch, uint64_t event, uint64_t root);
/**
* True if the address is in the PMA
*/
bool
pma_in_arena(void *address);
/*
bp(X) where X is false will raise a SIGTRAP. If the process is being run
inside a debugger, this can be caught and ignored. It's equivalent to a
breakpoint. If run without a debugger, it will dump core, like an assert
*/
#if defined(__i386__) || defined(__x86_64__)
#define bp(x) do { if(!(x)) __asm__ volatile("int $3"); } while (0)
#elif defined(__thumb__)
#define bp(x) do { if(!(x)) __asm__ volatile(".inst 0xde01"); } while (0)
#elif defined(__aarch64__)
#define bp(x) do { if(!(x)) __asm__ volatile(".inst 0xd4200000"); } while (0)
#elif defined(__arm__)
#define bp(x) do { if(!(x)) __asm__ volatile(".inst 0xe7f001f0"); } while (0)
#else
STATIC_ASSERT(0, "debugger break instruction unimplemented");
#endif

View File

@ -1,198 +0,0 @@
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
//==============================================================================
// MACROS
//==============================================================================
#define PMA_PAGE_SHIFT 12U
#define PMA_MIN_ALLOC_SHIFT 4U
#define PMA_BITMAP_BITS (8 * sizeof(uint8_t))
#define PMA_SNAPSHOT_RESIZE_INC 0x100000000
#define PMA_PAGE_SIZE (1UL << PMA_PAGE_SHIFT)
#define PMA_PAGE_MASK (PMA_PAGE_SIZE - 1)
#define PMA_MIN_ALLOC_SIZE (1U << PMA_MIN_ALLOC_SHIFT)
#define PMA_MAX_SHARED_SHIFT (PMA_PAGE_SHIFT - 2U)
#define PMA_MAX_SHARED_ALLOC (1UL << PMA_MAX_SHARED_SHIFT)
#define PMA_SHARED_BUCKETS (PMA_MAX_SHARED_SHIFT - PMA_MIN_ALLOC_SHIFT + 1)
#define PAGE_ROUND_DOWN(foo) (foo & (~PMA_PAGE_MASK))
#define PAGE_ROUND_UP(foo) ((foo + PMA_PAGE_MASK) & (~PMA_PAGE_MASK))
#define PTR_TO_INDEX(foo) ((((uint64_t)(foo)) - ((uint64_t)_pma_state->metadata->arena_start)) >> PMA_PAGE_SHIFT)
#define INDEX_TO_PTR(foo) (void *)((char *)_pma_state->metadata->arena_start + ((foo) * PMA_PAGE_SIZE))
#ifdef __linux__
#define PMA_MMAP_FLAGS (MAP_SHARED | MAP_FIXED_NOREPLACE)
#else
#define PMA_MMAP_FLAGS (MAP_SHARED | MAP_FIXED)
#endif
#define PMA_MAGIC_CODE 0xBADDECAFC0FFEE00 // i.e. all decaf coffee
#define PMA_DATA_VERSION 1
#define PMA_EMPTY_BITMAP 0xFF
#define PMA_BITMAP_SIZE 32
#define PMA_DPAGE_CACHE_SIZE ((PMA_PAGE_SIZE - sizeof(PMADPageCache)) / sizeof(uint64_t))
#define PMA_DIRTY_PAGE_LIMIT 164
#define PMA_SNAPSHOT_FILENAME "snap.bin"
#define PMA_PAGE_DIR_FILENAME "page.bin"
#define PMA_DEFAULT_DIR_NAME ".bin"
#define PMA_NEW_FILE_FLAGS (O_RDWR | O_CREAT)
#define PMA_LOAD_FILE_FLAGS (O_RDWR
#define PMA_DIR_PERMISSIONS (S_IRWXU | S_IRWXG | S_IRWXO)
#define PMA_FILE_PERMISSIONS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
#define PMA_INIT_SNAP_SIZE 0x40000000
#define PMA_INIT_DIR_SIZE 0x400000
#define PMA_MAXIMUM_DIR_SIZE 0x5500000000
#ifdef __linux__
#define PMA_SNAPSHOT_ADDR 0x10000
#else
#define PMA_SNAPSHOT_ADDR 0x28000000000
#endif
#define PMA_MAX_DISK_FILE_SIZE 0x100000000000
#define PMA_MAX_RESIZE_FACTOR (PMA_MAX_DISK_FILE_SIZE / PMA_SNAPSHOT_RESIZE_INC)
//==============================================================================
// TYPES
//==============================================================================
enum PMAPageStatus {
UNALLOCATED,
FREE,
SHARED,
FIRST,
FOLLOW
};
typedef enum PMAPageStatus PMAPageStatus;
typedef struct PMAPageDirEntry PMAPageDirEntry;
struct PMAPageDirEntry {
uint64_t offset;
PMAPageStatus status;
};
typedef struct PMAPageDir PMAPageDir;
struct PMAPageDir {
uint64_t size;
uint64_t next_index;
PMAPageDirEntry *entries;
};
typedef struct PMASharedPageHeader PMASharedPageHeader;
struct PMASharedPageHeader {
struct PMASharedPageHeader *next;
uint8_t dirty;
uint8_t size;
uint8_t free;
uint8_t bits[PMA_BITMAP_SIZE];
};
typedef struct PMADirtyPageEntry PMADirtyPageEntry;
struct PMADirtyPageEntry {
uint64_t index;
uint64_t offset;
uint32_t num_pages;
PMAPageStatus status;
};
typedef struct PMASinglePageCache PMASinglePageCache;
struct PMASinglePageCache {
PMASinglePageCache *next;
void *page;
};
typedef struct PMAPageRunCache PMAPageRunCache;
struct PMAPageRunCache {
PMAPageRunCache *next;
void *page;
uint64_t length;
};
typedef struct PMADPageCache PMADPageCache;
struct PMADPageCache {
uint8_t dirty;
uint16_t size;
uint16_t head;
uint16_t tail;
uint64_t queue[];
};
typedef struct PMAMetadata PMAMetadata;
struct PMAMetadata {
uint64_t magic_code;
uint32_t checksum;
uint32_t version;
uint64_t epoch;
uint64_t event;
uint64_t root;
void *arena_start;
void *arena_end;
PMASharedPageHeader *shared_pages[PMA_SHARED_BUCKETS];
PMADPageCache *dpage_cache;
uint64_t snapshot_size;
uint64_t next_offset;
uint8_t num_dirty_pages;
uint64_t padding[2];
PMADirtyPageEntry dirty_pages[PMA_DIRTY_PAGE_LIMIT];
};
static_assert(sizeof(PMAMetadata) == PMA_PAGE_SIZE, "PMAMetadata must be a page in length");
typedef struct PMAState PMAState;
struct PMAState {
PMAMetadata *metadata;
uint64_t meta_page_offset;
PMAPageDir page_directory;
int snapshot_fd;
int page_dir_fd;
PMASinglePageCache *free_pages;
PMAPageRunCache *free_page_runs;
};
//==============================================================================
// GLOBALS
//==============================================================================
extern PMAState *_pma_state;
//==============================================================================
// FUNCTIONS
//==============================================================================
int _pma_verify_checksum(PMAMetadata *meta_page);
int _pma_sync_dirty_pages(int fd, uint8_t num_dirty_pages, PMADirtyPageEntry *dirty_pages);
int _pma_write_page_status(int fd, uint64_t index, PMAPageStatus status);
int _pma_write_page_offset(int fd, uint64_t index, uint64_t offset);
int _pma_update_free_pages(uint8_t num_dirty_pages, PMADirtyPageEntry *dirty_pages);
void *_pma_malloc_bytes(size_t size);
int _pma_malloc_shared_page(uint8_t bucket);
void *_pma_malloc_pages(size_t size);
void *_pma_malloc_single_page(PMAPageStatus status);
void *_pma_malloc_multi_pages(uint64_t num_pages);
void *_pma_get_cached_pages(uint64_t num_pages);
void *_pma_get_new_page(PMAPageStatus status);
void *_pma_get_new_pages(uint64_t num_pages);
int _pma_free_pages(void *address);
int _pma_free_bytes(void *address);
int _pma_copy_shared_page(void *address);
uint64_t _pma_get_single_dpage(void);
uint64_t _pma_get_cached_dpage(void);
int _pma_copy_dpage_cache(void);
uint64_t _pma_get_disk_dpage(void);
void _pma_copy_page(void *address, uint64_t offset, PMAPageStatus status, int fd);
void _pma_mark_page_dirty(uint64_t index, uint64_t offset, PMAPageStatus status, uint32_t num_pages);
int _pma_extend_snapshot_file(uint32_t multiplier);
void _pma_warning(const char *p, void *a, int l);
void _pma_state_free(void);
int _pma_state_malloc(void);

File diff suppressed because it is too large Load Diff

View File

@ -10,13 +10,16 @@ use crate::mem::NockStack;
use crate::mug::*;
use crate::newt::Newt;
use crate::noun::{Atom, Cell, DirectAtom, Noun, Slots, D, T};
use crate::persist::pma_meta_set;
use crate::persist::{pma_meta_get, pma_open, pma_sync, Persist};
use crate::trace::*;
use ares_macros::tas;
use signal_hook;
use signal_hook::consts::SIGINT;
use std::fs::create_dir_all;
use std::io;
use std::path::{Path, PathBuf};
use std::mem::size_of;
use std::path::PathBuf;
use std::result::Result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@ -26,6 +29,57 @@ crate::gdb!();
const FLAG_TRACE: u32 = 1 << 8;
#[repr(usize)]
enum BTMetaField {
SnapshotVersion = 0,
Snapshot = 1,
}
struct Snapshot(pub *mut SnapshotMem);
impl Persist for Snapshot {
unsafe fn space_needed(&mut self, stack: &mut NockStack) -> usize {
let mut arvo = (*(self.0)).arvo;
let mut cold = (*(self.0)).cold;
let arvo_space_needed = arvo.space_needed(stack);
let cold_space_needed = cold.space_needed(stack);
(((size_of::<SnapshotMem>() + 7) >> 3) << 3) + arvo_space_needed + cold_space_needed
}
unsafe fn copy_to_buffer(&mut self, stack: &mut NockStack, buffer: &mut *mut u8) {
let snapshot_buffer = *buffer as *mut SnapshotMem;
std::ptr::copy_nonoverlapping(self.0, snapshot_buffer, 1);
*self = Snapshot(snapshot_buffer);
*buffer = snapshot_buffer.add(1) as *mut u8;
let mut arvo = (*snapshot_buffer).arvo;
arvo.copy_to_buffer(stack, buffer);
(*snapshot_buffer).arvo = arvo;
let mut cold = (*snapshot_buffer).cold;
cold.copy_to_buffer(stack, buffer);
(*snapshot_buffer).cold = cold;
}
unsafe fn handle_to_u64(&self) -> u64 {
self.0 as u64
}
unsafe fn handle_from_u64(meta_handle: u64) -> Self {
Snapshot(meta_handle as *mut SnapshotMem)
}
}
#[repr(C)]
#[repr(packed)]
struct SnapshotMem {
pub epoch: u64,
pub event_num: u64,
pub arvo: Noun,
pub cold: Cold,
}
const PMA_CURRENT_SNAPSHOT_VERSION: u64 = 1;
struct Context {
epoch: u64,
event_num: u64,
@ -35,27 +89,87 @@ struct Context {
}
impl Context {
pub fn new(
_snap_path: &Path,
pub fn load(
snap_path: PathBuf,
trace_info: Option<TraceInfo>,
constant_hot_state: &[HotEntry],
) -> Context {
pma_open(snap_path).expect("serf: pma open failed");
let snapshot_version = pma_meta_get(BTMetaField::SnapshotVersion as usize);
let snapshot = match snapshot_version {
0 => None,
1 => Some(unsafe {
Snapshot::handle_from_u64(pma_meta_get(BTMetaField::Snapshot as usize))
}),
_ => panic!("Unsupported snapshot version"),
};
Context::new(trace_info, snapshot, constant_hot_state)
}
pub unsafe fn save(&mut self) {
let handle = {
let mut snapshot = Snapshot({
let snapshot_mem_ptr: *mut SnapshotMem = self.nock_context.stack.struct_alloc(1);
// Save into PMA (does not sync)
(*snapshot_mem_ptr).epoch = self.epoch;
(*snapshot_mem_ptr).event_num = self.event_num;
(*snapshot_mem_ptr).arvo = self.arvo;
(*snapshot_mem_ptr).cold = self.nock_context.cold;
snapshot_mem_ptr
});
let handle = snapshot.save_to_pma(&mut self.nock_context.stack);
self.epoch = (*snapshot.0).epoch;
self.arvo = (*snapshot.0).arvo;
self.event_num = (*snapshot.0).event_num;
self.nock_context.cold = (*snapshot.0).cold;
handle
};
pma_meta_set(
BTMetaField::SnapshotVersion as usize,
PMA_CURRENT_SNAPSHOT_VERSION,
);
pma_meta_set(BTMetaField::Snapshot as usize, handle);
}
fn new(
trace_info: Option<TraceInfo>,
snapshot: Option<Snapshot>,
constant_hot_state: &[HotEntry],
) -> Self {
// TODO: switch to Pma when ready
let mut stack = NockStack::new(512 << 10 << 10, 0);
let mut stack = NockStack::new(1024 << 10 << 10, 0);
let newt = Newt::new();
let cache = Hamt::<Noun>::new(&mut stack);
let (epoch, event_num, arvo, mut cold) = unsafe {
match snapshot {
Some(snapshot) => (
(*(snapshot.0)).epoch,
(*(snapshot.0)).event_num,
(*(snapshot.0)).arvo,
(*(snapshot.0)).cold,
),
None => (0, 0, D(0), Cold::new(&mut stack)),
}
};
let cold = Cold::new(&mut stack);
let hot = Hot::init(&mut stack, constant_hot_state);
let (epoch, event_num, arvo) = (0, 0, D(0));
let warm = Warm::init(&mut stack, &mut cold, &hot);
let mug = mug_u32(&mut stack, arvo);
let nock_context = interpreter::Context {
stack,
newt: Newt::new(),
newt,
cold,
warm: Warm::new(),
warm,
hot,
cache: Hamt::<Noun>::new(),
cache,
scry_stack: D(0),
trace_info,
};
@ -73,20 +187,35 @@ impl Context {
// Setters
//
pub fn event_update(&mut self, new_event_num: u64, new_arvo: Noun) {
///
/// ## Safety
///
/// calls save(), which invalidates all nouns not in the context
/// until [preserve_event_update_leftovers] is called to resolve forwarding pointers.
pub unsafe fn event_update(&mut self, new_event_num: u64, new_arvo: Noun) {
// XX: assert event numbers are continuous
self.arvo = new_arvo;
self.event_num = new_event_num;
self.save();
self.nock_context.cache = Hamt::new(&mut self.nock_context.stack);
self.nock_context.scry_stack = D(0);
// XX save to PMA
self.mug = mug_u32(&mut self.nock_context.stack, self.arvo);
}
//
// Snapshot functions
//
pub fn sync(&mut self) {
// TODO actually sync
eprintln!("serf: TODO sync");
///
/// ## Safety
///
/// Preserves nouns and jet states in context and then calls [flip_top_frame].
/// Other stack-allocated objects needing preservation should be preserved between
/// [event_update] and invocation of this function
pub unsafe fn preserve_event_update_leftovers(&mut self) {
let stack = &mut self.nock_context.stack;
stack.preserve(&mut self.nock_context.warm);
stack.preserve(&mut self.nock_context.hot);
stack.flip_top_frame(0);
}
//
@ -208,13 +337,13 @@ pub fn serf(constant_hot_state: &[HotEntry]) -> io::Result<()> {
}
}
let mut context = Context::new(&snap_path, trace_info, constant_hot_state);
let mut context = Context::load(snap_path, trace_info, constant_hot_state);
context.ripe();
// Can't use for loop because it borrows newt
while let Some(writ) = context.next() {
// Reset the local cache and scry handler stack
context.nock_context.cache = Hamt::<Noun>::new();
context.nock_context.cache = Hamt::<Noun>::new(&mut context.nock_context.stack);
context.nock_context.scry_stack = D(0);
let tag = slot(writ, 2)?.as_direct().unwrap();
@ -229,8 +358,7 @@ pub fn serf(constant_hot_state: &[HotEntry]) -> io::Result<()> {
}
tas!(b"save") => {
// XX what is eve for?
eprintln!("\r %save");
context.sync();
pma_sync();
}
tas!(b"meld") => eprintln!("\r %meld: not implemented"),
tas!(b"pack") => eprintln!("\r %pack: not implemented"),
@ -261,18 +389,6 @@ pub fn serf(constant_hot_state: &[HotEntry]) -> io::Result<()> {
};
clear_interrupt();
// Persist data that should survive between events
// XX: Such data should go in the PMA once that's available, except
// the warm and hot state which should survive between events but not interpreter runs
unsafe {
let stack = &mut context.nock_context.stack;
stack.preserve(&mut context.arvo);
stack.preserve(&mut context.nock_context.cold);
stack.preserve(&mut context.nock_context.warm);
stack.preserve(&mut context.nock_context.hot);
stack.flip_top_frame(0);
}
}
Ok(())
@ -365,7 +481,10 @@ fn play_life(context: &mut Context, eve: Noun) {
let eved = lent(eve).expect("serf: play: boot event number failure") as u64;
let arvo = slot(gat, 7).expect("serf: play: lifecycle didn't return initial Arvo");
context.event_update(eved, arvo);
unsafe {
context.event_update(eved, arvo);
context.preserve_event_update_leftovers();
}
context.play_done();
}
Err(error) => match error {
@ -384,6 +503,7 @@ fn play_list(context: &mut Context, mut lit: Noun) {
let mut eve = context.event_num;
while let Ok(cell) = lit.as_cell() {
let ovo = cell.head();
lit = cell.tail();
let trace_name = if context.nock_context.trace_info.is_some() {
Some(format!("play [{}]", eve))
} else {
@ -398,13 +518,16 @@ fn play_list(context: &mut Context, mut lit: Noun) {
.tail();
eve += 1;
context.event_update(eve, arvo);
unsafe {
context.event_update(eve, arvo);
context.nock_context.stack.preserve(&mut lit);
context.preserve_event_update_leftovers();
}
}
Err(goof) => {
return context.play_bail(goof);
}
}
lit = cell.tail();
}
context.play_done();
}
@ -427,10 +550,14 @@ fn work(context: &mut Context, job: Noun) {
match soft(context, job, trace_name) {
Ok(res) => {
let cell = res.as_cell().expect("serf: work: +slam returned atom");
let fec = cell.head();
let mut fec = cell.head();
let eve = context.event_num;
context.event_update(eve + 1, cell.tail());
unsafe {
context.event_update(eve + 1, cell.tail());
context.nock_context.stack.preserve(&mut fec);
context.preserve_event_update_leftovers();
}
context.work_done(fec);
}
Err(goof) => {
@ -447,14 +574,14 @@ fn work_swap(context: &mut Context, job: Noun, goof: Noun) {
clear_interrupt();
let stack = &mut context.nock_context.stack;
context.nock_context.cache = Hamt::<Noun>::new();
context.nock_context.cache = Hamt::<Noun>::new(stack);
// crud ovo = [+(now) [%$ %arvo ~] [%crud goof ovo]]
let job_cell = job.as_cell().expect("serf: work: job not a cell");
let job_now = job_cell.head().as_atom().expect("serf: work: now not atom");
let now = inc(stack, job_now).as_noun();
let wire = T(stack, &[D(0), D(tas!(b"arvo")), D(0)]);
let crud = DirectAtom::new_panic(tas!(b"crud"));
let ovo = T(stack, &[now, wire, crud.as_noun(), goof, job_cell.tail()]);
let mut ovo = T(stack, &[now, wire, crud.as_noun(), goof, job_cell.tail()]);
let trace_name = if context.nock_context.trace_info.is_some() {
Some(work_trace_name(
&mut context.nock_context.stack,
@ -468,10 +595,15 @@ fn work_swap(context: &mut Context, job: Noun, goof: Noun) {
match soft(context, ovo, trace_name) {
Ok(res) => {
let cell = res.as_cell().expect("serf: work: crud +slam returned atom");
let fec = cell.head();
let mut fec = cell.head();
let eve = context.event_num;
context.event_update(eve + 1, cell.tail());
unsafe {
context.event_update(eve + 1, cell.tail());
context.nock_context.stack.preserve(&mut ovo);
context.nock_context.stack.preserve(&mut fec);
context.preserve_event_update_leftovers();
}
context.work_swap(ovo, fec);
}
Err(goof_crud) => {

View File

@ -0,0 +1,254 @@
use crate::assert_acyclic;
use crate::assert_no_forwarding_pointers;
use crate::assert_no_junior_pointers;
use crate::mem::{NockStack, ALLOC, FRAME, STACK};
use crate::noun::Noun;
use crate::persist::{pma_contains, pma_dirty};
use either::Either::*;
use libc::{c_void, memcmp};
#[cfg(feature = "check_junior")]
#[macro_export]
macro_rules! assert_no_junior_pointers {
( $x:expr, $y:expr ) => {
assert_no_alloc::permit_alloc(|| {
assert!($x.no_junior_pointers($y));
})
};
}
#[cfg(not(feature = "check_junior"))]
#[macro_export]
macro_rules! assert_no_junior_pointers {
( $x:expr, $y:expr ) => {};
}
pub unsafe fn unifying_equality(stack: &mut NockStack, a: *mut Noun, b: *mut Noun) -> bool {
/* This version of unifying equality is not like that of vere.
* Vere does a tree comparison (accelerated by pointer equality and short-circuited by mug
* equality) and then unifies the nouns at the top level if they are equal.
*
* Here we recursively attempt to unify nouns. Pointer-equal nouns are already unified.
* Disequal mugs again short-circuit the unification and equality check.
*
* Since we expect atoms to be normalized, direct and indirect atoms do not unify with each
* other. For direct atoms, no unification is possible as there is no pointer involved in their
* representation. Equality is simply direct equality on the word representation. Indirect
* atoms require equality first of the size and then of the memory buffers' contents.
*
* Cell equality is tested (after mug and pointer equality) by attempting to unify the heads and tails,
* respectively, of cells, and then re-testing. If unification succeeds then the heads and
* tails will be pointer-wise equal and the cell itself can be unified. A failed unification of
* the head or the tail will already short-circuit the unification/equality test, so we will
* not return to re-test the pointer equality.
*
* When actually mutating references for unification, we must be careful to respect seniority.
* A reference to a more junior noun should always be replaced with a reference to a more
* senior noun, *never vice versa*, to avoid introducing references from more senior frames
* into more junior frames, which would result in incorrect operation of the copier.
*/
assert_acyclic!(*a);
assert_acyclic!(*b);
assert_no_forwarding_pointers!(*a);
assert_no_forwarding_pointers!(*b);
assert_no_junior_pointers!(stack, *a);
assert_no_junior_pointers!(stack, *b);
// If the nouns are already word-equal we have nothing to do
if (*a).raw_equals(*b) {
return true;
};
// If the nouns have cached mugs which are disequal we have nothing to do
if let (Ok(a_alloc), Ok(b_alloc)) = ((*a).as_allocated(), (*b).as_allocated()) {
if let (Some(a_mug), Some(b_mug)) = (a_alloc.get_cached_mug(), b_alloc.get_cached_mug()) {
if a_mug != b_mug {
return false;
};
};
};
stack.frame_push(0);
*(stack.push::<(*mut Noun, *mut Noun)>()) = (a, b);
loop {
if stack.stack_is_empty() {
break;
};
let (x, y): (*mut Noun, *mut Noun) = *(stack.top());
if (*x).raw_equals(*y) {
stack.pop::<(*mut Noun, *mut Noun)>();
continue;
};
if let (Ok(x_alloc), Ok(y_alloc)) = (
// equal direct atoms return true for raw_equals()
(*x).as_allocated(),
(*y).as_allocated(),
) {
if let (Some(x_mug), Some(y_mug)) = (x_alloc.get_cached_mug(), y_alloc.get_cached_mug())
{
if x_mug != y_mug {
break; // short-circuit, the mugs differ therefore the nouns must differ
}
};
match (x_alloc.as_either(), y_alloc.as_either()) {
(Left(x_indirect), Left(y_indirect)) => {
let x_as_ptr = x_indirect.to_raw_pointer();
let y_as_ptr = y_indirect.to_raw_pointer();
if x_indirect.size() == y_indirect.size()
&& memcmp(
x_indirect.data_pointer() as *const c_void,
y_indirect.data_pointer() as *const c_void,
x_indirect.size() << 3,
) == 0
{
let (_senior, junior) = senior_pointer_first(stack, x_as_ptr, y_as_ptr);
if x_as_ptr == junior {
if pma_contains(x, 1) {
pma_dirty(x, 1);
}
*x = *y;
} else {
if pma_contains(y, 1) {
pma_dirty(y, 1);
}
*y = *x;
}
stack.pop::<(*mut Noun, *mut Noun)>();
continue;
} else {
break;
}
}
(Right(x_cell), Right(y_cell)) => {
let x_as_ptr = x_cell.to_raw_pointer() as *const u64;
let y_as_ptr = y_cell.to_raw_pointer() as *const u64;
if x_cell.head().raw_equals(y_cell.head())
&& x_cell.tail().raw_equals(y_cell.tail())
{
let (_senior, junior) = senior_pointer_first(stack, x_as_ptr, y_as_ptr);
if x_as_ptr == junior {
if pma_contains(x, 1) {
pma_dirty(x, 1);
}
*x = *y;
} else {
if pma_contains(y, 1) {
pma_dirty(y, 1);
}
*y = *x;
}
stack.pop::<(*mut Noun, *mut Noun)>();
continue;
} else {
/* THIS ISN'T AN INFINITE LOOP
* If we discover a disequality in either side, we will
* short-circuit the entire loop and reset the work stack.
*
* If both sides are equal, then we will discover pointer
* equality when we return and unify the cell.
*/
*(stack.push::<(*mut Noun, *mut Noun)>()) =
(x_cell.tail_as_mut(), y_cell.tail_as_mut());
*(stack.push::<(*mut Noun, *mut Noun)>()) =
(x_cell.head_as_mut(), y_cell.head_as_mut());
continue;
}
}
(_, _) => {
break; // cells don't unify with atoms
}
}
} else {
break; // direct atom not raw equal, so short circuit
}
}
stack.frame_pop();
assert_acyclic!(*a);
assert_acyclic!(*b);
assert_no_forwarding_pointers!(*a);
assert_no_forwarding_pointers!(*b);
assert_no_junior_pointers!(stack, *a);
assert_no_junior_pointers!(stack, *b);
(*a).raw_equals(*b)
}
unsafe fn senior_pointer_first(
stack: &NockStack,
a: *const u64,
b: *const u64,
) -> (*const u64, *const u64) {
let mut frame_pointer: *const u64 = stack.get_frame_pointer();
let mut stack_pointer: *const u64 = stack.get_stack_pointer();
let mut alloc_pointer: *const u64 = stack.get_alloc_pointer();
let prev_stack_pointer = *(stack.prev_stack_pointer_pointer());
let (mut high_pointer, mut low_pointer): (*const u64, *const u64) = if stack.is_west() {
(prev_stack_pointer, alloc_pointer)
} else {
(alloc_pointer, prev_stack_pointer)
};
loop {
if low_pointer.is_null() || high_pointer.is_null() {
// we found the bottom of the stack; check entirety of the stack
low_pointer = stack.get_start();
high_pointer = stack.get_start().add(stack.get_size());
}
match (
a < high_pointer && a >= low_pointer,
b < high_pointer && b >= low_pointer,
) {
(true, true) => {
// both pointers are in the same frame, pick arbitrarily (lower in mem)
break lower_pointer_first(a, b);
}
(true, false) => break (b, a), // a is in the frame, b is not, so b is senior
(false, true) => break (a, b), // b is in the frame, a is not, so a is senior
(false, false) => {
// chase up the stack
#[allow(clippy::comparison_chain)]
// test to see if the frame under consideration is a west frame
if stack_pointer < alloc_pointer {
stack_pointer = *(frame_pointer.sub(STACK + 1)) as *const u64;
alloc_pointer = *(frame_pointer.sub(ALLOC + 1)) as *const u64;
frame_pointer = *(frame_pointer.sub(FRAME + 1)) as *const u64;
// both pointers are in the PMA, pick arbitrarily (lower in mem)
if frame_pointer.is_null() {
break lower_pointer_first(a, b);
};
// previous allocation pointer
high_pointer = alloc_pointer;
// "previous previous" stack pointer. this is the other boundary of the previous allocation arena
low_pointer = *(frame_pointer.add(STACK)) as *const u64;
} else if stack_pointer > alloc_pointer {
stack_pointer = *(frame_pointer.add(STACK)) as *const u64;
alloc_pointer = *(frame_pointer.add(ALLOC)) as *const u64;
frame_pointer = *(frame_pointer.add(FRAME)) as *const u64;
// both pointers are in the PMA, pick arbitrarily (lower in mem)
if frame_pointer.is_null() {
break lower_pointer_first(a, b);
};
// previous allocation pointer
low_pointer = alloc_pointer;
// "previous previous" stack pointer. this is the other boundary of the previous allocation arena
high_pointer = *(frame_pointer.sub(STACK + 1)) as *const u64;
} else {
panic!("senior_pointer_first: stack_pointer == alloc_pointer");
}
}
}
}
}
fn lower_pointer_first(a: *const u64, b: *const u64) -> (*const u64, *const u64) {
if a < b {
(a, b)
} else {
(b, a)
}
}

454
rust/ares_pma/Cargo.lock generated Normal file
View File

@ -0,0 +1,454 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "ares_pma"
version = "0.1.0"
dependencies = [
"bindgen",
"cc",
]
[[package]]
name = "bindgen"
version = "0.69.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
"which",
]
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "home"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "prettyplease"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "shlex"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "syn"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"

15
rust/ares_pma/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "ares_pma"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[build-dependencies]
bindgen = "0.69.1"
cc = "1.0"
[features]
debug_prints = []

83
rust/ares_pma/build.rs Normal file
View File

@ -0,0 +1,83 @@
extern crate bindgen;
use std::env;
use std::path::PathBuf;
fn main() {
let opt_level = env::var("OPT_LEVEL").unwrap();
let define_debug = if env::var("CARGO_FEATURE_DEBUG_PRINTS").is_ok() {
"-DDEBUG"
} else {
"-UDEBUG"
};
// This is the directory where the `c` library is located.
let libdir_path = PathBuf::from("c-src")
// Canonicalize the path as `rustc-link-search` requires an absolute
// path.
.canonicalize()
.expect("cannot canonicalize path");
let libdir_path_str = libdir_path.to_str().expect("Path is not a valid string");
// This is the path to the `c` headers file.
let headers_path = libdir_path.join("wrapper.h");
let headers_path_str = headers_path.to_str().expect("Path is not a valid string");
println!("cargo:rerun-if-changed={}", libdir_path_str);
let res = cc::Build::new()
.file(
libdir_path
.join("btree.c")
.to_str()
.expect("Path is not a valid string"),
)
.file(
libdir_path
.join("lib")
.join("checksum.c")
.to_str()
.expect("Path is not a valid string"),
)
.flag(format!("-O{}", opt_level).as_ref())
.flag(define_debug)
.flag("-g3")
.flag("-Wall")
.flag("-Wextra")
.flag("-Wpedantic")
.flag("-Wformat=2")
.flag("-Wno-unused-parameter")
.flag("-Wshadow")
.flag("-Wwrite-strings")
.flag("-Wstrict-prototypes")
.flag("-Wold-style-definition")
.flag("-Wredundant-decls")
.flag("-Wnested-externs")
.flag("-Wmissing-include-dirs")
.try_compile("btree");
if let Err(err) = res {
panic!("{}", err);
}
// The bindgen::Builder is the main entry point
// to bindgen, and lets you build up options for
// the resulting bindings.
let bindings = bindgen::Builder::default()
// The input header we would like to generate
// bindings for.
.header(headers_path_str)
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
bindings
.write_to_file(out_path)
.expect("Couldn't write bindings!");
}

298
rust/ares_pma/c-src/btest.c Normal file
View File

@ -0,0 +1,298 @@
#include "btree.h"
#include "btree.c"
#include <stdlib.h>
#include <stdio.h>
static void
_test_nodeinteg(BT_state *state, BT_findpath *path,
vaof_t lo, vaof_t hi, pgno_t pg)
{
size_t childidx = 0;
BT_page *parent = 0;
assert(SUCC(_bt_find(state, path, lo, hi)));
parent = path->path[path->depth];
childidx = path->idx[path->depth];
assert(parent->datk[childidx].fo == pg);
assert(parent->datk[childidx].va == lo);
assert(parent->datk[childidx+1].va == hi);
}
static size_t
_mlist_sizep(BT_mlistnode *head)
/* calculate the size of the mlist in pages */
{
size_t sz = 0;
while (head) {
size_t sz_p = addr2off(head->hi) - addr2off(head->lo);
sz += sz_p;
head = head->next;
}
return sz;
}
static size_t
_flist_sizep(BT_flistnode *head)
/* calculate the size of the flist in pages */
{
size_t sz = 0;
while (head) {
size_t sz_p = head->hi - head->lo;
sz += sz_p;
head = head->next;
}
return sz;
}
static BT_mlistnode *
_mlist_copy(BT_state *state)
{
BT_mlistnode *head = state->mlist;
BT_mlistnode *ret, *prev;
ret = prev = calloc(1, sizeof *ret);
memcpy(ret, head, sizeof *head);
ret->next = 0;
head = head->next;
while (head) {
BT_mlistnode *copy = calloc(1, sizeof *copy);
memcpy(copy, head, sizeof *head);
prev->next = copy;
prev = copy;
head = head->next;
}
return ret;
}
static BT_nlistnode *
_nlist_copy(BT_state *state)
{
BT_nlistnode *head = state->nlist;
BT_nlistnode *ret, *prev;
ret = prev = calloc(1, sizeof *ret);
memcpy(ret, head, sizeof *head);
ret->next = 0;
head = head->next;
while (head) {
BT_nlistnode *copy = calloc(1, sizeof *copy);
memcpy(copy, head, sizeof *head);
prev->next = copy;
prev = copy;
head = head->next;
}
return ret;
}
static BT_flistnode *
_flist_copy(BT_state *state)
{
BT_flistnode *head = state->flist;
BT_flistnode *ret, *prev;
ret = prev = calloc(1, sizeof *ret);
memcpy(ret, head, sizeof *head);
ret->next = 0;
head = head->next;
while (head) {
BT_flistnode *copy = calloc(1, sizeof *copy);
memcpy(copy, head, sizeof *head);
prev->next = copy;
prev = copy;
head = head->next;
}
return ret;
}
static int
_mlist_eq(BT_mlistnode *l, BT_mlistnode *r)
{
while (l && r) {
if (l->lo != r->lo)
bp(0);
if (l->hi != r->hi)
bp(0);
l = l->next; r = r->next;
}
if (l == 0 && r == 0)
return 1;
bp(0);
}
static int
_nlist_eq(BT_nlistnode *l, BT_nlistnode *r)
{
while (l && r) {
if (l->lo != r->lo)
bp(0);
if (l->hi != r->hi)
bp(0);
l = l->next; r = r->next;
}
if (l == 0 && r == 0)
return 1;
bp(0);
}
static int
_flist_eq(BT_flistnode *l, BT_flistnode *r)
{
while (l && r) {
if (l->lo != r->lo)
bp(0);
if (l->hi != r->hi)
bp(0);
l = l->next; r = r->next;
}
if (l == 0 && r == 0)
return 1;
bp(0);
}
int main(int argc, char *argv[])
{
DPRINTF("PMA Max Storage: %lld", ((uint64_t)UINT32_MAX * BT_PAGESIZE) - BLK_BASE_LEN_TOTAL);
DPUTS("PMA Tests");
BT_state *state1;
BT_findpath path = {0};
int rc = 0;
DPUTS("== test 1: insert");
bt_state_new(&state1);
if (mkdir("./pmatest1", 0774) == -1)
return errno;
assert(SUCC(bt_state_open(state1, "./pmatest1", 0, 0644)));
#define LOWEST_ADDR 0x2aaa80;
vaof_t lo = LOWEST_ADDR;
vaof_t hi = 0xDEADBEEF;
pgno_t pg = 1; /* dummy value */
for (size_t i = 0; i < BT_DAT_MAXKEYS * 4; ++i) {
_bt_insert(state1, lo, hi, pg);
_test_nodeinteg(state1, &path, lo, hi, pg);
lo++; pg++;
}
bt_state_close(state1);
DPUTS("== test 2: malloc");
BT_state *state2;
bt_state_new(&state2);
if (mkdir("./pmatest2", 0774) == -1)
return errno;
assert(SUCC(bt_state_open(state2, "./pmatest2", 0, 0644)));
void *t2a = bt_malloc(state2, 10);
bt_free(state2, t2a, (BT_page*)t2a + 10);
void *t2b = bt_malloc(state2, 10);
/* should have pulled the same pointer due to eager mlist coalescing */
assert(t2a == t2b);
ZERO(&path, sizeof path);
_bt_find(state2, &path, addr2off(t2b), addr2off((BT_page *)t2b + 10));
#define T2P1_PRNT0 (path.path[path.depth])
#define T2P1_CIDX0 (path.idx[path.depth])
#define T2P1_CIDX1 (path.idx[path.depth] + 1)
/* check length as represented in btree */
assert(T2P1_PRNT0->datk[T2P1_CIDX1].va
- T2P1_PRNT0->datk[T2P1_CIDX0].va
== 10);
bt_free(state2, t2b, (BT_page*)t2b + 10);
ZERO(&path, sizeof path);
_bt_find(state2, &path, addr2off(t2b), addr2off((BT_page *)t2b + 10));
/* fo should be zero (free) */
assert(path.path[path.depth]->datk[path.idx[path.depth]].fo == 0);
/* should invoke deletion coalescing - 10 page free range in btree */
void *t2c = bt_malloc(state2, 20);
bt_state_close(state2);
DPUTS("== test 3: ephemeral structure restoration");
BT_state *state3;
bt_state_new(&state3);
if (mkdir("./pmatest3", 0774) == -1)
return errno;
assert(SUCC(bt_state_open(state3, "./pmatest3", 0, 0644)));
typedef struct lohi_pair lohi_pair;
struct lohi_pair
{
BT_page *lo;
BT_page *hi;
};
#define ITERATIONS 1000
#define MAXALLOCPG 0xFF
lohi_pair allocs[ITERATIONS] = {0};
size_t alloc_sizp = 0;
size_t flist_sizp = _flist_sizep(state3->flist);
size_t mlist_sizp = _mlist_sizep(state3->mlist);
BT_meta *meta = state3->meta_pages[state3->which];
BT_page *root = _node_get(state3, meta->root);
size_t N;
for (size_t i = 0; i < ITERATIONS; i++) {
/* malloc a random number of pages <= 256 and store in the allocs array */
int pages = random();
pages &= MAXALLOCPG;
pages += 1;
allocs[i].lo = bt_malloc(state3, pages);
allocs[i].hi = allocs[i].lo + pages;
alloc_sizp += pages;
/* validate size changes to mlist and flist */
assert(_flist_sizep(state3->flist)
== (flist_sizp - alloc_sizp));
assert(_mlist_sizep(state3->mlist)
== (mlist_sizp - alloc_sizp));
N = _bt_numkeys(root);
assert(root->datk[N-2].fo == 0);
}
/* sync the state */
/* bt_sync(state3); */
/* TODO: close and reopen state. validate ephemeral structures */
flist_sizp = _flist_sizep(state3->flist);
mlist_sizp = _mlist_sizep(state3->mlist);
alloc_sizp = 0;
/* for (size_t i = 0; i < ITERATIONS / 2; i++) { */
/* /\* free half of the allocations *\/ */
/* bt_free(state3, allocs[i].lo, allocs[i].hi); */
/* alloc_sizp += allocs[i].hi - allocs[i].lo; */
/* /\* validate size changes to mlist *\/ */
/* assert(_mlist_sizep(state3->mlist) */
/* == (mlist_sizp + alloc_sizp)); */
/* } */
/* copy ephemeral structures */
BT_mlistnode *mlist_copy = _mlist_copy(state3);
BT_nlistnode *nlist_copy = _nlist_copy(state3);
BT_flistnode *flist_copy = _flist_copy(state3);
assert(_mlist_eq(mlist_copy, state3->mlist));
assert(_nlist_eq(nlist_copy, state3->nlist));
assert(_flist_eq(flist_copy, state3->flist));
meta = state3->meta_pages[state3->which];
BT_meta metacopy = {0};
memcpy(&metacopy, meta, sizeof metacopy);
bt_state_close(state3);
bt_state_new(&state3);
assert(SUCC(bt_state_open(state3, "./pmatest3", 0, 0644)));
/* compare for equality copies of ephemeral structures with restored ephemeral
structures */
meta = state3->meta_pages[state3->which];
assert(meta->root == metacopy.root);
assert(_mlist_eq(mlist_copy, state3->mlist));
assert(_nlist_eq(nlist_copy, state3->nlist));
assert(_flist_eq(flist_copy, state3->flist));
return 0;
}

3199
rust/ares_pma/c-src/btree.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
#ifndef __BTREE_H__
#define __BTREE_H__
#include <sys/types.h>
#include <stdint.h>
struct BT_state;
typedef struct BT_state BT_state;
#define BT_PAGEBITS 14ULL
#define BT_PAGESIZE (1ULL << BT_PAGEBITS) /* 16K */
typedef unsigned long ULONG;
//// ===========================================================================
//// btree external routines
/**
* instantiate an opaque BT_state handle
*/
int bt_state_new(BT_state **state);
/**
* Open the persistent state or create if one doesn't exist
*/
int bt_state_open(BT_state *state, const char *path, ULONG flags, mode_t mode);
/**
* Close the persistent state
*/
int bt_state_close(BT_state *state);
/**
* Allocate persistent memory space
*/
void * bt_malloc(BT_state *state, size_t pages);
/**
* Free persistent memory space
*/
void bt_free(BT_state *state, void *lo, void *hi);
/**
* Sync a snapshot of the persistent memory to disk
* This will **exit the process** on failure to avoid data corruption
*/
int bt_sync(BT_state *state);
/**
* Get a metadata entry
*/
uint64_t bt_meta_get(BT_state *state, size_t idx);
/**
* Set a metadata entry
*/
void bt_meta_set(BT_state *state, size_t idx, uint64_t val);
/**
* Give the allocation range in the btree that a pointer lives in
*/
int bt_range_of(BT_state *state, void *p, void **lo, void **hi);
/**
* Ensure a region of memory is "dirty" i.e. can be mutated
*
* A successful call to bt_dirty ensures that the memory range can be mutated
* until the next call to `bt_sync()`
*/
int bt_dirty(BT_state *state, void *lo, void *hi);
/**
* Given a pointer, give the containing region of allocated memory, or the next
* highest if the pointer is to free memory
*/
int bt_next_alloc(BT_state *state, void *p, void **lo, void **hi);
/**
* Return the memory bounds of the persistent-memory B-tree
*/
void bt_bounds(BT_state *state, void **lo, void **hi);
/**
* Return whether a pointer is within the persistent-memory B-tree
*/
int bt_inbounds(BT_state *state, void *p);
#endif

View File

@ -0,0 +1 @@
#include "btree.h"

5
rust/ares_pma/src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));