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_bytecode_frame(
45 &mut self,
46 _arg_count: usize,
47 _: &Rc<Lambda>,
48 _call_depth: usize,
49 ) {
50 }
51
52 fn observe_exit_bytecode_frame(&mut self, _frame_at: usize, _stack: &[Value]) {}
54
55 fn observe_suspend_bytecode_frame(&mut self, _frame_at: usize, _stack: &[Value]) {}
57
58 fn observe_enter_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
60
61 fn observe_exit_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
63
64 fn observe_suspend_generator(&mut self, _frame_at: usize, _name: &str, _stack: &[Value]) {}
66
67 fn observe_generator_request(&mut self, _name: &str, _msg: &VMRequest) {}
69
70 fn observe_tail_call(&mut self, _frame_at: usize, _: &Rc<Lambda>) {}
73
74 fn observe_enter_builtin(&mut self, _name: &'static str) {}
76
77 fn observe_exit_builtin(&mut self, _name: &'static str, _stack: &[Value]) {}
79
80 fn observe_execute_op(&mut self, _ip: CodeIdx, _: &Op, _: &[Value]) {}
83}
84
85#[derive(Default)]
86pub struct NoOpObserver {}
87
88impl CompilerObserver for NoOpObserver {}
89impl RuntimeObserver for NoOpObserver {}
90
91#[derive(Default)]
97pub struct OptionalCompilerObserver<'o>(pub Option<&'o mut dyn CompilerObserver>);
98impl<'o> CompilerObserver for OptionalCompilerObserver<'o> {
99 fn observe_compiled_toplevel(&mut self, lambda: &Rc<Lambda>) {
100 if let Some(ref mut obs) = self.0 {
101 obs.observe_compiled_toplevel(lambda);
102 }
103 }
104
105 fn observe_compiled_lambda(&mut self, lambda: &Rc<Lambda>) {
106 if let Some(ref mut obs) = self.0 {
107 obs.observe_compiled_lambda(lambda);
108 }
109 }
110
111 fn observe_compiled_thunk(&mut self, lambda: &Rc<Lambda>) {
112 if let Some(ref mut obs) = self.0 {
113 obs.observe_compiled_thunk(lambda)
114 }
115 }
116}
117
118impl<'o> From<&'o mut dyn CompilerObserver> for OptionalCompilerObserver<'o> {
119 fn from(val: &'o mut dyn CompilerObserver) -> Self {
120 OptionalCompilerObserver(Some(val))
121 }
122}
123
124impl<'o> From<Option<&'o mut dyn CompilerObserver>> for OptionalCompilerObserver<'o> {
125 fn from(val: Option<&'o mut dyn CompilerObserver>) -> Self {
126 Self(val)
127 }
128}
129
130pub struct OptionalRuntimeObserver<'o>(pub Option<&'o mut dyn RuntimeObserver>);
136
137impl<'o> From<&'o mut dyn RuntimeObserver> for OptionalRuntimeObserver<'o> {
138 fn from(val: &'o mut dyn RuntimeObserver) -> Self {
139 OptionalRuntimeObserver(Some(val))
140 }
141}
142
143impl<'o> RuntimeObserver for OptionalRuntimeObserver<'o> {
144 #[inline(always)]
145 fn observe_enter_bytecode_frame(
146 &mut self,
147 arg_count: usize,
148 lambda: &Rc<Lambda>,
149 call_depth: usize,
150 ) {
151 if let Some(ref mut obs) = self.0 {
152 obs.observe_enter_bytecode_frame(arg_count, lambda, call_depth);
153 }
154 }
155
156 #[inline(always)]
157 fn observe_exit_bytecode_frame(&mut self, frame_at: usize, stack: &[Value]) {
158 if let Some(ref mut obs) = self.0 {
159 obs.observe_exit_bytecode_frame(frame_at, stack);
160 }
161 }
162
163 #[inline(always)]
164 fn observe_suspend_bytecode_frame(&mut self, frame_at: usize, stack: &[Value]) {
165 if let Some(ref mut obs) = self.0 {
166 obs.observe_suspend_bytecode_frame(frame_at, stack);
167 }
168 }
169
170 #[inline(always)]
171 fn observe_enter_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
172 if let Some(ref mut obs) = self.0 {
173 obs.observe_enter_generator(frame_at, name, stack);
174 }
175 }
176
177 #[inline(always)]
178 fn observe_exit_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
179 if let Some(ref mut obs) = self.0 {
180 obs.observe_exit_generator(frame_at, name, stack);
181 }
182 }
183
184 #[inline(always)]
185 fn observe_suspend_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
186 if let Some(ref mut obs) = self.0 {
187 obs.observe_suspend_generator(frame_at, name, stack);
188 }
189 }
190
191 #[inline(always)]
192 fn observe_generator_request(&mut self, name: &str, msg: &VMRequest) {
193 if let Some(ref mut obs) = self.0 {
194 obs.observe_generator_request(name, msg);
195 }
196 }
197
198 #[inline(always)]
199 fn observe_tail_call(&mut self, frame_at: usize, lambda: &Rc<Lambda>) {
200 if let Some(ref mut obs) = self.0 {
201 obs.observe_tail_call(frame_at, lambda);
202 }
203 }
204
205 #[inline(always)]
206 fn observe_enter_builtin(&mut self, name: &'static str) {
207 if let Some(ref mut obs) = self.0 {
208 obs.observe_enter_builtin(name);
209 }
210 }
211
212 #[inline(always)]
213 fn observe_exit_builtin(&mut self, name: &'static str, stack: &[Value]) {
214 if let Some(ref mut obs) = self.0 {
215 obs.observe_exit_builtin(name, stack);
216 }
217 }
218
219 #[inline(always)]
220 fn observe_execute_op(&mut self, ip: CodeIdx, op: &Op, stack: &[Value]) {
221 if let Some(ref mut obs) = self.0 {
222 obs.observe_execute_op(ip, op, stack);
223 }
224 }
225}
226
227pub struct DisassemblingObserver<W: Write> {
231 source: SourceCode,
232 writer: TabWriter<W>,
233}
234
235impl<W: Write> DisassemblingObserver<W> {
236 pub fn new(source: SourceCode, writer: W) -> Self {
237 Self {
238 source,
239 writer: TabWriter::new(writer),
240 }
241 }
242
243 fn lambda_header(&mut self, kind: &str, lambda: &Rc<Lambda>) {
244 let _ = writeln!(
245 &mut self.writer,
246 "=== compiled {} @ {:p} ({} ops, {} length) ===",
247 kind,
248 *lambda,
249 lambda.chunk.op_count(),
250 lambda.chunk.code.len(),
251 );
252 }
253
254 fn disassemble_chunk(&mut self, chunk: &Chunk) {
255 let width = format!("{:#x}", chunk.code.len() - 1).len();
257
258 let mut idx = 0;
259 while idx < chunk.code.len() {
260 let size = chunk
261 .disassemble_op(&mut self.writer, &self.source, width, CodeIdx(idx))
262 .expect("writing debug output should work");
263 idx += size;
264 }
265 }
266}
267
268impl<W: Write> CompilerObserver for DisassemblingObserver<W> {
269 fn observe_compiled_toplevel(&mut self, lambda: &Rc<Lambda>) {
270 self.lambda_header("toplevel", lambda);
271 self.disassemble_chunk(&lambda.chunk);
272 let _ = self.writer.flush();
273 }
274
275 fn observe_compiled_lambda(&mut self, lambda: &Rc<Lambda>) {
276 self.lambda_header("lambda", lambda);
277 self.disassemble_chunk(&lambda.chunk);
278 let _ = self.writer.flush();
279 }
280
281 fn observe_compiled_thunk(&mut self, lambda: &Rc<Lambda>) {
282 self.lambda_header("thunk", lambda);
283 self.disassemble_chunk(&lambda.chunk);
284 let _ = self.writer.flush();
285 }
286}
287
288pub struct TracingObserver<W: Write> {
291 last_event: Option<Instant>,
293 writer: TabWriter<W>,
294}
295
296impl<W: Write> TracingObserver<W> {
297 pub fn new(writer: W) -> Self {
298 Self {
299 last_event: None,
300 writer: TabWriter::new(writer),
301 }
302 }
303
304 pub fn enable_timing(&mut self) {
306 self.last_event = Some(Instant::now());
307 }
308
309 fn maybe_write_time(&mut self) {
310 if let Some(last_event) = &mut self.last_event {
311 let _ = write!(&mut self.writer, "+{}ns\t", last_event.elapsed().as_nanos());
312 *last_event = Instant::now();
313 }
314 }
315
316 fn write_value(&mut self, val: &Value) {
317 let _ = match val {
318 Value::List(l) => write!(&mut self.writer, "list[{}] ", l.len()),
321 Value::Attrs(a) => write!(&mut self.writer, "attrs[{}] ", a.len()),
322 Value::Thunk(t) if t.is_evaluated() => {
323 self.write_value(&t.value());
324 Ok(())
325 }
326
327 _ => write!(&mut self.writer, "{val} "),
329 };
330 }
331
332 fn write_stack(&mut self, stack: &[Value]) {
333 let _ = write!(&mut self.writer, "[ ");
334
335 for (i, val) in stack.iter().rev().enumerate() {
338 if i == 6 {
339 let _ = write!(&mut self.writer, "...");
340 break;
341 }
342
343 self.write_value(val);
344 }
345
346 let _ = writeln!(&mut self.writer, "]");
347 }
348}
349
350impl<W: Write> RuntimeObserver for TracingObserver<W> {
351 fn observe_enter_bytecode_frame(
352 &mut self,
353 arg_count: usize,
354 lambda: &Rc<Lambda>,
355 call_depth: usize,
356 ) {
357 self.maybe_write_time();
358
359 let _ = write!(&mut self.writer, "=== entering ");
360
361 let _ = if arg_count == 0 {
362 write!(&mut self.writer, "thunk ")
363 } else {
364 write!(&mut self.writer, "closure ")
365 };
366
367 if let Some(name) = &lambda.name {
368 let _ = write!(&mut self.writer, "'{name}' ");
369 }
370
371 let _ = writeln!(
372 &mut self.writer,
373 "in frame[{}] @ {:p} ===",
374 call_depth, *lambda
375 );
376 }
377
378 fn observe_exit_bytecode_frame(&mut self, frame_at: usize, stack: &[Value]) {
380 self.maybe_write_time();
381 let _ = write!(&mut self.writer, "=== exiting frame {frame_at} ===\t ");
382 self.write_stack(stack);
383 }
384
385 fn observe_suspend_bytecode_frame(&mut self, frame_at: usize, stack: &[Value]) {
386 self.maybe_write_time();
387 let _ = write!(&mut self.writer, "=== suspending frame {frame_at} ===\t");
388
389 self.write_stack(stack);
390 }
391
392 fn observe_enter_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
393 self.maybe_write_time();
394 let _ = write!(
395 &mut self.writer,
396 "=== entering generator frame '{name}' [{frame_at}] ===\t",
397 );
398
399 self.write_stack(stack);
400 }
401
402 fn observe_exit_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
403 self.maybe_write_time();
404 let _ = write!(
405 &mut self.writer,
406 "=== exiting generator '{name}' [{frame_at}] ===\t"
407 );
408
409 self.write_stack(stack);
410 }
411
412 fn observe_suspend_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
413 self.maybe_write_time();
414 let _ = write!(
415 &mut self.writer,
416 "=== suspending generator '{name}' [{frame_at}] ===\t"
417 );
418
419 self.write_stack(stack);
420 }
421
422 fn observe_generator_request(&mut self, name: &str, msg: &VMRequest) {
423 self.maybe_write_time();
424 let _ = writeln!(
425 &mut self.writer,
426 "=== generator '{name}' requested {msg} ==="
427 );
428 }
429
430 fn observe_enter_builtin(&mut self, name: &'static str) {
431 self.maybe_write_time();
432 let _ = writeln!(&mut self.writer, "=== entering builtin {name} ===");
433 }
434
435 fn observe_exit_builtin(&mut self, name: &'static str, stack: &[Value]) {
436 self.maybe_write_time();
437 let _ = write!(&mut self.writer, "=== exiting builtin {name} ===\t");
438 self.write_stack(stack);
439 }
440
441 fn observe_tail_call(&mut self, frame_at: usize, lambda: &Rc<Lambda>) {
442 self.maybe_write_time();
443 let _ = writeln!(
444 &mut self.writer,
445 "=== tail-calling {:p} in frame[{}] ===",
446 *lambda, frame_at
447 );
448 }
449
450 fn observe_execute_op(&mut self, ip: CodeIdx, op: &Op, stack: &[Value]) {
451 self.maybe_write_time();
452 let _ = write!(&mut self.writer, "{:04} {:?}\t", ip.0, op);
453 self.write_stack(stack);
454 }
455}
456
457impl<W: Write> Drop for TracingObserver<W> {
458 fn drop(&mut self) {
459 let _ = self.writer.flush();
460 }
461}