From adbd8a360347666addaa475418171bc92e1c5295 Mon Sep 17 00:00:00 2001 From: Alex Shelkovnykov Date: Tue, 5 Dec 2023 16:17:38 -0300 Subject: [PATCH] jets: consolidate virtualization logic --- rust/ares/src/interpreter.rs | 42 ++++-- rust/ares/src/jets/nock.rs | 248 +++++++++++++++++------------------ rust/ares/src/serf.rs | 15 +-- 3 files changed, 158 insertions(+), 147 deletions(-) diff --git a/rust/ares/src/interpreter.rs b/rust/ares/src/interpreter.rs index 8540efb..2ebb752 100644 --- a/rust/ares/src/interpreter.rs +++ b/rust/ares/src/interpreter.rs @@ -254,20 +254,36 @@ enum NockWork { Work12(Nock12), } +pub struct ContextSnapshot { + cold: Cold, + warm: Warm, +} + pub struct Context { pub stack: NockStack, - // For printing slogs; if None, print to stdout; Option slated to be removed pub newt: Newt, pub cold: Cold, pub warm: Warm, pub hot: Hot, - // XX: persistent memo cache - // Per-event cache; option to share cache with virtualized events pub cache: Hamt, pub scry_stack: Noun, pub trace_info: Option, } +impl Context { + pub fn save(&self) -> ContextSnapshot { + ContextSnapshot { + cold: self.cold, + warm: self.warm, + } + } + + pub fn restore(&mut self, saved: &ContextSnapshot) { + self.cold = saved.cold; + self.warm = saved.warm; + } +} + #[derive(Clone, Copy, Debug)] pub enum Error { ScryBlocked(Noun), // path @@ -301,6 +317,7 @@ fn debug_assertions(stack: &mut NockStack, noun: Noun) { pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Result { let terminator = Arc::clone(&TERMINATOR); let orig_subject = subject; // for debugging + let snapshot = context.save(); let virtual_frame: *const u64 = context.stack.get_frame_pointer(); let mut res: Noun = D(0); @@ -858,7 +875,7 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res match nock { Ok(res) => Ok(res), - Err(err) => Err(exit(context, virtual_frame, err)), + Err(err) => Err(exit(context, &snapshot, virtual_frame, err)), } } @@ -1075,10 +1092,16 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> result::Res Ok(()) } -fn exit(context: &mut Context, virtual_frame: *const u64, error: Error) -> Error { +fn exit( + context: &mut Context, + snapshot: &ContextSnapshot, + virtual_frame: *const u64, + error: Error, +) -> Error { unsafe { - let stack = &mut context.stack; + context.restore(snapshot); + let stack = &mut context.stack; let mut preserve = match error { Error::ScryBlocked(path) => path, Error::Deterministic(t) | Error::NonDeterministic(t) | Error::ScryCrashed(t) => { @@ -1088,10 +1111,9 @@ fn exit(context: &mut Context, virtual_frame: *const u64, error: Error) -> Error } }; - while (stack).get_frame_pointer() != virtual_frame { - (stack).preserve(&mut preserve); - // (stack).preserve(&mut context.cold); - (stack).frame_pop(); + while stack.get_frame_pointer() != virtual_frame { + stack.preserve(&mut preserve); + stack.frame_pop(); } match error { diff --git a/rust/ares/src/jets/nock.rs b/rust/ares/src/jets/nock.rs index 4cf6ff5..45dbe4b 100644 --- a/rust/ares/src/jets/nock.rs +++ b/rust/ares/src/jets/nock.rs @@ -1,32 +1,12 @@ /** Virtualization jets */ -use crate::interpreter::{interpret, Context, Error}; +use crate::interpreter::Context; use crate::jets::util::slot; use crate::jets::{JetErr, Result}; -use crate::noun::{Cell, Noun, D, NO, T, YES}; +use crate::noun::{Noun, D, NO, T}; crate::gdb!(); -// Deterministic scry crashes should have the following behaviour: -// -// root <-- bail, %crud -// mink <-- return Deterministic -// scry <-- return ScryCrashed -// -// root <-- bail, %crud -// mink <-- return Deterministic -// mink <-- return ScryCrashed -// scry <-- return ScryCrashed -// scry <-- return ScryCrashed -// -// root -// mink <-- return Tone -// mink <-- return Deterministic -// mink <-- return ScryCrashed -// scry <-- return ScryCrashed -// scry <-- return ScryCrashed -// scry -// pub fn jet_mink(context: &mut Context, subject: Noun) -> Result { let arg = slot(subject, 6)?; // mink sample = [nock scry_namespace] @@ -35,41 +15,8 @@ pub fn jet_mink(context: &mut Context, subject: Noun) -> Result { let v_formula = slot(arg, 5)?; let scry_handler = slot(arg, 3)?; - let snapshot = util::snapshot_and_virtualize(context, scry_handler); - - match util::mink(context, v_subject, v_formula) { - Ok(noun) => { - context.cache = snapshot.cache; - context.scry_stack = snapshot.scry_stack; - Ok(noun) - } - Err(error) => match error { - Error::NonDeterministic(_) => Err(JetErr::Fail(error)), - Error::ScryCrashed(trace) => { - // When we enter a +mink call, we record the state of the scry handler stack at the - // time (i.e. the Noun representing (list scry)). Each scry will pop the head off of - // this scry handler stack and calls interpret(), using the rest of the scry handler - // stack in the event that it scries again recursively. When a scry succeeds, it - // replaces the scry handler that it used by pushing it back onto the top of the - // scry handler stack. However, it never does so when it fails. Therefore, we can - // tell which particular virtualization instance failed by comparing the scry - // handler stack at the time of failure (i.e. the scry handler stack in the context - // after a failed scry) with the scry handler stack at the time of the virtualization - // call. Thus, whenever a virtualized interpret() call fails with a - // Error::ScryCrashed, jet_mink() compares the two scry handler stack Nouns> If they - // are identical, jet_mink() bails with Error::Deterministic. Otherwise, it forwards - // the Error::ScryCrashed to the senior virtualization call. - if unsafe { context.scry_stack.raw_equals(snapshot.scry_stack) } { - Err(JetErr::Fail(Error::Deterministic(trace))) - } else { - Err(JetErr::Fail(error)) - } - } - Error::Deterministic(_) | Error::ScryBlocked(_) => { - panic!("scry: mink: unhandled errors in helper") - } - }, - } + // Implicit error conversion + Ok(util::mink(context, v_subject, v_formula, scry_handler)?) } pub fn jet_mole(context: &mut Context, subject: Noun) -> Result { @@ -85,29 +32,15 @@ pub fn jet_mure(context: &mut Context, subject: Noun) -> Result { let fol = util::slam_gate_fol(&mut context.stack); let scry = util::pass_thru_scry(&mut context.stack); - let snapshot = util::snapshot_and_virtualize(context, scry); - - match interpret(context, tap, fol) { - Ok(res) => { - context.cache = snapshot.cache; - context.scry_stack = snapshot.scry_stack; - Ok(T(&mut context.stack, &[D(0), res])) + match util::mink(context, tap, fol, scry) { + Ok(tone) => { + if unsafe { tone.as_cell()?.head().raw_equals(D(0)) } { + Ok(tone) + } else { + Ok(D(0)) + } } - Err(error) => match error { - // Since we are using the pass-through scry handler, we know for a fact that a scry - // crash must have come from a senior virtualization context. - Error::NonDeterministic(_) | Error::ScryCrashed(_) => Err(JetErr::Fail(error)), - Error::ScryBlocked(_) => { - context.scry_stack = snapshot.scry_stack; - Ok(D(0)) - } - Error::Deterministic(_) => { - context.cold = snapshot.cold; - context.warm = snapshot.warm; - context.scry_stack = snapshot.scry_stack; - Ok(D(0)) - } - }, + Err(err) => Err(JetErr::Fail(err)), } } @@ -116,34 +49,23 @@ pub fn jet_mute(context: &mut Context, subject: Noun) -> Result { let fol = util::slam_gate_fol(&mut context.stack); let scry = util::pass_thru_scry(&mut context.stack); - let snapshot = util::snapshot_and_virtualize(context, scry); + let tone = util::mink(context, tap, fol, scry); - match interpret(context, tap, fol) { - Ok(res) => { - context.cache = snapshot.cache; - context.scry_stack = snapshot.scry_stack; - Ok(T(&mut context.stack, &[YES, res])) + match util::mook(context, tone?.as_cell()?, false) { + Ok(toon) => { + match toon.head() { + x if unsafe { x.raw_equals(D(0)) } => Ok(toon.as_noun()), + x if unsafe { x.raw_equals(D(1)) } => { + // XX: Need to check that result is actually of type path + // return [[%leaf "mute.hunk"] ~] if not + let bon = util::smyt(&mut context.stack, toon.tail())?; + Ok(T(&mut context.stack, &[NO, bon, D(0)])) + } + x if unsafe { x.raw_equals(D(2)) } => Ok(T(&mut context.stack, &[NO, toon.tail()])), + _ => panic!("serf: mook: invalid toon"), + } } - Err(error) => match error { - // Since we are using the pass-through scry handler, we know for a fact that a scry - // crash must have come from a senior virtualization context. - Error::NonDeterministic(_) | Error::ScryCrashed(_) => Err(JetErr::Fail(error)), - Error::ScryBlocked(path) => { - context.scry_stack = snapshot.scry_stack; - // XX: Need to check that result is actually of type path - // return [[%leaf "mute.hunk"] ~] if not - let bon = util::smyt(&mut context.stack, path)?; - Ok(T(&mut context.stack, &[NO, bon, D(0)])) - } - Error::Deterministic(trace) => { - context.cold = snapshot.cold; - context.warm = snapshot.warm; - context.scry_stack = snapshot.scry_stack; - let ton = Cell::new(&mut context.stack, D(2), trace); - let tun = util::mook(context, ton, false)?; - Ok(T(&mut context.stack, &[NO, tun.tail()])) - } - }, + Err(err) => Err(JetErr::Fail(err)), } } @@ -155,7 +77,6 @@ pub mod util { use crate::jets::cold::Cold; use crate::jets::form::util::scow; use crate::jets::warm::Warm; - use crate::jets::JetErr; use crate::mem::NockStack; use crate::noun::{tape, Cell, Noun, D, T}; use ares_macros::tas; @@ -193,7 +114,7 @@ pub mod util { /// The classic "pass-through" scry handler. pub fn pass_thru_scry(stack: &mut NockStack) -> Noun { - // > .* 0 != => ~ |=(a=^ ``.*(a [%12 [%0 2] %0 3])) + // .* 0 != => ~ |=(a=^ ``.*(a [%12 [%0 2] %0 3])) // [[[1 0] [1 0] 2 [0 6] 1 12 [0 2] 0 3] [0 0] 0] let sig = T(stack, &[D(1), D(0)]); let sam = T(stack, &[D(0), D(6)]); @@ -212,19 +133,94 @@ pub mod util { T(stack, &[dos, gat]) } - pub fn mink(context: &mut Context, subject: Noun, formula: Noun) -> Result { - let cold_snapshot = context.cold; - let warm_snapshot = context.warm; + /// The "always-fail" scry + pub fn null_scry(stack: &mut NockStack) -> Noun { + // .* 0 != => ~ |=(^ ~) + // [[1 0] [0 0] 0] + let sig = T(stack, &[D(1), D(0)]); + let zap = T(stack, &[D(0), D(0)]); + + T(stack, &[sig, zap, D(0)]) + } + + // Deterministic scry crashes should have the following behaviour: + // + // root <-- bail, %crud + // mink <-- return Deterministic + // scry <-- return ScryCrashed + // + // root <-- bail, %crud + // mink <-- return Deterministic + // mink <-- return ScryCrashed + // scry <-- return ScryCrashed + // scry <-- return ScryCrashed + // + // root + // mink <-- return Tone + // mink <-- return Deterministic + // mink <-- return ScryCrashed + // scry <-- return ScryCrashed + // scry <-- return ScryCrashed + // scry + // + pub fn mink( + context: &mut Context, + subject: Noun, + formula: Noun, + scry: Noun, + ) -> Result { + let cache_snapshot = context.cache; + let scry_snapshot = context.scry_stack; + + context.cache = Hamt::::new(); + context.scry_stack = T(&mut context.stack, &[scry, context.scry_stack]); + match interpret(context, subject, formula) { - Ok(res) => Ok(T(&mut context.stack, &[D(0), res])), + Ok(res) => { + context.cache = cache_snapshot; + context.scry_stack = scry_snapshot; + Ok(T(&mut context.stack, &[D(0), res])) + } Err(err) => match err { - Error::ScryBlocked(path) => Ok(T(&mut context.stack, &[D(1), path])), + Error::ScryBlocked(path) => { + context.cache = cache_snapshot; + context.scry_stack = scry_snapshot; + Ok(T(&mut context.stack, &[D(1), path])) + } Error::Deterministic(trace) => { - context.cold = cold_snapshot; - context.warm = warm_snapshot; + context.cache = cache_snapshot; + context.scry_stack = scry_snapshot; Ok(T(&mut context.stack, &[D(2), trace])) } - Error::NonDeterministic(_) | Error::ScryCrashed(_) => Err(err), + Error::ScryCrashed(trace) => { + context.cache = cache_snapshot; + // When we enter a +mink call, we record the state of the scry handler stack at the + // time (i.e. the Noun representing (list scry)). Each scry will pop the head off of + // this scry handler stack and calls interpret(), using the rest of the scry handler + // stack in the event that it scries again recursively. When a scry succeeds, it + // replaces the scry handler that it used by pushing it back onto the top of the + // scry handler stack. However, it never does so when it fails. Therefore, we can + // tell which particular virtualization instance failed by comparing the scry + // handler stack at the time of failure (i.e. the scry handler stack in the context + // after a failed scry) with the scry handler stack at the time of the virtualization + // call. Thus, whenever a virtualized interpret() call fails with a + // Error::ScryCrashed, jet_mink() compares the two scry handler stack Nouns> If they + // are identical, jet_mink() bails with Error::Deterministic. Otherwise, it forwards + // the Error::ScryCrashed to the senior virtualization call. + if unsafe { context.scry_stack.raw_equals(scry_snapshot) } { + Err(Error::Deterministic(trace)) + } else { + Err(err) + } + } + Error::NonDeterministic(_) => { + // We choose to restore the cache and scry stack even on NonDeterministic errors + // to keep the logic all in one place (as opposed to having the serf reset them + // manually ONLY for NonDeterministic errors). + context.cache = cache_snapshot; + context.scry_stack = scry_snapshot; + Err(err) + } }, } } @@ -232,14 +228,14 @@ pub mod util { /** Consume $tone, produce $toon */ // XX: should write a jet_mook wrapper for this function - pub fn mook(context: &mut Context, 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(); if (tag.data() != 2) | unsafe { original_list.raw_equals(D(0)) } { return Ok(tone); } else if original_list.atom().is_some() { - return Err(JetErr::Fail(Error::Deterministic(D(0)))); + return Err(Error::Deterministic(D(0))); } // XX: trim traces longer than 1024 frames @@ -277,17 +273,17 @@ pub mod util { } Right(_cell) => { // 'tank: { - // if let Ok(tone) = mink(context, dat, cell.head()) { - // if let Some(cell) = tone.cell() { - // if cell.head().raw_equals(D(0)) { - // // XX: need to check that this is - // // actually a path; - // // return leaf+"mook.mean" if not - // break 'tank cell.tail(); + // let scry = null_scry(context); + // if let Ok(tone) = mink(context, dat, cell.head(), scry) { + // if let Some(cell) = tone.cell() { + // if cell.head().raw_equals(D(0)) { + // // XX: need to check that this is + // // actually a path; + // // return leaf+"mook.mean" if not + // break 'tank cell.tail(); + // } // } // } - // } - { let stack = &mut context.stack; let tape = tape(stack, "####"); diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index 870028c..75f2454 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -42,12 +42,9 @@ impl Context { // 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(1024 << 10 << 10, 0); - let newt = Newt::new(); - let cache = Hamt::::new(); + let mut stack = NockStack::new(256 << 10 << 10, 0); let cold = Cold::new(&mut stack); - let warm = Warm::new(); let hot = Hot::init(&mut stack); let (epoch, event_num, arvo) = snapshot.load(&mut stack).unwrap_or((0, 0, D(0))); @@ -55,11 +52,11 @@ impl Context { let nock_context = interpreter::Context { stack, - newt, + newt: Newt::new(), cold, - warm, + warm: Warm::new(), hot, - cache, + cache: Hamt::::new(), scry_stack: D(0), trace_info, }; @@ -321,8 +318,6 @@ fn goof(context: &mut Context, traces: Noun) -> Noun { * Generate tracing events, if JSON tracing enabled. */ fn soft(context: &mut Context, ovo: Noun, trace_name: Option) -> Result { - let cold_snapshot = context.nock_context.cold; - let warm_snapshot = context.nock_context.warm; let slam_res = if context.nock_context.trace_info.is_some() { let start = Instant::now(); let slam_res = slam(context, POKE_AXIS, ovo); @@ -341,8 +336,6 @@ fn soft(context: &mut Context, ovo: Noun, trace_name: Option) -> Result< Ok(res) => Ok(res), Err(error) => match error { Error::Deterministic(trace) | Error::NonDeterministic(trace) => { - context.nock_context.cold = cold_snapshot; - context.nock_context.warm = warm_snapshot; Err(goof(context, trace)) } Error::ScryBlocked(_) | Error::ScryCrashed(_) => {