;;***** fix handling of throws and return-froms

(in-package "XLSCMP")

;;;;;
;;;;; Lambda Lifting
;;;;;

(defvar *pieces*)

;;***** is this right??
(defun free-variable-candidates (n)
  (if (gfun-eq (call-node-function n) '%throw-go)
      (list (call-node-arg n 0))
      (call-node-args n)))			    

(defun find-free-variables (n)
  (let ((vars (lambda-node-arglist n))
	(free nil)
	(nodes (list n)))
    (flet ((is-free-variable (v)
	     (if (local-symbol-node-p v)
		 (do ((m (symbol-node-owner v) (node-parent m)))
		     ((null m) t)
		     (if (eq m n) (return nil))))))
      (loop
       (if (null nodes) (return))
       (let ((n (pop nodes)))
	 (if (call-node-p n)
	     (dolist (a (free-variable-candidates n))
	       (if (is-free-variable a) (push a free))))
	 (dolist (c (node-children n)) (push c nodes))))
      (remove-duplicates free))))

(defun find-all-free-variables (nl)
  (let ((free nil))
    (dolist (n nl)
      (let ((fv (find-free-variables n)))
	(dolist (v fv) (pushnew v free))))
    free))

(defun find-and-fix-liftable-lambdas (n)
  (let ((flet-lambdas nil)
	(labels-lambdas nil)
	(nodes (list n)))
    (flet ((is-liftable (n)
	     (and (lambda-node-p n) (not (continuation-node-p n)))))
      (loop
       (if (null nodes) (return))
       (let ((n (pop nodes)))
	 (if (call-node-p n)
	     (let ((f (call-node-function n)))
	       (cond
		((lambda-node-p f)
		 (let ((names (lambda-node-arglist f))
		       (args (call-node-args n))
		       (body (lambda-node-body f)))
		   (mapcar #'(lambda (x y)
			       (when (is-liftable y)
				     (fix-liftable-lambda x y body)
				     (push (list x y) flet-lambdas)))
			   names
			   args)))
		(t
		 (case (gfun-symbol f)
		       (%y-list
			(let ((names nil)
			      (args nil)
			      (body (call-node-arg n 0)))
			  (mapc #'(lambda (x y)
				    (when (is-liftable y)
					  (push x names)
					  (push y args)
					  (push (list x y) labels-lambdas)))
				(find-y-list-names n)
				(rest (call-node-args n)))
			  (fix-liftable-y-lambdas n names args body))))))))
	 (dolist (c (node-children n)) (push c nodes))))
      (list flet-lambdas labels-lambdas))))

;;**** handle closures
;;**** handle %y, etc.
(defun new-lvar-node (v) (make-variable-node (leaf-node-value v)))
(defun new-lfun-node (v) (make-variable-node (leaf-node-value v)))

(defun substitute-variable (nv v f)
  (dolist (r (find-references v f)) (substitute-value nv r nil)))

(defun fix-liftable-lambda-arglist (f free)
  (let* ((nvars (mapcar #'new-lvar-node free))
	 (alist (lambda-node-arglist f))
	 (new-alist (cons (first alist) (append nvars (rest alist)))))
    (set-lambda-node-arglist f new-alist)
    ;;**** should be somewhere else??
    (dolist (v nvars) (set-symbol-node-owner v f))
    ;;**** a hack
    (let* ((fd (lambda-node-lambda-list f))
	   (nr (first fd)))
      (setf (first fd) (+ nr (length nvars))))
    (mapc #'(lambda (v nv) (substitute-variable nv v f)) free nvars)))

(defun fix-liftable-lambda-calls (vf body free)
  (dolist (r (find-references vf body))
    (let ((form (car r))
	  (pos (cdr r)))
      (if (= pos 0)
	  (let* ((args (call-node-args form))
		 (new-args (cons (first args) (append free (rest args)))))
	    (set-call-node-args form new-args))
          (let ((f (call-node-function form)))
	    (if (gfun-eq f '%make-closure)
		(set-call-node-args form
				    (append (call-node-args form) free))))))))

(defun fix-liftable-lambda (vf f body)
  (let ((free (find-free-variables f)))
    (when free
	  (fix-liftable-lambda-calls vf body free)
	  (fix-liftable-lambda-arglist f free))))

(defun fix-liftable-y-lambdas (n vfl fl body)
  (let ((free (find-all-free-variables fl)))
    (when free
	  (dolist (vf vfl)
	    (dolist (f (cons body fl))
	      (fix-liftable-lambda-calls vf f free)))
	  (mapc #'(lambda (vf f) (fix-liftable-lambda-arglist f free))
		vfl
		fl)
	  (let* ((pp (node-parent (node-parent n)))
		 (pf (call-node-function pp)))
	    (if (gfun-eq pf '%make-y-closures)
		(set-call-node-args pp (append (call-node-args pp) free)))))))

(defun lift-fixed-lambdas (nl)
  (dolist (l nl)
    (let* ((v (first l))
	   (f (second l))
	   (p (node-parent f))
	   (g (symbol-node-owner v)))
      (call-node-delete-arg p f) ;;**** fix
      (lambda-node-delete-arg g v)
      (set-symbol-node-owner v nil)
      (push (list v f) *pieces*))))

;;**** safe lambda args -- make into continuations??
(defun hoist-anonymous-closures (n)
  (let ((nodes (list n)))
    (loop
     (if (null nodes) (return))
     (let ((n (pop nodes)))
       (if (call-node-p n)
	   (let ((f (call-node-function n)))
	     (unless (or (lambda-node-p f)
			 (gfun-member f ;;**** check these out
				      '(%y-list
					%catch %throw
					%catch-block %throw-return-from
					%catch-tagbody %throw-go
					%errset
					%unwind-protect
					%dynamic-bind)))
		     (let ((fvars nil)
			   (funs nil)
			   (args (call-node-args n)))
		       (dolist (a args);**** is this right???
		         (if (and (lambda-node-p a)
				  (not (continuation-node-p a)))
			     (let ((v (make-leaf-node (gensym "F"))))
			       (push v fvars)
			       (push a funs)
			       (set-call-node-arg n (position a args) v))))
		       (if funs
			   (let ((temp (make-node)))
			     (move-node-tree n temp)
			     (move-node-tree
			      (apply #'make-call-node
				     (make-lambda-node fvars temp)
				     funs)
			      n)))))))
       (dolist (c (node-children n)) (push c nodes))))))

(defun find-closure-references (v body)
  (let ((crefs nil))
    (dolist (r (find-references v body) crefs)
      (if (/= (cdr r) 0) (push r crefs)))))

(defun insert-flet-closures (n)
  (let* ((f (call-node-function n))
	 (cvars nil)
	 (crefs nil)
	 (args (call-node-args n))
	 (alist (lambda-node-arglist f))
	 (body (lambda-node-body f)))
    (dolist (a args)
      (if (and (lambda-node-p a) (not (continuation-node-p a)))
	  (let* ((v (nth (position a args) alist))
		 (cr (find-closure-references v body)))
	    (when cr
		  (push v cvars)
		  (push cr crefs)))))
    (if crefs
	(let ((ncvars (mapcar #'new-lfun-node cvars)))
	  (mapc #'(lambda (nvf cr)
		    (dolist (r cr)
		      (set-call-node-arg (car r) (- (cdr r) 1) nvf)))
		ncvars
		crefs)
	  (mapc #'(lambda (vf nvf)
		    (let ((temp (make-node))
			  (b (lambda-node-body f)))
		      (move-node-tree b temp)
		      (move-node-tree
		       (make-call-node (get-gfun-node '%make-closure)
				       (make-continuation-node (list nvf) temp)
				       vf)
		       b)))
		cvars
		ncvars)))))

(defun insert-labels-closures (n)
  (let ((fvars (find-y-list-names n))
	(cvars nil)
	(crefs nil))
    (dolist (f fvars)
      (if (not (continuation-node-p (find-lambda-binding f)))
	  (let ((cr (find-closure-references f n)))
	    (when cr
		  (push f cvars)
		  (push cr crefs)))))
    (if cvars
	(let ((temp (make-node))
	      (ncvars (mapcar #'new-lfun-node cvars)))
	  (mapc #'(lambda (nvf cr)
		    (dolist (r cr)
		      (set-call-node-arg (car r) (- (cdr r) 1) nvf)))
		ncvars
		crefs)
	  (move-node-tree n temp)
	  (move-node-tree (apply #'make-call-node
				 (get-gfun-node '%make-y-closures)
				 (make-continuation-node ncvars temp)
				 cvars)
			  n)))))

(defun insert-closures (n)
  (if (call-node-p n)
      (let ((f (call-node-function n)))
	(cond
	 ((lambda-node-p f) (insert-flet-closures n))
	 ((gfun-eq f '%y-list) (insert-labels-closures n)))))
  (dolist (c (node-children n)) (insert-closures c)))

(defun lift-lambdas (n)
  (hoist-anonymous-closures n)
  (insert-closures n)
  (let ((lambdas (find-and-fix-liftable-lambdas n)))
    (progv '(*pieces*) (list (list (list (make-leaf-node (gensym "MAIN")) n)))
      (lift-fixed-lambdas (first lambdas))
      (lift-fixed-lambdas (second lambdas))
      (reverse *pieces*))))


;;;;
;;;; Extract Constants
;;;;

(defun extract-constants (n)
  (let ((consts nil)
	(nodes (list n)))
    (flet ((symfunp (f) (gfun-member f '(%symval %set-symval %symfun)))
	   (get-constant-variable (c)
	     (let ((ve (assoc c consts)))
		(if ve
		    (second ve)
	            (let ((v (make-leaf-node (gensym "C"))))
		      (push (list c v) consts)
		      v)))))
      (loop
       (if (null nodes) (return))
       (let ((n (pop nodes)))
	 (let ((ch (node-children n)))
	   (dolist (c (node-children n))
	     (if (and (leaf-node-p c)
		      (not (and (symfunp (call-node-function n))
				(= 2 (position c ch)))))
		 (if (constant-node-p c)
		     (setf (nth (position c ch) ch)
			   (get-constant-variable c)))
	       (push c nodes))))))
      (if consts
	  (let ((b (make-node)))
	    (move-node-tree (lambda-node-body n) b)
	    (move-node-tree (apply #'make-call-node
				   (make-lambda-node
				    (mapcar #'second consts)
				    b)
				   (mapcar #'first consts))
			    (lambda-node-body n)))))))


syntax highlighted by Code2HTML, v. 0.9.1