diff --git a/hoon/scaffolding/playpen.hoon b/hoon/scaffolding/playpen.hoon index 5b3c3df1..0eb9e78d 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 0080f3e6..577c9c7a 100644 --- a/hoon/scaffolding/toddler.hoon +++ b/hoon/scaffolding/toddler.hoon @@ -8,39 +8,46 @@ != => |% + +$ card (cask) ++ cask |$ [a] (pair mark a) + +$ goof [mote=term =tang] +$ mark @tas - +$ card (cask) +$ 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 fb69b328..a99638f1 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 8989827b..f8423191 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" } +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" -murmur3 = { git = "https://github.com/tloncorp/murmur3", rev = "7878a0f" } memmap = "0.7.0" -intmap = "1.1.0" -num-traits = "0.2" +murmur3 = { git = "https://github.com/tloncorp/murmur3", rev = "7878a0f" } num-derive = "0.3" -criterion = "0.4" +num-traits = "0.2" +signal-hook = "0.3" 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"]} [build-dependencies] cc = "1.0.79" @@ -40,4 +43,3 @@ opt-level = 3 opt-level = 3 [features] -check_acyclic=[] diff --git a/rust/ares/src/hamt.rs b/rust/ares/src/hamt.rs index 9087cf7f..2f2fb28e 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 8335ec9a..7f66ea04 100644 --- a/rust/ares/src/interpreter.rs +++ b/rust/ares/src/interpreter.rs @@ -1,14 +1,19 @@ 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::{Atom, Cell, IndirectAtom, Noun, Slots, D, T}; +use crate::noun; +use crate::noun::{tape, Atom, Cell, IndirectAtom, Noun, Slots, D, T}; +use crate::serf::TERMINATOR; use ares_macros::tas; 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!(); @@ -228,21 +233,44 @@ enum NockWork { #[derive(Debug)] pub enum NockErr { + Deterministic, + NonDeterministic, +} + +#[derive(Debug)] +pub enum Tone { Blocked(Noun), - Error(Noun), + Error(NockErr, Noun), } 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"), + } + } +} + /** 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 { +) -> Result { + let terminator = Arc::clone(&TERMINATOR); let mut res: Noun = D(0); let mut cache = Hamt::::new(); let virtual_frame = stack.get_frame_pointer(); @@ -266,7 +294,7 @@ pub fn interpret( // ``` // // (See https://docs.rs/assert_no_alloc/latest/assert_no_alloc/#advanced-use) - let tone = assert_no_alloc(|| unsafe { + let nock = assert_no_alloc(|| unsafe { push_formula(stack, formula, true)?; loop { @@ -305,44 +333,51 @@ pub fn interpret( res = noun; 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::(); } - 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::(); - subject = vale.subject; - push_formula(stack, res, true)?; - } else { - vale.todo = Todo2::RestoreSubject; - std::mem::swap(&mut vale.subject, &mut subject); + + match vale.todo { + Todo2::ComputeSubject => { + vale.todo = Todo2::ComputeFormula; *stack.top() = NockWork::Work2(vale); - mean_frame_push(stack, 0); - *stack.push() = NockWork::Ret; - push_formula(stack, res, true)?; + push_formula(stack, vale.subject, false)?; + } + 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::(); + 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)?; + } + } + Todo2::RestoreSubject => { + subject = vale.subject; + stack.pop::(); } } - Todo2::RestoreSubject => { - subject = vale.subject; - stack.pop::(); - } - }, + } NockWork::Work3(mut thee) => match thee.todo { Todo3::ComputeChild => { thee.todo = Todo3::ComputeType; @@ -366,7 +401,7 @@ pub fn interpret( stack.pop::(); } else { // Cannot increment (Nock 4) a cell - break Err(NockErr::Error(D(2))); + break Err(NockErr::Deterministic); } } }, @@ -407,11 +442,11 @@ pub fn interpret( push_formula(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); } } }, @@ -463,37 +498,43 @@ pub fn interpret( 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; + *stack.top() = NockWork::Work9(kale); + push_formula(stack, kale.core, false)?; + } + Todo9::ComputeResult => { + if let Ok(formula) = res.slot_atom(kale.axis) { + if kale.tail { + stack.pop::(); + subject = res; + push_formula(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)?; + } } 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; + stack.pop::(); } } - Todo9::RestoreSubject => { - subject = kale.core; - stack.pop::(); - } - }, + } NockWork::Work10(mut diet) => { match diet.todo { Todo10::ComputeTree => { @@ -515,20 +556,25 @@ pub fn interpret( } NockWork::Work11D(mut dint) => match dint.todo { Todo11D::ComputeHint => { - if let Some(found) = match_hint_pre_hint( + match 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)?; + Ok(Some(found)) => { + res = found; + stack.pop::(); + } + Ok(None) => { + dint.todo = Todo11D::ComputeResult; + *stack.top() = NockWork::Work11D(dint); + push_formula(stack, dint.hint, false)?; + } + Err(err) => { + break Err(err); + } } } Todo11D::ComputeResult => { - dint.todo = Todo11D::Done; - if let Some(found) = match_hint_pre_nock( + match match_hint_pre_nock( stack, newt, subject, @@ -537,16 +583,22 @@ pub fn interpret( dint.body, Some(res), ) { - res = found; - stack.pop::(); - } else { - dint.todo = Todo11D::Done; - if dint.tail { + Ok(Some(found)) => { + res = found; stack.pop::(); - } else { - *stack.top() = NockWork::Work11D(dint); } - push_formula(stack, dint.body, dint.tail)?; + Ok(None) => { + dint.todo = Todo11D::Done; + if dint.tail { + stack.pop::(); + } else { + *stack.top() = NockWork::Work11D(dint); + } + push_formula(stack, dint.body, dint.tail)?; + } + Err(err) => { + break Err(err); + } } } Todo11D::Done => { @@ -566,20 +618,25 @@ pub fn interpret( }, NockWork::Work11S(mut sint) => match sint.todo { Todo11S::ComputeResult => { - sint.todo = Todo11S::Done; - if let Some(found) = match_hint_pre_nock( + match 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 { + Ok(Some(found)) => { + res = found; stack.pop::(); - } else { - *stack.top() = NockWork::Work11S(sint); } - push_formula(stack, sint.body, sint.tail)?; + Ok(None) => { + sint.todo = Todo11S::Done; + if sint.tail { + stack.pop::(); + } else { + *stack.top() = NockWork::Work11S(sint); + } + push_formula(stack, sint.body, sint.tail)?; + } + Err(err) => { + break Err(err); + } } } Todo11S::Done => { @@ -595,9 +652,9 @@ pub fn interpret( } }); - match tone { + match nock { Ok(res) => Ok(res), - Err(_err) => Err(exit_early(stack, virtual_frame, &mut cache)), + Err(err) => Err(exit_early(stack, &mut cache, virtual_frame, err)), } } @@ -621,7 +678,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 +696,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 +720,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 +735,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 +752,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 +765,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 +779,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 +798,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 => { @@ -774,35 +831,40 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> Result<(), }); } 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 +872,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) } } @@ -937,49 +999,81 @@ fn match_hint_pre_hint( tag: Atom, hint: Noun, body: Noun, -) -> Option { +) -> Result, NockErr> { // XX: handle IndirectAtom tags - match tag.direct()?.data() { + match tag.as_direct()?.data() { // %sham hints are scaffolding until we have a real jet dashboard tas!(b"sham") => { - let jet_formula = hint.cell()?; + 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 - } else { - Some(jet_res) + if let Some(jet) = jets::get_jet(jet_name) { + match jet(stack, newt, 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(stack, newt, subject, body) { + Ok(mut nock_res) => { + 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, _)) => { + // 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") + } } - }) - .unwrap() - } else { - Some(jet_res) + } else { + Ok(Some(jet_res)) + } + } + Err(JetErr::Punt) => Ok(None), + Err(err) => { + // 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 { - // Print jet errors and punt to Nock - eprintln!("\rJet {} failed: ", jet_name); - None + Ok(None) } } tas!(b"memo") => { let mut key = Cell::new(stack, subject, body).as_noun(); - cache.lookup(stack, &mut key) + Ok(cache.lookup(stack, &mut key)) } - _ => None, + _ => Ok(None), } } @@ -992,24 +1086,31 @@ fn match_hint_pre_nock( _hint: Option, _body: Noun, res: Option, -) -> Option { +) -> Result, NockErr> { // XX: assert Some(res) <=> Some(hint) // XX: handle IndirectAtom tags - match tag.direct()?.data() { + match tag.as_direct()?.data() { tas!(b"slog") => { - let slog_cell = res?.cell()?; - let pri = slog_cell.head().direct()?.data(); + let slog_cell = res.ok_or(NockErr::Deterministic)?.as_cell()?; + let pri = slog_cell.head().as_direct()?.data(); let tank = slog_cell.tail(); if let Some(not) = newt { not.slog(stack, pri, tank); } else { eprintln!("raw slog: {} {}", 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?]); + let terminator = Arc::clone(&TERMINATOR); + if (*terminator).load(Ordering::Relaxed) { + return Err(NockErr::NonDeterministic); + } + + let noun = T(stack, &[tag.as_noun(), res.ok_or(NockErr::Deterministic)?]); mean_push(stack, noun); + Ok(None) } tas!(b"hela") => { // XX: should this be virtualized? @@ -1018,39 +1119,44 @@ fn match_hint_pre_nock( 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; - } - - let mut list = toon.tail(); - loop { - if unsafe { list.raw_equals(D(0)) } { - break; + match mook(stack, newt, tone, true) { + Ok(toon) => { + 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 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()); + let mut list = toon.tail(); + loop { + if unsafe { list.raw_equals(D(0)) } { + break; + } + + 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()); + } + + list = cell.tail(); } - list = cell.tail(); + Ok(None) + } + Err(err) => { + let tape = tape(stack, "%hela failed: mook error"); + let mean = T(stack, &[D(tas!(b"mean")), tape]); + mean_push(stack, mean); + Err(err.into()) } - } 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 */ diff --git a/rust/ares/src/jets/nock.rs b/rust/ares/src/jets/nock.rs index 6c50e7ea..0ab67114 100644 --- a/rust/ares/src/jets/nock.rs +++ b/rust/ares/src/jets/nock.rs @@ -1,15 +1,16 @@ /** Virtualization jets */ -use crate::interpreter::{interpret, NockErr}; +use crate::interpreter::{interpret, NockErr, Tone}; use crate::jets; use crate::jets::util::slot; +use crate::jets::JetErr; use crate::mem::NockStack; use crate::newt::Newt; use crate::noun::{Noun, D, T}; crate::gdb!(); -// XX: interpret should accept optional scry function and potentially produce blocked +// XX: interpret should accept optional scry function and potentially produce blocked pub fn jet_mink( stack: &mut NockStack, newt: &mut Option<&mut Newt>, @@ -22,12 +23,15 @@ pub fn jet_mink( let v_formula = slot(arg, 5)?; let _scry = slot(arg, 3)?; - // XX: NonDeterministic errors need to bail here, too + // XX: no partial traces; all of our traces go down to the "home road" 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])), + Tone::Blocked(block) => Ok(T(stack, &[D(1), block])), + Tone::Error(err, trace) => match err { + NockErr::Deterministic => Ok(T(stack, &[D(2), trace])), + NockErr::NonDeterministic => Err(JetErr::NonDeterministic), + }, }, } } @@ -252,6 +256,21 @@ pub mod util { mod tests { use super::*; use crate::jets::util::test::{assert_jet, init_stack}; + 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/lib.rs b/rust/ares/src/lib.rs index 5ae5ba2a..23e67e14 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/mem.rs b/rust/ares/src/mem.rs index 1ae78024..5bc2c35d 100644 --- a/rust/ares/src/mem.rs +++ b/rust/ares/src/mem.rs @@ -1,6 +1,7 @@ use crate::assert_acyclic; 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}; @@ -528,6 +529,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; } @@ -738,7 +751,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 { diff --git a/rust/ares/src/newt.rs b/rust/ares/src/newt.rs index a1ae33c5..1b996a94 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/serf.rs b/rust/ares/src/serf.rs index 02cbcc4a..1a089f9b 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -1,4 +1,4 @@ -use crate::interpreter::{interpret, NockErr}; +use crate::interpreter::{inc, interpret, Tone}; use crate::jets::nock::util::mook; use crate::jets::text::util::lent; use crate::mem::NockStack; @@ -7,12 +7,14 @@ use crate::newt::Newt; use crate::noun::{Cell, Noun, Slots, D, T}; use crate::snapshot::{self, 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!(); @@ -23,12 +25,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"))?; @@ -40,7 +50,7 @@ pub fn serf() -> io::Result<()> { // 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 stack = &mut NockStack::new(256 << 10 << 10, 0); let newt = &mut Newt::new(); let (_epoch, loaded_event_num, mut arvo) = snap.load(stack).unwrap_or((0, 0, D(0))); @@ -82,7 +92,6 @@ pub fn serf() -> io::Result<()> { 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) @@ -91,7 +100,7 @@ pub fn serf() -> io::Result<()> { lent(eve).expect("serf: play: boot event number failure") as u64; current_mug = mug_u32(stack, arvo); } - Err(NockErr::Error(trace)) => { + Err(Tone::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") @@ -99,7 +108,7 @@ pub fn serf() -> io::Result<()> { let goof = T(stack, &[D(tas!(b"exit")), tang]); newt.play_bail(stack, 0, 0, goof); } - Err(NockErr::Blocked(_)) => { + Err(Tone::Blocked(_)) => { panic!("play: blocked err handling unimplemented") } } @@ -118,7 +127,7 @@ pub fn serf() -> io::Result<()> { current_mug = mug_u32(stack, arvo); current_event_num += 1; } - Err(NockErr::Error(trace)) => { + Err(Tone::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") @@ -126,7 +135,7 @@ pub fn serf() -> io::Result<()> { let goof = T(stack, &[D(tas!(b"exit")), tang]); newt.play_bail(stack, current_event_num, current_mug as u64, goof); } - Err(NockErr::Blocked(_)) => { + Err(Tone::Blocked(_)) => { panic!("play: blocked err handling unimplemented") } } @@ -140,8 +149,9 @@ pub fn serf() -> io::Result<()> { } tas!(b"work") => { // XX: what is in slot 6? it's mil_w in Vere Serf + // TODO: assert event numbers are continuous let job = slot(writ, 7)?; - match slam(stack, newt, arvo, POKE_AXIS, job) { + match soft(stack, newt, arvo, POKE_AXIS, job) { Ok(res) => { let cell = res.as_cell().expect("serf: work: +slam returned atom"); let fec = cell.head(); @@ -153,24 +163,54 @@ pub fn serf() -> io::Result<()> { 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") + Err(goof) => { + // 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 now = inc( + stack, + job_cell.head().as_atom().expect("serf: work: now not atom"), + ) + .as_noun(); + let wire = T(stack, &[D(0), D(tas!(b"arvo")), D(0)]); + let crud = T(stack, &[now, wire, D(tas!(b"crud")), goof, job_cell.tail()]); + + match soft(stack, newt, arvo, POKE_AXIS, crud) { + Ok(res) => { + let cell = + res.as_cell().expect("serf: work: crud +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_swap( + stack, + current_event_num, + current_mug as u64, + crud, + fec, + ); + } + Err(goof_crud) => { + let lud = T(stack, &[goof_crud, goof, D(0)]); + newt.work_bail(stack, lud); + } + } } } } _ => panic!("got message with unknown tag {}", tag), }; + + clear_interrupt(); } Ok(()) @@ -182,7 +222,7 @@ pub fn slam( core: Noun, axis: u64, ovo: Noun, -) -> Result { +) -> 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)]); @@ -190,7 +230,36 @@ pub fn slam( interpret(stack, &mut Some(newt), sub, fol) } +/** Run slam, process stack trace to tang if error */ +pub fn soft( + stack: &mut NockStack, + newt: &mut Newt, + core: Noun, + axis: u64, + ovo: Noun, +) -> Result { + match slam(stack, newt, core, axis, ovo) { + Ok(res) => Ok(res), + Err(Tone::Error(_, trace)) => { + let tone = Cell::new(stack, D(2), trace); + let tang = mook(stack, &mut Some(newt), tone, false) + .expect("serf: soft: +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 + let goof = T(stack, &[D(tas!(b"exit")), tang]); + Err(goof) + } + Err(Tone::Blocked(_)) => panic!("soft: blocked err handling unimplemented"), + } +} + 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); +}