;;;-*-Mode: LISP; Package: CCL -*-
;;;
;;; Copyright 2010 Clozure Associates
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;;
;;;     http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

(in-package "CCL")

;(setq *print-array* nil)
;(setq *print-simple-bit-vector* nil)

#+:linuxx8664-target
(progn

(export '(idom-heap-utilization))

#|
(open-core "home:core.28209")
(idom-heap-utilization :unit nil :sort :size)
|#

(defconstant half-fixnum-shift (ash (integer-length most-positive-fixnum) -1))

(defconstant half-fixnum-mask (1- (ash 1 half-fixnum-shift)))

(defstruct (core-graph (:include core-info) (:conc-name "CG.") (:constructor %cons-cg))
  (heap-base 0 :type fixnum)
  (heap-end 0 :type fixnum)
  (stage nil) ;; indication of what has been computed and what hasn't, so can restart.
  (head-p #.(make-array 0 :element-type 'bit) :type simple-bit-vector)
  (ptrs-p #.(make-array 0 :element-type 'bit) :type simple-bit-vector)
  ;; Nodes after eliminating single-entry and leaf objects
  (nodes #() :type simple-vector) ;; map postorder-idx -> dnode
  (revnodes #() :type simple-vector) ;; map dnode -> postorder-idx
  (roots () :type list)
  (predecessors #() :type simple-vector) ;; postorder-idx -> list of postorder indices of predecessors
  (node-doms #() :type simple-vector) ;; postorder-idx of node -> postorder-idx of its immediate dominator
  (idoms #() :type simple-vector) ;; sequence of postorder indices of immediate dominators
  (revidoms #() :type simple-vector) ;; map dnode -> index in idoms
  (logsizes #() :type simple-vector) ;; corresponding sequence of logical sizes (including all owned objects)
  (physizes #() :type simple-vector) ;; corresponding sequence of physical sizes (including all owned objects)
  )

(setq *core-info-class* 'core-graph)

(defparameter *cg-stages* '(nil :objects :leaves :postorder :predecessors :idoms :idom-sizes t))

(defmethod cg-compute :before (stage &aux (cg (current-core)))
  (assert (memq stage *cg-stages*))
  (check-type cg core-graph)
  (when (eql (cg.heap-base cg) 0)
    (let ((area-ptr (core-q (kernel-global-address 'tenured-area))))
      (setf (cg.heap-base cg) (core-q area-ptr target::area.low))
      (setf (cg.heap-end cg) (core-q area-ptr target::area.active))))
  ;; ensure have all the prereqs
  (loop for undone = (cdr (memq (cg.stage cg) *cg-stages*))
        while (memq stage (cdr undone))
        do (format t "~&Computing ~a" (car undone))
        do (cg-compute (car undone))))

(defmethod cg-compute :after (stage &aux (cg (current-core)))
  (setf (cg.stage cg) stage))

(defmethod cg-compute ((stage (eql t))) ;; before method does all the work
  nil)


(declaim (inline core-node-p))
(defun core-node-p (ptr) (or (core-consp  ptr) (core-uvector-p ptr)))

(declaim (inline dnode addr))

(defun dnode (base n) (the fixnum (ash (%i- n base) (- target::dnode-shift))))

(defun addr (base n) (%i+ base (ash (the fixnum n) target::dnode-shift)))

(defun tagged-ptr (ptr)
  (let ((header (core-q ptr)))
    (cond ((uvheader-p header)
           (let ((subtag (uvheader-typecode header)))
             (+ ptr (cond ((eq subtag target::subtag-symbol) target::fulltag-symbol)
                          ((eq subtag target::subtag-function) target::fulltag-function)
                          (t target::fulltag-misc)))) )
          (t
           (+ ptr target::fulltag-cons)))))

(defun core-physsize (obj)
  ;; (assert (core-node-p obj))
  (if (core-consp obj)
    target::dnode-size
    (logandc2 (+ (uvheader-byte-size (core-uvheader obj)) target::node-size (1- target::dnode-size))
              (1- target::dnode-size))))

(defun core-object-sizes (obj)
  (let ((fulltag (logand obj target::fulltagmask)))
    (if (eq fulltag target::fulltag-cons)
      (values target::dnode-size target::dnode-size)
      (if (%i<= target::fulltag-misc fulltag)
        (let* ((header (core-uvheader obj))
               (logsize (uvheader-byte-size header))
               ;; total including header and alignment.
               (total (logandc2 (+ logsize target::node-size (1- target::dnode-size))
                                (1- target::dnode-size))))
          (values logsize total))))))

(defun link-range (ptr)
  (declare (fixnum ptr))
  (let* ((addr (logandc2 ptr target::fulltagmask))
         (header (core-q addr))
         (end addr))
    (declare (fixnum addr end))
    (if (uvheader-p header)
      (let ((subtag (%ilogand header target::fulltagmask)))
        (declare (fixnum subtag))
        (when (or (eq subtag target::fulltag-nodeheader-0)
                  (eq subtag target::fulltag-nodeheader-1))
          (incf addr target::node-size)
          (setq end (+ addr (ash (uvheader-size header) target::word-shift)))
          (when (eql (uvheader-typecode header) target::subtag-function)
            (incf addr (ash (core-l addr) target::word-shift)))))
      (setq end (+ addr target::dnode-size)))
    (values addr end)))

(defmethod cg-compute ((stage (eql :objects)) &aux (cg (current-core)))
  "Compute incoming pointer counts"
  (let* ((base (cg.heap-base cg))
         (high (cg.heap-end cg))
         (ndnodes (dnode base high))
         (ptrs (make-array ndnodes :element-type 'bit :initial-element 0))
         (head (make-array ndnodes :element-type 'bit :initial-element 0)))
    (declare (fixnum base ndnodes)
             (type simple-bit-vector ptrs head))
    (map-core-region base high
                     (lambda (obj)
                       (multiple-value-bind (start end) (link-range obj)
                         (loop for addr from start below end by target::node-size
                               as ptr = (core-q addr)
                               do (when (and (<= base ptr) (< ptr high) (core-node-p ptr))
                                    (let ((dnode (dnode base ptr)))
                                      (setf (aref head dnode) (aref ptrs dnode))
                                      (setf (aref ptrs dnode) 1)))))
                       ;; Mark that have an object even if there are no refs to it.
                       (let ((dnode (dnode base obj)))
                         (when (eql (aref ptrs dnode) 0)
                           (setf (aref head dnode) 1)))))
    ;; head = 0, ptrs = 0  -- not an object (internal dnode)
    ;; head = 0, ptrs = 1  -- single-entry object (exactly one pointer to it)
    ;; head = 1, ptrs = 0  -- root object (no pointers to it)
    ;; head = 1, ptrs = 1  -- multiple-entry object
    (setf (cg.head-p cg) head)
    (setf (cg.ptrs-p cg) ptrs)
    cg))

(defmethod cg-compute ((stage (eql :leaves)) &aux (cg (current-core)))
  "Mark leaf nodes (nodes with no outgoing pointers)"
  (let* ((base (cg.heap-base cg))
	 (high (cg.heap-end cg))
         (ptrs (cg.ptrs-p cg))
         (head (cg.head-p cg)))
    (declare (fixnum base high))
    (loop for dn upfrom 0
          for h bit across head
          for p bit across ptrs
	  do (unless (and (eql h 0) (eql p 0))
	       (unless (multiple-value-bind (start end) (link-range (addr base dn))
			 (loop for addr from start below end by target::node-size
			    as val = (core-q addr)
			    thereis (and (<= base val) (< val high) (core-node-p val))))
		 (setf (aref head dn) 0)
		 (setf (aref ptrs dn) 0))))
    ;; head = 0, ptrs = 0  -- not an object (internal dnode) or a leaf
    ;; head = 0, ptrs = 1  -- single-entry object (exactly one pointer to it), not leaf
    ;; head = 1, ptrs = 0  -- root object (no pointers to it), not leaf
    ;; head = 1, ptrs = 1  -- multiple-entry object, not leaf
    cg))

(defun collect-root-dnodes (cg)
  (let ((head (cg.head-p cg))
	(ptrs (cg.ptrs-p cg)))
    (loop for dn = (position 1 head) then (position 1 head :start (1+ dn)) while dn
          when (eql (aref ptrs dn) 0) collect dn)))

(defmethod cg-compute ((stage (eql :postorder)) &aux (cg (current-core)))
  (let* ((roots (collect-root-dnodes cg))
	 (head (cg.head-p cg))
	 (ptrs (cg.ptrs-p cg))
         (halo-roots ())
         (n (count 1 head))
         (base (cg.heap-base cg))
         (high (cg.heap-end cg))
         (ndnodes (dnode base high))
         (seen (make-array ndnodes :element-type 'bit :initial-element 0))
         (nodes (make-array n))
         (node-count 0))
    (assert (< ndnodes (ash 1 half-fixnum-shift)))
    (flet ((dfs (root-dn)
             (setf (aref seen root-dn) 1)
             (let ((path (multiple-value-bind (start end) (link-range (addr base root-dn))
			   (list (list* start end root-dn)))))
               (loop
		  (destructuring-bind (start end . pred-dn) (car path)
		    (incf (caar path) target::node-size)
		    (if (eql start end)
			(progn
			  (when (eql (aref head pred-dn) 1)
			    (setf (aref nodes node-count) pred-dn)
			    (incf node-count))
			  (setq path (cdr path))
			  (when (null path) (return)))
			(let ((next (core-q start)))
			  (when (and (<= base next) (< next high) (core-node-p next))
			    (let ((next-dn (dnode base next)))
			      (if (eql (aref ptrs next-dn) 0) ;; root or leaf -- ignore leaf
				  (when (eql (aref head next-dn) 1) ;; previously assumed halo root
				    #+debug (warn "REASSIGNING HALO ROOT ~s -> ~d" (assq next-dn halo-roots) node-count)
				    (assert (eql (aref seen next-dn) 1))
				    (setf (aref ptrs next-dn) 1)
				    ;; not actually a root after all. Shift the region containing
				    ;; nodes from previous handling of next-dn to the end, as if
				    ;; just walked it right now.
				    (destructuring-bind (start . end) (cdr (assq next-dn halo-roots))
				      (shift-vector-region nodes start end node-count))
				    (setq halo-roots (delete next-dn halo-roots :key 'car)))
				  ;; non-leaf non-root
				  (when (eq (aref seen next-dn) 0)
				    (setf (aref seen next-dn) 1)
				    (multiple-value-bind (start end) (link-range next)
				      (push (list* start end next-dn) path)))))))))))))
      (map nil #'dfs roots)
      ;; Map through "halo" roots
      (loop until (eql (length nodes) node-count)
	 as circ = (loop for nd = (position 1 head) then (position 1 head :start (1+ nd)) while nd
		      when (eql (aref seen nd) 0) return nd)
	 do (when (null circ)
	      ;; Must have some cycles consisting of just single-entry nodes, since we caught all other ones
	      (setq circ (loop for nd = (position 1 ptrs) then (position 1 ptrs :start (1+ nd)) while nd
			    when (eql (aref seen nd) 0) return nd))
              #+debug (progn (format t "~&Breaking a SINGLE-NODE CYCLE at ") (core-print (tagged-ptr (addr (cg.heap-base cg) circ))))
	      (setf (aref head circ) 1))
	 do (let ((start node-count))
              #+debug (progn (format t "~&Breaking out a HALO ROOT at ") (core-print (tagged-ptr (addr (cg.heap-base cg) circ))))
	      (dfs circ)
	      ;; This just makes it faster to find these in the dfs, it gets undone below.
	      (setf (aref ptrs circ) 0)
	      (push (list* circ start node-count) halo-roots))))
    (setq roots (nconc (mapcar (lambda (x &aux (dn (car x)))
                                 (setf (aref ptrs dn) 1)
                                 dn)
                               halo-roots)
                       roots))
    (setf (cg.roots cg) roots)
    (setf (cg.nodes cg) nodes)
    cg))

(defun shift-vector-region (vector start mid end)
  ;; move the interval from START to MID to after the interval from MID to END.
  (loop as n2 = (- end mid) as n1 = (- mid start)
        while (and (> n2 0) (> n1 0))
        do (if (< n1 n2)
             (loop for i from start below mid
                   do (rotatef (aref vector i) (aref vector (+ i n1)))
                   finally (setq start mid mid (+ mid n1)))
             (loop for i from mid below end
                   do (rotatef (aref vector i) (aref vector (- i n1)))
                   finally (setq start (+ start n2))))))


(declaim (inline make-rev-map))

(defun make-rev-map (arr &optional (fn 'identity))
  (let* ((n (length arr))
         (revarr (make-array n)))
    (loop for i from 0 below n as dn = (funcall fn (aref arr i))
          do (setf (aref revarr i) (+ (ash i half-fixnum-shift) dn))) ;; [pidx ,, dn]
    (sort revarr #'< :key (lambda (i.d) (logand i.d half-fixnum-mask)))))

(defun index-for-dnode (revnodes dn)
  (declare (type simple-vector revnodes) (fixnum dn)
           (optimize (speed 3) (safety 0)))
  (let ((low 0)
        (high (length revnodes)))
    (declare (fixnum low high) )
    (loop
      (when (eq low high) (return nil))
      (let* ((half (ash (%i+ high low) -1))
             (val (%ilogand2 (%svref revnodes half) half-fixnum-mask)))
        (declare (fixnum half val))
        (when (eq val dn)
          (return (the fixnum (ash (the fixnum (%svref revnodes half)) (- half-fixnum-shift)))))
        (if (< val dn)
          (setq low (1+ half))
          (setq high half))))))

(defmacro do-pointers ((child-var addr) &body body)
  (let ((path (gensym))
        (start (gensym))
        (end (gensym)))
    ` (macrolet ((descend-pointers (child)
                   `(multiple-value-bind (start end) (link-range ,child)
                      (push (cons start end) ,',path))))
        (let ((,path nil))
          (descend-pointers ,addr)
          (loop
            (destructuring-bind (,start . ,end) (car ,path)
              (incf (caar ,path) target::node-size)
              (if (eq ,start ,end)
                (unless (setq ,path (cdr ,path)) (return))
                (let ((,child-var (core-q ,start)))
                  (when (core-node-p ,child-var)
                    ,@body)))))))))

(defmethod cg-compute ((stage (eql :predecessors)) &aux (cg (current-core)))
  (let* ((base (cg.heap-base cg))
         (high (cg.heap-end cg))
	 (roots (cg.roots cg))
	 (head (cg.head-p cg))
	 (ptrs (cg.ptrs-p cg))
	 (nodes (cg.nodes cg)) ;; pidx -> dn
         (n (length nodes))
         (revnodes (make-rev-map nodes)) ;; dn -> pidx
         (predecessors (make-array (1+ n) :initial-element 0)))
    (flet ((record-predecessor (dn pred-i)
             (let* ((i (index-for-dnode revnodes dn))
                    (old (aref predecessors i)))
               (cond ((eql old 0)
                      (setf (aref predecessors i) (1+ pred-i)))
                     ((fixnump old)
                      (if (eql (logandc2 old half-fixnum-mask) 0)
                        (setf (aref predecessors i) (+ (ash old half-fixnum-shift) pred-i))
                        ;; could do more here, but most cases are covered by the 2-elt optimization
                        (setf (aref predecessors i)
                              (list pred-i
                                    (logand old half-fixnum-mask) (1- (ash old (- half-fixnum-shift)))))))
                     (t (setf (aref predecessors i) (cons pred-i old)))))))
      (loop for dn across nodes as dn-idx upfrom 0
            do (ASSERT (eql dn-idx (index-for-dnode revnodes dn)))
            do (do-pointers (next (addr base dn))
                 (when (and (<= base next) (< next high))
                   (let ((next-dn (dnode base next)))
                     (when (eq (aref ptrs next-dn) 1) ;; non-leaf
                       (if (eql (aref head next-dn) 1) ;; stop at head node
			 (record-predecessor next-dn dn-idx)
			 (descend-pointers next)))))))
      ;; Pretend there is one single root node which is the predecessor of all our roots.
      (loop for root-dn in roots do (record-predecessor root-dn n)))
    (setf (cg.revnodes cg) revnodes)
    (setf (cg.predecessors cg) predecessors)
    cg))

(defun predecessor-list (predecessors i)
  (let ((p (aref predecessors i)))
    (cond ((eql p 0) '())
          ((fixnump p)
           (let ((p1 (logand p half-fixnum-mask)))
             (if (eql p p1)
               (list (1- p1))
               (list p1 (1- (ash p (- half-fixnum-shift)))))))
          (t p))))

;;; Ok, now on to compute dominance
;; immediate dominators per Cooper, Harvey, Kennedy.
(defmethod cg-compute ((stage (eql :idoms)) &aux (cg (current-core)))
  (let* ((predecessors (cg.predecessors cg))
	 (root-idx (1- (length predecessors)))
         (doms (make-array (1+ root-idx) :initial-element nil)))
    (flet ((intersect (i1 i2)
             (when (and i1 i2)
               (loop until (eq i1 i2)
                     do (loop while (< i1 i2) do (setq i1 (aref doms i1)))
                     do (loop while (< i2 i1) do (setq i2 (aref doms i2)))))
             (or i1 i2))
           (preds (i)
             (predecessor-list predecessors i)))
      (declare (inline intersect preds))
      (setf (aref doms root-idx) root-idx)
      (loop for changed = 0
            do (loop for i from (1- root-idx) downto 0
                     do (let ((new-idom nil))
                          (loop for p in (preds i)
                                do (when (aref doms p) (setq new-idom (intersect p new-idom))))
                          (unless (eql new-idom (aref doms i))
                            (setf (aref doms i) new-idom)
                            (incf changed))))
            DO (progn #+debug (format t "~&Finished loop, changed=~d~%" changed))
            while (> changed 0)))
    (setf (cg.node-doms cg) doms)
    (setf (cg.idoms cg) (sort (delete root-idx (remove-duplicates doms)) #'<))
    (let ((nodes (cg.nodes cg)))
      (setf (cg.revidoms cg) (make-rev-map (cg.idoms cg) (lambda (ni) (aref nodes ni)))))
    cg))

(defmethod cg-compute ((stage (eql :idom-sizes)) &aux (cg (current-core)))
  (let* ((nodes (cg.nodes cg))
         (idom-nodes (cg.idoms cg))
         (idom-revnodes (cg.revidoms cg))
         (seen (make-array (length (cg.head-p cg)) :element-type 'bit :initial-element 0))
         (base (cg.heap-base cg))
         (high (cg.heap-end cg))
	 (nidoms (length idom-nodes))
	 (logsizes (make-array nidoms))
	 (physizes (make-array nidoms)))
    ;; Any object that's not an idom is only reachable by one idom,
    ;; so don't need to reinit SEEN bits between iterations.
    (setf (cg.idoms cg) idom-nodes)
    (loop for i from 0 below nidoms as idom = (aref idom-nodes i)
	 do (let* ((dn (aref nodes idom))
		   (addr (addr base dn))
		   (ptr (tagged-ptr addr)))
	      (multiple-value-bind (logsize physsize) (core-object-sizes ptr)
		(do-pointers (next addr)
		  (when (and (<= base next) (< next high))
		    (let ((next-dn (dnode base next)))
		      (unless (or (index-for-dnode idom-revnodes next-dn)
				  (eql (aref seen next-dn) 1))
			(setf (aref seen next-dn) 1)
			(multiple-value-bind (this-logsize this-physsize) (core-object-sizes next)
			  (incf logsize this-logsize)
			  (incf physsize this-physsize))
			(descend-pointers next)))))
		(setf (aref logsizes i) logsize)
		(setf (aref physizes i) physsize))))
    (setf (cg.logsizes cg) logsizes)
    (setf (cg.physizes cg) physizes)
    cg))

(defun idom-set-heap-range (area)
  (check-type area (member :tenured :dynamic))
  (multiple-value-bind (base end)
      (cond ((eq area :tenured)
             (let ((area-ptr (core-q (kernel-global-address 'tenured-area))))
               (values (core-q area-ptr target::area.low)
                       (core-q area-ptr target::area.active))))
            ((eq area :dynamic)
             (let* ((newest (core-q (core-q (kernel-global-address 'all-areas)) target::area.succ))
                    (oldest (core-q (kernel-global-address 'tenured-area))))
               (assert (loop for this = newest then older as older = (core-q this target::area.succ)
                             until (eql this oldest)
                             always (eql (core-q this target::area.low) (core-q older target::area.active))))
               (values (core-q oldest target::area.low)
                       (core-q newest target::area.active)))))
    (let ((cg (current-core)))
      (unless (and (eq base (cg.heap-base cg))
                   (eq end (cg.heap-end cg)))
        (setf (cg.stage cg) nil)
        (setf (cg.heap-base cg) base)
        (setf (cg.heap-end cg) end)))))
  

(defun report-idom-heap-utilization (type-infos &key unit sort threshold)
  (let ((data  (loop for type being the hash-key of type-infos using (hash-value info)
                     collect (cons (core-type-string type) info))))
    (report-heap-utilization data :unit unit :sort sort :stream *standard-output* :threshold threshold)))

(defun idom-heap-utilization (&key unit (sort :size) (threshold 0.01) (area :tenured))
  (idom-set-heap-range area)
  (cg-compute t)
  (loop with cg = (current-core)
        with nodes = (cg.nodes cg)
        with type-infos = (make-hash-table :test 'eql)
        with base = (cg.heap-base cg)
        for idx across (cg.idoms cg)
        for logsz across (cg.logsizes cg)
        for physz across (cg.physizes cg)
        as type = (core-object-type-key (tagged-ptr (addr base (aref nodes idx))))
        as info = (or (gethash type type-infos) (setf (gethash type type-infos) (list 0 0 0)))
        do (incf (car info))
        do (incf (cadr info) logsz)
        do (incf (caddr info) physz)
        finally (report-idom-heap-utilization type-infos :unit unit :sort sort :threshold threshold)))

(defun idom-frontier-heap-utilization (&key unit (sort :size) (threshold 0.01) (area :tenured) (test nil))
  ;; Compute the heap utilization WITHIN selected idom trees, aggregated.
  (idom-set-heap-range area)
  (cg-compute :idoms)
  (let* ((cg (current-core))
         (nodes (cg.nodes cg))
         (idom-nodes (cg.idoms cg))
         (idom-revnodes (cg.revidoms cg))
         (seen (make-array (length (cg.head-p cg)) :element-type 'bit :initial-element 0))
         (base (cg.heap-base cg))
         (high (cg.heap-end cg))
	 (nidoms (length idom-nodes))
         (type-infos (make-hash-table :test 'eql)))
    (flet ((record (ptr)
             (multiple-value-bind (logsize physsize) (core-object-sizes ptr)
               (let* ((type (core-object-type-key ptr))
                      (info (or (gethash type type-infos) (setf (gethash type type-infos) (list 0 0 0)))))
                 (incf (car info))
                 (incf (cadr info) logsize)
                 (incf (caddr info) physsize)))))
      (loop for i from 0 below nidoms as idom = (aref idom-nodes i)
            do (let* ((dn (aref nodes idom))
                      (addr (addr base dn))
                      (ptr (tagged-ptr addr)))
                 (when (or (null test) (funcall test ptr))
                   ;; Ok, idom of interest.  Walk its subgraph
                   (record ptr)
                   (do-pointers (next addr)
                     (when (and (<= base next) (< next high))
                       (let ((next-dn (dnode base next)))
                         (unless (or (index-for-dnode idom-revnodes next-dn)
                                     (eql (aref seen next-dn) 1))
                           (setf (aref seen next-dn) 1)
                           (record next)
                           (descend-pointers next)))))))
            finally (report-idom-heap-utilization type-infos :unit unit :sort sort :threshold threshold)))))

)
