Skip to content

Commit 54f043c

Browse files
committed
Serialize core to core files
Add a `.hi.core` file format to which we serialize out compiled core after generating it. This core is then read back in on subsequent runs and compiled to bytecode. This greatly speeds up startup times when we need compilation, as we can simply read bytecode off the disk instead of having to recompile a lot of modules This is based off Fat Interface files in GHC: https://gitlab.haskell.org/ghc/ghc/-/merge_requests/7502
1 parent 8466bc1 commit 54f043c

File tree

4 files changed

+329
-54
lines changed

4 files changed

+329
-54
lines changed

ghcide/ghcide.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ library
172172
Development.IDE.GHC.Compat.Units
173173
Development.IDE.GHC.Compat.Util
174174
Development.IDE.Core.Compile
175+
Development.IDE.GHC.CoreFile
175176
Development.IDE.GHC.Dump
176177
Development.IDE.GHC.Error
177178
Development.IDE.GHC.ExactPrint

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

Lines changed: 118 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ module Development.IDE.Core.Compile
3535

3636
import Control.Concurrent.Extra
3737
import Control.Concurrent.STM.Stats hiding (orElse)
38-
import Control.DeepSeq (force, liftRnf, rnf, rwhnf)
38+
import Control.DeepSeq (force, liftRnf, rnf, rwhnf, NFData(..))
3939
import Control.Exception (evaluate)
4040
import Control.Exception.Safe
41-
import Control.Lens hiding (List)
41+
import Control.Lens hiding (List, (<.>))
4242
import Control.Monad.Except
4343
import Control.Monad.Extra
4444
import Control.Monad.Trans.Except
@@ -62,7 +62,7 @@ import Data.Maybe
6262
import qualified Data.Text as T
6363
import Data.Time (UTCTime (..),
6464
getCurrentTime)
65-
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
65+
import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
6666
import Data.Tuple.Extra (dupe)
6767
import Data.Unique as Unique
6868
import Debug.Trace
@@ -84,6 +84,7 @@ import Development.IDE.Spans.Common
8484
import Development.IDE.Types.Diagnostics
8585
import Development.IDE.Types.Location
8686
import Development.IDE.Types.Options
87+
import Development.IDE.GHC.CoreFile
8788
import GHC (ForeignHValue,
8889
GetDocsFailure (..),
8990
mgModSummaries,
@@ -105,13 +106,23 @@ import ErrUtils
105106

106107
#if MIN_VERSION_ghc(9,0,1)
107108
import GHC.Tc.Gen.Splice
109+
110+
#if MIN_VERSION_ghc(9,2,1)
111+
import GHC.Types.HpcInfo
112+
import GHC.Types.ForeignStubs
113+
import GHC.Types.TypeEnv
114+
#else
115+
import GHC.Driver.Types
116+
#endif
117+
108118
#else
109119
import TcSplice
120+
import HscTypes
110121
#endif
111122

112-
#if MIN_VERSION_ghc(9,2,0)
113123
import Development.IDE.GHC.Compat.Util (emptyUDFM, fsLit,
114124
plusUDFM_C)
125+
#if MIN_VERSION_ghc(9,2,0)
115126
import GHC (Anchor (anchor),
116127
EpaComment (EpaComment),
117128
EpaCommentTok (EpaBlockComment, EpaLineComment),
@@ -381,7 +392,7 @@ mkHiFileResultCompile session' tcm simplified_guts ltype = catchErrs $ do
381392

382393
let genLinkable = case ltype of
383394
ObjectLinkable -> generateObjectCode
384-
BCOLinkable -> generateByteCode
395+
BCOLinkable -> generateByteCode WriteCoreFile
385396

386397
(linkable, details, diags) <-
387398
if mg_hsc_src simplified_guts == HsBootFile
@@ -483,8 +494,10 @@ generateObjectCode session summary guts = do
483494

484495
pure (map snd warnings, linkable)
485496

486-
generateByteCode :: HscEnv -> ModSummary -> CgGuts -> IO (IdeResult Linkable)
487-
generateByteCode hscEnv summary guts = do
497+
data WriteCoreFile = WriteCoreFile | CoreFileExists !UTCTime
498+
499+
generateByteCode :: WriteCoreFile -> HscEnv -> ModSummary -> CgGuts -> IO (IdeResult Linkable)
500+
generateByteCode write_core hscEnv summary guts = do
488501
fmap (either (, Nothing) (second Just)) $
489502
catchSrcErrors (hsc_dflags hscEnv) "bytecode" $ do
490503
(warnings, (_, bytecode, sptEntries)) <-
@@ -499,7 +512,14 @@ generateByteCode hscEnv summary guts = do
499512
summary'
500513
#endif
501514
let unlinked = BCOs bytecode sptEntries
502-
time <- liftIO getCurrentTime
515+
time <- case write_core of
516+
CoreFileExists time -> pure time
517+
WriteCoreFile -> liftIO $ do
518+
let core_fp = ml_core_file $ ms_location summary
519+
core_file = codeGutsToCoreFile guts
520+
atomicFileWrite core_fp $ \fp ->
521+
writeBinCoreFile fp core_file
522+
getModificationTime core_fp
503523
let linkable = LM time (ms_mod summary) [unlinked]
504524

505525
pure (map snd warnings, linkable)
@@ -1124,6 +1144,17 @@ data RecompilationInfo m
11241144
, regenerate :: Maybe LinkableType -> m ([FileDiagnostic], Maybe HiFileResult) -- ^ Action to regenerate an interface
11251145
}
11261146

1147+
-- | Either a regular GHC linkable or a core file that
1148+
-- can be later turned into a proper linkable
1149+
data IdeLinkable = GhcLinkable !Linkable | CoreLinkable !UTCTime !CoreFile
1150+
1151+
instance NFData IdeLinkable where
1152+
rnf (GhcLinkable lb) = rnf lb
1153+
rnf (CoreLinkable time _) = rnf time
1154+
1155+
ml_core_file :: ModLocation -> FilePath
1156+
ml_core_file ml = ml_hi_file ml <.> "core"
1157+
11271158
-- | Retuns an up-to-date module interface, regenerating if needed.
11281159
-- Assumes file exists.
11291160
-- Requires the 'HscEnv' to be set up with dependencies
@@ -1141,14 +1172,22 @@ loadInterface session ms linkableNeeded RecompilationInfo{..} = do
11411172
mb_old_version = snd <$> old_value
11421173

11431174
obj_file = ml_obj_file (ms_location ms)
1175+
core_file = ml_core_file (ms_location ms)
1176+
iface_file = ml_hi_file (ms_location ms)
11441177

11451178
!mod = ms_mod ms
11461179

11471180
mb_dest_version <- case mb_old_version of
11481181
Just ver -> pure $ Just ver
1149-
Nothing -> get_file_version $ toNormalizedFilePath' $ case linkableNeeded of
1150-
Just ObjectLinkable -> ml_obj_file (ms_location ms)
1151-
_ -> ml_hi_file (ms_location ms)
1182+
Nothing -> liftIO $ do
1183+
let file = case linkableNeeded of
1184+
Just ObjectLinkable -> obj_file
1185+
Just BCOLinkable -> core_file
1186+
Nothing -> iface_file
1187+
exists <- doesFileExist file
1188+
if exists
1189+
then Just . ModificationTime . utcTimeToPOSIXSeconds <$> getModificationTime file
1190+
else pure Nothing
11521191

11531192
-- The source is modified if it is newer than the destination
11541193
let sourceMod = case mb_dest_version of
@@ -1162,42 +1201,46 @@ loadInterface session ms linkableNeeded RecompilationInfo{..} = do
11621201
<- liftIO $ checkOldIface sessionWithMsDynFlags ms sourceMod mb_old_iface
11631202

11641203

1165-
let
1166-
(recomp_obj_reqd, mb_linkable) = case linkableNeeded of
1167-
Nothing -> (UpToDate, Nothing)
1168-
Just linkableType -> case old_value of
1169-
-- We don't have an old result
1170-
Nothing -> recompMaybeBecause "missing"
1171-
-- We have an old result
1172-
Just (old_hir, old_file_version) ->
1173-
case hm_linkable $ hirHomeMod old_hir of
1174-
Nothing -> recompMaybeBecause "missing [not needed before]"
1175-
Just old_lb
1176-
| Just True <- mi_used_th <$> mb_checked_iface -- No need to recompile if TH wasn't used
1177-
, old_file_version /= source_version -> recompMaybeBecause "out of date"
1178-
1179-
-- Check if it is the correct type
1180-
-- Ideally we could use object-code in case we already have
1181-
-- it when we are generating bytecode, but this is difficult because something
1182-
-- below us may be bytecode, and object code can't depend on bytecode
1183-
| ObjectLinkable <- linkableType, isObjectLinkable old_lb
1184-
-> (UpToDate, Just old_lb)
1185-
1186-
| BCOLinkable <- linkableType , not (isObjectLinkable old_lb)
1187-
-> (UpToDate, Just old_lb)
1188-
1189-
| otherwise -> recompMaybeBecause "missing [wrong type]"
1190-
where
1191-
recompMaybeBecause msg = case linkableType of
1192-
BCOLinkable -> (RecompBecause ("bytecode "++ msg), Nothing)
1193-
ObjectLinkable -> case mb_dest_version of -- The destination file should be the object code
1194-
Nothing -> (RecompBecause ("object code "++ msg), Nothing)
1195-
Just disk_obj_version@(ModificationTime t) ->
1196-
-- If we make it this far, assume that the object code on disk is up to date
1197-
-- This assertion works because of the sourceMod check
1198-
assert (disk_obj_version >= source_version)
1199-
(UpToDate, Just $ LM (posixSecondsToUTCTime t) mod [DotO obj_file])
1200-
Just (VFSVersion _) -> error "object code in vfs"
1204+
(recomp_obj_reqd, mb_linkable) <- case linkableNeeded of
1205+
Nothing -> pure (UpToDate, Nothing)
1206+
Just linkableType -> case old_value of
1207+
-- We don't have an old result
1208+
Nothing -> recompMaybeBecause "missing"
1209+
-- We have an old result
1210+
Just (old_hir, old_file_version) ->
1211+
case hm_linkable $ hirHomeMod old_hir of
1212+
Nothing -> recompMaybeBecause "missing [not needed before]"
1213+
Just old_lb
1214+
| Just True <- mi_used_th <$> mb_checked_iface -- No need to recompile if TH wasn't used
1215+
, old_file_version /= source_version -> recompMaybeBecause "out of date"
1216+
1217+
-- Check if it is the correct type
1218+
-- Ideally we could use object-code in case we already have
1219+
-- it when we are generating bytecode, but this is difficult because something
1220+
-- below us may be bytecode, and object code can't depend on bytecode
1221+
| ObjectLinkable <- linkableType, isObjectLinkable old_lb
1222+
-> pure (UpToDate, Just $ GhcLinkable old_lb)
1223+
1224+
| BCOLinkable <- linkableType , not (isObjectLinkable old_lb)
1225+
-> pure (UpToDate, Just $ GhcLinkable old_lb)
1226+
1227+
where
1228+
recompMaybeBecause msg =
1229+
case mb_dest_version of -- The destination file should be the object code or the core file
1230+
Nothing -> pure (RecompBecause msg', Nothing)
1231+
Just disk_obj_version@(ModificationTime t) ->
1232+
if (disk_obj_version >= source_version)
1233+
then case linkableType of
1234+
ObjectLinkable -> pure (UpToDate, Just $ GhcLinkable $ LM (posixSecondsToUTCTime t) mod [DotO obj_file])
1235+
BCOLinkable -> liftIO $ do
1236+
core <- readBinCoreFile (mkUpdater $ hsc_NC session) core_file
1237+
pure (UpToDate, Just $ CoreLinkable (posixSecondsToUTCTime t) core)
1238+
else pure (RecompBecause msg', Nothing)
1239+
Just (VFSVersion _) -> pure (RecompBecause msg', Nothing)
1240+
where
1241+
msg' = case linkableType of
1242+
BCOLinkable -> "bytecode " ++ msg
1243+
ObjectLinkable -> "Object code " ++ msg
12011244

12021245
let do_regenerate _reason = withTrace "regenerate interface" $ \setTag -> do
12031246
setTag "Module" $ moduleNameString $ moduleName mod
@@ -1217,12 +1260,12 @@ loadInterface session ms linkableNeeded RecompilationInfo{..} = do
12171260
-> do_regenerate msg
12181261
| otherwise -> return ([], Just old_hir)
12191262
Nothing -> do
1220-
hmi <- liftIO $ mkDetailsFromIface sessionWithMsDynFlags iface lb
1263+
(warns, hmi) <- liftIO $ mkDetailsFromIface sessionWithMsDynFlags ms iface lb
12211264
-- parse the runtime dependencies from the annotations
12221265
let runtime_deps
12231266
| not (mi_used_th iface) = emptyModuleEnv
12241267
| otherwise = parseRuntimeDeps (md_anns (hm_details hmi))
1225-
return ([], Just $ mkHiFileResult ms hmi runtime_deps)
1268+
return (warns, Just $ mkHiFileResult ms hmi runtime_deps)
12261269
(_, _reason) -> do_regenerate _reason
12271270

12281271
-- | ModDepTime is stored as an annotation in the iface to
@@ -1269,12 +1312,34 @@ showReason UpToDate = "UpToDate"
12691312
showReason MustCompile = "MustCompile"
12701313
showReason (RecompBecause s) = s
12711314

1272-
mkDetailsFromIface :: HscEnv -> ModIface -> Maybe Linkable -> IO HomeModInfo
1273-
mkDetailsFromIface session iface linkable = do
1315+
mkDetailsFromIface :: HscEnv -> ModSummary -> ModIface -> Maybe IdeLinkable -> IO ([FileDiagnostic], HomeModInfo)
1316+
mkDetailsFromIface session ms iface ide_linkable = do
12741317
details <- liftIO $ fixIO $ \details -> do
1275-
let hsc' = session { hsc_HPT = addToHpt (hsc_HPT session) (moduleName $ mi_module iface) (HomeModInfo iface details linkable) }
1318+
let hsc' = session { hsc_HPT = addToHpt (hsc_HPT session) (moduleName $ mi_module iface) (HomeModInfo iface details Nothing) }
12761319
initIfaceLoad hsc' (typecheckIface iface)
1277-
return (HomeModInfo iface details linkable)
1320+
(warns, linkable) <- liftIO $ case ide_linkable of
1321+
Nothing -> pure ([], Nothing)
1322+
Just (GhcLinkable lb) -> pure ([], Just lb)
1323+
Just (CoreLinkable t core_file) -> do
1324+
cgi_guts <- coreFileToCgGuts session iface details core_file
1325+
generateByteCode (CoreFileExists t) session ms cgi_guts
1326+
1327+
return (warns, HomeModInfo iface details linkable)
1328+
1329+
coreFileToCgGuts :: HscEnv -> ModIface -> ModDetails -> CoreFile -> IO CgGuts
1330+
coreFileToCgGuts session iface details core_file = do
1331+
let act hpt = addToHpt hpt (moduleName this_mod)
1332+
(HomeModInfo iface details Nothing)
1333+
this_mod = mi_module iface
1334+
types_var <- newIORef (md_types details)
1335+
let kv = Just (this_mod, types_var)
1336+
hsc_env' = session { hsc_HPT = act (hsc_HPT session)
1337+
, hsc_type_env_var = kv }
1338+
core_binds <- initIfaceCheck (text "l") hsc_env' $ typecheckCoreFile this_mod types_var core_file
1339+
-- Implicit binds aren't saved, so we need to regenerate them ourselves.
1340+
let implicit_binds = concatMap getImplicitBinds tyCons
1341+
tyCons = typeEnvTyCons (md_types details)
1342+
pure $ CgGuts this_mod tyCons (implicit_binds ++ core_binds) NoStubs [] [] (emptyHpcInfo False) Nothing []
12781343

12791344
-- | Non-interactive, batch version of 'InteractiveEval.getDocs'.
12801345
-- The interactive paths create problems in ghc-lib builds

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,7 @@ getModIfaceFromDiskRule recorder = defineEarlyCutoff (cmapWithPrio LogShake reco
767767
Just session -> do
768768
linkableType <- getLinkableType f
769769
ver <- use_ GetModificationTime f
770+
ShakeExtras{ideNc} <- getShakeExtras
770771
let m_old = case old of
771772
Shake.Succeeded (Just old_version) v -> Just (v, old_version)
772773
Shake.Stale _ (Just old_version) v -> Just (v, old_version)
@@ -834,9 +835,11 @@ instance IsIdeGlobal DisplayTHWarning
834835
getModSummaryRule :: LspT Config IO () -> Recorder (WithPriority Log) -> Rules ()
835836
getModSummaryRule displayTHWarning recorder = do
836837
menv <- lspEnv <$> getShakeExtrasRules
837-
forM_ menv $ \env -> do
838+
case menv of
839+
Just env -> do
838840
displayItOnce <- liftIO $ once $ LSP.runLspT env displayTHWarning
839841
addIdeGlobal (DisplayTHWarning displayItOnce)
842+
Nothing -> addIdeGlobal (DisplayTHWarning $ pure ())
840843

841844
defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModSummary f -> do
842845
session' <- hscEnv <$> use_ GhcSession f

0 commit comments

Comments
 (0)