mirror of
https://github.com/urbit/ares.git
synced 2024-12-23 21:31:57 +03:00
commit
c6a45aed9d
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@ ships/
|
||||
*.backup
|
||||
urbit
|
||||
*.jam.out
|
||||
*.o
|
||||
*.a
|
||||
|
34
rust/ares/Cargo.lock
generated
34
rust/ares/Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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>)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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)
|
||||
|
@ -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
311
rust/ares/src/persist.rs
Normal 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
|
||||
}
|
@ -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
@ -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
|
@ -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
@ -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) => {
|
||||
|
254
rust/ares/src/unifying_equality.rs
Normal file
254
rust/ares/src/unifying_equality.rs
Normal 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
454
rust/ares_pma/Cargo.lock
generated
Normal 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
15
rust/ares_pma/Cargo.toml
Normal 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
83
rust/ares_pma/build.rs
Normal 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
298
rust/ares_pma/c-src/btest.c
Normal 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
3199
rust/ares_pma/c-src/btree.c
Normal file
File diff suppressed because it is too large
Load Diff
87
rust/ares_pma/c-src/btree.h
Normal file
87
rust/ares_pma/c-src/btree.h
Normal 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
|
1
rust/ares_pma/c-src/wrapper.h
Normal file
1
rust/ares_pma/c-src/wrapper.h
Normal file
@ -0,0 +1 @@
|
||||
#include "btree.h"
|
5
rust/ares_pma/src/lib.rs
Normal file
5
rust/ares_pma/src/lib.rs
Normal 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"));
|
Loading…
Reference in New Issue
Block a user