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:
parent
cbf2d2d292
commit
14ff889d60
3 changed files with 44 additions and 30 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue