(* Copyright (C) 2017,2019-2020 Matthew Fluet.
 * Copyright (C) 1999-2006 Henry Cejtin, Matthew Fluet, Suresh
 *    Jagannathan, and Stephen Weeks.
 * Copyright (C) 1997-2000 NEC Research Institute.
 *
 * MLton is released under a HPND-style license.
 * See the file MLton-LICENSE for details.
 *)

functor AbstractValue (S: ABSTRACT_VALUE_STRUCTS): ABSTRACT_VALUE = 
struct

open S
open Sxml

structure Dset = DisjointSet

structure Lambda =
   struct
      datatype t = Lambda of {lambda: Sxml.Lambda.t,
                              hash: Word.t}

      val newHash = Random.word

      fun new lambda = Lambda {lambda = lambda,
                               hash = newHash ()}

      fun hash (Lambda {hash, ...}) = hash

      fun dest (Lambda {lambda, ...}) = lambda

      fun equals (Lambda r, Lambda r') =
         #hash r = #hash r'
         andalso Sxml.Lambda.equals (#lambda r, #lambda r')

      fun layout (Lambda {lambda, ...}) =
         let open Layout
         in seq [str "lambda ", Sxml.Var.layout (Sxml.Lambda.arg lambda)]
         end
   end

structure Lambdas = UniqueSet (structure Element = Lambda
                               val cacheSize: int = 5
                               val bits: int = 13)

structure LambdaNode:
   sig
      type t

      val addHandler: t * (Lambda.t -> unit) -> unit
      val coerce: {from: t, to: t} -> unit
      val lambda: Sxml.Lambda.t -> t
      val layout: t -> Layout.t
      val new: unit -> t
      val toSet: t -> Lambdas.t
      val unify: t * t -> unit
   end =
   struct
      datatype t = LambdaNode of {me: Lambdas.t ref,
                                  handlers: (Lambda.t -> unit) list ref,
                                  coercedTo: t list ref} Dset.t

      fun toSet (LambdaNode d) = !(#me (Dset.! d))

      val layout = Lambdas.layout o toSet

      fun newSet s = LambdaNode (Dset.singleton {me = ref s,
                                                 handlers = ref [],
                                                 coercedTo = ref []})

      fun new () = newSet Lambdas.empty

      fun lambda l = newSet (Lambdas.singleton (Lambda.new l))

      fun handles (h: Lambda.t -> unit, s: Lambdas.t): unit =
         Lambdas.foreach (s, fn l => h l)

      fun handless (hs: (Lambda.t -> unit) list, s: Lambdas.t): unit =
         List.foreach (hs, fn h => handles (h, s))

      fun addHandler (LambdaNode d, h: Lambda.t -> unit) =
         let val {me, handlers, ...} = Dset.! d
         in List.push (handlers, h)
            ; handles (h, !me)
         end

      fun send (LambdaNode d, s): unit =
         let val {me, coercedTo, handlers, ...} = Dset.! d
            val diff = Lambdas.- (s, !me)
         in if Lambdas.isEmpty diff
               then ()
            else (me := Lambdas.+ (diff, !me)
                  ; List.foreach (!coercedTo, fn to => send (to, diff))
                  ; handless (!handlers, diff))
         end

      val send =
         Trace.trace2 
         ("AbstractValue.LambdaNode.send", 
          layout, Lambdas.layout, Unit.layout)
         send

      fun equals (LambdaNode d, LambdaNode d') = Dset.equals (d, d')

      fun coerce {from = from as LambdaNode d, to: t}: unit =
         if equals (from, to)
            then ()
         else let
                 val {me, coercedTo, ...} = Dset.! d
              in
                 if List.exists (!coercedTo, fn ls => equals (ls, to))
                    then ()
                 else (List.push (coercedTo, to)
                       ; send (to, !me))
              end

      fun update (c, h, diff) =
         if Lambdas.isEmpty diff
            then ()
         else (List.foreach (c, fn to => send (to, diff))
               ; handless (h, diff))

      fun unify (LambdaNode d, LambdaNode d'): unit =
         if Dset.equals (d, d')
            then ()
         else
            let
               val {me = ref m, coercedTo = ref c, handlers = ref h, ...} =
                  Dset.! d
               val {me = ref m', coercedTo = ref c', handlers = ref h', ...} =
                  Dset.! d'
               val diff = Lambdas.- (m, m')
               val diff' = Lambdas.- (m', m)
            in Dset.union (d, d')
               ; (Dset.:=
                  (d, {me = ref (if Lambdas.isEmpty diff
                                   then m'
                                else Lambdas.+ (m', diff)),
                       coercedTo = ref (List.fold
                                        (c', c, fn (n', ac) =>
                                         if List.exists (c, fn n =>
                                                         equals (n, n'))
                                            then ac
                                         else n' :: ac)),
                       handlers = ref (List.appendRev (h, h'))}))
               ; update (c, h, diff')
               ; update (c', h', diff)
            end

(*
      val unify =
         Trace.trace2 
         ("AbstractValue.LambdaNode.unify", layout, layout, Unit.layout) 
         unify
*)       
   end

structure UnaryTycon =
   struct
      datatype t = Array | Ref | Vector | Weak

      val toString =
         fn Array => "Array"
          | Ref => "Ref"
          | Vector => "Vector"
          | Weak => "Weak"

      val equals: t * t -> bool = op =

      val layout = Layout.str o toString
   end

datatype tree =
   Lambdas of LambdaNode.t
 | Tuple of t vector
 | Type of Type.t
 | Unify of UnaryTycon.t * t

withtype t = {tree: tree,
              ty: Type.t,
              ssaType: Ssa.Type.t option ref} Dset.t

fun new (tree: tree, ty: Type.t): t =
   Dset.singleton {ssaType = ref NONE,
                   tree = tree,
                   ty = ty}

local
   fun make sel : t -> 'a = sel o Dset.!
in
   val ssaType = make #ssaType
   val tree = make #tree
   val ty = make #ty
end

fun layout v =
   let open Layout
   in case tree v of
      Type t => seq [str "Type ", Type.layout t]
    | Unify (t, v) => paren (seq [UnaryTycon.layout t, str " ", layout v])
    | Tuple vs => Vector.layout layout vs
    | Lambdas l => LambdaNode.layout l
   end

fun isEmpty v =
   case tree v of
      Lambdas n => Lambdas.isEmpty (LambdaNode.toSet n)
    | Tuple vs => Vector.exists (vs, isEmpty)
    | Unify (UnaryTycon.Ref, v) => isEmpty v
    | _ => false

(* used in closure converter *)
fun equals (v, v') =
   Dset.equals (v, v')
   orelse
   (case (tree v,      tree v') of
       (Type t,        Type t')   =>
          if Type.equals (t, t')
             then true
          else Error.bug "AbstractValue.equals: different types"
     | (Unify (t, v), Unify (t', v'))     =>
          UnaryTycon.equals (t, t') andalso equals (v, v')
     | (Tuple vs,  Tuple vs')  => Vector.forall2 (vs, vs', equals)
     | (Lambdas n, Lambdas n') => Lambdas.equals (LambdaNode.toSet n,
                                                 LambdaNode.toSet n')
     | _ => Error.bug "AbstractValue.equals: different values")

fun addHandler (v, h) =
   case tree v of
      Lambdas n => LambdaNode.addHandler (n, h)
    | _ => Error.bug "AbstractValue.addHandler: non-lambda"

local
   val {hom, destroy} =
      Type.makeMonoHom
      {con = fn (t, tycon, vs) =>
       let val new = fn tree => new (tree, t)
       in if Tycon.equals (tycon, Tycon.arrow)
             then {isFirstOrder = false,
                   make = fn () => new (Lambdas (LambdaNode.new ()))}
          else
             if Vector.forall (vs, #isFirstOrder)
                then {isFirstOrder = true,
                      make = let val v = new (Type t)
                             in fn () => v
                             end}
             else
                {isFirstOrder = false,
                 make = let
                           fun mutable mt =
                              let val make = #make (Vector.first vs)
                              in fn () => new (Unify (mt, make ()))
                              end
                        in if Tycon.equals (tycon, Tycon.reff)
                              then mutable UnaryTycon.Ref
                           else if Tycon.equals (tycon, Tycon.array)
                                   then mutable UnaryTycon.Array
                           else if Tycon.equals (tycon, Tycon.vector)
                                   then mutable UnaryTycon.Vector
                           else if Tycon.equals (tycon, Tycon.weak)
                                   then mutable UnaryTycon.Weak
                           else if Tycon.equals (tycon, Tycon.tuple)
                                   then (fn () =>
                                         new (Tuple
                                              (Vector.map (vs, fn {make, ...} =>
                                                           make ()))))
                           else Error.bug "AbstractValue.fromType: non-arrow"
                        end}
       end}
in
   val destroy = destroy
   val typeIsFirstOrder = #isFirstOrder o hom
   fun fromType t = #make (hom t) ()
end

val fromType = Trace.trace ("AbstractValue.fromType", Type.layout, layout) fromType

fun tuple (vs: t vector): t = new (Tuple vs,
                                   Type.tuple (Vector.map (vs, ty)))

fun select (v, i) =
   case tree v of
      Type t => fromType (Vector.sub (Type.deTuple t, i))
    | Tuple vs => Vector.sub (vs, i)
    | _ => Error.bug "AbstractValue.select: expected tuple"

fun deRef v =
   case tree v of
      Type t => fromType (Type.deRef t)
    | Unify (_, v) => v
    | _ => Error.bug "AbstractValue.deRef"

val deRef = Trace.trace ("AbstractValue.deRef", layout, layout) deRef

fun deWeak v =
   case tree v of
      Type t => fromType (Type.deWeak t)
    | Unify (_, v) => v
    | _ => Error.bug "AbstractValue.deWeak"

fun deArray v =
   case tree v of
      Type t => fromType (Type.deArray t)
    | Unify (_, v) => v
    | _ => Error.bug "AbstractValue.deArray"

fun deVector v =
   case tree v of
      Type t => fromType (Type.deVector t)
    | Unify (_, v) => v
    | _ => Error.bug "AbstractValue.deVector"

fun lambda (l: Sxml.Lambda.t, t: Type.t): t =
   new (Lambdas (LambdaNode.lambda l), t)       

fun unify (v, v') =
   if Dset.equals (v, v')
      then ()
   else let val t = tree v
            val t' = tree v'
        in Dset.union (v, v')
           ; (case (t, t') of
                 (Type t, Type t') => if Type.equals (t, t')
                                         then ()
                                      else Error.bug "AbstractValue.unify: different types"
               | (Unify (_, v), Unify (_, v')) => unify (v, v')
               | (Tuple vs, Tuple vs') => Vector.foreach2 (vs, vs', unify)
               | (Lambdas l, Lambdas l') => LambdaNode.unify (l, l')
               | _ => Error.bug "AbstractValue.unify: different values")
        end

val unify = Trace.trace2 ("AbstractValue.unify", layout, layout, Unit.layout) unify

fun coerce {from: t, to: t}: unit =
   if Dset.equals (from, to)
      then ()
   else (case (tree from, tree to) of
            (Type t,    Type t')    => if Type.equals (t, t')
                                          then ()
                                       else Error.bug "coerce"
          | (Unify _, Unify _) =>
               (* Can't do a coercion for vectors, since that would imply
                * walking over the entire vector and coercing each element
                *)
               unify (from, to)
          | (Tuple vs,  Tuple vs')  =>
               Vector.foreach2 (vs, vs', fn (v, v') =>
                                coerce {from = v, to = v'})
          | (Lambdas l, Lambdas l') => LambdaNode.coerce {from = l, to = l'}
          | _ => Error.bug "AbstractValue.coerce: different values")

val coerce = Trace.trace ("AbstractValue.coerce",
                          fn {from, to} =>
                          let open Layout
                          in record [("from", layout from),
                                     ("to" , layout to)]
                          end, Unit.layout) coerce

structure Dest =
   struct
      datatype dest =
         Array of t
       | Lambdas of Lambdas.t
       | Ref of t
       | Tuple of t vector
       | Type of Type.t
       | Vector of t
       | Weak of t
   end

fun dest v =
   case tree v of
      Type t => Dest.Type t
    | Unify (mt, v) => (case mt of
                           UnaryTycon.Array => Dest.Array v
                         | UnaryTycon.Ref => Dest.Ref v
                         | UnaryTycon.Vector => Dest.Vector v
                         | UnaryTycon.Weak => Dest.Weak v)
    | Tuple vs => Dest.Tuple vs
    | Lambdas l => Dest.Lambdas (LambdaNode.toSet l)

open Dest

(*---------------------------------------------------*)
(*                     primApply                     *)
(*---------------------------------------------------*)

val {get = serialValue: Type.t -> t, ...} =
   Property.get (Type.plist, Property.initFun fromType)

fun primApply {prim: Type.t Prim.t, args: t vector, resultTy: Type.t}: t =
   let 
      fun result () = fromType resultTy
      fun typeError () =
         (Control.message
          (Control.Silent, fn () =>
           let open Layout
           in align [seq [str "prim: ", Prim.layout prim],
                     seq [str "args: ", Vector.layout layout args]]
           end)
          ; Error.bug "AbstractValue.primApply: type error")
      fun arg i = Vector.sub (args, i)
      val n = Vector.length args
      fun oneArg () =
         if n = 1
            then arg 0
         else Error.bug "AbstractValue.primApply.oneArg"
      fun twoArgs () =
         if n = 2
            then (arg 0, arg 1)
         else Error.bug "AbstractValue.primApply.twoArgs"
      fun threeArgs () =
         if n = 3
            then (arg 0, arg 1, arg 2)
         else Error.bug "AbstractValue.primApply.threeArgs"
      fun fiveArgs () =
         if n = 5
            then (arg 0, arg 1, arg 2, arg 3, arg 4)
         else Error.bug "AbstractValue.primApply.fiveArgs"
   in
      case prim of
         Prim.Array_array =>
            let
                val r = result ()
                val _ =
                   case dest r of
                      Array x => Vector.foreach (args, fn arg => coerce {from = arg, to = x})
                    | Type _ => ()
                    | _ => typeError ()
            in
               r
            end
       | Prim.Array_copyArray =>
            let val (da, _, sa, _, _) = fiveArgs ()
            in (case (dest da, dest sa) of
                   (Array dx, Array sx) => unify (dx, sx)
                 | (Type _, Type _) => ()
                 | _ => typeError ()
                ; result ())
            end
       | Prim.Array_copyVector =>
            let val (da, _, sa, _, _) = fiveArgs ()
            in (case (dest da, dest sa) of
                   (Array dx, Vector sx) => unify (dx, sx)
                 | (Type _, Type _) => ()
                 | _ => typeError ()
                ; result ())
            end
       | Prim.Array_toArray =>
            let val r = result ()
            in (case (dest (oneArg ()), dest r) of
                   (Type _, Type _) => ()
                 | (Array x, Array y) =>
                      (* Can't do a coercion here because that would imply
                       * walking over each element of the array and coercing it.
                       *)
                      unify (x, y)
                 | _ => typeError ())
               ; r
            end
       | Prim.Array_toVector =>
            let val r = result ()
            in (case (dest (oneArg ()), dest r) of
                   (Type _, Type _) => ()
                 | (Array x, Vector y) =>
                      (* Can't do a coercion here because that would imply
                       * walking over each element of the array and coercing it.
                       *)
                      unify (x, y)
                 | _ => typeError ())
               ; r
            end
       | Prim.Array_sub =>
            (case dest (#1 (twoArgs ())) of
                Array x => x
              | Type _ => result ()
              | _ => typeError ())
       | Prim.Array_update =>
            let val (a, _, x) = threeArgs ()
            in (case dest a of
                   Array x' => coerce {from = x, to = x'} (* unify (x, x') *)
                 | Type _ => ()
                 | _ => typeError ())
               ; result ()
            end
       | Prim.MLton_deserialize => serialValue resultTy
       | Prim.MLton_serialize =>
            let val arg = oneArg ()
            in coerce {from = arg, to = serialValue (ty arg)}
               ; result ()
            end
       | Prim.Ref_assign =>
            let val (r, x) = twoArgs ()
            in (case dest r of
                   Ref x' => coerce {from = x, to = x'} (* unify (x, x') *)
                 | Type _ => ()
                 | _ => typeError ())
               ; result ()
            end
       | Prim.Ref_deref => (case dest (oneArg ()) of
                               Ref v => v
                             | Type _ => result ()
                             | _ => typeError ())
       | Prim.Ref_ref =>
            let
               val r = result ()
               val _ = 
                  case dest r of
                     Ref x => coerce {from = oneArg (), to = x} (* unify (oneArg (), x) *)
                   | Type _ => ()
                   | _ => typeError ()
            in
               r
            end
       | Prim.Vector_sub =>
            (case dest (#1 (twoArgs ())) of
                Vector x => x
              | Type _ => result ()
              | _ => typeError ())
       | Prim.Vector_vector =>
            let
                val r = result ()
                val _ =
                   case dest r of
                      Vector x => Vector.foreach (args, fn arg => coerce {from = arg, to = x})
                    | Type _ => ()
                    | _ => typeError ()
            in
               r
            end
       | Prim.Weak_get =>
            (case dest (oneArg ()) of
                Weak v => v
              | Type _ => result ()
              | _ => typeError ())
       | Prim.Weak_new =>
            let
               val r = result ()
               val _ =
                  case dest r of
                     Type _ => ()
                   | Weak x => coerce {from = oneArg (), to = x}
                   | _ => typeError ()
            in
               r
            end
       | _ => result ()
   end

end
