From 14ff889d607635083a030fc73d76b0263759be83 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sun, 4 Sep 2022 19:38:26 +0300 Subject: 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 Tested-by: BuildkiteCI --- tvix/eval/src/eval.rs | 5 +++-- tvix/eval/src/observer.rs | 19 +++++++++++++++++- tvix/eval/src/vm.rs | 50 ++++++++++++++++++++++------------------------- 3 files changed, 44 insertions(+), 30 deletions(-) (limited to 'tvix/eval/src') diff --git a/tvix/eval/src/eval.rs b/tvix/eval/src/eval.rs index f510cb6a5eaf..47c7203d28ac 100644 --- a/tvix/eval/src/eval.rs +++ b/tvix/eval/src/eval.rs @@ -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) -> EvalResult { return Err(err.clone()); } - crate::vm::run_lambda(result.lambda) + let mut tracer = NoOpObserver::default(); + crate::vm::run_lambda(&mut tracer, result.lambda) } diff --git a/tvix/eval/src/observer.rs b/tvix/eval/src/observer.rs index 62312a74ad36..427fc2c3996a 100644 --- a/tvix/eval/src/observer.rs +++ b/tvix/eval/src/observer.rs @@ -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) {} + + /// Called when the runtime enters a new call frame. + fn observe_enter_frame(&mut self, _arg_count: usize, _: &Rc, _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)] diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index b9445e39f1b1..73a03263c864 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -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, stack: Vec, @@ -34,8 +34,7 @@ pub struct VM { // dynamically resolved (`with`). with_stack: Vec, - #[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, arg_count: usize, ) -> EvalResult { - #[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(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } -pub fn run_lambda(lambda: Rc) -> EvalResult { - let mut vm = VM::default(); +pub fn run_lambda(observer: &mut dyn Observer, lambda: Rc) -> EvalResult { + let mut vm = VM::new(observer); let value = vm.call(lambda, vec![], 0)?; vm.force_for_output(&value)?; Ok(value) -- cgit 1.4.1