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) ===",
105 kind,
106 *lambda,
107 lambda.chunk.code.len()
108 );
109 }
110
111 fn disassemble_chunk(&mut self, chunk: &Chunk) {
112 let width = format!("{:#x}", chunk.code.len() - 1).len();
114
115 let mut idx = 0;
116 while idx < chunk.code.len() {
117 let size = chunk
118 .disassemble_op(&mut self.writer, &self.source, width, CodeIdx(idx))
119 .expect("writing debug output should work");
120 idx += size;
121 }
122 }
123}
124
125impl<W: Write> CompilerObserver for DisassemblingObserver<W> {
126 fn observe_compiled_toplevel(&mut self, lambda: &Rc<Lambda>) {
127 self.lambda_header("toplevel", lambda);
128 self.disassemble_chunk(&lambda.chunk);
129 let _ = self.writer.flush();
130 }
131
132 fn observe_compiled_lambda(&mut self, lambda: &Rc<Lambda>) {
133 self.lambda_header("lambda", lambda);
134 self.disassemble_chunk(&lambda.chunk);
135 let _ = self.writer.flush();
136 }
137
138 fn observe_compiled_thunk(&mut self, lambda: &Rc<Lambda>) {
139 self.lambda_header("thunk", lambda);
140 self.disassemble_chunk(&lambda.chunk);
141 let _ = self.writer.flush();
142 }
143}
144
145pub struct TracingObserver<W: Write> {
148 last_event: Option<Instant>,
150 writer: TabWriter<W>,
151}
152
153impl<W: Write> TracingObserver<W> {
154 pub fn new(writer: W) -> Self {
155 Self {
156 last_event: None,
157 writer: TabWriter::new(writer),
158 }
159 }
160
161 pub fn enable_timing(&mut self) {
163 self.last_event = Some(Instant::now());
164 }
165
166 fn maybe_write_time(&mut self) {
167 if let Some(last_event) = &mut self.last_event {
168 let _ = write!(&mut self.writer, "+{}ns\t", last_event.elapsed().as_nanos());
169 *last_event = Instant::now();
170 }
171 }
172
173 fn write_value(&mut self, val: &Value) {
174 let _ = match val {
175 Value::List(l) => write!(&mut self.writer, "list[{}] ", l.len()),
178 Value::Attrs(a) => write!(&mut self.writer, "attrs[{}] ", a.len()),
179 Value::Thunk(t) if t.is_evaluated() => {
180 self.write_value(&t.value());
181 Ok(())
182 }
183
184 _ => write!(&mut self.writer, "{val} "),
186 };
187 }
188
189 fn write_stack(&mut self, stack: &[Value]) {
190 let _ = write!(&mut self.writer, "[ ");
191
192 for (i, val) in stack.iter().rev().enumerate() {
195 if i == 6 {
196 let _ = write!(&mut self.writer, "...");
197 break;
198 }
199
200 self.write_value(val);
201 }
202
203 let _ = writeln!(&mut self.writer, "]");
204 }
205}
206
207impl<W: Write> RuntimeObserver for TracingObserver<W> {
208 fn observe_enter_call_frame(
209 &mut self,
210 arg_count: usize,
211 lambda: &Rc<Lambda>,
212 call_depth: usize,
213 ) {
214 self.maybe_write_time();
215
216 let _ = write!(&mut self.writer, "=== entering ");
217
218 let _ = if arg_count == 0 {
219 write!(&mut self.writer, "thunk ")
220 } else {
221 write!(&mut self.writer, "closure ")
222 };
223
224 if let Some(name) = &lambda.name {
225 let _ = write!(&mut self.writer, "'{name}' ");
226 }
227
228 let _ = writeln!(
229 &mut self.writer,
230 "in frame[{}] @ {:p} ===",
231 call_depth, *lambda
232 );
233 }
234
235 fn observe_exit_call_frame(&mut self, frame_at: usize, stack: &[Value]) {
237 self.maybe_write_time();
238 let _ = write!(&mut self.writer, "=== exiting frame {frame_at} ===\t ");
239 self.write_stack(stack);
240 }
241
242 fn observe_suspend_call_frame(&mut self, frame_at: usize, stack: &[Value]) {
243 self.maybe_write_time();
244 let _ = write!(&mut self.writer, "=== suspending frame {frame_at} ===\t");
245
246 self.write_stack(stack);
247 }
248
249 fn observe_enter_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
250 self.maybe_write_time();
251 let _ = write!(
252 &mut self.writer,
253 "=== entering generator frame '{name}' [{frame_at}] ===\t",
254 );
255
256 self.write_stack(stack);
257 }
258
259 fn observe_exit_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
260 self.maybe_write_time();
261 let _ = write!(
262 &mut self.writer,
263 "=== exiting generator '{name}' [{frame_at}] ===\t"
264 );
265
266 self.write_stack(stack);
267 }
268
269 fn observe_suspend_generator(&mut self, frame_at: usize, name: &str, stack: &[Value]) {
270 self.maybe_write_time();
271 let _ = write!(
272 &mut self.writer,
273 "=== suspending generator '{name}' [{frame_at}] ===\t"
274 );
275
276 self.write_stack(stack);
277 }
278
279 fn observe_generator_request(&mut self, name: &str, msg: &VMRequest) {
280 self.maybe_write_time();
281 let _ = writeln!(
282 &mut self.writer,
283 "=== generator '{name}' requested {msg} ==="
284 );
285 }
286
287 fn observe_enter_builtin(&mut self, name: &'static str) {
288 self.maybe_write_time();
289 let _ = writeln!(&mut self.writer, "=== entering builtin {name} ===");
290 }
291
292 fn observe_exit_builtin(&mut self, name: &'static str, stack: &[Value]) {
293 self.maybe_write_time();
294 let _ = write!(&mut self.writer, "=== exiting builtin {name} ===\t");
295 self.write_stack(stack);
296 }
297
298 fn observe_tail_call(&mut self, frame_at: usize, lambda: &Rc<Lambda>) {
299 self.maybe_write_time();
300 let _ = writeln!(
301 &mut self.writer,
302 "=== tail-calling {:p} in frame[{}] ===",
303 *lambda, frame_at
304 );
305 }
306
307 fn observe_execute_op(&mut self, ip: CodeIdx, op: &Op, stack: &[Value]) {
308 self.maybe_write_time();
309 let _ = write!(&mut self.writer, "{:04} {:?}\t", ip.0, op);
310 self.write_stack(stack);
311 }
312}
313
314impl<W: Write> Drop for TracingObserver<W> {
315 fn drop(&mut self) {
316 let _ = self.writer.flush();
317 }
318}