Merge pull request #4 from urbit/dev/iron-planet

Iron Planet Nock implementation in Rust
This commit is contained in:
Edward Amsden 2022-07-17 03:04:35 -05:00 committed by GitHub
commit 26424c4bae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 3001 additions and 0 deletions

1
rust/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

173
rust/iron-planet/Cargo.lock generated Normal file
View File

@ -0,0 +1,173 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitvec"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "intmap"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b357564d111300f8a33b79e06795235529a627a1f7078d2b1db7f7dcdf032874"
[[package]]
name = "iron-planet"
version = "0.1.0"
dependencies = [
"bitvec",
"either",
"intmap",
"libc",
"memmap",
"murmur3",
"num-derive",
"num-traits",
]
[[package]]
name = "libc"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "memmap"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "murmur3"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ead5388e485d38e622630c6b05afd3761a6701ff15c55b279ea5b31dcb62cff"
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "proc-macro2"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "syn"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "unicode-ident"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
[[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 = "wyz"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
dependencies = [
"tap",
]

View File

@ -0,0 +1,17 @@
[package]
name = "iron-planet"
version = "0.1.0"
authors = ["Edward Amsden <edward@blackriversoft.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bitvec = "1.0.0"
either = "1.6.1"
libc = "0.2.126"
murmur3 = "0.5.1"
memmap = "0.7.0"
intmap = "1.1.0"
num-traits = "0.2"
num-derive = "0.3"

View File

@ -0,0 +1,598 @@
use self::NockWork::*;
use crate::mem::unifying_equality;
use crate::mem::NockStack;
use crate::noun::{Atom, Cell, DirectAtom, IndirectAtom, Noun};
use bitvec::prelude::{BitSlice, Lsb0};
use either::Either::*;
use num_traits::cast::{FromPrimitive, ToPrimitive};
#[derive(Copy, Clone, FromPrimitive, ToPrimitive)]
#[repr(u64)]
enum NockWork {
Done,
NockCellComputeHead,
NockCellComputeTail,
NockCellCons,
Nock0Axis,
Nock1Constant,
Nock2ComputeSubject,
Nock2ComputeFormula,
Nock2ComputeResult,
Nock2RestoreSubject,
Nock3ComputeChild,
Nock3ComputeType,
Nock4ComputeChild,
Nock4Increment,
Nock5ComputeLeftChild,
Nock5ComputeRightChild,
Nock5TestEquals,
Nock6ComputeTest,
Nock6ComputeBranch,
Nock6Done,
Nock7ComputeSubject,
Nock7ComputeResult,
Nock7RestoreSubject,
Nock8ComputeSubject,
Nock8ComputeResult,
Nock8RestoreSubject,
Nock9ComputeCore,
Nock9ComputeResult,
Nock9RestoreSubject,
Nock10ComputeTree,
Nock10ComputePatch,
Nock10Edit,
Nock11ComputeHint,
Nock11ComputeResult,
Nock11Done,
}
fn work_to_noun(work: NockWork) -> Noun {
unsafe {
DirectAtom::new_unchecked(work.to_u64().expect("IMPOSSIBLE: work does not fit in u64"))
.as_atom()
.as_noun()
}
}
fn noun_to_work(noun: Noun) -> NockWork {
if let Left(direct) = noun.as_either_direct_allocated() {
NockWork::from_u64(direct.data()).expect("Invalid work")
} else {
panic!("Work should always be a direct atom.")
}
}
pub fn interpret(stack: &mut NockStack, mut subject: Noun, formula: Noun) -> Noun {
let mut res = unsafe { DirectAtom::new_unchecked(0).as_atom().as_noun() };
stack.push(1);
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Done);
}
push_formula(stack, formula);
loop {
match unsafe { noun_to_work(*(stack.local_noun_pointer(0))) } {
Done => {
stack.pop(&mut res);
break;
}
NockCellComputeHead => {
unsafe {
*stack.local_noun_pointer(0) = work_to_noun(NockCellComputeTail);
let formula = *stack.local_noun_pointer(1);
push_formula(stack, formula);
};
}
NockCellComputeTail => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(NockCellCons);
*(stack.local_noun_pointer(1)) = res;
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
};
}
NockCellCons => {
unsafe {
let head = *stack.local_noun_pointer(1);
res = Cell::new(stack, head, res).as_noun();
};
stack.pop(&mut res);
}
Nock0Axis => {
if let Ok(atom) = unsafe { (*(stack.local_noun_pointer(1))).as_atom() } {
res = axis(subject, atom.as_bitslice());
stack.pop(&mut res);
} else {
panic!("Axis must be atom");
};
}
Nock1Constant => {
unsafe {
res = *(stack.local_noun_pointer(1));
}
stack.pop(&mut res);
}
Nock2ComputeSubject => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock2ComputeFormula);
let formula = *stack.local_noun_pointer(1);
push_formula(stack, formula);
};
}
Nock2ComputeFormula => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock2ComputeResult);
*(stack.local_noun_pointer(1)) = res;
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
};
}
Nock2ComputeResult => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock2RestoreSubject);
*(stack.local_noun_pointer(2)) = subject;
subject = *(stack.local_noun_pointer(1));
push_formula(stack, res);
};
}
Nock2RestoreSubject => unsafe {
subject = *(stack.local_noun_pointer(2));
stack.pop(&mut res);
},
Nock3ComputeChild => unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock3ComputeType);
let formula = *stack.local_noun_pointer(1);
push_formula(stack, formula);
},
Nock3ComputeType => {
res = unsafe {
if res.is_cell() {
DirectAtom::new_unchecked(0).as_atom().as_noun()
} else {
DirectAtom::new_unchecked(1).as_atom().as_noun()
}
};
stack.pop(&mut res);
}
Nock4ComputeChild => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock4Increment);
let formula = *stack.local_noun_pointer(1);
push_formula(stack, formula);
};
}
Nock4Increment => {
if let Ok(atom) = res.as_atom() {
res = inc(stack, atom).as_noun();
stack.pop(&mut res);
} else {
panic!("Cannot increment (Nock 4) a cell");
};
}
Nock5ComputeLeftChild => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock5ComputeRightChild);
let formula = *stack.local_noun_pointer(1);
push_formula(stack, formula);
};
}
Nock5ComputeRightChild => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock5TestEquals);
*(stack.local_noun_pointer(1)) = res;
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
};
}
Nock5TestEquals => {
unsafe {
let saved_value_ptr = stack.local_noun_pointer(1);
res = if unifying_equality(stack, &mut res, saved_value_ptr) {
DirectAtom::new_unchecked(0).as_atom().as_noun()
} else {
DirectAtom::new_unchecked(1).as_atom().as_noun()
};
stack.pop(&mut res);
};
}
Nock6ComputeTest => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock6ComputeBranch);
let formula = *stack.local_noun_pointer(1);
push_formula(stack, formula);
};
}
Nock6ComputeBranch => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock6Done);
if let Left(direct) = res.as_either_direct_allocated() {
if direct.data() == 0 {
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
} else if direct.data() == 1 {
let formula = *stack.local_noun_pointer(3);
push_formula(stack, formula);
} else {
panic!("Test branch of Nock 6 must return 0 or 1");
};
} else {
panic!("Test branch of Nock 6 must return a direct atom");
}
};
}
Nock6Done => {
stack.pop(&mut res);
}
Nock7ComputeSubject => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock7ComputeResult);
let formula = *stack.local_noun_pointer(1);
push_formula(stack, formula);
};
}
Nock7ComputeResult => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock7RestoreSubject);
*(stack.local_noun_pointer(1)) = subject;
subject = res;
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
};
}
Nock7RestoreSubject => {
unsafe {
subject = *(stack.local_noun_pointer(1));
stack.pop(&mut res);
};
}
Nock8ComputeSubject => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock8ComputeResult);
let formula = *stack.local_noun_pointer(1);
push_formula(stack, formula);
};
}
Nock8ComputeResult => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock8RestoreSubject);
*(stack.local_noun_pointer(1)) = subject;
subject = Cell::new(stack, res, subject).as_noun();
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
};
}
Nock8RestoreSubject => {
unsafe {
subject = *(stack.local_noun_pointer(2));
stack.pop(&mut res);
};
}
Nock9ComputeCore => {
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock9ComputeResult);
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
};
}
Nock9ComputeResult => {
unsafe {
if let Ok(formula_axis) = (*(stack.local_noun_pointer(1))).as_atom() {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock9RestoreSubject);
*(stack.local_noun_pointer(2)) = subject;
subject = res;
push_formula(stack, axis(subject, formula_axis.as_bitslice()));
} else {
panic!("Axis into core must be atom");
}
};
}
Nock9RestoreSubject => unsafe {
subject = *(stack.local_noun_pointer(2));
stack.pop(&mut res);
},
Nock10ComputeTree => unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock10ComputePatch);
let formula = *stack.local_noun_pointer(3);
push_formula(stack, formula);
},
Nock10ComputePatch => unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock10Edit);
*(stack.local_noun_pointer(3)) = res;
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
},
Nock10Edit => unsafe {
if let Ok(edit_axis) = (*stack.local_noun_pointer(1)).as_atom() {
let tree = *stack.local_noun_pointer(3);
res = edit(stack, edit_axis.as_bitslice(), res, tree);
stack.pop(&mut res);
}
},
Nock11ComputeHint => unsafe {
let hint = *stack.local_noun_pointer(1);
if let Ok(hint_cell) = hint.as_cell() {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock11ComputeResult);
push_formula(stack, hint_cell.tail());
} else {
panic!("IMPOSSIBLE: tried to compute a dynamic hint but hint is an atom");
}
},
Nock11ComputeResult => unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock11Done);
let formula = *stack.local_noun_pointer(2);
push_formula(stack, formula);
},
Nock11Done => {
stack.pop(&mut res);
}
};
}
res
}
fn push_formula(stack: &mut NockStack, formula: Noun) {
if let Ok(formula_cell) = formula.as_cell() {
// Formula
match formula_cell.head().as_either_atom_cell() {
Right(_cell) => {
stack.push(3);
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(NockCellComputeHead);
*(stack.local_noun_pointer(1)) = formula_cell.head();
*(stack.local_noun_pointer(2)) = formula_cell.tail();
}
}
Left(atom) => {
if let Ok(direct) = atom.as_direct() {
match direct.data() {
0 => {
stack.push(2);
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock0Axis);
*(stack.local_noun_pointer(1)) = formula_cell.tail();
};
}
1 => {
stack.push(2);
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock1Constant);
*(stack.local_noun_pointer(1)) = formula_cell.tail();
};
}
2 => {
if let Ok(arg_cell) = formula_cell.tail().as_cell() {
stack.push(3);
unsafe {
*(stack.local_noun_pointer(0)) =
work_to_noun(Nock2ComputeSubject);
*(stack.local_noun_pointer(1)) = arg_cell.head();
*(stack.local_noun_pointer(2)) = arg_cell.tail();
};
} else {
panic!("Argument for Nock 2 must be cell");
};
}
3 => {
stack.push(2);
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock3ComputeChild);
*(stack.local_noun_pointer(1)) = formula_cell.tail();
};
}
4 => {
stack.push(2);
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock4ComputeChild);
*(stack.local_noun_pointer(1)) = formula_cell.tail();
};
}
5 => {
if let Ok(arg_cell) = formula_cell.tail().as_cell() {
stack.push(3);
unsafe {
*(stack.local_noun_pointer(0)) =
work_to_noun(Nock5ComputeLeftChild);
*(stack.local_noun_pointer(1)) = arg_cell.head();
*(stack.local_noun_pointer(2)) = arg_cell.tail();
};
} else {
panic!("Argument for Nock 5 must be cell");
};
}
6 => {
if let Ok(arg_cell) = formula_cell.tail().as_cell() {
if let Ok(branch_cell) = arg_cell.tail().as_cell() {
stack.push(4);
unsafe {
*(stack.local_noun_pointer(0)) =
work_to_noun(Nock6ComputeTest);
*(stack.local_noun_pointer(1)) = arg_cell.head();
*(stack.local_noun_pointer(2)) = branch_cell.head();
*(stack.local_noun_pointer(3)) = branch_cell.tail();
}
} else {
panic!("Argument tail for Nock 6 must be cell");
};
} else {
panic!("Argument for Nock 6 must be cell");
}
}
7 => {
if let Ok(arg_cell) = formula_cell.tail().as_cell() {
stack.push(3);
unsafe {
*(stack.local_noun_pointer(0)) =
work_to_noun(Nock7ComputeSubject);
*(stack.local_noun_pointer(1)) = arg_cell.head();
*(stack.local_noun_pointer(2)) = arg_cell.tail();
}
} else {
panic!("Argument for Nock 7 must be cell");
};
}
8 => {
if let Ok(arg_cell) = formula_cell.tail().as_cell() {
stack.push(3);
unsafe {
*(stack.local_noun_pointer(0)) =
work_to_noun(Nock8ComputeSubject);
*(stack.local_noun_pointer(1)) = arg_cell.head();
*(stack.local_noun_pointer(2)) = arg_cell.tail();
};
} else {
panic!("Argument for Nock 8 must be cell");
};
}
9 => {
if let Ok(arg_cell) = formula_cell.tail().as_cell() {
stack.push(3);
unsafe {
*(stack.local_noun_pointer(0)) = work_to_noun(Nock9ComputeCore);
*(stack.local_noun_pointer(1)) = arg_cell.head();
*(stack.local_noun_pointer(2)) = arg_cell.tail();
};
} else {
panic!("Argument for Nock 9 must be cell");
};
}
10 => {
if let Ok(arg_cell) = formula_cell.tail().as_cell() {
if let Ok(patch_cell) = arg_cell.head().as_cell() {
stack.push(4);
unsafe {
*(stack.local_noun_pointer(0)) =
work_to_noun(Nock10ComputeTree);
*(stack.local_noun_pointer(1)) = patch_cell.head();
*(stack.local_noun_pointer(2)) = patch_cell.tail();
*(stack.local_noun_pointer(3)) = arg_cell.tail();
};
} else {
panic!("Argument head for Nock 10 must be cell");
};
} else {
panic!("Argument for Nock 10 must be cell");
};
}
11 => {
if let Ok(arg_cell) = formula_cell.tail().as_cell() {
stack.push(3);
unsafe {
*(stack.local_noun_pointer(0)) =
work_to_noun(if arg_cell.head().is_cell() {
Nock11ComputeHint
} else {
Nock11ComputeResult
});
*(stack.local_noun_pointer(1)) = arg_cell.head();
*(stack.local_noun_pointer(2)) = arg_cell.tail();
};
} else {
panic!("Argument for Nock 11 must be cell");
};
}
_ => {
panic!("Invalid opcode");
}
}
} else {
panic!("Invalid opcode");
}
}
}
} else {
panic!("Bad formula: atoms are not formulas");
}
}
fn axis(mut noun: Noun, axis: &BitSlice<u64, Lsb0>) -> Noun {
let mut cursor = if let Some(x) = axis.last_one() {
x
} else {
panic!("0 is not allowed as an axis")
};
loop {
if cursor == 0 {
break;
};
cursor -= 1;
if let Ok(cell) = noun.as_cell() {
if axis[cursor] {
noun = cell.tail();
} else {
noun = cell.head();
}
} else {
panic!("Axis tried to descend through atom.");
};
}
noun
}
fn edit(
stack: &mut NockStack,
edit_axis: &BitSlice<u64, Lsb0>,
patch: Noun,
mut tree: Noun,
) -> Noun {
let mut res = patch;
let mut dest: *mut Noun = &mut res;
let mut cursor = edit_axis
.last_one()
.expect("0 is not allowed as an edit axis");
loop {
if cursor == 0 {
unsafe {
*dest = patch;
}
break;
};
if let Ok(tree_cell) = tree.as_cell() {
cursor -= 1;
if edit_axis[cursor] {
unsafe {
let (cell, cellmem) = Cell::new_raw_mut(stack);
*dest = cell.as_noun();
(*cellmem).head = tree_cell.head();
dest = &mut ((*cellmem).tail);
}
tree = tree_cell.tail();
} else {
unsafe {
let (cell, cellmem) = Cell::new_raw_mut(stack);
*dest = cell.as_noun();
(*cellmem).tail = tree_cell.tail();
dest = &mut ((*cellmem).head);
}
tree = tree_cell.tail();
}
} else {
panic!("Invalid axis for edit");
};
}
res
}
fn inc(stack: &mut NockStack, atom: Atom) -> Atom {
match atom.as_either() {
Left(direct) => Atom::new(stack, direct.data() + 1),
Right(indirect) => {
let indirect_slice = indirect.as_bitslice();
match indirect_slice.first_zero() {
None => {
// all ones, make an indirect one word bigger
let (new_indirect, new_slice) =
unsafe { IndirectAtom::new_raw_mut_bitslice(stack, indirect.size() + 1) };
new_slice.set(indirect_slice.len(), true);
new_indirect.as_atom()
}
Some(first_zero) => {
let (new_indirect, new_slice) =
unsafe { IndirectAtom::new_raw_mut_bitslice(stack, indirect.size()) };
new_slice.set(first_zero, true);
new_slice[first_zero + 1..]
.copy_from_bitslice(&indirect_slice[first_zero + 1..]);
new_indirect.as_atom()
}
}
}
}
}

View File

@ -0,0 +1,7 @@
#[macro_use]
extern crate num_derive;
pub mod interpreter;
pub mod mem;
pub mod mug;
pub mod noun;
pub mod serialization;

View File

@ -0,0 +1,52 @@
use iron_planet::interpreter::interpret;
use iron_planet::mem::NockStack;
use iron_planet::noun::IndirectAtom;
use iron_planet::serialization::{cue, jam};
use memmap::Mmap;
use memmap::MmapMut;
use std::env;
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::mem;
use std::ptr::copy_nonoverlapping;
use std::ptr::write_bytes;
fn main() -> io::Result<()> {
let filename = env::args().nth(1).expect("Must provide input filename");
let output_filename = format!("{}.out", filename.clone());
let f = File::open(filename)?;
let in_len = f.metadata()?.len();
let mut stack = NockStack::new(8 << 10 << 10, 0);
let jammed_input = unsafe {
let in_map = Mmap::map(&f)?;
let word_len = (in_len + 7) >> 3;
let (mut atom, dest) = IndirectAtom::new_raw_mut(&mut stack, word_len as usize);
write_bytes(dest.add(word_len as usize - 1), 0, 8);
copy_nonoverlapping(in_map.as_ptr(), dest as *mut u8, in_len as usize);
mem::drop(in_map);
atom.normalize_as_atom()
};
let input = cue(&mut stack, jammed_input);
let input_cell = input
.as_cell()
.expect("Input must be jam of subject/formula pair");
let result = interpret(&mut stack, input_cell.head(), input_cell.tail());
let jammed_result = jam(&mut stack, result);
let f_out = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(output_filename)?;
f_out.set_len((jammed_result.size() << 3) as u64)?;
unsafe {
let mut out_map = MmapMut::map_mut(&f_out)?;
copy_nonoverlapping(
jammed_result.data_pointer() as *mut u8,
out_map.as_mut_ptr(),
jammed_result.size() << 3,
);
out_map.flush()?;
};
Ok(())
}

760
rust/iron-planet/src/mem.rs Normal file
View File

@ -0,0 +1,760 @@
use crate::noun::{CellMemory, IndirectAtom, Noun, NounAllocator};
use either::Either::{self, Left, Right};
use libc::{c_void, memcmp};
use memmap::MmapMut;
use std::mem;
use std::ptr;
use std::ptr::copy_nonoverlapping;
/** Utility function to get size in words */
pub const fn word_size_of<T>() -> usize {
(mem::size_of::<T>() + 7) >> 3
}
/** Utility function to compute the raw memory usage of an IndirectAtom */
fn indirect_raw_size(atom: IndirectAtom) -> usize {
atom.size() + 2
}
/** Which side of the two opposing stacks are we working on? */
#[derive(Copy, Clone)]
pub enum Polarity {
/** Stack growing down from high memory */
East,
/** Stack growing up from low memory */
West,
}
/** A stack for Nock computation, which supports stack allocation and delimited copying collection
* for returned nouns
*/
pub struct NockStack {
/** The base pointer */
start: *const u64,
/** The size of the memory region */
size: usize,
/** Which side of the stack is the active stack frame on? */
polarity: Polarity,
/** Furthest extent of the current stack frame */
stack_pointer: *mut u64,
/** Base pointer for the current stack frame. Accesses to slots are computed from this base. */
frame_pointer: *mut u64,
/** MMap which must be kept alive as long as this NockStack is */
memory: MmapMut,
}
impl NockStack {
/** Size is in 64 bit words.
* top_slots is how many slots to allocate to the top stack frame.
*/
pub fn new(size: usize, top_slots: usize) -> NockStack {
let mut memory = MmapMut::map_anon(size << 3).expect("Mapping memory for nockstack failed");
let start = memory.as_ptr() as *const u64;
let frame_pointer = memory.as_mut_ptr() as *mut u64;
let stack_pointer = unsafe { frame_pointer.add(top_slots + 2) };
unsafe {
*frame_pointer = frame_pointer.add(size) as u64;
*frame_pointer.add(1) = ptr::null::<u64>() as u64;
};
NockStack {
start: start,
size: size,
polarity: Polarity::West,
stack_pointer: stack_pointer,
frame_pointer: frame_pointer,
memory: memory,
}
}
/** Size **in 64-bit words** of this NockStack */
pub fn size(&self) -> usize {
self.size
}
/** Mutable pointer to a slot in a stack frame: east stack */
unsafe fn slot_pointer_east(&mut self, slot: usize) -> *mut u64 {
self.frame_pointer.sub(slot + 1)
}
/** Mutable pointer to a slot in a stack frame: west stack */
unsafe fn slot_pointer_west(&mut self, slot: usize) -> *mut u64 {
self.frame_pointer.add(slot)
}
/** Mutable pointer to a slot in a stack frame */
unsafe fn slot_pointer(&mut self, slot: usize) -> *mut u64 {
match &self.polarity {
Polarity::East => self.slot_pointer_east(slot),
Polarity::West => self.slot_pointer_west(slot),
}
}
/** Pointer to a local slot typed as Noun */
pub unsafe fn local_noun_pointer(&mut self, local: usize) -> *mut Noun {
self.slot_pointer(local + 2) as *mut Noun
}
/** Save the stack pointer for the previous frame in a slot of an east frame */
unsafe fn save_prev_stack_pointer_to_local_east(&mut self, local: usize) {
*(self.slot_pointer_east(local + 2) as *mut *mut u64) =
*(self.previous_stack_pointer_pointer_east())
}
/** Save the stack pointer for the previous frame in a slot of a west frame */
unsafe fn save_prev_stack_pointer_to_local_west(&mut self, local: usize) {
*(self.slot_pointer_west(local + 2) as *mut *mut u64) =
*(self.previous_stack_pointer_pointer_west())
}
/** Save the stack pointer for the previous frame in a slot */
pub unsafe fn save_prev_stack_pointer_to_local(&mut self, local: usize) {
match &self.polarity {
Polarity::East => self.save_prev_stack_pointer_to_local_east(local),
Polarity::West => self.save_prev_stack_pointer_to_local_west(local),
}
}
unsafe fn restore_prev_stack_pointer_from_local_east(&mut self, local: usize) {
*(self.previous_stack_pointer_pointer_east()) =
*(self.slot_pointer_east(local + 2) as *mut *mut u64);
}
unsafe fn restore_prev_stack_pointer_from_local_west(&mut self, local: usize) {
*(self.previous_stack_pointer_pointer_east()) =
*(self.slot_pointer_east(local + 2) as *mut *mut u64);
}
unsafe fn restore_prev_stack_pointer_from_local(&mut self, local: usize) {
match &self.polarity {
Polarity::East => self.restore_prev_stack_pointer_from_local_east(local),
Polarity::West => self.restore_prev_stack_pointer_from_local_west(local),
}
}
unsafe fn prev_stack_pointer_equals_local_east(&mut self, local: usize) -> bool {
*(self.slot_pointer_east(local + 2) as *const *mut u64)
== *(self.previous_stack_pointer_pointer_east())
}
unsafe fn prev_stack_pointer_equals_local_west(&mut self, local: usize) -> bool {
*(self.slot_pointer_west(local + 2) as *const *mut u64)
== *(self.previous_stack_pointer_pointer_west())
}
/** Test the stack pointer for the previous frame against a slot */
pub unsafe fn prev_stack_pointer_equals_local(&mut self, local: usize) -> bool {
match &self.polarity {
Polarity::East => self.prev_stack_pointer_equals_local_east(local),
Polarity::West => self.prev_stack_pointer_equals_local_west(local),
}
}
unsafe fn alloc_in_previous_frame_west<T>(&mut self) -> *mut T {
let prev_stack_pointer_pointer = self.previous_stack_pointer_pointer_west();
// note that the allocation is on the east frame, and thus resembles raw_alloc_east
*prev_stack_pointer_pointer = (*prev_stack_pointer_pointer).sub(word_size_of::<T>());
*prev_stack_pointer_pointer as *mut T
}
unsafe fn alloc_in_previous_frame_east<T>(&mut self) -> *mut T {
let prev_stack_pointer_pointer = self.previous_stack_pointer_pointer_east();
// note that the allocation is on the west frame, and thus resembles raw_alloc_west
let alloc = *(prev_stack_pointer_pointer);
*prev_stack_pointer_pointer = (*prev_stack_pointer_pointer).add(word_size_of::<T>());
alloc as *mut T
}
pub unsafe fn alloc_in_previous_frame<T>(&mut self) -> *mut T {
match &self.polarity {
Polarity::East => self.alloc_in_previous_frame_east(),
Polarity::West => self.alloc_in_previous_frame_west(),
}
}
unsafe fn reclaim_in_previous_frame_east<T>(&mut self) {
let prev_stack_pointer_pointer = self.previous_stack_pointer_pointer_east();
*prev_stack_pointer_pointer = (*prev_stack_pointer_pointer).sub(word_size_of::<T>());
}
unsafe fn reclaim_in_previous_frame_west<T>(&mut self) {
let prev_stack_pointer_pointer = self.previous_stack_pointer_pointer_west();
*prev_stack_pointer_pointer = (*prev_stack_pointer_pointer).add(word_size_of::<T>());
}
/** Reclaim allocated space at the end of the previous stack frame.
* This is unsafe because if we're not checking against a saved pointer, we could reclaim
* space used for noun allocations and cause them to be overwritten
*/
pub unsafe fn reclaim_in_previous_frame<T>(&mut self) {
match &self.polarity {
Polarity::East => self.reclaim_in_previous_frame_east::<T>(),
Polarity::West => self.reclaim_in_previous_frame_west::<T>(),
}
}
unsafe fn top_in_previous_frame_east<T>(&mut self) -> *mut T {
let prev_stack_pointer_pointer = self.previous_stack_pointer_pointer_east();
(*prev_stack_pointer_pointer).sub(word_size_of::<T>()) as *mut T
}
unsafe fn top_in_previous_frame_west<T>(&mut self) -> *mut T {
let prev_stack_pointer_pointer = self.previous_stack_pointer_pointer_west();
*prev_stack_pointer_pointer as *mut T
}
/** Get a pointer to the top entry in the previous stack frame.
*
* Note that if the there are no entries the behavior is undefined.
*/
pub unsafe fn top_in_previous_frame<T>(&mut self) -> *mut T {
match &self.polarity {
Polarity::East => self.top_in_previous_frame_east::<T>(),
Polarity::West => self.top_in_previous_frame_west::<T>(),
}
}
/** Pointer to where the previous (west) stack pointer is saved in an east frame */
unsafe fn previous_stack_pointer_pointer_east(&mut self) -> *mut *mut u64 {
self.slot_pointer_east(0) as *mut *mut u64
}
/** Pointer to where the previous (east) stack pointer is saved in a west frame */
unsafe fn previous_stack_pointer_pointer_west(&mut self) -> *mut *mut u64 {
self.slot_pointer_west(0) as *mut *mut u64
}
/** Pointer to where the previous (west) frame pointer is saved in an east frame */
unsafe fn previous_frame_pointer_pointer_east(&mut self) -> *mut *mut u64 {
self.slot_pointer_east(1) as *mut *mut u64
}
/** Pointer to where the previous (east) frame pointer is saved in a west frame */
unsafe fn previous_frame_pointer_pointer_west(&mut self) -> *mut *mut u64 {
self.slot_pointer_west(1) as *mut *mut u64
}
/** Bump the stack pointer for an east frame to make space for an allocation */
unsafe fn raw_alloc_east(&mut self, words: usize) -> *mut u64 {
self.stack_pointer = self.stack_pointer.sub(words);
self.stack_pointer
}
/** Bump the stack pointer for a west frame to make space for an allocation */
unsafe fn raw_alloc_west(&mut self, words: usize) -> *mut u64 {
let alloc = self.stack_pointer;
self.stack_pointer = self.stack_pointer.add(words);
alloc
}
/** Allocate space for an indirect pointer in an east frame */
unsafe fn indirect_alloc_east(&mut self, words: usize) -> *mut u64 {
self.raw_alloc_east(words + 2)
}
/** Allocate space for an indirect pointer in a west frame */
unsafe fn indirect_alloc_west(&mut self, words: usize) -> *mut u64 {
self.raw_alloc_west(words + 2)
}
/** Allocate space for an indirect pointer in a stack frame */
unsafe fn indirect_alloc(&mut self, words: usize) -> *mut u64 {
match &self.polarity {
Polarity::East => self.indirect_alloc_east(words),
Polarity::West => self.indirect_alloc_west(words),
}
}
unsafe fn struct_alloc_east<T>(&mut self, count: usize) -> *mut T {
self.raw_alloc_east(word_size_of::<T>() * count) as *mut T
}
unsafe fn struct_alloc_west<T>(&mut self, count: usize) -> *mut T {
self.raw_alloc_west(word_size_of::<T>() * count) as *mut T
}
unsafe fn struct_alloc<T>(&mut self, count: usize) -> *mut T {
match &self.polarity {
Polarity::East => self.struct_alloc_east::<T>(count),
Polarity::West => self.struct_alloc_west::<T>(count),
}
}
/** Copy a result noun and its subnouns from an east frame to its parent west frame
*
* This is a fairly standard copying collector algorithm where the from arena is the current
* (east) frame, and the to arena is the parent (west) frame.
*
* There can be references outside the current frame, but since only the current frame will be
* discarded these can be left in place and not copied. Since there are no recursive or mutable
* references, there cannot be references from outside the current frame into the current
* frame. Thus, once we have copied out nouns which are reachable from the given result noun
* and are in the current frame, we are done.
*
* Since our to-space is the previous frame, we maintain a work stack at the end of the current
* frame, past the allocations. This is inverse from when we do a noun traversal generally,
* where we may want to allocate on the current frame, so we maintain a work stack adjacent to
* the previous frame.
*/
unsafe fn copy_east(&mut self, noun: &mut Noun) {
let noun_ptr = noun as *mut Noun;
let work_start = self.stack_pointer;
let mut other_stack_pointer = *(self.previous_stack_pointer_pointer_east());
self.stack_pointer = self.stack_pointer.sub(2);
*(self.stack_pointer as *mut Noun) = *noun;
*(self.stack_pointer.add(1) as *mut *mut Noun) = noun_ptr;
loop {
if self.stack_pointer == work_start {
break;
}
// Pop a noun to copy from the stack
let next_noun = *(self.stack_pointer as *const Noun);
let next_dest = *(self.stack_pointer.add(1) as *const *mut Noun);
self.stack_pointer = self.stack_pointer.add(2);
// If it's a direct atom, just write it to the destination
// Otherwise we have allocations to make
match next_noun.as_either_direct_allocated() {
Either::Left(_direct) => {
*next_dest = next_noun;
}
Either::Right(allocated) => {
// If it's an allocated noun with a forwarding pointer, just write the
// noun resulting from the forwarding pointer to the destination
//
// Otherwise, we have to allocate space for and copy the allocated noun
match allocated.forwarding_pointer() {
Option::Some(new_allocated) => {
*next_dest = new_allocated.as_noun();
}
Option::None => {
if (allocated.to_raw_pointer() as *const u64) > work_start
&& (allocated.to_raw_pointer() as *const u64) < self.frame_pointer
{
match allocated.as_either() {
Either::Left(mut indirect) => {
// Make space for the atom
let new_indirect_alloc = other_stack_pointer;
other_stack_pointer =
other_stack_pointer.add(indirect_raw_size(indirect));
// Indirect atoms can be copied directly
copy_nonoverlapping(
indirect.to_raw_pointer(),
new_indirect_alloc,
indirect_raw_size(indirect),
);
// Set a forwarding pointer so we don't create duplicates from other
// references
indirect.set_forwarding_pointer(new_indirect_alloc);
*next_dest =
IndirectAtom::from_raw_pointer(new_indirect_alloc)
.as_noun();
}
Either::Right(mut cell) => {
// Make space for the cell
let new_cell_alloc = other_stack_pointer as *mut CellMemory;
other_stack_pointer =
other_stack_pointer.add(word_size_of::<CellMemory>());
// Copy the cell metadata
(*new_cell_alloc).metadata =
(*cell.to_raw_pointer()).metadata;
// Set the forwarding pointer
cell.set_forwarding_pointer(new_cell_alloc);
// Push the tail and the head to the work stack
self.stack_pointer = self.stack_pointer.sub(4);
*(self.stack_pointer as *mut Noun) = cell.tail();
*(self.stack_pointer.add(1) as *mut *mut Noun) =
&mut (*new_cell_alloc).tail;
*(self.stack_pointer.add(2) as *mut Noun) = cell.head();
*(self.stack_pointer.add(3) as *mut *mut Noun) =
&mut (*new_cell_alloc).head;
}
}
} else {
*next_dest = allocated.as_noun(); // Don't copy references outside the current frame
}
}
}
}
}
}
*self.previous_stack_pointer_pointer_east() = other_stack_pointer;
}
/** Copy a result noun and its subnouns from a west frame to its parent east frame
*
* This is a fairly standard copying collector algorithm where the from arena is the current
* (west) frame, and the to arena is the parent (east) frame.
*
* There can be references outside the current frame, but since only the current frame will be
* discarded these can be left in place and not copied. Since there are no recursive or mutable
* references, there cannot be references from outside the current frame into the current
* frame. Thus, once we have copied out nouns which are reachable from the given result noun
* and are in the current frame, we are done.
*
* Since our to-space is the previous frame, we maintain a work stack at the end of the current
* frame, past the allocations. This is inverse from when we do a noun traversal generally,
* where we may want to allocate on the current frame, so we maintain a work stack adjacent to
* the previous frame.
*/
unsafe fn copy_west(&mut self, noun: &mut Noun) {
let noun_ptr = noun as *mut Noun;
let work_start = self.stack_pointer;
let mut other_stack_pointer = *(self.previous_stack_pointer_pointer_west());
self.stack_pointer = self.stack_pointer.add(2);
*(self.stack_pointer.sub(2) as *mut Noun) = *noun;
*(self.stack_pointer.sub(1) as *mut *mut Noun) = noun_ptr;
loop {
if self.stack_pointer == work_start {
break;
}
// Pop a noun to copy from the stack
let next_noun = *(self.stack_pointer.sub(2) as *const Noun);
let next_dest = *(self.stack_pointer.sub(1) as *const *mut Noun);
self.stack_pointer = self.stack_pointer.sub(2);
// If it's a direct atom, just write it to the destination.
// Otherwise we have allocations to make.
match next_noun.as_either_direct_allocated() {
Either::Left(_direct) => {
*next_dest = next_noun;
}
Either::Right(allocated) => {
// If it's an allocated noun with a forwarding pointer, just write the
// noun resulting from the forwarding pointer to the destination
//
// Otherwise, we have to allocate space for and copy the allocated noun
match allocated.forwarding_pointer() {
Option::Some(new_allocated) => {
*next_dest = new_allocated.as_noun();
}
Option::None => {
if (allocated.to_raw_pointer() as *const u64) < work_start
&& (allocated.to_raw_pointer() as *const u64) > self.frame_pointer
{
match allocated.as_either() {
Either::Left(mut indirect) => {
// Make space for the atom
other_stack_pointer =
other_stack_pointer.sub(indirect_raw_size(indirect));
let new_indirect_alloc = other_stack_pointer;
// Indirect atoms can be copied directly
copy_nonoverlapping(
indirect.to_raw_pointer(),
new_indirect_alloc,
indirect_raw_size(indirect),
);
// Set a forwarding pointer so we don't create duplicates
// from other references
indirect.set_forwarding_pointer(new_indirect_alloc);
*next_dest =
IndirectAtom::from_raw_pointer(new_indirect_alloc)
.as_noun();
}
Either::Right(mut cell) => {
// Make space for the cell
other_stack_pointer =
other_stack_pointer.sub(word_size_of::<CellMemory>());
let new_cell_alloc = other_stack_pointer as *mut CellMemory;
// Copy the cell metadata
(*new_cell_alloc).metadata =
(*cell.to_raw_pointer()).metadata;
// Set the forwarding pointer
cell.set_forwarding_pointer(new_cell_alloc);
*(self.stack_pointer as *mut Noun) = cell.tail();
*(self.stack_pointer.add(1) as *mut *mut Noun) =
&mut (*new_cell_alloc).tail;
*(self.stack_pointer.add(2) as *mut Noun) = cell.head();
*(self.stack_pointer.add(3) as *mut *mut Noun) =
&mut (*new_cell_alloc).head;
self.stack_pointer = self.stack_pointer.add(4);
}
}
} else {
*next_dest = allocated.as_noun(); // Don't copy references outside the current frame
}
}
}
}
}
}
*self.previous_stack_pointer_pointer_west() = other_stack_pointer;
}
/** Pop a frame from the (east) stack, providing a result, which will be copied to the return target
* (west) frame. */
unsafe fn pop_east(&mut self, result: &mut Noun) {
self.copy_east(result);
self.pop_no_copy_east();
}
unsafe fn pop_no_copy_east(&mut self) {
self.stack_pointer = *self.previous_stack_pointer_pointer_east();
self.frame_pointer = *self.previous_frame_pointer_pointer_east();
self.polarity = Polarity::West;
}
/** Pop a frame from the (west) stack, providing a result, which will be copied to the return target
* (east) frame. */
unsafe fn pop_west(&mut self, result: &mut Noun) {
self.copy_west(result);
self.pop_no_copy_west();
}
unsafe fn pop_no_copy_west(&mut self) {
self.stack_pointer = *self.previous_stack_pointer_pointer_west();
self.frame_pointer = *self.previous_frame_pointer_pointer_west();
self.polarity = Polarity::East;
}
pub unsafe fn pop_no_copy(&mut self) {
match &self.polarity {
Polarity::East => self.pop_no_copy_east(),
Polarity::West => self.pop_no_copy_west(),
}
}
/** Pop a frame from the stack, providing a result, which will be copied to the return target
* frame. */
pub fn pop(&mut self, result: &mut Noun) {
unsafe {
match &self.polarity {
Polarity::East => self.pop_east(result),
Polarity::West => self.pop_west(result),
};
}
}
/** Push a frame onto the west stack with 0 or more local variable slots.
*
* (The method is `push_east` because the naming convention refers to the beginning state of
* the stack, not the final state.)
*/
unsafe fn push_east(&mut self, num_locals: usize) {
let previous_stack_pointer: *mut u64 = *self.previous_stack_pointer_pointer_east();
*previous_stack_pointer = self.stack_pointer as u64;
*(previous_stack_pointer.add(1)) = self.frame_pointer as u64;
self.stack_pointer = previous_stack_pointer.add(num_locals + 2);
self.frame_pointer = previous_stack_pointer;
self.polarity = Polarity::West;
}
/** Push a frame onto the east stack with 0 or more local variable slots.
*
* (The method is `push_west` because the naming convention refers to the beginning state of the
* stack, not the final state.)
*/
unsafe fn push_west(&mut self, num_locals: usize) {
let previous_stack_pointer: *mut u64 = *self.previous_stack_pointer_pointer_west();
*(previous_stack_pointer.sub(1)) = self.stack_pointer as u64;
*(previous_stack_pointer.sub(2)) = self.frame_pointer as u64;
self.stack_pointer = previous_stack_pointer.sub(num_locals + 2);
self.frame_pointer = previous_stack_pointer;
self.polarity = Polarity::East;
}
/** Push a frame onto the stack with 0 or more local variable slots. */
pub fn push(&mut self, num_locals: usize) {
unsafe {
match &self.polarity {
Polarity::East => self.push_east(num_locals),
Polarity::West => self.push_west(num_locals),
}
}
}
}
/** Unifying equality compares nouns for equality in the obvious way, and replaces a noun pointing
* to a more junior allocation with a noun pointing to a more senior allocation if the two are
* equal.
*
* This function is unsafe because it demands that all atoms be normalized: direct and indirect atoms
* will be considered non-equal without comparing their values, and indirects of different sizes
* will be considered non-equal.
*
* TODO: we really should try to tie lifetimes into the stack and use mut references instead of raw
* pointers wherever we can, but this is hard and delaying progress right now
*/
pub unsafe fn unifying_equality(stack: &mut NockStack, a: *mut Noun, b: *mut Noun) -> bool {
stack.push(1);
stack.save_prev_stack_pointer_to_local(0);
*(stack.alloc_in_previous_frame()) = (a, b);
loop {
if stack.prev_stack_pointer_equals_local(0) {
break;
} else {
let (x, y): (*mut Noun, *mut Noun) = *(stack.top_in_previous_frame());
match (
(*x).as_either_direct_allocated(),
(*y).as_either_direct_allocated(),
) {
(Left(x_direct), Left(y_direct)) => {
if x_direct.data() == y_direct.data() {
stack.reclaim_in_previous_frame::<(*mut Noun, *mut Noun)>();
continue;
} else {
break;
}
}
(Right(x_alloc), Right(y_alloc)) => {
match (x_alloc.get_cached_mug(), y_alloc.get_cached_mug()) {
(Some(x_mug), Some(y_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_as_ptr == y_as_ptr {
stack.reclaim_in_previous_frame::<(*mut Noun, *mut Noun)>();
continue;
} else if x_indirect.size() == y_indirect.size()
&& memcmp(
x_indirect.data_pointer() as *const c_void,
y_indirect.data_pointer() as *const c_void,
indirect_raw_size(x_indirect),
) == 0
{
let (_senior, junior) =
senior_pointer_first(stack, x_as_ptr, y_as_ptr);
// unify
if x_as_ptr == junior {
*x = *y;
} else {
*y = *x;
}
stack.reclaim_in_previous_frame::<(*mut Noun, *mut Noun)>();
continue;
} else {
break;
}
}
(Right(x_cell), Right(y_cell)) => {
let x_as_ptr = x_cell.to_raw_pointer();
let y_as_ptr = y_cell.to_raw_pointer();
if x_as_ptr == y_as_ptr {
continue;
} else {
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_no_copy();
continue;
} else {
*(stack.alloc_in_previous_frame()) =
(x_cell.tail_as_mut(), y_cell.tail_as_mut());
*(stack.alloc_in_previous_frame()) =
(x_cell.head_as_mut(), y_cell.tail_as_mut());
continue;
}
}
}
(_, _) => {
break;
}
}
}
(_, _) => {
break;
}
}
}
}
stack.restore_prev_stack_pointer_from_local(0);
stack.pop_no_copy();
(*a).raw_equals(*b)
}
unsafe fn senior_pointer_first<T>(
stack: &NockStack,
a: *const T,
b: *const T,
) -> (*const T, *const T) {
let mut polarity = stack.polarity;
let mut frame_pointer = stack.frame_pointer as *const u64;
let (mut high_pointer, mut low_pointer) = match polarity {
Polarity::East => (
stack.frame_pointer as *const T,
stack.stack_pointer as *const T,
),
Polarity::West => (
stack.stack_pointer as *const T,
stack.frame_pointer as *const T,
),
};
loop {
if a < high_pointer && a >= low_pointer {
// a is in the current frame
if b < high_pointer && b >= low_pointer {
// so is b, pick arbitrarily
break (a, b);
} else {
// b is not, so b must be further up, b is senior
break (b, a);
}
} else {
// a is not in the current frame
if b < high_pointer && b >= low_pointer {
// b is, a is senior
break (a, b);
} else {
// chase up the stack
if (frame_pointer as *const u64) == stack.start {
// we found the top of the stack!
break (a, b); // both are out of the stack, pick arbitrarily
} else {
match polarity {
Polarity::East => {
high_pointer = *(frame_pointer.sub(2)) as *const T;
low_pointer = *(frame_pointer.sub(1)) as *const T;
frame_pointer = *(frame_pointer.sub(1)) as *const u64;
polarity = Polarity::West;
continue;
}
Polarity::West => {
high_pointer = *frame_pointer as *const T;
low_pointer = *(frame_pointer.add(1)) as *const T;
frame_pointer = *frame_pointer as *const u64;
polarity = Polarity::West;
continue;
}
}
}
}
}
}
}
impl NounAllocator for NockStack {
unsafe fn alloc_indirect(&mut self, words: usize) -> *mut u64 {
self.indirect_alloc(words)
}
unsafe fn alloc_cell(&mut self) -> *mut CellMemory {
self.struct_alloc::<CellMemory>(1)
}
}

237
rust/iron-planet/src/mug.rs Normal file
View File

@ -0,0 +1,237 @@
use crate::mem::*;
use crate::noun::{Allocated, Atom, DirectAtom, Noun};
use either::Either::*;
use murmur3::murmur3_32;
use std::cmp::min;
use std::io::{Read, Result};
use std::ptr::{copy_nonoverlapping, write_bytes};
/** A reader for an atom which pads the atom out to a given length */
struct PaddedReadAtom {
atom_bytes: usize, // actual size of the stored atom
atom_base: *const u8, // pointer to the atom data
atom_cursor: usize, // How many bytes we have read
atom_len: usize, // The total padded length
}
impl PaddedReadAtom {
fn new(atom: Atom, len: usize) -> Self {
match atom.as_either() {
Left(direct) => PaddedReadAtom {
atom_bytes: 8,
atom_base: (&direct as *const DirectAtom) as *const u8,
atom_cursor: 0,
atom_len: len,
},
Right(indirect) => PaddedReadAtom {
atom_bytes: indirect.size() << 3, // size is in 64 bit words, multiply by 8 to get bytes
atom_base: indirect.data_pointer() as *const u8, // data pointer, but for bytes
atom_cursor: 0,
atom_len: len,
},
}
}
}
impl Read for PaddedReadAtom {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if self.atom_cursor >= self.atom_len {
Ok(0) // we are done
} else {
let req = buf.len(); // How many bytes does the reading caller want?
if self.atom_cursor < self.atom_bytes {
// are we still reading bytes from the atom?
let len = min(
self.atom_len - self.atom_cursor,
min(self.atom_bytes - self.atom_cursor, req),
);
// copy out bytes into the buffer, not running over the atom length itself, the
// padded length, or the buffer length
unsafe {
copy_nonoverlapping(
self.atom_base.add(self.atom_cursor),
buf.as_mut_ptr(),
len,
);
}
self.atom_cursor += len;
Ok(len)
} else {
// We are past the atom and into padding
let len = min(self.atom_len - self.atom_cursor, req);
// write 0s until we hit the buffer length or the padded length
unsafe {
write_bytes(buf.as_mut_ptr(), 0, len);
}
self.atom_cursor += len;
Ok(len)
}
}
}
}
// Murmur3 hash an atom with a given padded length
fn muk_u32(syd: u32, len: usize, key: Atom) -> u32 {
murmur3_32(&mut PaddedReadAtom::new(key, len), syd).expect("Murmur3 hashing failed.")
}
/** Byte size of an atom.
*
* Assumes atom is normalized
*/
fn met3_usize(atom: Atom) -> usize {
match atom.as_either() {
Left(direct) => (64 - (direct.data().leading_zeros() as usize) + 7) >> 3,
Right(indirect) => {
let last_word = unsafe { *(indirect.data_pointer().add(indirect.size() - 1)) };
let last_word_bytes = (64 - (last_word.leading_zeros() as usize) + 7) >> 3;
((indirect.size() - 1) << 3) + last_word_bytes
}
}
}
fn mum_u32(syd: u32, fal: u32, key: Atom) -> u32 {
let wyd = met3_usize(key);
let mut i = 0;
loop {
if i == 8 {
break fal;
} else {
let haz = muk_u32(syd, wyd, key);
let ham = (haz >> 31) ^ (haz & !(1 << 31));
if ham == 0 {
i += 1;
continue;
} else {
break ham;
}
}
}
}
fn calc_atom_mug_u32(atom: Atom) -> u32 {
mum_u32(0xCAFEBABE, 0x7FFF, atom)
}
/** Unsafe because this passes a direct atom to mum_u32 made by concatenating the two mugs,
* so we must ensure that the tail_mug conforms to the mug invariant and is only 31 bits
*/
unsafe fn calc_cell_mug_u32(head_mug: u32, tail_mug: u32) -> u32 {
let cat_mugs = (head_mug as u64) | ((tail_mug as u64) << 32);
mum_u32(
0xDEADBEEF,
0xFFFE,
DirectAtom::new_unchecked(cat_mugs).as_atom(),
) // this is safe on mugs since mugs are 31 bits
}
pub fn get_mug(noun: Noun) -> Option<u32> {
match noun.as_either_direct_allocated() {
Left(direct) => Some(calc_atom_mug_u32(direct.as_atom())),
Right(allocated) => allocated.get_cached_mug(),
}
}
const MASK_OUT_MUG: u64 = !(u32::MAX as u64);
unsafe fn set_mug(allocated: Allocated, mug: u32) {
let metadata = allocated.get_metadata();
allocated.set_metadata((metadata & MASK_OUT_MUG) | (mug as u64));
}
/** Calculate and cache the mug for a noun, but do *not* recursively calculate cache mugs for
* children of cells.
*
* If called on a cell with no mug cached for the head or tail, this function will return `None`.
*/
pub fn allocated_mug_u32_one(allocated: Allocated) -> Option<u32> {
match allocated.get_cached_mug() {
Some(mug) => Some(mug),
None => match allocated.as_either() {
Left(indirect) => {
let mug = calc_atom_mug_u32(indirect.as_atom());
unsafe {
set_mug(allocated, mug);
}
Some(mug)
}
Right(cell) => match (get_mug(cell.head()), get_mug(cell.tail())) {
(Some(head_mug), Some(tail_mug)) => {
let mug = unsafe { calc_cell_mug_u32(head_mug, tail_mug) };
unsafe {
set_mug(allocated, mug);
}
Some(mug)
}
_ => None,
},
},
}
}
pub fn mug_u32_one(noun: Noun) -> Option<u32> {
match noun.as_either_direct_allocated() {
Left(direct) => Some(calc_atom_mug_u32(direct.as_atom())),
Right(allocated) => allocated_mug_u32_one(allocated),
}
}
pub fn mug_u32(stack: &mut NockStack, noun: Noun) -> u32 {
stack.push(1);
unsafe {
stack.save_prev_stack_pointer_to_local(0);
*(stack.alloc_in_previous_frame()) = noun;
}
loop {
if unsafe { stack.prev_stack_pointer_equals_local(0) } {
break;
} else {
let noun: Noun = unsafe { *(stack.top_in_previous_frame()) };
match noun.as_either_direct_allocated() {
Left(_direct) => {
unsafe {
stack.reclaim_in_previous_frame::<Noun>();
}
continue;
} // no point in calculating a direct mug here as we wont cache it
Right(allocated) => match allocated.get_cached_mug() {
Some(_mug) => {
unsafe {
stack.reclaim_in_previous_frame::<Noun>();
}
continue;
}
None => match allocated.as_either() {
Left(indirect) => unsafe {
set_mug(allocated, calc_atom_mug_u32(indirect.as_atom()));
stack.reclaim_in_previous_frame::<Noun>();
continue;
},
Right(cell) => unsafe {
match (get_mug(cell.head()), get_mug(cell.tail())) {
(Some(head_mug), Some(tail_mug)) => {
set_mug(allocated, calc_cell_mug_u32(head_mug, tail_mug));
stack.reclaim_in_previous_frame::<Noun>();
continue;
}
_ => {
*(stack.alloc_in_previous_frame()) = cell.tail();
*(stack.alloc_in_previous_frame()) = cell.head();
continue;
}
}
},
},
},
}
}
}
unsafe {
stack.pop_no_copy();
get_mug(noun).expect("Noun should have a mug once it is mugged.")
}
}
pub fn mug(stack: &mut NockStack, noun: Noun) -> DirectAtom {
unsafe { DirectAtom::new_unchecked(mug_u32(stack, noun) as u64) }
}

View File

@ -0,0 +1,609 @@
use bitvec::prelude::{BitSlice, Lsb0};
use either::Either;
use std::ptr;
use std::slice::{from_raw_parts, from_raw_parts_mut};
/** Tag for a direct atom. */
const DIRECT_TAG: u64 = 0x0;
/** Tag mask for a direct atom. */
const DIRECT_MASK: u64 = !(u64::MAX >> 1);
/** Maximum value of a direct atom. Values higher than this must be represented by indirect atoms. */
pub const DIRECT_MAX: u64 = u64::MAX >> 1;
/** Tag for an indirect atom. */
const INDIRECT_TAG: u64 = u64::MAX & DIRECT_MASK;
/** Tag mask for an indirect atom. */
const INDIRECT_MASK: u64 = !(u64::MAX >> 2);
/** Tag for a cell. */
const CELL_TAG: u64 = u64::MAX & INDIRECT_MASK;
/** Tag mask for a cell. */
const CELL_MASK: u64 = !(u64::MAX >> 3);
/** Tag for a forwarding pointer */
const FORWARDING_TAG: u64 = u64::MAX & CELL_MASK;
/** Tag mask for a forwarding pointer */
const FORWARDING_MASK: u64 = CELL_MASK;
/** Test if a noun is a direct atom. */
fn is_direct_atom(noun: u64) -> bool {
noun & DIRECT_MASK == DIRECT_TAG
}
/** Test if a noun is an indirect atom. */
fn is_indirect_atom(noun: u64) -> bool {
noun & INDIRECT_MASK == INDIRECT_TAG
}
/** Test if a noun is a cell. */
fn is_cell(noun: u64) -> bool {
noun & CELL_MASK == CELL_TAG
}
/** A direct atom.
*
* Direct atoms represent an atom up to and including DIRECT_MAX as a machine word.
*/
#[derive(Copy, Clone)]
#[repr(packed(8))]
pub struct DirectAtom(u64);
impl DirectAtom {
/** Create a new direct atom, or panic if the value is greater than DIRECT_MAX */
pub const fn new_panic(value: u64) -> Self {
if value > DIRECT_MAX {
panic!("Number is greater than DIRECT_MAX")
} else {
DirectAtom(value)
}
}
/** Create a new direct atom, or return Err if the value is greater than DIRECT_MAX */
pub const fn new(value: u64) -> Result<Self, ()> {
if value > DIRECT_MAX {
Err(())
} else {
Ok(DirectAtom(value))
}
}
/** Create a new direct atom. This is unsafe because the value is not checked.
*
* Attempting to create a direct atom with a value greater than DIRECT_MAX will
* result in this value being interpreted by the runtime as a cell or indirect atom,
* with corresponding memory accesses. Thus, this function is marked as unsafe.
*/
pub const unsafe fn new_unchecked(value: u64) -> Self {
DirectAtom(value)
}
pub fn as_atom(self) -> Atom {
Atom { direct: self }
}
pub fn data(self) -> u64 {
self.0
}
pub fn as_bitslice<'a>(&'a self) -> &'a BitSlice<u64, Lsb0> {
&(BitSlice::from_element(&self.0))
}
}
/** An indirect atom.
*
* Indirect atoms represent atoms above DIRECT_MAX as a tagged pointer to a memory buffer
* structured as:
* - first word: metadata
* - second word: size in 64-bit words
* - remaining words: data
* Indirect atoms are always stored in little-endian byte order
*/
#[derive(Copy, Clone)]
#[repr(packed(8))]
pub struct IndirectAtom(u64);
impl IndirectAtom {
/** Tag the pointer and type it as an indirect atom. */
pub unsafe fn from_raw_pointer(ptr: *const u64) -> Self {
IndirectAtom((ptr as u64) >> 3 | INDIRECT_TAG)
}
/** Strip the tag from an indirect atom and return it as a mutable pointer to its memory buffer. */
unsafe fn to_raw_pointer_mut(&mut self) -> *mut u64 {
(self.0 << 3) as *mut u64
}
/** Strip the tag from an indirect atom and return it as a pointer to its memory buffer. */
pub unsafe fn to_raw_pointer(&self) -> *const u64 {
(self.0 << 3) as *const u64
}
pub unsafe fn set_forwarding_pointer(&mut self, new_me: *const u64) {
// This is OK because the size is stored as 64 bit words, not bytes.
// Thus, a true size value will never be larger than U64::MAX >> 3, and so
// any of the high bits set as an MSB
*self.to_raw_pointer_mut().add(1) = (new_me as u64) >> 3 | FORWARDING_TAG;
}
pub unsafe fn forwarding_pointer(&self) -> Option<IndirectAtom> {
let size_raw = *self.to_raw_pointer().add(1);
if size_raw & FORWARDING_MASK == FORWARDING_TAG {
// we can replace this by masking out the forwarding pointer and putting in the
// indirect tag
Some(Self::from_raw_pointer((size_raw << 3) as *const u64))
} else {
None
}
}
/** Make an indirect atom by copying from other memory.
*
* The size is specified in 64 bit words, not in bytes.
*/
pub unsafe fn new_raw(
allocator: &mut dyn NounAllocator,
size: usize,
data: *const u64,
) -> Self {
let (mut indirect, buffer) = Self::new_raw_mut(allocator, size);
ptr::copy_nonoverlapping(data, buffer.add(2), size);
*(indirect.normalize())
}
/** Make an indirect atom that can be written into. Return the atom (which should not be used
* until it is written and normalized) and a mutable pointer which is the data buffer for the
* indirect atom, to be written into.
*/
pub unsafe fn new_raw_mut(allocator: &mut dyn NounAllocator, size: usize) -> (Self, *mut u64) {
let buffer = allocator.alloc_indirect(size);
*buffer = 0;
*buffer.add(1) = size as u64;
(Self::from_raw_pointer(buffer), buffer.add(2))
}
/** Make an indirect atom that can be written into, and zero the whole data buffer.
* Return the atom (which should not be used until it is written and normalized) and a mutable
* pointer which is the data buffer for the indirect atom, to be written into.
*/
pub unsafe fn new_raw_mut_zeroed(
allocator: &mut dyn NounAllocator,
size: usize,
) -> (Self, *mut u64) {
let allocation = Self::new_raw_mut(allocator, size);
ptr::write_bytes(allocation.1, 0, size << 3);
allocation
}
/** Make an indirect atom that can be written into as a bitslice. The constraints of
* [new_raw_mut_zeroed] also apply here
*/
pub unsafe fn new_raw_mut_bitslice<'a>(
allocator: &mut dyn NounAllocator,
size: usize,
) -> (Self, &'a mut BitSlice<u64, Lsb0>) {
let (noun, ptr) = Self::new_raw_mut_zeroed(allocator, size);
(
noun,
BitSlice::from_slice_mut(from_raw_parts_mut(ptr, size)),
)
}
/** Size of an indirect atom in 64-bit words */
pub fn size(&self) -> usize {
unsafe { *(self.to_raw_pointer().add(1)) as usize }
}
/** Pointer to data for indirect atom */
pub fn data_pointer(&self) -> *const u64 {
unsafe { self.to_raw_pointer().add(2) as *const u64 }
}
pub fn as_slice<'a>(&'a self) -> &'a [u64] {
unsafe { from_raw_parts(self.data_pointer(), self.size()) }
}
pub fn as_byte_size<'a>(&'a self) -> &'a [u8] {
unsafe { from_raw_parts(self.data_pointer() as *const u8, self.size() << 3) }
}
/** BitSlice view on an indirect atom, with lifetime tied to reference to indirect atom. */
pub fn as_bitslice<'a>(&'a self) -> &'a BitSlice<u64, Lsb0> {
BitSlice::from_slice(self.as_slice())
}
/** Ensure that the size does not contain any trailing 0 words */
pub unsafe fn normalize(&mut self) -> &Self {
let mut index = self.size() - 1;
let data = self.data_pointer();
loop {
if index == 0 || *(data.add(index)) != 0 {
break;
}
index = index - 1;
}
*(self.to_raw_pointer_mut().add(1)) = (index + 1) as u64;
self
}
/** Normalize, but convert to direct atom if it will fit */
pub unsafe fn normalize_as_atom(&mut self) -> Atom {
self.normalize();
if self.size() == 1 && *(self.data_pointer()) <= DIRECT_MAX {
Atom {
direct: DirectAtom(*(self.data_pointer())),
}
} else {
Atom { indirect: *self }
}
}
pub fn as_atom(self) -> Atom {
Atom { indirect: self }
}
pub fn as_allocated(self) -> Allocated {
Allocated { indirect: self }
}
pub fn as_noun(self) -> Noun {
Noun { indirect: self }
}
}
/**
* A cell.
*
* A cell is represented by a tagged pointer to a memory buffer with metadata, a word describing
* the noun which is the cell's head, and a word describing a noun which is the cell's tail, each
* at a fixed offset.
*/
#[derive(Copy, Clone)]
#[repr(packed(8))]
pub struct Cell(u64);
impl Cell {
pub unsafe fn from_raw_pointer(ptr: *const CellMemory) -> Self {
Cell((ptr as u64) >> 3 | CELL_TAG)
}
pub unsafe fn to_raw_pointer(&self) -> *const CellMemory {
(self.0 << 3) as *const CellMemory
}
unsafe fn to_raw_pointer_mut(&mut self) -> *mut CellMemory {
(self.0 << 3) as *mut CellMemory
}
pub unsafe fn head_as_mut(mut self) -> *mut Noun {
&mut (*self.to_raw_pointer_mut()).head as *mut Noun
}
pub unsafe fn tail_as_mut<'a>(mut self) -> *mut Noun {
&mut (*self.to_raw_pointer_mut()).tail as *mut Noun
}
pub unsafe fn set_forwarding_pointer(&mut self, new_me: *const CellMemory) {
(*self.to_raw_pointer_mut()).head = Noun {
raw: (new_me as u64) >> 3 | FORWARDING_TAG,
}
}
pub unsafe fn forwarding_pointer(&self) -> Option<Cell> {
let head_raw = (*self.to_raw_pointer()).head.raw;
if head_raw & FORWARDING_MASK == FORWARDING_TAG {
// we can replace this by masking out the forwarding pointer and putting in the cell
// tag
Some(Self::from_raw_pointer((head_raw << 3) as *const CellMemory))
} else {
None
}
}
pub fn new(allocator: &mut dyn NounAllocator, head: Noun, tail: Noun) -> Cell {
unsafe {
let (cell, memory) = Self::new_raw_mut(allocator);
(*memory).head = head;
(*memory).tail = tail;
cell
}
}
pub unsafe fn new_raw_mut(allocator: &mut dyn NounAllocator) -> (Cell, *mut CellMemory) {
let memory = allocator.alloc_cell();
(*memory).metadata = 0;
(Self::from_raw_pointer(memory), memory)
}
pub fn head(&self) -> Noun {
unsafe { (*(self.to_raw_pointer())).head }
}
pub fn tail(&self) -> Noun {
unsafe { (*(self.to_raw_pointer())).tail }
}
pub fn as_allocated(&self) -> Allocated {
Allocated { cell: *self }
}
pub fn as_noun(&self) -> Noun {
Noun { cell: *self }
}
}
/**
* Memory representation of the contents of a cell
*/
#[derive(Copy, Clone)]
#[repr(packed(8))]
pub struct CellMemory {
pub metadata: u64,
pub head: Noun,
pub tail: Noun,
}
#[derive(Copy, Clone)]
#[repr(packed(8))]
pub union Atom {
raw: u64,
direct: DirectAtom,
indirect: IndirectAtom,
}
impl Atom {
pub fn new(allocator: &mut dyn NounAllocator, value: u64) -> Atom {
if value <= DIRECT_MAX {
unsafe { DirectAtom::new_unchecked(value).as_atom() }
} else {
unsafe { IndirectAtom::new_raw(allocator, 1, &value).as_atom() }
}
}
pub fn is_direct(&self) -> bool {
unsafe { is_direct_atom(self.raw) }
}
pub fn is_indirect(&self) -> bool {
unsafe { is_indirect_atom(self.raw) }
}
pub fn as_direct(&self) -> Result<DirectAtom, ()> {
if self.is_direct() {
unsafe { Ok(self.direct) }
} else {
Err(())
}
}
pub fn as_indirect(&self) -> Result<IndirectAtom, ()> {
if self.is_indirect() {
unsafe { Ok(self.indirect) }
} else {
Err(())
}
}
pub fn as_either(&self) -> Either<DirectAtom, IndirectAtom> {
if self.is_indirect() {
unsafe { Either::Right(self.indirect) }
} else {
unsafe { Either::Left(self.direct) }
}
}
pub fn as_bitslice<'a>(&'a self) -> &'a BitSlice<u64, Lsb0> {
if self.is_indirect() {
unsafe { self.indirect.as_bitslice() }
} else {
unsafe { &(self.direct.as_bitslice()) }
}
}
pub fn size(&self) -> usize {
match self.as_either() {
Either::Left(_direct) => 1,
Either::Right(indirect) => indirect.size(),
}
}
pub fn data_pointer(&self) -> *const u64 {
match self.as_either() {
Either::Left(direct) => (self as *const Atom) as *const u64,
Either::Right(indirect) => indirect.data_pointer(),
}
}
pub unsafe fn normalize(&mut self) -> Atom {
if self.is_indirect() {
self.indirect.normalize_as_atom()
} else {
*self
}
}
pub fn as_noun(self) -> Noun {
Noun { atom: self }
}
}
#[derive(Copy, Clone)]
#[repr(packed(8))]
pub union Allocated {
raw: u64,
indirect: IndirectAtom,
cell: Cell,
}
impl Allocated {
pub fn is_indirect(&self) -> bool {
unsafe { is_indirect_atom(self.raw) }
}
pub fn is_cell(&self) -> bool {
unsafe { is_cell(self.raw) }
}
pub unsafe fn to_raw_pointer(&self) -> *const u64 {
(self.raw << 3) as *const u64
}
pub unsafe fn to_raw_pointer_mut(&mut self) -> *mut u64 {
(self.raw << 3) as *mut u64
}
unsafe fn const_to_raw_pointer_mut(self) -> *mut u64 {
(self.raw << 3) as *mut u64
}
pub unsafe fn forwarding_pointer(&self) -> Option<Allocated> {
match self.as_either() {
Either::Left(indirect) => indirect.forwarding_pointer().map(|i| i.as_allocated()),
Either::Right(cell) => cell.forwarding_pointer().map(|c| c.as_allocated()),
}
}
pub unsafe fn get_metadata(&self) -> u64 {
*(self.to_raw_pointer() as *const u64)
}
pub unsafe fn set_metadata(self, metadata: u64) {
*(self.const_to_raw_pointer_mut() as *mut u64) = metadata;
}
pub fn as_either(&self) -> Either<IndirectAtom, Cell> {
if self.is_indirect() {
unsafe { Either::Left(self.indirect) }
} else {
unsafe { Either::Right(self.cell) }
}
}
pub fn as_noun(&self) -> Noun {
Noun { allocated: *self }
}
pub fn get_cached_mug(self: Allocated) -> Option<u32> {
unsafe {
let bottom_metadata = self.get_metadata() as u32 & 0x7FFFFFFF; // magic number: LS 31 bits
if bottom_metadata > 0 {
Some(bottom_metadata)
} else {
None
}
}
}
}
#[derive(Copy, Clone)]
#[repr(packed(8))]
pub union Noun {
raw: u64,
direct: DirectAtom,
indirect: IndirectAtom,
atom: Atom,
cell: Cell,
allocated: Allocated,
}
impl Noun {
pub fn is_direct(&self) -> bool {
unsafe { is_direct_atom(self.raw) }
}
pub fn is_indirect(&self) -> bool {
unsafe { is_indirect_atom(self.raw) }
}
pub fn is_atom(&self) -> bool {
self.is_direct() || self.is_indirect()
}
pub fn is_allocated(&self) -> bool {
self.is_indirect() || self.is_cell()
}
pub fn is_cell(&self) -> bool {
unsafe { is_cell(self.raw) }
}
pub fn as_direct(&self) -> Result<DirectAtom, ()> {
if self.is_direct() {
unsafe { Ok(self.direct) }
} else {
Err(())
}
}
pub fn as_indirect(&self) -> Result<IndirectAtom, ()> {
if self.is_indirect() {
unsafe { Ok(self.indirect) }
} else {
Err(())
}
}
pub fn as_cell(&self) -> Result<Cell, ()> {
if self.is_cell() {
unsafe { Ok(self.cell) }
} else {
Err(())
}
}
pub fn as_atom(&self) -> Result<Atom, ()> {
if self.is_atom() {
unsafe { Ok(self.atom) }
} else {
Err(())
}
}
pub fn as_allocated(&self) -> Result<Allocated, ()> {
if self.is_allocated() {
unsafe { Ok(self.allocated) }
} else {
Err(())
}
}
pub fn as_either_atom_cell(&self) -> Either<Atom, Cell> {
if self.is_cell() {
unsafe { Either::Right(self.cell) }
} else {
unsafe { Either::Left(self.atom) }
}
}
pub fn as_either_direct_allocated(&self) -> Either<DirectAtom, Allocated> {
if self.is_direct() {
unsafe { Either::Left(self.direct) }
} else {
unsafe { Either::Right(self.allocated) }
}
}
/** Are these the same noun */
pub unsafe fn raw_equals(self, other: Noun) -> bool {
self.raw == other.raw
}
}
/**
* An allocation object (probably a mem::NockStack) which can allocate a memory buffer sized to
* a certain number of nouns
*/
pub trait NounAllocator {
/** Allocate memory for some multiple of the size of a noun
*
* This should allocate *two more* `u64`s than `words` to make space for the size and metadata
*/
unsafe fn alloc_indirect(&mut self, words: usize) -> *mut u64;
/** Allocate memory for a cell */
unsafe fn alloc_cell(&mut self) -> *mut CellMemory;
}

View File

@ -0,0 +1,334 @@
use crate::mem::unifying_equality;
use crate::mem::NockStack;
use crate::mug::mug_u32;
use crate::noun::{Atom, Cell, DirectAtom, IndirectAtom, Noun};
use bitvec::prelude::{BitSlice, Lsb0};
use either::Either::{Left, Right};
use intmap::IntMap;
pub fn met0_usize(atom: Atom) -> usize {
let atom_bitslice = atom.as_bitslice();
match atom_bitslice.last_one() {
Some(last_one) => last_one + 1,
None => 0,
}
}
pub fn met0_u64_to_usize(x: u64) -> usize {
let usize_bitslice = BitSlice::<u64, Lsb0>::from_element(&x);
match usize_bitslice.last_one() {
Some(last_one) => last_one + 1,
None => 0,
}
}
pub fn cue(stack: &mut NockStack, buffer: Atom) -> Noun {
let buffer_bitslice = buffer.as_bitslice();
let mut cursor: usize = 0;
let mut backref_map = IntMap::<Noun>::new();
stack.push(2);
unsafe {
stack.save_prev_stack_pointer_to_local(0);
*(stack.alloc_in_previous_frame()) = stack.local_noun_pointer(1);
};
loop {
if unsafe { stack.prev_stack_pointer_equals_local(0) } {
let mut result = unsafe { *(stack.local_noun_pointer(1)) };
unsafe {
stack.pop(&mut result);
};
break result;
} else {
let dest_ptr: *mut Noun = unsafe { *(stack.top_in_previous_frame()) };
if buffer_bitslice[cursor] {
// 1 bit
if buffer_bitslice[cursor + 1] {
// 11 bits - cue backreference
cursor += 2;
unsafe {
*dest_ptr = *(backref_map
.get(rub_backref(&mut cursor, buffer_bitslice))
.expect("Invalid backref in cue"));
stack.reclaim_in_previous_frame::<*mut Noun>();
}
continue;
} else {
// 10 bits - cue cell
let backref = cursor;
cursor += 2;
unsafe {
let (cell, cell_mem_ptr) = Cell::new_raw_mut(stack);
*dest_ptr = cell.as_noun();
backref_map.insert(backref as u64, *dest_ptr);
stack.reclaim_in_previous_frame::<*mut Noun>();
*(stack.alloc_in_previous_frame::<*mut Noun>()) =
&mut ((*cell_mem_ptr).tail);
*(stack.alloc_in_previous_frame::<*mut Noun>()) =
&mut ((*cell_mem_ptr).head);
}
continue;
}
} else {
// 0 bit - cue atom
let backref = cursor;
cursor += 1;
unsafe {
*dest_ptr = rub_atom(stack, &mut cursor, buffer_bitslice).as_noun();
backref_map.insert(backref as u64, *dest_ptr);
stack.reclaim_in_previous_frame::<*mut Noun>();
};
continue;
}
};
}
}
// TODO: use first_zero() on a slice of the buffer
fn get_size(cursor: &mut usize, buffer: &BitSlice<u64, Lsb0>) -> usize {
let mut bitsize: usize = 0;
loop {
if buffer[*cursor + bitsize] {
break;
} else {
bitsize += 1;
continue;
}
}
if bitsize == 0 {
*cursor += 1;
0
} else {
let mut size: u64 = 0;
BitSlice::from_element_mut(&mut size)[0..bitsize - 1]
.copy_from_bitslice(&buffer[*cursor + bitsize + 1..*cursor + bitsize + bitsize]);
*cursor += bitsize + bitsize;
(size as usize) + (1 << (bitsize - 1))
}
}
fn rub_atom(stack: &mut NockStack, cursor: &mut usize, buffer: &BitSlice<u64, Lsb0>) -> Atom {
let size = get_size(cursor, buffer);
if size == 0 {
unsafe { DirectAtom::new_unchecked(0).as_atom() }
} else if size < 64 {
// fits in a direct atom
let mut direct_raw = 0;
BitSlice::from_element_mut(&mut direct_raw)[0..size]
.copy_from_bitslice(&buffer[*cursor..*cursor + size]);
*cursor += size;
unsafe { DirectAtom::new_unchecked(direct_raw).as_atom() }
} else {
// need an indirect atom
let wordsize = (size + 63) >> 6;
let (atom, slice) = unsafe { IndirectAtom::new_raw_mut_bitslice(stack, wordsize) }; // fast round to wordsize
slice[0..size].copy_from_bitslice(&buffer[*cursor..*cursor + size]);
atom.as_atom()
}
}
fn rub_backref(cursor: &mut usize, buffer: &BitSlice<u64, Lsb0>) -> u64 {
let size = get_size(cursor, buffer);
if size == 0 {
0
} else if size <= 64 {
let mut backref: u64 = 0;
BitSlice::from_element_mut(&mut backref)[0..size]
.copy_from_bitslice(&buffer[*cursor..*cursor + size]);
*cursor += size;
backref
} else {
panic!("Backreference size too big for vere")
}
}
struct JamState<'a> {
cursor: usize,
size: usize,
atom: IndirectAtom,
slice: &'a mut BitSlice<u64, Lsb0>,
}
pub fn jam(stack: &mut NockStack, noun: Noun) -> Atom {
let mut backref_map: IntMap<Vec<(Noun, u64)>> = IntMap::new();
let size = 8;
let (atom, slice) = unsafe { IndirectAtom::new_raw_mut_bitslice(stack, size) };
let mut state = JamState {
cursor: 0,
size: size,
atom: atom,
slice: slice,
};
stack.push(1);
unsafe {
stack.save_prev_stack_pointer_to_local(0);
*(stack.alloc_in_previous_frame()) = noun;
};
'jam: loop {
if unsafe { stack.prev_stack_pointer_equals_local(0) } {
break;
} else {
let mut noun = unsafe { *(stack.top_in_previous_frame::<Noun>()) };
let mug = mug_u32(stack, noun);
match backref_map.get_mut(mug as u64) {
None => {}
Some(backref_chain) => {
for (mut key, backref) in backref_chain {
if unsafe { unifying_equality(stack, &mut noun, &mut key) } {
match noun.as_either_atom_cell() {
Left(atom) => {
let atom_size = met0_usize(atom);
let backref_size = met0_u64_to_usize(*backref);
if atom_size <= backref_size {
jam_atom(stack, &mut state, atom);
} else {
jam_backref(stack, &mut state, *backref);
}
}
Right(_cell) => {
jam_backref(stack, &mut state, *backref);
}
}
unsafe {
stack.reclaim_in_previous_frame::<Noun>();
}
continue 'jam;
}
}
}
};
match noun.as_either_atom_cell() {
Left(atom) => {
let backref = state.cursor;
match backref_map.get_mut(mug as u64) {
None => {
backref_map.insert(mug as u64, vec![(noun, backref as u64)]);
}
Some(vec) => {
vec.push((noun, backref as u64));
}
};
jam_atom(stack, &mut state, atom);
unsafe {
stack.reclaim_in_previous_frame::<Noun>();
};
continue;
}
Right(cell) => {
let backref = state.cursor;
match backref_map.get_mut(mug as u64) {
None => {
backref_map.insert(mug as u64, vec![(noun, backref as u64)]);
}
Some(vec) => {
vec.push((noun, backref as u64));
}
};
jam_cell(stack, &mut state);
unsafe {
stack.reclaim_in_previous_frame::<Noun>();
*(stack.alloc_in_previous_frame()) = cell.tail();
*(stack.alloc_in_previous_frame()) = cell.head();
};
continue;
}
}
}
}
let mut result = unsafe { state.atom.normalize_as_atom().as_noun() };
stack.pop(&mut result);
result.as_atom().expect(
"IMPOSSIBLE: result was coerced from an atom so should not fail coercion to an atom",
)
}
fn jam_atom(traversal: &mut NockStack, state: &mut JamState, atom: Atom) {
loop {
if state.cursor + 1 > state.slice.len() {
double_atom_size(traversal, state);
} else {
break;
}
}
state.slice.set(state.cursor, false);
state.cursor += 1;
loop {
if let Ok(()) = mat(traversal, state, atom) {
break;
} else {
double_atom_size(traversal, state);
}
}
}
fn jam_cell(traversal: &mut NockStack, state: &mut JamState) {
loop {
if state.cursor + 2 > state.slice.len() {
double_atom_size(traversal, state);
} else {
break;
}
}
state.slice.set(state.cursor, true);
state.slice.set(state.cursor + 1, false);
state.cursor += 2;
}
fn jam_backref(traversal: &mut NockStack, state: &mut JamState, backref: u64) {
loop {
if state.cursor + 2 > state.slice.len() {
double_atom_size(traversal, state);
} else {
break;
}
}
state.slice.set(state.cursor, true);
state.slice.set(state.cursor + 1, true);
state.cursor += 2;
let backref_atom = Atom::new(traversal, backref);
loop {
if let Ok(()) = mat(traversal, state, backref_atom) {
break;
} else {
double_atom_size(traversal, state);
}
}
}
fn double_atom_size(traversal: &mut NockStack, state: &mut JamState) {
let new_size = state.size + state.size;
let (new_atom, new_slice) = unsafe { IndirectAtom::new_raw_mut_bitslice(traversal, new_size) };
new_slice[0..state.cursor].copy_from_bitslice(&state.slice[0..state.cursor]);
state.size = new_size;
state.atom = new_atom;
state.slice = new_slice;
}
// INVARIANT: mat must not modify state.cursor unless it will also return `Ok(())`
fn mat(traversal: &mut NockStack, state: &mut JamState, atom: Atom) -> Result<(), ()> {
let b_atom_size = met0_usize(atom);
let b_atom_size_atom = Atom::new(traversal, b_atom_size as u64);
if b_atom_size == 0 {
if state.cursor + 1 > state.slice.len() {
Err(())
} else {
state.slice.set(state.cursor, true);
state.cursor += 1;
Ok(())
}
} else {
let c_b_size = met0_usize(b_atom_size_atom);
if state.cursor + c_b_size + c_b_size + b_atom_size > state.slice.len() {
Err(())
} else {
state.slice[state.cursor..state.cursor + c_b_size].fill(false); // a 0 bit for each bit in the atom size
state.slice.set(state.cursor + c_b_size, true); // a terminating 1 bit
state.slice[state.cursor + c_b_size + 1..state.cursor + c_b_size + c_b_size]
.copy_from_bitslice(&b_atom_size_atom.as_bitslice()[0..c_b_size - 1]); // the atom size excepting the most significant 1 (since we know where that is from the size-of-the-size)
state.slice[state.cursor + c_b_size + c_b_size
..state.cursor + c_b_size + c_b_size + b_atom_size]
.copy_from_bitslice(&atom.as_bitslice()[0..b_atom_size]); // the atom itself
state.cursor += c_b_size + c_b_size + b_atom_size;
Ok(())
}
}
}

View File

@ -0,0 +1 @@
><3E>:9<><39><

14
rust/nix/sources.json Normal file
View File

@ -0,0 +1,14 @@
{
"nixpkgs": {
"branch": "release-22.05",
"description": "Nix Packages collection",
"homepage": "",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9ff65cb28c43236eac4cbbd20a5781581d9cbbf6",
"sha256": "1vfz9xdkpwp1bbp7p7abwl64sfdsg0g10hbvxsvma1jdz2pnxl5h",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/9ff65cb28c43236eac4cbbd20a5781581d9cbbf6.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}
}

194
rust/nix/sources.nix Normal file
View File

@ -0,0 +1,194 @@
# This file has been generated by Niv.
let
#
# The fetchers. fetch_<type> fetches specs of type <type>.
#
fetch_file = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; name = name'; }
else
pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
fetch_tarball = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
else
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
fetch_git = name: spec:
let
ref =
if spec ? ref then spec.ref else
if spec ? branch then "refs/heads/${spec.branch}" else
if spec ? tag then "refs/tags/${spec.tag}" else
abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
submodules = if spec ? submodules then spec.submodules else false;
submoduleArg =
let
nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0;
emptyArgWithWarning =
if submodules == true
then
builtins.trace
(
"The niv input \"${name}\" uses submodules "
+ "but your nix's (${builtins.nixVersion}) builtins.fetchGit "
+ "does not support them"
)
{}
else {};
in
if nixSupportsSubmodules
then { inherit submodules; }
else emptyArgWithWarning;
in
builtins.fetchGit
({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg);
fetch_local = spec: spec.path;
fetch_builtin-tarball = name: throw
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=tarball -a builtin=true'';
fetch_builtin-url = name: throw
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=file -a builtin=true'';
#
# Various helpers
#
# https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
sanitizeName = name:
(
concatMapStrings (s: if builtins.isList s then "-" else s)
(
builtins.split "[^[:alnum:]+._?=-]+"
((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
)
);
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources: system:
let
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
in
if builtins.hasAttr "nixpkgs" sources
then sourcesNixpkgs
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
import <nixpkgs> {}
else
abort
''
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
add a package called "nixpkgs" to your sources.json.
'';
# The actual fetching function.
fetch = pkgs: name: spec:
if ! builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then fetch_file pkgs name spec
else if spec.type == "tarball" then fetch_tarball pkgs name spec
else if spec.type == "git" then fetch_git name spec
else if spec.type == "local" then fetch_local spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
else if spec.type == "builtin-url" then fetch_builtin-url name
else
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# If the environment variable NIV_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
replace = name: drv:
let
saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
in
if ersatz == "" then drv else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
mapAttrs = builtins.mapAttrs or (
f: set: with builtins;
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatMapStrings = f: list: concatStrings (map f list);
concatStrings = builtins.concatStringsSep "";
# https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
optionalAttrs = cond: as: if cond then as else {};
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchTarball;
in
if lessThan nixVersion "1.12" then
fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
else
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchurl;
in
if lessThan nixVersion "1.12" then
fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
else
fetchurl attrs;
# Create the final "sources" from the config
mkSources = config:
mapAttrs (
name: spec:
if builtins.hasAttr "outPath" spec
then abort
"The values in sources.json should not have an 'outPath' attribute"
else
spec // { outPath = replace name (fetch config.pkgs name spec); }
) config.sources;
# The "config" used by the fetchers
mkConfig =
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
, sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
, system ? builtins.currentSystem
, pkgs ? mkPkgs sources system
}: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
};
in
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }

4
rust/shell.nix Normal file
View File

@ -0,0 +1,4 @@
{ sources ? import ./nix/sources.nix, pkgs ? import sources.nixpkgs {} }:
pkgs.mkShell {
packages = with pkgs; [ rustc cargo cargo-watch rustfmt ];
}