;;; -*- Mode:LISP; Package:META-EVAL; Base:8 -*-


;;; I got the name PROGITOR from RG 9/02/84 14:39:25 -gjc
;;; This source->source transformation stuff has some demand amoung meta-lisp
;;; hackers such as Gary Drescher (QLOGO message passing stuff) and
;;; Mats Carlson (LM PROLOG). Mats has his own version of progitor called DEFUNN.
;;; Which isn't semantically correct but he uses it anyway. He could better switch
;;; to this. The primary motivating force to putting this into the default system
;;; was to make it available for running the RPG benchmarks of lisp.

;;; the only way now to turn on "things" on a per-file basis is with
;;; file mode-lines. So we define a new mode.

(defconst *source-optimizations* nil)

;; khs didn't like the name "source-optimizations" so I've changed it to
;; source->source-optimizations which is what it really is.

(defprop :source-optimizations source->source-file-switch fs:file-attribute-bindings)
(defprop :source->source-optimizations source->source-file-switch fs:file-attribute-bindings)

(defun source->source-file-switch (file key value) file key
  (values (list '*source-optimizations*)
	  ;; as I recall, somebody will rplacd our return values someplace, so we beware -gjc.
	  (list (optimization-name-eval value))))


(defprop :t  (:progitor :argument-reduction) optimization-name-value)
(defprop :ucode (:t :map-open-coding :cons-MAPCAR :FANCY-MAPCAR) optimization-name-value)

(defun optimization-name-eval-map (l)
  (lexpr-funcall #'union () (mapcar #'optimization-name-eval l)))

(defun optimization-name-eval (x)
  (cond ((symbolp x)
	 (cond ((get x 'optimization-name-value)
		(optimization-name-eval-map (get x 'optimization-name-value)))
	       ((missing-optimization-property? x)
		(ferror 'fs:invalid-file-attribute
			"unknown optimization name: ~S" x))
	       (t (list x))))
	((listp x)
	 (optimization-name-eval-map x))
	(t
	 (ferror 'fs:invalid-file-attribute
		 "Bad form of optimization spec: ~S" x))))

(defun vanilla-arglist? (l)
  (or (null l)
      (and (listp l)
	   (do ((l l (cdr l)))
	       ((null l) t)
	     (and (memq (car l) lambda-list-keywords)
		  (return nil))))))

(defun source-optimizations-qc-translate-function-hook (fspec exp)
  ;; (named-lambda name argl . body)
  (cond ((and *source-optimizations*
	      (or (symbolp fspec) (and (listp fspec)
				       (eq ':property (Car fspec))))
	      (eq (car exp) 'named-lambda)
	      (vanilla-arglist? (caddr exp)))
	 (let ((*source-optimizations* (if (listp fspec)
					   (remq :progitor *source-optimizations*)
					 *source-optimizations*)))
	   (attempt-progitor-source-optimization fspec exp)))
	(t exp)))

(defconst *progitor-catch-error? t)
(defconst *progitor-result-trace? nil)

(defmacro defprogy (name arglist &body body)
  "Use defprogy to test the progitor"
  (let ((*progitor-catch-error? nil)
	(*progitor-result-trace? t)
	(*source-optimizations* (optimization-name-eval :t)))
    (select-match (attempt-progitor-source-optimization
			  name `(named-lambda ,name ,arglist (block ,name . ,body)))
      (`(named-lambda ,name ,arglist (block ,ignore . ,body))
       t
       `(defun ,name ,arglist . ,body))
      (otherwise "internal inconsistency lossage"))))

;; generalize things to hack other optimizations in the future.
;; This will entail organizing a set of HOOKS and HOOK-STATES in the meta-eval,
;; allowing things to trigger. Probably a two-pass is called for, first pass
;; allows things to detect of optimizations will win, gathering information,
;; then second pass allows extra-code to be wrapped on various places,
;; (e.g. here in progitor we introduce an outer tagbody/block form),
;; while the hooks are run again to expand into what they want to expand into.
;; Common-subexpression optimizations fit into this model.
;; We also want to attack a more subtle optimization, the detection of
;; possible interation, examples:
;; (defun fact (x) (if (zerop x) 1 (times x (fact (sub1 x)))))
;; (defun foo (l) (if (null l) nil (cons (bar (car l)) (foo (cdr l)))))
;; ==>
;; maybe that is asking too much, or perhaps too little.
;; a more pattern-match driven meta-eval could be called for here.
;; on each recursion we match a pattern of both CONTEXT (e.g. *META-TARGET*)
;; and FORM. 
;; Also, common-sub-expression optimizations must deal with wrapping code
;; in the form of LET's at various contontours. So we would have
;; (defun meta-eval-sub (...) (enclose-wrappers-from-below ... do what you did before...))
;; If the wrappers would indicate further optimization then we get a hairy inefficient
;; multiple-try-until-nothing style things. My taste is to restrict what you can
;; wrap.
;; Another thing we could put into this pass: type-propagation and type-specific
;; code generation. meta-eval-sub then returns two values, form and <type>.

(defun attempt-progitor-source-optimization (fspec exp)
  (format t "~&Attempting ~S optimizations on ~S" *source-optimizations* fspec)
  ;; (named-lambda foo argl (block foo . body))
  ;; we want to extract the body inside the block as our actually body.
  ;; the strange use of prog here is because select-match is somewhat broken.
  (PROG ()
	(SELECT-MATCH EXP
	  (`(named-lambda ,name ,argl (block ,bname . ,body))
	   (eq name bname)
	   (RETURN
	     (multiple-value-bind (name argl new-form)
		 (progitor-source-optimization-doit fspec argl body)
	       (if name
		   `(named-lambda ,name ,argl (block ,bname ,@new-form))
		 exp)))))
	(SELECT-MATCH EXP
	  (`(named-lambda ,name ,argl . ,body)
	   t
	   (RETURN (multiple-value-bind (stuff argl new-form)
		       (progitor-source-optimization-doit fspec argl body)
		     (if stuff
			 `(named-lambda ,name ,argl ,@new-form)
		       exp)))))
	(format t "Didn't match known defun form: ~S no optimizations." fspec)
	(RETURN exp)))

(defun progitor-source-optimization-doit (fspec argl body)
  (let ((def-form `(defun ,fspec ,argl ,@body)))
    (let (new-form n)
      (if *progitor-catch-error?
	  (catch-error (multiple-value (new-form n) (extended-progitor def-form)) nil)
	(multiple-value (new-form n) (extended-progitor def-form)))
      (cond ((Null new-form)
	     (format t "~&Error while optimizing ~S, punting." fspec)
	     nil)
	    ((eq new-form def-form)
	     (format t "~&No optimizations found possible for ~S." fspec)
	     nil)
	    (t
	     (format t "~&Optimizations won for ~S:~%" fspec)
	     (do ((l n (cdr l)))
		 ((null l))
	       (format t "~D ~A~p~%"
		       (cdr (car l))
		       (get (Caar l) 'optimization-saying)
		       (cdr (car l))))
	     (if *progitor-result-trace? (grind-top-level new-form))
	     ;; (defun foo () . body)
	     (values fspec argl (cdddr new-form)))))))


(compiler:set-qc-translate-function-hook 'source-optimizations-qc-translate-function-hook)


;; the :progitor optimization is kind of a special case in that it
;; needs information from outside to be passed in, and it needs an
;; outside-wrapper of code. We have made provision for this sort of
;; thing, but no gaurantee of non-interaction, extensibility but
;; not transparency.

(defconst *required-optimization-properties*
	  '(optimization-match? optimization optimization-saying))


(defun missing-optimization-property? (x)
  (do ((l *required-optimization-properties* (cdr l)))
      ((null l)
       ())
    (or (get x (car l)) (return (car l)))))

(defconst *extended-progitor-trace? nil)

(defvar *variables-to-substitue*)
(defvar *values-to-substitute*)

(defun mapkan (f l)
  ;; khs broke MAPCAN, my favorite function. So I use this instead now.
  (apply #'nconc (mapcar f l)))

(defun extended-progitor (a-defun &aux *variables-to-substitue* *values-to-substitute*)
  (let ((optimizations (mapkan #'(lambda (x)
				   (let ((missing? (missing-optimization-property? x)))
				     (cond ((null missing?)
					    (list (cons x 0)))
					   (t
					    (format t "~&~S optimization lacks the ~S property~%"
						    x missing?)
					    nil))))
			       *source-optimizations*))
	(body `(progn ,@(cdddr a-defun))))
    (let ((v (apply #'append
		    (mapcar #'(lambda (o)
				(if (Get (car o) 'optimization-outer-values-compute)
				    (funcall (Get (car o) 'optimization-outer-values-compute)
					     a-defun
					     o)))
			    optimizations))))
      (progv (mapcar #'car v)
	     (mapcar #'cdr v)
	(let ((new-body (caddr (meta-eval `(lambda ,(caddr a-defun)
					     (meta-let ((*meta-target* 'ret)) ,body))
					  *variables-to-substitue*
					  *values-to-substitute*
					  #'(lambda (form)
					      (when *extended-progitor-trace?
						(format t "~&~A: ~S"
							*meta-target* form))
					      (do ((l optimizations (cdr l)))
						  ((null l) form)
						(when (funcall (get (caar l)
								    'optimization-match?)
							       form)
						  (when *extended-progitor-trace?
						    (format t " winner: ~S" (caar l)))
						  (setf (cdar l) (1+ (cdar l)))
						  (return (funcall (get (caar l)
									'optimization)
								   form)))))))))
	  (cond ((zerop (apply #'+ (mapcar #'cdr optimizations)))
		 a-defun)
		('else
		 (do ((l optimizations (cdr l)))
		     ((null l))
		   (if (and (not (zerop (cdar l)))
			    (get (caar l) 'optimization-outer-wrap))
		       (setq new-body (funcall (get (caar l) 'optimization-outer-wrap)
					       new-body))))
		 (values `(defun ,(cadr a-defun) ,(caddr a-defun) ,new-body)
			 optimizations))))))))


;; progitor optimization:

(defprop :progitor "tail recursion" optimization-saying)

(defun (:progitor optimization-outer-wrap) (body)
  `(tagbody
    loop
       (block ,*progitor-tail-escape*
	 (return-from ,*progitor-function* ,body))
       (go loop)))

(defun (:progitor optimization-outer-values-compute) (a-defun ignore)
  `((*progitor-tail-escape* . ,(intern (string-append (cadr a-defun) "-tail-escape")))
    (*progitor-function* . ,(cadr a-defun))
    (*progitor-args* . ,(caddr a-defun))))

(defun (:Progitor optimization-match?) (form)
  (and (eq *meta-target* 'ret)
       (eq (car form) *progitor-function*)
       (do ((l *meta-BIND-stack* (cdr l)))
	   ((null l) t)
	 (and (meta-var-special-p (car l)) (return nil)))))


(defun (:progitor optimization) (form)
  `(progn ,(CONS-psetq (mapcan #'list
			       *progitor-args*
			       (cdr form)))
	  (return-from ,*progitor-tail-escape*)))

;; other optimizations:
;; I'm not putting these in as regular compiler:optimizers because there are
;; cases which "for-effect" optimizations come into play.
;; e.g. (NCONC A B) for effect is clearly
;; (RPLACD (LAST A) B).

(defprop :argument-reduction "argument reduction" optimization-saying)

(remprop 'nconc 'argument-reduction)
;(defprop nconc ((2 . nconc-2)) argument-reduction)
(remprop 'append 'argument-reduction)
;(defprop append ((2 . append-2)) argument-reduction)
; MAPCAR IS NOW UCODED.
;(defprop mapcar ((2 . mapcar-2)) argument-reduction)
(defprop fancy-mapcar ((3 . fancy-mapcar-3)) argument-reduction)

(defun (:argument-reduction optimization-match?) (form)
  (let ((l (get (car form) 'argument-reduction)))
    (cond ((null l) nil)
	  ((assq (1- (length form)) l))
	  ('else
	   (format t "~&Would have liked a ~D argument version of ~S~%"
		   (1- (length form))
		   (car form))
	   nil))))

(defun (:argument-reduction optimization) (form)
  (cons (cdr (assq (1- (length form)) (get (car form) 'argument-reduction)))
	(cdr form)))

(defprop :map-open-coding "map open coding" optimization-saying)

(defprop mapcar-2 mapcar-2-expander map-expander)

(defun (:map-open-coding optimization-match?) (form)
  (and (get (car form) 'map-expander)
       (and (listp (cadr form))
	    (Memq (caadr form) '(quote function)))))

(defun (:map-open-coding optimization) (form)
  (lexpr-funcall (get (car form) 'map-expander) (cdr form)))



(defprop :cons-MAPCAR "cons-mapcar" optimization-saying)

(defun (:cons-MAPCAR optimization-match?) (form)
  ;; looking for (CONS A (MAPCAR ...))
  (and (listp (caddr form))
       (eq (car (caddr form)) 'mapcar)))

(defun (:cons-MAPCAR optimization) (form)
  `(fancy-mapcar (ncons ,(cadr form)) ,@(cdr (caddr form))))


(DEFPROP :FANCY-MAPCAR "fancy mapcar" OPTIMIZATION-SAYING)

(DEFUN (:FANCY-MAPCAR OPTIMIZATION-MATCH?) (FORM)
  (AND (EQ (CAR FORM) 'FANCY-MAPCAR-3)
       (AND (LISTP (CADDR FORM))
	    (MEMQ (CAR (CADDR FORM)) '(QUOTE FUNCTION)))))


(DEFUN (:FANCY-MAPCAR OPTIMIZATION) (FORM)
  (LET ((CELL (CADR FORM))
	(F (CADR (CADDR FORM)))
	(ARG (CADDDR FORM)))
      `(let ((RESULT ,CELL)
	     (LL ,ARG))
	     
	   (prog (P)
		 (SETQ P RESULT)
	      loop
		 (if (null LL) (return RESULT))
		 (SETF (CDR P) (SETQ P (NCONS (,F (POP LL)))))
		 (GO LOOP)))))



(defprop :cspecials "non-setq'd special variables" optimization-saying)

(defvar *cspecials-renaming*)

(defun (:cspecials optimization-outer-values-compute) (a-defun cell)
  (let ((me-vars (apply #'append
			(mapcar #'(lambda (x)
				    (if (get (meta-var-name x) 'no-setq)
					(list (list (meta-var-name x) (gensym)))))
			 (meta-eval `(lambda ,(caddr a-defun) ,@(cdddr a-defun)))))))
    (when me-vars
      (setf (cdr cell) (length me-vars))
      (setq *variables-to-substitue*
	    (append *variables-to-substitue*
		    (mapcar #'car me-vars)))
      (setq *values-to-substitute*
	    (append *values-to-substitute*
		    (mapcar #'cadr me-vars)))
      `((*cspecials-renaming* . ,me-vars)))))


(defun (:cspecials optimization-outer-wrap) (body)
  ;; this doesn't interact, semantically with the tagbody generated by :progitor.
  (print *cspecials-renaming*)
  `(let ,(mapcar #'reverse *cspecials-renaming*)
     ,body))

(defun (:cspecials optimization-match?) (form)
  form
  ())

(defun (:cspecials optimization) (form)
  (ferror nil "should never have been called on: ~S" form))


