Skip to content

Commit

Permalink
Merge pull request #359 from Chia-Network/nodeptr-repr
Browse files Browse the repository at this point in the history
`NodePtr` internal representation
  • Loading branch information
arvidn authored Jan 9, 2024
2 parents 09276d0 + d9952fb commit 293e9ab
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 69 deletions.
139 changes: 110 additions & 29 deletions src/allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,61 @@ use crate::reduction::EvalErr;
use chia_bls::{G1Element, G2Element};
use clvm_traits::{ClvmDecoder, ClvmEncoder, FromClvmError, ToClvmError};

const MAX_NUM_ATOMS: usize = 62500000;
const MAX_NUM_PAIRS: usize = 62500000;
const NODE_PTR_IDX_BITS: u32 = 26;
const NODE_PTR_IDX_MASK: u32 = (1 << NODE_PTR_IDX_BITS) - 1;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodePtr(pub i32);
pub struct NodePtr(u32);

enum ObjectType {
Pair,
Bytes,
}

// The top 6 bits of the NodePtr indicate what type of object it is
impl NodePtr {
pub fn null() -> Self {
Self::new(ObjectType::Bytes, 0)
}

// TODO: remove this
pub fn hack(val: usize) -> Self {
Self::new(ObjectType::Bytes, val)
}

fn new(t: ObjectType, idx: usize) -> Self {
assert!(idx <= NODE_PTR_IDX_MASK as usize);
NodePtr(((t as u32) << NODE_PTR_IDX_BITS) | (idx as u32))
}

fn node_type(&self) -> (ObjectType, usize) {
(
match self.0 >> NODE_PTR_IDX_BITS {
0 => ObjectType::Pair,
1 => ObjectType::Bytes,
_ => {
panic!("unknown NodePtr type");
}
},
(self.0 & NODE_PTR_IDX_MASK) as usize,
)
}

pub(crate) fn as_index(&self) -> usize {
match self.node_type() {
(ObjectType::Pair, idx) => idx * 2,
(ObjectType::Bytes, idx) => idx * 2 + 1,
}
}
}

impl Default for NodePtr {
fn default() -> Self {
Self::null()
}
}

pub enum SExp {
Atom,
Expand Down Expand Up @@ -58,9 +111,6 @@ pub struct Allocator {
heap_limit: usize,
}

const MAX_NUM_ATOMS: usize = 62500000;
const MAX_NUM_PAIRS: usize = 62500000;

impl Default for Allocator {
fn default() -> Self {
Self::new()
Expand Down Expand Up @@ -122,13 +172,14 @@ impl Allocator {
if (self.heap_limit - start as usize) < v.len() {
return err(self.null(), "out of memory");
}
if self.atom_vec.len() == MAX_NUM_ATOMS {
let idx = self.atom_vec.len();
if idx == MAX_NUM_ATOMS {
return err(self.null(), "too many atoms");
}
self.u8_vec.extend_from_slice(v);
let end = self.u8_vec.len() as u32;
self.atom_vec.push(AtomBuf { start, end });
Ok(NodePtr(-(self.atom_vec.len() as i32)))
Ok(NodePtr::new(ObjectType::Bytes, idx))
}

pub fn new_number(&mut self, v: Number) -> Result<NodePtr, EvalErr> {
Expand All @@ -144,22 +195,22 @@ impl Allocator {
}

pub fn new_pair(&mut self, first: NodePtr, rest: NodePtr) -> Result<NodePtr, EvalErr> {
let r = self.pair_vec.len() as i32;
if self.pair_vec.len() == MAX_NUM_PAIRS {
let idx = self.pair_vec.len();
if idx == MAX_NUM_PAIRS {
return err(self.null(), "too many pairs");
}
self.pair_vec.push(IntPair { first, rest });
Ok(NodePtr(r))
Ok(NodePtr::new(ObjectType::Pair, idx))
}

pub fn new_substr(&mut self, node: NodePtr, start: u32, end: u32) -> Result<NodePtr, EvalErr> {
if node.0 >= 0 {
return err(node, "(internal error) substr expected atom, got pair");
}
if self.atom_vec.len() == MAX_NUM_ATOMS {
return err(self.null(), "too many atoms");
}
let atom = self.atom_vec[(-node.0 - 1) as usize];
let (ObjectType::Bytes, idx) = node.node_type() else {
return err(node, "(internal error) substr expected atom, got pair");
};
let atom = self.atom_vec[idx];
let atom_len = atom.end - atom.start;
if start > atom_len {
return err(node, "substr start out of bounds");
Expand All @@ -170,11 +221,12 @@ impl Allocator {
if end < start {
return err(node, "substr invalid bounds");
}
let idx = self.atom_vec.len();
self.atom_vec.push(AtomBuf {
start: atom.start + start,
end: atom.start + end,
});
Ok(NodePtr(-(self.atom_vec.len() as i32)))
Ok(NodePtr::new(ObjectType::Bytes, idx))
}

pub fn new_concat(&mut self, new_size: usize, nodes: &[NodePtr]) -> Result<NodePtr, EvalErr> {
Expand All @@ -189,12 +241,12 @@ impl Allocator {

let mut counter: usize = 0;
for node in nodes {
if node.0 >= 0 {
let (ObjectType::Bytes, idx) = node.node_type() else {
self.u8_vec.truncate(start);
return err(*node, "(internal error) concat expected atom, got pair");
}
};

let term = self.atom_vec[(-node.0 - 1) as usize];
let term = self.atom_vec[idx];
if counter + term.len() > new_size {
self.u8_vec.truncate(start);
return err(*node, "(internal error) concat passed invalid new_size");
Expand All @@ -211,25 +263,40 @@ impl Allocator {
);
}
let end = self.u8_vec.len() as u32;
let idx = self.atom_vec.len();
self.atom_vec.push(AtomBuf {
start: (start as u32),
end,
});
Ok(NodePtr(-(self.atom_vec.len() as i32)))
Ok(NodePtr::new(ObjectType::Bytes, idx))
}

pub fn atom_eq(&self, lhs: NodePtr, rhs: NodePtr) -> bool {
self.atom(lhs) == self.atom(rhs)
}

pub fn atom(&self, node: NodePtr) -> &[u8] {
assert!(node.0 < 0, "expected atom, got pair");
let atom = self.atom_vec[(-node.0 - 1) as usize];
&self.u8_vec[atom.start as usize..atom.end as usize]
match node.node_type() {
(ObjectType::Bytes, idx) => {
let atom = self.atom_vec[idx];
&self.u8_vec[atom.start as usize..atom.end as usize]
}
_ => {
panic!("expected atom, got pair");
}
}
}

pub fn atom_len(&self, node: NodePtr) -> usize {
self.atom(node).len()
match node.node_type() {
(ObjectType::Bytes, idx) => {
let atom = self.atom_vec[idx];
(atom.end - atom.start) as usize
}
_ => {
panic!("expected atom, got pair");
}
}
}

pub fn number(&self, node: NodePtr) -> Number {
Expand Down Expand Up @@ -265,11 +332,12 @@ impl Allocator {
}

pub fn sexp(&self, node: NodePtr) -> SExp {
if node.0 >= 0 {
let pair = self.pair_vec[node.0 as usize];
SExp::Pair(pair.first, pair.rest)
} else {
SExp::Atom
match node.node_type() {
(ObjectType::Bytes, _) => SExp::Atom,
(ObjectType::Pair, idx) => {
let pair = self.pair_vec[idx];
SExp::Pair(pair.first, pair.rest)
}
}
}

Expand All @@ -286,11 +354,11 @@ impl Allocator {
}

pub fn null(&self) -> NodePtr {
NodePtr(-1)
NodePtr::new(ObjectType::Bytes, 0)
}

pub fn one(&self) -> NodePtr {
NodePtr(-2)
NodePtr::new(ObjectType::Bytes, 1)
}

#[cfg(feature = "counters")]
Expand Down Expand Up @@ -345,6 +413,19 @@ impl ClvmDecoder for Allocator {
}
}

#[test]
fn test_node_as_index() {
assert_eq!(NodePtr::new(ObjectType::Pair, 0).as_index(), 0);
assert_eq!(NodePtr::new(ObjectType::Pair, 1).as_index(), 2);
assert_eq!(NodePtr::new(ObjectType::Pair, 2).as_index(), 4);
assert_eq!(NodePtr::new(ObjectType::Pair, 3).as_index(), 6);
assert_eq!(NodePtr::new(ObjectType::Bytes, 0).as_index(), 1);
assert_eq!(NodePtr::new(ObjectType::Bytes, 1).as_index(), 3);
assert_eq!(NodePtr::new(ObjectType::Bytes, 2).as_index(), 5);
assert_eq!(NodePtr::new(ObjectType::Bytes, 3).as_index(), 7);
assert_eq!(NodePtr::new(ObjectType::Bytes, 4).as_index(), 9);
}

#[test]
fn test_atom_eq() {
let mut a = Allocator::new();
Expand Down
20 changes: 14 additions & 6 deletions src/op_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn get_args<const N: usize>(
) -> Result<[NodePtr; N], EvalErr> {
let mut next = args;
let mut counter = 0;
let mut ret: [NodePtr; N] = [NodePtr(0); N];
let mut ret: [NodePtr; N] = [NodePtr::null(); N];

while let Some((first, rest)) = a.next(next) {
next = rest;
Expand Down Expand Up @@ -91,7 +91,7 @@ pub fn get_varargs<const N: usize>(
) -> Result<([NodePtr; N], usize), EvalErr> {
let mut next = args;
let mut counter = 0;
let mut ret: [NodePtr; N] = [NodePtr(0); N];
let mut ret: [NodePtr; N] = [NodePtr::null(); N];

while let Some((first, rest)) = a.next(next) {
next = rest;
Expand Down Expand Up @@ -131,19 +131,27 @@ fn test_get_varargs() {
);
assert_eq!(
get_varargs::<4>(&a, args3, "test").unwrap(),
([a1, a2, a3, NodePtr(0)], 3)
([a1, a2, a3, NodePtr::null()], 3)
);
assert_eq!(
get_varargs::<4>(&a, args2, "test").unwrap(),
([a2, a3, NodePtr(0), NodePtr(0)], 2)
([a2, a3, NodePtr::null(), NodePtr::null()], 2)
);
assert_eq!(
get_varargs::<4>(&a, args1, "test").unwrap(),
([a3, NodePtr(0), NodePtr(0), NodePtr(0)], 1)
([a3, NodePtr::null(), NodePtr::null(), NodePtr::null()], 1)
);
assert_eq!(
get_varargs::<4>(&a, args0, "test").unwrap(),
([NodePtr(0), NodePtr(0), NodePtr(0), NodePtr(0)], 0)
(
[
NodePtr::null(),
NodePtr::null(),
NodePtr::null(),
NodePtr::null()
],
0
)
);

let r = get_varargs::<3>(&a, args4, "test").unwrap_err();
Expand Down
25 changes: 2 additions & 23 deletions src/serde/object_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,6 @@ pub struct ObjectCache<'a, T> {
f: CachedFunction<T>,
}

/// turn a `NodePtr` into a `usize`. Positive values become even indices
/// and negative values become odd indices.

fn node_to_index(node: &NodePtr) -> usize {
let value = node.0;
if value < 0 {
(-value - value - 1) as usize
} else {
(value + value) as usize
}
}

impl<'a, T: Clone> ObjectCache<'a, T> {
pub fn new(allocator: &'a Allocator, f: CachedFunction<T>) -> Self {
let cache = vec![];
Expand All @@ -57,7 +45,7 @@ impl<'a, T: Clone> ObjectCache<'a, T> {

/// return the cached value for this node, or `None`
fn get_from_cache(&self, node: &NodePtr) -> Option<&T> {
let index = node_to_index(node);
let index = node.as_index();
if index < self.cache.len() {
self.cache[index].as_ref()
} else {
Expand All @@ -67,7 +55,7 @@ impl<'a, T: Clone> ObjectCache<'a, T> {

/// set the cached value for a node
fn set(&mut self, node: &NodePtr, v: T) {
let index = node_to_index(node);
let index = node.as_index();
if index >= self.cache.len() {
self.cache.resize(index + 1, None);
}
Expand Down Expand Up @@ -255,15 +243,6 @@ fn test_serialized_length() {
check("ff01ff02ff03ff04ff05ff0680", 13); // (1 2 3 4 5 6)
}

#[test]
fn test_node_to_index() {
assert_eq!(node_to_index(&NodePtr(0)), 0);
assert_eq!(node_to_index(&NodePtr(1)), 2);
assert_eq!(node_to_index(&NodePtr(2)), 4);
assert_eq!(node_to_index(&NodePtr(-1)), 1);
assert_eq!(node_to_index(&NodePtr(-2)), 3);
}

// this test takes a very long time (>60s) in debug mode, so it only runs in release mode

#[cfg(not(debug_assertions))]
Expand Down
Loading

0 comments on commit 293e9ab

Please sign in to comment.