[Git][ghc/ghc][wip/unload-strategy] 9 commits: linker: refactor loader so that object suffixes get passed through in a sane way
by Zubin (@wz1000) 25 Aug '26
by Zubin (@wz1000) 25 Aug '26
25 Aug '26
Zubin pushed to branch wip/unload-strategy at Glasgow Haskell Compiler / GHC
Commits:
8eea219f by Zubin Duggal at 2026-08-25T14:52:54+05:30
linker: refactor loader so that object suffixes get passed through in a sane way
- - - - -
e1c8030b by Zubin Duggal at 2026-08-25T14:52:54+05:30
Identify Linkable with fingerprints of their contents rather than modtimes
- - - - -
afdb51c2 by Zubin Duggal at 2026-08-25T14:52:54+05:30
Linker cleanup
delete a bunch of unused functions
- - - - -
c7f64e7d by Zubin Duggal at 2026-08-25T14:52:54+05:30
Record constituent hashes for bytecode libraries
A bytecode library now records the combined hash of its constituents,
so relinking can be skipped when they haven't changed.
- - - - -
8c6f94c5 by Zubin Duggal at 2026-08-25T14:52:54+05:30
linker: Automatically reload stale linkables
Linked BCOs contain direct references to closures, so replacing only a
changed module can leave stale references in other modules. Take
-- A.hs
f = 1
-- B.hs
import A
g = f + 1
After loading A and B, change A so that f = 2 and recompile it. B does
not need to be recompiled, but its loaded BCO still refers to the old
closure for f. The next splice should see f = 2 and g = 3. To get the
correct result, we have to *reload* B, even though we didn't need to
recompile it.
The GHC driver calls unload before each upsweep, which unloads
everything, so this does not arise during an ordinary GHCi reload. API
clients such as HLS may instead want to keep the loader state while
recompiling individual modules. Forcing them to unload everything or
carefully keep track of what to unload is not ideal.
Now we automatically reload stale linkables, so that if we are asked
to link a module whose code has changed, the old code, and everything
that refers to it, is dropped and reloaded. See
Note [Automatically reloading stale linkables].
Dropped objects are now purged, not unloaded, so values built by the
old code and computations still using it keep working. The object
stays in memory, but new code will not be able to link against it.
This is a change in behaviour, objects were previously only ever
unloaded, all at once, in unload. See
Note [Unloading vs purging objects]
Fixes #27606
- - - - -
c500df06 by Zubin Duggal at 2026-08-25T14:52:54+05:30
linker: Add unloadModules
Drop the given modules and every loaded module that transitively
refers to them. Reloading stale code does not need this, it happens
automatically. This is for API clients dropping modules they no
longer need. Interactive modules are ignored.
See Note [Automatically reloading stale linkables] in GHC.Linker.Loader
See Note [Unloading vs purging objects] in GHC.Runtime.Interpreter
- - - - -
d2e4f73e by Zubin Duggal at 2026-08-25T14:52:54+05:30
iserv: Add a PurgeObj message
purgeObj no longer falls back to unloading on external interpreters.
See Note [Unloading vs purging objects] in GHC.Runtime.Interpreter.
- - - - -
8dc9fe7d by Zubin Duggal at 2026-08-25T14:52:54+05:30
linker: Remove static pointer table entries when dropping modules
Entries were added by bytecode modules, but never removed. This meant
that every reload of a module with static forms leaked its old entries
and kept the old code alive, including in GHCi with :reload.
loaded_spt_keys now records the keys each loaded bytecode module
inserted. dropModules and unload remove them. We also add a new
RemoveSptEntry message to support this operation with the external
interpreter.
Object code manages its own entries with initialisers and finalisers,
so it was not changed.
Fixes #27740
- - - - -
eabe067a by Zubin Duggal at 2026-08-25T14:52:54+05:30
Introduce -funload-strategy
When unloading object code, we have a choice to make. Do we call
purgeObj or unloadObj?
purgeObj clears the symbol tables associated with an object, so that
future objects can't link against it, but the object stays in memory
unloadObj does the above, but it also marks the object as needing to be
unloaded, so at some point in a future GC, the RTS may notice that it is
no longer used, and if so, unload it entirely, freeing up the memory.
Ideally we would always unload, but a number of bugs with the
implementation of unloadObj mean that it is fragile on many platforms.
This is documented in Note [Unloading vs purging objects]. So on these
platforms we purge instead.
We introduce the -funload-strategy flag, so that users can opt into
purging/unloading on platforms where we make the other choice by
default.
The distinction is moot when we are using the dynamic RTS, we don't do
either then.
Fixes #27741
- - - - -
42 changed files:
- + changelog.d/unload-strategy
- compiler/GHC.hs
- compiler/GHC/ByteCode/Serialize.hs
- compiler/GHC/Driver/Config/Interpreter.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Main/Compile.hs
- compiler/GHC/Driver/Main/Passes.hs
- compiler/GHC/Driver/Pipeline.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Linker/ByteCode.hs
- compiler/GHC/Linker/Deps.hs
- compiler/GHC/Linker/Loader.hs
- compiler/GHC/Linker/Types.hs
- compiler/GHC/Runtime/Interpreter.hs
- compiler/GHC/Runtime/Interpreter/Init.hs
- compiler/GHC/Runtime/Interpreter/Types.hs
- compiler/GHC/Unit/Finder.hs
- docs/users_guide/ghci.rst
- libraries/ghci/GHCi/Message.hs
- libraries/ghci/GHCi/ObjLink.hs
- libraries/ghci/GHCi/Run.hs
- libraries/ghci/GHCi/StaticPtrTable.hs
- + testsuite/tests/driver/recomp023/M.hs
- + testsuite/tests/driver/recomp023/Makefile
- + testsuite/tests/driver/recomp023/all.T
- + testsuite/tests/driver/recomp023/recomp023.stdout
- + testsuite/tests/ghc-api/T27606/B.hs
- + testsuite/tests/ghc-api/T27606/T27606a.hs
- + testsuite/tests/ghc-api/T27606/T27606a.stdout
- + testsuite/tests/ghc-api/T27606/T27606b.hs
- + testsuite/tests/ghc-api/T27606/T27606b.stdout
- + testsuite/tests/ghc-api/T27606/T27606c.hs
- + testsuite/tests/ghc-api/T27606/T27606c.stdout
- + testsuite/tests/ghc-api/T27606/T27606c_purge.stdout
- + testsuite/tests/ghc-api/T27606/T27606d.hs
- + testsuite/tests/ghc-api/T27606/T27606d.stdout
- + testsuite/tests/ghc-api/T27606/T27606e.hs
- + testsuite/tests/ghc-api/T27606/T27606e.stdout
- + testsuite/tests/ghc-api/T27606/all.T
- + testsuite/tests/ghc-api/T27740.hs
- + testsuite/tests/ghc-api/T27740.stdout
- testsuite/tests/ghc-api/all.T
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/79f3073b4f9dd3e58959a6643e506c…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/79f3073b4f9dd3e58959a6643e506c…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/linkable-hashes] 4 commits: linker: refactor loader so that object suffixes get passed through in a sane way
by Zubin (@wz1000) 25 Aug '26
by Zubin (@wz1000) 25 Aug '26
25 Aug '26
Zubin pushed to branch wip/linkable-hashes at Glasgow Haskell Compiler / GHC
Commits:
8eea219f by Zubin Duggal at 2026-08-25T14:52:54+05:30
linker: refactor loader so that object suffixes get passed through in a sane way
- - - - -
e1c8030b by Zubin Duggal at 2026-08-25T14:52:54+05:30
Identify Linkable with fingerprints of their contents rather than modtimes
- - - - -
afdb51c2 by Zubin Duggal at 2026-08-25T14:52:54+05:30
Linker cleanup
delete a bunch of unused functions
- - - - -
c7f64e7d by Zubin Duggal at 2026-08-25T14:52:54+05:30
Record constituent hashes for bytecode libraries
A bytecode library now records the combined hash of its constituents,
so relinking can be skipped when they haven't changed.
- - - - -
15 changed files:
- compiler/GHC/ByteCode/Serialize.hs
- compiler/GHC/Driver/Main/Compile.hs
- compiler/GHC/Driver/Main/Passes.hs
- compiler/GHC/Driver/Pipeline.hs
- compiler/GHC/Linker/ByteCode.hs
- compiler/GHC/Linker/Deps.hs
- compiler/GHC/Linker/Loader.hs
- compiler/GHC/Linker/Types.hs
- compiler/GHC/Runtime/Interpreter/Init.hs
- compiler/GHC/Runtime/Interpreter/Types.hs
- compiler/GHC/Unit/Finder.hs
- + testsuite/tests/driver/recomp023/M.hs
- + testsuite/tests/driver/recomp023/Makefile
- + testsuite/tests/driver/recomp023/all.T
- + testsuite/tests/driver/recomp023/recomp023.stdout
Changes:
=====================================
compiler/GHC/ByteCode/Serialize.hs
=====================================
@@ -6,7 +6,7 @@
{- | This module implements the serialization of bytecode objects to and from disk.
-}
module GHC.ByteCode.Serialize
- ( writeBinByteCode, readBinByteCode, readOnDiskModuleByteCode
+ ( writeBinByteCode, readBinByteCode, readBinByteCodeHash, readOnDiskModuleByteCode
, ModuleByteCode(..)
, BytecodeLibX(..)
, BytecodeLib
@@ -15,6 +15,7 @@ module GHC.ByteCode.Serialize
, InterpreterLibraryContents(..)
, writeBytecodeLib
, readBytecodeLib
+ , readBytecodeLibInputsHash
, mkModuleByteCode
, fingerprintModuleByteCodeContents
, decodeOnDiskModuleByteCode
@@ -84,6 +85,7 @@ The ticket where bytecode objects were dicussed is #26298
See Note [-fwrite-byte-code is not the default]
See Note [Recompilation avoidance with bytecode objects]
See Note [Persistent bytecode file headers]
+See Note [Hash of bytecode libs]
Note [Persistent bytecode file headers]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -96,15 +98,28 @@ To make these failures explicit, we write a file-kind-specific magic word and
the current `hiVersion` ahead of the binary payload. Readers validate this
header before setting up the normal `Name`/`FastString` deserialisation
machinery. This follows the same approach as normal interface files.
+
+Note [Hash of bytecode libs]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A bytecode library records the combined hash of its constituents, so
+that relinking can be skipped when they haven't changed.
+
+We can't hash the library file itself: we would have to build the
+library to know which hash to expect. So we compare the inputs.
+
+The hash is written right after the file header. The constituent hashes
+are sorted before they are combined, so the order of the modules
+doesn't matter.
-}
-writeBytecodeLib :: BytecodeLib -> FilePath -> IO ()
-writeBytecodeLib lib path = do
+writeBytecodeLib :: Fingerprint -> BytecodeLib -> FilePath -> IO ()
+writeBytecodeLib inputs_hash lib path = do
odbco <- encodeBytecodeLib lib
createDirectoryIfMissing True (takeDirectory path)
bh' <- openBinMem initBinMemSize
bh <- addBinNameWriter bh'
writePersistentBytecodeHeader BytecodeLibraryFile bh
+ put_ bh inputs_hash
putWithUserData QuietBinIFace NormalCompression bh odbco
writeBinMem bh path
@@ -112,10 +127,18 @@ readBytecodeLib :: HscEnv -> FilePath -> IO OnDiskBytecodeLib
readBytecodeLib hsc_env path = do
bh' <- readBinMem path
readPersistentBytecodeHeader BytecodeLibraryFile path bh'
+ _inputs_hash <- get bh' :: IO Fingerprint
bh <- addBinNameReader (hsc_NC hsc_env) bh'
res <- getWithUserData (hsc_NC hsc_env) bh
pure res
+-- See Note [Hash of bytecode libs]
+readBytecodeLibInputsHash :: FilePath -> IO Fingerprint
+readBytecodeLibInputsHash path = do
+ bh <- readBinMem path
+ readPersistentBytecodeHeader BytecodeLibraryFile path bh
+ get bh
+
-- | Convert an 'OnDiskModuleByteCode' to an 'ModuleByteCode'.
-- 'OnDiskModuleByteCode' is the representation which we read from a file,
-- the 'ModuleByteCode' is the representation which is manipulated by program logic.
@@ -200,6 +223,10 @@ readBinByteCode hsc_env f = do
odbco <- readOnDiskModuleByteCode hsc_env f
decodeOnDiskModuleByteCode hsc_env odbco
+-- | Read only the hash from the start of a bytecode file
+readBinByteCodeHash :: HscEnv -> FilePath -> IO Fingerprint
+readBinByteCodeHash hsc_env f = odgbc_hash <$> readOnDiskModuleByteCode hsc_env f
+
readOnDiskModuleByteCode :: HscEnv -> FilePath -> IO OnDiskModuleByteCode
readOnDiskModuleByteCode hsc_env f = do
bh' <- readBinMem f
=====================================
compiler/GHC/Driver/Main/Compile.hs
=====================================
@@ -132,7 +132,6 @@ import GHC.Data.OsPath (unsafeEncodeUtf)
import qualified GHC.Data.Stream as Stream
-import Data.Traversable (for)
import Control.Monad
import Data.IORef
import System.Directory
@@ -141,7 +140,6 @@ import Data.Map (Map)
import qualified Data.Set as S
import GHC.Unit.Module.WholeCoreBindings
import GHC.Types.TypeEnv
-import Data.Time
import System.IO.Unsafe ( unsafeInterleaveIO )
import GHC.Iface.Env ( trace_if )
@@ -218,12 +216,7 @@ loadIfaceByteCode hsc_env iface location type_env =
where
compile decls = do
bco <- compileWholeCoreBindings hsc_env type_env decls
- linkable $ pure $ DotGBC bco
-
- linkable parts = do
- if_time <- modificationTimeIfExists (ml_hi_file_ospath location)
- time <- maybe getCurrentTime pure if_time
- return $! Linkable time (mi_module iface) parts
+ return $! Linkable (gbc_hash bco) (mi_module iface) (pure (DotGBC bco))
loadIfaceByteCodeLazy ::
HscEnv ->
@@ -240,12 +233,7 @@ loadIfaceByteCodeLazy hsc_env iface location type_env =
compile decls = do
bco <- unsafeInterleaveIO $ do
compileWholeCoreBindings hsc_env type_env decls
- linkable bco
-
- linkable parts = do
- if_time <- modificationTimeIfExists (ml_hi_file_ospath location)
- time <- maybe getCurrentTime pure if_time
- return $!Linkable time (mi_module iface) parts
+ return $! Linkable (gbc_hash bco) (mi_module iface) bco
-- | If the 'Linkable' contains Core bindings loaded from an interface, replace
-- them with a lazy IO thunk that compiles them to bytecode and foreign objects,
@@ -283,12 +271,13 @@ initWholeCoreBindings hsc_env iface details (RecompLinkables bc o) = do
go :: RecompBytecodeLinkable -> IO (Maybe (LinkableWith ModuleByteCode))
go (NormalLinkable l) = pure l
- go (WholeCoreBindingsLinkable wcbl) =
- fmap Just $ for wcbl $ \wcb -> do
- add_iface_to_hpt iface details hsc_env
- bco <- unsafeInterleaveIO $ do
- compileWholeCoreBindings hsc_env type_env wcb
- pure bco
+ go (WholeCoreBindingsLinkable wcbl) = do
+ add_iface_to_hpt iface details hsc_env
+ bco <- unsafeInterleaveIO $ do
+ compileWholeCoreBindings hsc_env type_env (linkableParts wcbl)
+ -- We need to fill in the hash over here, replacing the panic
+ -- because WholeCoreBindingsLinkable doesn't have a hash.
+ pure $ Just $ Linkable (gbc_hash bco) (linkableModule wcbl) bco
-- | Hydrate interface Core bindings and compile them to bytecode.
--
@@ -842,11 +831,7 @@ make user's opt into writing the files.
generateAndWriteByteCodeLinkable :: HscEnv -> CgInteractiveGuts -> ModLocation -> IO (LinkableWith ModuleByteCode)
generateAndWriteByteCodeLinkable hsc_env cgguts mod_location = do
bco_object <- generateAndWriteByteCode hsc_env cgguts mod_location
- -- Either, get the same time as the .gbc file if it exists, or just the current time.
- -- It's important the time of the linkable matches the time of the .gbc file for recompilation
- -- checking.
- bco_time <- maybe getCurrentTime pure =<< modificationTimeIfExists (ml_bytecode_file_ospath mod_location)
- return $ mkOnlyModuleByteCodeLinkable bco_time bco_object
+ return $ mkOnlyModuleByteCodeLinkable bco_object
mkModuleByteCode :: HscEnv -> Module -> ModLocation -> CgInteractiveGuts -> IO ModuleByteCode
mkModuleByteCode hsc_env mod mod_location cgguts = do
@@ -861,9 +846,8 @@ generateFreshByteCodeLinkable :: HscEnv
-> ModLocation
-> IO Linkable
generateFreshByteCodeLinkable hsc_env mod_name cgguts mod_location = do
- bco_time <- getCurrentTime
bco_object <- mkModuleByteCode hsc_env (mkHomeModule (hsc_home_unit hsc_env) mod_name) mod_location cgguts
- return $ mkModuleByteCodeLinkable bco_time bco_object
+ return $ mkModuleByteCodeLinkable bco_object
------------------------------
hscCompileCmmFile :: HscEnv -> FilePath -> FilePath -> FilePath -> IO (Maybe FilePath)
=====================================
compiler/GHC/Driver/Main/Passes.hs
=====================================
@@ -153,7 +153,7 @@ import GHC.Types.Unique.Set
import GHC.Types.Var.Env ( mkEmptyTidyEnv )
import GHC.Types.Var.Set
-import GHC.Utils.Fingerprint ( Fingerprint )
+import GHC.Utils.Fingerprint ( Fingerprint, getFileHash )
import GHC.Utils.Panic
import GHC.Utils.Error
import GHC.Utils.Outputable
@@ -168,8 +168,8 @@ import GHC.Data.Maybe
import qualified GHC.Data.Strict as Strict
import qualified Data.Array as A
-import Data.List ( nub, isPrefixOf, partition )
import qualified Data.List.NonEmpty as NE
+import Data.List ( nub, isPrefixOf, partition )
import Control.Monad
import Data.IORef
import System.FilePath as FilePath
@@ -180,7 +180,6 @@ import Data.Set (Set)
import Control.DeepSeq (force)
import Control.Exception as E (mask_, finally)
import Data.List.NonEmpty (NonEmpty ((:|)))
-import Data.Time
import System.IO.Unsafe ( unsafeInterleaveIO )
import GHC.Iface.Env ( trace_if )
@@ -707,15 +706,24 @@ checkObjects dflags mb_old_linkable summary = do
-- Not in dynamic-too mode
else k
+ -- We check by date first, even though we have the hash
+ -- If a compilation is interupted after writing the .hi
+ -- but before writing the .o, then we catch this through
+ -- modtimes.
+ -- If the object file is newer than the .hi file, and the
+ -- .hi file is up to date, we also assume the object file
+ -- is up to date.
checkDynamicObj $
case (,) <$> mb_obj_date <*> mb_if_date of
Just (obj_date, if_date)
- | obj_date >= if_date ->
+ | obj_date >= if_date -> do
+ disk_hash <- getFileHash obj_fn
case mb_old_linkable of
Just old_linkable
- | linkableIsNativeCodeOnly old_linkable, linkableTime old_linkable == obj_date
+ | linkableIsNativeCodeOnly old_linkable
+ , linkableHash old_linkable == disk_hash
-> return $ UpToDateItem old_linkable
- _ -> UpToDateItem <$> findObjectLinkable this_mod obj_fn obj_date
+ _ -> return $ UpToDateItem (findObjectLinkable this_mod obj_fn disk_hash)
_ -> return $ outOfDateItemBecause MissingObjectFile Nothing
-- | Check to see if we can reuse the old linkable, by this point we will
@@ -724,15 +732,22 @@ checkObjects dflags mb_old_linkable summary = do
checkByteCodeInMemory :: HscEnv -> ModSummary -> Maybe (LinkableWith ModuleByteCode) -> IO (MaybeValidated (LinkableWith ModuleByteCode))
checkByteCodeInMemory hsc_env mod_sum mb_old_linkable =
case mb_old_linkable of
- Just old_linkable
+ Just old_linkable -> do
-- If `-fwrite-byte-code` is enabled, then check that the .gbc file is
-- up-to-date with the linkable we have in our hand.
-- If ms_bytecode_date is Nothing, then the .gbc file does not exist yet.
- -- Otherwise, check that the date matches the linkable date exactly.
- | if gopt Opt_WriteByteCode (hsc_dflags hsc_env)
- then maybe False (linkableTime old_linkable ==) (ms_bytecode_date mod_sum)
- else True
- -> return $ (UpToDateItem old_linkable)
+ -- Otherwise, check that the hash matches the disk.
+ ok <- if gopt Opt_WriteByteCode (hsc_dflags hsc_env)
+ then case ms_bytecode_date mod_sum of
+ Nothing -> pure False
+ Just _ -> do
+ disk_hash <- ByteCode.readBinByteCodeHash hsc_env
+ (ml_bytecode_file (ms_location mod_sum))
+ pure (disk_hash == linkableHash old_linkable)
+ else pure True
+ if ok
+ then return (UpToDateItem old_linkable)
+ else return $ outOfDateItemBecause MissingBytecode Nothing
_ -> return $ outOfDateItemBecause MissingBytecode Nothing
-- | Load bytecode from a ".gbc" object file if it exists and is up-to-date
@@ -749,7 +764,7 @@ checkByteCodeFromObject hsc_env mod_sum = do
-- that the one we have on disk would be suitable as well.
linkable <- unsafeInterleaveIO $ do
bco <- ByteCode.readBinByteCode hsc_env obj_fn
- return $ mkOnlyModuleByteCodeLinkable obj_date bco
+ return $ mkOnlyModuleByteCodeLinkable bco
return $ UpToDateItem linkable
_ -> return $ outOfDateItemBecause MissingBytecode Nothing
@@ -759,9 +774,11 @@ checkByteCodeFromIfaceCoreBindings :: HscEnv -> ModIface -> ModSummary -> IO (Ma
checkByteCodeFromIfaceCoreBindings _hsc_env iface mod_sum = do
let
this_mod = ms_mod mod_sum
- if_date = fromJust $ ms_iface_date mod_sum
+ -- This hash isn't used, initWholeCoreBindings will replace it
+ -- with a real linkable with bytecode that has a hash.
+ wcb_hash = panic "linkableHash: WholeCoreBindingsLinkable"
case iface_core_bindings iface (ms_location mod_sum) of
- Just fi -> return $ UpToDateItem (Linkable if_date this_mod fi)
+ Just fi -> return $ UpToDateItem (Linkable wcb_hash this_mod fi)
_ -> return $ outOfDateItemBecause MissingBytecode Nothing
@@ -1621,10 +1638,9 @@ hscCompileCoreExpr' hsc_env srcspan ds_expr = do
Strict.Nothing -- no hpc info
{- load it -}
- bco_time <- getCurrentTime
mbc <- ByteCode.mkModuleByteCode this_mod bcos []
(mods_needed, units_needed) <- loadDecls interp hsc_env srcspan $
- Linkable bco_time this_mod $ NE.singleton (DotGBC mbc)
+ mkModuleByteCodeLinkable mbc
-- Get the foreign reference to the name we should have just loaded.
mhvs <- lookupFromLoadedEnv interp (idName binding_id)
{- Get the HValue for the root -}
@@ -1683,7 +1699,7 @@ jsCodeGen hsc_env srcspan i this_mod stg_binds_with_deps binding_id = do
-- state independently to load new objects here.
let objs = mapMaybe linkableFilterNative (ldNeededLinkables deps)
- (objs_loaded', _new_objs) = rmDupLinkables (objs_loaded pls) objs
+ (objs_loaded', _new_objs) = rmDupLinkables Nothing (objs_loaded pls) objs
-- Compute LoadedPkgInfo metadata for recompilation avoidance.
-- We don't call loadPackages' because the JS interpreter doesn't load
=====================================
compiler/GHC/Driver/Pipeline.hs
=====================================
@@ -126,9 +126,11 @@ import qualified Control.Monad.Catch as MC (handle, mask, onException)
import Data.Maybe
import qualified Data.Set as Set
import qualified Data.List.NonEmpty as NE
+import Data.List (sort)
import Data.List.NonEmpty (NonEmpty(..))
-import Data.Time ( getCurrentTime )
+import GHC.Utils.Fingerprint ( fingerprintFingerprints, getFileHash )
+import GHC.ByteCode.Serialize ( readBytecodeLibInputsHash )
import GHC.Tc.Utils.Monad (shutdownTcMPluginsIO, FrontendResult (..), tcg_plugins)
@@ -547,11 +549,13 @@ checkBytecodeLibraryLinkingNeeded _logger dflags unit_env linkables _pkg_deps =
e_bytecode_lib_time <- modificationTimeIfExists exe_file_os
case e_bytecode_lib_time of
Nothing -> return $ NeedsRecompile MustCompile
- Just t -> do
- let bytecode_times = map linkableTime linkables
- if any (t <) bytecode_times
- then return $ needsRecompileBecause ObjectsChanged
- else return UpToDate
+ Just _ -> do
+ -- See Note [Hash of bytecode libs] in GHC.ByteCode.Serialize
+ e_recorded <- tryIO (readBytecodeLibInputsHash exe_file)
+ let current = fingerprintFingerprints (sort (map linkableHash linkables))
+ case e_recorded of
+ Right recorded | recorded == current -> return UpToDate
+ _ -> return $ needsRecompileBecause ObjectsChanged
checkNativeLibraryLinkingNeeded :: Bool -> Logger -> DynFlags -> UnitEnv -> [Linkable] -> [UnitId] -> IO RecompileRequired
checkNativeLibraryLinkingNeeded staticLink _ dflags unit_env linkables pkg_deps = do
@@ -576,8 +580,9 @@ checkNativeLibraryLinkingNeeded staticLink _ dflags unit_env linkables pkg_deps
Just t -> do
-- first check object files and extra_ld_inputs
let extra_ld_inputs = [ f | FileOption _ f <- ldInputs dflags ]
- (errs,extra_times) <- partitionWithM (tryIO . getModificationUTCTime) extra_ld_inputs
- let obj_times = map linkableTime linkables ++ extra_times
+ obj_files = concatMap linkableFiles linkables
+ (errs,obj_times) <- partitionWithM (tryIO . getModificationUTCTime)
+ (obj_files ++ extra_ld_inputs)
if not (null errs) || any (t <) obj_times
then return $ needsRecompileBecause ObjectsChanged
else do
@@ -936,9 +941,9 @@ hscGenBackendPipeline pipe_env hsc_env mod_sum result = do
-- No object file produced, bytecode or NoBackend
Nothing -> return mlinkable
Just o_fp -> do
- part_time <- liftIO getCurrentTime
final_object <- use (T_MergeForeign pipe_env hsc_env o_fp fos)
- let !linkable = Linkable part_time (ms_mod mod_sum) (NE.singleton (DotO final_object ModuleObject))
+ !obj_hash <- liftIO $ getFileHash final_object
+ let !linkable = Linkable obj_hash (ms_mod mod_sum) (NE.singleton (DotO final_object ModuleObject))
-- Add the object linkable to the potential bytecode linkable which was generated in HscBackend.
return (mlinkable { homeMod_object = Just linkable })
=====================================
compiler/GHC/Linker/ByteCode.hs
=====================================
@@ -8,7 +8,8 @@ import GHC.Utils.Error
import GHC.Driver.Env
import GHC.Utils.Outputable
import GHC.Linker.Loader
-import Data.List (partition)
+import Data.List (partition, sort)
+import GHC.Utils.Fingerprint (fingerprintFingerprints)
import GHC.Driver.Phases (isBytecodeFilename)
import GHC.Runtime.Interpreter (interpreterDynamic)
import Data.Maybe
@@ -40,8 +41,10 @@ linkBytecodeLib hsc_env gbcs = do
bytecodeLibFiles = all_cbcs,
bytecodeLibForeign = interpreter_foreign_lib
}
+ let inputs_hash = fingerprintFingerprints
+ (sort [ gbc_hash m | m <- on_disk_bcos ++ gbcs ])
let output_fn = fromMaybe "a.out" (outputFile dflags)
- writeBytecodeLib bytecodeLib' output_fn
+ writeBytecodeLib inputs_hash bytecodeLib' output_fn
return ()
=====================================
compiler/GHC/Linker/Deps.hs
=====================================
@@ -41,11 +41,10 @@ import qualified GHC.Unit.Home.Graph as HUG
import GHC.Data.Maybe
import Control.Applicative
+import Control.Monad (forM_, unless)
-import Data.List (isSuffixOf)
+import System.Directory (doesFileExist)
-import System.FilePath
-import System.Directory
data LinkDepsOpts = LinkDepsOpts
{ ldObjSuffix :: !String -- ^ Suffix of .o files
@@ -87,18 +86,18 @@ getLinkDeps opts interp pls span mods = do
-- the "normal" way, i.e. no non-std ways like profiling or ticky-ticky.
-- So here we check the build tag: if we're building a non-standard way
-- then we need to find & link object files built the "normal" way.
- maybe_normal_osuf <- checkNonStdWay opts interp span
+ checkNonStdWay opts interp span
- get_link_deps opts pls maybe_normal_osuf span mods
+ get_link_deps opts interp pls span mods
get_link_deps
:: LinkDepsOpts
+ -> Interp
-> LoaderState
- -> Maybe FilePath -- replace object suffixes?
-> SrcSpan
-> [Module]
-> IO LinkDeps
-get_link_deps opts pls maybe_normal_osuf span mods = do
+get_link_deps opts interp pls span mods = do
-- Three step process:
@@ -122,9 +121,9 @@ get_link_deps opts pls maybe_normal_osuf span mods = do
-- 3. For each dependent module, find its linkable
-- This will either be in the HPT or (in the case of one-shot
-- compilation) we may need to use maybe_getFileLinkable
- lnks_needed <- mapM (get_linkable (ldObjSuffix opts)) mods_needed
+ lnks_needed <- mapM get_linkable mods_needed
let
- lnks_needed_usages = mkLinkablesUsage lnks_needed
+ lnks_needed_usages = mkLinkablesUsage (interpObjSuffix interp) lnks_needed
new_link_deps lnks = LinkDeps
{ ldNeededLinkables = lnks_needed
, ldAllLinkables = lnks
@@ -156,9 +155,12 @@ get_link_deps opts pls maybe_normal_osuf span mods = do
then homeModInfoByteCode hmi <|> homeModInfoObject hmi
else homeModInfoObject hmi <|> homeModInfoByteCode hmi
- get_linkable osuf mod -- A home-package module
+ get_linkable mod -- A home-package module
= HUG.lookupHugByModule mod (ue_home_unit_graph unit_env) >>= \case
- Just mod_info -> adjust_linkable (expectJust (homeModLinkable mod_info))
+ Just mod_info -> do
+ let lnk = expectJust (homeModLinkable mod_info)
+ validate_objects lnk
+ pure lnk
Nothing -> do
-- It's not in the HPT because we are in one shot mode,
-- so use the Finder to get a ModLocation...
@@ -185,30 +187,23 @@ get_link_deps opts pls maybe_normal_osuf span mods = do
mb_lnk <- findObjectLinkableMaybe mod loc
case mb_lnk of
Nothing -> no_obj mod
- Just lnk -> adjust_linkable lnk
+ Just lnk -> validate_objects lnk >> pure lnk
_ -> no_obj (moduleName mod)
- adjust_linkable lnk
- | Just new_osuf <- maybe_normal_osuf = do
- new_parts <- mapM (adjust_part new_osuf)
- (linkableParts lnk)
- return lnk{ linkableParts=new_parts }
- | otherwise =
- return lnk
-
- adjust_part new_osuf part = case part of
- DotO file ModuleObject -> do
- massert (osuf `isSuffixOf` file)
- let file_base = fromJust (stripExtension osuf file)
- new_file = file_base <.> new_osuf
- ok <- doesFileExist new_file
- if (not ok)
- then dieWith opts span $
- text "cannot find object file "
- <> quotes (text new_file) $$ while_linking_expr
- else return (DotO new_file ModuleObject)
- DotO file ForeignObject -> pure (DotO file ForeignObject)
- DotGBC {} -> pure part
+ -- The loader loads the interpreter's object variant of each
+ -- module object. Check it exists here, where we can say which
+ -- module and expression needed it.
+ validate_objects lnk
+ | Just suffixes <- interpObjSuffix interp =
+ forM_ (linkableParts lnk) $ \part -> case part of
+ DotO f ModuleObject -> do
+ let f' = swapObjSuffix suffixes f
+ ok <- doesFileExist f'
+ unless ok $ dieWith opts span $
+ text "cannot find object file" <+> quotes (text f') $$
+ while_linking_expr
+ _ -> pure ()
+ | otherwise = pure ()
{-
@@ -241,19 +236,11 @@ dieWith opts span msg = throwProgramError opts (mkLocMessage MCFatal span msg)
throwProgramError :: LinkDepsOpts -> SDoc -> IO a
throwProgramError opts doc = throwGhcExceptionIO (ProgramError (renderWithContext (ldPprOpts opts) doc))
-checkNonStdWay :: LinkDepsOpts -> Interp -> SrcSpan -> IO (Maybe FilePath)
+checkNonStdWay :: LinkDepsOpts -> Interp -> SrcSpan -> IO ()
checkNonStdWay _opts interp _srcspan
- -- On some targets (e.g. wasm) the RTS linker only supports loading
- -- dynamic code, in which case we need to ensure the .dyn_o object
- -- is picked (instead of .o which is also present because of
- -- -dynamic-too)
- | ldForceDyn _opts = do
- let target_ways = fullWays $ ldWays _opts
- pure $ if target_ways `hasWay` WayDyn
- then Nothing
- else Just $ waysTag (WayDyn `addWay` target_ways) ++ "_o"
-
- | ExternalInterp {} <- interpInstance interp = return Nothing
+ | ldForceDyn _opts = return ()
+
+ | ExternalInterp {} <- interpInstance interp = return ()
-- with -fexternal-interpreter we load the .o files, whatever way
-- they were built. If they were built for a non-std way, then
-- we will use the appropriate variant of the iserv binary to load them.
@@ -262,26 +249,23 @@ checkNonStdWay _opts interp _srcspan
-- complain that they are redundant.
#if defined(HAVE_INTERNAL_INTERPRETER)
checkNonStdWay opts _interp srcspan
- | hostFullWays == targetFullWays = return Nothing
+ | hostFullWays == targetFullWays = return ()
-- Only if we are compiling with the same ways as GHC is built
-- with, can we dynamically load those object files. (see #3604)
| ldObjSuffix opts == normalObjectSuffix && not (null targetFullWays)
= failNonStd opts srcspan
- | otherwise = return (Just (hostWayTag ++ "o"))
+ | otherwise = return ()
where
targetFullWays = fullWays (ldWays opts)
- hostWayTag = case waysTag hostFullWays of
- "" -> ""
- tag -> tag ++ "_"
normalObjectSuffix :: String
normalObjectSuffix = "o"
data Way' = Normal | Prof | Dyn | ProfDyn
-failNonStd :: LinkDepsOpts -> SrcSpan -> IO (Maybe FilePath)
+failNonStd :: LinkDepsOpts -> SrcSpan -> IO ()
failNonStd opts srcspan = dieWith opts srcspan $
text "Cannot load" <+> pprWay' compWay <+>
text "objects when GHC is built" <+> pprWay' ghciWay $$
=====================================
compiler/GHC/Linker/Loader.hs
=====================================
@@ -44,7 +44,6 @@ where
import GHC.Prelude
import GHC.Settings
-import GHC.Utils.Misc
import GHC.Platform
import GHC.Platform.Ways
@@ -673,14 +672,13 @@ findWholeCoreBindings hsc_env mod = do
findBytecodeLinkableMaybe :: HscEnv -> ModLocation -> IO (Maybe Linkable)
findBytecodeLinkableMaybe hsc_env locn = do
- let bytecode_fn = ml_bytecode_file locn
- bytecode_fn_os = ml_bytecode_file_ospath locn
- maybe_bytecode_time <- modificationTimeIfExists bytecode_fn_os
- case maybe_bytecode_time of
- Nothing -> return Nothing
- Just bytecode_time -> do
+ let bytecode_fn = ml_bytecode_file locn
+ exists <- doesFileExist bytecode_fn
+ if not exists
+ then return Nothing
+ else do
bco <- readBinByteCode hsc_env bytecode_fn
- return $ Just $ mkModuleByteCodeLinkable bytecode_time bco
+ return $ Just $ mkModuleByteCodeLinkable bco
get_reachable_nodes :: HscEnv -> [Module] -> IO ([Module], UniqDSet UnitId)
get_reachable_nodes hsc_env mods
@@ -834,7 +832,7 @@ linkableInSet :: Linkable -> LinkableSet LinkableUsage -> Bool
linkableInSet l objs_loaded =
case lookupModuleEnv objs_loaded (linkableModule l) of
Nothing -> False
- Just m -> linkableTime l == linkableTime m
+ Just m -> linkableHash l == linkableHash m
{- **********************************************************************
@@ -854,9 +852,9 @@ loadObjects
-> [Linkable]
-> IO (LoaderState, SuccessFlag)
loadObjects interp hsc_env pls objs = do
- let (objs_loaded', new_objs) = rmDupLinkables (objs_loaded pls) objs
+ let (objs_loaded', new_objs) = rmDupLinkables (interpObjSuffix interp) (objs_loaded pls) objs
pls1 = pls { objs_loaded = objs_loaded' }
- wanted_objs = concatMap linkableObjs new_objs
+ wanted_objs <- concat <$> mapM (loadableObjs interp) new_objs
if interpreterDynamic interp
then do pls2 <- dynLoadObjs interp hsc_env pls1 wanted_objs
@@ -875,6 +873,18 @@ loadObjects interp hsc_env pls objs = do
return (pls2, Failed)
+loadableObjs :: Interp -> Linkable -> IO [FilePath]
+loadableObjs interp l = concat <$> mapM go (Foldable.toList (linkableParts l))
+ where
+ go (DotO fn ModuleObject)
+ | Just suffixes <- interpObjSuffix interp = do
+ let fn' = swapObjSuffix suffixes fn
+ ok <- doesFileExist fn'
+ if ok
+ then return [fn']
+ else throwGhcExceptionIO (ProgramError ("cannot find object file " ++ fn'))
+ go part = return (linkablePartObjectPaths part)
+
-- | Create a shared library containing the given object files
mkDynLoadLib :: HscEnv -> (Ways -> Ways) -> [(FilePath, String)] ->[UnitId] -> [FilePath] -> IO (Maybe (FilePath, FilePath, String))
mkDynLoadLib _ _ _ _ [] = return Nothing
@@ -959,17 +969,18 @@ dynLoadObjs interp hsc_env pls objs = do
then addWay WayProf
else id
-rmDupLinkables :: LinkableSet LinkableUsage -- ^ Already loaded
+rmDupLinkables :: Maybe (String, String)
+ -> LinkableSet LinkableUsage -- ^ Already loaded
-> [Linkable] -- ^ New linkables
-> (LinkableSet LinkableUsage, -- New loaded set (including new ones)
[Linkable]) -- New linkables (excluding dups)
-rmDupLinkables already ls
+rmDupLinkables mb_osuf already ls
= go already [] ls
where
go !already extras [] = (already, extras)
go !already extras (l:ls)
| linkableInSet l already = go already extras ls
- | otherwise = go (extendModuleEnv already (linkableModule l) $! mkLinkableUsage l) (l:extras) ls
+ | otherwise = go (extendModuleEnv already (linkableModule l) $! mkLinkableUsage mb_osuf l) (l:extras) ls
{- **********************************************************************
@@ -981,7 +992,7 @@ rmDupLinkables already ls
dynLinkBCOs :: Interp -> LoaderState -> KeepModuleLinkableDefinitions -> [Linkable] -> IO LoaderState
dynLinkBCOs interp pls keep_spec bcos =
- let (bcos_loaded', new_bcos) = rmDupLinkables (bcos_loaded pls) bcos
+ let (bcos_loaded', new_bcos) = rmDupLinkables Nothing (bcos_loaded pls) bcos
pls1 = pls { bcos_loaded = bcos_loaded' }
cbcs :: [CompiledByteCode]
=====================================
compiler/GHC/Linker/Types.hs
=====================================
@@ -36,8 +36,6 @@ module GHC.Linker.Types
, LinkedBreaks(..)
, emptyLinkedBreaks
, LinkableSet
- , mkLinkableSet
- , unionLinkableSet
, ObjFile
, SptEntry(..)
, LibrarySpec(..)
@@ -58,9 +56,8 @@ module GHC.Linker.Types
, linkableBCOs
, linkablePartBCOs
, linkableModuleByteCodes
- , linkableNativeParts
- , linkablePartitionParts
, linkablePartPath
+ , linkablePartObjectPaths
, isNativeCode
, linkableFilterByteCode
, linkableFilterNative
@@ -70,6 +67,7 @@ module GHC.Linker.Types
, linkableUsageObjs
, mkLinkablesUsage
, mkLinkableUsage
+ , swapObjSuffix
, ModuleByteCode(..)
)
@@ -95,6 +93,7 @@ import GHC.Unit.Module.Deps (LinkablePartUsage (..), linkablePartUsageObjectPath
import GHC.Unit.Module.Env
import GHC.Unit.Module.WholeCoreBindings
import GHC.Utils.Misc (seqNonEmpty)
+import GHC.Utils.Panic (pprPanic)
import GHC.Utils.Outputable
@@ -102,10 +101,10 @@ import Control.Applicative ((<|>))
import Control.Concurrent.MVar
import Data.Array
import Data.Functor.Identity
-import Data.Time ( UTCTime )
import Data.Maybe (mapMaybe)
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import qualified Data.List.NonEmpty as NE
+import System.FilePath (stripExtension, (<.>))
{- **********************************************************************
@@ -376,10 +375,10 @@ instance Outputable LoadedPkgInfo where
-- | Information we can use to dynamically link modules into the compiler
data LinkableWith parts = Linkable
- { linkableTime :: !UTCTime
- -- ^ Time at which this linkable was built
- -- (i.e. when the bytecodes were produced,
- -- or the mod date on the files)
+ { linkableHash :: Fingerprint
+ -- ^ The identity of the linkable, derived from the hash of its contents
+ -- Lazy because bytecode is compiled lazily (see loadIfaceByteCodeLazy),
+ -- and inspecting the hash can force compiling the bytecode itself.
, linkableModule :: !Module
-- ^ The linkable module itself
@@ -396,22 +395,12 @@ type LinkableUsage = LinkableWith (NonEmpty LinkablePartUsage)
type LinkableSet = ModuleEnv
-mkLinkableSet :: [Linkable] -> LinkableSet Linkable
-mkLinkableSet ls = mkModuleEnv [(linkableModule l, l) | l <- ls]
-
--- | Union of LinkableSets.
---
--- In case of conflict, keep the most recent Linkable (as per linkableTime)
-unionLinkableSet :: LinkableSet (LinkableWith a) -> LinkableSet (LinkableWith a) -> LinkableSet (LinkableWith a)
-unionLinkableSet = plusModuleEnv_C go
- where
- go l1 l2
- | linkableTime l1 > linkableTime l2 = l1
- | otherwise = l2
instance Outputable a => Outputable (LinkableWith a) where
- ppr (Linkable when_made mod parts)
- = (text "Linkable" <+> parens (text (show when_made)) <+> ppr mod)
+ -- Don't print the hash, forcing it can trigger compilation
+ -- See the comment on 'linkableHash'
+ ppr (Linkable _ mod parts)
+ = (text "Linkable" <+> ppr mod)
$$ nest 3 (ppr parts)
type ObjFile = FilePath
@@ -452,13 +441,13 @@ data ModuleByteCode = ModuleByteCode { gbc_module :: Module
, gbc_hash :: !Fingerprint
}
-mkModuleByteCodeLinkable :: UTCTime -> ModuleByteCode -> Linkable
-mkModuleByteCodeLinkable linkable_time bco = do
- Linkable linkable_time (gbc_module bco) (pure (DotGBC bco))
+mkModuleByteCodeLinkable :: ModuleByteCode -> Linkable
+mkModuleByteCodeLinkable bco =
+ Linkable (gbc_hash bco) (gbc_module bco) (pure (DotGBC bco))
-mkOnlyModuleByteCodeLinkable :: UTCTime -> ModuleByteCode -> LinkableWith ModuleByteCode
-mkOnlyModuleByteCodeLinkable linkable_time bco = do
- Linkable linkable_time (gbc_module bco) bco
+mkOnlyModuleByteCodeLinkable :: ModuleByteCode -> LinkableWith ModuleByteCode
+mkOnlyModuleByteCodeLinkable bco =
+ Linkable (gbc_hash bco) (gbc_module bco) bco
instance Outputable ModuleByteCode where
ppr (ModuleByteCode mod _cbc _fos _) = text "ModuleByteCode" <+> ppr mod
@@ -484,21 +473,13 @@ linkableBCOs l = [ gbc_compiled_byte_code gbc | DotGBC gbc <- NE.toList (linkabl
linkableModuleByteCodes :: Linkable -> [ModuleByteCode]
linkableModuleByteCodes l = [ mbc | DotGBC mbc <- NE.toList (linkableParts l) ]
--- | List the native linkable parts (.o) of a linkable
-linkableNativeParts :: Linkable -> [LinkablePart]
-linkableNativeParts l = NE.filter isNativeCode (linkableParts l)
-
--- | Split linkable parts into (native code parts, BCOs parts)
-linkablePartitionParts :: Linkable -> ([LinkablePart],[LinkablePart])
-linkablePartitionParts l = NE.partition isNativeCode (linkableParts l)
-
-- | List the native objects (.o) of a linkable
linkableObjs :: Linkable -> [FilePath]
linkableObjs l = concatMap linkablePartObjectPaths (linkableParts l)
-- | List the paths of the native objects (.o)
linkableFiles :: Linkable -> [FilePath]
-linkableFiles l = concatMap linkablePartNativePaths (NE.toList (linkableParts l))
+linkableFiles l = mapMaybe linkablePartPath (NE.toList (linkableParts l))
-------------------------------------------
@@ -514,13 +495,6 @@ linkablePartPath = \case
DotO fn _ -> Just fn
DotGBC {} -> Nothing
--- | Return the paths of all object code files (.o) contained in this
--- 'LinkablePart'.
-linkablePartNativePaths :: LinkablePart -> [FilePath]
-linkablePartNativePaths = \case
- DotO fn _ -> [fn]
- DotGBC {} -> []
-
-- | Return the paths of all object files (.o) contained in this 'LinkablePart'.
linkablePartObjectPaths :: LinkablePart -> [FilePath]
linkablePartObjectPaths = \case
@@ -578,8 +552,13 @@ partitionLinkables linkables =
--
-- Each 'LinkablePartUsage' is fully evaluated to avoid retaining any reference
-- to the original 'LinkablePart'.
-mkLinkableUsage :: Linkable -> LinkableUsage
-mkLinkableUsage lnk =
+swapObjSuffix :: (String, String) -> FilePath -> FilePath
+swapObjSuffix (from, to) file = case stripExtension from file of
+ Just base -> base <.> to
+ Nothing -> pprPanic "swapObjSuffix" (text file <+> text from)
+
+mkLinkableUsage :: Maybe (String, String) -> Linkable -> LinkableUsage
+mkLinkableUsage mb_osuf lnk =
let
linkablesWithUsage = NE.map (go (linkableModule lnk)) (linkableParts lnk)
lnkUsage = lnk
@@ -589,7 +568,9 @@ mkLinkableUsage lnk =
seqNonEmpty linkablesWithUsage linkablesWithUsage
}
in
- linkableParts lnkUsage `seq` lnkUsage
+ -- Also force the hash so that we don't retain the actual bytecode
+ -- from a LinkableUsage
+ linkableHash lnkUsage `seq` linkableParts lnkUsage `seq` lnkUsage
where
mkFileLinkablePartUsage m fp objs =
FileLinkablePartUsage
@@ -609,11 +590,15 @@ mkLinkableUsage lnk =
go :: Module -> LinkablePart -> LinkablePartUsage
go m lnkPart = case lnkPart of
+ DotO fn ModuleObject
+ | Just suffixes <- mb_osuf
+ , let fn' = swapObjSuffix suffixes fn
+ -> mkFileLinkablePartUsage m fn' [fn']
DotO fn _ -> mkFileLinkablePartUsage m fn (linkablePartObjectPaths lnkPart)
DotGBC mbc -> mkByteCodeLinkablePartUsage m (gbc_hash mbc) (linkablePartObjectPaths lnkPart)
-mkLinkablesUsage :: [Linkable] -> [LinkableUsage]
-mkLinkablesUsage linkables = map mkLinkableUsage linkables
+mkLinkablesUsage :: Maybe (String, String) -> [Linkable] -> [LinkableUsage]
+mkLinkablesUsage mb_osuf linkables = map (mkLinkableUsage mb_osuf) linkables
linkableUsageObjs :: LinkableUsage -> [FilePath]
linkableUsageObjs lnkWithUsage = concatMap linkablePartUsageObjectPaths (linkableParts lnkWithUsage)
=====================================
compiler/GHC/Runtime/Interpreter/Init.hs
=====================================
@@ -11,6 +11,7 @@ where
import GHC.Prelude
import GHC.Data.FastString.Env
import GHC.Driver.DynFlags
+import GHC.Driver.Session (objectSuf)
import GHC.Platform
import GHC.Platform.Ways
import GHC.Settings
@@ -74,6 +75,23 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
fs_cache <- liftIO $ newMVar emptyFsEnv
+#if defined(HAVE_INTERNAL_INTERPRETER)
+ let host_way_tag = case waysTag hostFullWays of
+ "" -> ""
+ tag -> tag ++ "_"
+ internal_obj_suffix
+ | hostFullWays == fullWays (interpWays opts) = Nothing
+ | otherwise = Just (objectSuf dflags, host_way_tag ++ "o")
+#endif
+
+#if !defined(wasm32_HOST_ARCH)
+ let target_full_ways = fullWays (interpWays opts)
+ wasm_obj_suffix
+ | target_full_ways `hasWay` WayDyn = Nothing
+ | otherwise =
+ Just (objectSuf dflags, waysTag (WayDyn `addWay` target_full_ways) ++ "_o")
+#endif
+
-- see Note [Target code interpreter]
if
#if !defined(wasm32_HOST_ARCH)
@@ -103,7 +121,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
, wasmInterpHsSoSuffix = way_tag ++ dynLibSuffix (interpNameVer opts)
, wasmInterpUnitState = ue_homeUnitState unit_env
}
- pure $ Just $ Interp (ExternalInterp $ ExtWasm $ ExtInterpState cfg s) loader lookup_cache fs_cache
+ pure $ Just $ Interp (ExternalInterp $ ExtWasm $ ExtInterpState cfg s) loader lookup_cache fs_cache wasm_obj_suffix
#endif
-- JavaScript interpreter
@@ -122,7 +140,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
, jsInterpFinderOpts = interpFinderOpts opts
, jsInterpFinderCache = finder_cache
}
- return (Just (Interp (ExternalInterp (ExtJS (ExtInterpState cfg s))) loader lookup_cache fs_cache))
+ return (Just (Interp (ExternalInterp (ExtJS (ExtInterpState cfg s))) loader lookup_cache fs_cache Nothing))
-- external interpreter
| interpExternal opts
@@ -149,7 +167,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
}
s <- liftIO $ newMVar InterpPending
loader <- liftIO Loader.uninitializedLoader
- return (Just (Interp (ExternalInterp (ExtIServ (ExtInterpState conf s))) loader lookup_cache fs_cache))
+ return (Just (Interp (ExternalInterp (ExtIServ (ExtInterpState conf s))) loader lookup_cache fs_cache Nothing))
-- Internal interpreter
| otherwise
@@ -157,7 +175,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
#if defined(HAVE_INTERNAL_INTERPRETER)
do
loader <- liftIO Loader.uninitializedLoader
- return (Just (Interp InternalInterp loader lookup_cache fs_cache))
+ return (Just (Interp InternalInterp loader lookup_cache fs_cache internal_obj_suffix))
#else
return Nothing
#endif
=====================================
compiler/GHC/Runtime/Interpreter/Types.hs
=====================================
@@ -79,6 +79,10 @@ data Interp = Interp
, interpStringCache :: !(MVar (FastStringEnv (RemotePtr ())))
-- ^ MallocStrings cache
+
+ , interpObjSuffix :: !(Maybe (String, String))
+ -- ^ @(from, to)@ object suffixes to swap when the interpreter cannot
+ -- load objects built the target's way
}
data InterpInstance
=====================================
compiler/GHC/Unit/Finder.hs
=====================================
@@ -58,7 +58,6 @@ import GHC.Unit.Finder.Types
import qualified GHC.Data.ShortText as ST
-import GHC.Utils.Misc
import GHC.Utils.Outputable as Outputable
import GHC.Utils.Panic
@@ -72,7 +71,6 @@ import GHC.Fingerprint
import Data.IORef
import Control.Applicative ((<|>))
import Control.Monad
-import Data.Time
import qualified Data.Map as M
import GHC.Types.Unique.Map
import GHC.Driver.Env
@@ -1004,15 +1002,15 @@ mkStubPaths fopts mod location = do
findObjectLinkableMaybe :: Module -> ModLocation -> IO (Maybe Linkable)
findObjectLinkableMaybe mod locn
= do let obj_fn = ml_obj_file locn
- maybe_obj_time <- modificationTimeIfExists (ml_obj_file_ospath locn)
- case maybe_obj_time of
- Nothing -> return Nothing
- Just obj_time -> liftM Just (findObjectLinkable mod obj_fn obj_time)
-
--- Make an object linkable when we know the object file exists, and we know
--- its modification time.
-findObjectLinkable :: Module -> FilePath -> UTCTime -> IO Linkable
-findObjectLinkable mod obj_fn obj_time =
- pure (Linkable obj_time mod (NE.singleton (DotO obj_fn ModuleObject)))
+ exists <- doesFileExist (ml_obj_file_ospath locn)
+ if not exists
+ then return Nothing
+ else do
+ obj_hash <- getFileHash obj_fn
+ return (Just (findObjectLinkable mod obj_fn obj_hash))
+
+findObjectLinkable :: Module -> FilePath -> Fingerprint -> Linkable
+findObjectLinkable mod obj_fn obj_hash =
+ Linkable obj_hash mod (NE.singleton (DotO obj_fn ModuleObject))
-- We used to look for _stub.o files here, but that was a bug (#706)
-- Now GHC merges the stub.o into the main .o (#3687)
=====================================
testsuite/tests/driver/recomp023/M.hs
=====================================
@@ -0,0 +1,4 @@
+module M where
+
+m :: Int
+m = 5
=====================================
testsuite/tests/driver/recomp023/Makefile
=====================================
@@ -0,0 +1,13 @@
+TOP=../../..
+include $(TOP)/mk/boilerplate.mk
+include $(TOP)/mk/test.mk
+
+clean:
+
+recomp023: clean
+ '$(TEST_HC)' $(TEST_HC_OPTS) --make -bytecodelib -fbyte-code \
+ -fwrite-interface -fwrite-byte-code -this-unit-id=recomp023 \
+ -o recomp023.bytecodelib M.hs
+ '$(TEST_HC)' $(TEST_HC_OPTS) --make -bytecodelib -fbyte-code \
+ -fwrite-interface -fwrite-byte-code -this-unit-id=recomp023 \
+ -o recomp023.bytecodelib M.hs
=====================================
testsuite/tests/driver/recomp023/all.T
=====================================
@@ -0,0 +1,2 @@
+test('recomp023', [extra_files(['M.hs']), req_bco, normalise_slashes],
+ makefile_test, [])
=====================================
testsuite/tests/driver/recomp023/recomp023.stdout
=====================================
@@ -0,0 +1,2 @@
+[1 of 2] Compiling M ( M.hs, M.gbc )
+[2 of 2] Linking recomp023.bytecodelib
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f7b9750f8ac4e03574138e59a65bb9…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f7b9750f8ac4e03574138e59a65bb9…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/romes/27729] rts: Fix race condition in MSG_UPD_TSO_FLAGS execution
by Rodrigo Mesquita (@alt-romes) 25 Aug '26
by Rodrigo Mesquita (@alt-romes) 25 Aug '26
25 Aug '26
Rodrigo Mesquita pushed to branch wip/romes/27729 at Glasgow Haskell Compiler / GHC
Commits:
9dfc886c by Rodrigo Mesquita at 2026-08-25T10:36:32+01:00
rts: Fix race condition in MSG_UPD_TSO_FLAGS execution
The code for processing the MSG_UPD_TSO_FLAGS message was not taking
into consideration that the TSO's owner might have moved in between that
capability receiving the message (since it was its previous owner) and
starting to process its inbox (a point at which it was no longer the
owner)
Added Note [TSO owner may change in between Msg being sent and received]
to explain this race and the pattern used to fix this, where we just
forward the message to the new owner.
Fixes #27729
- - - - -
4 changed files:
- rts/CloneStack.c
- rts/Messages.c
- rts/Threads.c
- rts/Threads.h
Changes:
=====================================
rts/CloneStack.c
=====================================
@@ -88,6 +88,7 @@ void sendCloneStackMessage(StgTSO *tso, HsStablePtr mvar) {
void handleCloneStackMessage(Capability *cap, MessageCloneStack *msg){
// We must check that the current owner of the thread we want to clone the stack for
// is still this capability.
+ // See Note [TSO owner may change in between Msg being sent and received]
Capability *owner = RELAXED_LOAD(&msg->tso->cap);
if (owner != cap) {
// The target TSO may have migrated after the message was queued on the old
=====================================
rts/Messages.c
=====================================
@@ -66,6 +66,62 @@ void sendMessage(Capability *from_cap, Capability *to_cap, Message *msg)
Handle a message
------------------------------------------------------------------------- */
+/*
+Note [TSO owner may change in between Msg being sent and received]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+When a message is sent from Capability (C1) to a target TSO (T2) (e.g.
+MessageUpdTSOFlag, MessageCloneStack, ...), it is queued on the TSO's owner
+Capability (C3) inbox (inboxes are owned by Capabilities, not TSOs).
+
+At a later point, the Capability (C3) will process its inbox. Upon receiving
+the message meant for a specific TSO (T2), it must first always check that the
+TSO's owner is *still* itself (C3).
+
+The target TSO (T2) may have migrated after the message was queued on its old
+capability (C3). In that case we must forward the request to the new owner
+(say, C4); otherwise the Capability C3 could be modifying a TSO it no longer
+owns, racing with its actual owner mutating it, since it is no longer the owner.
+
+The message meant for a TSO should only be executed when the receiving
+Capability is still the owner of that TSO. Otherwise, it must be forwarded to
+the new owner.
+
+The general pattern is one where there's a top-level function which assumes it
+can be called by capabilities other than the TSO's owner. The function checks
+whether the current capability is the TSO owner. If yes, execute the action. If
+not, then it sends a message to the current TSO's owner. On receiving the
+message, the new capability will just call that top-level function, which will
+ensure the message is forwarded again if the TSO owner changed.
+It will look something like:
+
+ runMyMsg(Capability *from, StgTSO *target, ...) {
+
+#if defined(THREADED_RTS)
+ Capability *owner = RELAXED_LOAD(&target->cap)
+ if (owner != from) {
+ MessageMyMsg* msg = ...
+ sendMessage(cap, owner, msg)
+ return
+ }
+#endif
+
+ actuallyDoTheWork(...)
+ }
+
+ executeMessage(...) {
+
+ if (i == &stg_MY_MSG_info) {
+
+ MessageMyMsg* msg = (MessageMyMsg*) m
+ runMyMsg(cap, m->tso, ...)
+
+ }
+ }
+
+See example `updThreadFlag` and `executeMessage`'s `stg_MSG_UPD_TSO_FLAG_info`,
+or `tryWakeUpThread` and `stg_MSG_TRY_WAKEUP_info` for two live examples.
+*/
+
#if defined(THREADED_RTS)
void
@@ -142,13 +198,9 @@ loop:
}
else if(i == &stg_MSG_UPD_TSO_FLAG_info){
MessageUpdTSOFlag *u = (MessageUpdTSOFlag*) m;
- if (u->set) {
- u->tso->flags |= u->flag;
- }
- else {
- u->tso->flags &= ~u->flag;
- }
- return;
+
+ StgTSO *tso = RELAXED_LOAD(&u->tso);
+ updThreadFlag(cap, tso, u->flag, u->set);
}
else
{
=====================================
rts/Threads.c
=====================================
@@ -379,23 +379,25 @@ migrateThread (Capability *from, StgTSO *tso, Capability *to)
sets or unsets a flag in a given TSO
------------------------------------------------------------------------- */
-static void
-updThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag, StgBool set);
-
void setThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag)
{
- updThreadFlag(from, tso, flag, 1);
+ updThreadFlag(from, tso, flag, true);
}
void unsetThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag)
{
- updThreadFlag(from, tso, flag, 0);
+ updThreadFlag(from, tso, flag, false);
}
-static void
-updThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag, StgBool set /* true=set, false=unset */)
+void
+updThreadFlag(Capability *from USED_IF_THREADS, StgTSO *tso, StgWord32 flag, StgBool set /* true=set, false=unset */)
{
#if defined(THREADED_RTS)
+ // If we're the current owner of the thread we want to modify, do it.
+ // Otherwise, we must forward the message to the actual owner.
+ // When executing the upd message, we check again that we're still the TSO
+ // owner (which may have changed since the message was queued on this cap.)
+ // See Note [TSO owner may change in between Msg being sent and received]
Capability *tso_owner = RELAXED_LOAD(&tso->cap);
if (from != tso_owner) {
MessageUpdTSOFlag *msg;
@@ -407,8 +409,6 @@ updThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag, StgBool set /* true
sendMessage(from, tso_owner, (Message*)msg);
return;
}
-#else
- (void)from; // unused in non-threaded case
#endif
if (set) {
=====================================
rts/Threads.h
=====================================
@@ -21,6 +21,7 @@ void migrateThread (Capability *from, StgTSO *tso, Capability *to);
void setThreadFlag (Capability *from, StgTSO *tso, StgWord32 flag);
void unsetThreadFlag (Capability *from, StgTSO *tso, StgWord32 flag);
+void updThreadFlag (Capability *from, StgTSO *tso, StgWord32 flag, StgBool set);
// Wakes up a thread on a Capability (probably a different Capability
// from the one held by the current Task).
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/9dfc886ce2ae090792614ceb5b88e9a…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/9dfc886ce2ae090792614ceb5b88e9a…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/marge_bot_batch_merge_job] 9 commits: cmm dumps: Add machop width info with -dppr-debug for infix ops.
by Marge Bot (@marge-bot) 25 Aug '26
by Marge Bot (@marge-bot) 25 Aug '26
25 Aug '26
Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
7d1be475 by Andreas Klebinger at 2026-08-25T04:56:23-04:00
cmm dumps: Add machop width info with -dppr-debug for infix ops.
- - - - -
65f80955 by Andreas Klebinger at 2026-08-25T04:56:23-04:00
CmmLint: Check for unsupported MachOp widths
machOpArgReps now maps MachOp + Width to a list of supported
argument widths or Nothing if the given operation is not supported
at the given width.
This allows us to check for nonsensical combinations like FloatToInt
at Word16.
Similarly we now check that every address is actually wordwidth.
- - - - -
895335e0 by Andreas Klebinger at 2026-08-25T04:56:23-04:00
arm64 ncg: The big subword truncation fix.
A set of slightly related fixes to arm subword handling:
Bitmask immediates:
Don't produce overflowing assembly literals.
There is still another bug here that causes us to miss some valid
literals but we will fix that later.
Improve subword truncation handling:
We now use a small set of helpers to truncate `Register` values rather
than truncating immediate `Reg` values which greatly simplifies the code
structure. This fixes a great many bugs to do with sign/zero extending subwords
or the lack thereof.
We now establish the invariant that subword values are zero-extended at
every site at which they come into "scope" of the ncg, and rely on the
invariant throughout rather than pessimistically inserting redundant
extensions in a hodgepodge manner at the use sites of these values.
This fixes at least the bugs described in issues #27533, #27430
#27537, #27538, #27539, and #27550. But likely more bugs yet not
found.
Subword ffi results:
Apply truncations when calling functions returning
subword values.
genCondJump:
Don't sign extend signed values in the input register as
it might map to a local variable, corrupting the value stored within.
Fix subword store/load instructions.:
We used to read those at 32bit width even for smaller values possibly
resulting in invalid memory access. Now we construct the suffix for
subword variants based on the instruction format for these.
- - - - -
2a8c1fe5 by Andreas Klebinger at 2026-08-25T04:56:23-04:00
arm64 ncg: Fix MO_V_Broadcast for non-literals.
We now use OpReg instead of OpScalarAsVec as required since we broadcast a gp register.
Also adds a test. Fixes #27565.
- - - - -
ef57516c by Andreas Klebinger at 2026-08-25T04:56:23-04:00
Add some test cases covering bugs in the arm ncg.
* Test for #27430 (subword ffi results)
* #27537 - subword conversions
* #27538 - subwords used in conditional
* #27533 - single byte read
- - - - -
e6730fbe by Andreas Klebinger at 2026-08-25T04:56:23-04:00
cmmLint: Lint against MO_FS_Truncate subword use.
- - - - -
28dba691 by Andreas Klebinger at 2026-08-25T04:56:25-04:00
rts linker: Fix pointer arithmetic issue in flushInstructionCacheRISCV64
We accidentally operated over `uint64_t*` when we should use `uint8_t`.
Fixes #27569
- - - - -
43d01ada by Zubin Duggal at 2026-08-25T04:56:27-04:00
ghc-internal: annotateSTM should use catchSTM# rather than catch#
A catch# frame inside a transaction breaks retry and async exception
delivery.
Fixes #27657
- - - - -
719b68eb by Alan Zimmerman at 2026-08-25T04:56:28-04:00
EPA: Uses Parsers.parseModule for exactprint tests
Parsers.parseModule is the advertised way to parse for use for exact
printing in the ghc-exactprint library. This commit updates the GHC
exact print testing to use it.
This requires moving the comment balancing that was occurring
only in the test path into the advertising parser path, so it moves
from Transforms.hs to Utils.hs.
Also update the comment adding to honour trailing annotations
- - - - -
39 changed files:
- + changelog.d/T27657
- + changelog.d/arm_ncg_fixes_T27430
- compiler/GHC/Cmm/Expr.hs
- compiler/GHC/Cmm/Lint.hs
- compiler/GHC/Cmm/MachOp.hs
- compiler/GHC/Cmm/Parser.y
- compiler/GHC/CmmToAsm/AArch64/CodeGen.hs
- compiler/GHC/CmmToAsm/AArch64/Instr.hs
- compiler/GHC/CmmToAsm/AArch64/Ppr.hs
- libraries/ghc-internal/src/GHC/Internal/STM.hs
- rts/linker/elf_reloc_riscv64.c
- testsuite/tests/cmm/should_compile/Makefile
- + testsuite/tests/cmm/should_compile/T27368-ppr-debug.stderr
- − testsuite/tests/cmm/should_compile/T27368-ppr-debug.stdout
- testsuite/tests/cmm/should_compile/all.T
- + testsuite/tests/codeGen/should_run/T27430.hs
- + testsuite/tests/codeGen/should_run/T27430.stdout
- + testsuite/tests/codeGen/should_run/T27430_c.c
- + testsuite/tests/codeGen/should_run/T27533.hs
- + testsuite/tests/codeGen/should_run/T27533.stdout
- + testsuite/tests/codeGen/should_run/T27533_cmm.cmm
- + testsuite/tests/codeGen/should_run/T27537.hs
- + testsuite/tests/codeGen/should_run/T27537.stdout
- + testsuite/tests/codeGen/should_run/T27538.hs
- + testsuite/tests/codeGen/should_run/T27538.stdout
- testsuite/tests/codeGen/should_run/all.T
- + testsuite/tests/concurrent/should_run/T27657a.hs
- + testsuite/tests/concurrent/should_run/T27657a.stdout
- + testsuite/tests/concurrent/should_run/T27657b.hs
- + testsuite/tests/concurrent/should_run/T27657b.stdout
- testsuite/tests/concurrent/should_run/all.T
- + testsuite/tests/simd/should_run/T27565.hs
- + testsuite/tests/simd/should_run/T27565.stdout
- testsuite/tests/simd/should_run/all.T
- utils/check-exact/ExactPrint.hs
- utils/check-exact/Main.hs
- utils/check-exact/Parsers.hs
- utils/check-exact/Transform.hs
- utils/check-exact/Utils.hs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/cb5d38f50cf0c750136a20d1e2943b…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/cb5d38f50cf0c750136a20d1e2943b…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/27627] 5 commits: Never make an absent filler for a constraint type
by Zubin (@wz1000) 25 Aug '26
by Zubin (@wz1000) 25 Aug '26
25 Aug '26
Zubin pushed to branch wip/27627 at Glasgow Haskell Compiler / GHC
Commits:
89f1ccd1 by Simon Peyton Jones at 2026-08-25T14:15:09+05:30
Never make an absent filler for a constraint type
mkAbsentFiller used isTerminatingType to decide, but that is not enough.
Consider
class Eq a => UC a where {}
let u :: UC Int -- UC Int is a "non-terminating type"
u = error "Absent"
let e :: Eq Int -- Eq Int is a "terminating type"
e = $p1UC u
We clearly must not make a filler for `e`, because we speculatively
evaluate it. But speculatively evaluating `e` forces `u`, so we must not
make one for `u` either.
Asking isDictTy instead is not enough either, because it does not catch a
constraint hidden behind an unreduced type family application:
type family F a :: Constraint
type instance F W = TC W
a :: F W => Int -> Int -- (F W) argument is absent
Oops! Entered absent arg Arg: irred
Type: F W
So play safe and use isPredTy: never make an absent filler for any
constraint-kinded type.
Fixes #27627
- - - - -
f1f9c484 by Zubin Duggal at 2026-08-25T14:15:09+05:30
Add tests for absent fillers at dictionary types
T27627 a unary class whose superclass is a non-unary class
T27627a ...whose superclass is a Constraint-kinded type family
T27627b ...whose superclass is a quantified constraint
T27627c a unary class applied to itself, (UC (UC (TC a)))
T27627e a (forall b. P b) dictionary that loops
- - - - -
8b4217d2 by Zubin Duggal at 2026-08-25T14:15:09+05:30
An abstract TyCon may hide a unary class
A class declared in an hs-boot file is an AbstractTyCon inside the
module loop, and compiling the real declaration may reveal it to be a
UnaryClassTyCon.
- isTerminatingType returned True for such AbstractTyCons
- IfaceToCore set the unary flag to False in the DFunId
So we could end up speculating bottom dictionaries because inside a module
loop we see an UnaryClassTyCon as an AbstractTyCon
Use mayBeUnaryClassTyCon instead of isUnaryClassTyCon, which returns True for an
abstract TyCon.
Fixes #27704
- - - - -
0caf23fa by Zubin Duggal at 2026-08-25T14:15:09+05:30
Specialise: don't replace dead args with absent fillers
specHeader decides an argument is dead by calling isDeadBinder on a binder of
the /optimised RHS/, then applies the filler to the /stable unfolding/
template instead. The two may differ, so the argument can be dead in
the RHS and not in the template.
The specialised function's unfolding then has an absent filler, and any call
site that inlines it evaluates the error thunk.
Dropping dead args in the specialiser is rarely worth it, to quote Simon,
"The later worker/wrapper pass will pick up the dead arg later if it is really dead. Keeps the specialiser simpler."
So instead of trying to check if the arg really is dead in the stable unfolding,
just drop the logic for dropping dead args in the specialiser altogeher.
Fixes #27703
- - - - -
b74f9775 by Zubin Duggal at 2026-08-25T14:15:09+05:30
CorePrep: don't speculate a call across an hs-boot edge
We take care not to evaluate things that might be bottom, like a
looping dictionary group, but our analysis is defeated by boot files.
We only track recursion within a module, so two dictionaries that
depend on each other across a module loop each look non-recursive, and
we might speculate them.
Any recursion we cannot see must cross an hs-boot edge, so refuse to
speculate calls that cross one.
Fixes #27717
- - - - -
68 changed files:
- + changelog.d/27627
- + changelog.d/27703
- + changelog.d/27704
- + changelog.d/27717
- compiler/GHC/Core.hs
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Opt/WorkWrap/Utils.hs
- compiler/GHC/Core/Predicate.hs
- compiler/GHC/Core/TyCon.hs
- compiler/GHC/Core/Type.hs
- compiler/GHC/CoreToStg/Prep.hs
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Types/Demand.hs
- compiler/GHC/Types/Literal.hs
- + testsuite/tests/core-to-stg/T27627/Callee.hs
- + testsuite/tests/core-to-stg/T27627/Caller.hs
- + testsuite/tests/core-to-stg/T27627/Main.hs
- + testsuite/tests/core-to-stg/T27627/T27627.stdout
- + testsuite/tests/core-to-stg/T27627/all.T
- + testsuite/tests/core-to-stg/T27627a/Callee.hs
- + testsuite/tests/core-to-stg/T27627a/Caller.hs
- + testsuite/tests/core-to-stg/T27627a/Main.hs
- + testsuite/tests/core-to-stg/T27627a/T27627a.stdout
- + testsuite/tests/core-to-stg/T27627a/all.T
- + testsuite/tests/core-to-stg/T27627b/Callee.hs
- + testsuite/tests/core-to-stg/T27627b/Caller.hs
- + testsuite/tests/core-to-stg/T27627b/Main.hs
- + testsuite/tests/core-to-stg/T27627b/T27627b.stdout
- + testsuite/tests/core-to-stg/T27627b/all.T
- + testsuite/tests/core-to-stg/T27627c/Callee.hs
- + testsuite/tests/core-to-stg/T27627c/Caller.hs
- + testsuite/tests/core-to-stg/T27627c/Main.hs
- + testsuite/tests/core-to-stg/T27627c/T27627c.stdout
- + testsuite/tests/core-to-stg/T27627c/all.T
- + testsuite/tests/core-to-stg/T27627e.hs
- + testsuite/tests/core-to-stg/T27627e.stdout
- + testsuite/tests/core-to-stg/T27627f/Callee.hs
- + testsuite/tests/core-to-stg/T27627f/Caller.hs
- + testsuite/tests/core-to-stg/T27627f/Inst.hs
- + testsuite/tests/core-to-stg/T27627f/Main.hs
- + testsuite/tests/core-to-stg/T27627f/T27627f.stdout
- + testsuite/tests/core-to-stg/T27627f/all.T
- + testsuite/tests/core-to-stg/T27704/Callee.hs
- + testsuite/tests/core-to-stg/T27704/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704/Main.hs
- + testsuite/tests/core-to-stg/T27704/Mid.hs
- + testsuite/tests/core-to-stg/T27704/T27704.stdout
- + testsuite/tests/core-to-stg/T27704/all.T
- + testsuite/tests/core-to-stg/T27704a/Callee.hs
- + testsuite/tests/core-to-stg/T27704a/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704a/Main.hs
- + testsuite/tests/core-to-stg/T27704a/Mid.hs
- + testsuite/tests/core-to-stg/T27704a/T27704a.stdout
- + testsuite/tests/core-to-stg/T27704a/all.T
- + testsuite/tests/core-to-stg/T27717/Callee.hs
- + testsuite/tests/core-to-stg/T27717/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27717/Main.hs
- + testsuite/tests/core-to-stg/T27717/Mid.hs
- + testsuite/tests/core-to-stg/T27717/T27717.stdout
- + testsuite/tests/core-to-stg/T27717/Ty.hs
- + testsuite/tests/core-to-stg/T27717/all.T
- testsuite/tests/core-to-stg/all.T
- testsuite/tests/simplCore/should_compile/spec004.hs
- testsuite/tests/simplCore/should_compile/spec004.stderr
- + testsuite/tests/simplCore/should_run/T27703/Lib.hs
- + testsuite/tests/simplCore/should_run/T27703/Main.hs
- + testsuite/tests/simplCore/should_run/T27703/T27703.stdout
- + testsuite/tests/simplCore/should_run/T27703/all.T
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/4caa0c19733e49368f65c00f6056ba…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/4caa0c19733e49368f65c00f6056ba…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
25 Aug '26
Zubin pushed to branch wip/unload-strategy at Glasgow Haskell Compiler / GHC
Commits:
79f3073b by Zubin Duggal at 2026-08-25T13:17:55+05:30
Introduce -funload-strategy
When unloading object code, we have a choice to make. Do we call
purgeObj or unloadObj?
purgeObj clears the symbol tables associated with an object, so that
future objects can't link against it, but the object stays in memory
unloadObj does the above, but it also marks the object as needing to be
unloaded, so at some point in a future GC, the RTS may notice that it is
no longer used, and if so, unload it entirely, freeing up the memory.
Ideally we would always unload, but a number of bugs with the
implementation of unloadObj mean that it is fragile on many platforms.
This is documented in Note [Unloading vs purging objects]. So on these
platforms we purge instead.
We introduce the -funload-strategy flag, so that users can opt into
purging/unloading on platforms where we make the other choice by
default.
The distinction is moot when we are using the dynamic RTS, we don't do
either then.
Fixes #27741
- - - - -
13 changed files:
- + changelog.d/unload-strategy
- compiler/GHC.hs
- compiler/GHC/Driver/Config/Interpreter.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Linker/Loader.hs
- compiler/GHC/Runtime/Interpreter.hs
- compiler/GHC/Runtime/Interpreter/Init.hs
- compiler/GHC/Runtime/Interpreter/Types.hs
- docs/users_guide/ghci.rst
- testsuite/tests/ghc-api/T27606/T27606c.hs
- + testsuite/tests/ghc-api/T27606/T27606c_purge.stdout
- testsuite/tests/ghc-api/T27606/all.T
Changes:
=====================================
changelog.d/unload-strategy
=====================================
@@ -0,0 +1,16 @@
+section: linker
+synopsis: Add -funload-strategy to choose between purging and unloading object code
+issues: #27741
+mrs: !16583
+
+description: {
+ When the interpreter drops object code it has loaded it can either
+ purge it, clearing its symbol table entries but leaving it in memory,
+ or unload it, additionally allowing a later garbage collection to
+ reclaim its memory. Unloading is preferable, but its implementation is
+ fragile on a number of platforms, so GHC purges on those instead: it
+ unloads on Linux other than ARM, and purges everywhere else.
+ ``-funload-strategy=unload`` and ``-funload-strategy=purge`` opt into
+ the other choice. The flag has no effect when the interpreter is
+ dynamically linked, as neither happens then.
+}
=====================================
compiler/GHC.hs
=====================================
@@ -29,7 +29,7 @@ module GHC (
-- * Flags and settings
DynFlags(..), GeneralFlag(..), Severity(..), Backend, gopt,
ncgBackend, llvmBackend, viaCBackend, bytecodeBackend, interpreterBackend, noBackend,
- GhcMode(..), GhcLink(..),
+ GhcMode(..), GhcLink(..), UnloadStrategy(..),
parseDynamicFlags, parseTargetFiles,
getSessionDynFlags,
setTopSessionDynFlags,
@@ -728,6 +728,12 @@ setTopSessionDynFlags dflags = do
interp <- liftIO $ initInterpreter dflags tmpfs logger platform finder_cache unit_env interp_opts
+ case (hsc_interp hsc_env, unloadStrategy dflags, interp) of
+ (Nothing, Just _, Just i) | interpreterDynamic i ->
+ liftIO $ logInfo logger $ withPprStyle defaultUserStyle $
+ text "warning: -funload-strategy is ignored with a dynamic interpreter"
+ _ -> return ()
+
modifySession $ \h -> hscSetFlags dflags
h{ hsc_IC = (hsc_IC h){ ic_dflags = dflags }
, hsc_interp = hsc_interp h <|> interp
=====================================
compiler/GHC/Driver/Config/Interpreter.hs
=====================================
@@ -43,4 +43,5 @@ initInterpOpts dflags = do
, interpLdConfig = configureLd dflags
, interpCcConfig = configureCc dflags
, interpExecutableLinkOpts = initExecutableLinkOpts dflags Dynamic
+ , interpUnloadStrategyFlag = unloadStrategy dflags
}
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -40,6 +40,7 @@ module GHC.Driver.DynFlags (
isPackageDbRef,
Option(..), showOpt,
DynLibLoader(..),
+ UnloadStrategy(..),
positionIndependent,
optimisationFlags,
@@ -308,6 +309,7 @@ data DynFlags = DynFlags {
outputHi :: Maybe String,
dynOutputHi :: Maybe String,
dynLibLoader :: DynLibLoader,
+ unloadStrategy :: Maybe UnloadStrategy,
dynamicNow :: !Bool, -- ^ Indicate if we are now generating dynamic output
-- because of -dynamic-too. This predicate is
@@ -657,6 +659,7 @@ defaultDynFlags mySettings =
outputHi = Nothing,
dynOutputHi = Nothing,
dynLibLoader = SystemDependent,
+ unloadStrategy = Nothing,
dumpPrefix = "non-module.",
dumpPrefixForce = Nothing,
ldInputs = [],
@@ -965,6 +968,11 @@ data DynLibLoader
| SystemDependent
deriving Eq
+data UnloadStrategy
+ = UnloadStrategyUnload
+ | UnloadStrategyPurge
+ deriving Eq
+
data RtsOptsEnabled
= RtsOptsNone | RtsOptsIgnore | RtsOptsIgnoreAll | RtsOptsSafeOnly
| RtsOptsAll
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -55,6 +55,7 @@ module GHC.Driver.Session (
PackageDBFlag(..), PkgDbRef(..),
Option(..), showOpt,
DynLibLoader(..),
+ UnloadStrategy(..),
fFlags, fLangFlags, xFlags,
wWarningFlags,
makeDynFlagsConsistent,
@@ -725,6 +726,12 @@ parseDynLibLoaderMode f d =
("sysdep", "") -> d { dynLibLoader = SystemDependent }
_ -> throwGhcException (CmdLineError ("Unknown dynlib loader: " ++ f))
+parseUnloadStrategy :: String -> DynFlags -> DynFlags
+parseUnloadStrategy f d = case f of
+ "unload" -> d { unloadStrategy = Just UnloadStrategyUnload }
+ "purge" -> d { unloadStrategy = Just UnloadStrategyPurge }
+ _ -> throwGhcException (CmdLineError ("Unknown unload strategy: " ++ f))
+
setDumpPrefixForce f d = d { dumpPrefixForce = f}
-- XXX HACK: Prelude> words "'does not' work" ===> ["'does","not'","work"]
@@ -1892,6 +1899,8 @@ dynamic_flags_deps = [
(intSuffix (\n d -> d {maxForcedSpecArgs = n}))
, make_ord_flag defGhciFlag "fghci-hist-size"
(intSuffix (\n d -> d {ghciHistSize = n}))
+ , make_ord_flag defFlag "funload-strategy"
+ (hasArg parseUnloadStrategy)
-- wasm ghci browser mode
, make_ord_flag defGhciFlag "fghci-browser-host"
=====================================
compiler/GHC/Linker/Loader.hs
=====================================
@@ -111,6 +111,7 @@ import GHC.Linker.Types
import Control.Monad
import Data.Array
+import Data.Containers.ListUtils (nubOrd)
import Data.ByteString (ByteString)
import qualified Data.Set as Set
import Data.Char (isSpace)
@@ -898,8 +899,9 @@ dropModules interp mods pls = do
}
}
- mapM_ (purgeLinkableObjs interp) victim_usages
- when (any (not . null . linkableUsageObjs) victim_usages) $
+ let victim_objs = nubOrd (concatMap linkableUsageObjs victim_usages)
+ dropLinkableObjs interp victim_objs
+ when (not (null victim_objs)) $
purgeLookupSymbolCache interp
mapM_ (removeSptEntry interp)
@@ -914,31 +916,14 @@ dropModules interp mods pls = do
modifyHomePackageBytecodeState (bco_loader_state pls) drop_bytecode_state
}
--- | Purge the symbols of a dropped module's objects. We don't unload
--- them, because unloading is not well supported.
--- See Note [Automatically reloading stale linkables]
--- See Note [Unloading vs purging objects] in GHC.Runtime.Interpreter
-purgeLinkableObjs :: Interp -> LinkableUsage -> IO ()
-purgeLinkableObjs interp lnk
- | interpreterDynamic interp = return ()
- | otherwise
- = mapM_ (purgeObj interp) (linkableUsageObjs lnk)
-
-- See Note [Unloading vs purging objects] in GHC.Runtime.Interpreter
-unloadLinkableObjs :: Interp -> LinkableUsage -> IO ()
-unloadLinkableObjs interp lnk
+dropLinkableObjs :: Interp -> [FilePath] -> IO ()
+dropLinkableObjs interp objs
| interpreterDynamic interp = return ()
- -- We don't do any cleanup when linking objects with the
- -- dynamic linker. Doing so introduces extra complexity for
- -- not much benefit.
| otherwise
- = mapM_ (unloadObj interp) (linkableUsageObjs lnk)
- -- The components of a BCO linkable may contain
- -- dot-o files (generated from C stubs).
- --
- -- But the BCO parts can be unlinked just by
- -- letting go of them (plus of course depopulating
- -- the symbol table which is done in the main body)
+ = case interpUnloadStrategy interp of
+ UnloadStrategyUnload -> mapM_ (unloadObj interp) objs
+ UnloadStrategyPurge -> mapM_ (purgeObj interp) objs
-- | Load a linkable from a module, and add all the names from the linkable into the
-- closure environment.
@@ -1354,12 +1339,13 @@ unload_wkr interp pls@LoaderState{..} = do
-- testsuite/ghci can detect space leaks here.
let linkables_to_unload = moduleEnvElts objs_loaded ++ moduleEnvElts bcos_loaded
+ objs_to_unload = nubOrd (concatMap linkableUsageObjs linkables_to_unload)
- mapM_ (unloadLinkableObjs interp) linkables_to_unload
+ dropLinkableObjs interp objs_to_unload
-- If we unloaded any object files at all, we need to purge the cache
-- of lookupSymbol results.
- when (not (null (filter (not . null . linkableUsageObjs) linkables_to_unload))) $
+ when (not (null objs_to_unload)) $
purgeLookupSymbolCache interp
mapM_ (removeSptEntry interp) (concat (moduleEnvElts loaded_spt_keys))
=====================================
compiler/GHC/Runtime/Interpreter.hs
=====================================
@@ -601,24 +601,33 @@ unloadObj interp path = do
{- Note [Unloading vs purging objects]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-unloadObj removes the object's symbols and frees its memory. The memory
-is only freed at a major GC, once nothing references the object.
-purgeObj removes the symbols and never frees the memory.
-
-We only unloadObj in unload, which the driver calls before a
-compilation sweep, when everything is unloaded together. We purgeObj
-when dropModules replaces or removes single modules, because unloading
-is not well supported on many platforms/configurations. Purging is
-enough for correctness: new lookups find the replacement's symbols,
-and values built by the old code and computations still using it keep
-working.
-
-With a dynamic interpreter there is nothing to purge. Objects are
-linked into temporary shared libraries and their symbols are found by
-searching the loaded libraries, not in the linker's symbol table.
-Dropping a module flushes the symbol cache, and the replacement is
-loaded as a new library, so lookups find the replacement first and the
-old library stays loaded. This behaves like purging.
+There are two ways to drop objects:
+
+- unloadObj removes the object's symbols and eventually the RTS may decide to free its memory.
+- purgeObj removes the symbols and never frees the memory.
+
+purgeObj is enough for correctness, but leaks memory. unloadObj can be finnicky on certain
+platforms and/or may not be implemented correctly.
+
+We use the unload strategy given by -funload-strategy.
+The strategy we use by default depends on platform calculus, given the
+bugs that apply to each platform. We try to unload where we don't know of
+any bugs affecting correctness:
+
+- Linux: mostly unload.
+ - i386/x86_64: unload
+ - AArch64: We unload, but perhaps we should purge instead because of #24170.
+ - 32-bit ARM: purge. Unloading is broken (#21991).
+- FreeBSD: purge. Unloading is not implemented (#25491).
+- Darwin: purge. The process crashes at exit if an unloaded object
+ had C finalizers (#27616).
+- Windows: purge. Unloading code that is still in use is fragile
+ (#20852).
+- Purge as a fallback for everything else
+
+With a dynamic interpreter we never purge, or unload:
+Dynamic objects are linked into temporary shared libraries, and if a library is
+reloaded, then it shadows over the old one.
-}
-- | Purge an object's symbols.
=====================================
compiler/GHC/Runtime/Interpreter/Init.hs
=====================================
@@ -57,6 +57,7 @@ data InterpOpts = InterpOpts
, interpBrowserPlaywrightBrowserType :: Maybe String
, interpBrowserPlaywrightLaunchOpts :: Maybe String
, interpExecutableLinkOpts :: ExecutableLinkOpts
+ , interpUnloadStrategyFlag :: Maybe UnloadStrategy
}
-- | Initialize code interpreter
@@ -87,6 +88,14 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
| otherwise =
Just (objectSuf dflags, waysTag (WayDyn `addWay` target_full_ways) ++ "_o")
+ -- See Note [Unloading vs purging objects] in GHC.Runtime.Interpreter
+ let unload_strategy = case interpUnloadStrategyFlag opts of
+ Just s -> s
+ Nothing -> case (platformOS platform, platformArch platform) of
+ (OSLinux, ArchARM {}) -> UnloadStrategyPurge
+ (OSLinux, _) -> UnloadStrategyUnload
+ _ -> UnloadStrategyPurge
+
-- see Note [Target code interpreter]
if
#if !defined(wasm32_HOST_ARCH)
@@ -116,7 +125,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
, wasmInterpHsSoSuffix = way_tag ++ dynLibSuffix (interpNameVer opts)
, wasmInterpUnitState = ue_homeUnitState unit_env
}
- pure $ Just $ Interp (ExternalInterp $ ExtWasm $ ExtInterpState cfg s) loader lookup_cache fs_cache wasm_obj_suffix
+ pure $ Just $ Interp (ExternalInterp $ ExtWasm $ ExtInterpState cfg s) loader lookup_cache fs_cache wasm_obj_suffix unload_strategy
#endif
-- JavaScript interpreter
@@ -135,7 +144,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
, jsInterpFinderOpts = interpFinderOpts opts
, jsInterpFinderCache = finder_cache
}
- return (Just (Interp (ExternalInterp (ExtJS (ExtInterpState cfg s))) loader lookup_cache fs_cache Nothing))
+ return (Just (Interp (ExternalInterp (ExtJS (ExtInterpState cfg s))) loader lookup_cache fs_cache Nothing unload_strategy))
-- external interpreter
| interpExternal opts
@@ -162,7 +171,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
}
s <- liftIO $ newMVar InterpPending
loader <- liftIO Loader.uninitializedLoader
- return (Just (Interp (ExternalInterp (ExtIServ (ExtInterpState conf s))) loader lookup_cache fs_cache Nothing))
+ return (Just (Interp (ExternalInterp (ExtIServ (ExtInterpState conf s))) loader lookup_cache fs_cache Nothing unload_strategy))
-- Internal interpreter
| otherwise
@@ -170,7 +179,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do
#if defined(HAVE_INTERNAL_INTERPRETER)
do
loader <- liftIO Loader.uninitializedLoader
- return (Just (Interp InternalInterp loader lookup_cache fs_cache internal_obj_suffix))
+ return (Just (Interp InternalInterp loader lookup_cache fs_cache internal_obj_suffix unload_strategy))
#else
return Nothing
#endif
=====================================
compiler/GHC/Runtime/Interpreter/Types.hs
=====================================
@@ -52,6 +52,7 @@ import GHC.Platform
#if defined(HAVE_INTERNAL_INTERPRETER)
import GHC.Platform.Ways
#endif
+import GHC.Driver.DynFlags (UnloadStrategy)
import GHC.Utils.TmpFs
import GHC.Utils.Logger
import GHC.Unit.Env
@@ -83,6 +84,8 @@ data Interp = Interp
, interpObjSuffix :: !(Maybe (String, String))
-- ^ @(from, to)@ object suffixes to swap when the interpreter cannot
-- load objects built the target's way
+
+ , interpUnloadStrategy :: !UnloadStrategy
}
data InterpInstance
=====================================
docs/users_guide/ghci.rst
=====================================
@@ -3554,6 +3554,32 @@ breakpoints in object-code modules, for example. Only the exports of an
object-code module will be visible in GHCi, rather than all top-level
bindings as in interpreted modules.
+.. ghc-flag:: -funload-strategy=⟨strategy⟩
+ :shortdesc: Whether to ``purge`` or ``unload`` object code that the
+ interpreter drops.
+ :type: dynamic
+ :category: linking
+
+ :since: 10.2.1
+
+ When the interpreter drops object code it has loaded, it can either
+ *purge* it or *unload* it.
+
+ Purging clears the symbol table entries the object contributed, so
+ that nothing linked afterwards can refer to it, but the object stays
+ in memory. Unloading does the same, and additionally marks the object
+ as no longer needed, so that a later garbage collection may notice
+ that nothing refers to it and reclaim its memory.
+
+ Unloading is the better choice where it works, but its implementation
+ is fragile on a number of platforms, so GHC purges on those instead.
+ By default GHC unloads on Linux other than ARM, and purges everywhere
+ else. This flag opts into the other choice.
+
+ The distinction is moot when the interpreter is dynamically linked,
+ as neither purging nor unloading happens then. GHC warns that the
+ flag is ignored in that case.
+
.. _external-interpreter:
Running the interpreter in a separate process
=====================================
testsuite/tests/ghc-api/T27606/T27606c.hs
=====================================
@@ -24,11 +24,16 @@ import Unsafe.Coerce (unsafeCoerce)
main :: IO ()
main = do
- [libdir] <- getArgs
+ libdir:rest <- getArgs
+ let strat = case rest of
+ [] -> Nothing
+ ["purge"] -> Just UnloadStrategyPurge
+ ["unload"] -> Just UnloadStrategyUnload
+ _ -> error "usage: T27606c <libdir> [purge|unload]"
writeA 1
writeC
runGhc (Just libdir) $ do
- setupSession ["B.hs", "C.hs"]
+ setupSession strat ["B.hs", "C.hs"]
_ <- load LoadAllTargets
setContext [ IIDecl (simpleImportDecl (mkModuleName "Prelude"))
, IIDecl (simpleImportDecl (mkModuleName "A"))
@@ -61,10 +66,10 @@ writeC = writeFile "C.hs" $ unlines
, "c = unsafePerformIO (appendFile \"c.log\" \"x\" >> pure 100)"
]
-setupSession :: [String] -> Ghc ()
-setupSession targets = do
+setupSession :: Maybe UnloadStrategy -> [String] -> Ghc ()
+setupSession strat targets = do
df <- getSessionDynFlags
- _ <- setSessionDynFlags df { ghcLink = LinkInMemory }
+ _ <- setSessionDynFlags df { ghcLink = LinkInMemory, unloadStrategy = strat }
ts <- mapM (\t -> guessTarget t Nothing Nothing) targets
setTargets ts
@@ -74,7 +79,7 @@ setupSession targets = do
compileA :: String -> NameCache -> IO HomeModInfo
compileA libdir nc = runGhc (Just libdir) $ do
getSession >>= \h -> setSession h { hsc_NC = nc }
- setupSession ["A.hs"]
+ setupSession Nothing ["A.hs"]
ok <- load LoadAllTargets
when (failed ok) $ error "compileA: load failed"
hsc <- getSession
=====================================
testsuite/tests/ghc-api/T27606/T27606c_purge.stdout
=====================================
@@ -0,0 +1,5 @@
+102
+102
+104
+104
+1
=====================================
testsuite/tests/ghc-api/T27606/all.T
=====================================
@@ -25,6 +25,15 @@ test('T27606c',
compile_and_run,
['-package ghc'])
+test('T27606c_purge',
+ [extra_run_opts(f'"{config.libdir}" purge'),
+ extra_files(['T27606c.hs', 'B.hs']),
+ req_rts_linker,
+ when(config.ghc_dynamic, skip),
+ when(arch('wasm32'), skip)],
+ multimod_compile_and_run,
+ ['T27606c', '-package ghc'])
+
test('T27606d',
[extra_run_opts(f'"{config.libdir}"'),
req_rts_linker,
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/79f3073b4f9dd3e58959a6643e506c9…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/79f3073b4f9dd3e58959a6643e506c9…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/sjakobi/T27628-rebox-warning] 2 commits: Show binder uniques in reboxing warnings
by Simon Jakobi (@sjakobi) 25 Aug '26
by Simon Jakobi (@sjakobi) 25 Aug '26
25 Aug '26
Simon Jakobi pushed to branch wip/sjakobi/T27628-rebox-warning at Glasgow Haskell Compiler / GHC
Commits:
aecc832c by Simon Jakobi at 2026-08-24T23:05:40+02:00
Show binder uniques in reboxing warnings
The warning printed local binder names inconsistently: System names
(e.g. float-out copies) with their unique, Internal names (e.g. binders
inlined from interface unfoldings) without, and the spec binder never —
it was stored as a bare OccName. Print all of them dump-style, occ plus
unique, so a reported binder can be grepped verbatim in the
-ddump-spec-constr output of the same compilation. Core Tidy renumbers
uniques, so later dumps (-ddump-simpl, STG) still only match by
occurrence-name substring; see Note [Reboxing warning].
With uniques shown, unique-distinct copies of a specialised function no
longer render identically, so the render-based warning merge now applies
only under -dsuppress-uniques, where it remains literally true. The
testsuite passes that flag throughout, keeping the expected outputs
unchanged.
Context: #27628
Assisted-by: Claude Fable 5
- - - - -
7b055846 by Simon Jakobi at 2026-08-24T23:19:40+02:00
Refine docs on finding reboxing specs in later dumps
The GHC build sweep showed the previous text was too pessimistic
about -ddump-simpl: a spec that stays local to its enclosing binding
keeps its name and unique through Core Tidy, so it can be found there
verbatim. Only top-level-floated specs are renamed, and only STG
dumps renumber all uniques.
Assisted-by: Claude Fable 5
- - - - -
3 changed files:
- compiler/GHC/Core/Opt/SpecConstr.hs
- docs/users_guide/using-warnings.rst
- testsuite/tests/simplCore/should_compile/all.T
Changes:
=====================================
compiler/GHC/Core/Opt/SpecConstr.hs
=====================================
@@ -18,7 +18,8 @@ module GHC.Core.Opt.SpecConstr(
import GHC.Prelude
-import GHC.Driver.DynFlags ( DynFlags(..), GeneralFlag( Opt_SpecConstrKeen )
+import GHC.Driver.DynFlags ( DynFlags(..)
+ , GeneralFlag( Opt_SpecConstrKeen, Opt_SuppressUniques )
, WarningFlag( Opt_WarnSpecConstrReboxing )
, gopt, hasPprDebug )
@@ -61,7 +62,7 @@ import GHC.Types.Demand
import GHC.Types.Cpr
import GHC.Types.Unique.Supply
import GHC.Types.Unique.FM
-import GHC.Types.Unique( hasKey )
+import GHC.Types.Unique( hasKey, pprUniqueAlways )
import GHC.Data.Maybe ( fromMaybe, orElse, catMaybes, isJust, isNothing )
import GHC.Data.FastString
@@ -785,6 +786,7 @@ unbox the strict fields, because T is polymorphic!)
specConstrProgram :: ModGuts -> CoreM ModGuts
specConstrProgram guts
= do { env0 <- initScEnv guts
+ ; dflags <- getDynFlags
; us <- getUniqueSupplyM
; let (_usg, binds', warnings) = initUs_ us $
scTopBinds env0 (mg_binds guts)
@@ -795,7 +797,7 @@ specConstrProgram guts
; when (not (null forced_ws)) $ diagnostic WarningWithoutFlag (forced_msg forced_ws)
; mapM_ (diagnostic (WarningWithFlag Opt_WarnSpecConstrReboxing) . rebox_msg)
- (aggregateRebox rebox_ws)
+ (aggregateRebox (gopt Opt_SuppressUniques dflags) rebox_ws)
; return (guts { mg_binds = binds' }) }
@@ -806,11 +808,13 @@ specConstrProgram guts
nest 2 (vcat (map ppr warnings)) $$
(text "If this is expected you might want to increase -fmax-forced-spec-args to force specialization anyway.")
- -- One warning per specialised function (all its patterns listed),
- -- then warnings that would render identically merged too; see
- -- Note [Reboxing warning]
- aggregateRebox :: SpecConstrWarnings -> SpecConstrWarnings
- aggregateRebox = mergeBy same_render . mergeBy same_fn
+ -- One warning per specialised function (all its patterns listed).
+ -- Under -dsuppress-uniques, warnings that would render identically
+ -- are merged too; see Note [Reboxing warning]
+ aggregateRebox :: Bool -> SpecConstrWarnings -> SpecConstrWarnings
+ aggregateRebox uniqs_suppressed
+ | uniqs_suppressed = mergeBy same_render . mergeBy same_fn
+ | otherwise = mergeBy same_fn
where
mergeBy eq ws
= [ SpecReboxed fn ty parent recur (nubBy same_pat (concat patss)) (nub (concat callerss))
@@ -828,8 +832,10 @@ specConstrProgram guts
-- Merge warnings that would render identically: same occurrence
-- name, type, parent, displayed location, recursivity, and
-- patterns (with their spec signatures). The reader could not
- -- tell them apart, so printing both is noise; see
- -- Note [Reboxing warning]. Callers are aggregated, not compared.
+ -- tell them apart, so printing both is noise. Only applied under
+ -- -dsuppress-uniques: with uniques shown, distinct copies render
+ -- distinctly; see Note [Reboxing warning]. Callers are
+ -- aggregated, not compared.
same_render (SpecReboxed fn1 ty1 p1 r1 pats1 _) (SpecReboxed fn2 ty2 p2 r2 pats2 _)
= getOccName fn1 == getOccName fn2 && p1 == p2
&& nameSrcSpan (rebox_loc_name fn1 p1) == nameSrcSpan (rebox_loc_name fn2 p2)
@@ -841,10 +847,16 @@ specConstrProgram guts
same_render _ _ = False
-- Spec signatures of merge candidates are alpha-equivalent copies,
- -- so eqType; a mismatch just leaves two warnings unmerged
- same_pat p1@(ReboxedPat _ _ occ1 sty1) p2@(ReboxedPat _ _ occ2 sty2)
+ -- so eqType; a mismatch just leaves two warnings unmerged. Spec
+ -- names compare as displayed: by occurrence name only when the
+ -- uniques are suppressed
+ same_pat p1@(ReboxedPat _ _ nm1 sty1) p2@(ReboxedPat _ _ nm2 sty2)
= cmpReboxedPat p1 p2 == EQ
- && occ1 == occ2 && sty1 `eqType` sty2
+ && same_disp_name nm1 nm2 && sty1 `eqType` sty2
+
+ same_disp_name n1 n2
+ | uniqs_suppressed = getOccName n1 == getOccName n2
+ | otherwise = n1 == n2
-- Recursivity as displayed: siblings compare by occurrence name,
-- so span-less copies of one mutual group still merge
@@ -862,12 +874,23 @@ specConstrProgram guts
| not (isGoodSrcSpan (nameSrcSpan fn)) = parent
rebox_loc_name fn _ = fn
+ -- Local binders display occ plus unique, as pre-tidy dumps print
+ -- them, so the binder can be grepped in -ddump-spec-constr output
+ -- of the same compilation; see Note [Reboxing warning].
+ -- -dsuppress-uniques hides the unique.
+ pp_name :: Name -> SDoc
+ pp_name n
+ | isExternalName n = ppr n
+ | otherwise = ppr (getOccName n)
+ <> ppUnlessOption sdocSuppressUniques
+ (char '_' <> pprUniqueAlways (nameUnique n))
+
-- See Note [Reboxing warning]
rebox_msg :: SpecConstrWarning -> SDoc
rebox_msg w@(SpecFailForcedArgCount {}) = pprPanic "rebox_msg" (ppr w)
rebox_msg (SpecReboxed fn ty mb_parent recur pats callers)
= vcat [ hang (text "SpecConstr specialised") 2
- (quotes (ppr fn <+> dcolon <+> pp_ty ty))
+ (quotes (pp_name fn <+> dcolon <+> pp_ty ty))
, nest 2 $ vcat $ catMaybes
[ Just (fact "source:" pp_source)
, Just (fact "recursivity:" pp_recur)
@@ -892,8 +915,8 @@ specConstrProgram guts
-- unfolding: iface files record no spans for local binders
pp_source = case (mb_parent, isGoodSrcSpan (nameSrcSpan loc_name)) of
(Nothing, True) -> pp_loc
- (Just p, True) -> quotes (ppr p) <+> text "at" <+> pp_loc
- (Just p, False) -> quotes (ppr p) <> comma <+> pp_no_loc
+ (Just p, True) -> quotes (pp_name p) <+> text "at" <+> pp_loc
+ (Just p, False) -> quotes (pp_name p) <> comma <+> pp_no_loc
(Nothing, False) -> pp_no_loc
where
loc_name = rebox_loc_name fn mb_parent
@@ -906,7 +929,7 @@ specConstrProgram guts
ReboxNonRec False -> text "non-recursive"
ReboxMutualRec sibs
-> text "mutually recursive with"
- <+> pprWithCommas (quotes . ppr) named <> pp_rest
+ <+> pprWithCommas (quotes . pp_name) named <> pp_rest
where
(named, rest) = splitAt 3 (sortBy stableNameCmp sibs)
pp_rest = case length rest of
@@ -916,7 +939,7 @@ specConstrProgram guts
pp_callers = case sortBy stableNameCmp callers of
[] -> Nothing
- cs -> Just (pprWithCommas (quotes . ppr) cs)
+ cs -> Just (pprWithCommas (quotes . pp_name) cs)
pats_label = case pats of
[_] -> "call pattern:"
@@ -926,12 +949,12 @@ specConstrProgram guts
-- "-- reboxes" and "-- as" have equal width, aligning the
-- payloads; a wrapping signature continues under the type's start
- pp_pat (ReboxedPat shapes cons spec_occ spec_ty)
- = hang (hang (ppr fn) 2 (fsep (map pprPatShape shapes))) 2 $ vcat
+ pp_pat (ReboxedPat shapes cons spec_nm spec_ty)
+ = hang (hang (pp_name fn) 2 (fsep (map pprPatShape shapes))) 2 $ vcat
[ text "-- reboxes" <+>
pprWithCommas pp_con (sortBy stableNameCmp cons)
, text "-- as" <+>
- quotes (ppr spec_occ <+> dcolon <+> pp_ty spec_ty) ]
+ quotes (pp_name spec_nm <+> dcolon <+> pp_ty spec_ty) ]
-- Qualify imported constructors: they identify the package to
-- follow up with when the function itself has no location.
@@ -1614,9 +1637,13 @@ that decision bites, without changing which specialisations are made:
the common inlined-library-loop case — Core Tidy drops them.) Both
parts of the signature are approximate:
- - The name is the spec's occurrence name at creation ($s<fn>); tidying
- can prefix the parent and suffix a digit (`$sgo` may end up as
- `go_$sgo1`), so it is a substring of the final name.
+ - The name is the spec binder's at creation, unique included
+ ($s<fn>_sXYZ): it matches -ddump-spec-constr output exactly. A spec
+ that stays local keeps it through Tidy Core (-ddump-simpl); one
+ floated to top level is renamed by Core Tidy (`$sgo` may end up as
+ `go_$sgo1` with a fresh unique), and STG dumps renumber all uniques
+ — there only the occurrence-name part is a substring of the final
+ name.
- The type is the spec binder's type at creation. It normally survives
to the final program — the default pipeline runs no worker/wrapper
@@ -1635,14 +1662,18 @@ that decision bites, without changing which specialisations are made:
only freshens a binder on an in-scope clash, which cannot arise
between sibling top-level RHSs.
- Warnings that would render identically — same name, type, parent,
- definition site, recursivity, and patterns — are merged too: the
- reader could not tell them apart, so printing both is noise. For
- located functions the merged warnings are simplifier-made copies of one
- binding, addressed by a single source-level remedy; span-less loops
- inlined from other modules can in principle merge across different
- origins, but sharing name and type they are almost certainly copies of
- one function.
+ Local binder names in the warning — the function, its parent, the
+ callers, and the spec — display with their uniques, exactly as
+ -ddump-spec-constr prints them, so unique-distinct copies render
+ distinctly and each can be grepped in that dump. Under
+ -dsuppress-uniques the uniques disappear, and warnings that would then
+ render identically — same name, type, parent, definition site,
+ recursivity, and patterns — are merged: the reader could not tell
+ them apart, so printing both is noise. For located functions the
+ merged warnings are simplifier-made copies of one binding, addressed
+ by a single source-level remedy; span-less loops inlined from other
+ modules can in principle merge across different origins, but sharing
+ name and type they are almost certainly copies of one function.
* The warning classifies how the specialised function recurses
("recursivity"), taken from the binding SpecConstr saw: a Rec group of
@@ -2277,7 +2308,7 @@ specialise env recur bind_calls (RI { ri_fn = fn, ri_lam_bndrs = arg_bndrs
rebox_ws = [ SpecReboxed (idName fn) (idType fn) (sc_top_fn env)
recur
[ReboxedPat (patShapes p) (cp_rebox p)
- (getOccName spec_id) (idType spec_id)]
+ (idName spec_id) (idType spec_id)]
(cp_callers p)
| (p, OS { os_id = spec_id }) <- new_pats `zip` new_specs
, not (null (cp_rebox p)) ]
@@ -2845,9 +2876,9 @@ instance Outputable CallPat where
-- | One call pattern as displayed by the reboxing warning: the shapes of
-- the pattern's arguments, the reboxed constructors among them, and the
--- occurrence name and type of the specialisation made for the pattern.
+-- name and type of the specialisation made for the pattern.
-- See Note [Reboxing warning]
-data ReboxedPat = ReboxedPat [PatShape] [Name] OccName Type
+data ReboxedPat = ReboxedPat [PatShape] [Name] Name Type
-- | The constructor skeleton of one call-pattern argument, as displayed
-- by the reboxing warning
=====================================
docs/users_guide/using-warnings.rst
=====================================
@@ -551,26 +551,32 @@ of ``-W(no-)*``.
the name and type the specialisation was created with::
call patterns:
- go (_ : _) (Bin _ _ _)
+ go_s2Xy (_ : _) (Bin _ _ _)
-- reboxes ‘Bin’
- -- as ‘$sgo :: Int -> Int -> Map Int Bool -> Bool’
+ -- as ‘$sgo_s3k1 :: Int -> Int -> Map Int Bool -> Bool’
One warning is emitted per
- specialised function, and warnings that would read identically are
- merged into one. Specialisations on nullary constructors are not
- reported, since "reboxing" a nullary constructor simply references
- its shared static closure.
-
- The ``as`` signature is a guide for finding the specialisation in a
- Core dump (:ghc-flag:`-ddump-simpl`), for example to judge how much
- reboxing survives optimisation. Later passes may rename the binder —
- typically to ``<parent>_$s<function>``, possibly with a digit appended
- — so search for the shown name as a substring; the type normally
- survives unchanged. A specialisation can also be inlined, or merged
- with another one, and then appears in no dump. The calls rewritten to
- use specialisations can be traced with
- :ghc-flag:`-ddump-rule-firings`; the rewrite rules are named
- ``SC:<function><n>``.
+ specialised function. Under :ghc-flag:`-dsuppress-uniques`, warnings
+ that would read identically are merged into one. Specialisations on
+ nullary constructors are not reported, since "reboxing" a nullary
+ constructor simply references its shared static closure.
+
+ Compiler-generated bindings are shown with their unique suffix (as
+ in ``$sgo_s2Xy``), matching the Core dump of the SpecConstr pass
+ (:ghc-flag:`-ddump-spec-constr`) of the same compilation, where the
+ specialisation can therefore be located verbatim — for example to
+ judge how much reboxing survives optimisation. In final Core
+ (:ghc-flag:`-ddump-simpl`) a specialisation that remained local to
+ its enclosing binding still carries the same name, unique included;
+ one floated to the top level is renamed — typically to
+ ``<parent>_$s<function>``, possibly with a digit appended and a
+ fresh unique. In STG dumps (:ghc-flag:`-ddump-stg-final`) all
+ uniques are renumbered. Where the exact name fails, search for the
+ shown name without its unique as a substring; the type normally
+ survives unchanged. A specialisation can also be inlined, or merged with
+ another one, and then appears in no dump. The calls rewritten to use
+ specialisations can be traced with :ghc-flag:`-ddump-rule-firings`;
+ the rewrite rules are named ``SC:<function><n>``.
A ``source:`` reading ``inlined from another module (no source
location)`` concerns a function that reached the module being compiled
=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -613,16 +613,16 @@ test('T27589', [grep_errmsg(r'wombat')], compile, ['-O -ddump-simpl -dno-typeabl
test('T27590', [grep_errmsg(r'wombat')], compile, ['-O -ddump-simpl -dno-typeable-binds -dsuppress-uniques'])
# Tests for -Wspec-constr-reboxing (#27628)
-test('T27628', normal, compile, ['-O2 -Wspec-constr-reboxing'])
-test('T27628b', normal, compile, ['-O2 -Wspec-constr-reboxing'])
-test('T27628c', normal, compile, ['-O2 -Wspec-constr-reboxing'])
-test('T27628d', normal, compile, ['-O2 -Wspec-constr-reboxing'])
-test('T27628e', normal, compile, ['-O2 -Wspec-constr-reboxing'])
-test('T27628f', normal, compile, ['-O2 -Wspec-constr-reboxing'])
+test('T27628', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
+test('T27628b', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
+test('T27628c', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
+test('T27628d', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
+test('T27628e', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
+test('T27628f', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
test('T27628g', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
test('T27628h', [extra_files(['T27628h_M.hs'])], multimod_compile, ['T27628h', '-v0 -O2 -Wspec-constr-reboxing -dsuppress-uniques'])
test('T27628i', [extra_files(['T27628i_M.hs'])], multimod_compile, ['T27628i', '-v0 -O2 -Wspec-constr-reboxing -dsuppress-uniques'])
-test('T27628j', normal, compile, ['-O2 -Wspec-constr-reboxing'])
-test('T27628k', normal, compile, ['-O2 -Wspec-constr-reboxing'])
+test('T27628j', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
+test('T27628k', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
test('T27628l', normal, compile, ['-O2 -Wspec-constr-reboxing -dsuppress-uniques'])
test('T27628m', [extra_files(['T27628h_M.hs'])], multimod_compile, ['T27628m', '-v0 -O2 -Wspec-constr-reboxing -dsuppress-uniques'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/ba495af13cadbef3c88e38188c8aa8…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/ba495af13cadbef3c88e38188c8aa8…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
Zubin pushed new branch wip/unload-strategy at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/unload-strategy
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
Zubin pushed new branch wip/linkable-hashes at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/linkable-hashes
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/27627] 8 commits: Never make an absent filler for a constraint type
by Zubin (@wz1000) 25 Aug '26
by Zubin (@wz1000) 25 Aug '26
25 Aug '26
Zubin pushed to branch wip/27627 at Glasgow Haskell Compiler / GHC
Commits:
fd04b1ba by Simon Peyton Jones at 2026-08-25T12:27:11+05:30
Never make an absent filler for a constraint type
mkAbsentFiller used isTerminatingType to decide, but that is not enough.
Consider
class Eq a => UC a where {}
let u :: UC Int -- UC Int is a "non-terminating type"
u = error "Absent"
let e :: Eq Int -- Eq Int is a "terminating type"
e = $p1UC u
We clearly must not make a filler for `e`, because we speculatively
evaluate it. But speculatively evaluating `e` forces `u`, so we must not
make one for `u` either.
Asking isDictTy instead is not enough either, because it does not catch a
constraint hidden behind an unreduced type family application:
type family F a :: Constraint
type instance F W = TC W
a :: F W => Int -> Int -- (F W) argument is absent
Oops! Entered absent arg Arg: irred
Type: F W
So play safe and use isPredTy: never make an absent filler for any
constraint-kinded type.
Fixes #27627
- - - - -
6646e259 by Zubin Duggal at 2026-08-25T12:27:11+05:30
Add tests for absent fillers at dictionary types
T27627 a unary class whose superclass is a non-unary class
T27627a ...whose superclass is a Constraint-kinded type family
T27627b ...whose superclass is a quantified constraint
T27627c a unary class applied to itself, (UC (UC (TC a)))
T27627e a (forall b. P b) dictionary that loops
- - - - -
5a26c3cd by Zubin Duggal at 2026-08-25T12:27:11+05:30
An abstract TyCon may hide a unary class
A class declared in an hs-boot file is an AbstractTyCon inside the
module loop, and compiling the real declaration may reveal it to be a
UnaryClassTyCon.
- isTerminatingType returned True for such AbstractTyCons
- IfaceToCore set the unary flag to False in the DFunId
So we could end up speculating bottom dictionaries because inside a module
loop we see an UnaryClassTyCon as an AbstractTyCon
Use mayBeUnaryClassTyCon instead of isUnaryClassTyCon, which returns True for an
abstract TyCon.
Fixes #27704
- - - - -
b1b7d5d2 by Zubin Duggal at 2026-08-25T12:27:11+05:30
Specialise: don't replace dead args with absent fillers
specHeader decides an argument is dead by calling isDeadBinder on a binder of
the /optimised RHS/, then applies the filler to the /stable unfolding/
template instead. The two may differ, so the argument can be dead in
the RHS and not in the template.
The specialised function's unfolding then has an absent filler, and any call
site that inlines it evaluates the error thunk.
Dropping dead args in the specialiser is rarely worth it, to quote Simon,
"The later worker/wrapper pass will pick up the dead arg later if it is really dead. Keeps the specialiser simpler."
So instead of trying to check if the arg really is dead in the stable unfolding,
just drop the logic for dropping dead args in the specialiser altogeher.
Fixes #27703
- - - - -
283ef4f1 by Zubin Duggal at 2026-08-25T12:27:11+05:30
CorePrep: don't speculate a call across an hs-boot edge
We take care not to evaluate things that might be bottom, like a
looping dictionary group, but our analysis is defeated by boot files.
We only track recursion within a module, so two dictionaries that
depend on each other across a module loop each look non-recursive, and
we might speculate them.
Any recursion we cannot see must cross an hs-boot edge, so refuse to
speculate calls that cross one.
Fixes #27717
- - - - -
8b18df0a by Zubin Duggal at 2026-08-25T12:27:11+05:30
fixup! Specialise: don't drop a dead arg that the stable unfolding uses
- - - - -
7c8fd45d by Zubin Duggal at 2026-08-25T12:27:11+05:30
fixup! An abstract TyCon may hide a unary class
- - - - -
4caa0c19 by Zubin Duggal at 2026-08-25T12:27:11+05:30
fixup! CorePrep: don't speculate a call across an hs-boot edge
- - - - -
66 changed files:
- + changelog.d/27627
- + changelog.d/27703
- + changelog.d/27704
- + changelog.d/27717
- compiler/GHC/Core.hs
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Opt/WorkWrap/Utils.hs
- compiler/GHC/Core/Predicate.hs
- compiler/GHC/Core/TyCon.hs
- compiler/GHC/Core/Type.hs
- compiler/GHC/CoreToStg/Prep.hs
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Types/Demand.hs
- compiler/GHC/Types/Literal.hs
- + testsuite/tests/core-to-stg/T27627/Callee.hs
- + testsuite/tests/core-to-stg/T27627/Caller.hs
- + testsuite/tests/core-to-stg/T27627/Main.hs
- + testsuite/tests/core-to-stg/T27627/T27627.stdout
- + testsuite/tests/core-to-stg/T27627/all.T
- + testsuite/tests/core-to-stg/T27627a/Callee.hs
- + testsuite/tests/core-to-stg/T27627a/Caller.hs
- + testsuite/tests/core-to-stg/T27627a/Main.hs
- + testsuite/tests/core-to-stg/T27627a/T27627a.stdout
- + testsuite/tests/core-to-stg/T27627a/all.T
- + testsuite/tests/core-to-stg/T27627b/Callee.hs
- + testsuite/tests/core-to-stg/T27627b/Caller.hs
- + testsuite/tests/core-to-stg/T27627b/Main.hs
- + testsuite/tests/core-to-stg/T27627b/T27627b.stdout
- + testsuite/tests/core-to-stg/T27627b/all.T
- + testsuite/tests/core-to-stg/T27627c/Callee.hs
- + testsuite/tests/core-to-stg/T27627c/Caller.hs
- + testsuite/tests/core-to-stg/T27627c/Main.hs
- + testsuite/tests/core-to-stg/T27627c/T27627c.stdout
- + testsuite/tests/core-to-stg/T27627c/all.T
- + testsuite/tests/core-to-stg/T27627e.hs
- + testsuite/tests/core-to-stg/T27627e.stdout
- + testsuite/tests/core-to-stg/T27627f/Callee.hs
- + testsuite/tests/core-to-stg/T27627f/Caller.hs
- + testsuite/tests/core-to-stg/T27627f/Inst.hs
- + testsuite/tests/core-to-stg/T27627f/Main.hs
- + testsuite/tests/core-to-stg/T27627f/T27627f.stdout
- + testsuite/tests/core-to-stg/T27627f/all.T
- + testsuite/tests/core-to-stg/T27704/Callee.hs
- + testsuite/tests/core-to-stg/T27704/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704/Main.hs
- + testsuite/tests/core-to-stg/T27704/Mid.hs
- + testsuite/tests/core-to-stg/T27704/T27704.stdout
- + testsuite/tests/core-to-stg/T27704/all.T
- + testsuite/tests/core-to-stg/T27704a/Callee.hs
- + testsuite/tests/core-to-stg/T27704a/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704a/Main.hs
- + testsuite/tests/core-to-stg/T27704a/Mid.hs
- + testsuite/tests/core-to-stg/T27704a/T27704a.stdout
- + testsuite/tests/core-to-stg/T27704a/all.T
- + testsuite/tests/core-to-stg/T27717/Callee.hs
- + testsuite/tests/core-to-stg/T27717/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27717/Main.hs
- + testsuite/tests/core-to-stg/T27717/Mid.hs
- + testsuite/tests/core-to-stg/T27717/T27717.stdout
- + testsuite/tests/core-to-stg/T27717/Ty.hs
- + testsuite/tests/core-to-stg/T27717/all.T
- testsuite/tests/core-to-stg/all.T
- + testsuite/tests/simplCore/should_run/T27703/Lib.hs
- + testsuite/tests/simplCore/should_run/T27703/Main.hs
- + testsuite/tests/simplCore/should_run/T27703/T27703.stdout
- + testsuite/tests/simplCore/should_run/T27703/all.T
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/c049127a934fb42eb01216775a5487…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/c049127a934fb42eb01216775a5487…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0