zephyr-0.5.3: src/Language/PureScript/DCE/CoreFn.hs
{-# LANGUAGE NoImplicitPrelude #-}
-- |
-- Dead code elimination for `CoreFn`.
module Language.PureScript.DCE.CoreFn
( runDeadCodeElimination
, runBindDeadCodeElimination
) where
import Prelude hiding (mod)
import Control.Arrow ((***))
import Control.Monad ( guard )
import Data.Graph ( graphFromEdges, reachable, Vertex )
import Data.Foldable (foldl', foldr')
import Data.List (groupBy, sortBy)
import qualified Data.Map.Strict as M
import Data.Maybe (catMaybes, mapMaybe)
import qualified Data.Set as S
import Language.PureScript.CoreFn
( Literal(ObjectLiteral, ArrayLiteral),
Ann,
everywhereOnValues,
Binder(VarBinder, LiteralBinder, ConstructorBinder, NamedBinder),
Bind(..),
CaseAlternative(CaseAlternative),
Expr(..),
Module(Module, moduleImports, moduleExports, moduleForeign,
moduleDecls, moduleName, moduleReExports) )
import Language.PureScript.DCE.Utils (bindIdents, unBind)
import Language.PureScript.Names
( getQual,
isQualified,
mkQualified,
Ident(Ident),
ModuleName,
ProperName(runProperName),
Qualified(..),
QualifiedBy(..) )
type Key = Qualified Ident
data DCEVertex
= BindVertex (Bind Ann)
| ForeignVertex (Qualified Ident)
| ReExportedVertex (Qualified Ident)
-- | Dead code elimination of a list of modules module
--
runDeadCodeElimination
:: [Qualified Ident]
-- ^ entry points used to build the graph of
-- dependencies across module boundaries
-> [Module Ann]
-- ^ modules to dce
-> [Module Ann]
-- ^ dead code eliminated modules
runDeadCodeElimination entryPoints modules = uncurry runModuleDeadCodeElimination `map` reachableInModule
where
-- DCE of a single module.
runModuleDeadCodeElimination
:: [(DCEVertex, Key, [Key])]
-- list of qualified names that need to be preserved
-> Module Ann
-> Module Ann
runModuleDeadCodeElimination vs mod@Module{ moduleDecls
, moduleExports
, moduleReExports
, moduleImports
, moduleName
, moduleForeign
} =
let
-- | filter declarations preserving the order
moduleDecls' :: [Bind Ann]
moduleDecls' = runBindDeadCodeElimination `map` filter filterByIdents moduleDecls
where
declIdents :: [Ident]
declIdents = concatMap toIdents vs
toIdents :: (DCEVertex, Key, [Key]) -> [Ident]
toIdents (BindVertex b, _, _) = bindIdents b
toIdents _ = []
filterByIdents :: Bind Ann -> Bool
filterByIdents = any (`elem` declIdents) . bindIdents
idents :: [Ident]
idents = concatMap bindIdents moduleDecls'
moduleExports' :: [Ident]
moduleExports' =
filter (`elem` (idents ++ moduleForeign')) moduleExports
moduleReExports' :: M.Map ModuleName [Ident]
moduleReExports' =
filter (`elem` rexpIdents) <$> moduleReExports
where
toRexpIdents :: (DCEVertex, Key, [Key]) -> [Ident]
toRexpIdents (ReExportedVertex (Qualified _ i), _, _) = [i]
toRexpIdents _ = []
rexpIdents :: [Ident]
rexpIdents = concatMap toRexpIdents vs
mods :: [ModuleName]
mods = mapMaybe getQual (concatMap (\(_, _, ks) -> ks) vs)
moduleImports' :: [(Ann, ModuleName)]
moduleImports' = filter ((`elem` mods) . snd) moduleImports
moduleForeign' :: [Ident]
moduleForeign' = filter
((`S.member` reachableSet) . Qualified (ByModuleName moduleName))
moduleForeign
where
reachableSet = foldr'
(\(_, k, ks) s -> S.insert k s `S.union` S.fromList ks)
S.empty vs
in mod { moduleImports = moduleImports'
, moduleExports = moduleExports'
, moduleReExports = moduleReExports'
, moduleForeign = moduleForeign'
, moduleDecls = moduleDecls'
}
(graph, keyForVertex, vertexForKey) = graphFromEdges verts
-- | The Vertex set.
verts :: [(DCEVertex, Key, [Key])]
verts = do
Module _ _ mn _ _ _ rexp mf ds <- modules
concatMap (toVertices mn) ds -- Module local bindings
++ ((\q -> (ForeignVertex q, q, [])) . flip mkQualified mn) `map` mf -- Foreign bindings
++ reExportedVertices mn rexp -- Re-exported bindings
where
toVertices :: ModuleName -> Bind Ann -> [(DCEVertex, Key, [Key])]
toVertices mn b@(NonRec _ i e) =
[(BindVertex b, mkQualified i mn, deps e)]
toVertices mn b@(Rec bs) =
let ks :: [(Key, [Key])]
ks = map (\((_, i), e) -> (mkQualified i mn, deps e)) bs
in map (\(k, ks') -> (BindVertex b, k, map fst ks ++ ks')) ks
reExportedVertices :: ModuleName -> M.Map ModuleName [Ident] -> [(DCEVertex, Key, [Key])]
reExportedVertices parent rexp =
filter (isReExported rexp) modules >>= rexpsFor
where
rexpsFor :: Module Ann -> [(DCEVertex, Key, [Key])]
rexpsFor (Module _ _ mn _ _ _ _ _ _) = case M.lookup mn rexp of
Nothing -> []
Just ids -> (\i -> (ReExportedVertex (mkQualified i parent)
, mkQualified i parent
, [mkQualified i mn])
) <$> ids
isReExported :: M.Map ModuleName [Ident] -> Module Ann -> Bool
isReExported rexps (Module _ _ name _ _ _ _ _ _) =
M.member name rexps
-- | Find dependencies of an expression.
deps :: Expr Ann -> [Key]
deps = traverseExpr
where
-- | Build graph from qualified identifiers.
onExpr :: Expr Ann -> [Key]
onExpr (Var _ i) = [i | isQualified i]
onExpr _ = []
traverseExpr :: Expr Ann -> [Key]
traverseExpr v@(Literal _ l) =
foldl'
(++)
(onExpr v)
(map traverseExpr (extractLiteral l))
traverseExpr v@Constructor {} = onExpr v
traverseExpr v@(Accessor _ _ e1) = onExpr v ++ traverseExpr e1
traverseExpr v@(ObjectUpdate _ obj vs) =
foldl'
(++)
(onExpr v ++ traverseExpr obj)
(map (traverseExpr . snd) vs)
traverseExpr v@(Abs _ _ e1) = onExpr v ++ traverseExpr e1
traverseExpr v@(App _ e1 e2) =
onExpr v ++ traverseExpr e1 ++ traverseExpr e2
traverseExpr v@(Var _ _) = onExpr v
traverseExpr v@(Case _ vs alts) =
foldl'
(++)
(foldl' (++) (onExpr v) (map traverseExpr vs))
(map onCaseAlternative alts)
traverseExpr v@(Let _ ds e1) =
foldl'
(++)
(onExpr v)
(map onBind ds)
++
traverseExpr e1
onBind :: Bind Ann -> [Key]
onBind (NonRec _ _ e) = traverseExpr e
onBind (Rec es) = concatMap (traverseExpr . snd) es
onBinder :: Binder Ann -> [Key]
onBinder b@(LiteralBinder _ l) =
foldl'
(++)
(onBinder' b)
(map onBinder (extractLiteral l))
onBinder b@(ConstructorBinder _ _ _ bs) =
foldl'
(++)
(onBinder' b)
(map onBinder bs)
onBinder b@(NamedBinder _ _ b1) = onBinder' b ++ onBinder b1
onBinder b = onBinder' b
onBinder' :: Binder Ann -> [Key]
onBinder' (ConstructorBinder _ _ c _) =
[fmap (Ident . runProperName) c]
onBinder' _ = []
onCaseAlternative :: CaseAlternative Ann -> [Key]
onCaseAlternative (CaseAlternative bs (Right val)) =
concatMap onBinder bs ++ traverseExpr val
onCaseAlternative (CaseAlternative bs (Left gs)) =
concat
(map
onBinder
bs ++ concatMap
(\(grd, val) ->
[traverseExpr grd, traverseExpr val]) gs)
-- @f@ is either 'Expr' or 'Binder'
extractLiteral :: Literal (f Ann) -> [f Ann]
extractLiteral (ArrayLiteral xs) = xs
extractLiteral (ObjectLiteral xs) = map snd xs
extractLiteral _ = []
-- | Vertices corresponding to the entry points which we want to keep.
entryPointVertices :: [Vertex]
entryPointVertices = catMaybes $ do
(_, k, _) <- verts
guard $ k `elem` entryPoints
return (vertexForKey k)
-- | The list of reachable vertices grouped by module name.
reachableList :: [[(DCEVertex, Key, [Key])]]
reachableList
= groupBy (\(_, k1, _) (_, k2, _) -> getQual k1 == getQual k2)
$ sortBy (\(_, k1, _) (_, k2, _) -> getQual k1 `compare` getQual k2)
$ map keyForVertex (concatMap (reachable graph) entryPointVertices)
reachableInModule :: [([(DCEVertex, Key, [Key])], Module Ann)]
reachableInModule = do
vs <- reachableList
m <- modules
guard (getModuleName vs == Just (moduleName m))
return (vs, m)
getModuleName :: [(DCEVertex, Key, [Key])] -> Maybe ModuleName
getModuleName [] = Nothing
getModuleName ((_, k, _) : _) = getQual k
-- | Dead code elimination of local identifiers in `Bind`s, which detects and
-- removes unused bindings.
--
runBindDeadCodeElimination :: Bind Ann -> Bind Ann
runBindDeadCodeElimination = go
where
(go, _, _) = everywhereOnValues id exprFn id
exprFn :: Expr Ann -> Expr Ann
exprFn (Let ann bs ex) =
let nbs = foldr' bindFn [] bs
in if null nbs
then ex
else Let ann nbs ex
where
bindFn :: Bind Ann -> [Bind Ann] -> [Bind Ann]
bindFn b@(NonRec _ i _) r | i `elem` reachableIdents = b : r
| otherwise = r
bindFn (Rec l) r =
let l' = filter (\((_, i), _) -> i `elem` reachableIdents) l
in if null l'
then r
else Rec l' : r
(graph, keyForVertex, vertexForKey) = graphFromEdges verts
-- | Build list of vertices
--
-- Under the assumption that all identifiers are unique, which is
-- fullfiled by PureScript.
verts :: [(Ident, Ident, [Ident])]
verts = do
let bes = unBind `concatMap` bs
(i, e) <- bes
let deps = fst `map` filter fn bes
where
fn (i', _) = i' /= i && isUsedInExpr i' e
return (i, i, deps)
entryPointVertices :: [Vertex]
entryPointVertices = catMaybes $ do
(_, i, _) <- verts
guard $ isUsedInExpr i ex
return (vertexForKey i)
reachableIdents = map fn $ reachable graph `concatMap` entryPointVertices
where
fn v = case keyForVertex v of (_, i, _) -> i
exprFn e = e
isUsedInExpr :: Ident -> Expr Ann -> Bool
isUsedInExpr i (Literal _ (ArrayLiteral es))
= any (isUsedInExpr i) es
isUsedInExpr i (Literal _ (ObjectLiteral es))
= any (isUsedInExpr i . snd) es
isUsedInExpr _ (Literal _ _) = False
isUsedInExpr i (Constructor _ _ _ is) = i `elem` is
isUsedInExpr i (Accessor _ _ e) = isUsedInExpr i e
isUsedInExpr i (ObjectUpdate _ e ups)
= isUsedInExpr i e || any (isUsedInExpr i . snd) ups
isUsedInExpr i (App _ (Abs _ i' e) r)
= if isUsedInExpr i r
then isUsedInExpr i' e
else isUsedInExpr i e
isUsedInExpr i (App _ l r) = isUsedInExpr i l || isUsedInExpr i r
isUsedInExpr i (Abs _ i' e) = i /= i' && isUsedInExpr i e
isUsedInExpr i (Var _ qi@(Qualified by _)) = qi == Qualified by i
isUsedInExpr i (Case _ es alts)
= any (isUsedInExpr i) es || any (isUsedInCaseAlternative i) alts
isUsedInExpr i (Let _ bs e) =
if shadowed
then used
else used || isUsedInExpr i e
where
-- |
-- Check if an identifier is used in bindings and the resulting
-- expression. A binding might shadow an identifier. The first Boolean
-- value denotes if i is used in any bind expression, the second if it was
-- shadowed.
(used, shadowed) = foldl' fn (False, False) (concatMap unBind bs)
fn (u, s) (i', e')
| s || i == i' = (u, True)
| otherwise = (u || isUsedInExpr i e', False)
isUsedInCaseAlternative i (CaseAlternative bs ee) =
not (any (\b -> case b of
VarBinder _ i' -> i == i'
_ -> False) bs
)
&&
(case ee of
Right e -> isUsedInExpr i e
Left es
-> any (uncurry (||) . (isUsedInExpr i *** isUsedInExpr i)) es)