1use std::io::Write;
10use std::rc::Rc;
11use std::time::Instant;
12use tabwriter::TabWriter;
13
14use crate::SourceCode;
15use crate::Value;
16use crate::chunk::Chunk;
17use crate::generators::VMRequest;
18use crate::opcode::{CodeIdx, Op};
19use crate::value::Lambda;
20
21pub trait CompilerObserver {
24 fn observe_compiled_toplevel(&mut self, _: &Rc<Lambda>) {}
27
28 fn observe_compiled_lambda(&mut self, _: &Rc<Lambda>) {}
35
36 fn observe_compiled_thunk(&mut self, _: &Rc<Lambda>) {}
38}
39
40pub trait RuntimeObserver {
43 fn observe_enter_call_frame(&mut self, _arg_count: usize, _: &Rc<Lambda>, _call_depth: usize) {}
45
46 fn observe_exit_call_frame(&mut self, _frame_at: usize, _stack: &[Value]) {}
48
49 fn observe_suspend_call_frame(&mut self, _frame_at: usize, _stack: &[Value]) {}
51
52 fn observe_enter_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
54
55 fn observe_exit_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
57
58 fn observe_suspend_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
60
61 fn observe_generator_request(&mut self, _name: &str, _msg: &VMRequest) {}
63
64 fn observe_tail_call(&mut self, _frame_at: usize, _: &Rc<Lambda>) {}
67
68 fn observe_enter_builtin(&mut self, _name: &'static str) {}
70
71 fn observe_exit_builtin(&mut self, _name: &'static str, _stack: &[Value]) {}
73
74 fn observe_execute_op(&mut self, _ip: CodeIdx, _: &Op, _: &[Value]) {}
77}
78
79#[derive(Default)]
80pub struct NoOpObserver {}
81
82impl CompilerObserver for NoOpObserver {}
83impl RuntimeObserver for NoOpObserver {}
84
85pub struct DisassemblingObserver<W: Write> {
89 source: SourceCode,
90 writer: TabWriter<W>,
91}
92
93impl<W: Write> DisassemblingObserver<W> {
94 pub fn new(source: SourceCode, writer: W) -> Self {
95 Self {
96 source,
97 writer: TabWriter::new(writer),
98 }
99 }
100
101 fn lambda_header(&mut self, kind: &str, lambda: &Rc<Lambda>) {
102 let _ = writeln!(
103 &mut self.writer,
104 "=== compiled {} @ {:p} ({} ops, {} length) ===",
105 kind,
106 *lambda,
107 lambda.chunk.op_count(),
108 lambda.chunk.code.len(),
109 );
110 }
111
112 fn disassemble_chunk(&mut self, chunk: &Chunk) {
113 let width = format!("{:#x}", chunk.code.len() - 1).len();
115
116 let mut idx = 0;
117 while idx < chunk.code.len() {
118 let size = chunk
119 .disassemble_op(&mut self.writer, &self.source, width, CodeIdx(idx))
120 .expect("writing debug output should work");
121 idx += size;
122 }
123 }
124}
125
126impl<W: Write> CompilerObserver for DisassemblingObserver<W> {
127 fn observe_compiled_toplevel(&mut self, lambda: &Rc<Lambda>) {
128 self.lambda_header("toplevel", lambda);
129 self.disassemble_chunk(&lambda.chunk);
130 let _ = self.writer.flush();
131 }
132
133 fn observe_compiled_lambda(&mut self, lambda: &Rc<Lambda>) {
134 self.lambda_header("lambda", lambda);
135 self.disassemble_chunk(&lambda.chunk);
136 let _ = self.writer.flush();
137 }
138
139 fn observe_compiled_thunk(&mut self, lambda: &Rc<Lambda>) {
140 self.lambda_header("thunk", lambda);
141 self.disassemble_chunk(&lambda.chunk);
142 let _ = self.writer.flush();
143 }
144}
145
146pub struct TracingObserver<W: Write> {
149 last_event: Option<Instant>,
151 writer: TabWriter<W>,
152}
153
154impl<W: Write> TracingObserver<W> {
155 pub fn new(writer: W) -> Self {
156 Self {
157 last_event: None,
158 writer: TabWriter::new(writer),
159 }
160 }
161
162 pub fn enable_timing(&mut self) {
164 self.last_event = Some(Instant::now());
165 }
166
167 fn maybe_write_time(&mut self) {
168 if let Some(last_event) = &mut self.last_event {
169 let _ = write!(&mut self.writer, "+{}ns\t", last_event.elapsed().as_nanos());
170 *last_event = Instant::now();
171 }
172 }
173
174 fn write_value(&mut self, val: &Value) {
175 let _ = match val {
176 Value::List(l) => write!(&mut self.writer, "list[{}] ", l.len()),
179 Value::Attrs(a) => write!(&mut self.writer, "attrs[{}] ", a.len()),
180 Value::Thunk(t) if t.is_evaluated() => {
181 self.write_value(&t.value());
182 Ok(())
183 }
184
185 _ => write!(&mut self.writer, "{val} "),
187 };
188 }
189
190 fn write_stack(&mut self, stack: &[Value]) {
191 let _ = write!(&mut self.writer, "[ ");
192
193 for (i, val) in stack.iter().rev().enumerate() {
196 if i == 6 {
197 let _ = write!(&mut self.writer, "...");
198 break;
199 }
200
201 self.write_value(val);
202 }
203
204 let _ = writeln!(&mut self.writer, "]");
205 }
206}
207
208impl<W: Write> RuntimeObserver for TracingObserver<W> {
209 fn observe_enter_call_frame(
210 &mut self,
211 arg_count: usize,
212 lambda: &Rc<Lambda>,
213 call_depth: usize,
214 ) {
215 self.maybe_write_time();
216
217 let _ = write!(&mut self.writer, "=== entering ");
218
219 let _ = if arg_count == 0 {
220 write!(&mut self.writer, "thunk ")
221 } else {
222 write!(&mut self.writer, "closure ")
223 };
224
225 if let Some(name) = &lambda.name {
226 let _ = write!(&mut self.writer, "'{name}' ");
227 }
228
229 let _ = writeln!(
230 &mut self.writer,
231 "in frame[{}] @ {:p} ===",
232 call_depth, *lambda
233 );
234 }
235
236 fn observe_exit_call_frame(&mut self, frame_at: usize, stack: &[Value]) {
238 self.maybe_write_time();
239 let _ = write!(&mut self.writer, "=== exiting frame {frame_at} ===\t ");
240 self.write_stack(stack);
241 }
242
243 fn observe_suspend_call_frame(&mut self, frame_at: usize, stack: &[Value]) {
244 self.maybe_write_time();
245 let _ = write!(&mut self.writer, "=== suspending frame {frame_at} ===\t");
246
247 self.write_stack(stack);
248 }
249
250 fn observe_enter_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
251 self.maybe_write_time();
252 let _ = write!(
253 &mut self.writer,
254 "=== entering generator frame '{name}' [{frame_at}] ===\t",
255 );
256
257 self.write_stack(stack);
258 }
259
260 fn observe_exit_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
261 self.maybe_write_time();
262 let _ = write!(
263 &mut self.writer,
264 "=== exiting generator '{name}' [{frame_at}] ===\t"
265 );
266
267 self.write_stack(stack);
268 }
269
270 fn observe_suspend_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
271 self.maybe_write_time();
272 let _ = write!(
273 &mut self.writer,
274 "=== suspending generator '{name}' [{frame_at}] ===\t"
275 );
276
277 self.write_stack(stack);
278 }
279
280 fn observe_generator_request(&mut self, name: &str, msg: &VMRequest) {
281 self.maybe_write_time();
282 let _ = writeln!(
283 &mut self.writer,
284 "=== generator '{name}' requested {msg} ==="
285 );
286 }
287
288 fn observe_enter_builtin(&mut self, name: &'static str) {
289 self.maybe_write_time();
290 let _ = writeln!(&mut self.writer, "=== entering builtin {name} ===");
291 }
292
293 fn observe_exit_builtin(&mut self, name: &'static str, stack: &[Value]) {
294 self.maybe_write_time();
295 let _ = write!(&mut self.writer, "=== exiting builtin {name} ===\t");
296 self.write_stack(stack);
297 }
298
299 fn observe_tail_call(&mut self, frame_at: usize, lambda: &Rc<Lambda>) {
300 self.maybe_write_time();
301 let _ = writeln!(
302 &mut self.writer,
303 "=== tail-calling {:p} in frame[{}] ===",
304 *lambda, frame_at
305 );
306 }
307
308 fn observe_execute_op(&mut self, ip: CodeIdx, op: &Op, stack: &[Value]) {
309 self.maybe_write_time();
310 let _ = write!(&mut self.writer, "{:04} {:?}\t", ip.0, op);
311 self.write_stack(stack);
312 }
313}
314
315impl<W: Write> Drop for TracingObserver<W> {
316 fn drop(&mut self) {
317 let _ = self.writer.flush();
318 }
319}