;;;-*-Mode: LISP; Package: CCL -*-
;;;
;;;   Copyright (C) 2006-2009 Clozure Associates and contributors
;;;   This file is part of Clozure CL.  
;;;
;;;   Clozure CL is licensed under the terms of the Lisp Lesser GNU Public
;;;   License , known as the LLGPL and distributed with Clozure CL as the
;;;   file "LICENSE".  The LLGPL consists of a preamble and the LGPL,
;;;   which is distributed with Clozure CL as the file "LGPL".  Where these
;;;   conflict, the preamble takes precedence.  
;;;
;;;   Clozure CL is referenced in the preamble as the "LIBRARY."
;;;
;;;   The LLGPL is also available online at
;;;   http://opensource.franz.com/preamble.html


(in-package "CCL")


;;; Returns two values:
;;;  [nil, nil] if it can be reliably determined that function uses no registers at PC
;;;  [mask, saved-location]  if it can be reliably determined that the registers specified by "mask"
;;;      were saved at "saved-location" in the function's stack frame
;;;  [mask, nil] if registers in "mask" MAY have been saved, but we don't know how to restore them
;;;      (perhaps because the "at-pc" argument wasn't specified.


(defun registers-used-by (function &optional at-pc)
  (multiple-value-bind (mask stack-location rpc)
      (%function-register-usage function)
    (if (or (null mask)
            (and at-pc rpc (<= at-pc rpc)))
      (values nil nil)
      (values (canonicalize-register-mask mask) (if (and at-pc rpc) stack-location)))))

(defun canonicalize-register-mask (mask)
  (dpb (ldb (byte 2 14) mask) (byte 2 2) (ldb (byte 2 11) mask)))

(defun xcf-p (p)
  (eql 0 (%fixnum-ref p target::lisp-frame.return-address)))

(defun %current-xcf ()
  (do* ((q (%get-frame-ptr) (%%frame-backlink q)))
       ((zerop q))
    (declare (fixnum q))
    (when (xcf-p q) (return q))))

;;; Try to determine the program counter value, relative to an xcf's nominal function.
(defun pc-from-xcf (xcf)
  (let* ((nominal-function (%fixnum-ref xcf target::xcf.nominal-function))
         (containing-object (%fixnum-ref xcf target::xcf.containing-object)))
    (when (typep nominal-function 'function)
      (if (eq containing-object (function-to-function-vector nominal-function))
        (- (%fixnum-ref xcf target::xcf.relative-pc)
	   #+x8632-target x8632::fulltag-misc
	   #+x8664-target x8664::tag-function)
        (let* ((tra (%fixnum-ref xcf target::xcf.ra0)))
          (if (and #+x8664-target (= (lisptag tra) x8664::tag-tra)
		   #+x8632-target (= (fulltag tra) x8632::fulltag-tra)
                   (eq nominal-function (%return-address-function tra)))
            (%return-address-offset tra)))))))
            
(defun cfp-lfun (p)
  (if (xcf-p p)
    (values
     (%fixnum-ref p target::xcf.nominal-function)
     (pc-from-xcf p))
    (%cfp-lfun p)))

;;; On PPC, some frames on the control stack are associated with catch
;;; frames rather than with function calls.  The whole concept doesn't
;;; really apply here (e.g., nothing we encounter while walking frame
;;; pointer links belongs to a catch frame.)
(defun catch-csp-p (p context)
  (declare (ignore p context)))

(defun %raw-frame-ref (frame context idx bad)
  (declare (fixnum frame idx))
  (let* ((base (parent-frame frame context))
         (raw-size (- base frame)))
    (declare (fixnum base raw-size))
    (if (and (>= idx 0)
             (< idx raw-size))
      (let* ((addr (- (the fixnum (1- base))
                      idx)))
        (multiple-value-bind (db-count first-db last-db)
            (count-db-links-in-frame frame base context)
          (let* ((is-db-link
                  (unless (zerop db-count)
                    (do* ((last last-db (previous-db-link last first-db)))
                         ((null last))
                      (when (= addr last)
                        (return t))))))
            (if is-db-link
              (oldest-binding-frame-value context addr)
              (%fixnum-ref addr)))))
      bad)))

(defun %raw-frame-set (frame context idx new)
  (declare (fixnum frame idx))
  (let* ((base (parent-frame frame context))
         (raw-size (- base frame)))
    (declare (fixnum base raw-size))
    (if (and (>= idx 0)
             (< idx raw-size))
      (let* ((addr (- (the fixnum (1- base))
                      idx)))
        (multiple-value-bind (db-count first-db last-db)
            (count-db-links-in-frame frame base context)
          (let* ((is-db-link
                  (unless (zerop db-count)
                    (do* ((last last-db (previous-db-link last first-db)))
                         ((null last))
                      (when (= addr last)
                        (return t))))))
            (if is-db-link
              (setf (oldest-binding-frame-value context addr) new)
              (setf (%fixnum-ref addr) new))))))))

(defun %stack< (index1 index2 &optional context)
  (let* ((tcr (if context (bt.tcr context) (%current-tcr)))
         (vs-area (%fixnum-ref tcr (- target::tcr.vs-area
				      target::tcr-bias))))
    (and (%ptr-in-area-p index1 vs-area)
         (%ptr-in-area-p index2 vs-area)
         (< (the fixnum index1) (the fixnum index2)))))




(defun register-number->saved-register-index (regnum)
  (ecase regnum
    (#.x8664::save3 0)
    (#.x8664::save2 1)
    (#.x8664::save1 2)
    (#.x8664::save0 3)))


(defun get-register-value (address last-catch index)
  (if address
    (%fixnum-ref address)
    (uvref last-catch (+ index 
			 #+x8632-target
			 x8632::catch-frame.db-link-cell
			 #+x8664-target
			 x8664::catch-frame.save-save3-cell))))

;;; Inverse of get-register-value

(defun set-register-value (value address last-catch index)
  (if address
    (%fixnum-set address value)
    (setf (uvref last-catch (+ index
			       #+x8632-target
			       x8632::catch-frame.db-link-cell
			       #+x8664-target
			       x8664::catch-frame.save-save3-cell))
          value)))

(defun %find-register-argument-value (context cfp regval bad)
  (let* ((last-catch (last-catch-since cfp context))
         (index (register-number->saved-register-index regval)))
    (do* ((frame cfp (child-frame frame context))
          (first t))
         ((null frame))
      (if (xcf-p frame)
        (with-macptrs (xp)
          (%setf-macptr-to-object xp (%fixnum-ref frame x8664::xcf.xp))
          (return-from %find-register-argument-value
            (encoded-gpr-lisp xp regval)))
        (progn
          (unless first
            (multiple-value-bind (lfun pc)
                (cfp-lfun frame)
              (when lfun
                (multiple-value-bind (mask where)
                    (registers-used-by lfun pc)
                  (when (if mask (logbitp index mask))
                    (return-from %find-register-argument-value
                      (if where
                        (let ((offset (logcount (logandc2 mask (1- (ash 1 (1+ index)))))))
                          (raw-frame-ref frame context (+ where offset) bad))
                        bad)))))))
          (setq first nil))))
    (get-register-value nil last-catch index)))

(defun %set-register-argument-value (context cfp regval new)
  (let* ((last-catch (last-catch-since cfp context))
         (index (register-number->saved-register-index regval)))
    (do* ((frame cfp (child-frame frame context))
          (first t))
         ((null frame))
      (if (xcf-p frame)
        (with-macptrs (xp)
          (%setf-macptr-to-object xp (%fixnum-ref frame x8664::xcf.xp))
          (return-from %set-register-argument-value
            (setf (encoded-gpr-lisp xp regval) new)))
        (progn
          (unless first
            (multiple-value-bind (lfun pc)
                (cfp-lfun frame)
              (when lfun
                (multiple-value-bind (mask where)
                    (registers-used-by lfun pc)
                  (when (if mask (logbitp index mask))
                    (incf where (logcount (logandc2 mask (1- (ash 1 (1+ index))))))

                    (return-from %set-register-argument-value
                      (raw-frame-set frame context where new)))))))
          (setq first nil))))
    (set-register-value new nil last-catch index)))

;;; Used for printing only.
(defun index->address (p)
  (ldb (byte #+32-bit-target 32 #+64-bit-target 64 0)  (ash p target::fixnumshift)))

(defun exception-frame-p (x)
  (and x (xcf-p x)))

;;; Function has failed a number-of-arguments check; return a list
;;; of the actual arguments.
;;; On x86-64, the kernel has finished the frame and pushed everything
;;; for us, so all that we need to do is to hide any inherited arguments.
(defun arg-check-call-arguments (fp function)
  (when (xcf-p fp)
    (with-macptrs (xp)
      (%setf-macptr-to-object xp (%fixnum-ref fp target::xcf.xp))
      (let* ((numinh (ldb $lfbits-numinh (lfun-bits function)))
             (nargs (- (xp-argument-count xp) numinh))
             (p (- (%fixnum-ref fp target::xcf.backptr)
                   (* target::node-size numinh))))
        (declare (fixnum numinh nargs p))
        (collect ((args))
          (dotimes (i nargs (args))
            (args (%fixnum-ref p (- target::node-size)))
            (decf p)))))))

(defun vsp-limits (frame context)
  (let* ((parent (parent-frame frame context)))
    (if (xcf-p frame)
      (values (+ frame (ash target::xcf.size (- target::word-shift)))
              parent)
      (let* ((tra (%fixnum-ref frame target::lisp-frame.return-address)))
        (values (+ frame 2 (if (eq tra (%get-kernel-global ret1valaddr)) 1 0))
                parent)))))

(defun last-catch-since (fp context)
  (let* ((tcr (if context (bt.tcr context) (%current-tcr)))
         (catch (%catch-top tcr))
         (last-catch nil))
    (loop
      (unless catch (return last-catch))
      (let ((catch-fp (uvref catch
			     #+x8632-target
			     x8632::catch-frame.ebp-cell
			     #+x8664-target
			     x8664::catch-frame.rbp-cell)))
        (when (%stack< fp catch-fp context) (return last-catch))
        (setq last-catch catch
              catch (next-catch catch))))))

(defun last-xcf-since (target-fp start-fp context)
  (do* ((last-xcf nil)
        (fp start-fp (parent-frame fp context)))
       ((or (eql fp target-fp)
            (null fp)
            (%stack< target-fp fp)) last-xcf)
    (if (xcf-p fp) (setq last-xcf fp))))

(defun match-local-name (cellno info pc)
  (when info
    (let* ((syms (%car info))
           (ptrs (%cdr info)))
      (dotimes (i (length syms))
        (let ((j (%i+ i (%i+ i i ))))
          (and (eq (uvref ptrs j) (%ilogior (%ilsl (+ 6 target::word-shift) cellno) #o77))
               (%i>= pc (uvref ptrs (%i+ j 1)))
               (%i< pc (uvref ptrs (%i+ j 2)))
               (return (aref syms i))))))))

(defun apply-in-frame (frame function arglist &optional context)
  (setq function (coerce-to-function function))
  (let* ((parent (parent-frame frame context)))
    (when parent
      (if (xcf-p parent)
        (error "Can't unwind to exception frame ~s" frame)
        (setq frame parent))
      (if (or (null context)
              (eq (bt.tcr context) (%current-tcr)))
        (%apply-in-frame frame function arglist)
        (let* ((process (tcr->process (bt.tcr context))))
          (if process
            (process-interrupt process #'%apply-in-frame frame function arglist)
            (error "Can't find process for backtrace context ~s" context)))))))

(defun return-from-frame (frame &rest values)
  (apply-in-frame frame #'values values nil))
    

(defun last-tsp-before (target)
  (declare (fixnum target))
  (do* ((tsp (%fixnum-ref (%current-tcr) (- target::tcr.save-tsp
					    target::tcr-bias))
             (%fixnum-ref tsp target::tsp-frame.backptr)))
       ((zerop tsp) nil)
    (declare (fixnum tsp))
    (when (> (the fixnum (%fixnum-ref tsp #+x8632-target x8632::tsp-frame.ebp
				          #+x8664-target x8664::tsp-frame.rbp))
             target)
      (return tsp))))

    


;;; We can't determine this reliably (yet).
(defun last-foreign-sp-before (target)
  (declare (fixnum target))
  (do* ((cfp (%fixnum-ref (%current-tcr) (- target::tcr.foreign-sp
					    target::tcr-bias))
             (%fixnum-ref cfp target::csp-frame.backptr)))
       ((zerop cfp))
    (declare (fixnum cfp))
    (let* ((rbp (%fixnum-ref cfp #+x8632-target x8632::csp-frame.ebp
			         #+x8664-target x8664::csp-frame.rbp)))
      (declare (fixnum rbp))
      (if (> rbp target)
        (return cfp)
        (if (zerop rbp)
          (return nil))))))


(defun %tsp-frame-containing-progv-binding (db)
  (declare (fixnum db))
  (do* ((tsp (%fixnum-ref (%current-tcr) (- target::tcr.save-tsp
					    target::tcr-bias)) next)
        (next (%fixnum-ref tsp target::tsp-frame.backptr)
              (%fixnum-ref tsp target::tsp-frame.backptr)))
       ()
    (declare (fixnum tsp next))
    (let* ((rbp (%fixnum-ref tsp #+x8632-target x8632::tsp-frame.ebp
			         #+x8664-target x8664::tsp-frame.rbp)))
      (declare (fixnum rbp))
      (if (zerop rbp)
        (return (values nil nil))
        (if (and (> db tsp)
                 (< db next))
          (return (values tsp rbp)))))))

        




(defun last-binding-before (frame)
  (declare (fixnum frame))
  (do* ((db (%current-db-link) (%fixnum-ref db 0))
        (tcr (%current-tcr))
        (vs-area (%fixnum-ref tcr (- target::tcr.vs-area
				     target::tcr-bias)))
        (vs-low (%fixnum-ref vs-area target::area.low))
        (vs-high (%fixnum-ref vs-area target::area.high)))
       ((eql db 0) nil)
    (declare (fixnum db vs-low vs-high))
    (if (and (> db vs-low)
             (< db vs-high))
      (if (> db frame)
        (return db))
      ;; db link points elsewhere; PROGV uses the temp stack
      ;; to store an indefinite number of bindings.
      (multiple-value-bind (tsp rbp)
          (%tsp-frame-containing-progv-binding db)
        (if tsp
          (if (> rbp frame)
            (return db)
            ;; If the tsp frame is too young, we can skip
            ;; all of the bindings it contains.  The tsp
            ;; frame contains two words of overhead, followed
            ;; by a count of binding records in the frame,
            ;; followed by the youngest of "count" binding
            ;; records (which happens to be the value of
            ;; "db".)  Skip "count" binding records.
            (dotimes (i (the fixnum (%fixnum-ref tsp target::dnode-size)))
              (setq db (%fixnum-ref db 0))))
          ;; If the binding record wasn't on the temp stack and wasn't
          ;; on the value stack, that probably means that things are
          ;; seriously screwed up.  This error will be almost
          ;; meaningless to the user.
          (error "binding record (#x~16,'0x/#x~16,'0x) not on temp or value stack" (index->address db) db))))))
          


(defun find-x8664-saved-nvrs (frame start-fp context)
  (let* ((locations (make-array 16 :initial-element nil))
         (need (logior (ash 1 x8664::save0)
                       (ash 1 x8664::save1)
                       (ash 1 x8664::save2)
                       (ash 1 x8664::save3))))
    (declare (fixnum need)
             (dynamic-extent locations))
    (do* ((parent frame child)
          (child (child-frame parent context) (child-frame child context)))
         ((or (= need 0) (eq child start-fp))
          (values (%svref locations x8664::save0)
                  (%svref locations x8664::save1)
                  (%svref locations x8664::save2)
                  (%svref locations x8664::save3)))
      (multiple-value-bind (lfun pc) (cfp-lfun child)
        (when (and lfun pc)
          (multiple-value-bind (used where) (registers-used-by lfun pc)
            (when (and used where (logtest used need))
              (locally (declare (fixnum used))
                (do* ((i x8664::save3 (1+ i)))
                     ((or (= i 16) (= used 0)))
                  (declare (type (mod 16) i))
                  (when (logbitp i used)
                    (when (logbitp i need)
                      (setq need (logandc2 need (ash 1 i)))
                      (setf (%svref locations i)
                            (- (the fixnum (1- parent))
                               (+ where (logcount (logandc2 used (1+ (ash 1 (1+ i)))))))))
                    (setq used (logandc2 used (ash 1 i)))))))))))))
                                         
              
         
(defun %apply-in-frame (frame function arglist)
  (target-arch-case
   (:x8632 (error "%apply-in-frame doesn't work for x8632 yet"))
   (:x8664
    (let* ((target-catch (last-catch-since frame nil))
	   (start-fp (if target-catch
		       (uvref target-catch x8664::catch-frame.rbp-cell)
		       (%get-frame-ptr)))
	   (target-xcf (last-xcf-since frame start-fp nil))
	   (target-db-link (last-binding-before frame))
	   (target-tsp (last-tsp-before frame))
	   (target-foreign-sp (last-foreign-sp-before frame)))
      (multiple-value-bind (save0-loc save1-loc save2-loc save3-loc)
	  (find-x8664-saved-nvrs frame start-fp nil)
	(let* ((thunk (%clone-x86-function #'%%apply-in-frame-proto
					   frame
					   target-catch
					   target-db-link
					   target-xcf
					   target-tsp
					   target-foreign-sp
					   (if save0-loc
					     (- save0-loc frame)
					     0)
					   (if save1-loc
					     (- save1-loc frame)
					     0)
					   (if save2-loc
					     (- save2-loc frame)
					     0)
					   (if save3-loc
					     (- save3-loc frame)
					     0)
					   (coerce-to-function function)
					   arglist
					   0)))
	  (funcall thunk)))))))

            
    
