parsley-core-1.0.0.0: src/ghc/Parsley/Internal/Frontend/Dependencies.hs
{-# LANGUAGE RecordWildCards #-}
module Parsley.Internal.Frontend.Dependencies (dependencyAnalysis) where
import Control.Arrow (first, second)
import Control.Monad (unless, forM_)
import Data.Array (Array, (!), listArray)
import Data.Array.MArray (readArray, writeArray, newArray)
import Data.Array.ST (runSTUArray)
import Data.Array.Unboxed (assocs)
import Data.Dependent.Map (DMap)
import Data.List (foldl', partition, sortOn)
import Data.Map.Strict (Map)
import Data.Maybe (fromMaybe)
import Data.Set (Set, insert, (\\), union, notMember, empty)
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(..), IΣVar, ΣVar(..))
import qualified Data.Dependent.Map as DMap (foldrWithKey, filterWithKey)
import qualified Data.Map.Strict as Map ((!), empty, insert, mapMaybeWithKey, findMax, elems, lookup, foldMapWithKey)
import qualified Data.Set as Set (elems, empty, insert, lookupMax)
type Graph = Array IMVar [IMVar]
-- 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 IΣVar), IΣ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: find the largest name
n = fst (Map.findMax immediateDependencies)
-- Step 4: Build a dependency graph
graph = buildGraph n immediateDependencies
-- Step 5: construct the seen set (dfnum)
-- Step 6: dfs from toplevel (via roots) all with same seen set
-- Step 7: elems of seen set with dfnum 0 are dead, otherwise they are collected into a list in descending order
(topo, dead) = topoOrdering roots n graph
-- Step 8: perform a dfs on each of the topo, with a new seen set for each,
-- building the flattened dependency map. If the current focus has
-- already been computed, add all its deps to the seen set and skip.
-- The end seen set becomes out flattened deps.
trueDeps = flattenDependencies topo (minMax topo) graph
-- Step 8: Compute the new registers, and remove dead ones
addNewRegs v uses
| notMember v dead = let deps = trueDeps Map.! v
defs = definedRegisters Map.! v
subUses = foldMap (usedRegisters Map.!) deps
subDefs = foldMap (definedRegisters Map.!) deps
in Just $ (uses \\ defs) `union` (subUses \\ subDefs)
| otherwise = Nothing
trueRegs = Map.mapMaybeWithKey addNewRegs usedRegisters
largestRegister = fromMaybe (-1) (Set.lookupMax (Map.foldMapWithKey (const id) definedRegisters))
in (DMap.filterWithKey (\(MVar v) _ -> notMember v dead) μs, trueRegs, largestRegister)
minMax :: Ord a => [a] -> (a, a)
minMax [] = error "cannot find minimum or maximum of empty list"
minMax (x:xs) = foldl' (\(small, big) x -> (min small x, max big x)) (x, x) xs
buildGraph :: IMVar -> Map IMVar (Set IMVar) -> Graph
buildGraph n = listArray (0, n) . map Set.elems . Map.elems
topoOrdering :: Set IMVar -> IMVar -> Graph -> ([IMVar], Set IMVar)
topoOrdering roots n graph =
let dfnums = runSTUArray $ do
dfnums <- newArray (0, n) (0 :: Int)
nextDfnum <- newSTRef 1
let hasSeen v = (/= 0) <$> readArray dfnums v
let setSeen v = do dfnum <- readSTRef nextDfnum
writeArray dfnums v dfnum
writeSTRef nextDfnum (dfnum + 1)
forM_ roots (dfs hasSeen setSeen graph)
return dfnums
(lives, deads) = partition ((/= 0) . snd) (assocs dfnums)
in (reverseMap fst (sortOn snd lives), foldl' (\ds v0 -> Set.insert (fst v0) ds) Set.empty deads)
reverseMap :: (a -> b) -> [a] -> [b]
reverseMap f = foldl' (\xs x -> f x : xs) []
flattenDependencies :: [IMVar] -> (IMVar, IMVar) -> Graph -> Map IMVar (Set IMVar)
flattenDependencies topo range graph = foldl' reachable Map.empty topo
where
reachable :: Map IMVar (Set IMVar) -> IMVar -> Map IMVar (Set IMVar)
reachable deps root =
let seen = runSTUArray $ do
seen <- newArray range False
let setSeen v = writeArray seen v True
let seenOrSkip v = case Map.lookup v deps of
Nothing -> readArray seen v
Just ds -> setSeen v >> forM_ ds setSeen >> return True
dfs seenOrSkip setSeen graph root
return seen
ds = foldl' (\ds (v, b) -> if b then Set.insert v ds else ds) Set.empty (assocs seen)
in Map.insert root ds deps
dfs :: Monad m => (IMVar -> m Bool) -> (IMVar -> m ()) -> Graph -> IMVar -> m ()
dfs hasSeen setSeen graph = go
where
go v = do seen <- hasSeen v
unless seen $
do setSeen v
forM_ (graph ! v) go
-- IMMEDIATE DEPENDENCY MAPS
data DependencyMaps = DependencyMaps {
usedRegisters :: Map IMVar (Set IΣVar), -- Leave Lazy
immediateDependencies :: Map IMVar (Set IMVar), -- Could be Strict
definedRegisters :: Map IMVar (Set IΣVar)
}
buildDependencyMaps :: DMap MVar (Fix Combinator) -> DependencyMaps
buildDependencyMaps = DMap.foldrWithKey (\(MVar v) p deps@DependencyMaps{..} ->
let (frs, defs, ds) = freeRegistersAndDependencies v p
in deps { usedRegisters = Map.insert v frs 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 IΣVar, Set IΣVar, Set IMVar)
freeRegistersAndDependencies v p =
let frsm :*: depsm = zipper freeRegistersAlg (dependenciesAlg (Just v)) p
(frs, defs) = runFreeRegisters frsm
ds = runDependencies depsm
in (frs, defs, ds)
-- DEPENDENCY ANALYSIS
newtype Dependencies a = Dependencies { doDependencies :: State (Set IMVar) () }
runDependencies :: Dependencies a -> Set IMVar
runDependencies = flip execState 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' (insert v)
-- FREE REGISTER ANALYSIS
newtype FreeRegisters a = FreeRegisters { doFreeRegisters :: State (Set IΣVar, Set IΣVar) () }
runFreeRegisters :: FreeRegisters a -> (Set IΣVar, Set IΣVar)
runFreeRegisters = flip execState (empty, 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 Let{} = FreeRegisters $ do return () -- TODO This can be removed when Let doesn't have the body in it...
freeRegistersAlg p = FreeRegisters $ do traverseCombinator (fmap Const1 . doFreeRegisters) p; return ()
uses :: MonadState (Set IΣVar, vs) m => ΣVar a -> m ()
uses (ΣVar σ) = modify' (first (insert σ)) --tell (singleton σ, mempty)
defs :: MonadState (vs, Set IΣVar) m => ΣVar a -> m ()
defs (ΣVar σ) = modify' (second (insert σ)) --tell (mempty, singleton σ)