/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.Compiler.IR.Basic

namespace Lean.IR

namespace MaxIndex
/- Compute the maximum index `M` used in a declaration.
   We `M` to initialize the fresh index generator used to create fresh
   variable and join point names.

   Recall that we variable and join points share the same namespace in
   our implementation.
-/

abbrev Collector := Index → Index

@[inline] private def skip : Collector := id
@[inline] private def collect (x : Index) : Collector := fun y => if x > y then x else y
@[inline] private def collectVar (x : VarId) : Collector := collect x.idx
@[inline] private def collectJP (j : JoinPointId) : Collector := collect j.idx
@[inline] private def seq (k₁ k₂ : Collector) : Collector := k₂ ∘ k₁
instance : AndThen Collector := ⟨seq⟩

private def collectArg : Arg → Collector
  | Arg.var x  => collectVar x
  | irrelevant => skip

@[specialize] private def collectArray {α : Type} (as : Array α) (f : α → Collector) : Collector :=
  fun m => as.foldl (fun m a => f a m) m

private def collectArgs (as : Array Arg) : Collector := collectArray as collectArg
private def collectParam (p : Param) : Collector := collectVar p.x
private def collectParams (ps : Array Param) : Collector := collectArray ps collectParam

private def collectExpr : Expr → Collector
  | Expr.ctor _ ys      => collectArgs ys
  | Expr.reset _ x      => collectVar x
  | Expr.reuse x _ _ ys => collectVar x >> collectArgs ys
  | Expr.proj _ x       => collectVar x
  | Expr.uproj _ x      => collectVar x
  | Expr.sproj _ _ x    => collectVar x
  | Expr.fap _ ys       => collectArgs ys
  | Expr.pap _ ys       => collectArgs ys
  | Expr.ap x ys        => collectVar x >> collectArgs ys
  | Expr.box _ x        => collectVar x
  | Expr.unbox x        => collectVar x
  | Expr.lit v          => skip
  | Expr.isShared x     => collectVar x
  | Expr.isTaggedPtr x  => collectVar x

private def collectAlts (f : FnBody → Collector) (alts : Array Alt) : Collector :=
  collectArray alts $ fun alt => f alt.body

partial def collectFnBody : FnBody → Collector
  | FnBody.vdecl x _ v b    => collectVar x >> collectExpr v >> collectFnBody b
  | FnBody.jdecl j ys v b   => collectJP j >> collectFnBody v >> collectParams ys >> collectFnBody b
  | FnBody.set x _ y b      => collectVar x >> collectArg y >> collectFnBody b
  | FnBody.uset x _ y b     => collectVar x >> collectVar y >> collectFnBody b
  | FnBody.sset x _ _ y _ b => collectVar x >> collectVar y >> collectFnBody b
  | FnBody.setTag x _ b     => collectVar x >> collectFnBody b
  | FnBody.inc x _ _ _ b    => collectVar x >> collectFnBody b
  | FnBody.dec x _ _ _ b    => collectVar x >> collectFnBody b
  | FnBody.del x b          => collectVar x >> collectFnBody b
  | FnBody.mdata _ b        => collectFnBody b
  | FnBody.case _ x _ alts  => collectVar x >> collectAlts collectFnBody alts
  | FnBody.jmp j ys         => collectJP j >> collectArgs ys
  | FnBody.ret x            => collectArg x
  | FnBody.unreachable      => skip

partial def collectDecl : Decl → Collector
  | Decl.fdecl _ xs _ b  => collectParams xs >> collectFnBody b
  | Decl.extern _ xs _ _ => collectParams xs

end MaxIndex

def FnBody.maxIndex (b : FnBody) : Index :=
  MaxIndex.collectFnBody b 0

def Decl.maxIndex (d : Decl) : Index :=
  MaxIndex.collectDecl d 0

namespace FreeIndices
/- We say a variable (join point) index (aka name) is free in a function body
   if there isn't a `FnBody.vdecl` (`Fnbody.jdecl`) binding it. -/

abbrev Collector := IndexSet → IndexSet → IndexSet

@[inline] private def skip : Collector :=
  fun bv fv => fv

@[inline] private def collectIndex (x : Index) : Collector :=
  fun bv fv => if bv.contains x then fv else fv.insert x

@[inline] private def collectVar (x : VarId) : Collector :=
  collectIndex x.idx

@[inline] private def collectJP (x : JoinPointId) : Collector :=
  collectIndex x.idx

@[inline] private def withIndex (x : Index) : Collector → Collector :=
  fun k bv fv => k (bv.insert x) fv

@[inline] private def withVar (x : VarId) : Collector → Collector :=
  withIndex x.idx

@[inline] private def withJP (x : JoinPointId) : Collector → Collector :=
  withIndex x.idx

def insertParams (s : IndexSet) (ys : Array Param) : IndexSet :=
  ys.foldl (init := s) fun s p => s.insert p.x.idx

@[inline] private def withParams (ys : Array Param) : Collector → Collector :=
  fun k bv fv => k (insertParams bv ys) fv

@[inline] private def seq : Collector → Collector → Collector :=
  fun k₁ k₂ bv fv => k₂ bv (k₁ bv fv)

instance : AndThen Collector := ⟨seq⟩

private def collectArg : Arg → Collector
  | Arg.var x  => collectVar x
  | irrelevant => skip

@[specialize] private def collectArray {α : Type} (as : Array α) (f : α → Collector) : Collector :=
  fun bv fv => as.foldl (fun fv a => f a bv fv) fv

private def collectArgs (as : Array Arg) : Collector :=
  collectArray as collectArg

private def collectExpr : Expr → Collector
  | Expr.ctor _ ys      => collectArgs ys
  | Expr.reset _ x      => collectVar x
  | Expr.reuse x _ _ ys => collectVar x >> collectArgs ys
  | Expr.proj _ x       => collectVar x
  | Expr.uproj _ x      => collectVar x
  | Expr.sproj _ _ x    => collectVar x
  | Expr.fap _ ys       => collectArgs ys
  | Expr.pap _ ys       => collectArgs ys
  | Expr.ap x ys        => collectVar x >> collectArgs ys
  | Expr.box _ x        => collectVar x
  | Expr.unbox x        => collectVar x
  | Expr.lit v          => skip
  | Expr.isShared x     => collectVar x
  | Expr.isTaggedPtr x  => collectVar x

private def collectAlts (f : FnBody → Collector) (alts : Array Alt) : Collector :=
  collectArray alts $ fun alt => f alt.body

partial def collectFnBody : FnBody → Collector
  | FnBody.vdecl x _ v b    => collectExpr v >> withVar x (collectFnBody b)
  | FnBody.jdecl j ys v b   => withParams ys (collectFnBody v) >> withJP j (collectFnBody b)
  | FnBody.set x _ y b      => collectVar x >> collectArg y >> collectFnBody b
  | FnBody.uset x _ y b     => collectVar x >> collectVar y >> collectFnBody b
  | FnBody.sset x _ _ y _ b => collectVar x >> collectVar y >> collectFnBody b
  | FnBody.setTag x _ b     => collectVar x >> collectFnBody b
  | FnBody.inc x _ _ _ b    => collectVar x >> collectFnBody b
  | FnBody.dec x _ _ _ b    => collectVar x >> collectFnBody b
  | FnBody.del x b          => collectVar x >> collectFnBody b
  | FnBody.mdata _ b        => collectFnBody b
  | FnBody.case _ x _ alts  => collectVar x >> collectAlts collectFnBody alts
  | FnBody.jmp j ys         => collectJP j >> collectArgs ys
  | FnBody.ret x            => collectArg x
  | FnBody.unreachable      => skip

end FreeIndices

def FnBody.collectFreeIndices (b : FnBody) (vs : IndexSet) : IndexSet :=
  FreeIndices.collectFnBody b {} vs

def FnBody.freeIndices (b : FnBody) : IndexSet :=
  b.collectFreeIndices {}

namespace HasIndex
/- In principle, we can check whether a function body `b` contains an index `i` using
   `b.freeIndices.contains i`, but it is more efficient to avoid the construction
   of the set of freeIndices and just search whether `i` occurs in `b` or not.
-/
def visitVar (w : Index) (x : VarId) : Bool := w == x.idx
def visitJP (w : Index) (x : JoinPointId) : Bool := w == x.idx

def visitArg (w : Index) : Arg → Bool
  | Arg.var x => visitVar w x
  | _         => false

def visitArgs (w : Index) (xs : Array Arg) : Bool :=
  xs.any (visitArg w)

def visitParams (w : Index) (ps : Array Param) : Bool :=
  ps.any (fun p => w == p.x.idx)

def visitExpr (w : Index) : Expr → Bool
  | Expr.ctor _ ys      => visitArgs w ys
  | Expr.reset _ x      => visitVar w x
  | Expr.reuse x _ _ ys => visitVar w x || visitArgs w ys
  | Expr.proj _ x       => visitVar w x
  | Expr.uproj _ x      => visitVar w x
  | Expr.sproj _ _ x    => visitVar w x
  | Expr.fap _ ys       => visitArgs w ys
  | Expr.pap _ ys       => visitArgs w ys
  | Expr.ap x ys        => visitVar w x || visitArgs w ys
  | Expr.box _ x        => visitVar w x
  | Expr.unbox x        => visitVar w x
  | Expr.lit v          => false
  | Expr.isShared x     => visitVar w x
  | Expr.isTaggedPtr x  => visitVar w x

partial def visitFnBody (w : Index) : FnBody → Bool
  | FnBody.vdecl x _ v b    => visitExpr w v || visitFnBody w b
  | FnBody.jdecl j ys v b   => visitFnBody w v || visitFnBody w b
  | FnBody.set x _ y b      => visitVar w x || visitArg w y || visitFnBody w b
  | FnBody.uset x _ y b     => visitVar w x || visitVar w y || visitFnBody w b
  | FnBody.sset x _ _ y _ b => visitVar w x || visitVar w y || visitFnBody w b
  | FnBody.setTag x _ b     => visitVar w x || visitFnBody w b
  | FnBody.inc x _ _ _ b    => visitVar w x || visitFnBody w b
  | FnBody.dec x _ _ _ b    => visitVar w x || visitFnBody w b
  | FnBody.del x b          => visitVar w x || visitFnBody w b
  | FnBody.mdata _ b        => visitFnBody w b
  | FnBody.jmp j ys         => visitJP w j || visitArgs w ys
  | FnBody.ret x            => visitArg w x
  | FnBody.case _ x _ alts  => visitVar w x || alts.any (fun alt => visitFnBody w alt.body)
  | FnBody.unreachable      => false

end HasIndex

def Arg.hasFreeVar (arg : Arg) (x : VarId) : Bool := HasIndex.visitArg x.idx arg
def Expr.hasFreeVar (e : Expr) (x : VarId) : Bool := HasIndex.visitExpr x.idx e
def FnBody.hasFreeVar (b : FnBody) (x : VarId) : Bool := HasIndex.visitFnBody x.idx b

end Lean.IR
