;;; -*- Mode:LISP; Package:(NC LISP); Base:10; Readtable:CL -*-

;;; Copyright (c) 1985 Yale University
;;;     Authors: N Adams, R Kelsey, D Kranz, J Philbin, J Rees.
;;; This material was developed by the T Project at the Yale University Computer 
;;; Science Department.  Permission to copy this software, to redistribute it, 
;;; and to use it for any purpose is granted, subject to the following restric-
;;; tions and understandings.
;;; 1. Any copy made of this software must include this copyright notice in full.
;;; 2. Users of this software agree to make their best efforts (a) to return
;;;    to the T Project at Yale any improvements or extensions that they make,
;;;    so that these may be included in future releases; and (b) to inform
;;;    the T Project of noteworthy uses of this software.
;;; 3. All materials developed as a consequence of the use of this software
;;;    shall duly acknowledge such use, in accordance with the usual standards
;;;    of acknowledging credit in academic research.
;;; 4. Yale has made no warrantee or representation that the operation of
;;;    this software will be error-free, and Yale is under no obligation to
;;;    provide any services, by way of maintenance, update, or otherwise.
;;; 5. In conjunction with products arising from the use of this material,
;;;    there shall be no use of the name of the Yale University nor of any
;;;    adaptation thereof in any advertising, promotional, or sales literature
;;;    without prior written consent from Yale in each case.
;;;

;;; Copyright (c) 1985 David Kranz


;;; ALLOCATE-CALL The "top".  Dispatch on the type of call.

(defun allocate-call (node)
;  (if *call-break?* (breakpoint (pp-cps node)))
  (let ((cont (call-hoisted-cont node)))
    (if cont
;don't make any stack closure
;don't push any return address
;this is not the place for the open
;it must be before the arg calculations
;(way up in the cps tree)
;; so we stuff in a call to primop/open at node conversion time
;; (but what about real stack closures???)
;	;; make-stack-closure pushes the return address here
;        ;; this will not be called if the continuation was
;        ;; not hoisted  (hoist-continuation is called
;        ;; by live-analyze-lambda only for strategy/stack nodes)
;        (make-stack-closure node cont)))
;; but we must queue the continuation...
	  (lambda-queue cont)))
  (let ((proc (call-proc node)))
    (cond ((primop-node? proc)
           (ass-comment "~s" (pp-cps node))
           (allocate-primop-call node))
          ((lambda-node? proc)
           (generate-let node))
          (t
	   (let ((proc (variable-known (leaf-value proc))))
	     (cond (proc
		    (ass-comment "Call known procedure ~s" 
				 (cons (lambda-name proc) (cdr (pp-cps node))))
		    (cond ((zerop (call-exits node))
			   (allocate-known-return node proc))
			  ((= (call-exits node) 1)
			   (allocate-known-call node proc))
			  (t (error "Call exits not 0 or 1 in ALLOCATE-CALL"))))
		   ((zerop (call-exits node))
		    (ass-comment "Return from procedure ~s" (pp-cps node))
		    (allocate-return node))
		   ((= (call-exits node) 1)
		    (ass-comment "Call unknown procedure ~s" (pp-cps node))
		    (allocate-general-call node))
		   (t
		    (bug "too many exits - ~s" node))))))))
  
(defun allocate-known-call (node proc)
  (if (not (or (lambda-rest-var proc)
               (= (length (lambda-variables proc))
		  (length (call-args node)))))
      (warning "Wrong number of arguments in call ~s" (pp-cps node)))
  (ecase (lambda-strategy proc)
    (STRATEGY/LABEL (allocate-label-call node proc))
    (STRATEGY/HEAP (allocate-known-heap-call node proc))
    (STRATEGY/EZCLOSE (allocate-ezclose-call node proc))
    ((STRATEGY/VFRAME STRATEGY/EZCLOSE) (allocate-vframe-call node proc)))
  (if (<= (lambda-trace proc) (lambda-trace (node-parent node)))
      (generate-avoid-jump proc)
      (generate-jump proc)))
      

#||||
(define (allocate-known-heap-call node proc)
  (let ((cont ((call-arg 1) node)))
    (parallel-assign-general node)
    (if (leaf-node? cont) (fetch-continuation node cont)))
  (clear-slots)                            
  (generate-move (reg-offset P -2) TP)
  (if (n-ary? proc) (generate-move (length (call-args node)) NARGS)))

|||#

;;; ALLOCATE-LABEL-CALL If this is the path that arrives first, go to
;;; determine where the free variables of the join are to be kept.
;;; Make the state of the registers the way the join point wants it.
;;; Parallel assign and jump to the label.
                         
(defun allocate-label-call (node proc)
  (let ((join (get-or-set-join-state node proc))
        (cont (call-arg-n 1 node)))
    (parallel-assign node
                     (cdr (call-args node))
                     (join-point-arg-specs join)
                     nil
                     (join-point-global-registers join))
    (if (leaf-node? cont) (fetch-continuation node cont))
    (let ((contour (and (join-point-contour-needed? join)
			(join-point-contour join))))
      (if contour
	  (let ((b (variable-binder contour)))
	    (if (and (closure-cit-offset (environment-closure (lambda-env b)))
		     (not (eq *lambda* b)))
		(generate-move (reg-offset P -2) TP))))))
;  (clear-slots)
  )
                       

(defun allocate-vframe-call (node proc)
  (let ((cont (call-arg-n 1 node)))
    (cond ((lambda-node? cont)     
           (parallel-assign-vframe node proc))
          (t              
           (parallel-assign-vframe node proc)          
           (fetch-continuation node cont))))
  (clear-slots)                            
  (if (n-ary? proc) (generate-move (length (call-args node)) NARGS)))

                                                          
(defun allocate-ezclose-call (node proc)
  (let ((cont (call-arg-n 1 node)))
    (cond ((lambda-node? cont) (bug "non-variable continuation to ezclose call"))    
          (t
           (parallel-assign-known node)          
           (fetch-continuation node cont))))
  (clear-slots)                            
  (if (n-ary? proc) (generate-move (length (call-args node)) NARGS)))


#|||                         
(define (allocate-known-return node proc)
  (select (lambda-strategy proc)
    ((strategy/label) (allocate-label-return node proc))
    (else 
     (parallel-assign-return node)      
     (fetch-continuation node (call-proc node))
     (clear-slots)
     (generate-jump proc))))
      



(define (allocate-label-return node proc)
  (let ((join (get-or-set-join-state node proc)))
    (cond ((n-ary? proc)
           (really-parallel-assign node '() '() (join-point-global-registers join)))
          (else
           (parallel-assign node
                            (call-args node)
                            (join-point-arg-specs join)
                            nil
                            (join-point-global-registers join)))))
  (fetch-continuation node (call-proc node))
  (clear-slots)
  (generate-jump proc))
|||#

(defun allocate-conditional-continuation (node proc-leaf)
  (let ((proc (variable-known (leaf-value proc-leaf))))
    (case (lambda-strategy proc)
      (STRATEGY/STACK)
      (t
       (let ((join (get-or-set-join-state node proc)))
         (parallel-assign node
                          '()
                          (join-point-arg-specs join)
                          nil
                        (join-point-global-registers join)))))
   (fetch-continuation node proc-leaf)
;   (clear-slots)     why??
   (generate-jump proc)))


;; much changed
;;; parallel-assign will lock the regs
;;; so pop-o-regs should unlock
(defun allocate-general-call (node)
  (let* ((cont (call-arg-n 1 node))
	 (tail-p (not (lambda-node? cont))))
    (parallel-assign-general node)
    (if tail-p (fetch-continuation node cont))
    (generate-general-call (call-proc node)
			   tail-p
			 (- (length (call-args node)) 2))  ;flush cont also flush P
    (pop-o-regs)))

                                   
(defun allocate-return (node)
  (parallel-assign-return node)      
  (fetch-continuation node (call-proc node))
;  (clear-slots)     why???
  (generate-return (length (call-args node))))
                         


;;; does anyone call this except to assign to call regs?
(defun parallel-assign-general (node)
  (parallel-assign node (cdr (call-args node))   ;don't need to pass proc in P (cons (call-proc node) (cdr (call-args node)))
                        *open-registers*         ; was nil (P + arg regs)
			t '()))
                                 
(defun parallel-assign-known (node)
  (parallel-assign node (cdr (call-args node)) nil nil '()))
                                            

(defun parallel-assign-vframe (node proc)
  (if (not (lambda-env (node-parent (node-parent proc))))
      (parallel-assign node (cdr (call-args node)) nil nil '())   
      (parallel-assign node (cdr (call-args node)) nil nil 
               (list (cons P (lambda-self-var (node-parent (node-parent proc))))))))

(defun parallel-assign-return (node)
  (let ((nargs (length (call-args node))))                        ;added   -efh
    (parallel-assign node (call-args node) (if (= nargs 1)        ;nil
					       *return-dest*
					     *return-registers*)
		     nil '())))


;;; PARALLEL-ASSIGN Cons a closure if necessary.  It is known that there
;;; will only be one that needs to be consed.

(defun parallel-assign (node args p-list proc? solve-list)
  (let* ((pos-list (if p-list p-list (reg-positions (length args) proc?)))
         (closure (get-closure args)))
    (if closure
	(make-heap-closure node closure))
    (really-parallel-assign node args pos-list solve-list)))

(defun get-closure (args)
  (some #'(lambda (arg)               
	    (and (lambda-node? arg)
		 (not (eq (environment-closure (lambda-env arg)) *unit*))
		 (environment-closure (lambda-env arg))))
       args))


;;; do-now - register or temp pairs (source . target)
;;; trivial - immediate or lambda
;;; do-later - environment
;;; See implementor for this stuff. Hairy!!
                       
(defstruct arg-mover
  from
  from-rep
  to
  to-rep)                                  

(defun mover (from from-rep to to-rep)
  (let ((a (make-arg-mover)))
    (setf (arg-mover-from a) from)
    (setf (arg-mover-from-rep a) from-rep)
    (setf (arg-mover-to a) to)
    (setf (arg-mover-to-rep a) to-rep)
    a))

(defun really-parallel-assign (node args pos-list solve-list)
  (multiple-value-bind (do-now trivial do-later) (sort-by-difficulty args pos-list)
;    (format t "~%do-now: ~s" do-now)
;    (format t "~%trivial: ~s" trivial)
;    (format t "~%do-later: ~s" do-later)    
    (multiple-value-bind (do-now do-later) (add-on-free-list do-now do-later solve-list)
;      (format t "~%------------------------")
;      (format t "~%do-now: ~s" do-now)    
;      (format t "~%do-later: ~s" do-later)
      (solve node do-now do-later)                                    
      (lock AN)                     ; contains closures
      (dolist (pair trivial)
	(if (lambda-node? (car pair))
	    (do-trivial-lambda node (car pair) (cdr pair))))
      (unlock AN)
      (dolist (pair do-later)
	(do-indirect node pair))
      (dolist (pair trivial)
	(if (not (lambda-node? (car pair)))
	    (do-immediate node (car pair) (cdr pair)))))))
                                                      

(defun add-on-free-list (do-now do-later solve-list)
  (dolist (pair solve-list)
    (let ((reg (or (register-loc (cdr pair))
		   (temp-loc (cdr pair)))))
      (cond (reg
	     (push (mover reg (variable-rep (cdr pair)) 
			  (car pair) (variable-rep (cdr pair)))
		   do-now))
	    (t
	     (setq do-later
		   (if (= (car pair) P)
		       (nconc do-later (list (cons (cdr pair) P)))
                     (cons (cons (cdr pair) (car pair))
                           do-later)))))))
  (values do-now do-later))


(defun sort-by-difficulty (args pos-list)
  (let ((do-now '()) (trivial '()) (do-later '()) (pos-list pos-list))
    (dolist (arg args)
      (cond ((lambda-node? arg) 
	     (let ((l arg))
	       (cond ((eq (environment-closure (lambda-env l)) *unit*)
		      (push (cons l (car pos-list)) do-later))
		     (t
		      (push (cons l (car pos-list)) trivial)))))
	    ((addressable? (leaf-value arg))
	     (push (cons arg (car pos-list)) trivial))
	    (t
	     (let* ((val (leaf-value arg))
		    (v (and (variable-p val)
			    (variable-known val)))
		    (value (if v (lambda-self-var v) val))
		    (reg (or (register-loc value) (temp-loc value))))
	       (cond (reg
		      (push (mover reg (variable-rep value)
				   (caar pos-list) (cdar pos-list))
			    do-now))
		     (t
		      (setq do-later
			    (if (= (caar pos-list) P)
				(nconc do-later (list (cons value (car pos-list))))
                              (cons (cons value (car pos-list)) do-later))))))))
      (pop pos-list))
    (values do-now trivial do-later)))


(defun do-immediate (call-node node reg-rep)
  (generate-move (value-with-rep (leaf-value node) (cdr reg-rep)) 
                 (car reg-rep)))


(defun do-indirect (node pair) 
  (cond ((and (node-p (car pair)) (lambda-node? (car pair)))
         (lambda-queue (car pair))
         (generate-move (lookup node (car pair) nil) (cadr pair))
         (let ((rn (reg-node (cadr pair))))
	   (kill rn))
         (lock (cadr pair)))
        ((atom (cdr pair))                               ; free var of label
         (really-rep-convert node
                             (access-value node (car pair)) 
                             (variable-rep (car pair))
                             (cdr pair)
                             (variable-rep (car pair)))
         (let ((rn (reg-node (cdr pair)))) (kill rn))
         (mark (car pair) (cdr pair))
         (lock (cdr pair)))
        (t
         (let ((to-rep (cddr pair))
               (to-reg (cadr pair)))
           (really-rep-convert node 
                               (access-value node (car pair))
                               (if (variable-p (car pair))
                                   (variable-rep (car pair)) 
                                   'rep/pointer) 
                               to-reg 
                               to-rep)
           (let ((var (reg-node to-reg)))
	     (when var
	       (setf (register-loc var) nil)
	       (setf (reg-node to-reg) nil)))
           (kill (car pair))
           (mark (car pair) to-reg)
           (lock to-reg)))))      
                                         

(defun solve (node movers do-later)
;  (format t "~%Solve: node:     ~s" node)
;  (format t "~%       movers:   ~s" movers)
;  (format t "~%       do-later: ~s" do-later)
  (let* ((contour (lambda-self-var *lambda*))
	 (vals (mapcar #'(lambda (mover)
			   (reg-node (arg-mover-to mover)))
		       movers))
	 (mover (and do-later 
		     (find contour movers
			   :test #'eq
			   :key #'(lambda (mover) 
				    (reg-node (arg-mover-to mover)))))))
    (cond (mover
	   (if (not (eq (arg-mover-from mover) (arg-mover-to mover)))
	       (free-register node (register-loc contour)))
	   (dolist (val vals)
	     (unless (eq val contour) (kill val))))
          (t
           (mapc #'kill vals)))
    (dolist (mover movers)
      (lock (arg-mover-to mover)))
    (multiple-value-bind (movers self-movers) (separate-self-movers movers)
      (do ((movers movers)
	   (targets (mapcar #'arg-mover-to movers))
	   (temp nil))
	  ((null movers))
	(dolist (candidate targets (let ((mover (car movers)))
				     (generate-move (arg-mover-to mover) 
						    (reg-offset TASK 
								(if (eq (arg-mover-to-rep mover) 'rep/pointer)
								    task/extra-pointer
								  task/extra-scratch)))
				     (really-rep-convert node
							 (arg-mover-from mover)
							 (arg-mover-from-rep mover)
							 (arg-mover-to mover)
							 (arg-mover-to-rep mover))
				     (setq movers (cdr movers))
				     (setq targets (delete (arg-mover-to mover) targets :test #'eq))
				     (setq temp (arg-mover-to mover))))
	  (cond ((not (member candidate movers :test #'from-reg-eq?))
		 (let ((mover (car (member candidate movers :test #'to-reg-eq?))))
		   (really-rep-convert node
				       (cond ((eq (arg-mover-from mover) temp)
					      (if (eq (arg-mover-to-rep mover) 'rep/pointer)
						  (reg-offset TASK task/extra-pointer)
						(reg-offset TASK task/extra-scratch)))
					     (t
					      (arg-mover-from mover)))
				       (arg-mover-from-rep mover)
				       (arg-mover-to mover)
				       (arg-mover-to-rep mover))
		   (setq movers (delete mover movers :test #'eq))
		   (setq targets (delete (arg-mover-to mover) targets :test #'eq))
		   (return t))))))
      (dolist (mover self-movers)
	(really-rep-convert node (arg-mover-from mover)
			    (arg-mover-from-rep mover)
			    (arg-mover-to mover)
			    (arg-mover-to-rep mover))))))

(defun separate-self-movers (movers)
  (let ((m '()) (s '()))
    (dolist (mover movers)
      (if (eq (arg-mover-from mover) (arg-mover-to mover))
	  (push mover s)
	(push mover m)))
    (values m s)))

(defun to-reg-eq? (reg mover) (= (arg-mover-to mover) reg))
(defun from-reg-eq? (reg mover) (= (arg-mover-from mover) reg))

(defun access/make-closure (node lam)
  (let* ((closure (environment-closure (lambda-env lam))))
    (cond ((eq closure *unit*)
           (lambda-queue lam)
           (lookup node lam nil))
          (t
           (make-heap-closure node closure)
           nil))))


;;; MAKE-STACK-CLOSURE Push a continuation on the stack.  For now there are no
;;; scratch values.  When there are we will need to push zeroes for all the
;;; scratch slots and fill them in after pushing the template.  This is because
;;; the GC assumes that anything on top of the stack until the first template
;;; is a valid pointer.


(defun make-stack-closure (node cont)
  (lambda-queue cont)
  (let* ((closure (environment-closure (lambda-env cont)))
         (a-list (cdr (closure-env closure))))
    ;;; push zeros for scratch slots
    (dotimes (i (closure-scratch closure))
      (generate-push (machine-num 0)))

    ;; push pointer vars in environment of continuation
    ;; this may not be necc if they are in a regs
    ;; they will not be clobbered by the call -efh
    (dolist (pair (nthcdr (closure-scratch closure) (reverse a-list)))
      (generate-push (access-value node (car pair))))

    ;; this is where the return address gets pushed
    (generate-open (template cont)) ;efh
;    (generate-push-address (template cont))

    ;; move variables which are not pointers onto the stack
    (dolist (pair (nthcdr (closure-pointer closure) a-list))
      (really-rep-convert node
			  (access-value node (car pair))
			  (variable-rep (car pair))
			  (reg-offset SP (cdr pair))
			  (variable-rep (car pair))))))
  
(defun make-vframe-closure (node l closure ez?)
  (mapc #'lambda-queue (closure-vframe-lambdas closure))
  (let ((a-list (cdr (closure-env closure))))
    (dotimes (i (closure-scratch closure))
      (generate-push (machine-num 0)))
    (dolist (pair (nthcdr (closure-scratch closure) (reverse a-list)))
      (generate-push (access-value node (car pair))))
    (generate-push-address (template l))
    (dolist (pair (nthcdr (closure-pointer closure) a-list))
      (really-rep-convert node
			  (access-value node (car pair))
			  (variable-rep (car pair))
			  (reg-offset SP (cdr pair))
			  (variable-rep (car pair))))))

                                                            
  

