From b7b275171ac427812fc2d829d4ea3c94adbb738c Mon Sep 17 00:00:00 2001 From: Moritz Angermann Date: Thu, 19 Feb 2026 08:40:00 +0700 Subject: [PATCH 1/3] deprecate -dynamic-too: replace duplicate compilation with symlinks Since all .o files are now dynamic-capable, -dynamic-too no longer needs to run the backend a second time. Instead: - The -dynamic-too flag is marked as deprecated with a warning. - When -dynamic-too is active, the pipeline creates symlinks .dyn_o -> .o and .dyn_hi -> .hi instead of re-running codegen. This preserves backward compatibility for build systems that expect .dyn_o/.dyn_hi files to exist while eliminating the redundant compilation pass. --- compiler/GHC/Driver/Main.hs | 35 +++++++++++++++++---------------- compiler/GHC/Driver/Pipeline.hs | 32 +++++++++++++++++++----------- compiler/GHC/Driver/Session.hs | 3 ++- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/compiler/GHC/Driver/Main.hs b/compiler/GHC/Driver/Main.hs index 13e6c87fe93e..27407b787487 100644 --- a/compiler/GHC/Driver/Main.hs +++ b/compiler/GHC/Driver/Main.hs @@ -1389,21 +1389,18 @@ hscMaybeWriteIface logger dflags is_simple iface old_iface mod_location = do if (write_interface || force_write_interface) then do - -- FIXME: with -dynamic-too, "change" is only meaningful for the - -- non-dynamic interface, not for the dynamic one. We should have another - -- flag for the dynamic interface. In the meantime: - -- - -- * when we write a single full interface, we check if we are - -- currently writing the dynamic interface due to -dynamic-too, in - -- which case we ignore "change". - -- - -- * when we write two simple interfaces at once because of - -- dynamic-too, we use "change" both for the non-dynamic and the - -- dynamic interfaces. Hopefully both the dynamic and the non-dynamic - -- interfaces stay in sync... - -- let change = old_iface /= Just (mi_iface_hash iface) + -- Create a relative symlink from .dyn_hi -> .hi. + -- Used when -dynamic-too is active (deprecated). + let symlink_dyn_hi = do + let hi_file = ml_hi_file mod_location + dyn_hi_file = ml_dyn_hi_file mod_location + rel_hi = FilePath.takeFileName hi_file + exists <- doesPathExist dyn_hi_file + when exists $ removeFile dyn_hi_file + createFileLink rel_hi dyn_hi_file + let dt = dynamicTooState dflags when (logHasDumpFlag logger Opt_D_dump_if_trace) $ putMsg logger $ @@ -1414,16 +1411,20 @@ hscMaybeWriteIface logger dflags is_simple iface old_iface mod_location = do ] if is_simple - then when change $ do -- FIXME: see 'change' comment above + then when change $ do write_iface dflags iface case dt of DT_Dont -> return () DT_Dyn -> panic "Unexpected DT_Dyn state when writing simple interface" - DT_OK -> write_iface (setDynamicNow dflags) iface + -- -dynamic-too is deprecated: create a symlink .dyn_hi -> .hi + -- instead of writing a second copy of the interface file. + DT_OK -> symlink_dyn_hi else case dt of DT_Dont | change -> write_iface dflags iface - DT_OK | change -> write_iface dflags iface - -- FIXME: see change' comment above + DT_OK | change -> do + write_iface dflags iface + -- -dynamic-too is deprecated: create .dyn_hi -> .hi symlink + symlink_dyn_hi DT_Dyn -> write_iface dflags iface _ -> return () diff --git a/compiler/GHC/Driver/Pipeline.hs b/compiler/GHC/Driver/Pipeline.hs index 1b85504e21d5..b5c032407441 100644 --- a/compiler/GHC/Driver/Pipeline.hs +++ b/compiler/GHC/Driver/Pipeline.hs @@ -787,23 +787,33 @@ hscBackendPipeline pipe_env hsc_env mod_sum result = if backendGeneratesCode (backend (hsc_dflags hsc_env)) then do res <- hscGenBackendPipeline pipe_env hsc_env mod_sum result - -- Only run dynamic-too if the backend generates object files - -- See Note [Writing interface files] - -- If we are writing a simple interface (not . backendWritesFiles), then - -- hscMaybeWriteIface in the regular pipeline will write both the hi and - -- dyn_hi files. This way we can avoid running the pipeline twice and - -- generating a duplicate linkable. - -- We must not run the backend a second time with `dynamicNow` enable because - -- all the work has already been done in the first pipeline. - when (gopt Opt_BuildDynamicToo (hsc_dflags hsc_env) && backendWritesFiles (backend (hsc_dflags hsc_env)) ) $ do - let dflags' = setDynamicNow (hsc_dflags hsc_env) -- set "dynamicNow" - () <$ hscGenBackendPipeline pipe_env (hscSetFlags dflags' hsc_env) mod_sum result + -- -dynamic-too is deprecated: instead of running the backend a second + -- time to produce .dyn_o files, we create a symlink .dyn_o -> .o + -- since all object files are now dynamic-capable. + when (gopt Opt_BuildDynamicToo (hsc_dflags hsc_env) && backendWritesFiles (backend (hsc_dflags hsc_env)) ) $ liftIO $ do + let location = ms_location mod_sum + obj_fn = ml_obj_file location + dyn_obj_fn = ml_dyn_obj_file location + createOrUpdateSymlink obj_fn dyn_obj_fn return res else case result of HscUpdate iface -> return (iface, emptyHomeModInfoLinkable) HscRecomp {} -> (,) <$> liftIO (mkFullIface hsc_env (hscs_partial_iface result) Nothing Nothing NoStubs []) <*> pure emptyHomeModInfoLinkable +-- | Create a symlink from @link@ pointing to @source@, removing any +-- existing file or symlink at @link@ first. Used by the deprecated +-- -dynamic-too path to create .dyn_o -> .o and .dyn_hi -> .hi symlinks. +createOrUpdateSymlink :: FilePath -> FilePath -> IO () +createOrUpdateSymlink source link = do + -- Use the basename of source so the symlink is relative + -- (both files live in the same directory). + let relSource = takeFileName source + -- doesPathExist detects files, directories, and symlinks (including broken ones) + exists <- doesPathExist link + when exists $ removeFile link + createFileLink relSource link + hscGenBackendPipeline :: P m => PipeEnv -> HscEnv diff --git a/compiler/GHC/Driver/Session.hs b/compiler/GHC/Driver/Session.hs index f84fcc447cf6..3f79d6864049 100644 --- a/compiler/GHC/Driver/Session.hs +++ b/compiler/GHC/Driver/Session.hs @@ -1325,8 +1325,9 @@ dynamic_flags_deps = [ , make_ord_flag defGhcFlag "ddump-file-prefix" (hasArg (setDumpPrefixForce . Just . flip (++) ".")) - , make_ord_flag defGhcFlag "dynamic-too" + , make_dep_flag defGhcFlag "dynamic-too" (NoArg (setGeneralFlag Opt_BuildDynamicToo)) + "-dynamic-too is deprecated. All object files are now dynamic-capable; .dyn_o/.dyn_hi are created as symlinks." ------- Keeping temporary files ------------------------------------- -- These can be singular (think ghc -c) or plural (think ghc --make) From 10a402cb635be94d5925eaf15786dc4d98b21cea Mon Sep 17 00:00:00 2001 From: Moritz Angermann Date: Thu, 19 Feb 2026 11:22:23 +0700 Subject: [PATCH 2/3] ignore WayDyn in profile tags, finder, and linker for unified objects Since -dynamic-too is deprecated and .dyn_hi/.dyn_o are now symlinks to .hi/.o, WayDyn must not affect: - profileBuildTag: interface file profile tags must match regardless of whether -dynamic is active, since .dyn_hi is a symlink to .hi - Finder package suffix: look for .hi instead of .dyn_hi in packages - Linker checkNonStdWay: compare ways modulo WayDyn, compute object suffixes without the "dyn" component --- compiler/GHC/Linker/Deps.hs | 17 ++++++++++++----- compiler/GHC/Platform/Profile.hs | 6 +++++- compiler/GHC/Unit/Finder.hs | 9 +++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/compiler/GHC/Linker/Deps.hs b/compiler/GHC/Linker/Deps.hs index 0f36a791b47c..8a68b35b12f4 100644 --- a/compiler/GHC/Linker/Deps.hs +++ b/compiler/GHC/Linker/Deps.hs @@ -244,9 +244,12 @@ checkNonStdWay _opts interp _srcspan -- -dynamic-too) | ldForceDyn _opts = do let target_ways = fullWays $ ldWays _opts - pure $ if target_ways `hasWay` WayDyn + -- Ignore WayDyn: since -dynamic-too is deprecated, .dyn_o files are + -- symlinks to .o files. Use the plain .o suffix. + let non_dyn_ways = removeWay WayDyn target_ways + pure $ if null (waysTag non_dyn_ways) then Nothing - else Just $ waysTag (WayDyn `addWay` target_ways) ++ "_o" + else Just $ waysTag non_dyn_ways ++ "_o" | ExternalInterp {} <- interpInstance interp = return Nothing -- with -fexternal-interpreter we load the .o files, whatever way @@ -257,17 +260,21 @@ checkNonStdWay _opts interp _srcspan -- complain that they are redundant. #if defined(HAVE_INTERNAL_INTERPRETER) checkNonStdWay opts _interp srcspan - | hostFullWays == targetFullWays = return Nothing + -- Ignore WayDyn when comparing host and target ways: since -dynamic-too + -- is deprecated, WayDyn no longer affects object file suffixes. + | removeWay WayDyn hostFullWays == removeWay WayDyn targetFullWays + = return Nothing -- 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) + | ldObjSuffix opts == normalObjectSuffix && not (null targetNonDynWays) = failNonStd opts srcspan | otherwise = return (Just (hostWayTag ++ "o")) where targetFullWays = fullWays (ldWays opts) - hostWayTag = case waysTag hostFullWays of + targetNonDynWays = waysTag (removeWay WayDyn targetFullWays) + hostWayTag = case waysTag (removeWay WayDyn hostFullWays) of "" -> "" tag -> tag ++ "_" diff --git a/compiler/GHC/Platform/Profile.hs b/compiler/GHC/Platform/Profile.hs index d474ff34c8e7..89d43cbd5f70 100644 --- a/compiler/GHC/Platform/Profile.hs +++ b/compiler/GHC/Platform/Profile.hs @@ -50,4 +50,8 @@ profileBuildTag profile | otherwise = wayTag where platform = profilePlatform profile - wayTag = waysBuildTag (profileWays profile) + -- Ignore WayDyn: since -dynamic-too is deprecated and .dyn_hi/.dyn_o are + -- now symlinks to .hi/.o, the interface file profile tag must be consistent + -- regardless of whether -dynamic is active. Only non-dynamic ways (e.g. + -- profiling) contribute to the build tag. + wayTag = waysBuildTag (removeWay WayDyn (profileWays profile)) diff --git a/compiler/GHC/Unit/Finder.hs b/compiler/GHC/Unit/Finder.hs index cb91559cfee3..01ae323b115c 100644 --- a/compiler/GHC/Unit/Finder.hs +++ b/compiler/GHC/Unit/Finder.hs @@ -514,13 +514,18 @@ findPackageModule_ fc fopts mod pkg_conf = do (ppr (moduleUnit mod) <+> ppr (unitId pkg_conf)) modLocationCache fc mod $ let - tag = waysBuildTag (finder_ways fopts) + -- Ignore WayDyn when computing the package interface suffix: + -- since -dynamic-too is deprecated, .dyn_hi files are symlinks to + -- .hi files, so we always look for plain .hi (or e.g. p_hi for + -- profiled builds). + tag = waysBuildTag (removeWay WayDyn (finder_ways fopts)) -- hi-suffix for packages depends on the build tag. package_hisuf | null tag = os "hi" | otherwise = os (tag ++ "_hi") - package_dynhisuf = os $ waysBuildTag (addWay WayDyn (finder_ways fopts)) ++ "_hi" + package_dynhisuf | null tag = os "hi" + | otherwise = os (tag ++ "_hi") mk_hi_loc = mkHiOnlyModLocation fopts package_hisuf package_dynhisuf From a94242ab2c0de9076b2df7a8b82e4741e7112e79 Mon Sep 17 00:00:00 2001 From: Moritz Angermann Date: Thu, 19 Feb 2026 21:33:10 +0700 Subject: [PATCH 3/3] revert Finder suffix change: keep looking for .dyn_hi in packages The previous commit changed findPackageModule_ to look for .hi instead of .dyn_hi when WayDyn is present. This breaks plugin tests because Cabal's --enable-shared --disable-library-vanilla installs ONLY .dyn_hi files (not .hi) for the shared component. The profileBuildTag fix (ignoring WayDyn) is sufficient: the .dyn_hi files contain tag "" (written by our modified GHC), and the consumer also expects tag "" (from profileBuildTag ignoring WayDyn). --- compiler/GHC/Unit/Finder.hs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/compiler/GHC/Unit/Finder.hs b/compiler/GHC/Unit/Finder.hs index 01ae323b115c..cb91559cfee3 100644 --- a/compiler/GHC/Unit/Finder.hs +++ b/compiler/GHC/Unit/Finder.hs @@ -514,18 +514,13 @@ findPackageModule_ fc fopts mod pkg_conf = do (ppr (moduleUnit mod) <+> ppr (unitId pkg_conf)) modLocationCache fc mod $ let - -- Ignore WayDyn when computing the package interface suffix: - -- since -dynamic-too is deprecated, .dyn_hi files are symlinks to - -- .hi files, so we always look for plain .hi (or e.g. p_hi for - -- profiled builds). - tag = waysBuildTag (removeWay WayDyn (finder_ways fopts)) + tag = waysBuildTag (finder_ways fopts) -- hi-suffix for packages depends on the build tag. package_hisuf | null tag = os "hi" | otherwise = os (tag ++ "_hi") - package_dynhisuf | null tag = os "hi" - | otherwise = os (tag ++ "_hi") + package_dynhisuf = os $ waysBuildTag (addWay WayDyn (finder_ways fopts)) ++ "_hi" mk_hi_loc = mkHiOnlyModLocation fopts package_hisuf package_dynhisuf