Improve correctness and consistency of 'eval-when' usage.
[bpt/guile.git] / module / system / vm / traps.scm
1 ;;; Traps: stepping, breakpoints, and such.
2
3 ;; Copyright (C) 2010 Free Software Foundation, Inc.
4
5 ;;; This library is free software; you can redistribute it and/or
6 ;;; modify it under the terms of the GNU Lesser General Public
7 ;;; License as published by the Free Software Foundation; either
8 ;;; version 3 of the License, or (at your option) any later version.
9 ;;;
10 ;;; This library is distributed in the hope that it will be useful,
11 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;;; Lesser General Public License for more details.
14 ;;;
15 ;;; You should have received a copy of the GNU Lesser General Public
16 ;;; License along with this library; if not, write to the Free Software
17 ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19 ;;; Commentary:
20 ;;;
21 ;;; Guile's debugging capabilities come from the hooks that its VM
22 ;;; provides. For example, there is a hook that is fired when a function
23 ;;; is called, and even a hook that gets fired at every retired
24 ;;; instruction.
25 ;;;
26 ;;; But as the firing of these hooks is interleaved with the program
27 ;;; execution, if we want to debug a program, we have to write an
28 ;;; imperative program that mutates the state of these hooks, and to
29 ;;; dispatch the hooks to a more semantic context.
30 ;;;
31 ;;; For example if we have placed a breakpoint at foo.scm:38, and
32 ;;; determined that that location maps to the 18th instruction in
33 ;;; procedure `bar', then we will need per-instruction hooks within
34 ;;; `bar' -- but when running other procedures, we can have the
35 ;;; per-instruction hooks off.
36 ;;;
37 ;;; Our approach is to define "traps". The behavior of a trap is
38 ;;; specified when the trap is created. After creation, traps expose a
39 ;;; limited, uniform interface: they are either on or off.
40 ;;;
41 ;;; To take our foo.scm:38 example again, we can define a trap that
42 ;;; calls a function when control transfers to that source line --
43 ;;; trap-at-source-location below. Calling the trap-at-source-location
44 ;;; function adds to the VM hooks in such at way that it can do its job.
45 ;;; The result of calling the function is a "disable-hook" closure that,
46 ;;; when called, will turn off that trap.
47 ;;;
48 ;;; The result of calling the "disable-hook" closure, in turn, is an
49 ;;; "enable-hook" closure, which when called turns the hook back on, and
50 ;;; returns a "disable-hook" closure.
51 ;;;
52 ;;; It's a little confusing. The summary is, call these functions to add
53 ;;; a trap; and call their return value to disable the trap.
54 ;;;
55 ;;; Code:
56
57 (define-module (system vm traps)
58 #:use-module (system base pmatch)
59 #:use-module (system vm vm)
60 #:use-module (system vm frame)
61 #:use-module (system vm program)
62 #:use-module (system vm objcode)
63 #:use-module (system vm instruction)
64 #:use-module (system xref)
65 #:use-module (rnrs bytevectors)
66 #:export (trap-at-procedure-call
67 trap-in-procedure
68 trap-instructions-in-procedure
69 trap-at-procedure-ip-in-range
70 trap-at-source-location
71 trap-frame-finish
72 trap-in-dynamic-extent
73 trap-calls-in-dynamic-extent
74 trap-instructions-in-dynamic-extent
75 trap-calls-to-procedure
76 trap-matching-instructions))
77
78 (define-syntax arg-check
79 (syntax-rules ()
80 ((_ arg predicate? message)
81 (if (not (predicate? arg))
82 (error "bad argument ~a: ~a" 'arg message)))
83 ((_ arg predicate?)
84 (if (not (predicate? arg))
85 (error "bad argument ~a: expected ~a" 'arg 'predicate?)))))
86
87 (define (new-disabled-trap vm enable disable)
88 (let ((enabled? #f))
89 (define-syntax disabled?
90 (identifier-syntax
91 (disabled? (not enabled?))
92 ((set! disabled? val) (set! enabled? (not val)))))
93
94 (define* (enable-trap #:optional frame)
95 (if enabled? (error "trap already enabled"))
96 (enable frame)
97 (set! enabled? #t)
98 disable-trap)
99
100 (define* (disable-trap #:optional frame)
101 (if disabled? (error "trap already disabled"))
102 (disable frame)
103 (set! disabled? #t)
104 enable-trap)
105
106 enable-trap))
107
108 (define (new-enabled-trap vm frame enable disable)
109 ((new-disabled-trap vm enable disable) frame))
110
111 (define (frame-matcher proc match-objcode?)
112 (let ((proc (if (struct? proc)
113 (procedure proc)
114 proc)))
115 (if match-objcode?
116 (lambda (frame)
117 (let ((frame-proc (frame-procedure frame)))
118 (or (eq? frame-proc proc)
119 (and (program? frame-proc)
120 (eq? (program-objcode frame-proc)
121 (program-objcode proc))))))
122 (lambda (frame)
123 (eq? (frame-procedure frame) proc)))))
124
125 ;; A basic trap, fires when a procedure is called.
126 ;;
127 (define* (trap-at-procedure-call proc handler #:key (vm (the-vm))
128 (closure? #f)
129 (our-frame? (frame-matcher proc closure?)))
130 (arg-check proc procedure?)
131 (arg-check handler procedure?)
132 (let ()
133 (define (apply-hook frame)
134 (if (our-frame? frame)
135 (handler frame)))
136
137 (new-enabled-trap
138 vm #f
139 (lambda (frame)
140 (add-hook! (vm-apply-hook vm) apply-hook))
141 (lambda (frame)
142 (remove-hook! (vm-apply-hook vm) apply-hook)))))
143
144 ;; A more complicated trap, traps when control enters a procedure.
145 ;;
146 ;; Control can enter a procedure via:
147 ;; * A procedure call.
148 ;; * A return to a procedure's frame on the stack.
149 ;; * A continuation returning directly to an application of this
150 ;; procedure.
151 ;;
152 ;; Control can leave a procedure via:
153 ;; * A normal return from the procedure.
154 ;; * An application of another procedure.
155 ;; * An invocation of a continuation.
156 ;; * An abort.
157 ;;
158 (define* (trap-in-procedure proc enter-handler exit-handler
159 #:key current-frame (vm (the-vm))
160 (closure? #f)
161 (our-frame? (frame-matcher proc closure?)))
162 (arg-check proc procedure?)
163 (arg-check enter-handler procedure?)
164 (arg-check exit-handler procedure?)
165 (let ((in-proc? #f))
166 (define (enter-proc frame)
167 (if in-proc?
168 (warn "already in proc" frame)
169 (begin
170 (enter-handler frame)
171 (set! in-proc? #t))))
172
173 (define (exit-proc frame)
174 (if in-proc?
175 (begin
176 (exit-handler frame)
177 (set! in-proc? #f))
178 (warn "not in proc" frame)))
179
180 (define (apply-hook frame)
181 (if in-proc?
182 (exit-proc frame))
183 (if (our-frame? frame)
184 (enter-proc frame)))
185
186 (define (push-cont-hook frame)
187 (if in-proc?
188 (exit-proc frame)))
189
190 (define (pop-cont-hook frame)
191 (if in-proc?
192 (exit-proc frame))
193 (if (our-frame? (frame-previous frame))
194 (enter-proc (frame-previous frame))))
195
196 (define (abort-hook frame)
197 (if in-proc?
198 (exit-proc frame))
199 (if (our-frame? frame)
200 (enter-proc frame)))
201
202 (define (restore-hook frame)
203 (if in-proc?
204 (exit-proc frame))
205 (if (our-frame? frame)
206 (enter-proc frame)))
207
208 (new-enabled-trap
209 vm current-frame
210 (lambda (frame)
211 (add-hook! (vm-apply-hook vm) apply-hook)
212 (add-hook! (vm-push-continuation-hook vm) push-cont-hook)
213 (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
214 (add-hook! (vm-abort-continuation-hook vm) abort-hook)
215 (add-hook! (vm-restore-continuation-hook vm) restore-hook)
216 (if (and frame (our-frame? frame))
217 (enter-proc frame)))
218 (lambda (frame)
219 (if in-proc?
220 (exit-proc frame))
221 (remove-hook! (vm-apply-hook vm) apply-hook)
222 (remove-hook! (vm-push-continuation-hook vm) push-cont-hook)
223 (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
224 (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
225 (remove-hook! (vm-restore-continuation-hook vm) restore-hook)))))
226
227 ;; Building on trap-in-procedure, we have trap-instructions-in-procedure
228 ;;
229 (define* (trap-instructions-in-procedure proc next-handler exit-handler
230 #:key current-frame (vm (the-vm))
231 (closure? #f)
232 (our-frame?
233 (frame-matcher proc closure?)))
234 (arg-check proc procedure?)
235 (arg-check next-handler procedure?)
236 (arg-check exit-handler procedure?)
237 (let ()
238 (define (next-hook frame)
239 (if (our-frame? frame)
240 (next-handler frame)))
241
242 (define (enter frame)
243 (add-hook! (vm-next-hook vm) next-hook)
244 (if frame (next-hook frame)))
245
246 (define (exit frame)
247 (exit-handler frame)
248 (remove-hook! (vm-next-hook vm) next-hook))
249
250 (trap-in-procedure proc enter exit
251 #:current-frame current-frame #:vm vm
252 #:our-frame? our-frame?)))
253
254 (define (non-negative-integer? x)
255 (and (number? x) (integer? x) (exact? x) (not (negative? x))))
256
257 (define (positive-integer? x)
258 (and (number? x) (integer? x) (exact? x) (positive? x)))
259
260 (define (range? x)
261 (and (list? x)
262 (and-map (lambda (x)
263 (and (pair? x)
264 (non-negative-integer? (car x))
265 (non-negative-integer? (cdr x))))
266 x)))
267
268 (define (in-range? range i)
269 (or-map (lambda (bounds)
270 (and (<= (car bounds) i)
271 (< i (cdr bounds))))
272 range))
273
274 ;; Building on trap-instructions-in-procedure, we have
275 ;; trap-at-procedure-ip-in-range.
276 ;;
277 (define* (trap-at-procedure-ip-in-range proc range handler
278 #:key current-frame (vm (the-vm))
279 (closure? #f)
280 (our-frame?
281 (frame-matcher proc closure?)))
282 (arg-check proc procedure?)
283 (arg-check range range?)
284 (arg-check handler procedure?)
285 (let ((fp-stack '()))
286 (define (cull-frames! fp)
287 (let lp ((frames fp-stack))
288 (if (and (pair? frames) (< (car frames) fp))
289 (lp (cdr frames))
290 (set! fp-stack frames))))
291
292 (define (next-handler frame)
293 (let ((fp (frame-address frame))
294 (ip (frame-instruction-pointer frame)))
295 (cull-frames! fp)
296 (let ((now-in-range? (in-range? range ip))
297 (was-in-range? (and (pair? fp-stack) (= (car fp-stack) fp))))
298 (cond
299 (was-in-range?
300 (if (not now-in-range?)
301 (set! fp-stack (cdr fp-stack))))
302 (now-in-range?
303 (set! fp-stack (cons fp fp-stack))
304 (handler frame))))))
305
306 (define (exit-handler frame)
307 (if (and (pair? fp-stack)
308 (= (car fp-stack) (frame-address frame)))
309 (set! fp-stack (cdr fp-stack))))
310
311 (trap-instructions-in-procedure proc next-handler exit-handler
312 #:current-frame current-frame #:vm vm
313 #:our-frame? our-frame?)))
314
315 ;; FIXME: define this in objcode somehow. We are reffing the first
316 ;; uint32 in the objcode, which is the length of the program (without
317 ;; the meta).
318 (define (program-last-ip prog)
319 (bytevector-u32-native-ref (objcode->bytecode (program-objcode prog)) 0))
320
321 (define (program-sources-by-line proc file)
322 (let lp ((sources (program-sources-pre-retire proc))
323 (out '()))
324 (if (pair? sources)
325 (lp (cdr sources)
326 (pmatch (car sources)
327 ((,start-ip ,start-file ,start-line . ,start-col)
328 (if (equal? start-file file)
329 (cons (cons start-line
330 (if (pair? (cdr sources))
331 (pmatch (cadr sources)
332 ((,end-ip . _)
333 (cons start-ip end-ip))
334 (else (error "unexpected")))
335 (cons start-ip (program-last-ip proc))))
336 out)
337 out))
338 (else (error "unexpected"))))
339 (let ((alist '()))
340 (for-each
341 (lambda (pair)
342 (set! alist
343 (assv-set! alist (car pair)
344 (cons (cdr pair)
345 (or (assv-ref alist (car pair))
346 '())))))
347 out)
348 (sort! alist (lambda (x y) (< (car x) (car y))))
349 alist))))
350
351 (define (source->ip-range proc file line)
352 (or (or-map (lambda (line-and-ranges)
353 (cond
354 ((= (car line-and-ranges) line)
355 (cdr line-and-ranges))
356 ((> (car line-and-ranges) line)
357 (warn "no instructions found at" file ":" line
358 "; using line" (car line-and-ranges) "instead")
359 (cdr line-and-ranges))
360 (else #f)))
361 (program-sources-by-line proc file))
362 (begin
363 (warn "no instructions found for" file ":" line)
364 '())))
365
366 (define (source-closures-or-procedures file line)
367 (let ((closures (source-closures file line)))
368 (if (pair? closures)
369 (values closures #t)
370 (values (source-procedures file line) #f))))
371
372 ;; Building on trap-on-instructions-in-procedure, we have
373 ;; trap-at-source-location. The parameter `user-line' is one-indexed, as
374 ;; a user counts lines, instead of zero-indexed, as Guile counts lines.
375 ;;
376 (define* (trap-at-source-location file user-line handler
377 #:key current-frame (vm (the-vm)))
378 (arg-check file string?)
379 (arg-check user-line positive-integer?)
380 (arg-check handler procedure?)
381 (let ((traps #f))
382 (call-with-values
383 (lambda () (source-closures-or-procedures file (1- user-line)))
384 (lambda (procs closures?)
385 (new-enabled-trap
386 vm current-frame
387 (lambda (frame)
388 (set! traps
389 (map
390 (lambda (proc)
391 (let ((range (source->ip-range proc file (1- user-line))))
392 (trap-at-procedure-ip-in-range proc range handler
393 #:current-frame current-frame
394 #:vm vm
395 #:closure? closures?)))
396 procs))
397 (if (null? traps)
398 (error "No procedures found at ~a:~a." file user-line)))
399 (lambda (frame)
400 (for-each (lambda (trap) (trap frame)) traps)
401 (set! traps #f)))))))
402
403 \f
404
405 ;; On a different tack, now we're going to build up a set of traps that
406 ;; do useful things during the dynamic extent of a procedure's
407 ;; application. First, a trap for when a frame returns.
408 ;;
409 (define* (trap-frame-finish frame return-handler abort-handler
410 #:key (vm (the-vm)))
411 (arg-check frame frame?)
412 (arg-check return-handler procedure?)
413 (arg-check abort-handler procedure?)
414 (let ((fp (frame-address frame)))
415 (define (pop-cont-hook frame)
416 (if (and fp (eq? (frame-address frame) fp))
417 (begin
418 (set! fp #f)
419 (return-handler frame))))
420
421 (define (abort-hook frame)
422 (if (and fp (< (frame-address frame) fp))
423 (begin
424 (set! fp #f)
425 (abort-handler frame))))
426
427 (new-enabled-trap
428 vm frame
429 (lambda (frame)
430 (if (not fp)
431 (error "return-or-abort traps may only be enabled once"))
432 (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
433 (add-hook! (vm-abort-continuation-hook vm) abort-hook)
434 (add-hook! (vm-restore-continuation-hook vm) abort-hook))
435 (lambda (frame)
436 (set! fp #f)
437 (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
438 (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
439 (remove-hook! (vm-restore-continuation-hook vm) abort-hook)))))
440
441 ;; A more traditional dynamic-wind trap. Perhaps this should not be
442 ;; based on the above trap-frame-finish?
443 ;;
444 (define* (trap-in-dynamic-extent proc enter-handler return-handler abort-handler
445 #:key current-frame (vm (the-vm))
446 (closure? #f)
447 (our-frame? (frame-matcher proc closure?)))
448 (arg-check proc procedure?)
449 (arg-check enter-handler procedure?)
450 (arg-check return-handler procedure?)
451 (arg-check abort-handler procedure?)
452 (let ((exit-trap #f))
453 (define (return-hook frame)
454 (exit-trap frame) ; disable the return/abort trap.
455 (set! exit-trap #f)
456 (return-handler frame))
457
458 (define (abort-hook frame)
459 (exit-trap frame) ; disable the return/abort trap.
460 (set! exit-trap #f)
461 (abort-handler frame))
462
463 (define (apply-hook frame)
464 (if (and (not exit-trap) (our-frame? frame))
465 (begin
466 (enter-handler frame)
467 (set! exit-trap
468 (trap-frame-finish frame return-hook abort-hook
469 #:vm vm)))))
470
471 (new-enabled-trap
472 vm current-frame
473 (lambda (frame)
474 (add-hook! (vm-apply-hook vm) apply-hook))
475 (lambda (frame)
476 (if exit-trap
477 (abort-hook frame))
478 (set! exit-trap #f)
479 (remove-hook! (vm-apply-hook vm) apply-hook)))))
480
481 ;; Trapping all procedure calls within a dynamic extent, recording the
482 ;; depth of the call stack relative to the original procedure.
483 ;;
484 (define* (trap-calls-in-dynamic-extent proc apply-handler return-handler
485 #:key current-frame (vm (the-vm))
486 (closure? #f)
487 (our-frame?
488 (frame-matcher proc closure?)))
489 (arg-check proc procedure?)
490 (arg-check apply-handler procedure?)
491 (arg-check return-handler procedure?)
492 (let ((*call-depth* 0))
493 (define (trace-push frame)
494 (set! *call-depth* (1+ *call-depth*)))
495
496 (define (trace-pop frame)
497 (return-handler frame *call-depth*)
498 (set! *call-depth* (1- *call-depth*)))
499
500 (define (trace-apply frame)
501 (apply-handler frame *call-depth*))
502
503 ;; FIXME: recalc depth on abort
504
505 (define (enter frame)
506 (add-hook! (vm-push-continuation-hook vm) trace-push)
507 (add-hook! (vm-pop-continuation-hook vm) trace-pop)
508 (add-hook! (vm-apply-hook vm) trace-apply))
509
510 (define (leave frame)
511 (remove-hook! (vm-push-continuation-hook vm) trace-push)
512 (remove-hook! (vm-pop-continuation-hook vm) trace-pop)
513 (remove-hook! (vm-apply-hook vm) trace-apply))
514
515 (define (return frame)
516 (leave frame))
517
518 (define (abort frame)
519 (leave frame))
520
521 (trap-in-dynamic-extent proc enter return abort
522 #:current-frame current-frame #:vm vm
523 #:our-frame? our-frame?)))
524
525 ;; Trapping all retired intructions within a dynamic extent.
526 ;;
527 (define* (trap-instructions-in-dynamic-extent proc next-handler
528 #:key current-frame (vm (the-vm))
529 (closure? #f)
530 (our-frame?
531 (frame-matcher proc closure?)))
532 (arg-check proc procedure?)
533 (arg-check next-handler procedure?)
534 (let ()
535 (define (trace-next frame)
536 (next-handler frame))
537
538 (define (enter frame)
539 (add-hook! (vm-next-hook vm) trace-next))
540
541 (define (leave frame)
542 (remove-hook! (vm-next-hook vm) trace-next))
543
544 (define (return frame)
545 (leave frame))
546
547 (define (abort frame)
548 (leave frame))
549
550 (trap-in-dynamic-extent proc enter return abort
551 #:current-frame current-frame #:vm vm
552 #:our-frame? our-frame?)))
553
554 ;; Traps calls and returns for a given procedure, keeping track of the call depth.
555 ;;
556 (define* (trap-calls-to-procedure proc apply-handler return-handler
557 #:key (vm (the-vm)))
558 (arg-check proc procedure?)
559 (arg-check apply-handler procedure?)
560 (arg-check return-handler procedure?)
561 (let ((pending-finish-traps '())
562 (last-fp #f))
563 (define (apply-hook frame)
564 (let ((depth (length pending-finish-traps)))
565
566 (apply-handler frame depth)
567
568 (if (not (eq? (frame-address frame) last-fp))
569 (let ((finish-trap #f))
570 (define (frame-finished frame)
571 (finish-trap frame) ;; disables the trap.
572 (set! pending-finish-traps
573 (delq finish-trap pending-finish-traps))
574 (set! finish-trap #f))
575
576 (define (return-hook frame)
577 (frame-finished frame)
578 (return-handler frame depth))
579
580 ;; FIXME: abort handler?
581 (define (abort-hook frame)
582 (frame-finished frame))
583
584 (set! finish-trap
585 (trap-frame-finish frame return-hook abort-hook #:vm vm))
586 (set! pending-finish-traps
587 (cons finish-trap pending-finish-traps))))))
588
589 ;; The basic idea is that we install one trap that fires for calls,
590 ;; but that each call installs its own finish trap. Those finish
591 ;; traps remove themselves as their frames finish or abort.
592 ;;
593 ;; However since to the outside world we present the interface of
594 ;; just being one trap, disabling this calls-to-procedure trap
595 ;; should take care of disabling all of the pending finish traps. We
596 ;; keep track of pending traps through the pending-finish-traps
597 ;; list.
598 ;;
599 ;; So since we know that the trap-at-procedure will be enabled, and
600 ;; thus returning a disable closure, we make sure to wrap that
601 ;; closure in something that will disable pending finish traps.
602 (define (with-pending-finish-disablers trap)
603 (define (with-pending-finish-enablers trap)
604 (lambda* (#:optional frame)
605 (with-pending-finish-disablers (trap frame))))
606
607 (lambda* (#:optional frame)
608 (for-each (lambda (disable) (disable frame))
609 pending-finish-traps)
610 (set! pending-finish-traps '())
611 (with-pending-finish-enablers (trap frame))))
612
613 (with-pending-finish-disablers
614 (trap-at-procedure-call proc apply-hook #:vm vm))))
615
616 ;; Trap when the source location changes.
617 ;;
618 (define* (trap-matching-instructions frame-pred handler
619 #:key (vm (the-vm)))
620 (arg-check frame-pred procedure?)
621 (arg-check handler procedure?)
622 (let ()
623 (define (next-hook frame)
624 (if (frame-pred frame)
625 (handler frame)))
626
627 (new-enabled-trap
628 vm #f
629 (lambda (frame)
630 (add-hook! (vm-next-hook vm) next-hook))
631 (lambda (frame)
632 (remove-hook! (vm-next-hook vm) next-hook)))))