Skip to content

Commit 89faced

Browse files
committed
Add --verify-core-file to do roundtrip testing of core-files
1 parent 00f04ca commit 89faced

File tree

10 files changed

+96
-21
lines changed

10 files changed

+96
-21
lines changed

ghcide/exe/Arguments.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ data Arguments = Arguments
1515
,argsOTMemoryProfiling :: Bool
1616
,argsTesting :: Bool
1717
,argsDisableKick :: Bool
18+
,argsVerifyCoreFile :: Bool
1819
,argsThreads :: Int
1920
,argsVerbose :: Bool
2021
,argsCommand :: Command
@@ -36,6 +37,7 @@ arguments plugins = Arguments
3637
<*> switch (long "ot-memory-profiling" <> help "Record OpenTelemetry info to the eventlog. Needs the -l RTS flag to have an effect")
3738
<*> switch (long "test" <> help "Enable additional lsp messages used by the testsuite")
3839
<*> switch (long "test-no-kick" <> help "Disable kick. Useful for testing cancellation")
40+
<*> switch (long "verify-core-file" <> help "Verify core trips by roundtripping after serialization. Slow, only useful for testing purposes")
3941
<*> option auto (short 'j' <> help "Number of threads (0: automatic)" <> metavar "NUM" <> value 0 <> showDefault)
4042
<*> switch (short 'd' <> long "verbose" <> help "Include internal events in logging output")
4143
<*> (commandP plugins <|> lspCommand <|> checkCommand)

ghcide/exe/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,6 @@ main = withTelemetryLogger $ \telemetryLogger -> do
141141
, optCheckParents = pure $ checkParents config
142142
, optCheckProject = pure $ checkProject config
143143
, optRunSubset = not argsConservativeChangeTracking
144+
, optVerifyCoreFile = argsVerifyCoreFile
144145
}
145146
}

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

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ import qualified GHC as G
132132
import GHC.Hs (LEpaComment)
133133
import qualified GHC.Types.Error as Error
134134
#endif
135+
import qualified Control.Monad.Trans.State.Strict as S
136+
import Data.Generics.Schemes
137+
import Data.Generics.Aliases
135138

136139
-- | Given a string buffer, return the string (after preprocessing) and the 'ParsedModule'.
137140
parseModule
@@ -380,12 +383,13 @@ mkHiFileResultNoCompile session tcm = do
380383
pure $! mkHiFileResult ms mod_info (tmrRuntimeModules tcm)
381384

382385
mkHiFileResultCompile
383-
:: HscEnv
386+
:: ShakeExtras
387+
-> HscEnv
384388
-> TcModuleResult
385389
-> ModGuts
386390
-> LinkableType -- ^ use object code or byte code?
387391
-> IO (IdeResult HiFileResult)
388-
mkHiFileResultCompile session' tcm simplified_guts ltype = catchErrs $ do
392+
mkHiFileResultCompile se session' tcm simplified_guts ltype = catchErrs $ do
389393
let session = hscSetFlags (ms_hspp_opts ms) session'
390394
ms = pm_mod_summary $ tmrParsed tcm
391395
tcGblEnv = tmrTypechecked tcm
@@ -394,17 +398,17 @@ mkHiFileResultCompile session' tcm simplified_guts ltype = catchErrs $ do
394398
ObjectLinkable -> generateObjectCode
395399
BCOLinkable -> generateByteCode WriteCoreFile
396400

397-
(linkable, details, diags) <-
401+
(linkable, details, mguts, diags) <-
398402
if mg_hsc_src simplified_guts == HsBootFile
399403
then do
400404
-- give variables unique OccNames
401405
details <- mkBootModDetailsTc session tcGblEnv
402-
pure (Nothing, details, [])
406+
pure (Nothing, details, Nothing, [])
403407
else do
404408
-- give variables unique OccNames
405409
(guts, details) <- tidyProgram session simplified_guts
406410
(diags, linkable) <- genLinkable session ms guts
407-
pure (linkable, details, diags)
411+
pure (linkable, details, Just guts, diags)
408412
#if MIN_VERSION_ghc(9,0,1)
409413
let !partial_iface = force (mkPartialIface session details simplified_guts)
410414
final_iface <- mkFullIface session partial_iface Nothing
@@ -415,6 +419,51 @@ mkHiFileResultCompile session' tcm simplified_guts ltype = catchErrs $ do
415419
(final_iface,_) <- mkIface session Nothing details simplified_guts
416420
#endif
417421
let mod_info = HomeModInfo final_iface details linkable
422+
423+
-- Verify core file by rountrip testing and comparison
424+
IdeOptions{optVerifyCoreFile} <- getIdeOptionsIO se
425+
when (maybe False (not . isObjectLinkable) linkable && optVerifyCoreFile) $ do
426+
let core_fp = ml_core_file $ ms_location ms
427+
traceIO $ "Verifying " ++ core_fp
428+
core <- readBinCoreFile (mkUpdater $ hsc_NC session) core_fp
429+
let CgGuts{cg_binds = unprep_binds, cg_tycons = tycons } = case mguts of
430+
Nothing -> error "invariant optVerifyCoreFile: guts must exist if linkable exists)"
431+
Just g -> g
432+
mod = ms_mod ms
433+
data_tycons = filter isDataTyCon tycons
434+
CgGuts{cg_binds = unprep_binds'} <- coreFileToCgGuts session final_iface details core
435+
436+
-- Run corePrep first as we want to test the final version of the program that will
437+
-- get translated to STG/Bytecode
438+
(prepd_binds , _) <- corePrepPgm session mod (ms_location ms) unprep_binds data_tycons
439+
(prepd_binds', _) <- corePrepPgm session mod (ms_location ms) unprep_binds' data_tycons
440+
let binds = noUnfoldings $ (map flattenBinds . (:[])) $ prepd_binds
441+
binds' = noUnfoldings $ (map flattenBinds . (:[])) $ prepd_binds'
442+
443+
-- diffBinds is unreliable, sometimes it goes down the wrong track.
444+
-- This fixes the order of the bindings so that it is less likely to do so.
445+
diffs2 = concat $ flip S.evalState (mkRnEnv2 emptyInScopeSet) $ zipWithM go binds binds'
446+
-- diffs1 = concat $ flip S.evalState (mkRnEnv2 emptyInScopeSet) $ zipWithM go (map (:[]) $ concat binds) (map (:[]) $ concat binds')
447+
-- diffs3 = flip S.evalState (mkRnEnv2 emptyInScopeSet) $ go (concat binds) (concat binds')
448+
449+
diffs = diffs2
450+
go x y = S.state $ \s -> diffBinds True s x y
451+
452+
-- The roundtrip doesn't preserver OtherUnfolding or occInfo, but neither are of these
453+
-- are used for generate core or bytecode, so we can safely ignore them
454+
-- SYB is slow but fine given that this is only used for testing
455+
noUnfoldings = everywhere $ mkT $ \v -> if isId v
456+
then
457+
let v' = if isOtherUnfolding (realIdUnfolding v) then (setIdUnfolding v noUnfolding) else v
458+
in setIdOccInfo v' noOccInfo
459+
else v
460+
isOtherUnfolding (OtherCon _) = True
461+
isOtherUnfolding _ = False
462+
463+
464+
when (not $ null diffs) $
465+
panicDoc "verify core failed!" (vcat $ punctuate (text "\n\n") (diffs )) -- ++ [ppr binds , ppr binds']))
466+
418467
pure (diags, Just $! mkHiFileResult ms mod_info (tmrRuntimeModules tcm))
419468

420469
where

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,8 @@ getModIfaceRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $
895895
linkableType <- getLinkableType f
896896
hsc <- hscEnv <$> use_ GhcSessionDeps f
897897
let compile = fmap ([],) $ use GenerateCore f
898-
(diags, !hiFile) <- compileToObjCodeIfNeeded hsc linkableType compile tmr
898+
se <- getShakeExtras
899+
(diags, !hiFile) <- compileToObjCodeIfNeeded se hsc linkableType compile tmr
899900
let fp = hiFileFingerPrint <$> hiFile
900901
hiDiags <- case hiFile of
901902
Just hiFile
@@ -945,8 +946,9 @@ regenerateHiFile sess f ms compNeeded = do
945946
-- compile writes .o file
946947
let compile = compileModule (RunSimplifier True) hsc (pm_mod_summary pm) $ tmrTypechecked tmr
947948

949+
se <- getShakeExtras
948950
-- Bang pattern is important to avoid leaking 'tmr'
949-
(diags'', !res) <- liftIO $ compileToObjCodeIfNeeded hsc compNeeded compile tmr
951+
(diags'', !res) <- liftIO $ compileToObjCodeIfNeeded se hsc compNeeded compile tmr
950952

951953
-- Write hi file
952954
hiDiags <- case res of
@@ -977,16 +979,16 @@ regenerateHiFile sess f ms compNeeded = do
977979
type CompileMod m = m (IdeResult ModGuts)
978980

979981
-- | HscEnv should have deps included already
980-
compileToObjCodeIfNeeded :: MonadIO m => HscEnv -> Maybe LinkableType -> CompileMod m -> TcModuleResult -> m (IdeResult HiFileResult)
981-
compileToObjCodeIfNeeded hsc Nothing _ tmr = liftIO $ do
982+
compileToObjCodeIfNeeded :: MonadIO m => ShakeExtras -> HscEnv -> Maybe LinkableType -> CompileMod m -> TcModuleResult -> m (IdeResult HiFileResult)
983+
compileToObjCodeIfNeeded _ hsc Nothing _ tmr = liftIO $ do
982984
res <- mkHiFileResultNoCompile hsc tmr
983985
pure ([], Just $! res)
984-
compileToObjCodeIfNeeded hsc (Just linkableType) getGuts tmr = do
986+
compileToObjCodeIfNeeded se hsc (Just linkableType) getGuts tmr = do
985987
(diags, mguts) <- getGuts
986988
case mguts of
987989
Nothing -> pure (diags, Nothing)
988990
Just guts -> do
989-
(diags', !res) <- liftIO $ mkHiFileResultCompile hsc tmr guts linkableType
991+
(diags', !res) <- liftIO $ mkHiFileResultCompile se hsc tmr guts linkableType
990992
pure (diags++diags', res)
991993

992994
getClientSettingsRule :: Recorder (WithPriority Log) -> Rules ()

ghcide/src/Development/IDE/GHC/Compat.hs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ module Development.IDE.GHC.Compat(
7979
tidyExpr,
8080
emptyTidyEnv,
8181
corePrepExpr,
82+
corePrepPgm,
8283
lintInteractiveExpr,
8384
icInteractiveModule,
8485
HomePackageTable,
@@ -93,6 +94,12 @@ module Development.IDE.GHC.Compat(
9394
module UniqSet,
9495
module UniqDFM,
9596
getDependentMods,
97+
diffBinds,
98+
flattenBinds,
99+
mkRnEnv2,
100+
emptyInScopeSet,
101+
Unfolding(..),
102+
noUnfolding,
96103
#if MIN_VERSION_ghc(9,2,0)
97104
loadExpr,
98105
byteCodeGen,
@@ -122,11 +129,12 @@ import GHC hiding (HasSrcSpan,
122129
lookupName, exprType)
123130
#if MIN_VERSION_ghc(9,0,0)
124131
import GHC.Driver.Hooks (hscCompileCoreExprHook)
125-
import GHC.Core (CoreExpr, CoreProgram)
132+
import GHC.Core (CoreExpr, CoreProgram, Unfolding(..))
126133
import qualified GHC.Core.Opt.Pipeline as GHC
127134
import GHC.Core.Tidy (tidyExpr)
128135
import GHC.Types.Var.Env (emptyTidyEnv)
129136
import qualified GHC.CoreToStg.Prep as GHC
137+
import GHC.CoreToStg.Prep (corePrepPgm)
130138
import GHC.Core.Lint (lintInteractiveExpr)
131139
#if MIN_VERSION_ghc(9,2,0)
132140
import GHC.Unit.Home.ModInfo (lookupHpt, HomePackageTable)
@@ -146,11 +154,11 @@ import GHC.Types.Unique.Set as UniqSet
146154
import GHC.Types.Unique.DFM as UniqDFM
147155
#else
148156
import Hooks (hscCompileCoreExprHook)
149-
import CoreSyn (CoreExpr)
157+
import CoreSyn (CoreExpr, flattenBinds, Unfolding(..), noUnfolding)
150158
import qualified SimplCore as GHC
151159
import CoreTidy (tidyExpr)
152-
import VarEnv (emptyTidyEnv)
153-
import CorePrep (corePrepExpr)
160+
import VarEnv (emptyTidyEnv, mkRnEnv2, emptyInScopeSet)
161+
import CorePrep (corePrepExpr, corePrepPgm)
154162
import CoreLint (lintInteractiveExpr)
155163
import ByteCodeGen (coreExprToBCOs)
156164
import HscTypes (icInteractiveModule, HomePackageTable, lookupHpt, Dependencies(dep_mods))
@@ -234,6 +242,8 @@ import GHC.ByteCode.Types
234242
import GHC.Linker.Loader (loadDecls)
235243
import GHC.Data.Maybe
236244
import GHC.CoreToStg
245+
import GHC.Core.Utils
246+
import GHC.Types.Var.Env
237247
#endif
238248

239249
type ModIfaceAnnotation = Annotation

ghcide/src/Development/IDE/GHC/Compat/Core.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ module Development.IDE.GHC.Compat.Core (
305305
-- * Panic
306306
PlainGhcException,
307307
panic,
308+
panicDoc,
308309
-- * Other
309310
GHC.CoreModule(..),
310311
GHC.SafeHaskellMode(..),

ghcide/src/Development/IDE/GHC/Compat/Outputable.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module Development.IDE.GHC.Compat.Outputable (
66
showSDoc,
77
showSDocUnsafe,
88
showSDocForUser,
9-
ppr, pprPanic, text, vcat, (<+>), ($$), empty, hang, nest,
9+
ppr, pprPanic, text, vcat, (<+>), ($$), empty, hang, nest, punctuate,
1010
printSDocQualifiedUnsafe,
1111
printNameWithoutUniques,
1212
printSDocAllTheWay,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ data IdeOptions = IdeOptions
8383
, optProgressStyle :: ProgressReportingStyle
8484
, optRunSubset :: Bool
8585
-- ^ Experimental feature to re-run only the subset of the Shake graph that has changed
86+
, optVerifyCoreFile :: Bool
87+
-- ^ Verify core files after serialization
8688
}
8789

8890
data OptHaddockParse = HaddockParse | NoHaddockParse
@@ -135,6 +137,7 @@ defaultIdeOptions session = IdeOptions
135137
,optSkipProgress = defaultSkipProgress
136138
,optProgressStyle = Explicit
137139
,optRunSubset = True
140+
,optVerifyCoreFile = False
138141
,optMaxDirtyAge = 100
139142
}
140143

ghcide/test/data/THUnboxed/THA.hs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
{-# LANGUAGE TemplateHaskell, UnboxedTuples #-}
1+
{-# LANGUAGE TemplateHaskell, UnboxedTuples, BangPatterns #-}
22
module THA where
33
import Language.Haskell.TH
44

5-
f :: Int -> (# Int, Int #)
6-
f x = (# x , x+1 #)
5+
data Foo = Foo !Int !Char !String
6+
deriving Show
7+
8+
newtype Bar = Bar Int
9+
deriving Show
10+
11+
12+
f :: Int -> (# Int, Int, Foo, Bar#)
13+
f x = (# x , x+1 , Foo x 'a' "test" #)
714

815
th_a :: DecsQ
9-
th_a = case f 1 of (# a , b #) -> [d| a = () |]
16+
th_a = case f 1 of (# a , b, Foo a b c, Bar !d #) -> [d| a = () |]

ghcide/test/exe/Main.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6323,7 +6323,7 @@ runInDir' dir startExeIn startSessionIn extraOptions s = do
63236323

63246324
shakeProfiling <- getEnv "SHAKE_PROFILING"
63256325
let cmd = unwords $
6326-
[ghcideExe, "--lsp", "--test", "--verbose", "-j2", "--cwd", startDir
6326+
[ghcideExe, "--lsp", "--test", "--verify-core-file", "--verbose", "-j2", "--cwd", startDir
63276327
] ++ ["--shake-profiling=" <> dir | Just dir <- [shakeProfiling]
63286328
] ++ extraOptions
63296329
-- HIE calls getXgdDirectory which assumes that HOME is set.

0 commit comments

Comments
 (0)