summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--aclocal.m43
-rw-r--r--configure.ac47
-rw-r--r--docs/users_guide/8.4.1-notes.rst10
-rw-r--r--ghc.mk7
-rw-r--r--rts/ghc.mk24
-rw-r--r--rules/build-package-way.mk31
-rw-r--r--utils/gen-dll/Main.hs510
-rw-r--r--utils/gen-dll/Makefile15
-rw-r--r--utils/gen-dll/gen-dll.cabal.in37
-rw-r--r--utils/gen-dll/ghc.mk19
11 files changed, 669 insertions, 35 deletions
diff --git a/.gitignore b/.gitignore
index 939183c416..245b2a527d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,6 +176,7 @@ _darcs/
/testlog*
/utils/mkUserGuidePart/mkUserGuidePart.cabal
/utils/runghc/runghc.cabal
+/utils/gen-dll/gen-dll.cabal
/extra-gcc-opts
/sdistprep
diff --git a/aclocal.m4 b/aclocal.m4
index 11606c7842..6a732d08ee 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -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"])
diff --git a/configure.ac b/configure.ac
index dd721447c4..500be7e491 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
diff --git a/docs/users_guide/8.4.1-notes.rst b/docs/users_guide/8.4.1-notes.rst
index d3cef24c57..8a6d4048b4 100644
--- a/docs/users_guide/8.4.1-notes.rst
+++ b/docs/users_guide/8.4.1-notes.rst
@@ -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 59fc0d365f..7f532df8ed 100644
--- 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)
diff --git a/rts/ghc.mk b/rts/ghc.mk
index e3de93dc49..ed1b89c0f4 100644
--- a/rts/ghc.mk
+++ b/rts/ghc.mk
@@ -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)
diff --git a/rules/build-package-way.mk b/rules/build-package-way.mk
index 73fc684257..9c101c4a9d 100644
--- a/rules/build-package-way.mk
+++ b/rules/build-package-way.mk
@@ -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
index 0000000000..0383b8e322
--- /dev/null
+++ b/utils/gen-dll/Main.hs
@@ -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
index 0000000000..8b17a7ec76
--- /dev/null
+++ b/utils/gen-dll/Makefile
@@ -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
index 0000000000..4dd7dc5e8c
--- /dev/null
+++ b/utils/gen-dll/gen-dll.cabal.in
@@ -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
index 0000000000..5b4ba3a398
--- /dev/null
+++ b/utils/gen-dll/ghc.mk
@@ -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))