/-
Copyright (c) 2018 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import init.lean.ir.instances init.control.state init.lean.disjoint_set

namespace lean
namespace ir
/-
We need to eliminate Phi nodes before we translate the IR to C/C++.

The procedure is the following. First, for each instruction `x : ty := phi y_1 ... y_n`,
we put `x`, `y_1`, ... `y_n` in the same equivalence class.
Then, we select a representative from each equivalence class and replace each
variable with its representative.
-/
@[reducible] def elim_phi_m (α : Type) := state_t (disjoint_set var) id α

def elim_phi_m.run {α} (a : elim_phi_m α) : α :=
run a (mk_disjoint_set var)

def merge (x y : var) : elim_phi_m unit :=
modify $ λ s, s.merge x y

def find (x : var) : elim_phi_m var :=
do s ← get, return $ s.find x

def group_vars : decl → elim_phi_m unit
| (decl.defn _ bs) := bs.mfor $ λ b, b.phis.mfor $ λ p, p.ys.mfor (merge p.x)
| _                := return ()

def instr.replace_vars : instr → elim_phi_m instr
| (instr.assign x ty y)            := instr.assign <$> find x <*> pure ty <*> find y
| (instr.assign_lit x ty lit)      := instr.assign_lit <$> find x <*> pure ty <*> pure lit
| (instr.assign_unop x ty op y)    := instr.assign_unop <$> find x <*> pure ty <*> pure op <*> find y
| (instr.assign_binop x ty op y z) := instr.assign_binop <$> find x <*> pure ty <*> pure op <*> find y <*> find z
| (instr.unop op x)                := instr.unop op <$> find x
| (instr.call xs f ys)             := instr.call <$> xs.mmap find <*> pure f <*> ys.mmap find
| (instr.cnstr o tag n s)          := instr.cnstr <$> find o <*> pure tag <*> pure n <*> pure s
| (instr.set o i x)                := instr.set <$> find o <*> pure i <*> find x
| (instr.get x o i)                := instr.get <$> find x <*> find o <*> pure i
| (instr.sset o i x)               := instr.sset <$> find o <*> pure i <*> find x
| (instr.sget x ty o i)            := instr.sget <$> find x <*> pure ty <*> find o <*> pure i
| (instr.closure x f ys)           := instr.closure <$> find x <*> pure f <*> ys.mmap find
| (instr.apply x ys)               := instr.apply <$> find x <*> ys.mmap find
| (instr.array a sz c)             := instr.array <$> find a <*> find sz <*> find c
| (instr.array_write a i v)        := instr.array_write <$> find a <*> find i <*> find v
| (instr.sarray a ty sz c)         := instr.sarray <$> find a <*> pure ty <*> find sz <*> find c

def terminator.replace_vars : terminator → elim_phi_m terminator
| (terminator.ret xs)    := terminator.ret <$> xs.mmap find
| (terminator.case x bs) := terminator.case <$> find x <*> pure bs
| j@(terminator.jmp _)   := pure j

def arg.replace_vars (a : arg) : elim_phi_m arg :=
do x ← find a.n, return { n := x, ..a }

def header.replace_vars (h : header) : elim_phi_m header :=
do as ← h.args.mmap arg.replace_vars, return { args := as, ..h }

def block.replace_vars (b : block) : elim_phi_m block :=
do instrs' ← b.instrs.mmap instr.replace_vars,
   term'   ← b.term.replace_vars,
   return
     { phis   := [],
       instrs := instrs',
       term   := term', ..b}

def decl.replace_vars : decl → elim_phi_m decl
| (decl.defn h bs) := decl.defn <$> (header.replace_vars h) <*> bs.mmap block.replace_vars
| other            := pure other

def elim_phi_aux (d : decl) : elim_phi_m decl :=
group_vars d >> d.replace_vars

def elim_phi (d : decl) : decl :=
(elim_phi_aux d).run

end ir
end lean
