mirror of
https://github.com/urbit/ares.git
synced 2024-11-26 20:58:02 +03:00
jets: consolidate virtualization logic
This commit is contained in:
parent
ff08e07b3f
commit
adbd8a3603
@ -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 {
|
||||
|
@ -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, "####");
|
||||
|
@ -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(_) => {
|
||||
|
Loading…
Reference in New Issue
Block a user