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
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:
| 1 | +section: linker
|
|
| 2 | +synopsis: Add -funload-strategy to choose between purging and unloading object code
|
|
| 3 | +issues: #27741
|
|
| 4 | +mrs: !16583
|
|
| 5 | + |
|
| 6 | +description: {
|
|
| 7 | + When the interpreter drops object code it has loaded it can either
|
|
| 8 | + purge it, clearing its symbol table entries but leaving it in memory,
|
|
| 9 | + or unload it, additionally allowing a later garbage collection to
|
|
| 10 | + reclaim its memory. Unloading is preferable, but its implementation is
|
|
| 11 | + fragile on a number of platforms, so GHC purges on those instead: it
|
|
| 12 | + unloads on Linux other than ARM, and purges everywhere else.
|
|
| 13 | + ``-funload-strategy=unload`` and ``-funload-strategy=purge`` opt into
|
|
| 14 | + the other choice. The flag has no effect when the interpreter is
|
|
| 15 | + dynamically linked, as neither happens then.
|
|
| 16 | +} |
| ... | ... | @@ -29,7 +29,7 @@ module GHC ( |
| 29 | 29 | -- * Flags and settings
|
| 30 | 30 | DynFlags(..), GeneralFlag(..), Severity(..), Backend, gopt,
|
| 31 | 31 | ncgBackend, llvmBackend, viaCBackend, bytecodeBackend, interpreterBackend, noBackend,
|
| 32 | - GhcMode(..), GhcLink(..),
|
|
| 32 | + GhcMode(..), GhcLink(..), UnloadStrategy(..),
|
|
| 33 | 33 | parseDynamicFlags, parseTargetFiles,
|
| 34 | 34 | getSessionDynFlags,
|
| 35 | 35 | setTopSessionDynFlags,
|
| ... | ... | @@ -728,6 +728,12 @@ setTopSessionDynFlags dflags = do |
| 728 | 728 | |
| 729 | 729 | interp <- liftIO $ initInterpreter dflags tmpfs logger platform finder_cache unit_env interp_opts
|
| 730 | 730 | |
| 731 | + case (hsc_interp hsc_env, unloadStrategy dflags, interp) of
|
|
| 732 | + (Nothing, Just _, Just i) | interpreterDynamic i ->
|
|
| 733 | + liftIO $ logInfo logger $ withPprStyle defaultUserStyle $
|
|
| 734 | + text "warning: -funload-strategy is ignored with a dynamic interpreter"
|
|
| 735 | + _ -> return ()
|
|
| 736 | + |
|
| 731 | 737 | modifySession $ \h -> hscSetFlags dflags
|
| 732 | 738 | h{ hsc_IC = (hsc_IC h){ ic_dflags = dflags }
|
| 733 | 739 | , hsc_interp = hsc_interp h <|> interp
|
| ... | ... | @@ -43,4 +43,5 @@ initInterpOpts dflags = do |
| 43 | 43 | , interpLdConfig = configureLd dflags
|
| 44 | 44 | , interpCcConfig = configureCc dflags
|
| 45 | 45 | , interpExecutableLinkOpts = initExecutableLinkOpts dflags Dynamic
|
| 46 | + , interpUnloadStrategyFlag = unloadStrategy dflags
|
|
| 46 | 47 | } |
| ... | ... | @@ -40,6 +40,7 @@ module GHC.Driver.DynFlags ( |
| 40 | 40 | isPackageDbRef,
|
| 41 | 41 | Option(..), showOpt,
|
| 42 | 42 | DynLibLoader(..),
|
| 43 | + UnloadStrategy(..),
|
|
| 43 | 44 | positionIndependent,
|
| 44 | 45 | optimisationFlags,
|
| 45 | 46 | |
| ... | ... | @@ -308,6 +309,7 @@ data DynFlags = DynFlags { |
| 308 | 309 | outputHi :: Maybe String,
|
| 309 | 310 | dynOutputHi :: Maybe String,
|
| 310 | 311 | dynLibLoader :: DynLibLoader,
|
| 312 | + unloadStrategy :: Maybe UnloadStrategy,
|
|
| 311 | 313 | |
| 312 | 314 | dynamicNow :: !Bool, -- ^ Indicate if we are now generating dynamic output
|
| 313 | 315 | -- because of -dynamic-too. This predicate is
|
| ... | ... | @@ -657,6 +659,7 @@ defaultDynFlags mySettings = |
| 657 | 659 | outputHi = Nothing,
|
| 658 | 660 | dynOutputHi = Nothing,
|
| 659 | 661 | dynLibLoader = SystemDependent,
|
| 662 | + unloadStrategy = Nothing,
|
|
| 660 | 663 | dumpPrefix = "non-module.",
|
| 661 | 664 | dumpPrefixForce = Nothing,
|
| 662 | 665 | ldInputs = [],
|
| ... | ... | @@ -965,6 +968,11 @@ data DynLibLoader |
| 965 | 968 | | SystemDependent
|
| 966 | 969 | deriving Eq
|
| 967 | 970 | |
| 971 | +data UnloadStrategy
|
|
| 972 | + = UnloadStrategyUnload
|
|
| 973 | + | UnloadStrategyPurge
|
|
| 974 | + deriving Eq
|
|
| 975 | + |
|
| 968 | 976 | data RtsOptsEnabled
|
| 969 | 977 | = RtsOptsNone | RtsOptsIgnore | RtsOptsIgnoreAll | RtsOptsSafeOnly
|
| 970 | 978 | | RtsOptsAll
|
| ... | ... | @@ -55,6 +55,7 @@ module GHC.Driver.Session ( |
| 55 | 55 | PackageDBFlag(..), PkgDbRef(..),
|
| 56 | 56 | Option(..), showOpt,
|
| 57 | 57 | DynLibLoader(..),
|
| 58 | + UnloadStrategy(..),
|
|
| 58 | 59 | fFlags, fLangFlags, xFlags,
|
| 59 | 60 | wWarningFlags,
|
| 60 | 61 | makeDynFlagsConsistent,
|
| ... | ... | @@ -725,6 +726,12 @@ parseDynLibLoaderMode f d = |
| 725 | 726 | ("sysdep", "") -> d { dynLibLoader = SystemDependent }
|
| 726 | 727 | _ -> throwGhcException (CmdLineError ("Unknown dynlib loader: " ++ f))
|
| 727 | 728 | |
| 729 | +parseUnloadStrategy :: String -> DynFlags -> DynFlags
|
|
| 730 | +parseUnloadStrategy f d = case f of
|
|
| 731 | + "unload" -> d { unloadStrategy = Just UnloadStrategyUnload }
|
|
| 732 | + "purge" -> d { unloadStrategy = Just UnloadStrategyPurge }
|
|
| 733 | + _ -> throwGhcException (CmdLineError ("Unknown unload strategy: " ++ f))
|
|
| 734 | + |
|
| 728 | 735 | setDumpPrefixForce f d = d { dumpPrefixForce = f}
|
| 729 | 736 | |
| 730 | 737 | -- XXX HACK: Prelude> words "'does not' work" ===> ["'does","not'","work"]
|
| ... | ... | @@ -1892,6 +1899,8 @@ dynamic_flags_deps = [ |
| 1892 | 1899 | (intSuffix (\n d -> d {maxForcedSpecArgs = n}))
|
| 1893 | 1900 | , make_ord_flag defGhciFlag "fghci-hist-size"
|
| 1894 | 1901 | (intSuffix (\n d -> d {ghciHistSize = n}))
|
| 1902 | + , make_ord_flag defFlag "funload-strategy"
|
|
| 1903 | + (hasArg parseUnloadStrategy)
|
|
| 1895 | 1904 | |
| 1896 | 1905 | -- wasm ghci browser mode
|
| 1897 | 1906 | , make_ord_flag defGhciFlag "fghci-browser-host"
|
| ... | ... | @@ -111,6 +111,7 @@ import GHC.Linker.Types |
| 111 | 111 | import Control.Monad
|
| 112 | 112 | |
| 113 | 113 | import Data.Array
|
| 114 | +import Data.Containers.ListUtils (nubOrd)
|
|
| 114 | 115 | import Data.ByteString (ByteString)
|
| 115 | 116 | import qualified Data.Set as Set
|
| 116 | 117 | import Data.Char (isSpace)
|
| ... | ... | @@ -898,8 +899,9 @@ dropModules interp mods pls = do |
| 898 | 899 | }
|
| 899 | 900 | }
|
| 900 | 901 | |
| 901 | - mapM_ (purgeLinkableObjs interp) victim_usages
|
|
| 902 | - when (any (not . null . linkableUsageObjs) victim_usages) $
|
|
| 902 | + let victim_objs = nubOrd (concatMap linkableUsageObjs victim_usages)
|
|
| 903 | + dropLinkableObjs interp victim_objs
|
|
| 904 | + when (not (null victim_objs)) $
|
|
| 903 | 905 | purgeLookupSymbolCache interp
|
| 904 | 906 | |
| 905 | 907 | mapM_ (removeSptEntry interp)
|
| ... | ... | @@ -914,31 +916,14 @@ dropModules interp mods pls = do |
| 914 | 916 | modifyHomePackageBytecodeState (bco_loader_state pls) drop_bytecode_state
|
| 915 | 917 | }
|
| 916 | 918 | |
| 917 | --- | Purge the symbols of a dropped module's objects. We don't unload
|
|
| 918 | --- them, because unloading is not well supported.
|
|
| 919 | --- See Note [Automatically reloading stale linkables]
|
|
| 920 | --- See Note [Unloading vs purging objects] in GHC.Runtime.Interpreter
|
|
| 921 | -purgeLinkableObjs :: Interp -> LinkableUsage -> IO ()
|
|
| 922 | -purgeLinkableObjs interp lnk
|
|
| 923 | - | interpreterDynamic interp = return ()
|
|
| 924 | - | otherwise
|
|
| 925 | - = mapM_ (purgeObj interp) (linkableUsageObjs lnk)
|
|
| 926 | - |
|
| 927 | 919 | -- See Note [Unloading vs purging objects] in GHC.Runtime.Interpreter
|
| 928 | -unloadLinkableObjs :: Interp -> LinkableUsage -> IO ()
|
|
| 929 | -unloadLinkableObjs interp lnk
|
|
| 920 | +dropLinkableObjs :: Interp -> [FilePath] -> IO ()
|
|
| 921 | +dropLinkableObjs interp objs
|
|
| 930 | 922 | | interpreterDynamic interp = return ()
|
| 931 | - -- We don't do any cleanup when linking objects with the
|
|
| 932 | - -- dynamic linker. Doing so introduces extra complexity for
|
|
| 933 | - -- not much benefit.
|
|
| 934 | 923 | | otherwise
|
| 935 | - = mapM_ (unloadObj interp) (linkableUsageObjs lnk)
|
|
| 936 | - -- The components of a BCO linkable may contain
|
|
| 937 | - -- dot-o files (generated from C stubs).
|
|
| 938 | - --
|
|
| 939 | - -- But the BCO parts can be unlinked just by
|
|
| 940 | - -- letting go of them (plus of course depopulating
|
|
| 941 | - -- the symbol table which is done in the main body)
|
|
| 924 | + = case interpUnloadStrategy interp of
|
|
| 925 | + UnloadStrategyUnload -> mapM_ (unloadObj interp) objs
|
|
| 926 | + UnloadStrategyPurge -> mapM_ (purgeObj interp) objs
|
|
| 942 | 927 | |
| 943 | 928 | -- | Load a linkable from a module, and add all the names from the linkable into the
|
| 944 | 929 | -- closure environment.
|
| ... | ... | @@ -1354,12 +1339,13 @@ unload_wkr interp pls@LoaderState{..} = do |
| 1354 | 1339 | -- testsuite/ghci can detect space leaks here.
|
| 1355 | 1340 | |
| 1356 | 1341 | let linkables_to_unload = moduleEnvElts objs_loaded ++ moduleEnvElts bcos_loaded
|
| 1342 | + objs_to_unload = nubOrd (concatMap linkableUsageObjs linkables_to_unload)
|
|
| 1357 | 1343 | |
| 1358 | - mapM_ (unloadLinkableObjs interp) linkables_to_unload
|
|
| 1344 | + dropLinkableObjs interp objs_to_unload
|
|
| 1359 | 1345 | |
| 1360 | 1346 | -- If we unloaded any object files at all, we need to purge the cache
|
| 1361 | 1347 | -- of lookupSymbol results.
|
| 1362 | - when (not (null (filter (not . null . linkableUsageObjs) linkables_to_unload))) $
|
|
| 1348 | + when (not (null objs_to_unload)) $
|
|
| 1363 | 1349 | purgeLookupSymbolCache interp
|
| 1364 | 1350 | |
| 1365 | 1351 | mapM_ (removeSptEntry interp) (concat (moduleEnvElts loaded_spt_keys))
|
| ... | ... | @@ -601,24 +601,33 @@ unloadObj interp path = do |
| 601 | 601 | |
| 602 | 602 | {- Note [Unloading vs purging objects]
|
| 603 | 603 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 604 | -unloadObj removes the object's symbols and frees its memory. The memory
|
|
| 605 | -is only freed at a major GC, once nothing references the object.
|
|
| 606 | -purgeObj removes the symbols and never frees the memory.
|
|
| 607 | - |
|
| 608 | -We only unloadObj in unload, which the driver calls before a
|
|
| 609 | -compilation sweep, when everything is unloaded together. We purgeObj
|
|
| 610 | -when dropModules replaces or removes single modules, because unloading
|
|
| 611 | -is not well supported on many platforms/configurations. Purging is
|
|
| 612 | -enough for correctness: new lookups find the replacement's symbols,
|
|
| 613 | -and values built by the old code and computations still using it keep
|
|
| 614 | -working.
|
|
| 615 | - |
|
| 616 | -With a dynamic interpreter there is nothing to purge. Objects are
|
|
| 617 | -linked into temporary shared libraries and their symbols are found by
|
|
| 618 | -searching the loaded libraries, not in the linker's symbol table.
|
|
| 619 | -Dropping a module flushes the symbol cache, and the replacement is
|
|
| 620 | -loaded as a new library, so lookups find the replacement first and the
|
|
| 621 | -old library stays loaded. This behaves like purging.
|
|
| 604 | +There are two ways to drop objects:
|
|
| 605 | + |
|
| 606 | +- unloadObj removes the object's symbols and eventually the RTS may decide to free its memory.
|
|
| 607 | +- purgeObj removes the symbols and never frees the memory.
|
|
| 608 | + |
|
| 609 | +purgeObj is enough for correctness, but leaks memory. unloadObj can be finnicky on certain
|
|
| 610 | +platforms and/or may not be implemented correctly.
|
|
| 611 | + |
|
| 612 | +We use the unload strategy given by -funload-strategy.
|
|
| 613 | +The strategy we use by default depends on platform calculus, given the
|
|
| 614 | +bugs that apply to each platform. We try to unload where we don't know of
|
|
| 615 | +any bugs affecting correctness:
|
|
| 616 | + |
|
| 617 | +- Linux: mostly unload.
|
|
| 618 | + - i386/x86_64: unload
|
|
| 619 | + - AArch64: We unload, but perhaps we should purge instead because of #24170.
|
|
| 620 | + - 32-bit ARM: purge. Unloading is broken (#21991).
|
|
| 621 | +- FreeBSD: purge. Unloading is not implemented (#25491).
|
|
| 622 | +- Darwin: purge. The process crashes at exit if an unloaded object
|
|
| 623 | + had C finalizers (#27616).
|
|
| 624 | +- Windows: purge. Unloading code that is still in use is fragile
|
|
| 625 | + (#20852).
|
|
| 626 | +- Purge as a fallback for everything else
|
|
| 627 | + |
|
| 628 | +With a dynamic interpreter we never purge, or unload:
|
|
| 629 | +Dynamic objects are linked into temporary shared libraries, and if a library is
|
|
| 630 | +reloaded, then it shadows over the old one.
|
|
| 622 | 631 | -}
|
| 623 | 632 | |
| 624 | 633 | -- | Purge an object's symbols.
|
| ... | ... | @@ -57,6 +57,7 @@ data InterpOpts = InterpOpts |
| 57 | 57 | , interpBrowserPlaywrightBrowserType :: Maybe String
|
| 58 | 58 | , interpBrowserPlaywrightLaunchOpts :: Maybe String
|
| 59 | 59 | , interpExecutableLinkOpts :: ExecutableLinkOpts
|
| 60 | + , interpUnloadStrategyFlag :: Maybe UnloadStrategy
|
|
| 60 | 61 | }
|
| 61 | 62 | |
| 62 | 63 | -- | Initialize code interpreter
|
| ... | ... | @@ -87,6 +88,14 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do |
| 87 | 88 | | otherwise =
|
| 88 | 89 | Just (objectSuf dflags, waysTag (WayDyn `addWay` target_full_ways) ++ "_o")
|
| 89 | 90 | |
| 91 | + -- See Note [Unloading vs purging objects] in GHC.Runtime.Interpreter
|
|
| 92 | + let unload_strategy = case interpUnloadStrategyFlag opts of
|
|
| 93 | + Just s -> s
|
|
| 94 | + Nothing -> case (platformOS platform, platformArch platform) of
|
|
| 95 | + (OSLinux, ArchARM {}) -> UnloadStrategyPurge
|
|
| 96 | + (OSLinux, _) -> UnloadStrategyUnload
|
|
| 97 | + _ -> UnloadStrategyPurge
|
|
| 98 | + |
|
| 90 | 99 | -- see Note [Target code interpreter]
|
| 91 | 100 | if
|
| 92 | 101 | #if !defined(wasm32_HOST_ARCH)
|
| ... | ... | @@ -116,7 +125,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do |
| 116 | 125 | , wasmInterpHsSoSuffix = way_tag ++ dynLibSuffix (interpNameVer opts)
|
| 117 | 126 | , wasmInterpUnitState = ue_homeUnitState unit_env
|
| 118 | 127 | }
|
| 119 | - pure $ Just $ Interp (ExternalInterp $ ExtWasm $ ExtInterpState cfg s) loader lookup_cache fs_cache wasm_obj_suffix
|
|
| 128 | + pure $ Just $ Interp (ExternalInterp $ ExtWasm $ ExtInterpState cfg s) loader lookup_cache fs_cache wasm_obj_suffix unload_strategy
|
|
| 120 | 129 | #endif
|
| 121 | 130 | |
| 122 | 131 | -- JavaScript interpreter
|
| ... | ... | @@ -135,7 +144,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do |
| 135 | 144 | , jsInterpFinderOpts = interpFinderOpts opts
|
| 136 | 145 | , jsInterpFinderCache = finder_cache
|
| 137 | 146 | }
|
| 138 | - return (Just (Interp (ExternalInterp (ExtJS (ExtInterpState cfg s))) loader lookup_cache fs_cache Nothing))
|
|
| 147 | + return (Just (Interp (ExternalInterp (ExtJS (ExtInterpState cfg s))) loader lookup_cache fs_cache Nothing unload_strategy))
|
|
| 139 | 148 | |
| 140 | 149 | -- external interpreter
|
| 141 | 150 | | interpExternal opts
|
| ... | ... | @@ -162,7 +171,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do |
| 162 | 171 | }
|
| 163 | 172 | s <- liftIO $ newMVar InterpPending
|
| 164 | 173 | loader <- liftIO Loader.uninitializedLoader
|
| 165 | - return (Just (Interp (ExternalInterp (ExtIServ (ExtInterpState conf s))) loader lookup_cache fs_cache Nothing))
|
|
| 174 | + return (Just (Interp (ExternalInterp (ExtIServ (ExtInterpState conf s))) loader lookup_cache fs_cache Nothing unload_strategy))
|
|
| 166 | 175 | |
| 167 | 176 | -- Internal interpreter
|
| 168 | 177 | | otherwise
|
| ... | ... | @@ -170,7 +179,7 @@ initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do |
| 170 | 179 | #if defined(HAVE_INTERNAL_INTERPRETER)
|
| 171 | 180 | do
|
| 172 | 181 | loader <- liftIO Loader.uninitializedLoader
|
| 173 | - return (Just (Interp InternalInterp loader lookup_cache fs_cache internal_obj_suffix))
|
|
| 182 | + return (Just (Interp InternalInterp loader lookup_cache fs_cache internal_obj_suffix unload_strategy))
|
|
| 174 | 183 | #else
|
| 175 | 184 | return Nothing
|
| 176 | 185 | #endif |
| ... | ... | @@ -52,6 +52,7 @@ import GHC.Platform |
| 52 | 52 | #if defined(HAVE_INTERNAL_INTERPRETER)
|
| 53 | 53 | import GHC.Platform.Ways
|
| 54 | 54 | #endif
|
| 55 | +import GHC.Driver.DynFlags (UnloadStrategy)
|
|
| 55 | 56 | import GHC.Utils.TmpFs
|
| 56 | 57 | import GHC.Utils.Logger
|
| 57 | 58 | import GHC.Unit.Env
|
| ... | ... | @@ -83,6 +84,8 @@ data Interp = Interp |
| 83 | 84 | , interpObjSuffix :: !(Maybe (String, String))
|
| 84 | 85 | -- ^ @(from, to)@ object suffixes to swap when the interpreter cannot
|
| 85 | 86 | -- load objects built the target's way
|
| 87 | + |
|
| 88 | + , interpUnloadStrategy :: !UnloadStrategy
|
|
| 86 | 89 | }
|
| 87 | 90 | |
| 88 | 91 | data InterpInstance
|
| ... | ... | @@ -3554,6 +3554,32 @@ breakpoints in object-code modules, for example. Only the exports of an |
| 3554 | 3554 | object-code module will be visible in GHCi, rather than all top-level
|
| 3555 | 3555 | bindings as in interpreted modules.
|
| 3556 | 3556 | |
| 3557 | +.. ghc-flag:: -funload-strategy=⟨strategy⟩
|
|
| 3558 | + :shortdesc: Whether to ``purge`` or ``unload`` object code that the
|
|
| 3559 | + interpreter drops.
|
|
| 3560 | + :type: dynamic
|
|
| 3561 | + :category: linking
|
|
| 3562 | + |
|
| 3563 | + :since: 10.2.1
|
|
| 3564 | + |
|
| 3565 | + When the interpreter drops object code it has loaded, it can either
|
|
| 3566 | + *purge* it or *unload* it.
|
|
| 3567 | + |
|
| 3568 | + Purging clears the symbol table entries the object contributed, so
|
|
| 3569 | + that nothing linked afterwards can refer to it, but the object stays
|
|
| 3570 | + in memory. Unloading does the same, and additionally marks the object
|
|
| 3571 | + as no longer needed, so that a later garbage collection may notice
|
|
| 3572 | + that nothing refers to it and reclaim its memory.
|
|
| 3573 | + |
|
| 3574 | + Unloading is the better choice where it works, but its implementation
|
|
| 3575 | + is fragile on a number of platforms, so GHC purges on those instead.
|
|
| 3576 | + By default GHC unloads on Linux other than ARM, and purges everywhere
|
|
| 3577 | + else. This flag opts into the other choice.
|
|
| 3578 | + |
|
| 3579 | + The distinction is moot when the interpreter is dynamically linked,
|
|
| 3580 | + as neither purging nor unloading happens then. GHC warns that the
|
|
| 3581 | + flag is ignored in that case.
|
|
| 3582 | + |
|
| 3557 | 3583 | .. _external-interpreter:
|
| 3558 | 3584 | |
| 3559 | 3585 | Running the interpreter in a separate process
|
| ... | ... | @@ -24,11 +24,16 @@ import Unsafe.Coerce (unsafeCoerce) |
| 24 | 24 | |
| 25 | 25 | main :: IO ()
|
| 26 | 26 | main = do
|
| 27 | - [libdir] <- getArgs
|
|
| 27 | + libdir:rest <- getArgs
|
|
| 28 | + let strat = case rest of
|
|
| 29 | + [] -> Nothing
|
|
| 30 | + ["purge"] -> Just UnloadStrategyPurge
|
|
| 31 | + ["unload"] -> Just UnloadStrategyUnload
|
|
| 32 | + _ -> error "usage: T27606c <libdir> [purge|unload]"
|
|
| 28 | 33 | writeA 1
|
| 29 | 34 | writeC
|
| 30 | 35 | runGhc (Just libdir) $ do
|
| 31 | - setupSession ["B.hs", "C.hs"]
|
|
| 36 | + setupSession strat ["B.hs", "C.hs"]
|
|
| 32 | 37 | _ <- load LoadAllTargets
|
| 33 | 38 | setContext [ IIDecl (simpleImportDecl (mkModuleName "Prelude"))
|
| 34 | 39 | , IIDecl (simpleImportDecl (mkModuleName "A"))
|
| ... | ... | @@ -61,10 +66,10 @@ writeC = writeFile "C.hs" $ unlines |
| 61 | 66 | , "c = unsafePerformIO (appendFile \"c.log\" \"x\" >> pure 100)"
|
| 62 | 67 | ]
|
| 63 | 68 | |
| 64 | -setupSession :: [String] -> Ghc ()
|
|
| 65 | -setupSession targets = do
|
|
| 69 | +setupSession :: Maybe UnloadStrategy -> [String] -> Ghc ()
|
|
| 70 | +setupSession strat targets = do
|
|
| 66 | 71 | df <- getSessionDynFlags
|
| 67 | - _ <- setSessionDynFlags df { ghcLink = LinkInMemory }
|
|
| 72 | + _ <- setSessionDynFlags df { ghcLink = LinkInMemory, unloadStrategy = strat }
|
|
| 68 | 73 | ts <- mapM (\t -> guessTarget t Nothing Nothing) targets
|
| 69 | 74 | setTargets ts
|
| 70 | 75 | |
| ... | ... | @@ -74,7 +79,7 @@ setupSession targets = do |
| 74 | 79 | compileA :: String -> NameCache -> IO HomeModInfo
|
| 75 | 80 | compileA libdir nc = runGhc (Just libdir) $ do
|
| 76 | 81 | getSession >>= \h -> setSession h { hsc_NC = nc }
|
| 77 | - setupSession ["A.hs"]
|
|
| 82 | + setupSession Nothing ["A.hs"]
|
|
| 78 | 83 | ok <- load LoadAllTargets
|
| 79 | 84 | when (failed ok) $ error "compileA: load failed"
|
| 80 | 85 | hsc <- getSession
|
| 1 | +102
|
|
| 2 | +102
|
|
| 3 | +104
|
|
| 4 | +104
|
|
| 5 | +1 |
| ... | ... | @@ -25,6 +25,15 @@ test('T27606c', |
| 25 | 25 | compile_and_run,
|
| 26 | 26 | ['-package ghc'])
|
| 27 | 27 | |
| 28 | +test('T27606c_purge',
|
|
| 29 | + [extra_run_opts(f'"{config.libdir}" purge'),
|
|
| 30 | + extra_files(['T27606c.hs', 'B.hs']),
|
|
| 31 | + req_rts_linker,
|
|
| 32 | + when(config.ghc_dynamic, skip),
|
|
| 33 | + when(arch('wasm32'), skip)],
|
|
| 34 | + multimod_compile_and_run,
|
|
| 35 | + ['T27606c', '-package ghc'])
|
|
| 36 | + |
|
| 28 | 37 | test('T27606d',
|
| 29 | 38 | [extra_run_opts(f'"{config.libdir}"'),
|
| 30 | 39 | req_rts_linker,
|