packages feed

parsley-core-2.3.0.0: src/ghc/Parsley/Internal/Frontend/Analysis/Dependencies.hs

{-# LANGUAGE RecordWildCards #-}
{-|
Module      : Parsley.Internal.Frontend.Analysis.Dependencies
Description : Calculate dependencies of a collection of bindings.
License     : BSD-3-Clause
Maintainer  : Jamie Willis
Stability   : experimental

Exposes `dependencyAnalysis`, which is used to calculate information
regarding the dependencies of each let-bound parser, as well as their
free-registers.

@since 1.5.0.0
-}
module Parsley.Internal.Frontend.Analysis.Dependencies (dependencyAnalysis) where

import Control.Monad                        (unless, forM_)
import Control.Monad.ST                     (ST)
import Data.Array                           (Array)
import Data.Array.MArray                    (readArray, writeArray, newArray, newArray_)
import Data.Array.ST                        (STArray, runSTUArray, runSTArray)
import Data.Array.Unboxed                   (UArray)
import Data.Bifunctor                       (first, second)
import Data.Dependent.Map                   (DMap)
import Data.Foldable                        (foldl')
import Data.Map.Strict                      (Map)
import Data.Set                             (Set)
import Data.STRef                           (newSTRef, readSTRef, writeSTRef)
import Parsley.Internal.Common.Indexed      (Fix, cata, Const1(..), (:*:)(..), zipper)
import Parsley.Internal.Common.State        (State, MonadState, execState, modify')
import Parsley.Internal.Core.CombinatorAST  (Combinator(..), traverseCombinator)
import Parsley.Internal.Core.Identifiers    (IMVar, MVar(..), ΣVar, SomeΣVar(..))

import qualified Data.Dependent.Map as DMap   (foldrWithKey, filterWithKey)
import qualified Data.Map.Strict    as Map    ((!), empty, insert, findMax, elems, maxView, fromList, fromDistinctAscList)
import qualified Data.Set           as Set    (toList, insert, union, unions, member, notMember, empty, (\\), fromDistinctAscList, size)
import qualified Data.Array         as Array  ((!), listArray, bounds, indices)
import qualified Data.Array.Unboxed as UArray ((!), assocs)
import qualified Data.List          as List   (partition)

type Graph = Array IMVar [IMVar]
type PQueue k a = Map k a

{-|
Given a top-level parser and a collection of its let-bound subjects performs the following tasks:

* Determines which parser depend on which others.
* Use the previous information to remove any dead bindings.
* Calculate the direct free registers for each binding.
* Propogate the free registers according to transitive need via the dependency graph.

Returns the non-dead bindings, the information about each bindings free registers, and the next
free index for any registers created in code generation.

@since 1.5.0.0
-}
-- TODO This actually should be in the backend... dead bindings and the topological ordering can be computed here
--      but the register stuff should come after register optimisation and instruction peephole
dependencyAnalysis :: Fix Combinator a -> DMap MVar (Fix Combinator) -> (DMap MVar (Fix Combinator), Map IMVar (Set SomeΣVar))
dependencyAnalysis toplevel μs =
  let -- Step 1: find roots of the toplevel
      roots = directDependencies toplevel
      -- Step 2: build immediate dependencies
      DependencyMaps{..} = buildDependencyMaps μs
      -- Step 3: Build a call graph
      callees = buildGraph immediateDependencies
      -- Step 4: traverse the call graph, finding unreachable nodes and establishing a topological dfnum for each node
      (dfnums, lives, dead) = topoOrdering roots callees
      -- Step 5: reverse the call graph to make a callers graph
      callers = invertGraph callers dead
      -- Step 6: iterate over the live registers, and propagate the free registers until fix-point
      regs = propagateRegs lives dfnums usedRegisters definedRegisters callees callers
  in (DMap.filterWithKey (\(MVar v) _ -> Set.notMember v dead) μs, regs)

buildGraph :: Map IMVar (Set IMVar) -> Graph
buildGraph deps = Array.listArray (0, fst (Map.findMax deps)) (map Set.toList (Map.elems deps))

invertGraph :: Graph -> Set IMVar -> Graph
invertGraph g unreachable = runSTArray $ do
  g' <- newArray (Array.bounds g) []
  forM_ (Array.indices g) $ \n ->
    unless (Set.member n unreachable) $
      forM_ (g Array.! n) $ \s -> do
        preds <- readArray g' s
        writeArray g' s (n : preds)
  return g'

type DFNum = Int
topoOrdering :: Set IMVar -> Graph -> (UArray IMVar DFNum, [IMVar], Set IMVar)
topoOrdering roots graph =
  let dfnums :: UArray IMVar DFNum
      dfnums = runSTUArray $ do
        dfnums <- newArray (Array.bounds graph) 0
        nextDfnum <- newSTRef 1
        let dfs v = do seen <- (/= 0) <$> readArray dfnums v
                       unless seen $
                         do dfnum <- readSTRef nextDfnum
                            writeArray dfnums v dfnum
                            writeSTRef nextDfnum (dfnum + 1)
                            forM_ (graph Array.! v) dfs
        -- Assign a DFNum to each IMVar
        forM_ roots dfs
        return dfnums
      -- if something still has dfnum 0, it was not visited, and is dead
      lives, deads :: [(IMVar, DFNum)]
      (lives, deads) = List.partition ((/= 0) . snd) (UArray.assocs dfnums)
      -- The DFNums are unique
  in (dfnums, map fst lives, Set.fromDistinctAscList (map fst deads))

propagateRegs :: [IMVar] -> UArray IMVar DFNum -> Map IMVar (Set SomeΣVar) -> Map IMVar (Set SomeΣVar) -> Graph -> Graph -> Map IMVar (Set SomeΣVar)
propagateRegs reachables dfnums uses defs callees callers = toMap $ runSTArray $
  do freeRegs <- newArray_ (Array.bounds callees)
     forM_ reachables $ \v -> writeArray freeRegs v ((uses Map.! v) Set.\\ (defs Map.! v))
     let worklist = Map.fromList (map (\v -> (dfnums UArray.! v, v)) reachables)
     maybe (return ()) (unfoldM_ (uncurry (propagate freeRegs))) (Map.maxView worklist)
     return freeRegs
  where
    propagate :: STArray s IMVar (Set SomeΣVar) -> IMVar -> PQueue DFNum IMVar -> ST s (Maybe (IMVar, PQueue DFNum IMVar))
    propagate freeRegs v work = do
      !frees        <- readArray freeRegs v
      !freesCallees <- Set.unions <$> traverse (readArray freeRegs) (callees Array.! v)
      let !frees'   =  frees `Set.union` (freesCallees Set.\\ (defs Map.! v))
      if Set.size frees /= Set.size frees' then do
        writeArray freeRegs v frees'
        return (Map.maxView (addWork (callers Array.! v) work))
      else return (Map.maxView work)

    addWork :: [IMVar] -> PQueue DFNum IMVar -> PQueue DFNum IMVar
    addWork vs work = foldl' (flip (\v -> Map.insert (dfnums UArray.! v) v)) work vs

    unfoldM_ :: Monad m => (s -> m (Maybe s)) -> s -> m ()
    unfoldM_ f s = f s >>= mapM_ (unfoldM_ f)

    toMap arr = Map.fromDistinctAscList (map (\v -> (v, arr Array.! v)) reachables)

-- IMMEDIATE DEPENDENCY MAPS
data DependencyMaps = DependencyMaps {
  usedRegisters         :: !(Map IMVar (Set SomeΣVar)), -- Leave Lazy
  immediateDependencies :: !(Map IMVar (Set IMVar)), -- Could be Strict
  definedRegisters      :: !(Map IMVar (Set SomeΣVar))
}

buildDependencyMaps :: DMap MVar (Fix Combinator) -> DependencyMaps
buildDependencyMaps = DMap.foldrWithKey (\(MVar v) p deps@DependencyMaps{..} ->
  let (uses, defs, ds) = freeRegistersAndDependencies v p
  in deps { usedRegisters = Map.insert v uses usedRegisters
          , immediateDependencies = Map.insert v ds immediateDependencies
          , definedRegisters = Map.insert v defs definedRegisters}) (DependencyMaps Map.empty Map.empty Map.empty)

freeRegistersAndDependencies :: IMVar -> Fix Combinator a -> (Set SomeΣVar,  Set SomeΣVar, Set IMVar)
freeRegistersAndDependencies v p =
  let frsm :*: depsm = zipper freeRegistersAlg (dependenciesAlg (Just v)) p
      (uses, defs) = runFreeRegisters frsm
      ds = runDependencies depsm
  in (uses, defs, ds)

-- DEPENDENCY ANALYSIS
newtype Dependencies a = Dependencies { doDependencies :: State (Set IMVar) () }
runDependencies :: Dependencies a -> Set IMVar
runDependencies = flip execState Set.empty . doDependencies

directDependencies :: Fix Combinator a -> Set IMVar
directDependencies = runDependencies . cata (dependenciesAlg Nothing)

{-# INLINE dependenciesAlg #-}
dependenciesAlg :: Maybe IMVar -> Combinator Dependencies a -> Dependencies a
dependenciesAlg (Just v) (Let μ@(MVar u)) = Dependencies $ do unless (u == v) (dependsOn μ)
dependenciesAlg Nothing  (Let μ)          = Dependencies $ do dependsOn μ
dependenciesAlg _ p                       = Dependencies $ do traverseCombinator (fmap Const1 . doDependencies) p; return ()

dependsOn :: MonadState (Set IMVar) m => MVar a -> m ()
dependsOn (MVar v) = modify' (Set.insert v)

-- FREE REGISTER ANALYSIS
newtype FreeRegisters a = FreeRegisters { doFreeRegisters :: State (Set SomeΣVar, Set SomeΣVar) () }
runFreeRegisters :: FreeRegisters a -> (Set SomeΣVar, Set SomeΣVar)
runFreeRegisters = flip execState (Set.empty, Set.empty) . doFreeRegisters

{-# INLINE freeRegistersAlg #-}
freeRegistersAlg :: Combinator FreeRegisters a -> FreeRegisters a
freeRegistersAlg (GetRegister σ)      = FreeRegisters $ do uses σ
freeRegistersAlg (PutRegister σ p)    = FreeRegisters $ do uses σ; doFreeRegisters p
freeRegistersAlg (MakeRegister σ p q) = FreeRegisters $ do defs σ; doFreeRegisters p; doFreeRegisters q
freeRegistersAlg p                    = FreeRegisters $ do traverseCombinator (fmap Const1 . doFreeRegisters) p; return ()

uses :: MonadState (Set SomeΣVar, vs) m => ΣVar a -> m ()
uses σ = modify' (first (Set.insert (SomeΣVar σ)))

defs :: MonadState (vs, Set SomeΣVar) m => ΣVar a -> m ()
defs σ = modify' (second (Set.insert (SomeΣVar σ)))