;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; This software is derived from the CMU CL system, which was
;;;; written at Carnegie Mellon University and released into the
;;;; public domain. The software is in the public domain and is
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
;;;; files for more information.

(in-package "SB-C")

(defstruct (equality-constraint
            (:include constraint)
            (:constructor make-equality-constraint
                (number operator x y not-p
                 &aux (kind 'equality))))

  (operator nil :type symbol))

(defun find-equality-constraint (operator x y not-p)
  (let ((constraints (lambda-var-equality-constraints x)))
    (when constraints
      (loop for con across constraints
            when (and (eq (equality-constraint-operator con) operator)
                      (eq (constraint-not-p con) not-p)
                      (eq (constraint-x con) x)
                      (eq (constraint-y con) y))
            return con))))

(defun find-or-create-equality-constraint (operator x y not-p)
  (or (find-equality-constraint operator x y not-p)
      (let ((new (make-equality-constraint (length *constraint-universe*)
                                           operator
                                           x y not-p)))
        (vector-push-extend new *constraint-universe* (1+ (length *constraint-universe*)))
        (flet ((add (var)
                 (conset-adjoin new (lambda-var-constraints var))
                 (macrolet ((ensure-vec (place)
                              `(or ,place
                                   (setf ,place
                                         (make-array 8 :adjustable t :fill-pointer 0)))))
                   (vector-push-extend new (ensure-vec (lambda-var-equality-constraints var))))))
          (add x)
          (when (lambda-var-p y)
            (add y)))
        new)))

(defun add-eq-constraint (var lvar gen)
  (let ((var2 (ok-lvar-lambda-var lvar gen)))
    (when var2
      (conset-adjoin (find-or-create-equality-constraint 'eq var var2 nil) gen))))

(defun add-equality-constraints (operator args constraints
                                 consequent-constraints
                                 alternative-constraints)
  (case operator
    ((eq eql char= two-arg-char-equal
      > < =)
     (when (= (length args) 2)
       (let* ((x (ok-lvar-lambda-var (first args) constraints))
              (second (second args))
              (y (if (constant-lvar-p second)
                     (find-constant (lvar-value second))
                     (ok-lvar-lambda-var second constraints))))
         (when (and x y)
           ;; TODO: inherit constraints
           (let ((con (find-or-create-equality-constraint operator x y nil))
                 (not-con (find-or-create-equality-constraint operator x y t)))
             (conset-adjoin con consequent-constraints)
             (conset-adjoin not-con alternative-constraints))))))))


(defun find-ref-equality-constraint (operator lvar1 lvar2 &optional (commutative t))
  (let ((ref1 (principal-lvar-use lvar1))
        (ref2 (principal-lvar-use lvar2)))
    (when (and (ref-p ref1)
               (ref-p ref2))
      (let ((leaf1 (ref-leaf ref1))
            (leaf2 (ref-leaf ref2)))
        (flet ((find-constraint (ref)
                 (loop for con in (ref-constraints ref)
                       when (and (equality-constraint-p con)
                                 (eq (equality-constraint-operator con) operator)
                                 (or (and (eq (constraint-x con) leaf1)
                                          (eq (constraint-y con) leaf2))
                                     (and commutative
                                          (eq (constraint-x con) leaf2)
                                          (eq (constraint-y con) leaf1))))
                       return con))
               (has-sets (leaf)
                 (and (lambda-var-p leaf)
                      (lambda-var-sets leaf))))
          (let ((ref1-con (find-constraint ref1)))
            (when (and ref1-con
                       ;; If the variables are set both references
                       ;; need to have the same constraint, otherwise
                       ;; one the references may be done before the
                       ;; set.
                       (or (and (not (has-sets leaf1))
                                (not (has-sets leaf2)))
                           (eq ref1-con (find-constraint ref2))))
              ref1-con)))))))

(defun try-equality-constraint (call)
  (let ((constraint (fun-info-equality-constraint (basic-combination-fun-info call)))
        (lvar (node-lvar call)))
    (when (and constraint
               lvar)
      (let ((result (funcall constraint call)))
        (unless (eq result :give-up)
          (replace-combination-with-constant result call)
          t)))))

(defmacro equality-constraint (operator &optional type (commutative t))
  `(let ((constraint (find-ref-equality-constraint ',operator x y ,commutative)))
     ,(ecase type
        ((nil)
         `(when constraint
            (setf result (not (constraint-not-p constraint)))
            t))
        (true
         `(when (and constraint
                     (not (constraint-not-p constraint)))
            (setf result t)))
        (false
         `(when (and constraint
                     (constraint-not-p constraint))
            (setf result nil)
            t))
        (inverse
         `(when (and constraint
                     (not (constraint-not-p constraint)))
            (setf result nil)
            t)))))

(defoptimizer (eql equality-constraint) ((x y))
  (let ((result :give-up))
    (or
     (equality-constraint eq true)
     (equality-constraint eql)
     (equality-constraint char=)
     (equality-constraint two-arg-char-equal false)
     (equality-constraint > inverse)
     (equality-constraint < inverse)
     (equality-constraint = false))
    result))

(defoptimizer (eq equality-constraint) ((x y))
  (let ((result :give-up))
    (or
     (equality-constraint eq)
     (equality-constraint eql)
     (equality-constraint char=)
     (equality-constraint two-arg-char-equal false)
     (equality-constraint > inverse)
     (equality-constraint < inverse)
     (equality-constraint = false))
    result))

(defoptimizer (= equality-constraint) ((x y))
  (let ((result :give-up))
    (or
     (equality-constraint =)
     (equality-constraint eq true)
     (equality-constraint eql true)
     (equality-constraint > inverse)
     (equality-constraint < inverse))
    result))

(defoptimizer (char= equality-constraint) ((x y))
  (let ((result :give-up))
    (or
     (equality-constraint char=)
     (equality-constraint eq)
     (equality-constraint eql)
     (equality-constraint two-arg-char-equal false))
    result))

(defoptimizer (two-arg-char-equal equality-constraint) ((x y))
  (let ((result :give-up))
    (or
     (equality-constraint two-arg-char-equal)
     (equality-constraint char= true)
     (equality-constraint eq true)
     (equality-constraint eql true))
    result))

(defoptimizer (> equality-constraint) ((x y))
  (let ((result :give-up))
    (or
     (equality-constraint > nil nil)
     (equality-constraint < inverse nil)
     (equality-constraint eq inverse)
     (equality-constraint eql inverse)
     (equality-constraint = inverse))
    result))

(defoptimizer (< equality-constraint) ((x y))
  (let ((result :give-up))
    (or
     (equality-constraint < nil nil)
     (equality-constraint > inverse nil)
     (equality-constraint eq inverse)
     (equality-constraint eql inverse)
     (equality-constraint = inverse))
    result))

(defoptimizer (- equality-constraint) ((x y) node)
  (let ((integer (specifier-type 'integer)))
    (when (and (types-equal-or-intersect (lvar-type x) integer)
               (types-equal-or-intersect (lvar-type y) integer))
      (macrolet ((f (op x y pos)
                   `(let ((constr (find-ref-equality-constraint ',op ,x ,y nil)))
                      (when constr
                        (if (constraint-not-p constr)
                            ,@(if pos
                                  `((go POS) (go NEG))
                                  `((go NEG) (go POS))))))))
        (tagbody
           (f < x y t)
           (f > x y nil)
           (f > y x t)
           (f < y x nil)
           (go DONE)
         POS
           (derive-node-type node (specifier-type '(integer 0)))
           (go DONE)
         NEG
           (derive-node-type node (specifier-type '(integer * (0))))
         DONE))))
  :give-up)
