Skip to content

Commit cbafcf2

Browse files
authored
Performance improvements for GetSpanInfo (#681)
* Performance improvements getSpanInfo was naively calling getDocumentations multiple times on the same name. Fixed by deduplicating these calls. getDocumentations is implemented on top of InteractiveEval.getDocs, which does a lot of Ghc setup internally and is very inefficient. Fixed by introducing a batch version of getDocs and batching all the calls in getSpanInfo name | success | samples | startup | setup | experiment | maxResidency ------------- | ------- | ------- | ------- | ----- | ---------- | ------------ edit (before) | True | 10 | 6.94s | 0.00s | 6.57s | 177MB edit (after) | True | 10 | 6.44s | 0.00s | 4.38s | 174MB * More performance improvements Played the deduplication trick on lookupName, which is slow for the same reasons as getDocs. Batching made a smaller difference in my measurements, so did not implement it * Fix redundant constraints * Skip the GHCi code paths for documentation We don't use the interactive module, so there's no reason to go through the GHCi code paths. Moreover, they apparently cause problems with ghc-lib. * Skip the GHCi paths for lookupName * Correctly load the module interface * Compatibility with GHC 8.4 and 8.6 * Fix ghc-lib build
1 parent 9272bfe commit cbafcf2

File tree

7 files changed

+166
-110
lines changed

7 files changed

+166
-110
lines changed

src/Development/IDE/Core/Compile.hs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ module Development.IDE.Core.Compile
2626
, loadDepModule
2727
, loadModuleHome
2828
, setupFinderCache
29+
, getDocsBatch
30+
, lookupName
2931
) where
3032

3133
import Development.IDE.Core.RuleTypes
@@ -41,10 +43,10 @@ import Development.IDE.Types.Options
4143
import Development.IDE.Types.Location
4244

4345
#if MIN_GHC_API_VERSION(8,6,0)
44-
import DynamicLoading (initializePlugins)
46+
import DynamicLoading (initializePlugins)
47+
import LoadIface (loadModuleInterface)
4548
#endif
4649

47-
import GHC hiding (parseModule, typecheckModule)
4850
import qualified Parser
4951
import Lexer
5052
#if MIN_GHC_API_VERSION(8,10,0)
@@ -53,6 +55,7 @@ import ErrUtils
5355
#endif
5456

5557
import Finder
58+
import Development.IDE.GHC.Compat hiding (parseModule, typecheckModule)
5659
import qualified Development.IDE.GHC.Compat as GHC
5760
import qualified Development.IDE.GHC.Compat as Compat
5861
import GhcMonad
@@ -61,7 +64,7 @@ import qualified HeaderInfo as Hdr
6164
import HscMain (hscInteractive, hscSimplify)
6265
import MkIface
6366
import StringBuffer as SB
64-
import TcRnMonad (initIfaceLoad, tcg_th_coreplugins)
67+
import TcRnMonad (tct_id, TcTyThing(AGlobal, ATcId), initTc, initIfaceLoad, tcg_th_coreplugins)
6568
import TcIface (typecheckIface)
6669
import TidyPgm
6770

@@ -81,6 +84,7 @@ import System.IO.Extra
8184
import Control.DeepSeq (rnf)
8285
import Control.Exception (evaluate)
8386
import Exception (ExceptionMonad)
87+
import TcEnv (tcLookup)
8488

8589

8690
-- | Given a string buffer, return the string (after preprocessing) and the 'ParsedModule'.
@@ -621,3 +625,58 @@ loadInterface session ms sourceMod regen = do
621625
| not (mi_used_th x) || SourceUnmodifiedAndStable == sourceMod
622626
-> return ([], Just $ HiFileResult ms x)
623627
(_reason, _) -> regen
628+
629+
-- | Non-interactive, batch version of 'InteractiveEval.getDocs'.
630+
-- The interactive paths create problems in ghc-lib builds
631+
--- and leads to fun errors like "Cannot continue after interface file error".
632+
getDocsBatch :: GhcMonad m
633+
=> Module -- ^ a moudle where the names are in scope
634+
-> [Name]
635+
-> m [Either String (Maybe HsDocString, Map.Map Int HsDocString)]
636+
getDocsBatch _mod _names =
637+
#if MIN_GHC_API_VERSION(8,6,0)
638+
withSession $ \hsc_env -> liftIO $ do
639+
((_warns,errs), res) <- initTc hsc_env HsSrcFile False _mod fakeSpan $ forM _names $ \name ->
640+
case nameModule_maybe name of
641+
Nothing -> return (Left $ NameHasNoModule name)
642+
Just mod -> do
643+
ModIface { mi_doc_hdr = mb_doc_hdr
644+
, mi_decl_docs = DeclDocMap dmap
645+
, mi_arg_docs = ArgDocMap amap
646+
} <- loadModuleInterface "getModuleInterface" mod
647+
if isNothing mb_doc_hdr && Map.null dmap && Map.null amap
648+
then pure (Left (NoDocsInIface mod $ compiled name))
649+
else pure (Right ( Map.lookup name dmap
650+
, Map.findWithDefault Map.empty name amap))
651+
case res of
652+
Just x -> return $ map (first prettyPrint) x
653+
Nothing -> throwErrors errs
654+
where
655+
throwErrors = liftIO . throwIO . mkSrcErr
656+
compiled n =
657+
-- TODO: Find a more direct indicator.
658+
case nameSrcLoc n of
659+
RealSrcLoc {} -> False
660+
UnhelpfulLoc {} -> True
661+
#else
662+
return []
663+
#endif
664+
665+
fakeSpan :: RealSrcSpan
666+
fakeSpan = realSrcLocSpan $ mkRealSrcLoc (fsLit "<ghcide>") 1 1
667+
668+
-- | Non-interactive, batch version of 'InteractiveEval.lookupNames'.
669+
-- The interactive paths create problems in ghc-lib builds
670+
--- and leads to fun errors like "Cannot continue after interface file error".
671+
lookupName :: GhcMonad m
672+
=> Module -- ^ A module where the Names are in scope
673+
-> Name
674+
-> m (Maybe TyThing)
675+
lookupName mod name = withSession $ \hsc_env -> liftIO $ do
676+
(_messages, res) <- initTc hsc_env HsSrcFile False mod fakeSpan $ do
677+
tcthing <- tcLookup name
678+
case tcthing of
679+
AGlobal thing -> return thing
680+
ATcId{tct_id=id} -> return (AnId id)
681+
_ -> panic "tcRnLookupName'"
682+
return res

src/Development/IDE/GHC/Compat.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ import GHC hiding (
8383
VarPat,
8484
ModLocation,
8585
HasSrcSpan,
86+
lookupName,
8687
getLoc
8788
#if MIN_GHC_API_VERSION(8,6,0)
8889
, getConArgs

src/Development/IDE/GHC/Util.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module Development.IDE.GHC.Util(
2727
readFileUtf8,
2828
hDuplicateTo',
2929
setHieDir,
30-
dontWriteHieFiles
30+
dontWriteHieFiles,
3131
) where
3232

3333
import Control.Concurrent

src/Development/IDE/Plugin/Completions/Logic.hs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import Coercion
3636
import Language.Haskell.LSP.Types
3737
import Language.Haskell.LSP.Types.Capabilities
3838
import qualified Language.Haskell.LSP.VFS as VFS
39+
import Development.IDE.Core.Compile
3940
import Development.IDE.Plugin.Completions.Types
4041
import Development.IDE.Spans.Documentation
4142
import Development.IDE.GHC.Compat as GHC
@@ -230,7 +231,8 @@ cacheDataProducer :: HscEnv -> TypecheckedModule -> [ParsedModule] -> IO CachedC
230231
cacheDataProducer packageState tm deps = do
231232
let parsedMod = tm_parsed_module tm
232233
dflags = hsc_dflags packageState
233-
curMod = moduleName $ ms_mod $ pm_mod_summary parsedMod
234+
curMod = ms_mod $ pm_mod_summary parsedMod
235+
curModName = moduleName curMod
234236
Just (_,limports,_,_) = tm_renamed_source tm
235237

236238
iDeclToModName :: ImportDecl name -> ModuleName
@@ -263,11 +265,11 @@ cacheDataProducer packageState tm deps = do
263265
case lookupTypeEnv typeEnv n of
264266
Just tt -> case safeTyThingId tt of
265267
Just var -> (\x -> ([x],mempty)) <$> varToCompl var
266-
Nothing -> (\x -> ([x],mempty)) <$> toCompItem curMod n
267-
Nothing -> (\x -> ([x],mempty)) <$> toCompItem curMod n
268+
Nothing -> (\x -> ([x],mempty)) <$> toCompItem curMod curModName n
269+
Nothing -> (\x -> ([x],mempty)) <$> toCompItem curMod curModName n
268270
getComplsForOne (GRE n _ False prov) =
269271
flip foldMapM (map is_decl prov) $ \spec -> do
270-
compItem <- toCompItem (is_mod spec) n
272+
compItem <- toCompItem curMod (is_mod spec) n
271273
let unqual
272274
| is_qual spec = []
273275
| otherwise = [compItem]
@@ -282,21 +284,15 @@ cacheDataProducer packageState tm deps = do
282284
varToCompl var = do
283285
let typ = Just $ varType var
284286
name = Var.varName var
285-
docs <- evalGhcEnv packageState $ getDocumentationTryGhc (tm_parsed_module tm : deps) name
286-
return $ mkNameCompItem name curMod typ Nothing docs
287-
288-
toCompItem :: ModuleName -> Name -> IO CompItem
289-
toCompItem mn n = do
290-
docs <- evalGhcEnv packageState $ getDocumentationTryGhc (tm_parsed_module tm : deps) n
291-
-- lookupName uses runInteractiveHsc, i.e., GHCi stuff which does not work with GHCi
292-
-- and leads to fun errors like "Cannot continue after interface file error".
293-
#ifdef GHC_LIB
294-
let ty = Right Nothing
295-
#else
287+
docs <- evalGhcEnv packageState $ getDocumentationTryGhc curMod (tm_parsed_module tm : deps) name
288+
return $ mkNameCompItem name curModName typ Nothing docs
289+
290+
toCompItem :: Module -> ModuleName -> Name -> IO CompItem
291+
toCompItem m mn n = do
292+
docs <- evalGhcEnv packageState $ getDocumentationTryGhc curMod (tm_parsed_module tm : deps) n
296293
ty <- evalGhcEnv packageState $ catchSrcErrors "completion" $ do
297-
name' <- lookupName n
294+
name' <- lookupName m n
298295
return $ name' >>= safeTyThingType
299-
#endif
300296
return $ mkNameCompItem n mn (either (const Nothing) id ty) Nothing docs
301297

302298
(unquals,quals) <- getCompls rdrElts

0 commit comments

Comments
 (0)