From e6c803f702e8b09dfd0073b973b8afcd7071db50 Mon Sep 17 00:00:00 2001
From: Rodrigo Mesquita <rodrigo.m.mesquita@gmail.com>
Date: Wed, 8 Nov 2023 13:54:54 +0000
Subject: [PATCH] darwin: Fix single_module is obsolete warning

In XCode 15's linker, -single_module is the default and otherwise
passing it as a flag results in a warning being raised:

    ld: warning: -single_module is obsolete

This patch fixes this warning by, at configure time, determining whether
the linker supports -single_module (which is likely false for all
non-darwin linkers, and true for darwin linkers in previous versions of
macOS), and using that information at runtime to decide to pass or not
the flag in the invocation.

Fixes #24168
---
 compiler/GHC/Linker/Dynamic.hs                | 11 ++++--
 compiler/GHC/Settings.hs                      |  1 +
 compiler/GHC/Settings/IO.hs                   |  2 ++
 configure.ac                                  |  1 +
 distrib/configure.ac.in                       |  1 +
 hadrian/bindist/Makefile                      |  1 +
 hadrian/bindist/config.mk.in                  |  1 +
 hadrian/cfg/default.host.target.in            |  1 +
 hadrian/cfg/default.target.in                 |  1 +
 hadrian/src/Rules/Generate.hs                 |  2 ++
 m4/fp_prog_ld_single_module.m4                | 30 ++++++++++++++++
 m4/prep_target_file.m4                        |  1 +
 .../src/GHC/Toolchain/Tools/Link.hs           | 34 ++++++++++++++++++-
 13 files changed, 84 insertions(+), 3 deletions(-)
 create mode 100644 m4/fp_prog_ld_single_module.m4

diff --git a/compiler/GHC/Linker/Dynamic.hs b/compiler/GHC/Linker/Dynamic.hs
index 7cfd797d6b06..053a2ca89006 100644
--- a/compiler/GHC/Linker/Dynamic.hs
+++ b/compiler/GHC/Linker/Dynamic.hs
@@ -11,6 +11,7 @@ where
 import GHC.Prelude
 import GHC.Platform
 import GHC.Platform.Ways
+import GHC.Settings (ToolSettings(toolSettings_ldSupportsSingleModule))
 
 import GHC.Driver.Config.Linker
 import GHC.Driver.Session
@@ -152,6 +153,9 @@ linkDynLib logger tmpfs dflags0 unit_env o_files dep_packages
             --   dynamic binding nonsense when referring to symbols from
             --   within the library. The NCG assumes that this option is
             --   specified (on i386, at least).
+            --   In XCode 15, -single_module is the default and passing the
+            --   flag is now obsolete and raises a warning (#24168). We encode
+            --   this information into the toolchain field ...SupportsSingleModule.
             -- -install_name
             --   Mac OS/X stores the path where a dynamic library is (to
             --   be) installed in the library itself.  It's called the
@@ -177,8 +181,11 @@ linkDynLib logger tmpfs dflags0 unit_env o_files dep_packages
                     ]
                  ++ map Option o_files
                  ++ [ Option "-undefined",
-                      Option "dynamic_lookup",
-                      Option "-single_module" ]
+                      Option "dynamic_lookup"
+                    ]
+                 ++ (if toolSettings_ldSupportsSingleModule (toolSettings dflags)
+                        then [ Option "-single_module" ]
+                        else [ ])
                  ++ (if platformArch platform `elem` [ ArchX86_64, ArchAArch64 ]
                      then [ ]
                      else [ Option "-Wl,-read_only_relocs,suppress" ])
diff --git a/compiler/GHC/Settings.hs b/compiler/GHC/Settings.hs
index c1a9d4d788c9..e2fc7338ecdf 100644
--- a/compiler/GHC/Settings.hs
+++ b/compiler/GHC/Settings.hs
@@ -86,6 +86,7 @@ data Settings = Settings
 data ToolSettings = ToolSettings
   { toolSettings_ldSupportsCompactUnwind :: Bool
   , toolSettings_ldSupportsFilelist      :: Bool
+  , toolSettings_ldSupportsSingleModule  :: Bool
   , toolSettings_mergeObjsSupportsResponseFiles :: Bool
   , toolSettings_ldIsGnuLd               :: Bool
   , toolSettings_ccSupportsNoPie         :: Bool
diff --git a/compiler/GHC/Settings/IO.hs b/compiler/GHC/Settings/IO.hs
index e765e2e5cfb8..dedd6fb9af12 100644
--- a/compiler/GHC/Settings/IO.hs
+++ b/compiler/GHC/Settings/IO.hs
@@ -107,6 +107,7 @@ initSettings top_dir = do
 
   ldSupportsCompactUnwind <- getBooleanSetting "ld supports compact unwind"
   ldSupportsFilelist      <- getBooleanSetting "ld supports filelist"
+  ldSupportsSingleModule  <- getBooleanSetting "ld supports single module"
   mergeObjsSupportsResponseFiles <- getBooleanSetting "Merge objects supports response files"
   ldIsGnuLd               <- getBooleanSetting "ld is GNU ld"
   arSupportsDashL         <- getBooleanSetting "ar supports -L"
@@ -171,6 +172,7 @@ initSettings top_dir = do
     , sToolSettings = ToolSettings
       { toolSettings_ldSupportsCompactUnwind = ldSupportsCompactUnwind
       , toolSettings_ldSupportsFilelist      = ldSupportsFilelist
+      , toolSettings_ldSupportsSingleModule  = ldSupportsSingleModule
       , toolSettings_mergeObjsSupportsResponseFiles = mergeObjsSupportsResponseFiles
       , toolSettings_ldIsGnuLd               = ldIsGnuLd
       , toolSettings_ccSupportsNoPie         = gccSupportsNoPie
diff --git a/configure.ac b/configure.ac
index 3a6dfd32da86..36a754d75eea 100644
--- a/configure.ac
+++ b/configure.ac
@@ -452,6 +452,7 @@ CFLAGS="$CFLAGS $GccUseLdOpt"
 FP_PROG_LD_IS_GNU
 FP_PROG_LD_NO_COMPACT_UNWIND
 FP_PROG_LD_FILELIST
+FP_PROG_LD_SINGLE_MODULE
 
 
 dnl ** Which nm to use?
diff --git a/distrib/configure.ac.in b/distrib/configure.ac.in
index ecbfebef5784..8a2016c1c8fa 100644
--- a/distrib/configure.ac.in
+++ b/distrib/configure.ac.in
@@ -136,6 +136,7 @@ CFLAGS="$CFLAGS $GccUseLdOpt"
 FP_PROG_LD_IS_GNU
 FP_PROG_LD_NO_COMPACT_UNWIND
 FP_PROG_LD_FILELIST
+FP_PROG_LD_SINGLE_MODULE
 
 dnl ** which strip to use?
 dnl --------------------------------------------------------------
diff --git a/hadrian/bindist/Makefile b/hadrian/bindist/Makefile
index 31fe94c72b1b..293a0bfdd380 100644
--- a/hadrian/bindist/Makefile
+++ b/hadrian/bindist/Makefile
@@ -104,6 +104,7 @@ lib/settings : config.mk
 	@echo ',("Haskell CPP flags", "$(SettingsHaskellCPPFlags)")' >> $@
 	@echo ',("ld supports compact unwind", "$(LdHasNoCompactUnwind)")' >> $@
 	@echo ',("ld supports filelist", "$(LdHasFilelist)")' >> $@
+	@echo ',("ld supports single module", "$(LdHasSingleModule)")' >> $@
 	@echo ',("ld is GNU ld", "$(LdIsGNULd)")' >> $@
 	@echo ',("Merge objects command", "$(SettingsMergeObjectsCommand)")' >> $@
 	@echo ',("Merge objects flags", "$(SettingsMergeObjectsFlags)")' >> $@
diff --git a/hadrian/bindist/config.mk.in b/hadrian/bindist/config.mk.in
index 4a21d7d0e437..fd158c411948 100644
--- a/hadrian/bindist/config.mk.in
+++ b/hadrian/bindist/config.mk.in
@@ -191,6 +191,7 @@ LdHasBuildId = @LdHasBuildId@
 LdHasFilelist = @LdHasFilelist@
 LdIsGNULd = @LdIsGNULd@
 LdHasNoCompactUnwind = @LdHasNoCompactUnwind@
+LdHasSingleModule = @LdHasSingleModule@
 ArArgs = @ArArgs@
 ArSupportsAtFile = @ArSupportsAtFile@
 ArSupportsDashL  = @ArSupportsDashL@
diff --git a/hadrian/cfg/default.host.target.in b/hadrian/cfg/default.host.target.in
index e38e20cda7a9..eaf01470c887 100644
--- a/hadrian/cfg/default.host.target.in
+++ b/hadrian/cfg/default.host.target.in
@@ -21,6 +21,7 @@ Target
 , ccLinkSupportsNoPie = False
 , ccLinkSupportsCompactUnwind = False
 , ccLinkSupportsFilelist = False
+, ccLinkSupportsSingleModule = True
 , ccLinkIsGnu = False
 }
 
diff --git a/hadrian/cfg/default.target.in b/hadrian/cfg/default.target.in
index 84da17bc160d..a9888a2df4ac 100644
--- a/hadrian/cfg/default.target.in
+++ b/hadrian/cfg/default.target.in
@@ -21,6 +21,7 @@ Target
 , ccLinkSupportsNoPie = @CONF_GCC_SUPPORTS_NO_PIEBool@
 , ccLinkSupportsCompactUnwind = @LdHasNoCompactUnwindBool@
 , ccLinkSupportsFilelist = @LdHasFilelistBool@
+, ccLinkSupportsSingleModule = @LdHasSingleModuleBool@
 , ccLinkIsGnu = @LdIsGNULdBool@
 }
 
diff --git a/hadrian/src/Rules/Generate.hs b/hadrian/src/Rules/Generate.hs
index 5787e2054e81..bf84da2f9de3 100644
--- a/hadrian/src/Rules/Generate.hs
+++ b/hadrian/src/Rules/Generate.hs
@@ -373,6 +373,7 @@ generateSettings = do
         , ("Haskell CPP flags",   queryTarget hsCppFlags)
         , ("ld supports compact unwind", queryTarget linkSupportsCompactUnwind)
         , ("ld supports filelist",       queryTarget linkSupportsFilelist)
+        , ("ld supports single module",       queryTarget linkSupportsSingleModule)
         , ("ld is GNU ld",               queryTarget linkIsGnu)
         , ("Merge objects command", queryTarget mergeObjsPath)
         , ("Merge objects flags", queryTarget mergeObjsFlags)
@@ -431,6 +432,7 @@ generateSettings = do
     hsCppFlags = unwords . prgFlags . hsCppProgram . tgtHsCPreprocessor
     mergeObjsPath  = maybe "" (prgPath . mergeObjsProgram) . tgtMergeObjs
     mergeObjsFlags = maybe "" (unwords . prgFlags . mergeObjsProgram) . tgtMergeObjs
+    linkSupportsSingleModule    = yesNo . ccLinkSupportsSingleModule . tgtCCompilerLink
     linkSupportsFilelist        = yesNo . ccLinkSupportsFilelist . tgtCCompilerLink
     linkSupportsCompactUnwind   = yesNo . ccLinkSupportsCompactUnwind . tgtCCompilerLink
     linkIsGnu                   = yesNo . ccLinkIsGnu . tgtCCompilerLink
diff --git a/m4/fp_prog_ld_single_module.m4 b/m4/fp_prog_ld_single_module.m4
new file mode 100644
index 000000000000..3e124c81ca3e
--- /dev/null
+++ b/m4/fp_prog_ld_single_module.m4
@@ -0,0 +1,30 @@
+# FP_PROG_LD_SINGLE_MODULE
+# ----------------------------
+# Sets the output variable LdHasSingleModule to YES if the darwin ld supports
+# -single_module, or NO otherwise.
+#
+# In XCode 15, -single_module is a default and passing it as a flag raises a
+# warning.
+AC_DEFUN([FP_PROG_LD_SINGLE_MODULE],
+[
+AC_CACHE_CHECK([whether ld supports -single_module], [fp_cv_ld_single_module],
+[
+case $target in
+  *-darwin)
+    echo 'int foo(int x) { return x*x; }' > conftest.c
+    echo 'extern int foo(int); int main() { return foo(5); }' > conftestmain.c
+    "$CC" -c -o conftestmain.o conftestmain.c
+    "$CC" -shared -o conftest.dylib conftest.c
+    if "$CC" -Wl,-single_module -o conftest conftestmain.o conftest.dylib 2>&1 | grep obsolete > /dev/null; then
+      fp_cv_ld_single_module=no
+    else
+      fp_cv_ld_single_module=yes
+    fi
+    rm -rf conftest* ;;
+  *)
+    fp_cv_ld_single_module=no ;;
+esac
+])
+FP_CAPITALIZE_YES_NO(["$fp_cv_ld_single_module"], [LdHasSingleModule])
+AC_SUBST([LdHasSingleModule])
+])# FP_PROG_LD_SINGLE_MODULE
diff --git a/m4/prep_target_file.m4 b/m4/prep_target_file.m4
index 89e773f75127..2cee899c1620 100644
--- a/m4/prep_target_file.m4
+++ b/m4/prep_target_file.m4
@@ -131,6 +131,7 @@ AC_DEFUN([PREP_TARGET_FILE],[
     PREP_BOOLEAN([TargetHasIdentDirective])
     PREP_BOOLEAN([CONF_GCC_SUPPORTS_NO_PIE])
     PREP_BOOLEAN([LdHasFilelist])
+    PREP_BOOLEAN([LdHasSingleModule])
     PREP_BOOLEAN([LdIsGNULd])
     PREP_BOOLEAN([LdHasNoCompactUnwind])
     PREP_BOOLEAN([TargetHasSubsectionsViaSymbols])
diff --git a/utils/ghc-toolchain/src/GHC/Toolchain/Tools/Link.hs b/utils/ghc-toolchain/src/GHC/Toolchain/Tools/Link.hs
index 080427c7d634..3dcf24745884 100644
--- a/utils/ghc-toolchain/src/GHC/Toolchain/Tools/Link.hs
+++ b/utils/ghc-toolchain/src/GHC/Toolchain/Tools/Link.hs
@@ -22,6 +22,7 @@ data CcLink = CcLink { ccLinkProgram :: Program
                      , ccLinkSupportsNoPie :: Bool -- See Note [No PIE when linking] in GHC.Driver.Session
                      , ccLinkSupportsCompactUnwind :: Bool
                      , ccLinkSupportsFilelist :: Bool
+                     , ccLinkSupportsSingleModule :: Bool
                      , ccLinkIsGnu :: Bool
                      }
     deriving (Read, Eq, Ord)
@@ -34,6 +35,7 @@ instance Show CcLink where
     , ", ccLinkSupportsNoPie = " ++ show ccLinkSupportsNoPie
     , ", ccLinkSupportsCompactUnwind = " ++ show ccLinkSupportsCompactUnwind
     , ", ccLinkSupportsFilelist = " ++ show ccLinkSupportsFilelist
+    , ", ccLinkSupportsSingleModule = " ++ show ccLinkSupportsSingleModule
     , ", ccLinkIsGnu = " ++ show ccLinkIsGnu
     , "}"
     ]
@@ -66,12 +68,13 @@ findCcLink target ld progOpt ldOverride archOs cc readelf = checking "for C comp
   ccLinkSupportsNoPie         <- checkSupportsNoPie  cc ccLinkProgram
   ccLinkSupportsCompactUnwind <- checkSupportsCompactUnwind archOs cc ccLinkProgram
   ccLinkSupportsFilelist      <- checkSupportsFilelist cc ccLinkProgram
+  ccLinkSupportsSingleModule  <- checkSupportsSingleModule archOs cc ccLinkProgram
   ccLinkIsGnu                 <- checkLinkIsGnu archOs ccLinkProgram
   checkBfdCopyBug archOs cc readelf ccLinkProgram
   ccLinkProgram <- addPlatformDepLinkFlags archOs cc ccLinkProgram
   let ccLink = CcLink {ccLinkProgram, ccLinkSupportsNoPie,
                        ccLinkSupportsCompactUnwind, ccLinkSupportsFilelist,
-                       ccLinkIsGnu}
+                       ccLinkSupportsSingleModule, ccLinkIsGnu}
   ccLink <- linkRequiresNoFixupChains archOs cc ccLink
   ccLink <- linkRequiresNoWarnDuplicateLibraries archOs cc ccLink
   return ccLink
@@ -164,6 +167,35 @@ checkSupportsFilelist cc ccLink = checking "whether the cc linker understands -f
 
     return (isSuccess exitCode)
 
+-- | Check that the (darwin) linker supports @-single_module@.
+--
+-- In XCode 15, the linker warns when @-single_module@ is passed as the flag
+-- became the default and is now obsolete to pass.
+--
+-- We assume non-darwin linkers don't support this flag.
+checkSupportsSingleModule :: ArchOS -> Cc -> Program -> M Bool
+checkSupportsSingleModule archOs cc link
+  | ArchOS _ OSDarwin <- archOs
+  = checking "whether the darwin linker supports -single_module" $ do
+      withTempDir $ \dir -> do
+        let test_dylib = dir </> "test.dylib"
+            test_c     = dir </> "test.c"
+            testmain_o = dir </> "testmain.o"
+            testmain   = dir </> "testmain"
+
+        -- Main
+        compileC cc testmain_o "extern int foo(int); int main() { return foo(5); }"
+
+        -- Dynamic library
+        writeFile test_c "int foo(int x) { return x*x; }"
+        _ <- runProgram (ccProgram cc) ["-shared", "-o", test_dylib, test_c]
+
+        (_, out, err) <- readProgram link ["-Wl,-single_module", "-o", testmain, test_dylib, testmain_o]
+
+        return $ not $ "obsolete" `isInfixOf` err || "obsolete" `isInfixOf` out
+  | otherwise
+  = return False
+
 -- | Check whether linking works.
 checkLinkWorks :: Cc -> Program -> M ()
 checkLinkWorks cc ccLink = withTempDir $ \dir -> do
-- 
GitLab