feat(tvix/eval): implement runtime tracing methods for Observer

These methods make it possible to trace the runtime execution of the
VM through an observer.

Change-Id: I90e26853ba2fe44748613e7f761ed5c1c5fc9ff7
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6452
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
This commit is contained in:
Vincent Ambo 2022-09-04 19:38:26 +03:00 committed by tazjin
parent cbf2d2d292
commit 14ff889d60
3 changed files with 44 additions and 30 deletions

View file

@ -3,7 +3,7 @@ use std::{path::PathBuf, rc::Rc};
use crate::{
builtins::global_builtins,
errors::{Error, ErrorKind, EvalResult},
observer::DisassemblingObserver,
observer::{DisassemblingObserver, NoOpObserver},
value::Value,
};
@ -68,5 +68,6 @@ pub fn interpret(code: &str, location: Option<PathBuf>) -> EvalResult<Value> {
return Err(err.clone());
}
crate::vm::run_lambda(result.lambda)
let mut tracer = NoOpObserver::default();
crate::vm::run_lambda(&mut tracer, result.lambda)
}

View file

@ -10,8 +10,9 @@ use std::rc::Rc;
use tabwriter::TabWriter;
use crate::chunk::Chunk;
use crate::opcode::CodeIdx;
use crate::opcode::{CodeIdx, OpCode};
use crate::value::Lambda;
use crate::Value;
/// Implemented by types that wish to observe internal happenings of
/// Tvix.
@ -33,6 +34,22 @@ pub trait Observer {
/// Called when the compiler finishes compilation of a thunk.
fn observe_compiled_thunk(&mut self, _: &Rc<Lambda>) {}
/// Called when the runtime enters a new call frame.
fn observe_enter_frame(&mut self, _arg_count: usize, _: &Rc<Lambda>, _call_depth: usize) {}
/// Called when the runtime exits a call frame.
fn observe_exit_frame(&mut self, _frame_at: usize) {}
/// Called when the runtime enters a builtin.
fn observe_enter_builtin(&mut self, _name: &'static str) {}
/// Called when the runtime exits a builtin.
fn observe_exit_builtin(&mut self, _name: &'static str) {}
/// Called when the runtime *begins* executing an instruction. The
/// provided stack is the state at the beginning of the operation.
fn observe_execute_op(&mut self, _ip: usize, _: &OpCode, _: &[Value]) {}
}
#[derive(Default)]

View file

@ -6,6 +6,7 @@ use std::{cell::RefMut, rc::Rc};
use crate::{
chunk::Chunk,
errors::{Error, ErrorKind, EvalResult},
observer::Observer,
opcode::{CodeIdx, ConstantIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx},
upvalues::UpvalueCarrier,
value::{Closure, Lambda, NixAttrs, NixList, Thunk, Value},
@ -25,8 +26,7 @@ impl CallFrame {
}
}
#[derive(Default)]
pub struct VM {
pub struct VM<'o> {
frames: Vec<CallFrame>,
stack: Vec<Value>,
@ -34,8 +34,7 @@ pub struct VM {
// dynamically resolved (`with`).
with_stack: Vec<usize>,
#[cfg(feature = "disassembler")]
pub tracer: crate::disassembler::Tracer,
observer: &'o mut dyn Observer,
}
/// This macro wraps a computation that returns an ErrorKind or a
@ -111,7 +110,16 @@ macro_rules! cmp_op {
}};
}
impl VM {
impl<'o> VM<'o> {
pub fn new(observer: &'o mut dyn Observer) -> Self {
Self {
observer,
frames: vec![],
stack: vec![],
with_stack: vec![],
}
}
fn frame(&self) -> &CallFrame {
&self.frames[self.frames.len() - 1]
}
@ -171,13 +179,8 @@ impl VM {
upvalues: Vec<Value>,
arg_count: usize,
) -> EvalResult<Value> {
#[cfg(feature = "disassembler")]
self.tracer.literal(&format!(
"=== entering closure/{} @ {:p} [{}] ===",
arg_count,
lambda,
self.frames.len()
));
self.observer
.observe_enter_frame(arg_count, &lambda, self.frames.len() + 1);
let frame = CallFrame {
lambda,
@ -189,12 +192,7 @@ impl VM {
self.frames.push(frame);
let result = self.run();
#[cfg(feature = "disassembler")]
self.tracer.literal(&format!(
"=== exiting closure/{} [{}] ===",
arg_count,
self.frames.len()
));
self.observer.observe_exit_frame(self.frames.len() + 1);
result
}
@ -213,8 +211,8 @@ impl VM {
let op = self.inc_ip();
#[cfg(feature = "disassembler")]
self.tracer.trace(&op, self.frame().ip, &self.stack);
self.observer
.observe_execute_op(self.frame().ip, &op, &self.stack);
match op {
OpCode::OpConstant(idx) => {
@ -450,15 +448,13 @@ impl VM {
}
Value::Builtin(builtin) => {
#[cfg(feature = "disassembler")]
self.tracer
.literal(&format!("=== entering builtins.{} ===", builtin.name()));
let builtin_name = builtin.name();
self.observer.observe_enter_builtin(builtin_name);
let arg = self.pop();
let result = fallible!(self, builtin.apply(self, arg));
#[cfg(feature = "disassembler")]
self.tracer.literal("=== exiting builtin ===");
self.observer.observe_exit_builtin(builtin_name);
self.push(result);
}
@ -711,8 +707,8 @@ fn unwrap_or_clone_rc<T: Clone>(rc: Rc<T>) -> T {
Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone())
}
pub fn run_lambda(lambda: Rc<Lambda>) -> EvalResult<Value> {
let mut vm = VM::default();
pub fn run_lambda(observer: &mut dyn Observer, lambda: Rc<Lambda>) -> EvalResult<Value> {
let mut vm = VM::new(observer);
let value = vm.call(lambda, vec![], 0)?;
vm.force_for_output(&value)?;
Ok(value)