--------------------------------------------------------------------------------
-- Copyright 2001-2012, Daan Leijen, Bastiaan Heeren, Jurriaan Hage. This file 
-- is distributed under the terms of the BSD3 License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
--------------------------------------------------------------------------------
--  $Id: Inline.hs 291 2012-11-08 11:27:33Z heere112 $
module Lvm.Asm.Inline (asmInline) where

import Data.Maybe
import Lvm.Asm.Data
import Lvm.Asm.Occur ( asmOccur )
import Lvm.Common.Id       
import Lvm.Common.IdMap

{---------------------------------------------------------------
  Inline environment maps identifiers to their definition
---------------------------------------------------------------}
type Env  = IdMap Expr

removeIds :: [Id] -> IdMap a -> IdMap a
removeIds = flip (foldr deleteMap)

{---------------------------------------------------------------
  asmInline
---------------------------------------------------------------}
asmInline :: AsmModule -> AsmModule
asmInline = fmap inlineTop . asmOccur

inlineTop :: Top -> Top
inlineTop (Top params expr)
  = Top params (inlineExpr emptyMap expr)

inlineExpr :: Env -> Expr -> Expr
inlineExpr env expr
  = case expr of
      -- dead variable
      Let _ (Note (Occur Never) _) e2
                    -> inlineExpr env e2
      -- once
      Let x (Note (Occur Once) e1) e2
                    -> let e1' = inlineExpr env e1  -- de-annotate
                       in  inlineExpr (extendMap x e1' env) e2

      -- trivial, inline everywhere
      Let x e1 e2   | trivial e1
                    -> let e1' = inlineExpr env (deAnnotate e1)
                       in inlineExpr (extendMap x e1' env) e2
                       
      Eval x e1 e2  | whnfTrivial e1
                    -> let e1' = inlineExpr env (deAnnotate e1)
                       in inlineExpr (extendMap x e1' env) e2

      -- inline-able let! binding?
      Eval x (Note (Occur Once) e1) e2  
                    -> let e1' = inlineExpr env e1 -- de-annotate
                       in if firstuse x e2
                           -- firstuse is true, we can inline immediately
                           then let env' = extendMap x (Eval x e1' (Ap x [])) env  -- NOTE: should we use a fresh id?
                                in inlineExpr env' e2
                           else let e2'  = inlineExpr env e2
                                in if firstuse x e2'
                                    -- firstuse became true after inlining! re-inline this definition again (is this too expensive?)
                                    then let env' = extendMap x (Eval x e1' (Ap x [])) emptyMap  -- NOTE: should we use a fresh id?
                                         in inlineExpr env' e2'
                                    -- otherwise, don't inline this definition
                                    else Eval x (Note (Occur Once) e1') e2'
      
      -- basic cases
      Let x e1 e2   -> let env' = deleteMap x env
                       in Let x (inlineExpr env e1) (inlineExpr env' e2)
      
      Eval x e1 e2  -> let env' = deleteMap x env 
                       in Eval x (inlineExpr env e1) (inlineExpr env' e2)

      LetRec bs e   -> let (bs',env') = inlineBinds env bs 
                       in LetRec bs' (inlineExpr env' e)

      Match x alts  -> case lookupMap x env of
                         Just e  -> -- trivial inlining of a let! binding leads to this configuration.
                                    -- a case-of-known transformation would actually remove this match.
                                    Eval x (Note (Occur Once) e) (Match x (inlineAlts env alts))
                         Nothing -> Match x (inlineAlts env alts)

      Ap x []       -> fromMaybe (Ap x []) (lookupMap x env)
      Ap x args     -> let args0 = inlineExprs env args
                       in case lookupMap x env of
                            Just e   -> case e of
                                          Ap id1 args1 -> Ap id1 (args1 ++ args0)     -- flatten applications
                                          Eval id1 e1 (Ap id2 [])  | id1==id2         -- special case for the strict inliner
                                                       -> Eval id1 e1 (Ap id1 args0)   
                                          _            -> Let x e (Ap x args)       -- don't inline!
                            Nothing  -> Ap x args0
      Con con args  -> Con (inlineCon env con)  (inlineExprs env args)
      Prim x args   -> Prim x (inlineExprs env args)
      Lit _         -> expr
      Note note e   -> Note note (inlineExpr env e)

inlineCon :: Env -> Con Expr -> Con Expr
inlineCon env con
  = case con of
      ConTag tag arity  -> ConTag (inlineExpr env tag) arity
      _                 -> con

inlineExprs :: Env -> [Expr] -> [Expr]
inlineExprs env exprs
  = [inlineExpr env expr | expr <- exprs]

inlineBinds :: Env -> [(Id,Expr)] -> ([(Id,Expr)],Env)
inlineBinds env binds 
  = let env' = removeIds (map fst binds) env
    in ([(x,inlineExpr env' e) | (x,e) <- binds], env')

inlineAlts :: Env -> [Alt] -> [Alt]
inlineAlts env alts
  = [inlineAlt env alt | alt <- alts]

inlineAlt :: IdMap Expr -> Alt -> Alt
inlineAlt env (Alt pat expr)
  = Alt pat (inlineExpr (removeIds (patIds pat) env) expr)
  where
    patIds (PatVar x)    = [x]
    patIds (PatCon _ xs) = xs
    patIds (PatLit _)    = []


{---------------------------------------------------------------
  deAnnotate
---------------------------------------------------------------}
deAnnotate :: Expr -> Expr
deAnnotate expr
  = case expr of
      Note _ e  -> deAnnotate e
      _         -> expr

{---------------------------------------------------------------
  trivial   
---------------------------------------------------------------}
trivial :: Expr -> Bool
trivial expr
  = case expr of
      Note _ e            -> trivial e
      Ap _ []             -> True
      Con (ConId _) []    -> True
      Lit _               -> True
      _                   -> False

whnfTrivial :: Expr -> Bool
whnfTrivial expr
  = case expr of
      Note _ e            -> whnfTrivial e
      Con (ConId _) []    -> True
      Lit _               -> True
      _                   -> False

{---------------------------------------------------------------
  firstuse
---------------------------------------------------------------}

firstuse :: Id -> Expr -> Bool
firstuse x = first x False

firsts :: Id -> Bool -> [Expr] -> Bool
firsts = foldl . first

first :: Id -> Bool -> Expr -> Bool
first x c expr
  = case expr of
      LetRec bs e   -> firsts x c (map snd bs ++ [e])
      Let _ e1 e2   -> firsts x c [e1,e2]
      Eval _ e1 _   -> first x False e1
      Match _ _     -> False
      Prim _ args   -> firsts x False args
      Ap y args    | null args && y == x -> True
                   | not (null args)     -> firsts x c (Ap y [] : args)
      Con _ args    -> firsts x c args
      Note _ e      -> first x c e
      _             -> c