2022-10-03 15:08:59 +02:00
|
|
|
//! Implements traits for things that wish to observe internal state
|
2022-09-04 15:51:33 +02:00
|
|
|
//! changes of tvix-eval.
|
|
|
|
//!
|
|
|
|
//! This can be used to gain insights from compilation, to trace the
|
|
|
|
//! runtime, and so on.
|
2022-10-03 15:08:59 +02:00
|
|
|
//!
|
|
|
|
//! All methods are optional, that is, observers can implement only
|
|
|
|
/// what they are interested in observing.
|
2022-09-04 15:56:20 +02:00
|
|
|
use std::io::Write;
|
2022-09-04 15:51:33 +02:00
|
|
|
use std::rc::Rc;
|
2022-09-04 15:56:20 +02:00
|
|
|
use tabwriter::TabWriter;
|
|
|
|
|
|
|
|
use crate::chunk::Chunk;
|
2023-03-13 22:44:48 +01:00
|
|
|
use crate::generators::VMRequest;
|
2022-09-04 18:38:26 +02:00
|
|
|
use crate::opcode::{CodeIdx, OpCode};
|
2022-09-04 15:56:20 +02:00
|
|
|
use crate::value::Lambda;
|
2022-10-04 16:05:34 +02:00
|
|
|
use crate::SourceCode;
|
2022-09-04 18:38:26 +02:00
|
|
|
use crate::Value;
|
2022-09-04 15:51:33 +02:00
|
|
|
|
|
|
|
/// Implemented by types that wish to observe internal happenings of
|
2022-10-03 15:08:59 +02:00
|
|
|
/// the Tvix compiler.
|
|
|
|
pub trait CompilerObserver {
|
2022-09-04 15:51:33 +02:00
|
|
|
/// Called when the compiler finishes compilation of the top-level
|
|
|
|
/// of an expression (usually the root Nix expression of a file).
|
|
|
|
fn observe_compiled_toplevel(&mut self, _: &Rc<Lambda>) {}
|
|
|
|
|
|
|
|
/// Called when the compiler finishes compilation of a
|
|
|
|
/// user-defined function.
|
|
|
|
///
|
|
|
|
/// Note that in Nix there are only single argument functions, so
|
|
|
|
/// in an expression like `a: b: c: ...` this method will be
|
|
|
|
/// called three times.
|
|
|
|
fn observe_compiled_lambda(&mut self, _: &Rc<Lambda>) {}
|
|
|
|
|
|
|
|
/// Called when the compiler finishes compilation of a thunk.
|
|
|
|
fn observe_compiled_thunk(&mut self, _: &Rc<Lambda>) {}
|
2022-10-03 15:08:59 +02:00
|
|
|
}
|
2022-09-04 18:38:26 +02:00
|
|
|
|
2022-10-03 15:08:59 +02:00
|
|
|
/// Implemented by types that wish to observe internal happenings of
|
|
|
|
/// the Tvix virtual machine at runtime.
|
|
|
|
pub trait RuntimeObserver {
|
2022-09-04 18:38:26 +02:00
|
|
|
/// Called when the runtime enters a new call frame.
|
2023-03-10 12:22:52 +01:00
|
|
|
fn observe_enter_call_frame(&mut self, _arg_count: usize, _: &Rc<Lambda>, _call_depth: usize) {}
|
2022-09-04 18:38:26 +02:00
|
|
|
|
|
|
|
/// Called when the runtime exits a call frame.
|
2023-03-10 12:22:52 +01:00
|
|
|
fn observe_exit_call_frame(&mut self, _frame_at: usize, _stack: &[Value]) {}
|
|
|
|
|
|
|
|
/// Called when the runtime suspends a call frame.
|
|
|
|
fn observe_suspend_call_frame(&mut self, _frame_at: usize, _stack: &[Value]) {}
|
|
|
|
|
|
|
|
/// Called when the runtime enters a generator frame.
|
2023-03-03 22:52:37 +01:00
|
|
|
fn observe_enter_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
|
2023-03-10 12:22:52 +01:00
|
|
|
|
|
|
|
/// Called when the runtime exits a generator frame.
|
2023-03-03 22:52:37 +01:00
|
|
|
fn observe_exit_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
|
2023-03-10 12:22:52 +01:00
|
|
|
|
|
|
|
/// Called when the runtime suspends a generator frame.
|
2023-03-03 22:52:37 +01:00
|
|
|
fn observe_suspend_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
|
2023-03-10 12:22:52 +01:00
|
|
|
|
|
|
|
/// Called when a generator requests an action from the VM.
|
2023-03-13 22:44:48 +01:00
|
|
|
fn observe_generator_request(&mut self, _name: &str, _msg: &VMRequest) {}
|
2022-09-04 18:38:26 +02:00
|
|
|
|
2022-09-04 22:16:59 +02:00
|
|
|
/// Called when the runtime replaces the current call frame for a
|
|
|
|
/// tail call.
|
|
|
|
fn observe_tail_call(&mut self, _frame_at: usize, _: &Rc<Lambda>) {}
|
|
|
|
|
2022-09-04 18:38:26 +02:00
|
|
|
/// Called when the runtime enters a builtin.
|
|
|
|
fn observe_enter_builtin(&mut self, _name: &'static str) {}
|
|
|
|
|
|
|
|
/// Called when the runtime exits a builtin.
|
2022-10-11 01:24:37 +02:00
|
|
|
fn observe_exit_builtin(&mut self, _name: &'static str, _stack: &[Value]) {}
|
2022-09-04 18:38:26 +02:00
|
|
|
|
|
|
|
/// Called when the runtime *begins* executing an instruction. The
|
|
|
|
/// provided stack is the state at the beginning of the operation.
|
2022-09-13 14:58:55 +02:00
|
|
|
fn observe_execute_op(&mut self, _ip: CodeIdx, _: &OpCode, _: &[Value]) {}
|
2022-09-04 15:51:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct NoOpObserver {}
|
|
|
|
|
2022-10-03 15:08:59 +02:00
|
|
|
impl CompilerObserver for NoOpObserver {}
|
|
|
|
impl RuntimeObserver for NoOpObserver {}
|
2022-09-04 15:56:20 +02:00
|
|
|
|
|
|
|
/// An observer that prints disassembled chunk information to its
|
|
|
|
/// internal writer whenwever the compiler emits a toplevel function,
|
|
|
|
/// closure or thunk.
|
|
|
|
pub struct DisassemblingObserver<W: Write> {
|
2022-10-04 16:05:34 +02:00
|
|
|
source: SourceCode,
|
2022-09-04 15:56:20 +02:00
|
|
|
writer: TabWriter<W>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<W: Write> DisassemblingObserver<W> {
|
2022-10-04 16:05:34 +02:00
|
|
|
pub fn new(source: SourceCode, writer: W) -> Self {
|
2022-09-04 15:56:20 +02:00
|
|
|
Self {
|
2022-10-04 16:05:34 +02:00
|
|
|
source,
|
2022-09-04 15:56:20 +02:00
|
|
|
writer: TabWriter::new(writer),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn lambda_header(&mut self, kind: &str, lambda: &Rc<Lambda>) {
|
|
|
|
let _ = writeln!(
|
|
|
|
&mut self.writer,
|
|
|
|
"=== compiled {} @ {:p} ({} ops) ===",
|
|
|
|
kind,
|
2022-09-06 20:26:31 +02:00
|
|
|
*lambda,
|
2022-09-04 15:56:20 +02:00
|
|
|
lambda.chunk.code.len()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn disassemble_chunk(&mut self, chunk: &Chunk) {
|
|
|
|
// calculate width of the widest address in the chunk
|
|
|
|
let width = format!("{:#x}", chunk.code.len() - 1).len();
|
|
|
|
|
|
|
|
for (idx, _) in chunk.code.iter().enumerate() {
|
2022-10-04 16:05:34 +02:00
|
|
|
let _ = chunk.disassemble_op(&mut self.writer, &self.source, width, CodeIdx(idx));
|
2022-09-04 15:56:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-03 15:08:59 +02:00
|
|
|
impl<W: Write> CompilerObserver for DisassemblingObserver<W> {
|
2022-09-04 15:56:20 +02:00
|
|
|
fn observe_compiled_toplevel(&mut self, lambda: &Rc<Lambda>) {
|
|
|
|
self.lambda_header("toplevel", lambda);
|
|
|
|
self.disassemble_chunk(&lambda.chunk);
|
|
|
|
let _ = self.writer.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn observe_compiled_lambda(&mut self, lambda: &Rc<Lambda>) {
|
|
|
|
self.lambda_header("lambda", lambda);
|
|
|
|
self.disassemble_chunk(&lambda.chunk);
|
|
|
|
let _ = self.writer.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn observe_compiled_thunk(&mut self, lambda: &Rc<Lambda>) {
|
|
|
|
self.lambda_header("thunk", lambda);
|
|
|
|
self.disassemble_chunk(&lambda.chunk);
|
|
|
|
let _ = self.writer.flush();
|
|
|
|
}
|
|
|
|
}
|
2022-09-04 19:06:27 +02:00
|
|
|
|
|
|
|
/// An observer that collects a textual representation of an entire
|
|
|
|
/// runtime execution.
|
|
|
|
pub struct TracingObserver<W: Write> {
|
|
|
|
writer: TabWriter<W>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<W: Write> TracingObserver<W> {
|
|
|
|
pub fn new(writer: W) -> Self {
|
|
|
|
Self {
|
|
|
|
writer: TabWriter::new(writer),
|
|
|
|
}
|
|
|
|
}
|
2023-03-03 19:23:12 +01:00
|
|
|
|
2023-03-03 19:42:12 +01:00
|
|
|
fn write_value(&mut self, val: &Value) {
|
|
|
|
let _ = match val {
|
|
|
|
// Potentially large types which we only want to print
|
|
|
|
// the type of (and avoid recursing).
|
|
|
|
Value::List(l) => write!(&mut self.writer, "list[{}] ", l.len()),
|
|
|
|
Value::Attrs(a) => write!(&mut self.writer, "attrs[{}] ", a.len()),
|
2023-08-19 17:25:06 +02:00
|
|
|
Value::Thunk(t) if t.is_evaluated() => {
|
|
|
|
self.write_value(&t.value());
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-03-03 19:42:12 +01:00
|
|
|
|
|
|
|
// For other value types, defer to the standard value printer.
|
|
|
|
_ => write!(&mut self.writer, "{} ", val),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-03 19:23:12 +01:00
|
|
|
fn write_stack(&mut self, stack: &[Value]) {
|
|
|
|
let _ = write!(&mut self.writer, "[ ");
|
|
|
|
|
2023-03-03 20:53:48 +01:00
|
|
|
// Print out a maximum of 6 values from the top of the stack,
|
|
|
|
// before abbreviating it to `...`.
|
|
|
|
for (i, val) in stack.iter().rev().enumerate() {
|
|
|
|
if i == 6 {
|
|
|
|
let _ = write!(&mut self.writer, "...");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-08-19 17:28:56 +02:00
|
|
|
self.write_value(val);
|
2023-03-03 19:23:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let _ = writeln!(&mut self.writer, "]");
|
|
|
|
}
|
2022-09-04 19:06:27 +02:00
|
|
|
}
|
|
|
|
|
2022-10-03 15:08:59 +02:00
|
|
|
impl<W: Write> RuntimeObserver for TracingObserver<W> {
|
2023-03-10 12:22:52 +01:00
|
|
|
fn observe_enter_call_frame(
|
|
|
|
&mut self,
|
|
|
|
arg_count: usize,
|
|
|
|
lambda: &Rc<Lambda>,
|
|
|
|
call_depth: usize,
|
|
|
|
) {
|
2022-10-22 22:55:21 +02:00
|
|
|
let _ = write!(&mut self.writer, "=== entering ");
|
|
|
|
|
|
|
|
let _ = if arg_count == 0 {
|
|
|
|
write!(&mut self.writer, "thunk ")
|
|
|
|
} else {
|
|
|
|
write!(&mut self.writer, "closure ")
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(name) = &lambda.name {
|
|
|
|
let _ = write!(&mut self.writer, "'{}' ", name);
|
|
|
|
}
|
|
|
|
|
2022-09-04 19:06:27 +02:00
|
|
|
let _ = writeln!(
|
|
|
|
&mut self.writer,
|
2022-10-22 22:55:21 +02:00
|
|
|
"in frame[{}] @ {:p} ===",
|
2023-02-07 20:06:55 +01:00
|
|
|
call_depth, *lambda
|
2022-09-04 19:06:27 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-10 12:22:52 +01:00
|
|
|
/// Called when the runtime exits a call frame.
|
|
|
|
fn observe_exit_call_frame(&mut self, frame_at: usize, stack: &[Value]) {
|
2023-03-03 19:23:12 +01:00
|
|
|
let _ = write!(&mut self.writer, "=== exiting frame {} ===\t ", frame_at);
|
|
|
|
self.write_stack(stack);
|
2022-09-04 19:06:27 +02:00
|
|
|
}
|
|
|
|
|
2023-03-10 12:22:52 +01:00
|
|
|
fn observe_suspend_call_frame(&mut self, frame_at: usize, stack: &[Value]) {
|
2023-03-03 19:23:12 +01:00
|
|
|
let _ = write!(&mut self.writer, "=== suspending frame {} ===\t", frame_at);
|
2023-03-10 12:22:52 +01:00
|
|
|
|
2023-03-03 19:23:12 +01:00
|
|
|
self.write_stack(stack);
|
2023-03-10 12:22:52 +01:00
|
|
|
}
|
|
|
|
|
2023-03-03 22:52:37 +01:00
|
|
|
fn observe_enter_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
|
2023-03-10 12:22:52 +01:00
|
|
|
let _ = write!(
|
|
|
|
&mut self.writer,
|
2023-03-03 22:52:37 +01:00
|
|
|
"=== entering generator frame '{}' [{}] ===\t",
|
|
|
|
name, frame_at,
|
2023-03-10 12:22:52 +01:00
|
|
|
);
|
|
|
|
|
2023-03-03 19:23:12 +01:00
|
|
|
self.write_stack(stack);
|
2023-03-10 12:22:52 +01:00
|
|
|
}
|
|
|
|
|
2023-03-03 22:52:37 +01:00
|
|
|
fn observe_exit_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
|
|
|
|
let _ = write!(
|
|
|
|
&mut self.writer,
|
|
|
|
"=== exiting generator '{}' [{}] ===\t",
|
|
|
|
name, frame_at
|
|
|
|
);
|
2023-03-10 12:22:52 +01:00
|
|
|
|
2023-03-03 19:23:12 +01:00
|
|
|
self.write_stack(stack);
|
2023-03-10 12:22:52 +01:00
|
|
|
}
|
|
|
|
|
2023-03-03 22:52:37 +01:00
|
|
|
fn observe_suspend_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
|
2023-03-10 12:22:52 +01:00
|
|
|
let _ = write!(
|
|
|
|
&mut self.writer,
|
2023-03-03 22:52:37 +01:00
|
|
|
"=== suspending generator '{}' [{}] ===\t",
|
|
|
|
name, frame_at
|
2023-03-10 12:22:52 +01:00
|
|
|
);
|
|
|
|
|
2023-03-03 19:23:12 +01:00
|
|
|
self.write_stack(stack);
|
2023-03-10 12:22:52 +01:00
|
|
|
}
|
|
|
|
|
2023-03-13 22:44:48 +01:00
|
|
|
fn observe_generator_request(&mut self, name: &str, msg: &VMRequest) {
|
2023-03-03 22:52:37 +01:00
|
|
|
let _ = writeln!(
|
|
|
|
&mut self.writer,
|
|
|
|
"=== generator '{}' requested {} ===",
|
|
|
|
name, msg
|
|
|
|
);
|
2023-03-10 12:22:52 +01:00
|
|
|
}
|
|
|
|
|
2022-09-04 19:06:27 +02:00
|
|
|
fn observe_enter_builtin(&mut self, name: &'static str) {
|
|
|
|
let _ = writeln!(&mut self.writer, "=== entering builtin {} ===", name);
|
|
|
|
}
|
|
|
|
|
2022-10-11 01:24:37 +02:00
|
|
|
fn observe_exit_builtin(&mut self, name: &'static str, stack: &[Value]) {
|
2023-03-03 19:23:12 +01:00
|
|
|
let _ = write!(&mut self.writer, "=== exiting builtin {} ===\t", name);
|
|
|
|
self.write_stack(stack);
|
2022-09-04 19:06:27 +02:00
|
|
|
}
|
|
|
|
|
2022-09-04 22:16:59 +02:00
|
|
|
fn observe_tail_call(&mut self, frame_at: usize, lambda: &Rc<Lambda>) {
|
|
|
|
let _ = writeln!(
|
|
|
|
&mut self.writer,
|
|
|
|
"=== tail-calling {:p} in frame[{}] ===",
|
2023-03-10 12:22:52 +01:00
|
|
|
*lambda, frame_at
|
2022-09-04 22:16:59 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-13 14:58:55 +02:00
|
|
|
fn observe_execute_op(&mut self, ip: CodeIdx, op: &OpCode, stack: &[Value]) {
|
2023-03-03 19:23:12 +01:00
|
|
|
let _ = write!(&mut self.writer, "{:04} {:?}\t", ip.0, op);
|
|
|
|
self.write_stack(stack);
|
2022-09-04 19:06:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<W: Write> Drop for TracingObserver<W> {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
let _ = self.writer.flush();
|
|
|
|
}
|
|
|
|
}
|