;;; -*- 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

(defvar *assembly-comments?* nil)
(defvar *lambda-queue* '())         ;; queue of lambda bodies to process
(defvar *stack-pos* 0)              ;; distance of stack-pointer from "frame"
(defvar *max-temp* 0)               ;; maximum number of temporaries used
(defvar *lambda* nil)               ;; the procedure being compiled
(defvar *call-break?* nil)           
;(defvar *registers* nil)

(defmacro generate-init (&body body)
  `(let ((*unit-literals* '())
         (*unit-variables* '())
         (*unit-closures* '())
         (*unit-templates* '())
         (*unit* nil) 
         (*registers* (vector-fill (make-vector *no-of-registers*) nil))
         (*lambda* nil))
     (rr)
     ,@body))


(defun generate (top-node)
  (generate-code-top (car (call-args (lambda-body top-node)))))


(defun rr ()
  (setq *lambda-queue* '()) 
  (setq *locations* (make-table 'locations))
  (fill *registers* nil)
  *repl-wont-print*)


(defmacro ass-comment (string &rest rest)
  `(if *assembly-comments?*
       (emit-comment (format nil ,string ,@rest))))                      

(defstruct vframe
  lambda
  return-size)
  

;;; GENERATE-CODE Initialize lambda queue. Go.

(defun generate-code-top (top)
  (setq *lambda-queue* '())
  (generate-code top))

(defun generate-code (node)
  (format t "~&Generating: ~&") (pp-cps node)
  (if *debug-flag* (format t "~%~d ~s~%" (object-hash node) (lambda-name node)))
  (setq *stack-pos* 0)
  (allocate-registers node)                                          
  (process-lambda-queue))

(defun generate-vframe (vframe)
  (let ((l-node (vframe-lambda vframe))
        (offset (vframe-return-size vframe)))
    (emit-template l-node l-node)
    (adjust-stack-pointer offset)
    (generate-return-without-nargs))
  (process-lambda-queue))

#|||||||                                              
(defun generate-code-for-object (node)
  (if *debug-flag* (format t "~%~d ~s~%" (object-hash node) (lambda-name node)))
  (setq *stack-pos* 0)            
  (setq *lambda* node)
  (let ((object-proc (call-arg-n 2 (lambda-body node))))
    (emit-template node object-proc)
    (if (closure-env (environment-closure (lambda-env node)))
        (mark (lambda-self-var node) P))
    (mark-vars-in-regs (cdr (lambda-variables object-proc)))
    (if (n-ary? object-proc)
        (n-ary-setup object-proc))
    (allocate-call (lambda-body object-proc))
    (emit-tag object-proc))
  (generate-handler node)
  (process-lambda-queue))
|||||#
                                              


(defun lambda-queue (node)
  (push node *lambda-queue*))

(defun process-lambda-queue ()
  (if *lambda-queue*
      (let ((thing (pop *lambda-queue*)))
        (cond ((object-lambda? thing)
                (generate-code-for-object thing))
	      ((lambda-node? thing)
                (generate-code thing))
	      ((vframe-p thing)
                (generate-vframe thing))
	      ((lap-template-struct? thing)
                (process-lap-template thing))
	      (t (bug "Something odd in lambda queue: ~s" thing))))))

;;; ALLOCATE-REGISTERS Sets *lambda* to be the lambda-node representing the
;;; environment the node argument is compiled in.  Generate code for the body.

(defun allocate-registers (node)
    (case (lambda-strategy node)
      ((STRATEGY/STACK STRATEGY/HEAP)
       (setq *lambda* node)
       (emit-template node node))
      ((STRATEGY/VFRAME STRATEGY/EZCLOSE)
       (setq *lambda* (node-parent (node-parent node)))
       (emit-tag node))
      (t
       (setq *lambda* (variable-binder (join-point-contour (lambda-env node))))
       (emit-tag node)))
    (initialize-registers node)
    (if (n-ary? node)
        (n-ary-setup node))
    (allocate-call (lambda-body node)))
    
;;; INITIALIZE-REGISTERS Here we mark the arguments of a closure as being in
;;; the argument registers.  For a heaped lambda there is also the environment
;;; in the P register.  For a join point the state is initialized.

(zl:defsubst method-lambda (node)
  (let ((p (node-parent node)))
    (if (primop-ref? (call-proc p) primop/proc+handler)
        (node-parent p)
        nil)))
   

(defun initialize-registers (node)
  (ecase (lambda-strategy node)
    (STRATEGY/HEAP                                       
     (ass-comment "Procedure ~s (lambda ~s ...)" 
             (lambda-name node)
             (nconc (mapcar #'variable-unique-name (lambda-variables node))
		    (let ((v (lambda-rest-var node)))
		      (if v
			  (variable-unique-name v)
			  '()))))
     (if (closure-env (environment-closure (lambda-env node)))
         (let ((obj (method-lambda node)))
	   (cond (obj
		  (mark (lambda-self-var obj) P)
		  (setq *lambda* obj))
		 (t (mark (lambda-self-var node) P)))))
     (mark-vars-in-regs (cdr (lambda-variables node))))
    (STRATEGY/STACK
     (ass-comment "Continuation ~s (lambda ~s ...)"
             (lambda-name node)
             (nconc (mapcar #'variable-unique-name (lambda-variables node))
		    (let ((v (lambda-rest-var node)))
		      (if v (variable-unique-name v)
			'()))))
     ;;why does it think that the continuation arg(s?) are in the arg regs?
     ;;is this just to mark the returned value of the unknown call this is a continuation
     ;;of as being in the first arg reg? is this always a continuation of a call that returns
     ;; in a1? and what about multiple values?
;     (mark-vars-in-regs (lambda-variables node))
     ;; if this is only a continuation to a call then open will save the dest that the
     ;;continuation wants so the continuation will get its arg (what about multiple values?) where
     ;; it wants it
     ;; also hack ignored value
     (let ((vars (lambda-variables node)))
       (when (and vars
		  (variable-refs (car vars)))
	 (let ((wants (continuation-wants node)))
	   (mark (car vars) (get-register 'pointer node (if (register? wants) wants '*))))))
     )
    (STRATEGY/VFRAME
     (ass-comment "Procedure ~s (lambda ~s ...)" 
             (lambda-name node)
             (mapcar #'variable-unique-name (lambda-variables node)))
     (mark (lambda-self-var *lambda*) P)
     (mark-vars-in-regs (cdr (lambda-variables node))))
    (STRATEGY/EZCLOSE
     (ass-comment "Procedure ~s (lambda ~s ...)" 
             (lambda-name node)
             (mapcar #'variable-unique-name (lambda-variables node)))
     (mark-vars-in-regs (cdr (lambda-variables node))))
    (STRATEGY/LABEL
     (ass-comment "Label procedure ~s (lambda ~s ...)" 
             (lambda-name node)
             (mapcar #'variable-unique-name (lambda-variables node)))
     (cond ((join-point-contour-needed? (lambda-env node))
            (let ((contour (join-point-contour (lambda-env node))))
              (mark contour P)
              (if (closure-cit-offset (environment-closure 
					(lambda-env (variable-binder contour))))
                  (generate-move (reg-offset P -2) TP)))))
     (mapc #'(lambda (var arg-spec)
	       (mark var (car arg-spec)))
	   (if (continuation? node)
	       (lambda-variables node)
	     (cdr (lambda-variables node)))
          (join-point-arg-specs (lambda-env node)))
     (dolist (pair (join-point-global-registers (lambda-env node)))
	       (mark (cdr pair) (car pair))))))


;;; Mark vars as being in argument registers

(defun mark-vars-in-regs (vars)
  (do ((vars vars (cdr vars))
       (reg A0 (+ reg 1)))
      ((or (>= reg AN) (null vars))
       (do ((vars vars (cdr vars))
            (reg (+ reg (1+ *argument-registers*)) (1+ reg)))
           ((null vars))
         (cond ((and (car vars) (variable-refs (car vars)))
                (mark-temp (car vars) reg)))))
    (cond ((and (car vars) (variable-refs (car vars)))
           (mark (car vars) reg)))))
     
;;; A closure is n-ary if it has a non null rest arg.

(defun n-ary? (closure) (lambda-rest-var closure))

(defun n-ary-setup (node)
  (cond ((used? (lambda-rest-var node))
         (generate-nary-setup node
                              (if (eq (lambda-strategy node) strategy/stack)
                                  (length (lambda-variables node))
                                  (length (cdr (lambda-variables node))))))))



(defun allocate-primop-call (node)
  (let* ((prim (primop-value (call-proc node))))
    (cond ((primop.conditional? prim)
           (allocate-conditional-primop node prim))
          ((and (eq prim primop/contents-location)
                (not (eq (leaf-value (call-arg-n 2 node)) primop/cell-value)))
           (allocate-location node prim))
          ((primop.special? prim)
           (primop.generate prim node))
          (t
           (really-allocate-primop-call node prim)))))
                                       

;;; ALLOCATE-CONDITIONAL-PRIMOP When we come to a split we save the state of
;;; the world and traverse one arm, then restore the state and traverse the
;;; other.

(defun allocate-conditional-primop (node prim)
  (primop.generate prim node)      
  (let ((then (then-cont node))
        (else (else-cont node)))
  (multiple-value-bind (then else)
;; emit-jump is for now just emiting a jump
;; it doesn't save both tags for possible 
;; reordering of blocks
;      (cond ((or (leaf-node? then) 
;		 (leaf-node? else) 
;		 (< (lambda-trace then)
;		    (lambda-trace else)))
;	     (values then else))
;	    (t
;	     (values else then)))

      (values then else)  ;efh

    (let ((*registers* (copy-registers))
	  (*stack-pos* *stack-pos*)
          (*lambda* *lambda*))
      (emit-tag then)
      (cond ((lambda-node? then)
;             (dolist (n (cddr (call-args node)))
;                     (kill-if-dead n then))
             (allocate-call (lambda-body then)))
            (t
             (allocate-conditional-continuation node then)))
      (return-registers))
    (restore-slots)
    (emit-tag else)
    (cond ((lambda-node? else)
;           (dolist (n (cddr (call-args node)))
;	     (kill-if-dead n else))
           (allocate-call (lambda-body else)))
          (t
           (allocate-conditional-continuation node else))))))
                                        
;; We must decide whether to try to delay dereferencing the location.
;; We do this if the value is used just once and in the next frob and
;; is an operand to a primop.




(defun allocate-location (node prim)
  (let ((c (cont node)))
    (if (and (lambda-node? c)
             (let ((refs (variable-refs (car (lambda-variables c)))))
               (and refs
                    (null (cdr refs))
                    (let ((p (node-parent (node-parent (car refs)))))
                      (or (and (eq p c) 
                               (let ((proc (call-proc (lambda-body c))))
                                 (and (primop-node? proc)
                                      (not (eq (primop-value proc)
                                            primop/make-cell)))))
                          (and (eq (node-parent (node-parent p)) c)     
                               (let ((proc (call-proc (node-parent (car refs)))))
                                 (and (primop-node? proc)
                                      (not (eq (primop-value proc)
                                            primop/contents-location))))                                                        
                               (let ((p (call-proc (lambda-body c))))
                                 (and (primop-node? p)
                                      (eq (primop-value p) 
                                           primop/contents-location))))))
                    (reps-compatable? 
                      (primop.rep-wants (leaf-value (call-arg-n 2 node)))
                      (variable-rep (car (lambda-variables c)))))))
        (generate-location-access node)
        (really-allocate-primop-call node prim))))


(defun reps-compatable? (accessor-rep use-rep)
  (and (eq (rep-size accessor-rep) (rep-size use-rep))
       (not (rep-converter accessor-rep use-rep))))

(defun really-allocate-primop-call (node prim)
  (let ((c (cont node)))
    (cond ((lambda-node? c)
           (let ((cont (call-hoisted-cont node)))
	     (when cont
	       (mapc #'(lambda (a-pair)
			 (or (member (car a-pair) (lambda-live c) :test #'eq)
			     (zerop (variable-number (car a-pair)))
			     (some #'(lambda (node)
				       (and (leaf-node? node)
					    (eq (leaf-value node) (car a-pair ))))
				   (cdr (call-args node)))
			     (kill (car a-pair))))
		     (closure-env (environment-closure (lambda-env cont))))))
           (primop.generate prim node)
;;why?
;           (dolist (node (cdr (call-args node)))
;	     (kill-if-dead node c))
           (allocate-call (lambda-body c)))
          (t                            
           (primop.generate prim node)
           (dolist (node (cdr (call-args node)))
	     (if (leaf-node? node) (kill (leaf-value node))))
           (fetch-continuation node c)
           (clear-slots)
           (generate-return 1)))))
         

;;; ACCESS-VALUE This is the primary routine to get addressability to values.
;;; Just a giant case statement.

(defun access-value (node value)               
 (let* ((v (and (variable-p value) (variable-known value)))
	(value (if v (lambda-self-var v) value))
	(spec (register-loc value)))
   (cond (spec
	  (cond ((integerp spec))
		(t
		 (cond ((consp (car spec))
			(unlock (caar spec))
			(let ((var (reg-node (caar spec))))
			  (if var (kill-if-dying var node)))
			(unlock (cdar spec)))
		       (t
			(unlock (car spec))
			(let ((var (reg-node (car spec))))
			  (if var (kill-if-dying var node)))))
		 (setf (register-loc value) nil)))
              spec)
        ((temp-loc value))
        ((variable-p value)
         (let ((binder (variable-binder value)))
           (cond ((not binder)
                  (lookup node value nil))
                 ((and (zerop (variable-number value)) 
                       (assoc binder (closure-env *unit*) :test #'eq))
                  (lookup node binder nil))
                 (t
                  (lookup node value binder)))))
        ((primop? value)
         (if (eq value primop/undefined)
             (machine-num 0)
             (lookup node value nil)))
;; ?? nil and t are addressable
;        ((eq value t)
;         (machine-num header/true))
;        ((eq value nil)
;          nil-reg)
        ((addressable? value)
         (lit value))
        (t
         (lookup node value nil)))))

;;; LOOKUP If the value is a known procedure, if it is in the unit we get it
;;; from there, otherwise we get the variable which the known procedure is
;;; bound to.

(defun lookup (node value lambda-bound?)
  (ecase (lambda-strategy *lambda*)
    ((strategy/stack strategy/ezclose)
     (fetch-from-stack node value lambda-bound?))
    ((strategy/vframe)
     (let ((contour (lambda-self-var *lambda*)))
       (->register 'pointer node contour '*)
       (fetch-from-vframe node contour value lambda-bound?)))
    ((strategy/heap)
     (let ((contour (lambda-self-var *lambda*)))
       (->register 'pointer node contour '*)
       (fetch-from-heap node contour value lambda-bound?)))))




                                
;;; ACCESS-FROM-UNIT Get from unit when there is a closure-internal-template.
;;; If we have one, just offset from template-pointer. If we are internal to
;;; a closure which has one, get it first and then offset into unit.

#|||
(define (access-from-unit node contour var)
  (let ((closure (environment-closure (lambda-env (variable-binder contour)))))
    (cond ((closure-cit-offset closure)
           => (lambda (current-offset) 
                (let ((cl? (or (and (node? var) (lambda-node? var))
                           (closure? var)))
                      (disp (fx- (cdr (assq var (closure-env *unit*))) 
                                 (fx+ current-offset tag/extend))))
                  (cond ((and (eq? (lambda-strategy *lambda*) strategy/heap)
                              (eq? contour (car (closure-members closure))))
                         (if cl?
                             (list (reg-offset TP (fx+ disp tag/extend)))
                             (reg-offset TP disp)))
                        ((register-loc (variable-binder (car (closure-members closure))))
                         => (lambda (reg)
                              (if cl?
                                  (list (reg-offset reg (fx+ disp tag/extend)))
                                  (reg-offset reg disp))))
                        (else
                         (lock (register-loc contour))
                         (let ((reg (get-register 'pointer node '*)))
                           (unlock (register-loc contour))
                           (generate-move
                              (reg-offset (register-loc contour)
                                          (fx- (fx- 0 tag/extend)
                                               (cdr (assq contour
                                                    (closure-env closure)))))
                              reg)                        
                           (mark (variable-binder (car (closure-members closure)))
                                 reg)
                           (if cl?
                               (list (reg-offset reg (fx+ disp tag/extend)))
                               (reg-offset reg disp))))))))
          (else nil))))

|||#

(defun get-env (var)
  (lambda-env (variable-binder var)))
                                      

;;; Yukk.  Here we get a variable from a stack frame.  If it is in the frame
;;; we are OK.  Otherwise we chain down stack frames as long as they are there.
;;; These frames are all simple offsets from SP.  When we arrive at a pointer
;;; into the heap, we load that pointer into a register and go to the heap
;;; code to do the rest.

#|||
(define (fetch-from-vframe node contour value lambda-bound?)
  (iterate loop ((offset 0) (l (variable-binder contour)))
    (select (lambda-strategy l)
      ((strategy/label strategy/open)
       (loop offset (node-parent (node-parent l))))
      (else
       (cond ((not (lambda-env l))
              (loop offset (node-parent (node-parent l))))
             (else
              (let ((closure (environment-closure (lambda-env l))))
                (cond ((and lambda-bound? (assq value (closure-env closure)))
                       => (lambda (env-pair) 
                            (reg-offset (register-loc contour)
                                        (+ offset 
                                             (- (cdr env-pair) tag/extend)))))
                      ((closure-link closure)
                       => (lambda (link)
                       (let ((accessor (reg-offset (register-loc contour)
                                                   (- (+ offset CELL)
                                                        tag/extend))))
                         (into-register 'pointer node link accessor '*)
                         (xselect (lambda-strategy (variable-binder link))
                            ((strategy/heap) 
                             (fetch-from-heap node link value lambda-bound?))
                            ((strategy/vframe) 
                             (fetch-from-vframe node link value lambda-bound?))))))
                      (else
                       (loop (+ (closure-size closure) offset)
                             (node-parent (node-parent l))))))))))))
|||#
                          

                                                 
(defun fetch-from-stack (node value lambda-bound?)
  (do ((offset 0)
       (l *lambda* (node-parent (node-parent l))))
      (())
    (let ((strategy (lambda-strategy l)))
      (cond ((or (eq strategy strategy/label)
		 (eq strategy strategy/open)
		 (not (lambda-env l))))
	    (t
	     (let* ((closure (environment-closure (lambda-env l)))
		    (env-pair (and lambda-bound? (assoc value (closure-env closure) :test #'eq))))
	       (cond (env-pair
		      (return (reg-offset SP (+ offset *stack-pos* (cdr env-pair)))))
		     (t
		      (let ((link (closure-link closure)))
			(cond (link
			       (let ((accessor (reg-offset SP (+ *stack-pos*
								 (+ offset CELL)))))
				 (into-register 'pointer node link accessor '*)
				 (return (ecase (lambda-strategy (variable-binder link))
					   (STRATEGY/HEAP
					    (fetch-from-heap node link value lambda-bound?))
					   (STRATEGY/VFRAME
					    (fetch-from-vframe node link value lambda-bound?))))))
			      (t
			       (incf offset (closure-size closure)))))))))))))
                          


(defun closure-internal-closure? (value closure)
  (cond ((not (eq closure *unit*))
         (member value (closure-members closure) :test #'eq))
        (t
         (or (and (node-p value) (lambda-node? value))
             (closure-p value)))))

(defun fetch-from-heap (node contour value lambda-bound?) 
  (do* ((contour contour (caadr a-list))
	(env (get-env contour) (get-env contour))
	(a-list))
       (())
    (format t "~%Contour:     ~s" contour)
    (format t "~%Env:         ~s" env)
    (format t "~%Closure-env: ~s" (closure-env (environment-closure env)))
    (setq a-list (closure-env (environment-closure env)))
    (let* ((current-offset (environment-cic-offset env))
	   (pair (assoc value a-list :test #'eq)))
      (cond (pair
	     (return (if (closure-internal-closure? value
						    (environment-closure env))
			 (list (reg-offset (register-loc contour)	; *** hack
					   (- (cdr pair) current-offset)))
		       (reg-offset (register-loc contour)
				   (- (cdr pair)
				      (+ current-offset tag/extend))))))
	    (t (let ((acc (and (not lambda-bound?) (access-from-unit node contour value))))
		 (cond (acc (return acc))
		       (t  (format t "~&into-register")
			(into-register 'pointer node (caadr a-list)
				       (reg-offset  (register-loc contour)
						    (+ (- current-offset) tag/extend))
				       '*)))))))))
                             

(defun fetch-continuation (node leaf)
  (let* ((var (leaf-value leaf))
         (amount (fetch-continuation-from-stack node var))
         (proc (leaf-value (call-proc node))))
    (let ((n (cond ((not (eq (lambda-strategy (variable-binder var)) 
			     strategy/ezclose))
                    0)
                   ((eq var proc) (follow-ezclose var 0))
                   (t (let ((label (variable-known proc)))
			(if label
			    (follow-ezclose var (lambda-depth label))
			  (follow-ezclose var 0)))))))
      (adjust-stack-pointer (+ amount n)))))


(defun follow-ezclose (var depth)
  (do ((pair (variable-type var) (caar pair))
       (i 0 (+ i (cdar pair))))
    ((or (atom pair)
	 (< (lambda-depth (variable-binder (caar pair))) depth))
     i)))

                         
                

                                          
;;; Code to get a continuation off the stack.
;;; Search up the tree until we find it.
;;; This relies on generating code for the body of a labels FIRST.


(defun fetch-continuation-from-stack (node var)
  (do ((offset 0)
       (l (node-parent node) (node-parent (node-parent l))))
      ((eq (variable-binder var) l)
       offset)
    (case (lambda-strategy l)
      ((STRATEGY/STACK)
;*******
       0  ;-efh
;      we don't push stuff
;       (incf offset (closure-size (environment-closure (lambda-env l))))
;*******
       )
      ((STRATEGY/VFRAME)
       (cond ((or (null (lambda-env l))
		  (vframe-p (lambda-live l))))
	     (t
	      (let ((vsize (closure-size (environment-closure 
					   (lambda-env l))))
		    (a (fetch-continuation-from-stack (node-parent l) var))
		    (vframe (make-vframe)))
		(setf (vframe-lambda vframe) l)
		(setf (vframe-return-size vframe) (+ vsize a))
		(setf (lambda-live l) vframe)
		(lambda-queue vframe))))
       (return offset))
      ((STRATEGY/EZCLOSE)
       (let ((frame-size 
	       (+ (fetch-continuation-from-stack (node-parent l) var)
		  (closure-size (environment-closure (lambda-env l))))))
	 (dolist (label (cdr (call-args (lambda-body l))))
	   (push (cons var frame-size)
		 (variable-type (car (lambda-variables label)))))
	 (cond ((not (vframe-p (lambda-live l)))
		(let ((vframe (make-vframe)))
		  (setf (vframe-lambda vframe) l)
		  (setf (vframe-return-size vframe) frame-size)
		  (setf (lambda-live l) vframe)
		  (lambda-queue vframe)))))
       (return offset)))))

