{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} module HERMIT.Dictionary.GHC ( -- * GHC-based Transformations -- | This module contains transformations that are reflections of GHC functions, or derived from GHC functions. externals -- ** Dynamic Loading , loadLemmaLibraryT , LemmaLibrary , injectDependencyT -- ** Substitution , substR -- ** Utilities , dynFlagsT , arityOf -- ** Lifted GHC capabilities -- A zombie is an identifer that has 'OccInfo' 'IAmDead', but still has occurrences. , lintExprT , lintModuleT , lintClauseT , occurAnalyseR , occurAnalyseChangedR , occurAnalyseExprChangedR , occurAnalyseAndDezombifyR , dezombifyR , buildDictionary , buildDictionaryT #if __GLASGOW_HASKELL__ < 710 , buildTypeable #endif ) where import qualified Bag import qualified CoreLint import Control.Arrow import Control.Monad import Control.Monad.IO.Class import Data.Char (isSpace) import Data.Either (partitionEithers) import Data.List (mapAccumL, nub) import qualified Data.Map as M import Data.String import HERMIT.Core import HERMIT.Context import HERMIT.External import HERMIT.GHC import HERMIT.Kure import HERMIT.Lemma import HERMIT.Monad import HERMIT.Name ------------------------------------------------------------------------ -- | Externals that reflect GHC functions, or are derived from GHC functions. externals :: [External] externals = [ external "deshadow-prog" (promoteProgR deShadowProgR :: RewriteH LCore) [ "Deshadow a program." ] .+ Deep , external "dezombify" (promoteExprR dezombifyR :: RewriteH LCore) [ "Zap the occurrence information in the current identifer if it is a zombie."] .+ Shallow , external "occurrence-analysis" (occurrenceAnalysisR :: RewriteH LCore) [ "Perform dependency analysis on all sub-expressions; simplifying and updating identifer info."] .+ Deep , external "lint-expr" (promoteExprT lintExprT :: TransformH LCoreTC String) [ "Runs GHC's Core Lint, which typechecks the current expression." , "Note: this can miss several things that a whole-module core lint will find." , "For instance, running this on the RHS of a binding, the type of the RHS will" , "not be checked against the type of the binding. Running on the whole let expression" , "will catch that however."] .+ Deep .+ Debug .+ Query , external "lint-module" (promoteModGutsT lintModuleT :: TransformH LCoreTC String) [ "Runs GHC's Core Lint, which typechecks the current module."] .+ Deep .+ Debug .+ Query , external "lint" (promoteT lintClauseT :: TransformH LCoreTC String) [ "Lint check a clause." ] , external "load-lemma-library" (flip loadLemmaLibraryT Nothing :: HermitName -> TransformH LCore String) [ "Dynamically load a library of lemmas." ] , external "load-lemma-library" ((\nm -> loadLemmaLibraryT nm . Just) :: HermitName -> LemmaName -> TransformH LCore String) [ "Dynamically load a specific lemma from a library of lemmas." ] , external "inject-dependency" (promoteModGutsT . injectDependencyT . mkModuleName :: String -> TransformH LCore ()) [ "Inject a dependency on the given module." ] ] ------------------------------------------------------------------------ -- | Substitute all occurrences of a variable with an expression, in either a program, an expression, or a case alternative. substR :: MonadCatch m => Var -> CoreExpr -> Rewrite c m Core substR v e = setFailMsg "Can only perform substitution on expressions, case alternatives or programs." $ promoteExprR (arr $ substCoreExpr v e) <+ promoteProgR (substTopBindR v e) <+ promoteAltR (arr $ substCoreAlt v e) -- | Substitute all occurrences of a variable with an expression, in a program. substTopBindR :: Monad m => Var -> CoreExpr -> Rewrite c m CoreProg substTopBindR v e = contextfreeT $ \ p -> do -- TODO. Do we need to initialize the emptySubst with bindFreeVars? let emptySub = emptySubst -- mkEmptySubst (mkInScopeSet (exprFreeVars exp)) return $ bindsToProg $ snd (mapAccumL substBind (extendSubst emptySub v e) (progToBinds p)) ------------------------------------------------------------------------ -- | [from GHC documentation] De-shadowing the program is sometimes a useful pre-pass. -- It can be done simply by running over the bindings with an empty substitution, -- becuase substitution returns a result that has no-shadowing guaranteed. -- -- (Actually, within a single /type/ there might still be shadowing, because -- 'substTy' is a no-op for the empty substitution, but that's probably OK.) deShadowProgR :: Monad m => Rewrite c m CoreProg deShadowProgR = arr (bindsToProg . deShadowBinds . progToBinds) -------------------------------------------------------- -- | Try to figure out the arity of an identifier. arityOf :: ReadBindings c => c -> Id -> Int arityOf c i = case lookupHermitBinding i c of Nothing -> idArity i -- Note: the exprArity will call idArity if -- it hits an id; perhaps we should do the counting -- The advantage of idArity is it will terminate, though. Just b -> runKureM exprArity (const 0) -- conservative estimate, as we don't know what the expression looks like (hermitBindingExpr b) ------------------------------------------- -- | Run the Core Lint typechecker. -- Fails on errors, with error messages. -- Succeeds returning warnings. lintModuleT :: TransformH ModGuts String lintModuleT = do dynFlags <- dynFlagsT bnds <- arr mg_binds #if __GLASGOW_HASKELL__ < 710 let (warns, errs) = CoreLint.lintCoreBindings [] bnds -- [] are vars to treat as in scope, used by GHCi #else -- [] are vars to treat as in scope, used by GHCi -- 'CoreDesugar' so we check for global ids, but not INLINE loop breakers, see notes in GHC's CoreLint module. let (warns, errs) = CoreLint.lintCoreBindings CoreDesugar [] bnds #endif dumpSDocs endMsg = Bag.foldBag (\ d r -> d ++ ('\n':r)) (showSDoc dynFlags) endMsg if Bag.isEmptyBag errs then return $ dumpSDocs "Core Lint Passed" warns else fail $ "Core Lint Failed:\n" ++ dumpSDocs "" errs -- | Note: this can miss several things that a whole-module core lint will find. -- For instance, running this on the RHS of a binding, the type of the RHS will -- not be checked against the type of the binding. Running on the whole let expression -- will catch that however. lintExprT :: (BoundVars c, Monad m, HasDynFlags m) => Transform c m CoreExpr String lintExprT = transform $ \ c e -> do dflags <- getDynFlags case e of Type _ -> fail "cannot core lint types." _ -> maybe (return "Core Lint Passed") (fail . showSDoc dflags) (CoreLint.lintExpr (varSetElems $ boundVars c) e) ------------------------------------------- -- | Lifted version of 'getDynFlags'. dynFlagsT :: HasDynFlags m => Transform c m a DynFlags dynFlagsT = constT getDynFlags ------------------------------------------- ---------------------------------------------------------------------- -- TODO: Ideally, occurAnalyseExprR would fail if nothing changed. -- This is tricky though, as it's not just the structure of the expression, but also the meta-data. -- | Zap the 'OccInfo' in a zombie identifier. dezombifyR :: (ExtendPath c Crumb, Monad m) => Rewrite c m CoreExpr dezombifyR = varR (acceptR isDeadBinder >>^ zapVarOccInfo) -- | Apply 'occurAnalyseExprR' to all sub-expressions. occurAnalyseR :: (Injection CoreExpr u, Walker c u, MonadCatch m) => Rewrite c m u occurAnalyseR = let r = promoteExprR (arr occurAnalyseExpr_NoBinderSwap) -- See Note [No Binder Swap] go = r <+ anyR go in tryR go -- always succeed {- Note [No Binder Swap] The binder swap performed by occurrence analysis in GHC <= 7.8.3 is buggy in that it can lead to unintended variable capture (Trac #9440). Concretely, this will send bash into a loop, or cause core lint to fail. As this is an un-expected change as far as HERMIT users are concerned anyway, we use the version that doesn't perform the binder swap. -} -- | Occurrence analyse an expression, failing if the result is syntactically equal to the initial expression. occurAnalyseExprChangedR :: MonadCatch m => Rewrite c m CoreExpr occurAnalyseExprChangedR = changedByR exprSyntaxEq (arr occurAnalyseExpr_NoBinderSwap) -- See Note [No Binder Swap] -- | Occurrence analyse all sub-expressions, failing if the result is syntactically equal to the initial expression. occurAnalyseChangedR :: (AddBindings c, ExtendPath c Crumb, HasEmptyContext c, LemmaContext c, ReadPath c Crumb, MonadCatch m) => Rewrite c m LCore occurAnalyseChangedR = changedByR lcoreSyntaxEq occurAnalyseR -- | Run GHC's occurrence analyser, and also eliminate any zombies. occurAnalyseAndDezombifyR :: (AddBindings c, ExtendPath c Crumb, ReadPath c Crumb, HasEmptyContext c, MonadCatch m, Walker c u, Injection CoreExpr u) => Rewrite c m u occurAnalyseAndDezombifyR = allbuR (tryR $ promoteExprR dezombifyR) >>> occurAnalyseR occurrenceAnalysisR :: (AddBindings c, ExtendPath c Crumb, ReadPath c Crumb, HasEmptyContext c, MonadCatch m, Walker c LCore) => Rewrite c m LCore occurrenceAnalysisR = occurAnalyseAndDezombifyR ---------------------------------------------------------------------- #if __GLASGOW_HASKELL__ < 710 -- TODO: this is mostly an example, move somewhere? buildTypeable :: (HasDynFlags m, HasHermitMEnv m, LiftCoreM m, MonadIO m) => Type -> m (Id, [CoreBind]) buildTypeable ty = do evar <- runTcM $ do cls <- tcLookupClass typeableClassName let predTy = mkClassPred cls [typeKind ty, ty] -- recall that Typeable is now poly-kinded newWantedEvVar predTy buildDictionary evar #endif -- | Build a dictionary for the given buildDictionary :: (HasDynFlags m, HasHermitMEnv m, LiftCoreM m, MonadIO m) => Id -> m (Id, [CoreBind]) buildDictionary evar = do (i, bs) <- runTcM $ do loc <- getCtLoc $ GivenOrigin UnkSkol let predTy = varType evar nonC = mkNonCanonical $ CtWanted { ctev_pred = predTy, ctev_evar = evar, ctev_loc = loc } #if __GLASGOW_HASKELL__ < 710 wCs = mkFlatWC [nonC] #else wCs = mkSimpleWC [nonC] #endif (_wCs', bnds) <- solveWantedsTcM wCs -- reportAllUnsolved _wCs' -- this is causing a panic with dictionary instantiation -- revist and fix! return (evar, bnds) bnds <- runDsM $ dsEvBinds bs return (i,bnds) buildDictionaryT :: (HasDynFlags m, HasHermitMEnv m, LiftCoreM m, MonadCatch m, MonadIO m, MonadUnique m) => Transform c m Type CoreExpr buildDictionaryT = prefixFailMsg "buildDictionaryT failed: " $ contextfreeT $ \ ty -> do dflags <- getDynFlags binder <- newIdH ("$d" ++ zEncodeString (filter (not . isSpace) (showPpr dflags ty))) ty (i,bnds) <- buildDictionary binder guardMsg (notNull bnds) "no dictionary bindings generated." return $ case bnds of [NonRec v e] | i == v -> e -- the common case that we would have gotten a single non-recursive let _ -> mkCoreLets bnds (varToCoreExpr i) ---------------------------------------------------------------------- lintClauseT :: forall c m. ( AddBindings c, BoundVars c, ExtendPath c Crumb, HasEmptyContext c, LemmaContext c, ReadPath c Crumb , HasDynFlags m, MonadCatch m ) => Transform c m Clause String lintClauseT = do strs <- extractT (collectPruneT (promoteExprT $ lintExprT `catchM` return) :: Transform c m LCore [String]) let strs' = nub $ filter notNull strs guardMsg (null strs' || (strs' == ["Core Lint Passed"])) $ unlines strs' return "Core Lint Passed" ---------------------------------------------------------------------- -- | A LemmaLibrary is a transformation that produces a set of lemmas, -- which are then added to the lemma store. It is not allowed to insert -- its own lemmas directly (if it tries they are throw away), but can -- certainly read the existing store. type LemmaLibrary = TransformH () Lemmas loadLemmaLibraryT :: HermitName -> Maybe LemmaName -> TransformH x String loadLemmaLibraryT nm mblnm = prefixFailMsg "Loading lemma library failed: " $ contextonlyT $ \ c -> do hscEnv <- getHscEnv comp <- liftAndCatchIO $ loadLemmaLibrary hscEnv nm ls <- applyT comp c () -- TODO: discard side effects nls <- maybe (return $ M.toList ls) (\lnm -> maybe (fail $ show lnm ++ " not found in library.") (\ l -> return [(lnm,l)]) (M.lookup lnm ls)) mblnm r <- forM nls $ \ nl@(n, l) -> do er <- attemptM $ applyT lintClauseT c $ lemmaC l case er of Left msg -> return $ Left $ "Not adding lemma " ++ show n ++ " because lint failed.\n" ++ msg Right _ -> return $ Right nl let (fs,nls') = partitionEithers r m <- getLemmas putLemmas $ (M.fromList nls') `M.union` m return $ unlines (fs ++ ["Successfully loaded library " ++ show nm]) loadLemmaLibrary :: HscEnv -> HermitName -> IO LemmaLibrary loadLemmaLibrary hscEnv hnm = do name <- lookupHermitNameForPlugins hscEnv varNS hnm lib_tycon_name <- lookupHermitNameForPlugins hscEnv tyConClassNS $ fromString "HERMIT.Dictionary.GHC.LemmaLibrary" lib_tycon <- forceLoadTyCon hscEnv lib_tycon_name mb_v <- getValueSafely hscEnv name $ mkTyConTy lib_tycon let dflags = hsc_dflags hscEnv maybe (fail $ showSDoc dflags $ hsep [ ptext (sLit "The value"), ppr name , ptext (sLit "did not have the type") , ppr lib_tycon, ptext (sLit "as required.")]) return mb_v lookupHermitNameForPlugins :: HscEnv -> NameSpace -> HermitName -> IO Name lookupHermitNameForPlugins hscEnv ns hnm = do modName <- maybe (fail "name must be fully qualified with module name.") return (hnModuleName hnm) let dflags = hsc_dflags hscEnv rdrName = toRdrName ns hnm mbName <- lookupRdrNameInModuleForPlugins hscEnv modName rdrName maybe (fail $ showSDoc dflags $ hsep [ ptext (sLit "The module"), ppr modName , ptext (sLit "did not export the name") , ppr rdrName ]) return mbName injectDependencyT :: (LiftCoreM m, MonadIO m) => ModuleName -> Transform c m ModGuts () injectDependencyT mn = contextfreeT $ \ guts -> do env <- getHscEnv liftIO $ injectDependency env guts mn