#| -*-Scheme-*-

Copyright (c) 1989-1999 Massachusetts Institute of Technology

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program 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
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|#

;;;; RTL Common Suffix Merging
;; Package: (compiler rtl-optimizer common-suffix-merging)

(declare (usual-integrations))

(define (merge-common-suffixes! rgraphs)
  (for-each merge-suffixes-of-rgraph! rgraphs))

(define (merge-suffixes-of-rgraph! rgraph)
  (let loop ()
    (let ((suffix-classes (rgraph-matching-suffixes rgraph)))
      (if (not (null? suffix-classes))
	  (begin
	    ;; Because many of the original bblocks can be discarded
	    ;; by the merging process, processing of one suffix class
	    ;; can make the information in the subsequent suffix
	    ;; classes incorrect.  However, reanalysis will still
	    ;; reproduce the remaining suffix classes.  So, process
	    ;; one class and reanalyze before continuing.
	    (merge-suffixes! rgraph (car suffix-classes))
	    (loop))))))

(define (merge-suffixes! rgraph suffixes)
  (with-values
      (lambda ()
	(discriminate-items suffixes
	  (lambda (suffix)
	    (eq? (cdr suffix) (bblock-instructions (car suffix))))))
    (lambda (total-suffixes partial-suffixes)
      (if (not (null? total-suffixes))
	  (let ((new-bblock (caar total-suffixes)))
	    (for-each (lambda (suffix)
			(replace-suffix-block! rgraph suffix new-bblock))
		      (cdr total-suffixes))
	    (replace-suffixes! rgraph new-bblock partial-suffixes))
	  (let ((suffix (car partial-suffixes)))
	    (split-suffix-block! rgraph suffix)
	    (replace-suffixes! rgraph (car suffix) (cdr partial-suffixes)))))))

(define (replace-suffixes! rgraph new-bblock partial-suffixes)
  (for-each (lambda (suffix)
	      (split-suffix-block! rgraph suffix)
	      (replace-suffix-block! rgraph suffix new-bblock))
	    partial-suffixes))

(define (split-suffix-block! rgraph suffix)
  (let ((old-bblock (car suffix))
	(instructions (cdr suffix)))
    (rinst-disconnect-previous! old-bblock instructions)
    (let ((sblock (make-sblock (bblock-instructions old-bblock))))
      (node-insert-snode! old-bblock sblock)
      (add-rgraph-bblock! rgraph sblock))
    (set-bblock-instructions! old-bblock instructions)))

(define (replace-suffix-block! rgraph suffix new-bblock)
  (let ((old-bblock (car suffix)))
    (node-replace-on-right! old-bblock new-bblock)
    (node-disconnect-on-left! old-bblock)
    (delete-rgraph-bblock! rgraph old-bblock)))

(define (rgraph-matching-suffixes rgraph)
  (append-map (lambda (bblock-class)
		(suffix-classes (initial-bblock-matches bblock-class)))
	      (rgraph/bblock-classes rgraph)))

(define (rgraph/bblock-classes rgraph)
  (let ((sblock-classes (list false))
	(pblock-classes (list false)))
    (for-each (lambda (bblock)
		(if (sblock? bblock)
		    (add-sblock-to-classes! sblock-classes bblock)
		    (add-pblock-to-classes! pblock-classes bblock)))
	      (rgraph-bblocks rgraph))
    (let ((singleton? (lambda (x) (null? (cdr x)))))
      (append! (list-transform-negative (cdr sblock-classes) singleton?)
	       (list-transform-negative (cdr pblock-classes) singleton?)))))

(define (add-sblock-to-classes! classes sblock)
  (let ((next (snode-next sblock)))
    (let loop ((previous classes) (classes (cdr classes)))
      (if (null? classes)
	  (set-cdr! previous (list (list sblock)))
	  (if (eq? next (snode-next (caar classes)))
	      (set-car! classes (cons sblock (car classes)))
	      (loop classes (cdr classes)))))))

(define (add-pblock-to-classes! classes pblock)
  (let ((consequent (pnode-consequent pblock))
	(alternative (pnode-alternative pblock)))
    (let loop ((previous classes) (classes (cdr classes)))
      (if (null? classes)
	  (set-cdr! previous (list (list pblock)))
	  (if (let ((pblock* (caar classes)))
		(and (eq? consequent (pnode-consequent pblock*))
		     (eq? alternative (pnode-alternative pblock*))))
	      (set-car! classes (cons pblock (car classes)))
	      (loop classes (cdr classes)))))))

(define (initial-bblock-matches bblocks)
  (let loop ((bblocks bblocks))
    (if (null? bblocks)
	'()
	(let ((entries (find-matching-bblocks (car bblocks) (cdr bblocks))))
	  (if (null? entries)
	      (loop (cdr bblocks))
	      (append! entries (loop (cdr bblocks))))))))

(define (suffix-classes entries)
  (let ((classes '())
	(class-member?
	 (lambda (class suffix)
	   (list-search-positive class
	     (lambda (suffix*)
	       (and (eq? (car suffix) (car suffix*))
		    (eq? (cdr suffix) (cdr suffix*))))))))
    (for-each (lambda (entry)
		(let ((class
		       (list-search-positive classes
			 (lambda (class)
			   (class-member? class (car entry))))))
		  (if class
		      (if (not (class-member? class (cdr entry)))
			  (set-cdr! class (cons (cdr entry) (cdr class))))
		      (let ((class
			     (list-search-positive classes
			       (lambda (class)
				 (class-member? class (cdr entry))))))
			(if class
			    (set-cdr! class (cons (car entry) (cdr class)))
			    (set! classes
				  (cons (list (car entry) (cdr entry))
					classes))))))
		unspecific)
	      entries)
    (map cdr
	 (sort (map (lambda (class) (cons (rinst-length (cdar class)) class))
		    classes)
	       (lambda (x y)
		 (< (car x) (car y)))))))

;;;; Basic Block Matching

(define (find-matching-bblocks bblock bblocks)
  (let loop ((bblocks bblocks))
    (if (null? bblocks)
	'()
	(with-values (lambda () (matching-suffixes bblock (car bblocks)))
	  (lambda (sx sy adjustments)
	    (if (or (interesting-suffix? bblock sx)
		    (interesting-suffix? (car bblocks) sy))
		(begin
		  (for-each (lambda (adjustment) (adjustment)) adjustments)
		  (cons (cons (cons bblock sx) (cons (car bblocks) sy))
			(loop (cdr bblocks))))
		(loop (cdr bblocks))))))))

(define (interesting-suffix? bblock rinst)
  (and rinst
       (or (rinst-next rinst)
	   (eq? rinst (bblock-instructions bblock))
	   (and (sblock? bblock)
		(snode-next bblock))
	   (let ((rtl (rinst-rtl rinst)))
	     (let ((type (rtl:expression-type rtl)))
	       (if (eq? type 'INVOCATION:PRIMITIVE)
		   (let ((procedure (rtl:invocation:primitive-procedure rtl)))
		     (and (not (eq? compiled-error-procedure procedure))
			  (negative? (primitive-procedure-arity procedure))))
		   (memq type
			 '(INTERPRETER-CALL:ACCESS
			   INTERPRETER-CALL:DEFINE
			   INTERPRETER-CALL:LOOKUP
			   INTERPRETER-CALL:SET!
			   INTERPRETER-CALL:UNASSIGNED?
			   INTERPRETER-CALL:UNBOUND
			   INTERPRETER-CALL:CACHE-ASSIGNMENT
			   INTERPRETER-CALL:CACHE-REFERENCE
			   INTERPRETER-CALL:CACHE-UNASSIGNED?
			   INVOCATION:COMPUTED-LEXPR
			   INVOCATION:CACHE-REFERENCE
			   INVOCATION:LOOKUP))))))))

(define (matching-suffixes x y)
  (let loop
      ((rx (bblock-reversed-instructions x))
       (ry (bblock-reversed-instructions y))
       (wx false)
       (wy false)
       (e '())
       (adjustments '()))
    (if (or (null? rx) (null? ry))
	(values wx wy adjustments)
	(with-values
	    (lambda ()
	      (match-rtl (rinst-rtl (car rx)) (rinst-rtl (car ry)) e))
	  (lambda (e adjustment)
	    (if (eq? e 'FAILURE)
		(values wx wy adjustments)
		(let ((adjustments
		       (if adjustment
			   (cons adjustment adjustments)
			   adjustments)))
		  (if (for-all? e (lambda (b) (eqv? (car b) (cdr b))))
		      (loop (cdr rx) (cdr ry)
			    (car rx) (car ry)
			    e adjustments)
		      (loop (cdr rx) (cdr ry)
			    wx wy
			    e adjustments)))))))))

;;;; RTL Instruction Matching

(define (match-rtl x y e)
  (cond ((not (eq? (rtl:expression-type x) (rtl:expression-type y)))
	 (values 'FAILURE false))
	((rtl:assign? x)
	 (values
	  (let ((ax (rtl:assign-address x)))
	    (let ((e (match ax (rtl:assign-address y) e)))
	      (if (eq? e 'FAILURE)
		  'FAILURE
		  (match (rtl:assign-expression x)
			 (rtl:assign-expression y)
			 (remove-from-environment!
			  e
			  (if (rtl:pseudo-register-expression? ax)
			      (list (rtl:register-number ax))
			      '()))))))
	  false))
	((and (rtl:invocation? x)
	      (rtl:invocation:continuation-unimportant? x)
	      (not (eqv? (rtl:invocation-continuation x)
			 (rtl:invocation-continuation y))))
	 (let ((x* (rtl:map-subexpressions x identity-procedure))
	       (y* (rtl:map-subexpressions y identity-procedure)))
	   (rtl:set-invocation-continuation! x* false)
	   (rtl:set-invocation-continuation! y* false)
	   (values (match x* y* e)
		   (lambda ()
		     (rtl:set-invocation-continuation! x false)
		     (rtl:set-invocation-continuation! y false)))))
	((and (rtl:profile-data? x)
	      (rtl:profile-data? y))
	 ;; We test profile data here because the constants do not have to be
	 ;; EQV?
	 (if (equal? (rtl:profile-data-data x) (rtl:profile-data-data y))
	     (values e false)
	     (values 'FAILURE false)))
	(else
	 (values (match x y e) false))))

(define (rtl:invocation:continuation-unimportant? expression)
  ;; This should probably be in the back-end where we decide on a
  ;; case-by-case basis whether or not to generate code referencing
  ;; the continuation label.
  (not (memq (rtl:expression-type expression)
	     '(INVOCATION:PROCEDURE
	       INVOCATION:NEW-APPLY
	       INVOCATION:REGISTER
	       INVOCATION:UUO-LINK
	       INVOCATION:GLOBAL-LINK))))

(define (remove-from-environment! e keys)
  (if (null? keys)
      e
      (remove-from-environment! (del-assv! (car keys) e) (cdr keys))))

(define (match x y e)
  (cond ((pair? x)
	 (let ((type (car x)))
	   (if (and (pair? y) (eq? type (car y)))
	       (case type
		 ((CONSTANT)
		  (if (eqv? (cadr x) (cadr y))
		      e
		      'FAILURE))
		 ((REGISTER)
		  (let ((rx (cadr x))
			(ry (cadr y)))
		    (if (pseudo-register? rx)
			(if (pseudo-register? ry)
			    (let ((entry (assv rx e)))
			      (cond ((not entry) (cons (cons rx ry) e))
				    ((eqv? (cdr entry) ry) e)
				    (else 'FAILURE)))
			    'FAILURE)
			(if (pseudo-register? ry)
			    'FAILURE
			    (if (eqv? rx ry)
				e
				'FAILURE)))))
		 (else
		  (let loop ((x (cdr x)) (y (cdr y)) (e e))
		    (cond ((pair? x)
			   (if (pair? y)
			       (let ((e (match (car x) (car y) e)))
				 (if (eq? e 'FAILURE)
				     'FAILURE
				     (loop (cdr x) (cdr y) e)))
			       'FAILURE))
			  ((eqv? x y) e)
			  (else 'FAILURE)))))
	       'FAILURE)))
	((eqv? x y) e)
	(else 'FAILURE)))