diff --git a/.github/workflows/ares-shared.yml b/.github/workflows/ares-shared.yml index 7c00c57..ac35f4b 100644 --- a/.github/workflows/ares-shared.yml +++ b/.github/workflows/ares-shared.yml @@ -36,7 +36,7 @@ jobs: -A clippy::missing_safety_doc - name: Build - run: cargo build --release --verbose + run: cargo build --release --verbose --features check_all - name: Run tests run: cargo test --verbose -- --test-threads=1 diff --git a/hoon/scaffolding/playpen.hoon b/hoon/scaffolding/playpen.hoon index 5b3c3df..0eb9e78 100644 --- a/hoon/scaffolding/playpen.hoon +++ b/hoon/scaffolding/playpen.hoon @@ -10,6 +10,7 @@ :: +$ cord @t +$ knot @ta ++$ term @tas +$ char @t +$ ship @p +$ life @ud @@ -54,9 +55,10 @@ [%palm p=(qual tape tape tape tape) q=(list tank)] [%rose p=(trel tape tape tape) q=(list tank)] == ++$ tang (list tank) +$ toon $% [%0 p=*] [%1 p=*] - [%2 p=(list tank)] + [%2 p=tang] == ++ tree |$ [node] $@(~ [n=node l=(tree node) r=(tree node)]) ++ gate $-(* *) diff --git a/hoon/scaffolding/toddler.hoon b/hoon/scaffolding/toddler.hoon index 0080f3e..577c9c7 100644 --- a/hoon/scaffolding/toddler.hoon +++ b/hoon/scaffolding/toddler.hoon @@ -8,39 +8,46 @@ != => |% - ++ cask |$ [a] (pair mark a) - +$ mark @tas +$ card (cask) + ++ cask |$ [a] (pair mark a) + +$ goof [mote=term =tang] + +$ mark @tas +$ ovum [=wire =card] + +$ vere [[non=@ta rev=path] kel=wynn] +$ wire path + +$ wasp + :: %crud: reroute $ovum with $goof + :: %wack: iterate entropy + :: %wyrd: check/record runtime kelvin stack + :: + $% [%crud =goof =ovum] + [%wack p=@uvJ] + [%wyrd p=vere] + == + +$ weft [lal=@tas num=@ud] + +$ wynn (list weft) :: mutually recursive Ackermann functions :: test turning %spot hints on/off - ++ wack + ++ ack :: re-enable %spot hints !: |= [m=@ud n=@ud] - :: %mean hint - ~_ [%leaf "I am a %mean hint via ~_ from +wack"] - :: %hela hint - ~> %hela :: %memo hint ~+ ?~ m +(n) ?~ n - (tack (dec m) 1) - (tack (dec m) $(n (dec n))) - ++ tack - :: disable %spot hints + (ack (dec m) 1) + (ack (dec m) $(n (dec n))) + ++ slow + |= x=@ud + !: + (slow-help x 0) + ++ slow-help + |= [x=@ud y=@ud] !. - |= [m=@ud n=@ud] - :: %hela hint - ~> %hela - :: %memo hint - ~+ - ?~ m +(n) - ?~ n - (wack (dec m) 1) - (wack (dec m) $(n (dec n))) + ?: .= x y + x + $(y .+(y)) -- => :: |% @@ -50,8 +57,26 @@ ++ poke |= [now=@da ovo=ovum] ^- ^ - ~> %slog.[0 leaf+(scow %ud (wack 2 1))] - [~ ..poke] + :: + ?. ?=(?(%crud %wack %wyrd) p.card.ovo) + ~> %slog.[0 leaf+(scow %ud (slow (bex 23)))] + [~ ..poke] + :: + =/ buz + ~> %mean.'pith: bad wasp' + ;;(wasp card.ovo) + ?+ -.buz + ~> %slog.[0 leaf+(scow %ud (ack 2 1))] + [~ ..poke] + :: + %crud + =/ tang tang.goof.buz + |- + ?~ tang + [~ ..poke] + ~> %slog.[0 -.tang] + $(tang +.tang) + == -- :: |= [now=@da ovo=ovum] diff --git a/rust/ares/Cargo.lock b/rust/ares/Cargo.lock index fb69b32..a99638f 100644 --- a/rust/ares/Cargo.lock +++ b/rust/ares/Cargo.lock @@ -20,11 +20,13 @@ dependencies = [ "either", "ibig", "intmap", + "lazy_static", "libc", "memmap", "murmur3", "num-derive", "num-traits", + "signal-hook", "static_assertions", ] @@ -569,6 +571,25 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/rust/ares/Cargo.toml b/rust/ares/Cargo.toml index 8989827..8988f91 100644 --- a/rust/ares/Cargo.toml +++ b/rust/ares/Cargo.toml @@ -9,22 +9,25 @@ edition = "2018" # [patch.crates-io] # ibig = { path = "../ibig-rs" } +# Please keep these alphabetized [dependencies] ares_macros = { path = "../ares_macros" } -bitvec = "1.0.0" -either = "1.9.0" -libc = "0.2.126" -murmur3 = { git = "https://github.com/tloncorp/murmur3", rev = "7878a0f" } -memmap = "0.7.0" -intmap = "1.1.0" -num-traits = "0.2" -num-derive = "0.3" -criterion = "0.4" -static_assertions = "1.1.0" -ibig = { path = "../ibig-rs" } assert_no_alloc = "1.1.2" # use this when debugging requires allocation (e.g. eprintln) # assert_no_alloc = {version="1.1.2", features=["warn_debug"]} +bitvec = "1.0.0" +criterion = "0.4" +either = "1.9.0" +ibig = { path = "../ibig-rs" } +intmap = "1.1.0" +lazy_static = "1.4.0" +libc = "0.2.126" +memmap = "0.7.0" +murmur3 = { git = "https://github.com/tloncorp/murmur3", rev = "7878a0f" } +num-derive = "0.3" +num-traits = "0.2" +signal-hook = "0.3" +static_assertions = "1.1.0" [build-dependencies] cc = "1.0.79" @@ -39,5 +42,9 @@ opt-level = 3 [profile.dev.package."*"] opt-level = 3 +# run with e.g. 'cargo build --features check_forwarding,check_acyclic' [features] -check_acyclic=[] +check_all = [ "check_acyclic", "check_forwarding", "check_junior" ] +check_acyclic = [] +check_forwarding = [] +check_junior = [] diff --git a/rust/ares/src/hamt.rs b/rust/ares/src/hamt.rs index 9087cf7..2f2fb28 100644 --- a/rust/ares/src/hamt.rs +++ b/rust/ares/src/hamt.rs @@ -104,7 +104,7 @@ impl MutHamt { mug >>= 5; match (*stem).entry(chunk) { None => { - let new_leaf_buffer = stack.struct_alloc(1); + let new_leaf_buffer = stack.struct_alloc::<(Noun, T)>(1); *new_leaf_buffer = (*n, t); (*stem).bitmap |= chunk_to_bit(chunk); (*stem).typemap &= !chunk_to_bit(chunk); @@ -480,7 +480,7 @@ impl Preserve for Hamt { }; *(stem.buffer.add(idx) as *mut Entry) = Entry { stem: new_stem }; assert!(traversal_depth <= 5); // will increment - traversal_stack[traversal_depth - 1].unwrap().1 = position + 1; + traversal_stack[traversal_depth - 1] = Some((stem, position + 1)); traversal_stack[traversal_depth] = Some((new_stem, 0)); traversal_depth += 1; continue 'preserve; diff --git a/rust/ares/src/interpreter.rs b/rust/ares/src/interpreter.rs index 8335ec9..aa70d36 100644 --- a/rust/ares/src/interpreter.rs +++ b/rust/ares/src/interpreter.rs @@ -1,14 +1,19 @@ +use crate::assert_acyclic; +use crate::assert_no_forwarding_pointers; +use crate::assert_no_junior_pointers; use crate::hamt::Hamt; -use crate::jets; -use crate::jets::nock::util::mook; +use crate::jets::JetErr; use crate::mem::unifying_equality; use crate::mem::NockStack; use crate::newt::Newt; +use crate::noun; use crate::noun::{Atom, Cell, IndirectAtom, Noun, Slots, D, T}; -use ares_macros::tas; +use crate::serf::TERMINATOR; use assert_no_alloc::assert_no_alloc; use bitvec::prelude::{BitSlice, Lsb0}; use either::Either::*; +use std::sync::atomic::Ordering; +use std::sync::Arc; crate::gdb!(); @@ -226,33 +231,67 @@ enum NockWork { Work11S(Nock11S), } +pub struct Context<'a, 'b, 'c> { + pub stack: &'a mut NockStack, + // For printing slogs; if None, print to stdout; Option slated to be removed + pub newt: Option<&'b mut Newt>, + // Per-event cache; option to share cache with virtualized events + pub cache: &'c mut Hamt, + // XX: persistent memo cache +} + +#[derive(Debug)] +pub enum Tone { + Blocked(Noun), + Error(NockErr, Noun), +} + #[derive(Debug)] pub enum NockErr { - Blocked(Noun), - Error(Noun), + Deterministic, + NonDeterministic, } impl From for () { fn from(_: NockErr) -> Self {} } +impl From for NockErr { + fn from(_: noun::Error) -> Self { + NockErr::Deterministic + } +} + +impl From for NockErr { + fn from(e: JetErr) -> Self { + match e { + JetErr::Deterministic => NockErr::Deterministic, + JetErr::NonDeterministic => NockErr::NonDeterministic, + JetErr::Punt => panic!("unhandled JetErr::Punt"), + } + } +} + +#[allow(unused_variables)] +fn debug_assertions(stack: &mut NockStack, noun: Noun) { + assert_acyclic!(noun); + assert_no_forwarding_pointers!(noun); + assert_no_junior_pointers!(stack, noun); +} + /** Interpret nock */ -pub fn interpret( - stack: &mut NockStack, - newt: &mut Option<&mut Newt>, // For printing slogs; if None, print to stdout - mut subject: Noun, - formula: Noun, -) -> Result { +pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Result { + let terminator = Arc::clone(&TERMINATOR); + let orig_subject = subject; // for debugging + let virtual_frame: *const u64 = context.stack.get_frame_pointer(); let mut res: Noun = D(0); - let mut cache = Hamt::::new(); - let virtual_frame = stack.get_frame_pointer(); // Setup stack for Nock computation unsafe { - stack.frame_push(1); + context.stack.frame_push(1); // Bottom of mean stack - *(stack.local_noun_pointer(0)) = D(0); - *stack.push() = NockWork::Done; + *(context.stack.local_noun_pointer(0)) = D(0); + *context.stack.push() = NockWork::Done; }; // DO NOT REMOVE THIS ASSERTION @@ -266,338 +305,389 @@ pub fn interpret( // ``` // // (See https://docs.rs/assert_no_alloc/latest/assert_no_alloc/#advanced-use) - let tone = assert_no_alloc(|| unsafe { - push_formula(stack, formula, true)?; + let nock = assert_no_alloc(|| unsafe { + push_formula(context.stack, formula, true)?; loop { - let work: NockWork = *stack.top(); + let work: NockWork = *context.stack.top(); match work { NockWork::Done => { - stack.preserve(&mut cache); - stack.preserve(&mut res); - stack.frame_pop(); + debug_assertions(context.stack, orig_subject); + debug_assertions(context.stack, subject); + debug_assertions(context.stack, res); + + context.stack.preserve(context.cache); + context.stack.preserve(&mut res); + context.stack.frame_pop(); + + debug_assertions(context.stack, orig_subject); + debug_assertions(context.stack, res); + break Ok(res); } NockWork::Ret => { - stack.preserve(&mut cache); - stack.preserve(&mut res); - stack.frame_pop(); + debug_assertions(context.stack, orig_subject); + debug_assertions(context.stack, subject); + debug_assertions(context.stack, res); + + context.stack.preserve(context.cache); + context.stack.preserve(&mut res); + context.stack.frame_pop(); + + debug_assertions(context.stack, orig_subject); + debug_assertions(context.stack, res); } NockWork::WorkCons(mut cons) => match cons.todo { TodoCons::ComputeHead => { cons.todo = TodoCons::ComputeTail; - *stack.top() = NockWork::WorkCons(cons); - push_formula(stack, cons.head, false)?; + *context.stack.top() = NockWork::WorkCons(cons); + push_formula(context.stack, cons.head, false)?; } TodoCons::ComputeTail => { cons.todo = TodoCons::Cons; cons.head = res; - *stack.top() = NockWork::WorkCons(cons); - push_formula(stack, cons.tail, false)?; + *context.stack.top() = NockWork::WorkCons(cons); + push_formula(context.stack, cons.tail, false)?; } TodoCons::Cons => { - res = T(stack, &[cons.head, res]); - stack.pop::(); + res = T(context.stack, &[cons.head, res]); + context.stack.pop::(); } }, NockWork::Work0(zero) => { if let Ok(noun) = subject.slot_atom(zero.axis) { res = noun; - stack.pop::(); + context.stack.pop::(); } else { - break Err(NockErr::Error(D(1))); + // Axis invalid for input Noun + break Err(NockErr::Deterministic); } } NockWork::Work1(once) => { res = once.noun; - stack.pop::(); + context.stack.pop::(); } - NockWork::Work2(mut vale) => match vale.todo { - Todo2::ComputeSubject => { - vale.todo = Todo2::ComputeFormula; - *stack.top() = NockWork::Work2(vale); - push_formula(stack, vale.subject, false)?; + NockWork::Work2(mut vale) => { + if (*terminator).load(Ordering::Relaxed) { + break Err(NockErr::NonDeterministic); } - Todo2::ComputeFormula => { - vale.todo = Todo2::ComputeResult; - vale.subject = res; - *stack.top() = NockWork::Work2(vale); - push_formula(stack, vale.formula, false)?; - } - Todo2::ComputeResult => { - if vale.tail { - stack.pop::(); + + match vale.todo { + Todo2::ComputeSubject => { + vale.todo = Todo2::ComputeFormula; + *context.stack.top() = NockWork::Work2(vale); + push_formula(context.stack, vale.subject, false)?; + } + Todo2::ComputeFormula => { + vale.todo = Todo2::ComputeResult; + vale.subject = res; + *context.stack.top() = NockWork::Work2(vale); + push_formula(context.stack, vale.formula, false)?; + } + Todo2::ComputeResult => { + if vale.tail { + context.stack.pop::(); + subject = vale.subject; + push_formula(context.stack, res, true)?; + } else { + vale.todo = Todo2::RestoreSubject; + std::mem::swap(&mut vale.subject, &mut subject); + *context.stack.top() = NockWork::Work2(vale); + + debug_assertions(context.stack, orig_subject); + debug_assertions(context.stack, subject); + debug_assertions(context.stack, res); + + mean_frame_push(context.stack, 0); + *context.stack.push() = NockWork::Ret; + push_formula(context.stack, res, true)?; + } + } + Todo2::RestoreSubject => { subject = vale.subject; - push_formula(stack, res, true)?; - } else { - vale.todo = Todo2::RestoreSubject; - std::mem::swap(&mut vale.subject, &mut subject); - *stack.top() = NockWork::Work2(vale); - mean_frame_push(stack, 0); - *stack.push() = NockWork::Ret; - push_formula(stack, res, true)?; + context.stack.pop::(); + + debug_assertions(context.stack, orig_subject); + debug_assertions(context.stack, subject); + debug_assertions(context.stack, res); } } - Todo2::RestoreSubject => { - subject = vale.subject; - stack.pop::(); - } - }, + } NockWork::Work3(mut thee) => match thee.todo { Todo3::ComputeChild => { thee.todo = Todo3::ComputeType; - *stack.top() = NockWork::Work3(thee); - push_formula(stack, thee.child, false)?; + *context.stack.top() = NockWork::Work3(thee); + push_formula(context.stack, thee.child, false)?; } Todo3::ComputeType => { res = if res.is_cell() { D(0) } else { D(1) }; - stack.pop::(); + context.stack.pop::(); } }, NockWork::Work4(mut four) => match four.todo { Todo4::ComputeChild => { four.todo = Todo4::Increment; - *stack.top() = NockWork::Work4(four); - push_formula(stack, four.child, false)?; + *context.stack.top() = NockWork::Work4(four); + push_formula(context.stack, four.child, false)?; } Todo4::Increment => { if let Ok(atom) = res.as_atom() { - res = inc(stack, atom).as_noun(); - stack.pop::(); + res = inc(context.stack, atom).as_noun(); + context.stack.pop::(); } else { // Cannot increment (Nock 4) a cell - break Err(NockErr::Error(D(2))); + break Err(NockErr::Deterministic); } } }, NockWork::Work5(mut five) => match five.todo { Todo5::ComputeLeftChild => { five.todo = Todo5::ComputeRightChild; - *stack.top() = NockWork::Work5(five); - push_formula(stack, five.left, false)?; + *context.stack.top() = NockWork::Work5(five); + push_formula(context.stack, five.left, false)?; } Todo5::ComputeRightChild => { five.todo = Todo5::TestEquals; five.left = res; - *stack.top() = NockWork::Work5(five); - push_formula(stack, five.right, false)?; + *context.stack.top() = NockWork::Work5(five); + push_formula(context.stack, five.right, false)?; } Todo5::TestEquals => { let saved_value_ptr = &mut five.left; - res = if unifying_equality(stack, &mut res, saved_value_ptr) { + res = if unifying_equality(context.stack, &mut res, saved_value_ptr) { D(0) } else { D(1) }; - stack.pop::(); + context.stack.pop::(); } }, NockWork::Work6(mut cond) => match cond.todo { Todo6::ComputeTest => { cond.todo = Todo6::ComputeBranch; - *stack.top() = NockWork::Work6(cond); - push_formula(stack, cond.test, false)?; + *context.stack.top() = NockWork::Work6(cond); + push_formula(context.stack, cond.test, false)?; } Todo6::ComputeBranch => { - stack.pop::(); + context.stack.pop::(); if let Left(direct) = res.as_either_direct_allocated() { if direct.data() == 0 { - push_formula(stack, cond.zero, cond.tail)?; + push_formula(context.stack, cond.zero, cond.tail)?; } else if direct.data() == 1 { - push_formula(stack, cond.once, cond.tail)?; + push_formula(context.stack, cond.once, cond.tail)?; } else { // Test branch of Nock 6 must return 0 or 1 - break Err(NockErr::Error(D(3))); + break Err(NockErr::Deterministic); } } else { // Test branch of Nock 6 must return a direct atom - break Err(NockErr::Error(D(4))); + break Err(NockErr::Deterministic); } } }, NockWork::Work7(mut pose) => match pose.todo { Todo7::ComputeSubject => { pose.todo = Todo7::ComputeResult; - *stack.top() = NockWork::Work7(pose); - push_formula(stack, pose.subject, false)?; + *context.stack.top() = NockWork::Work7(pose); + push_formula(context.stack, pose.subject, false)?; } Todo7::ComputeResult => { if pose.tail { - stack.pop::(); + context.stack.pop::(); subject = res; - push_formula(stack, pose.formula, true)?; + push_formula(context.stack, pose.formula, true)?; } else { pose.todo = Todo7::RestoreSubject; pose.subject = subject; - *stack.top() = NockWork::Work7(pose); + *context.stack.top() = NockWork::Work7(pose); subject = res; - push_formula(stack, pose.formula, false)?; + push_formula(context.stack, pose.formula, false)?; } } Todo7::RestoreSubject => { subject = pose.subject; - stack.pop::(); + context.stack.pop::(); } }, NockWork::Work8(mut pins) => match pins.todo { Todo8::ComputeSubject => { pins.todo = Todo8::ComputeResult; - *stack.top() = NockWork::Work8(pins); - push_formula(stack, pins.pin, false)?; + *context.stack.top() = NockWork::Work8(pins); + push_formula(context.stack, pins.pin, false)?; } Todo8::ComputeResult => { if pins.tail { - subject = T(stack, &[res, subject]); - stack.pop::(); - push_formula(stack, pins.formula, true)?; + subject = T(context.stack, &[res, subject]); + context.stack.pop::(); + push_formula(context.stack, pins.formula, true)?; } else { pins.todo = Todo8::RestoreSubject; pins.pin = subject; - *stack.top() = NockWork::Work8(pins); - subject = T(stack, &[res, subject]); - push_formula(stack, pins.formula, false)?; + *context.stack.top() = NockWork::Work8(pins); + subject = T(context.stack, &[res, subject]); + push_formula(context.stack, pins.formula, false)?; } } Todo8::RestoreSubject => { subject = pins.pin; - stack.pop::(); + context.stack.pop::(); } }, - NockWork::Work9(mut kale) => match kale.todo { - Todo9::ComputeCore => { - kale.todo = Todo9::ComputeResult; - *stack.top() = NockWork::Work9(kale); - push_formula(stack, kale.core, false)?; + NockWork::Work9(mut kale) => { + if (*terminator).load(Ordering::Relaxed) { + break Err(NockErr::NonDeterministic); } - Todo9::ComputeResult => { - if let Ok(formula) = res.slot_atom(kale.axis) { - if kale.tail { - stack.pop::(); - subject = res; - push_formula(stack, formula, true)?; + + match kale.todo { + Todo9::ComputeCore => { + kale.todo = Todo9::ComputeResult; + *context.stack.top() = NockWork::Work9(kale); + push_formula(context.stack, kale.core, false)?; + } + Todo9::ComputeResult => { + if let Ok(formula) = res.slot_atom(kale.axis) { + if kale.tail { + context.stack.pop::(); + subject = res; + push_formula(context.stack, formula, true)?; + } else { + kale.todo = Todo9::RestoreSubject; + kale.core = subject; + *context.stack.top() = NockWork::Work9(kale); + + debug_assertions(context.stack, orig_subject); + debug_assertions(context.stack, subject); + debug_assertions(context.stack, res); + + subject = res; + mean_frame_push(context.stack, 0); + *context.stack.push() = NockWork::Ret; + push_formula(context.stack, formula, true)?; + } } else { - kale.todo = Todo9::RestoreSubject; - kale.core = subject; - *stack.top() = NockWork::Work9(kale); - subject = res; - mean_frame_push(stack, 0); - *stack.push() = NockWork::Ret; - push_formula(stack, formula, true)?; + // Axis into core must be atom + break Err(NockErr::Deterministic); } - } else { - // Axis into core must be atom - break Err(NockErr::Error(D(5))); + } + Todo9::RestoreSubject => { + subject = kale.core; + context.stack.pop::(); + + debug_assertions(context.stack, orig_subject); + debug_assertions(context.stack, subject); + debug_assertions(context.stack, res); } } - Todo9::RestoreSubject => { - subject = kale.core; - stack.pop::(); - } - }, + } NockWork::Work10(mut diet) => { match diet.todo { Todo10::ComputeTree => { diet.todo = Todo10::ComputePatch; // should we compute patch then tree? - *stack.top() = NockWork::Work10(diet); - push_formula(stack, diet.tree, false)?; + *context.stack.top() = NockWork::Work10(diet); + push_formula(context.stack, diet.tree, false)?; } Todo10::ComputePatch => { diet.todo = Todo10::Edit; diet.tree = res; - *stack.top() = NockWork::Work10(diet); - push_formula(stack, diet.patch, false)?; + *context.stack.top() = NockWork::Work10(diet); + push_formula(context.stack, diet.patch, false)?; } Todo10::Edit => { - res = edit(stack, diet.axis.as_bitslice(), res, diet.tree); - stack.pop::(); + res = edit(context.stack, diet.axis.as_bitslice(), res, diet.tree); + context.stack.pop::(); } } } NockWork::Work11D(mut dint) => match dint.todo { Todo11D::ComputeHint => { - if let Some(found) = match_hint_pre_hint( - stack, newt, &cache, subject, dint.tag, dint.hint, dint.body, - ) { - res = found; - stack.pop::(); - } else { - dint.todo = Todo11D::ComputeResult; - *stack.top() = NockWork::Work11D(dint); - push_formula(stack, dint.hint, false)?; + match hint::match_pre_hint(context, subject, dint.tag, dint.hint, dint.body) + { + Ok(Some(found)) => { + res = found; + context.stack.pop::(); + } + Ok(None) => { + dint.todo = Todo11D::ComputeResult; + *context.stack.top() = NockWork::Work11D(dint); + push_formula(context.stack, dint.hint, false)?; + } + Err(err) => { + break Err(err); + } } } Todo11D::ComputeResult => { - dint.todo = Todo11D::Done; - if let Some(found) = match_hint_pre_nock( - stack, - newt, + match hint::match_pre_nock( + context, subject, dint.tag, - Some(dint.hint), + Some((dint.hint, res)), dint.body, - Some(res), ) { - res = found; - stack.pop::(); - } else { - dint.todo = Todo11D::Done; - if dint.tail { - stack.pop::(); - } else { - *stack.top() = NockWork::Work11D(dint); + Ok(Some(found)) => { + res = found; + context.stack.pop::(); + } + Ok(None) => { + dint.todo = Todo11D::Done; + if dint.tail { + context.stack.pop::(); + } else { + *context.stack.top() = NockWork::Work11D(dint); + } + push_formula(context.stack, dint.body, dint.tail)?; + } + Err(err) => { + break Err(err); } - push_formula(stack, dint.body, dint.tail)?; } } Todo11D::Done => { - if let Some(found) = match_hint_post_nock( - stack, - &mut cache, - subject, - dint.tag, - Some(dint.hint), - dint.body, - res, - ) { + if let Some(found) = + hint::match_post_nock(context, subject, dint.tag, dint.body, res) + { res = found; } - stack.pop::(); + context.stack.pop::(); } }, NockWork::Work11S(mut sint) => match sint.todo { Todo11S::ComputeResult => { - sint.todo = Todo11S::Done; - if let Some(found) = match_hint_pre_nock( - stack, newt, subject, sint.tag, None, sint.body, None, - ) { - res = found; - stack.pop::(); - } else { - sint.todo = Todo11S::Done; - if sint.tail { - stack.pop::(); - } else { - *stack.top() = NockWork::Work11S(sint); + match hint::match_pre_nock(context, subject, sint.tag, None, sint.body) { + Ok(Some(found)) => { + res = found; + context.stack.pop::(); + } + Ok(None) => { + sint.todo = Todo11S::Done; + if sint.tail { + context.stack.pop::(); + } else { + *context.stack.top() = NockWork::Work11S(sint); + } + push_formula(context.stack, sint.body, sint.tail)?; + } + Err(err) => { + break Err(err); } - push_formula(stack, sint.body, sint.tail)?; } } Todo11S::Done => { - if let Some(found) = match_hint_post_nock( - stack, &mut cache, subject, sint.tag, None, sint.body, res, - ) { + if let Some(found) = + hint::match_post_nock(context, subject, sint.tag, sint.body, res) + { res = found; } - stack.pop::(); + context.stack.pop::(); } }, }; } }); - match tone { + match nock { Ok(res) => Ok(res), - Err(_err) => Err(exit_early(stack, virtual_frame, &mut cache)), + Err(err) => Err(exit_early(context.stack, context.cache, virtual_frame, err)), } } @@ -621,7 +711,7 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), *stack.push() = NockWork::Work0(Nock0 { axis: axis_atom }); } else { // Axis for Nock 0 must be an atom - return Err(NockErr::Error(D(1))); + return Err(NockErr::Deterministic); } } 1 => { @@ -639,7 +729,7 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), }); } else { // Argument to Nock 2 must be cell - return Err(NockErr::Error(D(21))); + return Err(NockErr::Deterministic); }; } 3 => { @@ -663,7 +753,7 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), }); } else { // Argument to Nock 5 must be cell - return Err(NockErr::Error(D(51))); + return Err(NockErr::Deterministic); }; } 6 => { @@ -678,11 +768,11 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), }); } else { // Argument tail to Nock 6 must be cell - return Err(NockErr::Error(D(62))); + return Err(NockErr::Deterministic); }; } else { // Argument to Nock 6 must be cell - return Err(NockErr::Error(D(61))); + return Err(NockErr::Deterministic); } } 7 => { @@ -695,7 +785,7 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), }); } else { // Argument to Nock 7 must be cell - return Err(NockErr::Error(D(71))); + return Err(NockErr::Deterministic); }; } 8 => { @@ -708,7 +798,7 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), }); } else { // Argument to Nock 8 must be cell - return Err(NockErr::Error(D(81))); + return Err(NockErr::Deterministic); }; } 9 => { @@ -722,11 +812,11 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), }); } else { // Axis for Nock 9 must be an atom - return Err(NockErr::Error(D(92))); + return Err(NockErr::Deterministic); } } else { // Argument to Nock 9 must be cell - return Err(NockErr::Error(D(91))); + return Err(NockErr::Deterministic); }; } 10 => { @@ -741,15 +831,15 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), }); } else { // Axis for Nock 10 must be an atom - return Err(NockErr::Error(D(103))); + return Err(NockErr::Deterministic); } } else { // Heah of argument to Nock 10 must be a cell - return Err(NockErr::Error(D(102))); + return Err(NockErr::Deterministic); }; } else { // Argument to Nock 10 must be a cell - return Err(NockErr::Error(D(101))); + return Err(NockErr::Deterministic); }; } 11 => { @@ -760,7 +850,7 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), todo: Todo11S::ComputeResult, tag: tag_atom, body: arg_cell.tail(), - tail: tail && is_hint_tail(tag_atom), + tail: tail && hint::is_tail(tag_atom), }); } Right(hint_cell) => { @@ -770,39 +860,44 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), tag: tag_atom, hint: hint_cell.tail(), body: arg_cell.tail(), - tail: tail && is_hint_tail(tag_atom), + tail: tail && hint::is_tail(tag_atom), }); } else { // Hint tag must be an atom - return Err(NockErr::Error(D(112))); + return Err(NockErr::Deterministic); } } }; } else { // Argument for Nock 11 must be cell - return Err(NockErr::Error(D(111))); + return Err(NockErr::Deterministic); }; } _ => { // Invalid formula opcode - return Err(NockErr::Error(D(0))); + return Err(NockErr::Deterministic); } } } else { // Formula opcode must be direct atom - return Err(NockErr::Error(D(0))); + return Err(NockErr::Deterministic); } } } } else { // Bad formula: atoms are not formulas - return Err(NockErr::Error(D(0))); + return Err(NockErr::Deterministic); } } Ok(()) } -fn exit_early(stack: &mut NockStack, virtual_frame: *const u64, cache: &mut Hamt) -> NockErr { +pub fn exit_early( + stack: &mut NockStack, + cache: &mut Hamt, + virtual_frame: *const u64, + error: NockErr, +) -> Tone { unsafe { let mut trace = *(stack.local_noun_pointer(0)); while stack.get_frame_pointer() != virtual_frame { @@ -810,7 +905,7 @@ fn exit_early(stack: &mut NockStack, virtual_frame: *const u64, cache: &mut Hamt stack.preserve(cache); stack.frame_pop(); } - NockErr::Error(trace) + Tone::Error(error, trace) } } @@ -915,165 +1010,228 @@ pub fn inc(stack: &mut NockStack, atom: Atom) -> Atom { } } -fn is_hint_tail(tag: Atom) -> bool { - // XX: handle IndirectAtom tags - match tag.direct() { - #[allow(clippy::match_like_matches_macro)] - Some(dtag) => match dtag.data() { - tas!(b"fast") => false, - tas!(b"memo") => false, - _ => true, - }, - None => true, +mod hint { + use super::*; + use crate::jets; + use crate::jets::nock::util::mook; + use crate::mem::unifying_equality; + use crate::noun::{tape, Atom, Cell, Noun, D, T}; + use crate::serf::TERMINATOR; + use ares_macros::tas; + use std::sync::atomic::Ordering; + use std::sync::Arc; + + pub fn is_tail(tag: Atom) -> bool { + // XX: handle IndirectAtom tags + match tag.direct() { + #[allow(clippy::match_like_matches_macro)] + Some(dtag) => match dtag.data() { + tas!(b"fast") => false, + tas!(b"memo") => false, + _ => true, + }, + None => true, + } } -} -/** Match dynamic hints before the hint formula is evaluated */ -fn match_hint_pre_hint( - stack: &mut NockStack, - newt: &mut Option<&mut Newt>, - cache: &Hamt, - subject: Noun, - tag: Atom, - hint: Noun, - body: Noun, -) -> Option { - // XX: handle IndirectAtom tags - match tag.direct()?.data() { - // %sham hints are scaffolding until we have a real jet dashboard - tas!(b"sham") => { - let jet_formula = hint.cell()?; - // XX: what is the head here? - let jet_name = jet_formula.tail(); + /** Match dynamic hints before the hint formula is evaluated */ + pub fn match_pre_hint( + context: &mut Context, + subject: Noun, + tag: Atom, + hint: Noun, + body: Noun, + ) -> Result, NockErr> { + // XX: handle IndirectAtom tags + match tag.as_direct()?.data() { + // %sham hints are scaffolding until we have a real jet dashboard + tas!(b"sham") => { + let jet_formula = hint.as_cell()?; + // XX: what is the head here? + let jet_name = jet_formula.tail(); - let jet = jets::get_jet(jet_name)?; - if let Ok(mut jet_res) = jet(stack, newt, subject) { - // if in test mode, check that the jet returns the same result as the raw nock - if jets::get_jet_test_mode(jet_name) { - // Throw away trace because we'll regenerate it later, and this is in test mode - // so it's okay if it runs twice - interpret(stack, newt, subject, body) - .ok() - .map(|mut nock_res| { - if unsafe { !unifying_equality(stack, &mut nock_res, &mut jet_res) } { - eprintln!( - "\rJet {} failed, raw: {}, jetted: {}", - jet_name, nock_res, jet_res - ); - None + if let Some(jet) = jets::get_jet(jet_name) { + match jet(context, subject) { + Ok(mut jet_res) => { + // XX: simplify this by moving jet test mode into the 11 code in interpret, or into its own function? + // if in test mode, check that the jet returns the same result as the raw nock + if jets::get_jet_test_mode(jet_name) { + // XX: we throw away trace, which might matter for non-deterministic errors + // maybe mook and slog it? + match interpret(context, subject, body) { + Ok(mut nock_res) => { + let stack = &mut context.stack; + if unsafe { + !unifying_equality(stack, &mut nock_res, &mut jet_res) + } { + // XX: need string interpolation without allocation, then delete eprintln + // let tape = tape(stack, "jet mismatch in {}, raw: {}, jetted: {}", jet_name, nock_res, jet_res); + eprintln!( + "\rjet {} failed, raw: {:?}, jetted: {}", + jet_name, nock_res, jet_res + ); + let tape = tape(*stack, "jet mismatch"); + let mean = T(*stack, &[D(tas!(b"mean")), tape]); + mean_push(stack, mean); + Err(NockErr::Deterministic) + } else { + Ok(Some(nock_res)) + } + } + Err(Tone::Error(err, _)) => { + let stack = &mut context.stack; + // XX: need string interpolation without allocation, then delete eprintln + // let tape = tape(stack, "jet mismatch in {}, raw: {}, jetted: {}", jet_name, err, jet_res); + eprintln!( + "\rjet {} failed, raw: {:?}, jetted: {}", + jet_name, err, jet_res + ); + let tape = tape(*stack, "jet mismatch"); + let mean = T(*stack, &[D(tas!(b"mean")), tape]); + mean_push(stack, mean); + Err(err) + } + Err(Tone::Blocked(_)) => { + panic!("jet test mode: no scry handling") + } + } } else { - Some(jet_res) + Ok(Some(jet_res)) } - }) - .unwrap() + } + Err(JetErr::Punt) => Ok(None), + Err(err) => { + let stack = &mut context.stack; + // XX: need string interpolation without allocation + // let tape = tape(stack, "{} jet error in {}", err, jet_name); + let tape = tape(*stack, "jet error"); + let mean = T(*stack, &[D(tas!(b"mean")), tape]); + mean_push(stack, mean); + Err(err.into()) + } + } } else { - Some(jet_res) + Ok(None) } - } else { - // Print jet errors and punt to Nock - eprintln!("\rJet {} failed: ", jet_name); - None } + tas!(b"memo") => { + let stack = &mut context.stack; + let mut key = Cell::new(*stack, subject, body).as_noun(); + Ok(context.cache.lookup(stack, &mut key)) + } + _ => Ok(None), } - tas!(b"memo") => { - let mut key = Cell::new(stack, subject, body).as_noun(); - cache.lookup(stack, &mut key) - } - _ => None, } -} -/** Match static and dynamic hints before the nock formula is evaluated */ -fn match_hint_pre_nock( - stack: &mut NockStack, - newt: &mut Option<&mut Newt>, - _subject: Noun, - tag: Atom, - _hint: Option, - _body: Noun, - res: Option, -) -> Option { - // XX: assert Some(res) <=> Some(hint) - - // XX: handle IndirectAtom tags - match tag.direct()?.data() { - tas!(b"slog") => { - let slog_cell = res?.cell()?; - let pri = slog_cell.head().direct()?.data(); - let tank = slog_cell.tail(); - if let Some(not) = newt { - not.slog(stack, pri, tank); - } else { - eprintln!("raw slog: {} {}", pri, tank); + /** Match static and dynamic hints before the nock formula is evaluated */ + pub fn match_pre_nock( + context: &mut Context, + _subject: Noun, + tag: Atom, + hint: Option<(Noun, Noun)>, + _body: Noun, + ) -> Result, NockErr> { + // XX: handle IndirectAtom tags + match tag.as_direct()?.data() { + tas!(b"slog") => { + let (_, hint_res) = hint.ok_or(NockErr::Deterministic)?; + let slog_cell = hint_res.as_cell()?; + let pri = slog_cell.head().as_direct()?.data(); + let tank = slog_cell.tail(); + if context.newt.is_none() { + eprintln!("raw slog: {} {}", pri, tank); + } else { + context + .newt + .as_mut() + .unwrap() + .slog(context.stack, pri, tank); + } + Ok(None) } - } - tas!(b"hand") | tas!(b"hunk") | tas!(b"lose") | tas!(b"mean") | tas!(b"spot") => { - let noun = T(stack, &[tag.as_noun(), res?]); - mean_push(stack, noun); - } - tas!(b"hela") => { - // XX: should this be virtualized? - // pretty sure we should be bailing on error - // might need to switch return type to Result, NockErr> - let stak = unsafe { *(stack.local_noun_pointer(0)) }; - let tone = Cell::new(stack, D(2), stak); - - if let Ok(toon) = mook(stack, newt, tone, true) { - if unsafe { !toon.head().raw_equals(D(2)) } { - // Print jet error and punt to Nock - eprintln!("\r%hela failed: toon not %2"); - return None; + tas!(b"hand") | tas!(b"hunk") | tas!(b"lose") | tas!(b"mean") | tas!(b"spot") => { + let terminator = Arc::clone(&TERMINATOR); + if (*terminator).load(Ordering::Relaxed) { + return Err(NockErr::NonDeterministic); } - let mut list = toon.tail(); - loop { - if unsafe { list.raw_equals(D(0)) } { - break; + let stack = &mut context.stack; + let (_, hint_res) = hint.ok_or(NockErr::Deterministic)?; + let noun = T(*stack, &[tag.as_noun(), hint_res]); + mean_push(stack, noun); + Ok(None) + } + tas!(b"hela") => { + // XX: should this be virtualized? + // pretty sure we should be bailing on error + // might need to switch return type to Result, NockErr> + let mean = unsafe { *(context.stack.local_noun_pointer(0)) }; + let tone = Cell::new(context.stack, D(2), mean); + + match mook(context, tone, true) { + Ok(toon) => { + let stack = &mut context.stack; + if unsafe { !toon.head().raw_equals(D(2)) } { + let tape = tape(*stack, "%hela failed: toon not %2"); + let mean = T(*stack, &[D(tas!(b"mean")), tape]); + mean_push(stack, mean); + return Err(NockErr::Deterministic); + } + + let mut list = toon.tail(); + loop { + if unsafe { list.raw_equals(D(0)) } { + break; + } + + let cell = list.as_cell().unwrap(); + if context.newt.is_none() { + eprintln!("raw slog: {} {}", 0, cell.head()); + } else { + context.newt.as_mut().unwrap().slog(stack, 0, cell.head()); + } + + list = cell.tail(); + } + + Ok(None) } - - let cell = list.as_cell().unwrap(); - if let Some(not) = newt { - // XX: %hela priority is configurable, but I'm not sure how - not.slog(stack, 0, cell.head()); - } else { - eprintln!("raw slog: {} {}", 0, cell.head()); + Err(err) => { + let stack = &mut context.stack; + let tape = tape(*stack, "%hela failed: mook error"); + let mean = T(*stack, &[D(tas!(b"mean")), tape]); + mean_push(stack, mean); + Err(err.into()) } - - list = cell.tail(); } - } else { - // Print jet errors and punt to Nock - eprintln!("\r%hela failed: mook error"); - return None; } + _ => Ok(None), } - _ => {} } - None -} + /** Match static and dynamic hints after the nock formula is evaluated */ + pub fn match_post_nock( + context: &mut Context, + subject: Noun, + tag: Atom, + body: Noun, + res: Noun, + ) -> Option { + let stack = &mut context.stack; + let cache = &mut context.cache; -/** Match static and dynamic hints after the nock formula is evaluated */ -fn match_hint_post_nock( - stack: &mut NockStack, - cache: &mut Hamt, - subject: Noun, - tag: Atom, - _hint: Option, - body: Noun, - res: Noun, -) -> Option { - // XX: handle IndirectAtom tags - match tag.direct()?.data() { - tas!(b"memo") => { - let mut key = Cell::new(stack, subject, body).as_noun(); - *cache = cache.insert(stack, &mut key, res); + // XX: handle IndirectAtom tags + match tag.direct()?.data() { + tas!(b"memo") => { + let mut key = Cell::new(*stack, subject, body).as_noun(); + **cache = (*cache).insert(stack, &mut key, res); + } + tas!(b"hand") | tas!(b"hunk") | tas!(b"lose") | tas!(b"mean") | tas!(b"spot") => { + mean_pop(stack); + } + _ => {} } - tas!(b"hand") | tas!(b"hunk") | tas!(b"lose") | tas!(b"mean") | tas!(b"spot") => { - mean_pop(stack); - } - _ => {} + + None } - - None } diff --git a/rust/ares/src/jets.rs b/rust/ares/src/jets.rs index b058ed5..fdba605 100644 --- a/rust/ares/src/jets.rs +++ b/rust/ares/src/jets.rs @@ -7,6 +7,7 @@ pub mod sort; pub mod text; pub mod tree; +use crate::interpreter::Context; use crate::jets::bits::*; use crate::jets::form::*; use crate::jets::hash::*; @@ -16,7 +17,6 @@ use crate::jets::sort::*; use crate::jets::text::*; use crate::jets::tree::*; use crate::mem::NockStack; -use crate::newt::Newt; use crate::noun::{self, Noun, Slots}; use ares_macros::tas; use std::cmp; @@ -25,7 +25,7 @@ crate::gdb!(); /// Return Err if the computation crashed or should punt to Nock pub type Result = std::result::Result; -pub type Jet = fn(&mut NockStack, &mut Option<&mut Newt>, Noun) -> Result; +pub type Jet = fn(&mut Context, Noun) -> Result; /** * Only return a deterministic error if the Nock would have deterministically @@ -294,6 +294,7 @@ pub mod util { pub mod test { use super::*; + use crate::hamt::Hamt; use crate::mem::{unifying_equality, NockStack}; use crate::noun::{Atom, Noun, D, T}; use assert_no_alloc::assert_no_alloc; @@ -314,8 +315,13 @@ pub mod util { } pub fn assert_jet(stack: &mut NockStack, jet: Jet, sam: Noun, res: Noun) { - let sam = T(stack, &[D(0), sam, D(0)]); - let jet_res = assert_no_alloc(|| jet(stack, &mut None, sam).unwrap()); + let mut context = Context { + stack, + newt: None, + cache: &mut Hamt::::new(), + }; + let sam = T(context.stack, &[D(0), sam, D(0)]); + let jet_res = assert_no_alloc(|| jet(&mut context, sam).unwrap()); assert_noun_eq(stack, jet_res, res); } @@ -330,8 +336,13 @@ pub mod util { } pub fn assert_jet_err(stack: &mut NockStack, jet: Jet, sam: Noun, err: JetErr) { - let sam = T(stack, &[D(0), sam, D(0)]); - let jet_res = jet(stack, &mut None, sam); + let mut context = Context { + stack, + newt: None, + cache: &mut Hamt::::new(), + }; + let sam = T(context.stack, &[D(0), sam, D(0)]); + let jet_res = jet(&mut context, sam); assert!( jet_res.is_err(), "with sample: {}, expected err: {:?}, got: {:?}", diff --git a/rust/ares/src/jets/bits.rs b/rust/ares/src/jets/bits.rs index 57c4588..a12be17 100644 --- a/rust/ares/src/jets/bits.rs +++ b/rust/ares/src/jets/bits.rs @@ -1,10 +1,9 @@ /** Bit arithmetic & logic jets */ -use crate::jets; +use crate::interpreter::Context; use crate::jets::util::*; use crate::jets::JetErr::*; -use crate::mem::NockStack; -use crate::newt::Newt; +use crate::jets::Result; use crate::noun::{DirectAtom, IndirectAtom, Noun, D}; use std::cmp; @@ -14,20 +13,12 @@ crate::gdb!(); * Bit arithmetic */ -pub fn jet_bex( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_bex(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?.as_direct()?.data() as usize; - Ok(bex(stack, arg).as_noun()) + Ok(bex(context.stack, arg).as_noun()) } -pub fn jet_can( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_can(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let bloq = bloq(slot(arg, 2)?)?; let original_list = slot(arg, 3)?; @@ -52,7 +43,7 @@ pub fn jet_can( } else { unsafe { let (mut new_indirect, new_slice) = - IndirectAtom::new_raw_mut_bitslice(stack, bite_to_word(bloq, len)?); + IndirectAtom::new_raw_mut_bitslice(context.stack, bite_to_word(bloq, len)?); let mut pos = 0; let mut list = original_list; loop { @@ -74,11 +65,7 @@ pub fn jet_can( } } -pub fn jet_cat( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_cat(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let bloq = bloq(slot(arg, 2)?)?; let a = slot(arg, 6)?.as_atom()?; @@ -91,7 +78,8 @@ pub fn jet_cat( Ok(a.as_noun()) } else { unsafe { - let (mut new_indirect, new_slice) = IndirectAtom::new_raw_mut_bitslice(stack, new_len); + let (mut new_indirect, new_slice) = + IndirectAtom::new_raw_mut_bitslice(context.stack, new_len); chop(bloq, 0, len_a, 0, new_slice, a.as_bitslice())?; chop(bloq, 0, len_b, len_a, new_slice, b.as_bitslice())?; Ok(new_indirect.normalize_as_atom().as_noun()) @@ -99,11 +87,7 @@ pub fn jet_cat( } } -pub fn jet_cut( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_cut(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let bloq = bloq(slot(arg, 2)?)?; let start = slot(arg, 12)?.as_direct()?.data() as usize; @@ -116,18 +100,14 @@ pub fn jet_cut( let new_indirect = unsafe { let (mut new_indirect, new_slice) = - IndirectAtom::new_raw_mut_bitslice(stack, bite_to_word(bloq, run)?); + IndirectAtom::new_raw_mut_bitslice(context.stack, bite_to_word(bloq, run)?); chop(bloq, start, run, 0, new_slice, atom.as_bitslice())?; new_indirect.normalize_as_atom() }; Ok(new_indirect.as_noun()) } -pub fn jet_end( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_end(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let (bloq, step) = bite(slot(arg, 2)?)?; let a = slot(arg, 3)?.as_atom()?; @@ -139,18 +119,14 @@ pub fn jet_end( } else { unsafe { let (mut new_indirect, new_slice) = - IndirectAtom::new_raw_mut_bitslice(stack, bite_to_word(bloq, step)?); + IndirectAtom::new_raw_mut_bitslice(context.stack, bite_to_word(bloq, step)?); chop(bloq, 0, step, 0, new_slice, a.as_bitslice())?; Ok(new_indirect.normalize_as_atom().as_noun()) } } } -pub fn jet_lsh( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_lsh(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let (bloq, step) = bite(slot(arg, 2)?)?; let a = slot(arg, 3)?.as_atom()?; @@ -162,17 +138,13 @@ pub fn jet_lsh( let new_size = bits_to_word(checked_add(a.bit_size(), checked_left_shift(bloq, step)?)?)?; unsafe { - let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(stack, new_size); + let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(context.stack, new_size); chop(bloq, 0, len, step, dest, a.as_bitslice())?; Ok(atom.normalize_as_atom().as_noun()) } } -pub fn jet_met( - _stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_met(_context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let bloq = bloq(slot(arg, 2)?)?; let a = slot(arg, 3)?.as_atom()?; @@ -180,11 +152,7 @@ pub fn jet_met( Ok(D(met(bloq, a) as u64)) } -pub fn jet_rap( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_rap(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let bloq = bloq(slot(arg, 2)?)?; let original_list = slot(arg, 3)?; @@ -207,7 +175,7 @@ pub fn jet_rap( } else { unsafe { let (mut new_indirect, new_slice) = - IndirectAtom::new_raw_mut_bitslice(stack, bite_to_word(bloq, len)?); + IndirectAtom::new_raw_mut_bitslice(context.stack, bite_to_word(bloq, len)?); let mut pos = 0; let mut list = original_list; @@ -230,11 +198,7 @@ pub fn jet_rap( } } -pub fn jet_rep( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_rep(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let (bloq, step) = bite(slot(arg, 2)?)?; let original_list = slot(arg, 3)?; @@ -257,7 +221,7 @@ pub fn jet_rep( } else { unsafe { let (mut new_indirect, new_slice) = - IndirectAtom::new_raw_mut_bitslice(stack, bite_to_word(bloq, len)?); + IndirectAtom::new_raw_mut_bitslice(context.stack, bite_to_word(bloq, len)?); let mut pos = 0; let mut list = original_list; loop { @@ -277,11 +241,7 @@ pub fn jet_rep( } } -pub fn jet_rev( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_rev(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let boz = slot(arg, 2)?.as_atom()?.as_direct()?.data(); @@ -301,7 +261,7 @@ pub fn jet_rev( let mut output = if dat.is_direct() && bits < 64 { unsafe { DirectAtom::new_unchecked(0).as_atom() } } else { - unsafe { IndirectAtom::new_raw(stack, ((bits + 7) / 8) as usize, &0).as_atom() } + unsafe { IndirectAtom::new_raw(context.stack, ((bits + 7) / 8) as usize, &0).as_atom() } }; let src = dat.as_bitslice(); @@ -317,22 +277,14 @@ pub fn jet_rev( Ok(unsafe { output.normalize() }.as_noun()) } -pub fn jet_rip( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_rip(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let (bloq, step) = bite(slot(arg, 2)?)?; let atom = slot(arg, 3)?.as_atom()?; - rip(stack, bloq, step, atom) + rip(context.stack, bloq, step, atom) } -pub fn jet_rsh( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_rsh(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let (bloq, step) = bite(slot(arg, 2)?)?; let a = slot(arg, 3)?.as_atom()?; @@ -344,7 +296,7 @@ pub fn jet_rsh( let new_size = bits_to_word(checked_sub(a.bit_size(), checked_left_shift(bloq, step)?)?)?; unsafe { - let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(stack, new_size); + let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(context.stack, new_size); chop(bloq, step, len - step, 0, dest, a.as_bitslice())?; Ok(atom.normalize_as_atom().as_noun()) } @@ -354,23 +306,15 @@ pub fn jet_rsh( * Bit logic */ -pub fn jet_con( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_con(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; - Ok(con(stack, a, b).as_noun()) + Ok(con(context.stack, a, b).as_noun()) } -pub fn jet_dis( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_dis(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -378,7 +322,7 @@ pub fn jet_dis( let new_size = cmp::max(a.size(), b.size()); unsafe { - let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(stack, new_size); + let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(context.stack, new_size); let a_bit = a.as_bitslice(); dest[..a_bit.len()].copy_from_bitslice(a_bit); *dest &= b.as_bitslice(); @@ -386,11 +330,7 @@ pub fn jet_dis( } } -pub fn jet_mix( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_mix(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -398,7 +338,7 @@ pub fn jet_mix( let new_size = cmp::max(a.size(), b.size()); unsafe { - let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(stack, new_size); + let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(context.stack, new_size); let a_bit = a.as_bitslice(); dest[..a_bit.len()].copy_from_bitslice(a_bit); *dest ^= b.as_bitslice(); diff --git a/rust/ares/src/jets/form.rs b/rust/ares/src/jets/form.rs index 494e15e..8e819ae 100644 --- a/rust/ares/src/jets/form.rs +++ b/rust/ares/src/jets/form.rs @@ -1,17 +1,16 @@ -use crate::jets::util::slot; /** Formatting jets */ +use crate::interpreter::Context; +use crate::jets::util::slot; use crate::jets::Result; -use crate::mem::NockStack; -use crate::newt::Newt; use crate::noun::Noun; crate::gdb!(); -pub fn jet_scow(stack: &mut NockStack, _newt: &mut Option<&mut Newt>, subject: Noun) -> Result { +pub fn jet_scow(context: &mut Context, subject: Noun) -> Result { let aura = slot(subject, 12)?.as_direct()?; let atom = slot(subject, 13)?.as_atom()?; - util::scow(stack, aura, atom) + util::scow(context.stack, aura, atom) } pub mod util { diff --git a/rust/ares/src/jets/hash.rs b/rust/ares/src/jets/hash.rs index a58262b..f622319 100644 --- a/rust/ares/src/jets/hash.rs +++ b/rust/ares/src/jets/hash.rs @@ -1,17 +1,16 @@ -use crate::jets::util::*; /** Hash jets */ +use crate::interpreter::Context; +use crate::jets::util::*; use crate::jets::Result; -use crate::mem::NockStack; use crate::mug::mug; -use crate::newt::Newt; use crate::noun::Noun; crate::gdb!(); -pub fn jet_mug(stack: &mut NockStack, _newt: &mut Option<&mut Newt>, subject: Noun) -> Result { +pub fn jet_mug(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; - Ok(mug(stack, arg).as_noun()) + Ok(mug(context.stack, arg).as_noun()) } #[cfg(test)] diff --git a/rust/ares/src/jets/math.rs b/rust/ares/src/jets/math.rs index fa27142..201eb3f 100644 --- a/rust/ares/src/jets/math.rs +++ b/rust/ares/src/jets/math.rs @@ -12,11 +12,10 @@ * Another approach is use a global custom allocator. This is fairly involved, but it would allow * us to use any library without worrying whether it allocates. */ -use crate::jets; +use crate::interpreter::Context; use crate::jets::util::*; use crate::jets::JetErr::*; -use crate::mem::NockStack; -use crate::newt::Newt; +use crate::jets::Result; use crate::noun::{Atom, DirectAtom, IndirectAtom, Noun, D, DIRECT_MAX, NO, T, YES}; use either::{Left, Right}; use ibig::ops::DivRem; @@ -24,30 +23,23 @@ use ibig::UBig; crate::gdb!(); -pub fn jet_add( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_add(context: &mut Context, subject: Noun) -> Result { + let stack = &mut context.stack; let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) { - Ok(Atom::new(stack, a.data() + b.data()).as_noun()) + Ok(Atom::new(*stack, a.data() + b.data()).as_noun()) } else { - let a_big = a.as_ubig(stack); - let b_big = b.as_ubig(stack); - let res = UBig::add_stack(stack, a_big, b_big); - Ok(Atom::from_ubig(stack, &res).as_noun()) + let a_big = a.as_ubig(*stack); + let b_big = b.as_ubig(*stack); + let res = UBig::add_stack(*stack, a_big, b_big); + Ok(Atom::from_ubig(*stack, &res).as_noun()) } } -pub fn jet_dec( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_dec(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; if let Ok(atom) = arg.as_atom() { match atom.as_either() { @@ -65,8 +57,9 @@ pub fn jet_dec( panic!("Decrementing 0 stored as an indirect atom"); } Some(first_one) => { - let (mut new_indirect, new_slice) = - unsafe { IndirectAtom::new_raw_mut_bitslice(stack, indirect.size()) }; + let (mut new_indirect, new_slice) = unsafe { + IndirectAtom::new_raw_mut_bitslice(context.stack, indirect.size()) + }; if first_one > 0 { new_slice[..first_one].fill(true); } @@ -84,11 +77,8 @@ pub fn jet_dec( } } -pub fn jet_div( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_div(context: &mut Context, subject: Noun) -> Result { + let stack = &mut context.stack; let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -98,18 +88,15 @@ pub fn jet_div( } else if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) { Ok(unsafe { DirectAtom::new_unchecked(a.data() / b.data()) }.as_noun()) } else { - let a_big = a.as_ubig(stack); - let b_big = b.as_ubig(stack); - let res = UBig::div_stack(stack, a_big, b_big); - Ok(Atom::from_ubig(stack, &res).as_noun()) + let a_big = a.as_ubig(*stack); + let b_big = b.as_ubig(*stack); + let res = UBig::div_stack(*stack, a_big, b_big); + Ok(Atom::from_ubig(*stack, &res).as_noun()) } } -pub fn jet_dvr( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_dvr(context: &mut Context, subject: Noun) -> Result { + let stack = &mut context.stack; let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -126,22 +113,18 @@ pub fn jet_dvr( ) } } else { - let (div, rem) = a.as_ubig(stack).div_rem(b.as_ubig(stack)); + let (div, rem) = a.as_ubig(*stack).div_rem(b.as_ubig(*stack)); ( - Atom::from_ubig(stack, &div).as_noun(), - Atom::from_ubig(stack, &rem).as_noun(), + Atom::from_ubig(*stack, &div).as_noun(), + Atom::from_ubig(*stack, &rem).as_noun(), ) }; - Ok(T(stack, &[div, rem])) + Ok(T(*stack, &[div, rem])) } } -pub fn jet_gte( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_gte(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -156,18 +139,14 @@ pub fn jet_gte( YES } else if a.bit_size() < b.bit_size() { NO - } else if a.as_ubig(stack) >= b.as_ubig(stack) { + } else if a.as_ubig(context.stack) >= b.as_ubig(context.stack) { YES } else { NO }) } -pub fn jet_gth( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_gth(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -182,18 +161,14 @@ pub fn jet_gth( YES } else if a.bit_size() < b.bit_size() { NO - } else if a.as_ubig(stack) > b.as_ubig(stack) { + } else if a.as_ubig(context.stack) > b.as_ubig(context.stack) { YES } else { NO }) } -pub fn jet_lte( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_lte(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -208,7 +183,7 @@ pub fn jet_lte( YES } else if a.bit_size() > b.bit_size() { NO - } else if a.as_ubig(stack) <= b.as_ubig(stack) { + } else if a.as_ubig(context.stack) <= b.as_ubig(context.stack) { YES } else { NO @@ -227,11 +202,8 @@ pub fn jet_lth( Ok(util::lth(a, b)) } -pub fn jet_mod( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_mod(context: &mut Context, subject: Noun) -> Result { + let stack = &mut context.stack; let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -241,16 +213,13 @@ pub fn jet_mod( } else if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) { Ok(unsafe { DirectAtom::new_unchecked(a.data() % b.data()) }.as_noun()) } else { - let res = a.as_ubig(stack) % b.as_ubig(stack); - Ok(Atom::from_ubig(stack, &res).as_noun()) + let res = a.as_ubig(*stack) % b.as_ubig(*stack); + Ok(Atom::from_ubig(*stack, &res).as_noun()) } } -pub fn jet_mul( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_mul(context: &mut Context, subject: Noun) -> Result { + let stack = &mut context.stack; let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; @@ -258,11 +227,11 @@ pub fn jet_mul( if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) { let res = a.data() as u128 * b.data() as u128; if res < DIRECT_MAX as u128 { - Ok(Atom::new(stack, res as u64).as_noun()) + Ok(Atom::new(*stack, res as u64).as_noun()) } else { Ok(unsafe { IndirectAtom::new_raw_bytes( - stack, + *stack, if res < u64::MAX as u128 { 8 } else { 16 }, &res as *const u128 as *const u8, ) @@ -270,23 +239,19 @@ pub fn jet_mul( .as_noun()) } } else { - let a_big = a.as_ubig(stack); - let b_big = b.as_ubig(stack); - let res = UBig::mul_stack(stack, a_big, b_big); - Ok(Atom::from_ubig(stack, &res).as_noun()) + let a_big = a.as_ubig(*stack); + let b_big = b.as_ubig(*stack); + let res = UBig::mul_stack(*stack, a_big, b_big); + Ok(Atom::from_ubig(*stack, &res).as_noun()) } } -pub fn jet_sub( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_sub(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let a = slot(arg, 2)?.as_atom()?; let b = slot(arg, 3)?.as_atom()?; - Ok(sub(stack, a, b)?.as_noun()) + Ok(sub(context.stack, a, b)?.as_noun()) } pub mod util { diff --git a/rust/ares/src/jets/nock.rs b/rust/ares/src/jets/nock.rs index 6c50e7e..ed22fc8 100644 --- a/rust/ares/src/jets/nock.rs +++ b/rust/ares/src/jets/nock.rs @@ -1,44 +1,30 @@ /** Virtualization jets */ -use crate::interpreter::{interpret, NockErr}; -use crate::jets; +use crate::interpreter::Context; use crate::jets::util::slot; -use crate::mem::NockStack; -use crate::newt::Newt; -use crate::noun::{Noun, D, T}; +use crate::jets::Result; +use crate::noun::Noun; crate::gdb!(); -// XX: interpret should accept optional scry function and potentially produce blocked -pub fn jet_mink( - stack: &mut NockStack, - newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +// XX: interpret should accept optional scry function and potentially produce blocked +pub fn jet_mink(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; // mink sample = [nock scry_namespace] // = [[subject formula] scry_namespace] let v_subject = slot(arg, 4)?; let v_formula = slot(arg, 5)?; let _scry = slot(arg, 3)?; - - // XX: NonDeterministic errors need to bail here, too - match interpret(stack, newt, v_subject, v_formula) { - Ok(res) => Ok(T(stack, &[D(0), res])), - Err(err) => match err { - NockErr::Blocked(block) => Ok(T(stack, &[D(1), block])), - NockErr::Error(error) => Ok(T(stack, &[D(2), error])), - }, - } + util::mink(context, v_subject, v_formula) } pub mod util { + use crate::interpreter::{interpret, Context, NockErr, Tone}; use crate::jets; use crate::jets::form::util::scow; use crate::jets::util::rip; - use crate::jets::{jet_mink, JetErr}; + use crate::jets::JetErr; use crate::mem::NockStack; - use crate::newt::Newt; use crate::noun::{tape, Cell, Noun, D, T}; use ares_macros::tas; use std::result; @@ -46,15 +32,24 @@ pub mod util { const LEAF: Noun = D(tas!(b"leaf")); const ROSE: Noun = D(tas!(b"rose")); + pub fn mink(context: &mut Context, subject: Noun, formula: Noun) -> jets::Result { + // XX: no partial traces; all of our traces go down to the "home road" + match interpret(context, subject, formula) { + Ok(res) => Ok(T(context.stack, &[D(0), res])), + Err(err) => match err { + Tone::Blocked(block) => Ok(T(context.stack, &[D(1), block])), + Tone::Error(err, trace) => match err { + NockErr::Deterministic => Ok(T(context.stack, &[D(2), trace])), + NockErr::NonDeterministic => Err(JetErr::NonDeterministic), + }, + }, + } + } + /** Consume $tone, produce $toon */ // XX: should write a jet_mook wrapper for this function - pub fn mook( - stack: &mut NockStack, - newt: &mut Option<&mut Newt>, - tone: Cell, - flop: bool, - ) -> result::Result { + pub fn mook(context: &mut Context, tone: Cell, flop: bool) -> result::Result { let tag = tone.head().as_direct()?; let original_list = tone.tail(); @@ -76,7 +71,7 @@ pub mod util { let mut res = D(0); let mut list = original_list; // Unused if flopping - let (mut new_cell, mut new_memory) = Cell::new_raw_mut(stack); + let (mut new_cell, mut new_memory) = Cell::new_raw_mut(context.stack); let mut memory = new_memory; // loop guaranteed to run at least once @@ -86,7 +81,7 @@ pub mod util { } else if !flop && res.raw_equals(D(0)) { res = new_cell.as_noun(); } else if !flop { - (new_cell, new_memory) = Cell::new_raw_mut(stack); + (new_cell, new_memory) = Cell::new_raw_mut(context.stack); (*memory).tail = new_cell.as_noun(); memory = new_memory } @@ -99,16 +94,13 @@ pub mod util { let tank: Noun = match tag.data() { tas!(b"mean") => { if let Ok(atom) = dat.as_atom() { - let tape = rip(stack, 3, 1, atom)?; - T(stack, &[LEAF, tape]) + let tape = rip(context.stack, 3, 1, atom)?; + T(context.stack, &[LEAF, tape]) } else { - let virt = T(stack, &[dat, dat.as_cell()?.head()]); - let load = T(stack, &[virt, D(0)]); - let subj = T(stack, &[D(0), load, D(0)]); - let tone = jet_mink(stack, newt, subj)?.as_cell()?; + let tone = mink(context, dat, dat.as_cell()?.head())?.as_cell()?; if !tone.head().raw_equals(D(0)) { - let tape = tape(stack, "####"); - T(stack, &[LEAF, tape]) + let tape = tape(context.stack, "####"); + T(context.stack, &[LEAF, tape]) } else { // XX: need to check that this is actually a tank // return leaf+"mean.mook" if not @@ -117,13 +109,14 @@ pub mod util { } } tas!(b"spot") => { + let stack = &mut context.stack; let spot = dat.as_cell()?; let pint = spot.tail().as_cell()?; let pstr = pint.head().as_cell()?; let pend = pint.tail().as_cell()?; - let colo = T(stack, &[D(b':' as u64), D(0)]); - let trel = T(stack, &[colo, D(0), D(0)]); + let colo = T(*stack, &[D(b':' as u64), D(0)]); + let trel = T(*stack, &[colo, D(0), D(0)]); let smyt = smyt(stack, spot.head())?; @@ -141,7 +134,7 @@ pub mod util { list = list.tail().as_cell()?; } // "{end_col}]>" - let p4 = T(stack, &[D(b']' as u64), D(b'>' as u64), D(0)]); + let p4 = T(*stack, &[D(b']' as u64), D(b'>' as u64), D(0)]); (*list.tail_as_mut()) = p4; list = end_lin.as_cell()?; @@ -152,7 +145,7 @@ pub mod util { list = list.tail().as_cell()?; } // "{end_lin} {end_col}]>" - let p3 = T(stack, &[D(b' ' as u64), end_col]); + let p3 = T(*stack, &[D(b' ' as u64), end_col]); (*list.tail_as_mut()) = p3; list = str_col.as_cell()?; @@ -164,7 +157,7 @@ pub mod util { } // "{str_col}].[{end_lin} {end_col}]>" let p2 = T( - stack, + *stack, &[D(b']' as u64), D(b'.' as u64), D(b'[' as u64), end_lin], ); (*list.tail_as_mut()) = p2; @@ -177,19 +170,19 @@ pub mod util { list = list.tail().as_cell()?; } // "{str_lin} {str_col}].[{end_lin} {end_col}]>" - let p1 = T(stack, &[D(b' ' as u64), str_col]); + let p1 = T(*stack, &[D(b' ' as u64), str_col]); (*list.tail_as_mut()) = p1; // "<[{str_lin} {str_col}].[{end_lin} {end_col}]>" - let tape = T(stack, &[D(b'<' as u64), D(b'[' as u64), str_lin]); - let finn = T(stack, &[LEAF, tape]); + let tape = T(*stack, &[D(b'<' as u64), D(b'[' as u64), str_lin]); + let finn = T(*stack, &[LEAF, tape]); - T(stack, &[ROSE, trel, smyt, finn, D(0)]) + T(*stack, &[ROSE, trel, smyt, finn, D(0)]) } _ => { - let tape = rip(stack, 3, 1, tag.as_atom())?; + let tape = rip(context.stack, 3, 1, tag.as_atom())?; T( - stack, + context.stack, &[ D(tas!(b"m")), D(tas!(b"o")), @@ -206,7 +199,7 @@ pub mod util { }; if flop { - res = T(stack, &[tank, res]); + res = T(context.stack, &[tank, res]); } else { (*memory).head = tank; } @@ -217,7 +210,7 @@ pub mod util { (*memory).tail = D(0); } - let toon = Cell::new(stack, D(2), res); + let toon = Cell::new(context.stack, D(2), res); Ok(toon) } } @@ -252,6 +245,23 @@ pub mod util { mod tests { use super::*; use crate::jets::util::test::{assert_jet, init_stack}; + use crate::mem::NockStack; + use crate::noun::{D, T}; + use crate::serf::TERMINATOR; + use std::sync::Arc; + + #[test] + fn init() { + // This needs to be done because TERMINATOR is lazy allocated, and if you don't + // do it before you call the unit tests it'll get allocated on the Rust heap + // inside an assert_no_alloc block. + // + // Also Rust has no primitive for pre-test setup / post-test teardown, so we + // do it in a test that we rely on being called before any other in this file, + // since we're already using single-threaded test mode to avoid race conditions + // (because Rust doesn't support test order dependencies either). + let _ = Arc::clone(&TERMINATOR); + } #[test] fn test_mink_success() { diff --git a/rust/ares/src/jets/text.rs b/rust/ares/src/jets/text.rs index 62d702d..6500505 100644 --- a/rust/ares/src/jets/text.rs +++ b/rust/ares/src/jets/text.rs @@ -1,5 +1,7 @@ /** Text processing jets */ +use crate::interpreter::Context; +use crate::jets::util::slot; use crate::jets::Result; use crate::jets::util::slot; use crate::mem::NockStack; @@ -8,7 +10,7 @@ use crate::noun::{Noun, D}; crate::gdb!(); -pub fn jet_lent(_stack: &mut NockStack, _newt: &mut Option<&mut Newt>, subject: Noun) -> Result { +pub fn jet_lent(_context: &mut Context, subject: Noun) -> Result { let tape = slot(subject, 6)?; util::lent(tape).map(|x| D(x as u64)) } diff --git a/rust/ares/src/jets/tree.rs b/rust/ares/src/jets/tree.rs index 9e71263..891e793 100644 --- a/rust/ares/src/jets/tree.rs +++ b/rust/ares/src/jets/tree.rs @@ -1,19 +1,14 @@ /** Tree jets */ -use crate::jets; +use crate::interpreter::Context; use crate::jets::util::*; use crate::jets::JetErr::*; -use crate::mem::NockStack; -use crate::newt::Newt; +use crate::jets::Result; use crate::noun::{Noun, D}; crate::gdb!(); -pub fn jet_cap( - _stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_cap(_context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; let tom = arg.as_atom()?; let met = met(0, tom); @@ -29,11 +24,8 @@ pub fn jet_cap( } } -pub fn jet_mas( - stack: &mut NockStack, - _newt: &mut Option<&mut Newt>, - subject: Noun, -) -> jets::Result { +pub fn jet_mas(context: &mut Context, subject: Noun) -> Result { + let stack = &mut context.stack; let arg = slot(subject, 6)?; let tom = arg.as_atom()?; let met = met(0, tom); diff --git a/rust/ares/src/lib.rs b/rust/ares/src/lib.rs index 5ae5ba2..23e67e1 100644 --- a/rust/ares/src/lib.rs +++ b/rust/ares/src/lib.rs @@ -1,5 +1,7 @@ extern crate num_derive; #[macro_use] +extern crate lazy_static; +#[macro_use] extern crate static_assertions; pub mod interpreter; pub mod jets; diff --git a/rust/ares/src/main.rs b/rust/ares/src/main.rs index 3fafd59..75afefb 100644 --- a/rust/ares/src/main.rs +++ b/rust/ares/src/main.rs @@ -1,6 +1,7 @@ -use ares::interpreter::interpret; +use ares::hamt::Hamt; +use ares::interpreter::{interpret, Context}; use ares::mem::NockStack; -use ares::noun::IndirectAtom; +use ares::noun::{IndirectAtom, Noun}; use ares::serf::serf; use ares::serialization::{cue, jam}; use memmap::Mmap; @@ -61,8 +62,13 @@ fn main() -> io::Result<()> { let input_cell = input .as_cell() .expect("Input must be jam of subject/formula pair"); - let result = interpret(&mut stack, &mut None, input_cell.head(), input_cell.tail()) - .expect("nock failed"); + let mut context = Context { + stack: &mut stack, + newt: None, + cache: &mut Hamt::::new(), + }; + let result = + interpret(&mut context, input_cell.head(), input_cell.tail()).expect("nock failed"); if let Ok(atom) = result.as_atom() { println!("Result: {}", atom); } diff --git a/rust/ares/src/mem.rs b/rust/ares/src/mem.rs index 1ae7802..c8c80c1 100644 --- a/rust/ares/src/mem.rs +++ b/rust/ares/src/mem.rs @@ -1,6 +1,9 @@ use crate::assert_acyclic; +use crate::assert_no_forwarding_pointers; +use crate::assert_no_junior_pointers; use crate::noun::{Atom, Cell, CellMemory, IndirectAtom, Noun, NounAllocator}; use crate::snapshot::pma::{pma_in_arena, pma_malloc_w}; +use assert_no_alloc::permit_alloc; use either::Either::{self, Left, Right}; use ibig::Stack; use libc::{c_void, memcmp}; @@ -85,6 +88,19 @@ impl NockStack { } } + /** Resets the NockStack. */ + pub fn reset(&mut self, top_slots: usize) { + self.frame_pointer = unsafe { self.start.add(RESERVED + top_slots) } as *mut u64; + self.stack_pointer = self.frame_pointer; + self.alloc_pointer = unsafe { self.start.add(self.size) } as *mut u64; + self.pc = false; + unsafe { + *self.frame_pointer.sub(FRAME + 1) = ptr::null::() as u64; // "frame pointer" from "previous" frame + *self.frame_pointer.sub(STACK + 1) = ptr::null::() as u64; // "stack pointer" from "previous" frame + *self.frame_pointer.sub(ALLOC + 1) = ptr::null::() as u64; // "alloc pointer" from "previous" frame + }; + } + /** Current frame pointer of this NockStack */ pub fn get_frame_pointer(&self) -> *const u64 { self.frame_pointer @@ -355,6 +371,10 @@ impl NockStack { } unsafe fn copy(&mut self, noun: &mut Noun) { + assert_acyclic!(*noun); + assert_no_forwarding_pointers!(*noun); + assert_no_junior_pointers!(self, *noun); + self.pre_copy(); assert!(self.stack_is_empty()); let noun_ptr = noun as *mut Noun; @@ -442,7 +462,10 @@ impl NockStack { } } // Set saved previous allocation pointer its new value after this allocation + assert_acyclic!(*noun); + assert_no_forwarding_pointers!(*noun); + assert_no_junior_pointers!(self, *noun); } pub unsafe fn copy_pma(&mut self, noun: &mut Noun) { @@ -516,7 +539,6 @@ impl NockStack { }, } } - assert_acyclic!(*noun); } pub unsafe fn frame_pop(&mut self) { @@ -528,6 +550,18 @@ impl NockStack { self.stack_pointer = prev_stack_ptr; self.alloc_pointer = prev_alloc_ptr; + if self.frame_pointer.is_null() + || self.stack_pointer.is_null() + || self.alloc_pointer.is_null() + { + permit_alloc(|| { + panic!( + "serf: frame_pop: null NockStack pointer f={:p} s={:p} a={:p}", + self.frame_pointer, self.stack_pointer, self.alloc_pointer + ); + }); + } + self.pc = false; } @@ -666,6 +700,88 @@ impl NockStack { unsafe { self.stack_pointer == self.alloc_pointer.add(RESERVED) } } } + + pub fn no_junior_pointers(&self, noun: Noun) -> bool { + unsafe { + if let Ok(c) = noun.as_cell() { + let mut fp: *mut u64; + let mut sp = self.stack_pointer; + let mut ap = self.alloc_pointer; + let mut pfp = *(self.prev_frame_pointer_pointer()); + let mut psp = *(self.prev_stack_pointer_pointer()); + let mut pap = *(self.prev_alloc_pointer_pointer()); + + let mut dbg_stack = Vec::new(); + + // Detemine range + let (rlo, rhi) = loop { + if psp.is_null() { + psp = ((self.start as u64) + ((self.size << 3) as u64)) as *mut u64; + } + let (lo, hi) = if sp < ap { (ap, psp) } else { (psp, ap) }; + let ptr = c.to_raw_pointer() as *mut u64; + if ptr >= lo && ptr < hi { + break if sp < ap { (sp, ap) } else { (ap, sp) }; + } else { + fp = pfp; + sp = psp; + ap = pap; + if sp < ap { + pfp = *(fp.sub(FRAME + 1)) as *mut u64; + psp = *(fp.sub(STACK + 1)) as *mut u64; + pap = *(fp.sub(ALLOC + 1)) as *mut u64; + } else { + pfp = *(fp.add(FRAME)) as *mut u64; + psp = *(fp.add(STACK)) as *mut u64; + pap = *(fp.add(ALLOC)) as *mut u64; + } + } + }; + + dbg_stack.push(c.head()); + dbg_stack.push(c.tail()); + while let Some(n) = dbg_stack.pop() { + if let Ok(a) = n.as_allocated() { + let ptr = a.to_raw_pointer(); + if ptr >= rlo && ptr < rhi { + eprintln!( + "\rserf: Noun {:x} has Noun {:x} in junior of range {:p}-{:p}", + (noun.raw << 3), + (n.raw << 3), + rlo, + rhi + ); + return false; + } + if let Some(c) = a.cell() { + dbg_stack.push(c.tail()); + dbg_stack.push(c.head()); + } + } + } + + true + } else { + true + } + } + } +} + +#[cfg(feature = "check_junior")] +#[macro_export] +macro_rules! assert_no_junior_pointers { + ( $x:expr, $y:expr ) => { + assert_no_alloc::permit_alloc(|| { + assert!($x.no_junior_pointers($y)); + }) + }; +} + +#[cfg(not(feature = "check_junior"))] +#[macro_export] +macro_rules! assert_no_junior_pointers { + ( $x:expr, $y:expr ) => {}; } pub unsafe fn unifying_equality(stack: &mut NockStack, a: *mut Noun, b: *mut Noun) -> bool { @@ -692,6 +808,13 @@ pub unsafe fn unifying_equality(stack: &mut NockStack, a: *mut Noun, b: *mut Nou * senior noun, *never vice versa*, to avoid introducing references from more senior frames * into more junior frames, which would result in incorrect operation of the copier. */ + assert_acyclic!(*a); + assert_acyclic!(*b); + assert_no_forwarding_pointers!(*a); + assert_no_forwarding_pointers!(*b); + assert_no_junior_pointers!(stack, *a); + assert_no_junior_pointers!(stack, *b); + // If the nouns are already word-equal we have nothing to do if (*a).raw_equals(*b) { return true; @@ -738,7 +861,6 @@ pub unsafe fn unifying_equality(stack: &mut NockStack, a: *mut Noun, b: *mut Nou ) == 0 { let (_senior, junior) = senior_pointer_first(stack, x_as_ptr, y_as_ptr); - // unify if x_as_ptr == junior { *x = *y; } else { @@ -788,8 +910,14 @@ pub unsafe fn unifying_equality(stack: &mut NockStack, a: *mut Noun, b: *mut Nou } } stack.frame_pop(); + assert_acyclic!(*a); assert_acyclic!(*b); + assert_no_forwarding_pointers!(*a); + assert_no_forwarding_pointers!(*b); + assert_no_junior_pointers!(stack, *a); + assert_no_junior_pointers!(stack, *b); + (*a).raw_equals(*b) } diff --git a/rust/ares/src/mug.rs b/rust/ares/src/mug.rs index 68d9b5c..7e5f0af 100644 --- a/rust/ares/src/mug.rs +++ b/rust/ares/src/mug.rs @@ -1,4 +1,6 @@ use crate::assert_acyclic; +use crate::assert_no_forwarding_pointers; +use crate::assert_no_junior_pointers; use crate::mem::*; use crate::noun::{Allocated, Atom, DirectAtom, Noun}; use either::Either::*; @@ -119,7 +121,11 @@ pub fn mug_u32(stack: &mut NockStack, noun: Noun) -> u32 { if let Some(mug) = get_mug(noun) { return mug; } + assert_acyclic!(noun); + assert_no_forwarding_pointers!(noun); + assert_no_junior_pointers!(stack, noun); + stack.frame_push(0); unsafe { *(stack.push()) = noun; @@ -171,6 +177,11 @@ pub fn mug_u32(stack: &mut NockStack, noun: Noun) -> u32 { unsafe { stack.frame_pop(); } + + assert_acyclic!(noun); + assert_no_forwarding_pointers!(noun); + assert_no_junior_pointers!(stack, noun); + get_mug(noun).expect("Noun should have a mug once it is mugged.") } diff --git a/rust/ares/src/newt.rs b/rust/ares/src/newt.rs index a1ae33c..1b996a9 100644 --- a/rust/ares/src/newt.rs +++ b/rust/ares/src/newt.rs @@ -110,7 +110,11 @@ impl Newt { self.output.write_all(buf).unwrap(); } - /** Send %ripe, the first event. */ + /** Send %ripe, the first event. + * + * eve = event number + * mug = mug of Arvo after above event + */ pub fn ripe(&mut self, stack: &mut NockStack, eve: u64, mug: u64) { let version = T( stack, @@ -130,7 +134,11 @@ impl Newt { self.write_noun(stack, live); } - /** Send %slog, pretty-printed debug output. */ + /** Send %slog, pretty-printed debug output. + * + * pri = debug priority + * tank = output as tank + */ pub fn slog(&mut self, stack: &mut NockStack, pri: u64, tank: Noun) { let slog = T(stack, &[D(tas!(b"slog")), D(pri), tank]); self.write_noun(stack, slog); @@ -148,19 +156,30 @@ impl Newt { self.write_noun(stack, peek); } - /** Send %peek %bail, unsuccessfully scried. */ + /** Send %peek %bail, unsuccessfully scried. + * + * dud = goof + */ pub fn peek_bail(&mut self, stack: &mut NockStack, dud: Noun) { let peek = T(stack, &[D(tas!(b"peek")), D(tas!(b"bail")), dud]); self.write_noun(stack, peek); } - /** Send %play %done, successfully replayed events. */ + /** Send %play %done, successfully replayed events. + * + * mug = mug of Arvo after full replay + */ pub fn play_done(&mut self, stack: &mut NockStack, mug: u64) { let play = T(stack, &[D(tas!(b"play")), D(tas!(b"done")), D(mug)]); self.write_noun(stack, play); } - /** Send %play %bail, failed to replay events. */ + /** Send %play %bail, failed to replay events. + * + * eve = last good event number + * mug = mug of Arvo after above event + * dud = goof when trying next event + */ pub fn play_bail(&mut self, stack: &mut NockStack, eve: u64, mug: u64, dud: Noun) { let play = T( stack, @@ -169,7 +188,12 @@ impl Newt { self.write_noun(stack, play); } - /** Send %work %done, successfully ran event. */ + /** Send %work %done, successfully ran event. + * + * eve = new event number + * mug = mug of Arvo after above event + * fec = list of effects + */ pub fn work_done(&mut self, stack: &mut NockStack, eve: u64, mug: u64, fec: Noun) { let work = T( stack, @@ -178,7 +202,13 @@ impl Newt { self.write_noun(stack, work); } - /** Send %work %swap, successfully replaced failed event. */ + /** Send %work %swap, successfully replaced failed event. + * + * eve = new event number + * mug = mug of Arvo after above event + * job = event performed instead of the one given to serf by king + * fec = list of effects + */ pub fn work_swap(&mut self, stack: &mut NockStack, eve: u64, mug: u64, job: Noun, fec: Noun) { let work = T( stack, @@ -187,7 +217,10 @@ impl Newt { self.write_noun(stack, work); } - /** Send %work %bail, failed to run event. */ + /** Send %work %bail, failed to run event. + * + * lud = list of goof + */ pub fn work_bail(&mut self, stack: &mut NockStack, lud: Noun) { let work = T(stack, &[D(tas!(b"work")), D(tas!(b"bail")), lud]); self.write_noun(stack, work); diff --git a/rust/ares/src/noun.rs b/rust/ares/src/noun.rs index 4404fd9..232d391 100644 --- a/rust/ares/src/noun.rs +++ b/rust/ares/src/noun.rs @@ -44,7 +44,9 @@ pub const NO: Noun = D(1); #[macro_export] macro_rules! assert_acyclic { ( $x:expr ) => { - assert!(crate::noun::acyclic_noun($x)); + assert_no_alloc::permit_alloc(|| { + assert!(crate::noun::acyclic_noun($x)); + }) }; } @@ -82,6 +84,42 @@ fn acyclic_noun_go(noun: Noun, seen: &mut IntMap<()>) -> bool { } } +#[cfg(feature = "check_forwarding")] +#[macro_export] +macro_rules! assert_no_forwarding_pointers { + ( $x:expr ) => { + assert_no_alloc::permit_alloc(|| { + assert!(crate::noun::no_forwarding_pointers($x)); + }) + }; +} + +#[cfg(not(feature = "check_forwarding"))] +#[macro_export] +macro_rules! assert_no_forwarding_pointers { + ( $x:expr ) => {}; +} + +pub fn no_forwarding_pointers(noun: Noun) -> bool { + let mut dbg_stack = Vec::new(); + dbg_stack.push(noun); + + while !dbg_stack.is_empty() { + if let Some(noun) = dbg_stack.pop() { + if unsafe { noun.raw & FORWARDING_MASK == FORWARDING_TAG } { + return false; + } else if let Ok(cell) = noun.as_cell() { + dbg_stack.push(cell.tail()); + dbg_stack.push(cell.head()); + } + } else { + break; + } + } + + true +} + /** Test if a noun is a direct atom. */ fn is_direct_atom(noun: u64) -> bool { noun & DIRECT_MASK == DIRECT_TAG @@ -188,7 +226,7 @@ impl DirectAtom { } pub fn as_bytes(&self) -> &[u8] { - let bytes: &[u8; 8] = unsafe { std::mem::transmute(self.0) }; + let bytes: &[u8; 8] = unsafe { std::mem::transmute(&self.0) }; &bytes[..] } } @@ -835,6 +873,14 @@ impl Allocated { } } + pub fn cell(&self) -> Option { + if self.is_cell() { + unsafe { Some(self.cell) } + } else { + None + } + } + pub fn as_noun(&self) -> Noun { Noun { allocated: *self } } diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index 02cbcc4..2442890 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -1,21 +1,158 @@ -use crate::interpreter::{interpret, NockErr}; +use crate::hamt::Hamt; +use crate::interpreter; +use crate::interpreter::{inc, interpret, Tone}; use crate::jets::nock::util::mook; use crate::jets::text::util::lent; use crate::mem::NockStack; use crate::mug::mug_u32; use crate::newt::Newt; use crate::noun::{Cell, Noun, Slots, D, T}; -use crate::snapshot::{self, Snapshot}; +use crate::snapshot::double_jam::DoubleJam; +use crate::snapshot::Snapshot; use ares_macros::tas; +use signal_hook; +use signal_hook::consts::SIGINT; use std::fs::create_dir_all; use std::io; use std::path::PathBuf; use std::result::Result; -use std::thread::sleep; -use std::time; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; crate::gdb!(); +struct Context { + epoch: u64, + event_num: u64, + snapshot: DoubleJam, + arvo: Noun, + mug: u32, + stack: NockStack, + newt: Newt, + cache: Hamt, + // XX: persistent memo cache +} + +impl Context { + pub fn new(snap_path: &PathBuf) -> Self { + // TODO: switch to Pma when ready + // let snap = &mut snapshot::pma::Pma::new(snap_path); + let mut snapshot = DoubleJam::new(snap_path); + let mut stack = NockStack::new(256 << 10 << 10, 0); + let newt = Newt::new(); + let cache = Hamt::::new(); + + let (epoch, event_num, arvo) = snapshot.load(&mut stack).unwrap_or((0, 0, D(0))); + let mug = mug_u32(&mut stack, arvo); + + Context { + epoch, + event_num, + snapshot, + arvo, + mug, + stack, + newt, + cache, + } + } + + // + // Getters + // + + pub fn epoch(&self) -> u64 { + self.epoch + } + + pub fn event_num(&self) -> u64 { + self.event_num + } + + pub fn arvo(&self) -> Noun { + self.arvo + } + + pub fn stack_as_mut(&mut self) -> &mut NockStack { + &mut self.stack + } + + pub fn for_interpreter(&mut self) -> interpreter::Context { + self.cache = Hamt::::new(); + + interpreter::Context { + stack: &mut self.stack, + newt: Some(&mut self.newt), + cache: &mut self.cache, + } + } + + // + // Setters + // + + pub fn event_update(&mut self, new_event_num: u64, new_arvo: Noun) { + // XX: assert event numbers are continuous + self.arvo = new_arvo; + self.event_num = new_event_num; + self.snapshot.save(&mut self.stack, &mut self.arvo); + self.mug = mug_u32(&mut self.stack, self.arvo); + } + + // + // Snapshot functions + // + + pub fn sync(&mut self) { + self.snapshot + .sync(&mut self.stack, self.epoch, self.event_num); + } + + // + // Newt functions + // + + pub fn next(&mut self) -> Option { + self.newt.next(&mut self.stack) + } + + pub fn ripe(&mut self) { + self.newt + .ripe(&mut self.stack, self.event_num, self.mug as u64); + } + + pub fn live(&mut self) { + self.newt.live(&mut self.stack); + } + + pub fn peek_done(&mut self, dat: Noun) { + self.newt.peek_done(&mut self.stack, dat); + } + + pub fn play_done(&mut self) { + self.newt.play_done(&mut self.stack, self.mug as u64); + } + + pub fn play_bail(&mut self, dud: Noun) { + self.newt + .play_bail(&mut self.stack, self.event_num, self.mug as u64, dud); + } + + pub fn work_done(&mut self, fec: Noun) { + self.newt + .work_done(&mut self.stack, self.event_num, self.mug as u64, fec); + } + + pub fn work_swap(&mut self, job: Noun, fec: Noun) { + self.newt + .work_swap(&mut self.stack, self.event_num, self.mug as u64, job, fec); + } + + pub fn work_bail(&mut self, lud: Noun) { + self.newt.work_bail(&mut self.stack, lud); + } +} + #[allow(dead_code)] const LOAD_AXIS: u64 = 4; const PEEK_AXIS: u64 = 22; @@ -23,12 +160,20 @@ const POKE_AXIS: u64 = 23; #[allow(dead_code)] const WISH_AXIS: u64 = 10; +// Necessary because Arc::new is not const +lazy_static! { + pub static ref TERMINATOR: Arc = Arc::new(AtomicBool::new(false)); +} + /** * This is suitable for talking to the king process. To test, change the arg_c[0] line in * u3_lord_init in vere to point at this binary and start vere like normal. */ pub fn serf() -> io::Result<()> { - sleep(time::Duration::from_secs(0)); + // Register SIGINT signal hook to set flag first time, shutdown second time + signal_hook::flag::register_conditional_shutdown(SIGINT, 1, Arc::clone(&TERMINATOR))?; + signal_hook::flag::register(SIGINT, Arc::clone(&TERMINATOR))?; + let snap_path_string = std::env::args() .nth(2) .ok_or(io::Error::new(io::ErrorKind::Other, "no pier path"))?; @@ -36,22 +181,14 @@ pub fn serf() -> io::Result<()> { snap_path.push(".urb"); snap_path.push("chk"); create_dir_all(&snap_path)?; - // TODO: switch to Pma when ready - // let snap = &mut snapshot::pma::Pma::new(snap_path); - let snap = &mut snapshot::double_jam::DoubleJam::new(snap_path); - let stack = &mut NockStack::new(96 << 10 << 10, 0); - let newt = &mut Newt::new(); - - let (_epoch, loaded_event_num, mut arvo) = snap.load(stack).unwrap_or((0, 0, D(0))); - let mut current_event_num = loaded_event_num; - let loaded_mug = mug_u32(stack, arvo); - let mut current_mug = loaded_mug; - - newt.ripe(stack, current_event_num, loaded_mug as u64); + let mut context = Context::new(&snap_path); + context.ripe(); // Can't use for loop because it borrows newt - while let Some(writ) = newt.next(stack) { + while let Some(writ) = context.next() { + // XX: probably want to bookend this logic frame_push / frame_pop + // preserve jet state and persistent cache, lose everything else let tag = slot(writ, 2)?.as_direct().unwrap(); match tag.data() { tas!(b"live") => { @@ -62,135 +199,185 @@ pub fn serf() -> io::Result<()> { tas!(b"save") => { // XX what is eve for? eprintln!("save"); - snap.sync(stack, 0, current_event_num); + context.sync(); } tas!(b"meld") => eprintln!("meld"), tas!(b"pack") => eprintln!("pack"), _ => eprintln!("unknown live"), } - newt.live(stack); + context.live(); } tas!(b"peek") => { let sam = slot(writ, 7)?; - let res = slam(stack, newt, arvo, PEEK_AXIS, sam) - .expect("peek error handling unimplemented"); - newt.peek_done(stack, res); + let res = + slam(&mut context, PEEK_AXIS, sam).expect("peek error handling unimplemented"); + context.peek_done(res); } tas!(b"play") => { - // apply lifecycle to first batch - if current_event_num == 0 { - let eve = slot(writ, 7)?; - let sub = T(stack, &[D(0), D(3)]); - let lyf = T(stack, &[D(2), sub, D(0), D(2)]); - // XX: TODO - match interpret(stack, &mut Some(newt), eve, lyf) { - Ok(gat) => { - arvo = slot(gat, 7) - .expect("serf: play: lifecycle didn't return initial Arvo"); - current_event_num = - lent(eve).expect("serf: play: boot event number failure") as u64; - current_mug = mug_u32(stack, arvo); - } - Err(NockErr::Error(trace)) => { - let tone = Cell::new(stack, D(2), trace); - let tang = mook(stack, &mut Some(newt), tone, false) - .expect("serf: play: +mook crashed on bail") - .tail(); - let goof = T(stack, &[D(tas!(b"exit")), tang]); - newt.play_bail(stack, 0, 0, goof); - } - Err(NockErr::Blocked(_)) => { - panic!("play: blocked err handling unimplemented") - } - } + let lit = slot(writ, 7)?; + if context.epoch() == 0 && context.event_num() == 0 { + // apply lifecycle to first batch + play_life(&mut context, lit); } else { - // do we need to assert something here? - // current_event_num = slot(writ, 6)?.as_direct().unwrap().data(); - - let mut lit = slot(writ, 7)?; - while let Ok(cell) = lit.as_cell() { - let ovo = cell.head(); - - match slam(stack, newt, arvo, POKE_AXIS, ovo) { - Ok(res) => { - let cell = res.as_cell().expect("serf: play: +slam returned atom"); - arvo = cell.tail(); - current_mug = mug_u32(stack, arvo); - current_event_num += 1; - } - Err(NockErr::Error(trace)) => { - let tone = Cell::new(stack, D(2), trace); - let tang = mook(stack, &mut Some(newt), tone, false) - .expect("serf: play: +mook crashed on bail") - .tail(); - let goof = T(stack, &[D(tas!(b"exit")), tang]); - newt.play_bail(stack, current_event_num, current_mug as u64, goof); - } - Err(NockErr::Blocked(_)) => { - panic!("play: blocked err handling unimplemented") - } - } - - lit = cell.tail(); - } + play_list(&mut context, lit); }; - - snap.save(stack, &mut arvo); - newt.play_done(stack, current_mug as u64); } tas!(b"work") => { // XX: what is in slot 6? it's mil_w in Vere Serf let job = slot(writ, 7)?; - match slam(stack, newt, arvo, POKE_AXIS, job) { - Ok(res) => { - let cell = res.as_cell().expect("serf: work: +slam returned atom"); - let fec = cell.head(); - arvo = cell.tail(); - snap.save(stack, &mut arvo); - - current_mug = mug_u32(stack, arvo); - current_event_num += 1; - - newt.work_done(stack, current_event_num, current_mug as u64, fec); - } - Err(NockErr::Error(trace)) => { - // XX: Our Arvo can't currently handle %crud, so just bail - let tone = Cell::new(stack, D(2), trace); - let tang = mook(stack, &mut Some(newt), tone, false) - .expect("serf: play: +mook crashed on bail") - .tail(); - let goof = T(stack, &[D(tas!(b"exit")), tang]); - // lud = (list goof) - let lud = T(stack, &[goof, D(0)]); - newt.work_bail(stack, lud); - } - Err(NockErr::Blocked(_)) => { - panic!("play: blocked err handling unimplemented") - } - } + work(&mut context, job); } _ => panic!("got message with unknown tag {}", tag), }; + + clear_interrupt(); } Ok(()) } -pub fn slam( - stack: &mut NockStack, - newt: &mut Newt, - core: Noun, - axis: u64, - ovo: Noun, -) -> Result { - let pul = T(stack, &[D(9), D(axis), D(0), D(2)]); - let sam = T(stack, &[D(6), D(0), D(7)]); - let fol = T(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]); - let sub = T(stack, &[core, ovo]); - interpret(stack, &mut Some(newt), sub, fol) +fn burn(context: &mut Context, subject: Noun, formula: Noun) -> Result { + let burn_context = &mut context.for_interpreter(); + interpret(burn_context, subject, formula) +} + +fn slam(context: &mut Context, axis: u64, ovo: Noun) -> Result { + let arvo = context.arvo(); + let pul = T(context.stack_as_mut(), &[D(9), D(axis), D(0), D(2)]); + let sam = T(context.stack_as_mut(), &[D(6), D(0), D(7)]); + let fol = T( + context.stack_as_mut(), + &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)], + ); + let sub = T(context.stack_as_mut(), &[arvo, ovo]); + burn(context, sub, fol) +} + +fn goof(context: &mut Context, trace: Noun) -> Noun { + let tone = Cell::new(context.stack_as_mut(), D(2), trace); + let mook_context = &mut context.for_interpreter(); + + let tang = mook(mook_context, tone, false) + .expect("serf: goof: +mook crashed on bail") + .tail(); + // XX: noun::Tone or noun::NockErr should use a bail enum system similar to u3m_bail motes; + // might be able to replace NockErr with mote and map determinism to individual motes; + // for, always set to %exit + T(mook_context.stack, &[D(tas!(b"exit")), tang]) +} + +/** Run slam, process stack trace to tang if error */ +fn soft(context: &mut Context, ovo: Noun) -> Result { + match slam(context, POKE_AXIS, ovo) { + Ok(res) => Ok(res), + Err(Tone::Error(_, trace)) => Err(goof(context, trace)), + Err(Tone::Blocked(_)) => panic!("soft: blocked err handling unimplemented"), + } +} + +fn play_life(context: &mut Context, eve: Noun) { + let sub = T(context.stack_as_mut(), &[D(0), D(3)]); + let lyf = T(context.stack_as_mut(), &[D(2), sub, D(0), D(2)]); + match burn(context, eve, lyf) { + Ok(gat) => { + let eved = lent(eve).expect("serf: play: boot event number failure") as u64; + let arvo = slot(gat, 7).expect("serf: play: lifecycle didn't return initial Arvo"); + + context.event_update(eved, arvo); + context.play_done(); + } + Err(Tone::Error(_, trace)) => { + let goof = goof(context, trace); + context.play_bail(goof); + } + Err(Tone::Blocked(_)) => { + panic!("play: blocked err handling unimplemented") + } + } +} + +fn play_list(context: &mut Context, mut lit: Noun) { + let mut eve = context.event_num(); + while let Ok(cell) = lit.as_cell() { + let ovo = cell.head(); + match soft(context, ovo) { + Ok(res) => { + let arvo = res + .as_cell() + .expect("serf: work: +slam returned atom") + .tail(); + eve += 1; + + context.event_update(eve, arvo); + } + Err(goof) => { + return context.play_bail(goof); + } + } + lit = cell.tail(); + } + context.play_done(); +} + +fn work(context: &mut Context, job: Noun) { + match soft(context, job) { + Ok(res) => { + let cell = res.as_cell().expect("serf: work: +slam returned atom"); + let fec = cell.head(); + let eve = context.event_num(); + + context.event_update(eve + 1, cell.tail()); + context.work_done(fec); + } + Err(goof) => { + work_swap(context, job, goof); + } + } +} + +fn work_swap(context: &mut Context, job: Noun, goof: Noun) { + // TODO: on decryption failure in aes_siv, should bail as fast as + // possible, without rendering stack trace or injecting crud event. See + // c3__evil in vere. + + clear_interrupt(); + + // crud = [+(now) [%$ %arvo ~] [%crud goof ovo]] + let job_cell = job.as_cell().expect("serf: work: job not a cell"); + let job_now = job_cell.head().as_atom().expect("serf: work: now not atom"); + let now = inc(context.stack_as_mut(), job_now).as_noun(); + let wire = T(context.stack_as_mut(), &[D(0), D(tas!(b"arvo")), D(0)]); + let crud = T( + context.stack_as_mut(), + &[now, wire, D(tas!(b"crud")), goof, job_cell.tail()], + ); + + match soft(context, crud) { + Ok(res) => { + let cell = res.as_cell().expect("serf: work: crud +slam returned atom"); + let fec = cell.head(); + let eve = context.event_num(); + + context.event_update(eve + 1, cell.tail()); + context.work_swap(crud, fec); + } + Err(goof_crud) => { + work_bail(context, &[goof_crud, goof]); + } + } +} + +fn work_bail(context: &mut Context, goofs: &[Noun]) { + let lest = T(context.stack_as_mut(), goofs); + let lud = T(context.stack_as_mut(), &[lest, D(0)]); + context.work_bail(lud); } fn slot(noun: Noun, axis: u64) -> io::Result { noun.slot(axis) .map_err(|_e| io::Error::new(io::ErrorKind::InvalidInput, "Bad axis")) } + +fn clear_interrupt() { + (*TERMINATOR).store(false, Ordering::Relaxed); +} diff --git a/rust/ares/src/snapshot/double_jam.rs b/rust/ares/src/snapshot/double_jam.rs index 2ae13c9..e021d2e 100644 --- a/rust/ares/src/snapshot/double_jam.rs +++ b/rust/ares/src/snapshot/double_jam.rs @@ -110,6 +110,7 @@ impl Snapshot for DoubleJam { } fn save(&mut self, _stack: &mut NockStack, noun: &mut Noun) { + // XX: I don't think this needs to be mut self.noun = *noun; }