Agda-2.6.4.2: src/full/Agda/TypeChecking/DeadCode.hs
{-# OPTIONS_GHC -Wunused-imports #-}
module Agda.TypeChecking.DeadCode (eliminateDeadCode) where
import Control.Monad ((<$!>))
import Control.Monad.Trans
import Data.Maybe
import Data.Monoid (All(..))
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Map.Strict as MapS
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.HashMap.Strict as HMap
import Agda.Interaction.Options
import qualified Agda.Syntax.Abstract as A
import Agda.Syntax.Common
import Agda.Syntax.Common.Pretty (prettyShow)
import Agda.Syntax.Internal
import Agda.Syntax.Internal.Names
import Agda.Syntax.Scope.Base
import qualified Agda.Benchmarking as Bench
import qualified Agda.TypeChecking.Monad.Benchmark as Bench
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Reduce
import Agda.Utils.Impossible
import Agda.Utils.Lens
import Agda.Utils.HashTable (HashTable)
import qualified Agda.Utils.HashTable as HT
-- | Run before serialisation to remove any definitions and
-- meta-variables that are not reachable from the module's public
-- interface.
--
-- Things that are reachable only from warnings are removed.
eliminateDeadCode ::
Map ModuleName a
-- ^ Exported modules whose telescopes should not be mutilated by the dead-code removal.
-> BuiltinThings PrimFun
-> DisplayForms
-> Signature
-> LocalMetaStore
-> TCM (DisplayForms, Signature, RemoteMetaStore)
eliminateDeadCode publicModules bs disp sig ms = Bench.billTo [Bench.DeadCode] $ do
!patsyn <- getPatternSyns
!public <- Set.mapMonotonic anameName . publicNames <$> getScope
!save <- optSaveMetas <$> pragmaOptions
!defs <- if save then return (sig ^. sigDefinitions)
else Bench.billTo [Bench.DeadCode, Bench.DeadCodeInstantiateFull]
(traverse (\x -> instantiateFull x) (sig ^. sigDefinitions))
-- #2921: Eliminating definitions with attached COMPILE pragmas results in
-- the pragmas not being checked. Simple solution: don't eliminate these.
-- #6022 (Andreas, 2022-09-30): Eliminating cubical primitives can lead to crashes.
-- Simple solution: retain all primitives (shouldn't be many).
let hasCompilePragma = not . MapS.null . defCompiledRep
isPrimitive = \case
Primitive{} -> True
PrimitiveSort{} -> True
_ -> False
extraRootsFilter (name, def)
| hasCompilePragma def || isPrimitive (theDef def) = Just name
| otherwise = Nothing
extraRoots =
Set.fromList $ mapMaybe extraRootsFilter $ HMap.toList defs
rootSections = Map.elems $ (sig ^. sigSections) `Map.intersection` publicModules
rootNames = Set.union public extraRoots
rootMetas =
if not save then Set.empty else metasIn
( bs
, sig ^. sigSections
, sig ^. sigRewriteRules
, HMap.filterWithKey (\x _ -> Set.member x rootNames) disp
)
(!rns, !rms) <- Bench.billTo [Bench.DeadCode, Bench.DeadCodeReachable] $ liftIO $
reachableFrom rootSections rootNames rootMetas patsyn disp defs ms
let !dead = Set.fromList (HMap.keys defs) `Set.difference` rns
!valid = getAll . namesIn' (All . (`Set.notMember` dead)) -- no used name is dead
!defs' = HMap.map ( \ d -> d { defDisplay = filter valid (defDisplay d) } )
$ HMap.filterWithKey (\ x _ -> Set.member x rns) defs
!disp' = HMap.filter (not . null) $ HMap.map (filter valid) disp
!ms' = HMap.fromList $
mapMaybe
(\(m, mv) ->
if not (Set.member m rms)
then Nothing
else Just (m, remoteMetaVariable mv)) $
MapS.toList ms
reportSLn "tc.dead" 10 $
"Removed " ++ show (HMap.size defs - HMap.size defs') ++
" unused definitions and " ++ show (MapS.size ms - HMap.size ms') ++
" unused meta-variables."
reportSLn "tc.dead" 90 $ unlines $
"Removed the following definitions from the signature:" :
map (("- " ++) . prettyShow) (Set.toList dead)
let !sig' = set sigDefinitions defs' sig
return (disp', sig', ms')
reachableFrom ::
[Section] -- ^ Root modules.
-> Set QName -- ^ Root names.
-> Set MetaId -- ^ Root metas.
-> A.PatternSynDefns
-> DisplayForms
-> Definitions
-> LocalMetaStore
-> IO (Set QName, Set MetaId)
reachableFrom sections ids ms psyns disp defs insts = do
!seenNames <- HT.empty :: IO (HashTable QName ())
!seenMetas <- HT.empty :: IO (HashTable MetaId ())
let goName :: QName -> IO ()
goName !x = HT.lookup seenNames x >>= \case
Just _ ->
pure ()
Nothing -> do
HT.insert seenNames x ()
go (HMap.lookup x defs)
go (PSyn <$!> MapS.lookup x psyns)
go (HMap.lookup x disp)
goMeta :: MetaId -> IO ()
goMeta !m = HT.lookup seenMetas m >>= \case
Just _ ->
pure ()
Nothing -> do
HT.insert seenMetas m ()
case MapS.lookup m insts of
Nothing -> pure ()
Just mv -> go (instBody (theInstantiation mv))
go :: NamesIn a => a -> IO ()
go = namesAndMetasIn' (either goName goMeta)
{-# INLINE go #-}
go sections
foldMap goName ids
foldMap goMeta ms
!ids' <- HT.keySet seenNames
!ms' <- HT.keySet seenMetas
pure (ids', ms')
-- | Returns the instantiation.
--
-- Precondition: The instantiation must be of the form @'InstV' inst@.
theInstantiation :: MetaVariable -> Instantiation
theInstantiation mv = case mvInstantiation mv of
InstV inst -> inst
Open{} -> __IMPOSSIBLE__
OpenInstance{} -> __IMPOSSIBLE__
BlockedConst{} -> __IMPOSSIBLE__
PostponedTypeCheckingProblem{} -> __IMPOSSIBLE__
-- | Converts from 'MetaVariable' to 'RemoteMetaVariable'.
--
-- Precondition: The instantiation must be of the form @'InstV' inst@.
remoteMetaVariable :: MetaVariable -> RemoteMetaVariable
remoteMetaVariable mv = RemoteMetaVariable
{ rmvInstantiation = theInstantiation mv
, rmvModality = getModality mv
, rmvJudgement = mvJudgement mv
}