Skip to content

Commit 2a08f05

Browse files
committed
override NeedsCompilation rule in eval plugin to generate linkables when Evaluating
In addition, we tune the newness check of the redefined NeedsCompilation rule so that the generated linkables are not thrown away unnecessarily, as described in: ndmitchell/shake#794
1 parent 0807587 commit 2a08f05

File tree

10 files changed

+128
-34
lines changed

10 files changed

+128
-34
lines changed

ghcide/src/Development/IDE/Core/RuleTypes.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ data LinkableType = ObjectLinkable | BCOLinkable
4949
instance Hashable LinkableType
5050
instance NFData LinkableType
5151

52+
encodeLinkableType :: Maybe LinkableType -> ByteString
53+
encodeLinkableType Nothing = "0"
54+
encodeLinkableType (Just BCOLinkable) = "1"
55+
encodeLinkableType (Just ObjectLinkable) = "2"
56+
5257
-- NOTATION
5358
-- Foo+ means Foo for the dependencies
5459
-- Foo* means Foo for me and Foo+

ghcide/src/Development/IDE/Core/Rules.hs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ module Development.IDE.Core.Rules(
5050
getHieAstsRule,
5151
getBindingsRule,
5252
needsCompilationRule,
53+
computeLinkableTypeForDynFlags,
5354
generateCoreRule,
5455
getImportMapRule,
5556
regenerateHiFile,
@@ -987,8 +988,9 @@ usePropertyAction kn plId p = do
987988
getLinkableType :: NormalizedFilePath -> Action (Maybe LinkableType)
988989
getLinkableType f = use_ NeedsCompilation f
989990

990-
needsCompilationRule :: Rules ()
991-
needsCompilationRule = defineEarlyCutoff $ RuleNoDiagnostics $ \NeedsCompilation file -> do
991+
-- needsCompilationRule :: Rules ()
992+
needsCompilationRule :: NormalizedFilePath -> Action (IdeResultNoDiagnosticsEarlyCutoff (Maybe LinkableType))
993+
needsCompilationRule file = do
992994
graph <- useNoFile GetModuleGraph
993995
res <- case graph of
994996
-- Treat as False if some reverse dependency header fails to parse
@@ -1012,30 +1014,34 @@ needsCompilationRule = defineEarlyCutoff $ RuleNoDiagnostics $ \NeedsCompilation
10121014
(uses NeedsCompilation revdeps)
10131015
pure $ computeLinkableType ms modsums (map join needsComps)
10141016

1015-
pure (Just $ LBS.toStrict $ B.encode $ hash res, Just res)
1017+
pure (Just $ encodeLinkableType res, Just res)
10161018
where
10171019
uses_th_qq (ms_hspp_opts -> dflags) =
10181020
xopt LangExt.TemplateHaskell dflags || xopt LangExt.QuasiQuotes dflags
10191021

1020-
unboxed_tuples_or_sums (ms_hspp_opts -> d) =
1021-
xopt LangExt.UnboxedTuples d || xopt LangExt.UnboxedSums d
1022-
10231022
computeLinkableType :: ModSummary -> [Maybe ModSummary] -> [Maybe LinkableType] -> Maybe LinkableType
10241023
computeLinkableType this deps xs
10251024
| Just ObjectLinkable `elem` xs = Just ObjectLinkable -- If any dependent needs object code, so do we
10261025
| Just BCOLinkable `elem` xs = Just this_type -- If any dependent needs bytecode, then we need to be compiled
10271026
| any (maybe False uses_th_qq) deps = Just this_type -- If any dependent needs TH, then we need to be compiled
10281027
| otherwise = Nothing -- If none of these conditions are satisfied, we don't need to compile
10291028
where
1030-
-- How should we compile this module? (assuming we do in fact need to compile it)
1031-
-- Depends on whether it uses unboxed tuples or sums
1032-
this_type
1029+
this_type = computeLinkableTypeForDynFlags (ms_hspp_opts this)
1030+
1031+
-- | How should we compile this module?
1032+
-- (assuming we do in fact need to compile it).
1033+
-- Depends on whether it uses unboxed tuples or sums
1034+
computeLinkableTypeForDynFlags :: DynFlags -> LinkableType
1035+
computeLinkableTypeForDynFlags d
10331036
#if defined(GHC_PATCHED_UNBOXED_BYTECODE)
10341037
= BCOLinkable
10351038
#else
1036-
| unboxed_tuples_or_sums this = ObjectLinkable
1037-
| otherwise = BCOLinkable
1039+
| unboxed_tuples_or_sums = ObjectLinkable
1040+
| otherwise = BCOLinkable
10381041
#endif
1042+
where
1043+
unboxed_tuples_or_sums =
1044+
xopt LangExt.UnboxedTuples d || xopt LangExt.UnboxedSums d
10391045

10401046
-- | Tracks which linkables are current, so we don't need to unload them
10411047
newtype CompiledLinkables = CompiledLinkables { getCompiledLinkables :: Var (ModuleEnv UTCTime) }
@@ -1074,7 +1080,8 @@ mainRule = do
10741080
getClientSettingsRule
10751081
getHieAstsRule
10761082
getBindingsRule
1077-
needsCompilationRule
1083+
defineEarlyCutoff $ RuleNoDiagnostics $ \NeedsCompilation file ->
1084+
needsCompilationRule file
10781085
generateCoreRule
10791086
getImportMapRule
10801087
getAnnotatedParsedSourceRule

ghcide/src/Development/IDE/Core/Shake.hs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -871,17 +871,25 @@ usesWithStale key files = do
871871
data RuleBody k v
872872
= Rule (k -> NormalizedFilePath -> Action (Maybe BS.ByteString, IdeResult v))
873873
| RuleNoDiagnostics (k -> NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe v))
874-
874+
| RuleWithCustomNewnessCheck
875+
{ newnessCheck :: BS.ByteString -> BS.ByteString -> Bool
876+
, build :: k -> NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe v)
877+
}
875878

876879
-- | Define a new Rule with early cutoff
877880
defineEarlyCutoff
878881
:: IdeRule k v
879882
=> RuleBody k v
880883
-> Rules ()
881884
defineEarlyCutoff (Rule op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ do
882-
defineEarlyCutoff' True key file old mode $ op key file
885+
defineEarlyCutoff' True (==) key file old mode $ op key file
883886
defineEarlyCutoff (RuleNoDiagnostics op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ do
884-
defineEarlyCutoff' False key file old mode $ second (mempty,) <$> op key file
887+
defineEarlyCutoff' False (==) key file old mode $ second (mempty,) <$> op key file
888+
defineEarlyCutoff RuleWithCustomNewnessCheck{..} =
889+
addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode ->
890+
otTracedAction key file mode traceA $
891+
defineEarlyCutoff' False newnessCheck key file old mode $
892+
second (mempty,) <$> build key file
885893

886894
defineNoFile :: IdeRule k v => (k -> Action v) -> Rules ()
887895
defineNoFile f = defineNoDiagnostics $ \k file -> do
@@ -896,13 +904,15 @@ defineEarlyCutOffNoFile f = defineEarlyCutoff $ RuleNoDiagnostics $ \k file -> d
896904
defineEarlyCutoff'
897905
:: IdeRule k v
898906
=> Bool -- ^ update diagnostics
907+
-- | compare current and previous for freshness
908+
-> (BS.ByteString -> BS.ByteString -> Bool)
899909
-> k
900910
-> NormalizedFilePath
901911
-> Maybe BS.ByteString
902912
-> RunMode
903913
-> Action (Maybe BS.ByteString, IdeResult v)
904914
-> Action (RunResult (A (RuleResult k)))
905-
defineEarlyCutoff' doDiagnostics key file old mode action = do
915+
defineEarlyCutoff' doDiagnostics cmp key file old mode action = do
906916
extras@ShakeExtras{state, progress, logger, dirtyKeys} <- getShakeExtras
907917
options <- getIdeOptions
908918
(if optSkipProgress options key then id else inProgress progress file) $ do
@@ -947,8 +957,8 @@ defineEarlyCutoff' doDiagnostics key file old mode action = do
947957
then updateFileDiagnostics file (Key key) extras $ map (\(_,y,z) -> (y,z)) diags
948958
else forM_ diags $ \d -> liftIO $ logWarning logger $ showDiagnosticsColored [d]
949959
let eq = case (bs, fmap decodeShakeValue old) of
950-
(ShakeResult a, Just (ShakeResult b)) -> a == b
951-
(ShakeStale a, Just (ShakeStale b)) -> a == b
960+
(ShakeResult a, Just (ShakeResult b)) -> cmp a b
961+
(ShakeStale a, Just (ShakeStale b)) -> cmp a b
952962
-- If we do not have a previous result
953963
-- or we got ShakeNoCutoff we always return False.
954964
_ -> False

ghcide/src/Development/IDE/Types/Diagnostics.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module Development.IDE.Types.Diagnostics (
1414
ideErrorWithSource,
1515
showDiagnostics,
1616
showDiagnosticsColored,
17-
) where
17+
IdeResultNoDiagnosticsEarlyCutoff) where
1818

1919
import Control.DeepSeq
2020
import Data.Maybe as Maybe
@@ -29,6 +29,7 @@ import Language.LSP.Types as LSP (Diagnostic (.
2929
DiagnosticSource,
3030
List (..))
3131

32+
import Data.ByteString (ByteString)
3233
import Development.IDE.Types.Location
3334

3435

@@ -44,6 +45,9 @@ import Development.IDE.Types.Location
4445
-- not propagate diagnostic errors through multiple phases.
4546
type IdeResult v = ([FileDiagnostic], Maybe v)
4647

48+
-- | an IdeResult with a fingerprint
49+
type IdeResultNoDiagnosticsEarlyCutoff v = (Maybe ByteString, Maybe v)
50+
4751
ideErrorText :: NormalizedFilePath -> T.Text -> FileDiagnostic
4852
ideErrorText = ideErrorWithSource (Just "compiler") (Just DsError)
4953

haskell-language-server.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ common haddockComments
212212

213213
common eval
214214
if flag(eval) || flag(all-plugins)
215-
build-depends: hls-eval-plugin ^>=1.1.0.0
215+
build-depends: hls-eval-plugin ^>=1.2.0.0
216216
cpp-options: -Deval
217217

218218
common importLens

hls-graph/src/Development/IDE/Graph/Internal/Database.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,9 @@ compute db@Database{..} key id mode result = do
168168
actualDeps = if runChanged /= ChangedNothing then deps else previousDeps
169169
previousDeps= maybe UnknownDeps resultDeps result
170170
let res = Result runValue built' changed built actualDeps execution runStore
171-
case actualDeps of
172-
ResultDeps deps | not(null deps) &&
173-
runChanged /= ChangedNothing
171+
case getResultDepsDefault [] actualDeps of
172+
deps | not(null deps)
173+
&& runChanged /= ChangedNothing
174174
-> do
175175
void $ forkIO $
176176
updateReverseDeps id db (getResultDepsDefault [] previousDeps) (Set.fromList deps)
@@ -284,7 +284,7 @@ mapConcurrentlyAIO_ f [one] = liftIO $ justWait $ fmap f one
284284
mapConcurrentlyAIO_ f many = do
285285
ref <- AIO ask
286286
waits <- liftIO $ uninterruptibleMask $ \restore -> do
287-
waits <- liftIO $ traverse waitOrSpawn (map (fmap (restore . f)) many)
287+
waits <- liftIO $ traverse (waitOrSpawn . fmap (restore . f)) many
288288
let asyncs = rights waits
289289
liftIO $ atomicModifyIORef'_ ref (asyncs ++)
290290
return waits

plugins/hls-eval-plugin/hls-eval-plugin.cabal

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 2.4
22
name: hls-eval-plugin
3-
version: 1.1.2.0
3+
version: 1.2.0.0
44
synopsis: Eval plugin for Haskell Language Server
55
description:
66
Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>
@@ -85,7 +85,7 @@ library
8585
, unordered-containers
8686

8787
ghc-options:
88-
-Wall -Wno-name-shadowing -Wno-unticked-promoted-constructors
88+
-Wall -Wno-name-shadowing -Wno-unticked-promoted-constructors -fno-ignore-asserts
8989

9090
if flag(pedantic)
9191
ghc-options: -Werror

plugins/hls-eval-plugin/src/Ide/Plugin/Eval/CodeLens.hs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import Development.IDE (Action, GetDependencies (..),
5151
HiFileResult (hirHomeMod, hirModSummary),
5252
HscEnvEq, IdeState,
5353
ModSummaryResult (..),
54+
NeedsCompilation (NeedsCompilation),
5455
evalGhcEnv,
5556
hscEnvWithImportPaths,
5657
prettyPrint, runAction,
@@ -109,7 +110,10 @@ import UnliftIO.Temporary (withSystemTempFile)
109110
import GHC.Driver.Session (unitDatabases, unitState)
110111
import GHC.Types.SrcLoc (UnhelpfulSpanReason (UnhelpfulInteractive))
111112
#else
113+
import Development.IDE.Core.FileStore (setSomethingModified)
114+
import Development.IDE.Types.Shake (toKey)
112115
import DynFlags
116+
import Ide.Plugin.Eval.Rules (queueForEvaluation)
113117
#endif
114118

115119

@@ -196,6 +200,10 @@ runEvalCmd st EvalParams{..} =
196200
let nfp = toNormalizedFilePath' fp
197201
mdlText <- moduleText _uri
198202

203+
-- enable codegen
204+
liftIO $ queueForEvaluation st nfp
205+
liftIO $ setSomethingModified st [toKey NeedsCompilation nfp] "Eval"
206+
199207
session <- runGetSession st nfp
200208

201209
ms <- fmap msrModSummary $

plugins/hls-eval-plugin/src/Ide/Plugin/Eval/Rules.hs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,54 @@
33
{-# LANGUAGE PatternSynonyms #-}
44
{-# LANGUAGE RecordWildCards #-}
55

6-
module Ide.Plugin.Eval.Rules (GetEvalComments(..), rules) where
6+
module Ide.Plugin.Eval.Rules (GetEvalComments(..), rules,queueForEvaluation) where
77

8+
import Control.Monad.IO.Class (MonadIO (liftIO))
9+
import Data.HashSet (HashSet)
10+
import qualified Data.HashSet as Set
11+
import Data.IORef
812
import qualified Data.Map.Strict as Map
9-
import Development.IDE (GetParsedModuleWithComments (GetParsedModuleWithComments),
10-
Rules,
11-
defineNoDiagnostics,
13+
import Data.String (fromString)
14+
import Development.IDE (GetModSummaryWithoutTimestamps (GetModSummaryWithoutTimestamps),
15+
GetParsedModuleWithComments (GetParsedModuleWithComments),
16+
IdeState,
17+
NeedsCompilation (NeedsCompilation),
18+
NormalizedFilePath,
19+
RuleBody (RuleNoDiagnostics),
20+
Rules, defineEarlyCutoff,
21+
encodeLinkableType,
1222
fromNormalizedFilePath,
23+
msrModSummary,
1324
realSrcSpanToRange,
1425
useWithStale_)
1526
import Development.IDE.Core.PositionMapping (toCurrentRange)
27+
import Development.IDE.Core.Rules (computeLinkableTypeForDynFlags,
28+
needsCompilationRule)
29+
import Development.IDE.Core.Shake (IsIdeGlobal,
30+
RuleBody (RuleWithCustomNewnessCheck),
31+
addIdeGlobal,
32+
getIdeGlobalAction,
33+
getIdeGlobalState)
1634
import Development.IDE.GHC.Compat
1735
import qualified Development.IDE.GHC.Compat as SrcLoc
1836
import qualified Development.IDE.GHC.Compat.Util as FastString
37+
import Development.IDE.Graph (alwaysRerun)
1938
import Ide.Plugin.Eval.Types
2039

2140

2241
rules :: Rules ()
2342
rules = do
2443
evalParsedModuleRule
44+
redefinedNeedsCompilation
45+
addIdeGlobal . EvaluatingVar =<< liftIO(newIORef mempty)
46+
47+
newtype EvaluatingVar = EvaluatingVar (IORef (HashSet NormalizedFilePath))
48+
instance IsIdeGlobal EvaluatingVar
49+
50+
queueForEvaluation :: IdeState -> NormalizedFilePath -> IO ()
51+
queueForEvaluation ide nfp = do
52+
EvaluatingVar var <- getIdeGlobalState ide
53+
modifyIORef var (Set.insert nfp)
2554

2655
#if MIN_VERSION_ghc(9,0,0)
2756
pattern RealSrcSpanAlready :: SrcLoc.RealSrcSpan -> SrcLoc.RealSrcSpan
@@ -37,10 +66,9 @@ pattern RealSrcSpanAlready x = SrcLoc.RealSrcSpan x Nothing
3766
#endif
3867

3968
evalParsedModuleRule :: Rules ()
40-
evalParsedModuleRule = defineNoDiagnostics $ \GetEvalComments nfp -> do
69+
evalParsedModuleRule = defineEarlyCutoff $ RuleNoDiagnostics $ \GetEvalComments nfp -> do
4170
(ParsedModule{..}, posMap) <- useWithStale_ GetParsedModuleWithComments nfp
42-
return $ Just $
43-
foldMap (\case
71+
let comments = foldMap (\case
4472
L (RealSrcSpanAlready real) bdy
4573
| FastString.unpackFS (srcSpanFile real) ==
4674
fromNormalizedFilePath nfp
@@ -59,3 +87,31 @@ evalParsedModuleRule = defineNoDiagnostics $ \GetEvalComments nfp -> do
5987
_ -> mempty
6088
)
6189
$ apiAnnComments' pm_annotations
90+
-- we only care about whether the comments are null
91+
-- this is valid because the only dependent is NeedsCompilation
92+
fingerPrint = fromString $ if nullComments comments then "" else "1"
93+
return (Just fingerPrint, Just comments)
94+
95+
-- Redefine the NeedsCompilation rule to set the linkable type to Just _
96+
-- whenever the module is being evaluated
97+
-- This will ensure that the modules are loaded with linkables
98+
-- and the interactive session won't try to compile them on the fly,
99+
-- leading to much better performance of the evaluate code lens
100+
redefinedNeedsCompilation :: Rules ()
101+
redefinedNeedsCompilation = defineEarlyCutoff $ RuleWithCustomNewnessCheck (<=) $ \NeedsCompilation f -> do
102+
alwaysRerun
103+
104+
EvaluatingVar var <- getIdeGlobalAction
105+
isEvaluating <- liftIO $ (f `elem`) <$> readIORef var
106+
107+
108+
if not isEvaluating then needsCompilationRule f else do
109+
ms <- msrModSummary . fst <$> useWithStale_ GetModSummaryWithoutTimestamps f
110+
let df' = ms_hspp_opts ms
111+
linkableType = computeLinkableTypeForDynFlags df'
112+
fp = encodeLinkableType $ Just linkableType
113+
114+
-- remove the module from the Evaluating state
115+
liftIO $ modifyIORef var (Set.delete f)
116+
117+
pure (Just fp, Just (Just linkableType))

plugins/hls-eval-plugin/src/Ide/Plugin/Eval/Types.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
{-# LANGUAGE TypeFamilies #-}
77
{-# LANGUAGE UndecidableInstances #-}
88
{-# OPTIONS_GHC -Wwarn #-}
9+
{-# LANGUAGE RecordWildCards #-}
910

1011
module Ide.Plugin.Eval.Types
1112
( locate,
@@ -28,7 +29,7 @@ module Ide.Plugin.Eval.Types
2829
Txt,
2930
EvalParams(..),
3031
GetEvalComments(..)
31-
)
32+
,nullComments)
3233
where
3334

3435
import Control.DeepSeq (deepseq)
@@ -107,6 +108,9 @@ data Comments = Comments
107108
}
108109
deriving (Show, Eq, Ord, Generic)
109110

111+
nullComments :: Comments -> Bool
112+
nullComments Comments{..} = null lineComments && null blockComments
113+
110114
instance NFData Comments
111115

112116
newtype RawBlockComment = RawBlockComment {getRawBlockComment :: String}

0 commit comments

Comments
 (0)