Add gen-dll as replacement for dll-split
authorTamar Christina <tamar@zhox.com>
Tue, 29 Aug 2017 22:09:09 +0000 (23:09 +0100)
committerTamar Christina <tamar@zhox.com>
Tue, 29 Aug 2017 22:09:09 +0000 (23:09 +0100)
Summary:
This tool can be used to generate dll's for any list of object files
given to it. It will then repartition them automatically to fit within
a dll and generates as many dll's as needed to do this. Cyclic dependencies
between these generated dlls are handle automatically so there is no need
to tell it how to partition.

It is also a lot more general than `dll-split` as it is able to split any
package not just `libGHC`. It also uses a trick using GNU style import libraries
to hide the splitting from the rest of the pipeline. Which means come linking time
you don't need to know which dll contains what symbol or how many split dlls were
created.

The import libraries are by default created with libtool. However since libtool is BFD
based it is very slow. So if present and detected by configure the `genlib` tool
from the msys2 project is used. This makes a difference of about ~45 minutes when compiling.

To install `genlib` run `pacman -Sy mingw-w64-$(uname -m)-tools-git`.

More detailed explaination of the process can be found here
https://ghc.haskell.org/trac/ghc/wiki/WindowsDynamicLinking

Test Plan: ./validate

Reviewers: austin, hvr, bgamari, erikd, simonmar

Reviewed By: bgamari

Subscribers: snowleopard, rwbarton, thomie, erikd, #ghc_windows_task_force

GHC Trac Issues: #5987

Differential Revision: https://phabricator.haskell.org/D3883

.gitignore
aclocal.m4
configure.ac
docs/users_guide/8.4.1-notes.rst
ghc.mk
rts/ghc.mk
rules/build-package-way.mk
utils/gen-dll/Main.hs [new file with mode: 0644]
utils/gen-dll/Makefile [new file with mode: 0644]
utils/gen-dll/gen-dll.cabal.in [new file with mode: 0644]
utils/gen-dll/ghc.mk [new file with mode: 0644]

index 939183c..245b2a5 100644 (file)
@@ -176,6 +176,7 @@ _darcs/
 /testlog*
 /utils/mkUserGuidePart/mkUserGuidePart.cabal
 /utils/runghc/runghc.cabal
+/utils/gen-dll/gen-dll.cabal
 
 /extra-gcc-opts
 /sdistprep
index 11606c7..6a732d0 100644 (file)
@@ -1145,6 +1145,9 @@ else
   fi
 fi])
 fp_prog_ar_args=$fp_cv_prog_ar_args
+if test "$HostOS" != "mingw32"; then
+    ArCmd = "$(cygpath -m $ArCmd)"
+fi
 AC_SUBST([ArCmd], ["$fp_prog_ar"])
 AC_SUBST([ArArgs], ["$fp_prog_ar_args"])
 
index dd72144..500be7e 100644 (file)
@@ -391,6 +391,8 @@ then
     OBJDUMP="${mingwbin}objdump.exe"
     fp_prog_ar="${mingwbin}ar.exe"
 
+    AC_PATH_PROG([Genlib],[genlib])
+
     # NB. Download the perl binaries if required
     if ! test -d inplace/perl ||
          test inplace/perl -ot ghc-tarballs/perl/ghc-perl*.tar.gz
@@ -420,13 +422,25 @@ then
     AC_PATH_PROG([OBJDUMP],[objdump])
     AC_PATH_PROG([DllWrap],[dllwrap])
     AC_PATH_PROG([Windres],[windres])
+    AC_PATH_PROG([Genlib],[genlib])
 fi
 
 DllWrapCmd="$DllWrap"
 WindresCmd="$Windres"
 
+HAVE_GENLIB=False
+if test "$HostOS" = "mingw32"
+then
+    if test "$Genlib" != ""; then
+        GenlibCmd="$(cygpath -m $Genlib)"
+        HAVE_GENLIB=True
+    fi
+fi
+
 AC_SUBST([DllWrapCmd])
 AC_SUBST([WindresCmd])
+AC_SUBST([GenlibCmd])
+AC_SUBST([HAVE_GENLIB])
 
 FP_ICONV
 FP_GMP
@@ -587,18 +601,6 @@ esac
 ObjdumpCmd="$OBJDUMP"
 AC_SUBST([ObjdumpCmd])
 
-dnl ** Which ar to use?
-dnl --------------------------------------------------------------
-if test "$HostOS" != "mingw32"; then
-    AC_CHECK_TARGET_TOOL([AR], [ar])
-    if test "$AR" = ":"; then
-        AC_MSG_ERROR([cannot find ar in your PATH])
-    fi
-fi
-ArCmd="$AR"
-fp_prog_ar="$AR"
-AC_SUBST([ArCmd])
-
 dnl ** Which ranlib to use?
 dnl --------------------------------------------------------------
 AC_PROG_RANLIB
@@ -610,9 +612,21 @@ RANLIB="$RanlibCmd"
 
 dnl ** which libtool to use?
 dnl --------------------------------------------------------------
-AC_CHECK_TARGET_TOOL([LIBTOOL], [libtool])
-LibtoolCmd="$LIBTOOL"
-LIBTOOL="$LibtoolCmd"
+# The host normalization on Windows breaks autoconf, it no longer
+# thinks that target == host so it never checks the unqualified
+# tools for Windows. I don't know why we do this whole normalization thing
+# as it just breaks everything.. but for now, just check the unqualified one
+# if on Windows.
+if test "$HostOS" = "mingw32"
+then
+    AC_PATH_PROG([LIBTOOL],[libtool])
+    LibtoolCmd="$(cygpath -m $LIBTOOL)"
+else
+    AC_CHECK_TARGET_TOOL([LIBTOOL], [libtool])
+    LibtoolCmd="$LIBTOOL"
+    LIBTOOL="$LibtoolCmd"
+fi
+AC_SUBST([LibtoolCmd])
 
 # Here is where we re-target which specific version of the LLVM
 # tools we are looking for. In the past, GHC supported a number of
@@ -1249,7 +1263,7 @@ checkMake380() {
 checkMake380 make
 checkMake380 gmake
 
-AC_CONFIG_FILES([mk/config.mk mk/install.mk mk/project.mk compiler/ghc.cabal ghc/ghc-bin.cabal utils/runghc/runghc.cabal libraries/ghc-boot/ghc-boot.cabal libraries/ghc-boot-th/ghc-boot-th.cabal libraries/ghci/ghci.cabal settings docs/users_guide/ghc_config.py docs/index.html libraries/prologue.txt distrib/configure.ac])
+AC_CONFIG_FILES([mk/config.mk mk/install.mk mk/project.mk compiler/ghc.cabal ghc/ghc-bin.cabal utils/runghc/runghc.cabal utils/gen-dll/gen-dll.cabal libraries/ghc-boot/ghc-boot.cabal libraries/ghc-boot-th/ghc-boot-th.cabal libraries/ghci/ghci.cabal settings docs/users_guide/ghc_config.py docs/index.html libraries/prologue.txt distrib/configure.ac])
 AC_OUTPUT
 [
 if test "$print_make_warning" = "true"; then
@@ -1302,6 +1316,7 @@ echo "\
    ranlib       : $RanlibCmd
    windres      : $WindresCmd
    dllwrap      : $DllWrapCmd
+   genlib       : $GenlibCmd
    Happy        : $HappyCmd ($HappyVersion)
    Alex         : $AlexCmd ($AlexVersion)
    Perl         : $PerlCmd
index d3cef24..8a6d404 100644 (file)
@@ -152,3 +152,13 @@ Template Haskell
 
 - Blank strings can now be used as values for environment variables using the
   System.Environment.Blank module. See :ghc-ticket:`12494`
+
+Build system
+~~~~~~~~~~~~
+
+- ``dll-split`` has been removed and replaced with an automatic partitioning utility ``gen-dll``.
+  This utility can transparently split and compile any DLLs that require this. Note that the ``rts`` and
+  ``base`` can not be split at this point because of the mutual recursion between ``base`` and ``rts``.
+  There is currently no explicit dependency between the two in the build system and such there is no way
+  to notify ``base`` that the ``rts`` has been split, or vice versa.
+  (see :ghc-ticket:`5987`).
diff --git a/ghc.mk b/ghc.mk
index 59fc0d3..7f532df 100644 (file)
--- a/ghc.mk
+++ b/ghc.mk
@@ -539,6 +539,9 @@ utils/runghc/dist-install/package-data.mk: $(fixed_pkg_prev)
 iserv/stage2/package-data.mk: $(fixed_pkg_prev)
 iserv/stage2_p/package-data.mk: $(fixed_pkg_prev)
 iserv/stage2_dyn/package-data.mk: $(fixed_pkg_prev)
+ifeq "$(Windows_Host)" "YES"
+utils/gen-dll/dist-install/package-data.mk: $(fixed_pkg_prev)
+endif
 
 # the GHC package doesn't live in libraries/, so we add its dependency manually:
 compiler/stage2/package-data.mk: $(fixed_pkg_prev)
@@ -646,6 +649,9 @@ BUILD_DIRS += includes
 BUILD_DIRS += rts
 BUILD_DIRS += bindisttest
 BUILD_DIRS += utils/genapply
+ifeq "$(Windows_Host)" "YES"
+BUILD_DIRS += utils/gen-dll
+endif
 
 # When cleaning, don't add any library packages to BUILD_DIRS. We include
 # ghc.mk files for all BUILD_DIRS, but they don't exist until after running
@@ -1393,6 +1399,7 @@ distclean : clean
        $(call removeFiles,ghc/ghc-bin.cabal)
        $(call removeFiles,libraries/ghci/ghci.cabal)
        $(call removeFiles,utils/runghc/runghc.cabal)
+       $(call removeFiles,utils/gen-dll/gen-dll.cabal)
        $(call removeFiles,settings)
        $(call removeFiles,docs/users_guide/ug-book.xml)
        $(call removeFiles,docs/users_guide/ug-ent.xml)
index e3de93d..ed1b89c 100644 (file)
@@ -207,11 +207,25 @@ ifneq "$$(findstring dyn, $1)" ""
 ifeq "$$(TargetOS_CPP)" "mingw32"
 $$(rts_$1_LIB) : $$(rts_$1_OBJS) $$(ALL_RTS_DEF_LIBS) rts/dist/libs.depend rts/dist/build/$$(LIBFFI_DLL)
        "$$(RM)" $$(RM_OPTS) $$@
-       "$$(rts_dist_HC)" -this-unit-id rts -shared -dynamic -dynload deploy \
-         -no-auto-link-packages -Lrts/dist/build -l$$(LIBFFI_NAME) \
-         `cat rts/dist/libs.depend` $$(rts_$1_OBJS) $$(ALL_RTS_DEF_LIBS) \
-         $$(rts_dist_$1_GHC_LD_OPTS) \
-         -o $$@
+       # Call out to the shell script to decide how to build the dll.
+       # Making a shared library for the RTS.
+       # $$1  = dir
+       # $$2  = distdir
+       # $$3  = way
+       # $$4  = extra flags
+       # $$5  = extra libraries to link
+       # $$6  = object files to link
+       # $$7  = output filename
+       # $$8  = link command
+       # $$9  = create delay load import lib
+       # $$10 = SxS Name
+       # $$11 = SxS Version
+       $$(gen-dll_INPLACE) link "rts/dist/build" "rts/dist/build" "" "" "$$(ALL_RTS_DEF_LIBS)" "$$(rts_$1_OBJS)" "$$@" "$$(rts_dist_HC) -this-unit-id rts -no-hs-main -shared -dynamic -dynload deploy \
+         -no-auto-link-packages -Lrts/dist/build -l$$(LIBFFI_NAME) \
+         `cat rts/dist/libs.depend | tr '\n' ' '` \
+         $$(rts_dist_$1_GHC_LD_OPTS)" "NO" \
+         "$(rts_INSTALL_INFO)-$(subst dyn,,$(subst _dyn,,$(subst v,,$1)))" "$(ProjectVersion)"
+
 else
 ifneq "$$(UseSystemLibFFI)" "YES"
 LIBFFI_LIBS = -Lrts/dist/build -l$$(LIBFFI_NAME)
index 73fc684..9c101c4 100644 (file)
@@ -134,20 +134,23 @@ endif # "$3" "v"
 $(call profEnd, build-package-way($1,$2,$3))
 endef # build-package-way
 
-# $1 = dir
-# $2 = distdir
-# $3 = way
-# $4 = extra flags
-# $5 = object files to link
-# $6 = output filename
 define build-dll
-       $(call cmd,$1_$2_HC) $($1_$2_$3_ALL_HC_OPTS) $($1_$2_$3_GHC_LD_OPTS) $4 $5 \
-           -shared -dynamic -dynload deploy \
-           $(addprefix -l,$($1_$2_EXTRA_LIBRARIES)) \
-           -no-auto-link-packages \
-           -o $6
-# Now check that the DLL doesn't have too many symbols. See trac #5987.
-       SYMBOLS=`$(OBJDUMP) -p $6 | sed -n "1,/^.Ordinal\/Name Pointer/ D; p; /^$$/ q" | tail -n +2 | wc -l`; echo "Number of symbols in $6: $$SYMBOLS"
-       case `$(OBJDUMP) -p $6 | sed -n "1,/^.Ordinal\/Name Pointer/ D; p; /^$$/ q" | grep "\[ *0\]" | wc -l` in 1) echo DLL $6 OK;; 0) echo No symbols in DLL $6; exit 1;; [0-9]*) echo Too many symbols in DLL $6; $(OBJDUMP) -p $6 | sed -n "1,/^.Ordinal\/Name Pointer/ D; p; /^$$/ q" | tail; exit 1;; *) echo bad DLL $6; exit 1;; esac
+# Call out to the shell script to decide how to build the util dll.
+# 1  = dir
+# 2  = distdir
+# 3  = way
+# 4  = extra flags
+# 5  = extra libraries to link
+# 6  = object files to link
+# 7  = output filename
+# 8  = link command
+# 9  = create delay load import lib
+# 10 = SxS Name
+# 11 = SxS Version
+$(gen-dll_INPLACE) link "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$(call cmd,$1_$2_HC) $(subst -no-hs-main,,$($1_$2_$3_ALL_HC_OPTS) $($1_$2_$3_GHC_LD_OPTS)) \
+           -shared -dynamic -dynload deploy \
+           $(addprefix -l,$($1_$2_EXTRA_LIBRARIES)) \
+           -no-auto-link-packages" "$8" \
+           "$9" "${10}"
 endef
 
diff --git a/utils/gen-dll/Main.hs b/utils/gen-dll/Main.hs
new file mode 100644 (file)
index 0000000..0383b8e
--- /dev/null
@@ -0,0 +1,510 @@
+{-# LANGUAGE Safe #-}
+{-# LANGUAGE CPP #-}
+
+{-
+  gen-dll is a replacement for dll-split which aims to solve a simple problem
+  during the building of stage2. The issue is that the PE image format only has
+  a 16-bit field for the symbol count. This means we can't have more than 2^16-1
+  symbols in a single PE file. See Trac #5987.
+
+  gen-dll solves this issue by partitioning the object files in such a way that
+  a single dll never has more than the allowed amount of symbols. The general
+  workflow of gen-dll is:
+
+  1) use nm -g to dump the symbols defined in each object file, from this dump
+     we collect three key pieces information:
+     a) the object file the symbol belongs to
+     b) the symbol's kind (e.g data or function)
+     c) the symbol name.
+
+  2) If the amount of symbols is lower than the maximum, we're done and we'll
+     just link the entire list of symbols and move on.
+
+     If however we have too many symbols we'll partition the symbols using a
+     per object file granularity. This is because we can't split the content of
+     an object file. An oc belongs to one and only one image file.
+
+  3) Once we have the partitioning, we sub partition these into two groups for
+     each partition:
+     a) data
+     b) function
+
+     The reason for this is that data exports are directly accessed, whereas
+     functions generally go through a trampoline. The trampolines are there to
+     allow for extra functionality such as delay loading (if requested) and to
+     cover for memory model changes due to linking all the object code in on
+     PE image.
+
+     Data is usually accessed direct, so we don't want the trampoline otherwise
+        extern int foo;
+     would point to executable code instead of data.
+
+  4) Once we have everything correctly tagged, the partitions are dumped into a
+     module definition file (def). Each file is named <dll-name>-pt<num>.<ext>
+     which is also the partitioning scheme used for all other files including
+     the resulting dlls.
+
+     From the .def file we use libtool or genlib (when available) to generate
+     an import library. In this case we generate a GNU style import library
+     See Note [BFD import library].
+
+     These import libraries are used to break the cyclic dependencies that may
+     exist between the symbols due to the random partitioning. e.g. A may
+     require B, but A and B can be in different dlls. With the import libraries
+     we promise A that at runtime it'll have B, and vice versa. The Windows
+     runtime linker and loader will take care of breaking this cycle at runtime.
+
+  5) Once we have an import library for each partition, we start linking the
+     final dlls. if e.g. we have 3 dlls, linking dll 1 means passing import
+     libraries 2 and 3 as an argument to the linking of dll 1. This allows it
+     to find all symbols since PE image files can't have dangling symbols.
+
+  6) After creating the dlls the final step is to create one top level import
+     library that is named after the original dll that we were supposed to link.
+
+     To continue the 3 split example. say we were supposed to make libfoo.dll,
+     instead we created libfoo-pt1.dll, libfoo-pt2.dll and libfoo-pt3.dll.
+     Obviously using -lfoo would no longer locate the dlls.
+
+     This is solved by using import libraries again. GNU style import libraries
+     are just plain AR archives where each object file essentially contains
+     only 1 symbol and the dll in which to find this symbol.
+
+     A proper linker processes all the object files in this AR file (lld, ld and
+     ghci do this.) and so while genlib and libtool don't allow you to create
+     import libraries with multiple dll pointers, it is trivial to do.
+
+     We use ar to merge together the import libraries into a large complete one.
+     e.g. libfoo-pt1.dll.a, libfoo-pt2.dll.a and libfoo-pt3.dll.a are merged
+     into libfoo.dll.a. The name isn't coincidental. On Windows you don't link
+     directly against a dll, instead you link against an import library that
+     then tells you how to get to the dll functions.
+
+     In this case by creating a correctly named merged import library we solve
+     the -lfoo problem.
+
+     In the end we end up with libfoo-pt1.dll, libfoo-pt2.dll and libfoo-pt3.dll
+     along with libfoo.dll.a. To the rest of the pipeline the split is
+     completely transparant as -lfoo will just continue to work, and the linker
+     is responsible for populating the IAT (Import Address Table) with the
+     actual dlls we need.
+
+  This scheme is fully scalable and will not need manual maintenance or
+  intervention like dll-split needed. If we ever do switch to compiling using
+  Microsoft compilers, we need to use a custom tool to modify the PE import
+  libraries lib.exe creates. This is slightly more work but for now we can just
+  rely on the GNU import libraries.
+
+  If supported by the stage1 compiler, we'll create dll's which can be used as
+  SxS assemblies, but in order for us to do so, we have to give GHC some extra
+  information such as the stable abi name for the dll and the version of the
+  dll being created. This is purely a deployment thing and does not really
+  affect the workings of this tool.
+-}
+module Main(main) where
+
+import Control.Arrow ((***))
+import Control.Monad (when, forM_)
+import Control.Exception (bracket)
+
+import Data.Char (toLower, isSpace)
+import Data.List (isPrefixOf, nub, sort, (\\))
+import qualified Data.Map as M (Map(), alter, empty, toList)
+
+import System.Environment (getArgs)
+import System.Exit (ExitCode(..), exitWith)
+import System.Directory (findFilesWith, getCurrentDirectory)
+import System.FilePath (takeBaseName, takeDirectory, dropExtension, (<.>)
+                       ,takeFileName)
+import System.IO (hClose, hGetContents, withFile, IOMode(..), hPutStrLn, openFile)
+import System.Process (proc, createProcess_, StdStream (..), CreateProcess(..)
+                      ,waitForProcess)
+
+import Foreign.C.Types (CInt(..), )
+import Foreign.C.String (withCWString, peekCWString, CWString)
+import Foreign.Ptr (Ptr)
+import Foreign.Storable (peek)
+import Foreign.Marshal.Array (peekArray)
+import Foreign.Marshal.Alloc (alloca)
+
+#if defined(i386_HOST_ARCH)
+# define WINDOWS_CCONV stdcall
+#elif defined(x86_64_HOST_ARCH)
+# define WINDOWS_CCONV ccall
+#else
+# error Unknown mingw32 arch
+#endif
+
+-- Setup some standard program names.
+nm :: FilePath
+nm = NM_TOOL_BIN
+
+libexe :: FilePath
+libexe = LIB_TOOL_BIN
+
+genlib :: FilePath
+genlib = GENLIB_TOOL_BIN
+
+ar :: FilePath
+ar = AR_TOOL_BIN
+
+-- Technically speaking the limit for the amount of symbols you can have in a
+-- dll is 2^16-1, however Microsoft's lib.exe for some reason refuses to link
+-- up to this amount. The reason is likely that it adds some extra symbols in
+-- the generated dll, such as dllmain etc. So we reserve some space in the
+-- symbol table to accomodate this. This number is just purely randomly chosen.
+#define SYMBOL_PADDING 10
+
+usage :: IO ()
+usage = putStrLn $ unlines [ " -= Split a dll if required and perform the linking =- "
+                           , ""
+                           , " Usage: gen-dll <action>"
+                           , ""
+                           , " Where <action> is one of:"
+                           , "     link     perform a real link of dll, "
+                           , "              arguments: dir distdir way flags libs objs out link_cmd delay name version"
+                           ]
+
+main :: IO ()
+main = do
+  args <- getArgs
+  if null args
+     then usage
+     else case (head args) of
+             "link" -> let (dir:distdir:way:extra_flags:extra_libs:objs:output:
+                            command:delayed:abi_name:abi_version:_) = tail args
+                       in process_dll_link dir distdir way extra_flags extra_libs
+                                           objs output command delayed abi_name
+                                           abi_version
+             _      -> usage
+
+type Symbol = String
+type Symbols = [Symbol]
+type SymbolType = Char
+data Obj
+  = Obj { objName  :: String
+        , objCount :: Int
+        , objItems :: [(SymbolType, Symbol)]
+        }
+    deriving Show
+type Objs = [Obj]
+
+-- | Create the final DLL by using the provided arguments
+--   This also creates the resulting special import library.
+process_dll_link :: String -- ^ dir
+                 -> String -- ^ distdir
+                 -> String -- ^ way
+                 -> String -- ^ extra flags
+                 -> String -- ^ extra libraries to link
+                 -> String -- ^ object files to link
+                 -> String -- ^ output filename
+                 -> String -- ^ link command
+                 -> String -- ^ create delay load import libs
+                 -> String -- ^ SxS Name
+                 -> String -- ^ SxS version
+                 -> IO ()
+process_dll_link _dir _distdir _way extra_flags extra_libs objs_files output
+                 link_cmd delay_imp sxs_name sxs_version
+  = do let base = dropExtension output
+       -- We need to know how many symbols came from other static archives
+       -- So take the total number of symbols and remove those we know came
+       -- from the object files. Use this to lower the max amount of symbols.
+       --
+       -- This granularity is the best we can do without --print-map like info.
+       raw_exports <- execProg nm Nothing ["-g", "--defined-only", objs_files]
+       putStrLn $ "Processing symbols.."
+
+       let objs    = collectObjs raw_exports
+           num_sym = foldr (\a b -> b + objCount a) 0 objs
+           exports = base <.> "lst"
+
+       putStrLn $ "Number of symbols in object files for " ++ output ++ ": " ++ show num_sym
+
+       _ <- withFile exports WriteMode $ \hExports ->
+             mapM_ (hPutStrLn hExports . unlines . map snd . objItems) objs
+
+#if defined(GEN_SXS)
+       -- Side-by-Side assembly generation flags for GHC. Pass these along so the DLLs
+       -- get the proper manifests generated.
+       let sxs_opts = [ "-fgen-sxs-assembly"
+                      , "-dylib-abi-name"
+                      , show sxs_name
+                      , "-dylib-abi-version"
+                      , show sxs_version
+                      ]
+#else
+       let sxs_opts = []
+#endif
+
+       -- Now check that the DLL doesn't have too many symbols. See trac #5987.
+       case num_sym > dll_max_symbols of
+         False -> do putStrLn $ "DLL " ++ output ++ " OK, no need to split."
+                     let defFile    = base <.> "def"
+                         dll_import = base <.> "dll.a"
+
+                     build_import_lib base (takeFileName output) defFile objs
+
+                     _ <- execProg link_cmd Nothing
+                              $ concat [[objs_files
+                                        ,extra_libs
+                                        ,extra_flags
+                                        ]
+                                       ,sxs_opts
+                                       ,["-fno-shared-implib"
+                                        ,"-optl-Wl,--retain-symbols-file=" ++ exports
+                                        ,"-o"
+                                        ,output
+                                        ]
+                                       ]
+
+                     build_delay_import_lib defFile dll_import delay_imp
+
+         True -> do putStrLn $ "Too many symbols for a single DLL " ++ output
+                    putStrLn "We'll have to split the dll..."
+                    putStrLn $  "OK, we only have space for "
+                             ++ show dll_max_symbols
+                             ++ " symbols from object files when building "
+                             ++ output
+
+                    -- First split the dlls up by whole object files
+                    -- To do this, we iterate over all object file and
+                    -- generate a the partitions based on allowing a
+                    -- maximum of $DLL_MAX_SYMBOLS in one DLL.
+                    let spl_objs   = groupObjs objs
+                        n_spl_objs = length spl_objs
+                        base'      = base ++ "-pt"
+
+                    mapM_ (\(n, _) -> putStrLn $ ">> DLL split at " ++ show n ++ " symbols.") spl_objs
+                    putStrLn $ "OK, based on the amount of symbols we'll split the DLL into " ++ show n_spl_objs ++ " pieces."
+
+                    -- Start off by creating the import libraries to break the
+                    -- mutual dependency chain.
+                    forM_ (zip [(1::Int)..] spl_objs) $ \(i, (n, o)) ->
+                      do putStrLn $ "Processing file " ++ show i   ++ " of "
+                                 ++ show n_spl_objs    ++ " with " ++ show n
+                                 ++ " symbols."
+                         let base_pt = base' ++ show i
+                             file    = base_pt <.> "def"
+                             dll     = base_pt <.> "dll"
+                             lst     = base_pt <.> "lst"
+
+                         _ <- withFile lst WriteMode $ \hExports ->
+                               mapM_ (hPutStrLn hExports . unlines . map snd . objItems) o
+
+                         build_import_lib base_pt (takeFileName dll) file o
+
+                    -- Now create the actual DLLs by using the import libraries
+                    -- to break the mutual recursion.
+                    forM_ (zip [1..] spl_objs) $ \(i, (n, _)) ->
+                      do putStrLn $ "Creating DLL " ++ show i   ++ " of "
+                                 ++ show n_spl_objs    ++ " with " ++ show n
+                                 ++ " symbols."
+                         let base_pt = base' ++ show i
+                             file    = base_pt <.> "def"
+                             dll     = base_pt <.> "dll"
+                             lst     = base_pt <.> "lst"
+                             imp_lib = base_pt <.> "dll.a"
+                             indexes = [1..(length spl_objs)]\\[i]
+                             libs    = map (\ix -> (base' ++ show ix) <.> "dll.a") indexes
+
+                         _ <- execProg link_cmd Nothing
+                                  $ concat [[objs_files
+                                            ,extra_libs
+                                            ,extra_flags
+                                            ,file
+                                            ]
+                                           ,libs
+                                           ,sxs_opts
+                                           ,["-fno-shared-implib"
+                                            ,"-optl-Wl,--retain-symbols-file=" ++ lst
+                                            ,"-o"
+                                            ,dll
+                                            ]
+                                           ]
+
+                         -- build_delay_import_lib file imp_lib delay_imp
+                         putStrLn $ "Created " ++ dll ++ "."
+
+                    -- And finally, merge the individual import libraries into
+                    -- one with the name of the original library we were
+                    -- supposed to make. This means that nothing has to really
+                    -- know how we split up the DLLs, for everything else it'so
+                    -- as if it's still one large assembly.
+                    create_merged_archive base base' (length spl_objs)
+
+
+collectObjs :: [String] -> Objs
+collectObjs = map snd . M.toList . foldr collectObjs' M.empty
+
+collectObjs' :: String -> M.Map String Obj -> M.Map String Obj
+collectObjs' []  m   = m
+collectObjs' str_in m
+  = let clean        = dropWhile isSpace
+        str          = clean str_in
+        (file, rest) = ((takeWhile (/=':') . clean) *** clean) $
+                         break isSpace str
+        (typ , sym ) = (id *** clean) $ break isSpace rest
+        obj          = Obj { objName  = file
+                           , objCount = 1
+                           , objItems = [(head typ, sym)]
+                           }
+        upd value
+          = if length typ /= 1
+               then value
+               else Just $ maybe obj
+                                 (\o -> o { objCount = objCount o + 1
+                                          , objItems = (head typ, sym) : objItems o
+                                          })
+                                 value
+    in M.alter upd file m
+
+-- Split a list of objects into globals and functions
+splitObjs :: Objs -> (Symbols, Symbols)
+splitObjs []     = ([], [])
+splitObjs (y:ys) = group_ (objItems y) (splitObjs ys)
+  where globals = "DdGgrRSsbBC"
+        group_ :: [(Char, Symbol)] -> (Symbols, Symbols) -> (Symbols, Symbols)
+        group_ []     x                             = x
+        group_ (x:xs) (g, f) | fst x `elem` globals = group_ xs (snd x:g, f)
+                             |     otherwise        = group_ xs (g, snd x:f)
+
+-- Determine how to split the objects up.
+groupObjs :: Objs -> [(Int, Objs)]
+groupObjs = binObjs 0 []
+ where binObjs :: Int -> Objs -> Objs -> [(Int, Objs)]
+       binObjs n l []     = [(n, l)]
+       binObjs n l (o:os)
+         = let nx = objCount o
+               n' = n + nx
+           in if n' > dll_max_symbols
+                 then (n, l) : binObjs 0 [] os
+                 else binObjs n' (o:l) os
+
+-- Maximum number of symbols to allow into
+-- one DLL. This is the split factor used.
+dll_max_symbols :: Int
+dll_max_symbols = 65535 - SYMBOL_PADDING -- Some padding for required symbols.
+
+isTrue :: String -> Bool
+isTrue s = let s' = map toLower s
+           in case () of
+               () | s' == "yes" -> True
+                  | s' == "no"  -> False
+                  | otherwise   -> error $ "Expected yes/no but got '" ++ s ++ "'"
+
+foreign import WINDOWS_CCONV unsafe "Shellapi.h CommandLineToArgvW"
+     c_CommandLineToArgvW :: CWString -> Ptr CInt -> IO (Ptr CWString)
+
+foreign import WINDOWS_CCONV unsafe "windows.h LocalFree"
+    localFree :: Ptr a -> IO (Ptr a)
+
+mkArgs :: String -> IO [String]
+mkArgs []  = return []
+mkArgs arg =
+  do withCWString arg $ \c_arg -> do
+       alloca $ \c_size -> do
+         res <- c_CommandLineToArgvW c_arg c_size
+         size <- peek c_size
+         args <- peekArray (fromIntegral size) res
+         values <- mapM peekCWString args
+         _ <- localFree res
+         return values
+
+execProg :: String -> Maybe FilePath -> [String] -> IO [String]
+execProg prog m_stdin args =
+  do args' <- fmap concat $ mapM mkArgs args
+     prog' <- mkArgs prog
+     let full@(c_prog:c_args) = prog' ++ args'
+     -- print the commands we're executing for debugging and transparency
+     putStrLn $ unwords $ full ++ [maybe "" ("< " ++) m_stdin]
+     cwdir <- getCurrentDirectory
+     let cp = (proc c_prog c_args)
+              { std_out = CreatePipe, cwd = Just cwdir }
+     cp' <- case m_stdin of
+              Nothing   -> return cp
+              Just path -> do h <- openFile path ReadMode
+                              return cp{ std_in = UseHandle h}
+     bracket
+       (createProcess_ ("execProg: " ++ prog)  cp')
+       (\(_, Just hout, _, ph) -> do
+         hClose hout
+         code <- waitForProcess ph
+         case std_in cp' of
+           UseHandle h -> hClose h
+           _           -> return ()
+         case code of
+           ExitFailure _ -> exitWith code
+           ExitSuccess   -> return ())
+       (\(_, Just hout, _, _) -> do
+         results <- hGetContents hout
+         length results `seq` return $ lines results)
+
+-- | Mingw-w64's genlib.exe is generally a few order of magnitudes faster than
+-- libtool which is BFD based. So we prefer it, but it's not standard so
+-- support both. We're talking a difference of 45 minutes in build time here.
+execLibTool :: String -> String -> IO [String]
+execLibTool input_def output_lib =
+  do if HAS_GENLIB
+        then execProg genlib Nothing [input_def, "-o", output_lib]
+        else execProg libexe Nothing ["-d", input_def, "-l", output_lib]
+
+-- Builds a delay import lib at the very end which is used to
+-- be able to delay the picking of a DLL on Windows.
+-- This function is called always and decided internally
+-- what to do.
+build_delay_import_lib :: String -- ^ input def file
+                       -> String -- ^ ouput import delayed import lib
+                       -> String -- ^ flag to indicate if delay import
+                                 --   lib should be created
+                       -> IO ()
+build_delay_import_lib input_def output_lib create_delayed
+  = when (isTrue create_delayed) $
+       execLibTool input_def output_lib >> return ()
+
+-- Build a normal import library from the object file definitions
+build_import_lib :: FilePath -> FilePath -> FilePath -> Objs -> IO ()
+build_import_lib base dll_name defFile objs
+  = do -- Create a def file hiding symbols not in original object files
+       -- because --export-all is re-exporting things from static libs
+       -- we need to separate out data from functions. So first create two temporaries
+       let (globals, functions) = splitObjs objs
+
+       -- This split is important because for DATA entries the compiler should not generate
+       -- a trampoline since CONTS DATA is directly referenced and not executed. This is not very
+       -- important for mingw-w64 which would generate both the trampoline and direct referecne
+       -- by default, but for libtool is it and even for mingw-w64 we can trim the output.
+       _ <- withFile defFile WriteMode $ \hDef -> do
+              hPutStrLn hDef $ unlines $ ["LIBRARY " ++ show dll_name
+                                         ,"EXPORTS"
+                                         ]
+              mapM_ (\v -> hPutStrLn hDef $ "    " ++ show v ++ " DATA") globals
+              mapM_ (\v -> hPutStrLn hDef $ "    " ++ show v           ) functions
+
+       let dll_import = base <.> "dll.a"
+       _ <- execLibTool defFile dll_import
+       return ()
+
+-- Do some cleanup and create merged lib.
+-- Because we have no split the DLL we need
+-- to provide a way for the linker to know about the split
+-- DLL. Also the compile was supposed to produce a DLL
+-- foo.dll and import library foo.lib. However we've actually
+-- produced foo-pt1.dll, foo-pt2.dll etc. What we don't want is to have
+-- To somehow convey back to the compiler that we split the DLL in x pieces
+-- as this would require a lot of changes.
+--
+-- Instead we produce a merged import library which contains the union of
+-- all the import libraries produced. This works because import libraries contain
+-- only .idata section which point to the right dlls. So LD will do the right thing.
+-- And this means we don't have to do any special handling for the rest of the pipeline.
+create_merged_archive :: FilePath -> String -> Int -> IO ()
+create_merged_archive base prefix count
+  = do let ar_script = base <.> "mri"
+           imp_lib   = base <.> "dll.a"
+           imp_libs  = map (\i -> prefix ++ show i <.> "dll.a") [1..count]
+       let script = [ "create " ++ imp_lib    ] ++
+                    map ("addlib " ++) imp_libs ++
+                    [ "save", "end" ]
+       writeFile ar_script (unlines script)
+       _ <- execProg ar (Just ar_script) ["-M"]
+       return ()
diff --git a/utils/gen-dll/Makefile b/utils/gen-dll/Makefile
new file mode 100644 (file)
index 0000000..8b17a7e
--- /dev/null
@@ -0,0 +1,15 @@
+# -----------------------------------------------------------------------------
+#
+# (c) 2009 The University of Glasgow
+#
+# This file is part of the GHC build system.
+#
+# To understand how the build system works and how to modify it, see
+#      http://ghc.haskell.org/trac/ghc/wiki/Building/Architecture
+#      http://ghc.haskell.org/trac/ghc/wiki/Building/Modifying
+#
+# -----------------------------------------------------------------------------
+
+dir = utils/gen-dll
+TOP = ../..
+include $(TOP)/mk/sub-makefile.mk
diff --git a/utils/gen-dll/gen-dll.cabal.in b/utils/gen-dll/gen-dll.cabal.in
new file mode 100644 (file)
index 0000000..4dd7dc5
--- /dev/null
@@ -0,0 +1,37 @@
+-- WARNING: gen-dll.cabal is automatically generated from gen-dll.cabal.in by
+-- ./configure.  Make sure you are editing gen-dll.cabal.in, not gen-dll.cabal.
+
+Name: gen-dll
+Version: 0.1
+Copyright: XXX
+License: BSD3
+-- XXX License-File: LICENSE
+Maintainer: ghc-devs@haskell.org
+author: Tamar Christina
+Synopsis: Generate GHC core boot library dlls
+Description:
+    This package is responsible for building DLLs that are delay loaded and
+    create optimized import libraries that can be used to delay load DLLs.
+    Particularly the RTS. This allows us to delay the loading of the DLL while
+    still having const data imports work. It also allows us to work around
+    certain dlltool limitations and the very slow BFD import lib implementation.
+
+build-type: Simple
+cabal-version: >=1.10
+
+Executable gen-dll
+    Default-Language: Haskell2010
+    Main-Is: Main.hs
+    Build-Depends: base       >= 3   && < 5  ,
+                   pretty     >= 1.1 && < 1.2,
+                   process    >= 1.2 && < 1.9,
+                   filepath   >= 1.3 && < 1.5,
+                   directory  >= 1.1 && < 1.4,
+                   containers >= 0.5 && < 0.6
+    Extra-Libraries: Shell32
+    ghc-options: -UGEN_SXS
+                 -DHAS_GENLIB=@HAVE_GENLIB@
+                 -DNM_TOOL_BIN="\"@NmCmd@\""
+                 -DLIB_TOOL_BIN="\"@LibtoolCmd@\""
+                 -DGENLIB_TOOL_BIN="\"@GenlibCmd@\""
+                 -DAR_TOOL_BIN="\"@ArCmd@\""
diff --git a/utils/gen-dll/ghc.mk b/utils/gen-dll/ghc.mk
new file mode 100644 (file)
index 0000000..5b4ba3a
--- /dev/null
@@ -0,0 +1,19 @@
+# -----------------------------------------------------------------------------
+#
+# (c) 2009 The University of Glasgow
+#
+# This file is part of the GHC build system.
+#
+# To understand how the build system works and how to modify it, see
+#      http://ghc.haskell.org/trac/ghc/wiki/Building/Architecture
+#      http://ghc.haskell.org/trac/ghc/wiki/Building/Modifying
+#
+# -----------------------------------------------------------------------------
+
+utils/gen-dll_USES_CABAL           = YES
+utils/gen-dll_PACKAGE              = gen-dll
+utils/gen-dll_dist_PROGNAME        = gen-dll
+utils/gen-dll_dist_INSTALL         = NO
+utils/gen-dll_dist_INSTALL_INPLACE = YES
+
+$(eval $(call build-prog,utils/gen-dll,dist,0))