packages feed

Jikka-5.6.0.0: src/Jikka/CPlusPlus/Convert/MoveSemantics.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : Jikka.CPlusPlus.Convert.MoveSemantics
-- Description : removes unnecessary copying. / 無用なコピーを削除します。
-- Copyright   : (c) Kimiyuki Onaka, 2020
-- License     : Apache License 2.0
-- Maintainer  : kimiyuki95@gmail.com
-- Stability   : experimental
-- Portability : portable
module Jikka.CPlusPlus.Convert.MoveSemantics
  ( run,
  )
where

import Control.Monad.State.Strict
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Set as S
import Jikka.CPlusPlus.Language.Expr
import Jikka.CPlusPlus.Language.Util
import Jikka.CPlusPlus.Language.VariableAnalysis
import Jikka.Common.Error

runExpr :: MonadState (M.Map VarName VarName) m => Expr -> m Expr
runExpr = \case
  Var x -> do
    y <- gets (M.lookup x)
    return $ Var (fromMaybe x y)
  Lit lit -> return $ Lit lit
  UnOp op e -> UnOp op <$> runExpr e
  BinOp op e1 e2 -> BinOp op <$> runExpr e1 <*> runExpr e2
  Cond e1 e2 e3 -> Cond <$> runExpr e1 <*> runExpr e2 <*> runExpr e3
  Lam args ret body -> Lam args ret <$> runStatements body []
  Call f args -> Call <$> runExpr f <*> mapM runExpr args
  Callable f -> return $ Callable f

runLeftExpr :: MonadState (M.Map VarName VarName) m => LeftExpr -> m LeftExpr
runLeftExpr = \case
  LeftVar x -> do
    y <- gets (M.lookup x)
    return $ LeftVar (fromMaybe x y)
  LeftAt e1 e2 -> LeftAt <$> runLeftExpr e1 <*> runExpr e2
  LeftGet n e -> LeftGet n <$> runLeftExpr e

runAssignExpr :: MonadState (M.Map VarName VarName) m => AssignExpr -> m AssignExpr
runAssignExpr = \case
  AssignExpr op e1 e2 -> AssignExpr op <$> runLeftExpr e1 <*> runExpr e2
  AssignIncr e -> AssignIncr <$> runLeftExpr e
  AssignDecr e -> AssignDecr <$> runLeftExpr e

data CopyType
  = SimpleCopy
  | UpdatedCopy
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

-- | @isMovableTo x y env cont@ checkes whether @x@ is movable to @y@ instead of copying, under a context @env@ and @cont@.
-- @x@ is already replaced by the mapping of @env@, but @y@ and @cont@ are not yet.
isMovableTo :: VarName -> VarName -> CopyType -> M.Map VarName VarName -> [[Statement]] -> Bool
isMovableTo x y0 typ env cont
  | x `S.notMember` readList' (analyzeStatements' (concat cont)) = True -- @x@ is movable if @x@ is not used after the current position
  | otherwise =
    let go = \case
          [] -> False
          (Assign (AssignExpr SimpleAssign (LeftVar x') (Var _)) : cont')
            | x' == x -> -- re-assignment to @x@
              let ReadWriteList _ ws' = analyzeStatements' cont'
                  ReadWriteList rs ws = analyzeStatements' (concat (tail cont))
               in y `S.notMember` S.unions [ws', rs, ws]
          (stmt : cont) ->
            let ReadWriteList rs ws = analyzeStatement' stmt
                rws = if typ == SimpleCopy then ws else S.union rs ws -- Reading @x@ is okay when it's a copy without updating.
             in x `S.notMember` rws && go cont -- @x@ is used
     in go (head cont) -- @x@ is movable if @x@ is unused until a next re-assignment
  where
    y :: VarName
    y = fromMaybe y0 (M.lookup y0 env)
    applyEnv :: ReadWriteList -> ReadWriteList
    applyEnv (ReadWriteList rs ws) =
      let f = S.map (\x -> fromMaybe x (M.lookup x env))
       in ReadWriteList (f rs) (f ws)
    analyzeStatements' = applyEnv . analyzeStatements
    analyzeStatement' = applyEnv . analyzeStatement

runStatement :: MonadState (M.Map VarName VarName) m => Statement -> [[Statement]] -> m [Statement]
runStatement stmt cont = case stmt of
  ExprStatement e -> do
    e <- runExpr e
    return [ExprStatement e]
  Block stmts -> do
    runStatements stmts cont
  If e body1 body2 -> do
    e <- runExpr e
    body1 <- runStatements body1 cont
    body2 <- traverse (`runStatements` cont) body2
    return [If e body1 body2]
  For t x init pred incr body -> do
    init <- runExpr init
    pred <- runExpr pred
    incr <- runAssignExpr incr
    body <- runStatements body cont
    return [For t x init pred incr body]
  ForEach t x e body -> do
    e <- runExpr e
    body <- runStatements body cont
    return [ForEach t x e body]
  While e body -> do
    e <- runExpr e
    body <- runStatements body cont
    return [While e body]
  Declare t y init -> do
    init <- case init of
      DeclareDefault -> return DeclareDefault
      DeclareCopy e -> DeclareCopy <$> runExpr e
      DeclareInitialize es -> DeclareInitialize <$> mapM runExpr es
    env <- get
    case init of
      DeclareCopy (Var x) | (x `isMovableTo` y) SimpleCopy env cont -> do
        modify' (M.insert y x)
        return []
      DeclareCopy (Call' (SetAt _) [Var x, i, xi])
        | (x `isMovableTo` y) UpdatedCopy env cont -> do
          modify' (M.insert y x)
          return [Assign (AssignExpr SimpleAssign (LeftAt (LeftVar x) i) xi)]
      DeclareCopy (Call' ConvexHullTrickCtor []) -> return [Declare t y DeclareDefault]
      DeclareCopy (Call' ConvexHullTrickCopyAddLine [Var x, a, b])
        | (x `isMovableTo` y) UpdatedCopy env cont -> do
          modify' (M.insert y x)
          return [callMethod' (Var x) "add_line" [a, b]]
      DeclareCopy (Call' (SegmentTreeCopySetPoint _) [Var x, i, a])
        | (x `isMovableTo` y) UpdatedCopy env cont -> do
          modify' (M.insert y x)
          return [callMethod' (Var x) "set" [i, a]]
      _ -> do
        return [Declare t y init]
  DeclareDestructure xs e -> do
    e <- runExpr e
    return [DeclareDestructure xs e]
  Assign e -> do
    e <- runAssignExpr e
    env <- get
    case e of
      AssignExpr SimpleAssign (LeftVar y) (Var x) | x == y -> return []
      AssignExpr SimpleAssign (LeftVar y) (Call' (SetAt _) [Var x, i, xi])
        | x == y -> return [Assign (AssignExpr SimpleAssign (LeftAt (LeftVar x) i) xi)]
        | (x `isMovableTo` y) UpdatedCopy env cont -> do
          modify' (M.insert y x)
          return [Assign (AssignExpr SimpleAssign (LeftAt (LeftVar x) i) xi)]
        | otherwise -> return [Assign e]
      AssignExpr SimpleAssign (LeftVar y) (Call' ConvexHullTrickCopyAddLine [Var x, a, b])
        | x == y -> return [callMethod' (Var x) "add_line" [a, b]]
        | (x `isMovableTo` y) UpdatedCopy env cont -> do
          modify' (M.insert y x)
          return [callMethod' (Var x) "add_line" [a, b]]
        | otherwise -> return [Assign e]
      AssignExpr SimpleAssign (LeftVar y) (Call' (SegmentTreeCopySetPoint _) [Var x, i, a])
        | x == y -> return [callMethod' (Var x) "set" [i, a]]
        | (x `isMovableTo` y) UpdatedCopy env cont -> do
          modify' (M.insert y x)
          return [callMethod' (Var x) "set" [i, a]]
        | otherwise -> return [Assign e]
      _ -> return [Assign e]
  Assert e -> do
    e <- runExpr e
    return [Assert e]
  Return e -> do
    e <- runExpr e
    return [Return e]

runStatements :: MonadState (M.Map VarName VarName) m => [Statement] -> [[Statement]] -> m [Statement]
runStatements stmts cont = case stmts of
  [] -> return []
  stmt : stmts -> do
    stmt <- runStatement stmt (stmts : cont)
    stmts <- runStatements stmts cont
    return (stmt ++ stmts)

runToplevelStatement :: MonadState (M.Map VarName VarName) m => ToplevelStatement -> m ToplevelStatement
runToplevelStatement = \case
  VarDef t x e -> VarDef t x <$> runExpr e
  FunDef ret f args body -> FunDef ret f args <$> runStatements body []
  StaticAssert e msg -> StaticAssert <$> runExpr e <*> pure msg

runProgram :: Monad m => Program -> m Program
runProgram (Program decls) = (`evalStateT` M.empty) $ do
  Program <$> mapM runToplevelStatement decls

-- | `run` replaces superfluous copying.
--
-- == Examples
--
-- Before:
--
-- > vector<int> solve(vector<int> a) {
-- >     vector<int> b = a;
-- >     b[0] = 1;
-- >     return b;
-- > }
--
-- After:
--
-- > vector<int> solve(vector<int> a) {
-- >     a[0] = 1;
-- >     return a;
-- > }
--
-- Before:
--
-- > int solve(int a, int b, int x) {
-- >     jikka::convex_hull_trick cht = jikka::convex_hull_trick();
-- >     cht = jikka::convex_hull_trick::persistent_add_line(cht, a, b);
-- >     return cht.get_min(x);
-- > }
--
-- After:
--
-- > int solve(int a, int b, int x) {
-- >     jikka::convex_hull_trick cht;
-- >     cht = cht.add_line(a, b);
-- >     return cht.get_min(x);
-- > }
run :: MonadError Error m => Program -> m Program
run prog = wrapError' "Jikka.CPlusPlus.Convert.MoveSemantics" $ do
  runProgram prog