;;; CMPEVAL  The Expression Dispatcher.
;;;
;; Copyright (C) 1994 M. Hagiya, W. Schelter, T. Yuasa

;; This file is part of GNU Common Lisp, herein referred to as GCL
;;
;; GCL is free software; you can redistribute it and/or modify it under
;;  the terms of the GNU LIBRARY GENERAL PUBLIC LICENSE as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; 
;; GCL is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
;; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public 
;; License for more details.
;; 
;; You should have received a copy of the GNU Library General Public License 
;; along with GCL; see the file COPYING.  If not, write to the Free Software
;; Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.




(export '(si::define-compiler-macro
	  si::undef-compiler-macro
          si::define-inline-function) 'system)

(in-package 'compiler)

(si:putprop 'progn 'c1progn 'c1special)
(si:putprop 'progn 'c2progn 'c2)

(si:putprop 'si:structure-ref 'c1structure-ref 'c1)
(si:putprop 'structure-ref 'c2structure-ref 'c2)
(si:putprop 'structure-ref 'wt-structure-ref 'wt-loc)
(si:putprop 'si:structure-set 'c1structure-set 'c1)
(si:putprop 'structure-set 'c2structure-set 'c2)

(defun c1expr* (form info)
  (setq form (c1expr form))
  (add-info info (cadr form))
  form)

(defun c1expr (form)
  (setq form (catch *cmperr-tag*
    (cond ((symbolp form)
           (cond ((eq form nil) (c1nil))
                 ((eq form t) (c1t))
                 ((keywordp form)
                  (list 'LOCATION (make-info :type (object-type form))
                        (list 'VV (add-object form))))
                 ((constantp form)
                  (let ((val (symbol-value form)))
                    (or (c1constant-value val nil)
                        (list 'LOCATION (make-info :type (object-type val))
                              (list 'VV (add-constant form))))))
                 (t (c1var form))))
          ((consp form)
           (let ((fun (car form)))
             (cond ((symbolp fun)
                    (c1symbol-fun fun (cdr form)))
                   ((and (consp fun) (eq (car fun) 'lambda))
                    (c1lambda-fun (cdr fun) (cdr form)))
                   ((and (consp fun) (eq (car fun) 'si:|#,|))
                    (cmperr "Sharp-comma-macro was found in a bad place."))
                   (t (cmperr "The function ~s is illegal." fun)))))
          (t (c1constant-value form t)))))
  (if (eq form '*cmperr-tag*) (c1nil) form))

(si::putprop 'si:|#,| 'c1sharp-comma 'c1special)

(defun c1sharp-comma (arg)
  (c1constant-value (cons 'si:|#,| arg) t))

(si::putprop 'si::define-structure 'c1define-structure 't1)

(defun c1define-structure (arg &aux *sharp-commas*)
  (declare (special *sharp-commas*))
  (eval (cons 'si::define-structure arg))
  (c1constant-value (cons 'si:|#,| (cons 'si::define-structure arg)) t)
  (add-load-time-sharp-comma)
  nil)

(defvar *c1nil* (list 'LOCATION (make-info :type (object-type nil)) nil))
(defun c1nil () *c1nil*)
(defvar *c1t* (list 'LOCATION (make-info :type (object-type t)) t))
(defun c1t () *c1t*)


(defun flags-pos (flag &aux (i 0))
  (declare (fixnum i))
  (dolist
      (v
       '((allocates-new-storage ans); might invoke gbc
	 (side-effect-p set)        ; no effect on arguments
	 (constantp)                ; always returns same result,
	                            ;double eval ok.
	 (result-type-from-args rfa); if passed args of matching
					;type result is of result type
         (is)))                     ;; extends the `integer stack'.
    (cond ((member flag v :test 'eq)
	   (return-from flags-pos i)))
    (setq i (+ i 1)))
  (error "unknown opt flag"))

(defmacro flag-p (n flag)
  `(logbitp ,(flags-pos  flag)  ,n))

;; old style opts had '(args ret new-storage side-effect string)
;; these new-storage and side-effect have been combined into
;; one integer, along with several other flags.

(defun fix-opt (opt)
  (let ((a (cddr opt)))
    (unless (typep (car a ) 'fixnum)
    (if *compiler-in-use*
	(cmpwarn "Obsolete optimization: use fix-opt ~s"  opt))
		     
    (setf (cddr opt)
	  (cons (logior (if (car a) 2 0)
			(if (cadr a) 1 0))
		(cddr a))))
    opt))

;; some hacks for revising a list of optimizers.
#+revise
(progn
(defun output-opt (opt sym flag)
  (fix-opt opt)
  (format t "(push '(~(~s ~s #.(flags~)" (car opt) (second opt))
  (let ((o (third opt)))
    (if (flag-p o set) (princ " set"))
    (if (flag-p o ans) (princ " ans"))
    (if (flag-p o rfa) (princ " rfa"))
    (if (flag-p o constantp) (princ "constantp ")))
  (format t ")")
  (if (and (stringp (nth 3 opt))
	   (> (length (nth 3 opt)) 40))
      (format t "~%  "))
  (prin1 (nth 3 opt))
  (format t ")~%   ~((get '~s '~s)~))~%"  sym flag))

(defun output-all-opts (&aux  lis did)
  (sloop::sloop
   for v in ;(list (find-package "LISP"))
					(list-all-packages)
   do
   (setq lis
	 (sloop::sloop
	  for sym in-package (package-name v)
	  when (or (get sym 'inline-always)
		   (get sym 'inline-safe)
		   (get sym 'inline-unsafe))
	  collect sym))
   (setq lis (sort lis #'(lambda (x y) (string-lessp (symbol-name x)
						      (symbol-name y)))))
   do
   (sloop::sloop for sym in lis do
		 (format t "~%;;~s~% " sym)
       (sloop::sloop for u in '(inline-always inline-safe inline-unsafe)
		     do (sloop::sloop
			 for w in (reverse (remove-duplicates
					    (copy-list (get sym u))
					    :test 'equal))
			 do (output-opt w  sym u))))))

)				      
				

(defun result-type-from-args(f args &aux tem)
  (when (and (setq tem (get f 'return-type))
             (not (eq tem '*))
	     (not (consp tem)))
    (dolist (v '(inline-always inline-unsafe))
      (dolist (w (get f v))
	(fix-opt w)
	(when (and
	       (flag-p (third w) result-type-from-args)
	       (eql (length args) (length (car w)))
	       (do ((a args (cdr a))
		    (b (car w) (cdr b)))
		   ((null a) t)
		 (unless (or  (eq (car a) (car b))
			      (type>= (car b)(car a) ))
			 (return nil))))
	  (return-from result-type-from-args (second w)))))))
		      

;; omitting a flag means it is set to nil.
(defmacro flags (&rest lis &aux (i 0))
  (dolist (v lis)
    (setq i (logior  i (ash 1 (flags-pos v)))))
  i)

;; Usage:
; (flagp-p (caddr ii) side-effect-p)
; (push '((integer integer) integer #.(flags const raf) "addii(#0,#1)")
;         (get '+ 'inline-always))



(defun c1symbol-fun (fname args &aux fd)
  (cond ((setq fd (get fname 'c1special)) (funcall fd args))
	((and (setq fd (get fname 'co1special))
	      (funcall fd fname args)))
        ((setq fd (c1local-fun fname))
         (if (eq (car fd) 'call-local)
             (let* ((info (make-info :sp-change t))
                    (forms (c1args args info)))
                  (let ((return-type (get-local-return-type (caddr fd))))
                       (when return-type (setf (info-type info) return-type)))
                  (let ((arg-types (get-local-arg-types (caddr fd))))
                       ;;; Add type information to the arguments.
                       (when arg-types
                             (let ((fl nil))
                                  (dolist** (form forms)
                                    (cond ((endp arg-types) (push form fl))
                                          (t (push (and-form-type
                                                    (car arg-types) form
                                                    (car args))
                                                   fl)
                                             (pop arg-types)
                                             (pop args))))
                                  (setq forms (reverse fl)))))
                  (list 'call-local info (cddr fd) forms))
             (c1expr (cmp-expand-macro fd fname args))))
	((and (setq fd (get fname 'co1))
	      (inline-possible fname)
	      (funcall fd fname args)))
        ((and (setq fd (get fname 'c1)) (inline-possible fname))
         (funcall fd args))
        ((and (setq fd (get fname 'c1conditional))
              (inline-possible fname)
              (funcall (car fd) args))
         (funcall (cdr fd) args))
	;; record the call info if we get to here
	((progn
	   (and (eq (symbol-package fname) (symbol-package 'and))
		(not (fboundp fname))
		(cmpwarn "~A (in lisp package) is called as a function--not yet defined"
			 fname))
	   (and *record-call-info* (record-call-info 'record-call-info
						   fname))
	   nil))
	;;continue
        ((setq fd (macro-function fname))
         (c1expr (cmp-expand-macro fd fname args)))
        ((setq fd (get fname 'compiler-macro))
         (c1expr (cmp-eval `(funcall ',fd ',(cons fname args) nil))))
        ((and (setq fd (get fname 'si::structure-access))
              (inline-possible fname)
              ;;; Structure hack.
              (consp fd)
              (si:fixnump (cdr fd))
              (not (endp args))
              (endp (cdr args)))
         (case (car fd)
               (vector (c1expr `(elt ,(car args) ,(cdr fd))))
               (list (c1expr `(si:list-nth ,(cdr fd) ,(car args))))
               (t (c1structure-ref1 (car args) (car fd) (cdr fd)))
               )
         )
        ((eq fname 'si:|#,|)
         (cmperr "Sharp-comma-macro was found in a bad place."))
        (t (let* ((info (make-info
                        :sp-change (null (get fname 'no-sp-change))))
                  (forms (c1args args info)))
                (let ((return-type (get-return-type fname)))
		  (when return-type
			(if (equal return-type '(*))
			    (setf return-type nil)
			(setf (info-type info) return-type))))
                (let ((arg-types (get-arg-types fname)))
                     ;;; Add type information to the arguments.
                     (when arg-types
                       (do ((fl forms (cdr fl))
                            (fl1 nil)
                            (al args (cdr al)))
                           ((endp fl)
                            (setq forms (reverse fl1)))
                           (cond ((endp arg-types) (push (car fl) fl1))
                                 (t (push (and-form-type (car arg-types)
                                                         (car fl)
                                                         (car al))
                                          fl1)
                                    (pop arg-types))))))
                (let ((arg-types (get fname 'arg-types)))
                     ;;; Check argument types.
                     (when arg-types
                           (do ((fl forms (cdr fl))
                                (al args (cdr al)))
                               ((or (endp arg-types) (endp fl)))
                               (check-form-type (car arg-types)
                                                (car fl) (car al))
                               (pop arg-types))))
                (case fname
                      (aref
                       (let ((etype (info-type (cadar forms))))
                            (when (or (and (eq etype 'string)
                                           (setq etype 'character))
                                      (and (consp etype)
                                           (or (eq (car etype) 'array)
                                               (eq (car etype) 'vector))
                                           (setq etype (cadr etype))))
                                  (setq etype
                                        (type-and (info-type info) etype))
                                  (when (null etype)
                                        (cmpwarn
                                         "Type mismatch was found in ~s."
                                         (cons fname args)))
                                  (setf (info-type info) etype))))
                      (si:aset
                       (let ((etype (info-type (cadar forms))))
                            (when (or (and (eq etype 'string)
                                           (setq etype 'character))
                                      (and (consp etype)
                                           (or (eq (car etype) 'array)
                                               (eq (car etype) 'vector))
                                           (setq etype (cadr etype))))
                                  (setq etype
                                        (type-and (info-type info)
                                          (type-and (info-type
                                                     (cadar (last forms)))
                                                    etype)))
                                  (when (null etype)
                                        (cmpwarn
                                         "Type mismatch was found in ~s."
                                         (cons fname args)))
                                  (setf (info-type info) etype)
                                  (setf (info-type (cadar (last forms)))
                                        etype)
                                  ))))
		;; some functions can have result type deduced from
		;; arg types.
		
		(let ((tem (result-type-from-args fname
						  (mapcar #'(lambda (x) (info-type (cadr x)))
							  forms))))
			  (when tem
			    (setq tem (type-and tem (info-type info)))
			    (setf (info-type info) tem)))
		(list 'call-global info fname forms)))
        )
  )

;;numbers and character constants may be sometimes used, instead
;;of the variable, eg inside eql

(defun replace-constant (lis &aux found tem)
  (do ((v lis (cdr v)))
      ((null v)  found)
      (cond ((and (constantp (car v))
		  (or (numberp (setq tem(eval (car v))))
		      (characterp tem)))
	     (setq found t) (setf (car v) tem)))))



(defun c1lambda-fun (lambda-expr args &aux (info (make-info :sp-change t)))
  (setq args (c1args args info))
  (setq lambda-expr (c1lambda-expr lambda-expr))
  (add-info info (cadr lambda-expr))
  (list 'call-lambda info lambda-expr args)
  )

(defun c2expr (form)
  (if (eq (car form) 'call-global)
      (c2call-global (caddr form) (cadddr form) nil  (info-type (cadr form)))
      (if (or (eq (car form) 'let)
                 (eq (car form) 'let*))
             (let ((*volatile* (volatile (cadr form))))
                   (declare (special *volatile*))
                   (apply (get (car form) 'c2) (cddr form)))
	(let ((tem (get (car form) 'c2)))
	  (cond (tem (apply tem (cddr form)))
		((setq tem (get (car form) 'wholec2))
		 (funcall tem form))
		(t (baboon)))))))

(defun c2funcall-sfun (fn args info &aux  locs (all (cons fn args))) info
  (let ((*inline-blocks* 0))
    (setq locs (get-inline-loc
		(list (make-list (length all) :initial-element t)
		      t #.(flags ans set) 'fcalln-inline) all))
    (unwind-exit locs)
    (close-inline-blocks)))

(defun c2expr* (form)
  (let* ((*exit* (next-label))
         (*unwind-exit* (cons *exit* *unwind-exit*)))
        (c2expr form)
        (wt-label *exit*))
  )

(defun c2expr-top (form top &aux (*vs* 0) (*max-vs* 0) (*level* (1+ *level*))
                                 (*reservation-cmacro* (next-cmacro)))
  (wt-nl "{register object *base" (1- *level*) "=base;")
  (base-used)
  (wt-nl "{register object *base=V" top ";")
  (wt-nl "register object *sup=vs_base+VM" *reservation-cmacro* ";")
  (if *safe-compile*
      (wt-nl "vs_reserve(VM" *reservation-cmacro* ");")
      (wt-nl "vs_check;"))
  (wt-nl) (reset-top)
  (c2expr form)
  (push (cons *reservation-cmacro* *max-vs*) *reservations*)
  (wt-nl "}}")
  )

(defun c2expr-top* (form top)
  (let* ((*exit* (next-label))
         (*unwind-exit* (cons *exit* *unwind-exit*)))
        (c2expr-top form top)
        (wt-label *exit*)))

(defun c1progn (forms &aux (fl nil))
  (cond ((endp forms) (c1nil))
        ((endp (cdr forms)) (c1expr (car forms)))
        (t (let ((info (make-info)))
                (dolist (form forms)
                        (setq form (c1expr form))
                        (push form fl)
                        (add-info info (cadr form)))
                (setf (info-type info) (info-type (cadar fl)))
                (list 'progn info (reverse fl))
                )))
  )

;;; Should be deleted.
(defun c1progn* (forms info)
  (setq forms (c1progn forms))
  (add-info info (cadr forms))
  forms)

(defun c2progn (forms)
  ;;; The length of forms may not be less than 1.
  (do ((l forms (cdr l)))
      ((endp (cdr l))
       (c2expr (car l)))
      (declare (object l))
      (let* ((*value-to-go* 'trash)
             (*exit* (next-label))
             (*unwind-exit* (cons *exit* *unwind-exit*)))
            (c2expr (car l))
            (wt-label *exit*)
            ))
  )

(defun c1args (forms info)
  (mapcar #'(lambda (form) (c1expr* form info)) forms))

;;; Structures

(defun c1structure-ref (args)
  (if (and (not *safe-compile*)
	   (not (endp args))
           (not (endp (cdr args)))
           (consp (cadr args))
           (eq (caadr args) 'quote)
           (not (endp (cdadr args)))
           (symbolp (cadadr args))
           (endp (cddadr args))
           (not (endp (cddr args)))
           (si:fixnump (caddr args))
           (endp (cdddr args)))
      (c1structure-ref1 (car args)  (cadadr args) (caddr args))
      (let ((info (make-info)))
        (list 'call-global info 'si:structure-ref (c1args args info)))))

(defun c1structure-ref1 (form name index &aux (info (make-info)))
  ;;; Explicitly called from c1expr and c1structure-ref.
  (declare (special  *aet-types*))
  (cond (*safe-compile* (c1expr `(si::structure-ref ,form ',name ,index)))
	(t
  (let* ((sd (get name 'si::s-data))
	 (aet-type (aref (si::s-data-raw sd) index))
	 )
    (setf (info-type info) (type-filter (aref *aet-types* aet-type)))
    (list 'structure-ref info
	  (c1expr* form info)
	  (add-symbol name)
	  index sd)
    
    ))))

(defun coerce-loc-structure-ref (arg type-wanted &aux (form (cdr arg)))
  (let* ((sd (fourth form))
	 (index (caddr form)))
    (cond (sd
	    (let* ((aet-type (aref (si::s-data-raw sd) index))
		   (type (aref *aet-types* aet-type)))
	      (cond ((eq (inline-type (type-filter type)) 'inline)
		     (or (eql aet-type 0) (error "bad type ~a" type))))
	      (setf (info-type (car arg)) (type-filter type))
	      (coerce-loc
		      (list (inline-type
			     (type-filter type))
		           (flags)
			    'my-call
			    (list
			     (car
			      (inline-args (list (car form))
					   '(t)))
			     'joe index sd))
		      (type-filter type-wanted)))
		)
	  (t (wfs-error)))))


(defun c2structure-ref (form name-vv index sd
                             &aux (*vs* *vs*) (*inline-blocks* 0))
  (let ((loc (car (inline-args (list form) '(t))))
	(type (aref *aet-types* (aref (si::s-data-raw sd) index))))
       (unwind-exit
	 (list (inline-type (type-filter type))
			  (flags) 'my-call
			  (list  loc  name-vv
				 index sd))))
  (close-inline-blocks)
  )


(defun my-call (loc name-vv ind sd) name-vv
  (let* ((raw (si::s-data-raw sd))
	 (spos (si::s-data-slot-position sd)))
    (if *safe-compile* (wfs-error)
      (wt "STREF("  (aet-c-type (aref *aet-types* (aref raw ind)) )
	  "," loc "," (aref spos ind) ")"))))


(defun c1structure-set (args &aux (info (make-info)))
  (if (and (not (endp args)) (not *safe-compile*)
           (not (endp (cdr args)))
           (consp (cadr args))
           (eq (caadr args) 'quote)
           (not (endp (cdadr args)))
           (symbolp (cadadr args))
           (endp (cddadr args))
           (not (endp (cddr args)))
           (si:fixnump (caddr args))
           (not (endp (cdddr args)))
           (endp (cddddr args)))
      (let ((x (c1expr (car args)))
            (y (c1expr (cadddr args))))
        (add-info info (cadr x))
        (add-info info (cadr y))
        (setf (info-type info) (info-type (cadr y)))
        (list 'structure-set info x
              (add-symbol (cadadr args)) ;;; remove QUOTE.
              (caddr args) y (get (cadadr args) 'si::s-data)))
      (list 'call-global info 'si:structure-set (c1args args info))))


;; The following (side-effects) exists for putting at the end of an
;; argument list to force all previous arguments to be stored in
;; variables, when computing inline-args.


(push '(() t #.(flags ans set) "Ct")  (get 'side-effects  'inline-always))

(defun c2structure-set (x name-vv ind y sd 
                          &aux locs (*vs* *vs*) (*inline-blocks* 0))
  name-vv
  (let* ((raw (si::s-data-raw sd))
  (type (aref *aet-types* (aref raw ind)))
  (spos (si::s-data-slot-position sd))
  (tftype (type-filter type))
  ix iy)

   (setq locs (inline-args
        (list x y (list 'call-global (make-info) 'side-effects nil))
        (if (eq type t) '(t t t)
   `(t ,tftype t))))
  
  (setq ix (car locs))
  (setq iy (cadr locs))
  (if *safe-compile* (wfs-error))
  (wt-nl "STSET(" (aet-c-type type )","
    ix "," (aref spos ind) ", " iy ");")
  (unwind-exit (list (inline-type tftype) (flags) 'wt-loc (list iy)))
  (close-inline-blocks)
  ))

(defun c1constant-value (val always-p)
  (cond
   ((eq val nil) (c1nil))
   ((eq val t) (c1t))
   ((si:fixnump val)
    (list 'LOCATION (make-info :type 'fixnum)
          (list 'FIXNUM-VALUE (and (>= (abs val) 1024)(add-object val))
		val)))
   ((characterp val)
    (list 'LOCATION (make-info :type 'character)
          (list 'CHARACTER-VALUE (add-object val) (char-code val))))
   ((typep val 'long-float)
    ;; We can't read in long-floats which are too big:
    (let (tem x)
      (unless (setq tem (cadr (assoc val *objects*)))
         (cond((or
		 (and
		   (> (setq x (abs val)) (/ most-positive-long-float 2))
		   (c1expr `(si::|#,| * ,(/ val most-positive-long-float)
				 most-positive-long-float)))
		 (and
		   (< x (* least-positive-long-float 1.0d20))
		   (c1expr `(si::|#,| * ,(/ val least-positive-long-float)
				 least-positive-long-float))))
	       (push (list val (setq tem *next-vv*)) *objects*))))
      (list 'LOCATION (make-info :type 'long-float)
	    (list 'LONG-FLOAT-VALUE (or tem (add-object val)) val))))
   ((typep val 'short-float)
    (list 'LOCATION (make-info :type 'short-float)
          (list 'SHORT-FLOAT-VALUE (add-object val) val)))
   (always-p
    (list 'LOCATION (make-info :type (object-type val))
          (list 'VV (add-object val))))
   (t nil)))

(defmacro si::define-compiler-macro (name vl &rest body)
  `(progn (si:putprop ',name
                      (caddr (si:defmacro* ',name ',vl ',body))
                      'compiler-macro)
          ',name))  

(defun si::undef-compiler-macro (name)
  (remprop name 'compiler-macro))

(defvar *compiler-temps*
        '(tmp0 tmp1 tmp2 tmp3 tmp4 tmp5 tmp6 tmp7 tmp8 tmp9))

(defmacro si:define-inline-function (name vars &body body)
  (let ((temps nil)
        (*compiler-temps* *compiler-temps*))
    (dolist (var vars)
      (if (and (symbolp var)
               (not (si:memq var '(&optional &rest &key &aux))))
          (push (or (pop *compiler-temps*)
                    (gentemp "TMP" (find-package 'compiler)))
                temps)
          (error "The parameter ~s for the inline function ~s is illegal."
                 var name)))
    (let ((binding (cons 'list (mapcar
                                #'(lambda (var temp) `(list ',var ,temp))
                                vars temps))))
      `(progn
         (defun ,name ,vars ,@body)
         (si:define-compiler-macro ,name ,temps
           (list* 'let ,binding ',body))))))

(defun name-to-sd (x &aux sd)
  (or (and (symbolp x) (setq sd (get x 'si::s-data)))
      (error "The structure ~a is undefined." x))
  sd)

;; lay down code for a load time eval constant.
(defun name-sd1 (x)
  (or  (get x 'name-to-sd)
      (setf (get x 'name-sd)
	    `(si::|#,| name-to-sd ',x))))

(defun co1structure-predicate (f args &aux tem)
  (cond ((and (symbolp f)
	      (setq tem (get f 'si::struct-predicate)))
	 (c1expr `(typep ,(car args) ',tem)))))


