Merge branch 'stable-2.0'
[bpt/guile.git] / module / system / vm / traps.scm
index 7abe453..114647e 100644 (file)
@@ -1,6 +1,6 @@
 ;;; Traps: stepping, breakpoints, and such.
 
-;; Copyright (C)  2010 Free Software Foundation, Inc.
+;; Copyright (C)  2010, 2012, 2013 Free Software Foundation, Inc.
 
 ;;; This library is free software; you can redistribute it and/or
 ;;; modify it under the terms of the GNU Lesser General Public
 (define-module (system vm traps)
   #:use-module (system base pmatch)
   #:use-module (system vm vm)
+  #:use-module (system vm debug)
   #:use-module (system vm frame)
   #:use-module (system vm program)
-  #:use-module (system vm objcode)
-  #:use-module (system vm instruction)
   #:use-module (system xref)
   #:use-module (rnrs bytevectors)
   #:export (trap-at-procedure-call
@@ -72,7 +71,8 @@
             trap-in-dynamic-extent
             trap-calls-in-dynamic-extent
             trap-instructions-in-dynamic-extent
-            trap-calls-to-procedure))
+            trap-calls-to-procedure
+            trap-matching-instructions))
 
 (define-syntax arg-check
   (syntax-rules ()
@@ -83,7 +83,7 @@
      (if (not (predicate? arg))
          (error "bad argument ~a: expected ~a" 'arg 'predicate?)))))
 
-(define (new-disabled-trap vm enable disable)
+(define (new-disabled-trap enable disable)
   (let ((enabled? #f))
     (define-syntax disabled?
       (identifier-syntax
 
     enable-trap))
 
-(define (new-enabled-trap vm frame enable disable)
-  ((new-disabled-trap vm enable disable) frame))
+(define (new-enabled-trap frame enable disable)
+  ((new-disabled-trap enable disable) frame))
 
-(define (frame-matcher proc match-objcode?)
-  (if match-objcode?
-      (lambda (frame)
-        (let ((frame-proc (frame-procedure frame)))
-          (or (eq? frame-proc proc)
-              (and (program? frame-proc)
-                   (eq? (program-objcode frame-proc)
-                        (program-objcode proc))))))
-      (lambda (frame)
-        (eq? (frame-procedure frame) proc))))
+;; Returns an absolute IP.
+(define (program-last-ip prog)
+  (let ((pdi (find-program-debug-info (program-code prog))))
+    (and pdi (program-debug-info-size pdi))))
+
+(define (frame-matcher proc match-code?)
+  (let ((proc (if (struct? proc)
+                  (procedure proc)
+                  proc)))
+    (if match-code?
+        (if (program? proc)
+            (let ((start (program-code proc))
+                  (end (program-last-ip proc)))
+              (lambda (frame)
+                (let ((ip (frame-instruction-pointer frame)))
+                  (and (<= start ip) (< ip end)))))
+            (lambda (frame) #f))
+        (lambda (frame)
+          (eq? (frame-procedure frame) proc)))))
 
 ;; A basic trap, fires when a procedure is called.
 ;;
-(define* (trap-at-procedure-call proc handler #:key (vm (the-vm))
-                                 (closure? #f)
+(define* (trap-at-procedure-call proc handler #:key (closure? #f)
                                  (our-frame? (frame-matcher proc closure?)))
   (arg-check proc procedure?)
   (arg-check handler procedure?)
           (handler frame)))
 
     (new-enabled-trap
-     vm #f
+     #f
      (lambda (frame)
-       (add-hook! (vm-apply-hook vm) apply-hook))
+       (add-hook! (vm-apply-hook) apply-hook))
      (lambda (frame)
-       (remove-hook! (vm-apply-hook vm) apply-hook)))))
+       (remove-hook! (vm-apply-hook) apply-hook)))))
 
 ;; A more complicated trap, traps when control enters a procedure.
 ;;
 ;;  * An abort.
 ;;
 (define* (trap-in-procedure proc enter-handler exit-handler
-                            #:key current-frame (vm (the-vm))
-                            (closure? #f)
+                            #:key current-frame (closure? #f)
                             (our-frame? (frame-matcher proc closure?)))
   (arg-check proc procedure?)
   (arg-check enter-handler procedure?)
       (if in-proc?
           (exit-proc frame)))
     
-    (define (pop-cont-hook frame)
+    (define (pop-cont-hook frame . values)
       (if in-proc?
           (exit-proc frame))
       (if (our-frame? (frame-previous frame))
           (enter-proc (frame-previous frame))))
 
-    (define (abort-hook frame)
-      (if in-proc?
-          (exit-proc frame))
-      (if (our-frame? frame)
-          (enter-proc frame)))
-
-    (define (restore-hook frame)
+    (define (abort-hook frame . values)
       (if in-proc?
           (exit-proc frame))
       (if (our-frame? frame)
           (enter-proc frame)))
 
     (new-enabled-trap
-     vm current-frame
+     current-frame
      (lambda (frame)
-       (add-hook! (vm-apply-hook vm) apply-hook)
-       (add-hook! (vm-push-continuation-hook vm) push-cont-hook)
-       (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
-       (add-hook! (vm-abort-continuation-hook vm) abort-hook)
-       (add-hook! (vm-restore-continuation-hook vm) restore-hook)
+       (add-hook! (vm-apply-hook) apply-hook)
+       (add-hook! (vm-push-continuation-hook) push-cont-hook)
+       (add-hook! (vm-pop-continuation-hook) pop-cont-hook)
+       (add-hook! (vm-abort-continuation-hook) abort-hook)
        (if (and frame (our-frame? frame))
            (enter-proc frame)))
      (lambda (frame)
        (if in-proc?
            (exit-proc frame))
-       (remove-hook! (vm-apply-hook vm) apply-hook)
-       (remove-hook! (vm-push-continuation-hook vm) push-cont-hook)
-       (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
-       (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
-       (remove-hook! (vm-restore-continuation-hook vm) restore-hook)))))
+       (remove-hook! (vm-apply-hook) apply-hook)
+       (remove-hook! (vm-push-continuation-hook) push-cont-hook)
+       (remove-hook! (vm-pop-continuation-hook) pop-cont-hook)
+       (remove-hook! (vm-abort-continuation-hook) abort-hook)))))
 
 ;; Building on trap-in-procedure, we have trap-instructions-in-procedure
 ;;
 (define* (trap-instructions-in-procedure proc next-handler exit-handler
-                                         #:key current-frame (vm (the-vm))
-                                         (closure? #f)
+                                         #:key current-frame (closure? #f)
                                          (our-frame?
                                           (frame-matcher proc closure?)))
   (arg-check proc procedure?)
           (next-handler frame)))
     
     (define (enter frame)
-      (add-hook! (vm-next-hook vm) next-hook)
+      (add-hook! (vm-next-hook) next-hook)
       (if frame (next-hook frame)))
 
     (define (exit frame)
       (exit-handler frame)
-      (remove-hook! (vm-next-hook vm) next-hook))
+      (remove-hook! (vm-next-hook) next-hook))
 
     (trap-in-procedure proc enter exit
-                       #:current-frame current-frame #:vm vm
+                       #:current-frame current-frame
                        #:our-frame? our-frame?)))
 
 (define (non-negative-integer? x)
           range))
 
 ;; Building on trap-instructions-in-procedure, we have
-;; trap-instructions-in-procedure.
+;; trap-at-procedure-ip-in-range.
 ;;
 (define* (trap-at-procedure-ip-in-range proc range handler
-                                        #:key current-frame (vm (the-vm))
-                                        (closure? #f)
+                                        #:key current-frame (closure? #f)
                                         (our-frame?
                                          (frame-matcher proc closure?)))
   (arg-check proc procedure?)
   (arg-check range range?)
   (arg-check handler procedure?)
-  (let ((was-in-range? #f))
+  (let ((fp-stack '()))
+    (define (cull-frames! fp)
+      (let lp ((frames fp-stack))
+        (if (and (pair? frames) (< (car frames) fp))
+            (lp (cdr frames))
+            (set! fp-stack frames))))
+
     (define (next-handler frame)
-      (let ((now-in-range? (in-range? range (frame-instruction-pointer frame))))
-        (cond
-         (was-in-range? (set! was-in-range? now-in-range?))
-         (now-in-range? (handler frame) (set! was-in-range? #t)))))
+      (let ((fp (frame-address frame))
+            (ip (frame-instruction-pointer frame)))
+        (cull-frames! fp)
+        (let ((now-in-range? (in-range? range ip))
+              (was-in-range? (and (pair? fp-stack) (= (car fp-stack) fp))))
+          (cond
+           (was-in-range?
+            (if (not now-in-range?)
+                (set! fp-stack (cdr fp-stack))))
+           (now-in-range?
+            (set! fp-stack (cons fp fp-stack))
+            (handler frame))))))
     
     (define (exit-handler frame)
-      (set! was-in-range? #f))
+      (if (and (pair? fp-stack)
+               (= (car fp-stack) (frame-address frame)))
+          (set! fp-stack (cdr fp-stack))))
     
     (trap-instructions-in-procedure proc next-handler exit-handler
-                                    #:current-frame current-frame #:vm vm
+                                    #:current-frame current-frame
                                     #:our-frame? our-frame?)))
 
-;; FIXME: pull this definition from elsewhere.
-(define *bytecode-header-len* 8)
-
-;; FIXME: define this in objcode somehow. We are reffing the first
-;; uint32 in the objcode, which is the length of the program (without
-;; the meta).
-(define (program-last-ip prog)
-  (bytevector-u32-native-ref (objcode->bytecode (program-objcode prog)) 0))
-
-;; We could decompile the program to get this, but that seems like a
-;; waste.
-(define (bytecode-instruction-length bytecode ip)
-  (let* ((idx (+ ip *bytecode-header-len*))
-         (inst (opcode->instruction (bytevector-u8-ref bytecode idx))))
-    ;; 1+ for the instruction itself.
-    (1+ (cond
-         ((eq? inst 'load-program)
-          (+ (bytevector-u32-native-ref bytecode (+ idx 1))
-             (bytevector-u32-native-ref bytecode (+ idx 5))))
-         ((< (instruction-length inst) 0)
-          ;; variable length instruction -- the length is encoded in the
-          ;; instruction stream.
-          (+ (ash (bytevector-u8-ref bytecode (+ idx 1)) 16)
-             (ash (bytevector-u8-ref bytecode (+ idx 2)) 8)
-             (bytevector-u8-ref bytecode (+ idx 3))))
-         (else
-          ;; fixed length
-          (instruction-length inst))))))
-
-;; Source information could in theory be correlated with the ip of the
-;; instruction, or the ip just after the instruction is retired. Guile
-;; does the latter, to make backtraces easy -- an error produced while
-;; running an opcode always happens after it has retired its arguments.
-;;
-;; But for breakpoints and such, we need the ip before the instruction
-;; is retired -- before it has had a chance to do anything. So here we
-;; change from the post-retire addresses given by program-sources to
-;; pre-retire addresses.
-;;
-(define (program-sources-before-retire proc)
-  (let ((bv (objcode->bytecode (program-objcode proc))))
-    (let lp ((in (program-sources proc))
-             (out '())
-             (ip 0))
-      (cond
-       ((null? in)
-        (reverse out))
-       (else
-        (pmatch (car in)
-          ((,post-ip . ,source)
-           (let lp2 ((ip ip)
-                     (next ip))
-             (if (< next post-ip)
-                 (lp2 next (+ next (bytecode-instruction-length bv next)))
-                 (lp (cdr in)
-                     (acons ip source out)
-                     next))))
-          (else
-           (error "unexpected"))))))))
-
 (define (program-sources-by-line proc file)
-  (let lp ((sources (program-sources-before-retire proc))
-           (out '()))
-    (if (pair? sources)
-        (lp (cdr sources)
-            (pmatch (car sources)
-              ((,start-ip ,start-file ,start-line . ,start-col)
-               (if (equal? start-file file)
-                   (cons (cons start-line
-                               (if (pair? (cdr sources))
-                                   (pmatch (cadr sources)
-                                     ((,end-ip . _)
-                                      (cons start-ip end-ip))
-                                     (else (error "unexpected")))
-                                   (cons start-ip (program-last-ip proc))))
-                         out)
-                   out))
-              (else (error "unexpected"))))
-        (let ((alist '()))
-          (for-each
-           (lambda (pair)
-             (set! alist
-                   (assv-set! alist (car pair)
-                              (cons (cdr pair)
-                                    (or (assv-ref alist (car pair))
-                                        '())))))
-           out)
-          (sort! alist (lambda (x y) (< (car x) (car y))))
-          alist))))
+  (cond
+   ((program? proc)
+    (let ((code (program-code proc)))
+      (let lp ((sources (program-sources proc))
+               (out '()))
+        (if (pair? sources)
+            (lp (cdr sources)
+                (pmatch (car sources)
+                  ((,start-ip ,start-file ,start-line . ,start-col)
+                   (if (equal? start-file file)
+                       (acons start-line
+                              (if (pair? (cdr sources))
+                                  (pmatch (cadr sources)
+                                    ((,end-ip . _)
+                                     (cons (+ start-ip code)
+                                           (+ end-ip code)))
+                                    (else (error "unexpected")))
+                                  (cons (+ start-ip code)
+                                        (program-last-ip proc)))
+                              out)
+                       out))
+                  (else (error "unexpected"))))
+            (let ((alist '()))
+              (for-each
+               (lambda (pair)
+                 (set! alist
+                       (assv-set! alist (car pair)
+                                  (cons (cdr pair)
+                                        (or (assv-ref alist (car pair))
+                                            '())))))
+               out)
+              (sort! alist (lambda (x y) (< (car x) (car y))))
+              alist)))))
+   (else '())))
 
 (define (source->ip-range proc file line)
   (or (or-map (lambda (line-and-ranges)
 ;; trap-at-source-location. The parameter `user-line' is one-indexed, as
 ;; a user counts lines, instead of zero-indexed, as Guile counts lines.
 ;;
-(define* (trap-at-source-location file user-line handler
-                                  #:key current-frame (vm (the-vm)))
+(define* (trap-at-source-location file user-line handler #:key current-frame)
   (arg-check file string?)
   (arg-check user-line positive-integer?)
   (arg-check handler procedure?)
         (lambda () (source-closures-or-procedures file (1- user-line)))
       (lambda (procs closures?)
         (new-enabled-trap
-         vm current-frame
+         current-frame
          (lambda (frame)
            (set! traps
                  (map
                     (let ((range (source->ip-range proc file (1- user-line))))
                       (trap-at-procedure-ip-in-range proc range handler
                                                      #:current-frame current-frame
-                                                     #:vm vm
                                                      #:closure? closures?)))
                   procs))
            (if (null? traps)
 ;; do useful things during the dynamic extent of a procedure's
 ;; application. First, a trap for when a frame returns.
 ;;
-(define* (trap-frame-finish frame return-handler abort-handler
-                            #:key (vm (the-vm)))
+(define (trap-frame-finish frame return-handler abort-handler)
   (arg-check frame frame?)
   (arg-check return-handler procedure?)
   (arg-check abort-handler procedure?)
   (let ((fp (frame-address frame)))
-    (define (pop-cont-hook frame)
+    (define (pop-cont-hook frame . values)
       (if (and fp (eq? (frame-address frame) fp))
           (begin
             (set! fp #f)
-            (return-handler frame))))
+            (apply return-handler frame values))))
     
-    (define (abort-hook frame)
+    (define (abort-hook frame . values)
       (if (and fp (< (frame-address frame) fp))
           (begin
             (set! fp #f)
-            (abort-handler frame))))
+            (apply abort-handler frame values))))
     
     (new-enabled-trap
-     vm frame
+     frame
      (lambda (frame)
        (if (not fp)
            (error "return-or-abort traps may only be enabled once"))
-       (add-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
-       (add-hook! (vm-abort-continuation-hook vm) abort-hook)
-       (add-hook! (vm-restore-continuation-hook vm) abort-hook))
+       (add-hook! (vm-pop-continuation-hook) pop-cont-hook)
+       (add-hook! (vm-abort-continuation-hook) abort-hook))
      (lambda (frame)
        (set! fp #f)
-       (remove-hook! (vm-pop-continuation-hook vm) pop-cont-hook)
-       (remove-hook! (vm-abort-continuation-hook vm) abort-hook)
-       (remove-hook! (vm-restore-continuation-hook vm) abort-hook)))))
+       (remove-hook! (vm-pop-continuation-hook) pop-cont-hook)
+       (remove-hook! (vm-abort-continuation-hook) abort-hook)))))
 
 ;; A more traditional dynamic-wind trap. Perhaps this should not be
 ;; based on the above trap-frame-finish?
 ;;
 (define* (trap-in-dynamic-extent proc enter-handler return-handler abort-handler
-                                 #:key current-frame (vm (the-vm))
-                                 (closure? #f)
+                                 #:key current-frame (closure? #f)
                                  (our-frame? (frame-matcher proc closure?)))
   (arg-check proc procedure?)
   (arg-check enter-handler procedure?)
   (arg-check return-handler procedure?)
   (arg-check abort-handler procedure?)
   (let ((exit-trap #f))
-    (define (return-hook frame)
+    (define (return-hook frame . values)
       (exit-trap frame) ; disable the return/abort trap.
       (set! exit-trap #f)
       (return-handler frame))
     
-    (define (abort-hook frame)
+    (define (abort-hook frame . values)
       (exit-trap frame) ; disable the return/abort trap.
       (set! exit-trap #f)
       (abort-handler frame))
           (begin
             (enter-handler frame)
             (set! exit-trap
-                  (trap-frame-finish frame return-hook abort-hook
-                                     #:vm vm)))))
+                  (trap-frame-finish frame return-hook abort-hook)))))
     
     (new-enabled-trap
-     vm current-frame
+     current-frame
      (lambda (frame)
-       (add-hook! (vm-apply-hook vm) apply-hook))
+       (add-hook! (vm-apply-hook) apply-hook))
      (lambda (frame)
        (if exit-trap
            (abort-hook frame))
        (set! exit-trap #f)
-       (remove-hook! (vm-apply-hook vm) apply-hook)))))
+       (remove-hook! (vm-apply-hook) apply-hook)))))
 
 ;; Trapping all procedure calls within a dynamic extent, recording the
 ;; depth of the call stack relative to the original procedure.
 ;;
 (define* (trap-calls-in-dynamic-extent proc apply-handler return-handler
-                                       #:key current-frame (vm (the-vm))
-                                       (closure? #f)
+                                       #:key current-frame (closure? #f)
                                        (our-frame?
                                         (frame-matcher proc closure?)))
   (arg-check proc procedure?)
     (define (trace-push frame)
       (set! *call-depth* (1+ *call-depth*)))
   
-    (define (trace-pop frame)
-      (return-handler frame *call-depth*)
+    (define (trace-pop frame . values)
+      (apply return-handler frame *call-depth* values)
       (set! *call-depth* (1- *call-depth*)))
   
     (define (trace-apply frame)
     ;; FIXME: recalc depth on abort
 
     (define (enter frame)
-      (add-hook! (vm-push-continuation-hook vm) trace-push)
-      (add-hook! (vm-pop-continuation-hook vm) trace-pop)
-      (add-hook! (vm-apply-hook vm) trace-apply))
+      (add-hook! (vm-push-continuation-hook) trace-push)
+      (add-hook! (vm-pop-continuation-hook) trace-pop)
+      (add-hook! (vm-apply-hook) trace-apply))
   
     (define (leave frame)
-      (remove-hook! (vm-push-continuation-hook vm) trace-push)
-      (remove-hook! (vm-pop-continuation-hook vm) trace-pop)
-      (remove-hook! (vm-apply-hook vm) trace-apply))
+      (remove-hook! (vm-push-continuation-hook) trace-push)
+      (remove-hook! (vm-pop-continuation-hook) trace-pop)
+      (remove-hook! (vm-apply-hook) trace-apply))
   
     (define (return frame)
       (leave frame))
       (leave frame))
 
     (trap-in-dynamic-extent proc enter return abort
-                            #:current-frame current-frame #:vm vm
+                            #:current-frame current-frame
                             #:our-frame? our-frame?)))
 
 ;; Trapping all retired intructions within a dynamic extent.
 ;;
 (define* (trap-instructions-in-dynamic-extent proc next-handler
-                                              #:key current-frame (vm (the-vm))
-                                              (closure? #f)
+                                              #:key current-frame (closure? #f)
                                               (our-frame?
                                                (frame-matcher proc closure?)))
   (arg-check proc procedure?)
       (next-handler frame))
   
     (define (enter frame)
-      (add-hook! (vm-next-hook vm) trace-next))
+      (add-hook! (vm-next-hook) trace-next))
   
     (define (leave frame)
-      (remove-hook! (vm-next-hook vm) trace-next))
+      (remove-hook! (vm-next-hook) trace-next))
   
     (define (return frame)
       (leave frame))
       (leave frame))
 
     (trap-in-dynamic-extent proc enter return abort
-                            #:current-frame current-frame #:vm vm
+                            #:current-frame current-frame
                             #:our-frame? our-frame?)))
 
 ;; Traps calls and returns for a given procedure, keeping track of the call depth.
 ;;
-(define* (trap-calls-to-procedure proc apply-handler return-handler
-                                  #:key (width 80) (vm (the-vm)))
+(define (trap-calls-to-procedure proc apply-handler return-handler)
   (arg-check proc procedure?)
   (arg-check apply-handler procedure?)
   (arg-check return-handler procedure?)
                       (delq finish-trap pending-finish-traps))
                 (set! finish-trap #f))
               
-              (define (return-hook frame)
+              (define (return-hook frame . values)
                 (frame-finished frame)
-                (return-handler frame depth))
+                (apply return-handler frame depth values))
         
               ;; FIXME: abort handler?
-              (define (abort-hook frame)
+              (define (abort-hook frame . values)
                 (frame-finished frame))
         
               (set! finish-trap
-                    (trap-frame-finish frame return-hook abort-hook #:vm vm))
+                    (trap-frame-finish frame return-hook abort-hook))
               (set! pending-finish-traps
                     (cons finish-trap pending-finish-traps))))))
 
         (with-pending-finish-enablers (trap frame))))
 
     (with-pending-finish-disablers
-     (trap-at-procedure-call proc apply-hook #:vm vm))))
+     (trap-at-procedure-call proc apply-hook))))
+
+;; Trap when the source location changes.
+;;
+(define (trap-matching-instructions frame-pred handler)
+  (arg-check frame-pred procedure?)
+  (arg-check handler procedure?)
+  (let ()
+    (define (next-hook frame)
+      (if (frame-pred frame)
+          (handler frame)))
+  
+    (new-enabled-trap
+     #f
+     (lambda (frame)
+       (add-hook! (vm-next-hook) next-hook))
+     (lambda (frame)
+       (remove-hook! (vm-next-hook) next-hook)))))