jets: consolidate virtualization logic

This commit is contained in:
Alex Shelkovnykov 2023-12-05 16:17:38 -03:00
parent ff08e07b3f
commit adbd8a3603
3 changed files with 158 additions and 147 deletions

View File

@ -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<Noun>,
pub scry_stack: Noun,
pub trace_info: Option<TraceInfo>,
}
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 {

View File

@ -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<Noun, Error> {
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<Noun, Error> {
let cache_snapshot = context.cache;
let scry_snapshot = context.scry_stack;
context.cache = Hamt::<Noun>::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<Cell, JetErr> {
pub fn mook(context: &mut Context, tone: Cell, flop: bool) -> result::Result<Cell, Error> {
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, "####");

View File

@ -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::<Noun>::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::<Noun>::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<String>) -> Result<Noun, Noun> {
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<String>) -> 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(_) => {