From c1feba8c006b114bc5ca6e870c3e320389f001ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonin=20D=C3=A9cimo?= Date: Tue, 15 Sep 2026 01:52:29 +0200 Subject: [PATCH 1/4] Add String.index_opt, Filename.remove_extension to Compat --- Compat.ml.in | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/Compat.ml.in b/Compat.ml.in index f7fb496..1a1217e 100644 --- a/Compat.ml.in +++ b/Compat.ml.in @@ -106,8 +106,8 @@ 407:module Stdlib = Pervasives -404:module String = struct -404: include String +405:module String = struct +405: include String 402: 402: let init n f = 402: let s = create n in @@ -131,7 +131,13 @@ 404: end 404: done; 404: sub s 0 !j :: !r -404:end +405: +405: let rec index_rec_opt s lim i c = +405: if i >= lim then None else +405: if unsafe_get s i = c then Some i else index_rec_opt s lim (i + 1) c +405: +405: let index_opt s c = index_rec_opt s (length s) 0 c +405:end 401:module Sys = struct 401: include Sys @@ -141,6 +147,34 @@ 401: let unix = (Sys.os_type = "Unix") 401:end +404:module Filename = struct +404: include Filename +404: +404: let is_dir_sep s i = +404: if Sys.unix then s.[i] = '/' +404: else if Sys.win32 || Sys.cygwin then +404: let c = s.[i] in +404: c = '/' || c = '\\' || c = ':' && i = 1 +404: else false +404: +404: let extension_len name = +404: let rec check i0 i = +404: if i < 0 || is_dir_sep name i then 0 +404: else if name.[i] = '.' then check i0 (i - 1) +404: else String.length name - i0 +404: in +404: let rec search_dot i = +404: if i < 0 || is_dir_sep name i then 0 +404: else if name.[i] = '.' then check i (i - 1) +404: else search_dot (i - 1) +404: in +404: search_dot (String.length name - 1) +404: +404: let remove_extension name = +404: let l = extension_len name in +404: if l = 0 then name else String.sub name 0 (String.length name - l) +404:end + 402:type bytes = string 402:let output_bytes = output_string From 96c5247137651091c9a0bad862de4a752dd784fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonin=20D=C3=A9cimo?= Date: Mon, 14 Sep 2026 18:47:47 +0200 Subject: [PATCH 2/4] Extract lib search dirs to its own helper --- reloc.ml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/reloc.ml b/reloc.ml index 070fe17..f49e4c4 100644 --- a/reloc.ml +++ b/reloc.ml @@ -1363,8 +1363,7 @@ let remove_duplicate_paths paths = loop paths let setup_toolchain () = - let mingw_libs pre = - objdump := pre ^ "objdump"; + let cc_lib_search_dirs () = let rec get_lib_search_dirs install libraries input = match input with | entry :: input -> @@ -1404,12 +1403,16 @@ let setup_toolchain () = |> List.map normalize_path |> remove_duplicate_paths in - search_path := !dirs @ lib_search_dirs; if !verbose >= 1 then begin Printf.printf "lib search dirs (%s):\n" (cc !toolchain); List.iter (Printf.printf " %s\n") lib_search_dirs; flush stdout end; + lib_search_dirs + in + let mingw_libs pre = + objdump := pre ^ "objdump"; + search_path := !dirs @ cc_lib_search_dirs (); default_libs := [ "-lmoldname"; "-lmingwex"; "-lmsvcrt"; "-luser32"; "-lkernel32"; "-ladvapi32"; "-lshell32" ]; From 98bb6a08695c2902617f24ed058322bf6ab132da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonin=20D=C3=A9cimo?= Date: Mon, 14 Sep 2026 18:47:47 +0200 Subject: [PATCH 3/4] Add a clang64 chain for Unix-CLI compilers targeting the MSVC ABI Compilers such as clang --target=x86_64-pc-windows-msvc and zig cc -target x86_64-windows-msvc have a Unix-style command line but target the MSVC ABI and runtime. The new chain drives the linker (lld-link by default, configurable with -use-linker) through the C compiler, expressing the MSVC link semantics with -Wl,/option arguments. The library search path is taken from the LIB environment variable (as with the MSVC chains) and from the compiler's -print-search-dirs. Import libraries are looked up with the MSVC naming rules, and .lib import libraries are produced. -Wl,/option spellings are now also recognized on the flexlink command line and forwarded to the linker instead of being split at the colon. The support objects are compiled with -fms-runtime-lib=dll, matching the /MD of the MSVC chains. --- CHANGES | 6 +++++ Makefile | 19 ++++++++++++++ README.md | 4 +++ cmdline.ml | 13 +++++++--- reloc.ml | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 110 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 257253c..414541e 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,12 @@ Next version (Nicolás Ojeda Bär, review by David Allsopp) - GRP#169: Drop the .llvm_addrsig section from rewritten objects. (Antonin Décimo, review by David Allsopp) +- New `clang64` chain for C compilers with a Unix-style command line targeting + the MSVC ABI and runtime, such as `clang --target=x86_64-pc-windows-msvc` and + `zig cc -target x86_64-windows-msvc`. The linker (lld-link by default, + configurable with `-use-linker`) is driven through the C compiler; + `-Wl,/option`-style arguments are now recognized and forwarded. + (Antonin Décimo) Version 0.44 - GPR#127: Recognise hyphens in option names in the COFF .drectve section. diff --git a/Makefile b/Makefile index 8927576..21b272a 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,10 @@ MIN64CC = $(MINGW64_PREFIX)gcc CYGWIN64_PREFIX = x86_64-pc-cygwin- CYG64CC = $(CYGWIN64_PREFIX)gcc +# A C compiler with a Unix-style CLI targeting the MSVC ABI/CRT +# (e.g. clang --target=x86_64-pc-windows-msvc, zig cc) +CLANG64CC = clang --target=x86_64-pc-windows-msvc + version.ml: Makefile flexdll.opam echo 'let version = "$(VERSION)"' > $@ echo 'let mingw_prefix = "$(MINGW_PREFIX)"' >> $@ @@ -47,6 +51,7 @@ version.ml: Makefile flexdll.opam echo 'let mingw = "$(notdir $(MINCC))"' >> $@ echo 'let mingw64 = "$(notdir $(MIN64CC))"' >> $@ echo 'let gnat = "gcc"' >> $@ + echo 'let clang64 = "$(CLANG64CC)"' >> $@ # Supported tool-chains @@ -151,6 +156,7 @@ support: $(addprefix build_, $(CHAINS)) build_gnat: flexdll_gnat.o flexdll_initer_gnat.o build_msvc: flexdll_msvc.obj flexdll_initer_msvc.obj build_msvc64: flexdll_msvc64.obj flexdll_initer_msvc64.obj +build_clang64: flexdll_clang64.o flexdll_initer_clang64.o build_cygwin64: flexdll_cygwin64.o flexdll_initer_cygwin64.o build_mingw: flexdll_mingw.o flexdll_initer_mingw.o build_mingw64: flexdll_mingw64.o flexdll_initer_mingw64.o @@ -215,6 +221,13 @@ flexdll_msvc.obj: flexdll.c flexdll.h flexdll_msvc64.obj: flexdll.c flexdll.h $(MSVC64_PREFIX) $(MSVCC64) $(MSVC_FLAGS) /DMSVC /DMSVC64 -c /Fo"$@" $< +# -fms-runtime-lib=dll matches the /MD of MSVC_FLAGS (the objects must not +# carry a -defaultlib:libcmt directive) +CLANG64_FLAGS = $(GCC_FLAGS) -fms-runtime-lib=dll -D_CRT_SECURE_NO_DEPRECATE + +flexdll_clang64.o: flexdll.c flexdll.h + $(CLANG64CC) $(CLANG64_FLAGS) -DMSVC -DMSVC64 -c -o $@ $< + flexdll_cygwin64.o: flexdll.c flexdll.h $(CYG64CC) $(GCC_FLAGS) -DCYGWIN -c -o $@ $< @@ -233,6 +246,9 @@ flexdll_initer_msvc.obj: flexdll_initer.c flexdll_initer_msvc64.obj: flexdll_initer.c $(MSVC64_PREFIX) $(MSVCC64) $(MSVC_FLAGS) -c /Fo"$@" $< +flexdll_initer_clang64.o: flexdll_initer.c + $(CLANG64CC) $(CLANG64_FLAGS) -c -o $@ $< + flexdll_initer_cygwin64.o: flexdll_initer.c $(CYG64CC) $(GCC_FLAGS) -c -o $@ $< @@ -263,6 +279,9 @@ demo_mingw64: flexlink.exe flexdll_mingw64.o flexdll_initer_mingw64.o demo_msvc64: flexlink.exe flexdll_msvc64.obj flexdll_initer_msvc64.obj $(MSVC64_PREFIX) $(MAKE) -C test clean demo CHAIN=msvc64 CC="$(MSVCC64)" CFLAGS="$(MSVC_FLAGS)" PLUG2_CFLAGS="/bigobj" O=obj +demo_clang64: flexlink.exe flexdll_clang64.o flexdll_initer_clang64.o + $(MAKE) -C test clean demo CHAIN=clang64 CC="$(CLANG64CC)" CFLAGS="$(CLANG64_FLAGS)" O=o + distclean: clean rm -f Makefile.winsdk diff --git a/README.md b/README.md index e03bcf1..a37205e 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ MSVC: the 32-bit C compiler from Microsoft. MSVC64: the 64-bit C compiler from Microsoft. +CLANG64: a 64-bit C compiler with a Unix-style command line targeting the +MSVC ABI and runtime (e.g. `clang --target=x86_64-pc-windows-msvc` or +`zig cc -target x86_64-windows-msvc`), driving an lld-link-style linker. + CYGWIN64: the 64-bit gcc compiler shipped with Cygwin. MINGW: the 32-bit gcc compiler from the MinGW-w64 project, packaged in diff --git a/cmdline.ml b/cmdline.ml index 35d757c..67e0b53 100644 --- a/cmdline.ml +++ b/cmdline.ml @@ -20,7 +20,7 @@ let use_default_libs = ref true let subsystem = ref "console" let explain = ref false let builtin_linker = ref false -let toolchain : [ `MSVC | `MSVC64 | `MINGW | `MINGW64 | `GNAT | `GNAT64 | `CYGWIN64 | `LIGHTLD ] ref = ref `MSVC +let toolchain : [ `MSVC | `MSVC64 | `CLANG64 | `MINGW | `MINGW64 | `GNAT | `GNAT64 | `CYGWIN64 | `LIGHTLD ] ref = ref `MSVC let use_linker = ref None let use_mt = ref None let save_temps = ref false @@ -103,12 +103,13 @@ let specs = [ "-l", Arg.String (fun s -> files := ("-l" ^ s) :: !files), " Library file"; - "-chain", Arg.Symbol (["msvc";"msvc64";"cygwin64";"mingw";"mingw64";"gnat";"gnat64";"ld"], + "-chain", Arg.Symbol (["msvc";"msvc64";"clang64";"cygwin64";"mingw";"mingw64";"gnat";"gnat64";"ld"], (fun s -> machine := `x86; underscore := true; toolchain := match s with | "msvc" -> `MSVC | "msvc64" -> machine := `x64; underscore := false; `MSVC64 + | "clang64" -> machine := `x64; underscore := false; `CLANG64 | "cygwin64" -> machine := `x64; underscore := false; `CYGWIN64 | "mingw" -> `MINGW | "gnat" -> `GNAT @@ -277,8 +278,12 @@ let parse_cmdline () = String.sub s 0 2 :: String.sub s 2 (String.length s - 2) :: tr rest | s :: rest when String.length s >= 5 && String.sub s 0 5 = "/link" -> "-link" :: String.sub s 5 (String.length s - 5) :: tr rest - (* Convert gcc linker option prefix -Wl, to flexlink linker prefix -link *) - | s :: rest when String.length s >= 6 && String.sub s 0 5 = "-Wl,-" -> + (* Convert gcc linker option prefix -Wl, to flexlink linker prefix -link. + Also accept MSVC-style option spellings after -Wl, (e.g. -Wl,/entry:Sym, + as used with the clang64 chain) which would otherwise be mangled by the + -opt:value splitting below. *) + | s :: rest when String.length s >= 5 && String.sub s 0 4 = "-Wl," + && (s.[4] = '-' || s.[4] = '/') -> let args = String.split_on_char ',' (String.sub s 4 (String.length s - 4)) in diff --git a/reloc.ml b/reloc.ml index f49e4c4..9455c7a 100644 --- a/reloc.ml +++ b/reloc.ml @@ -27,6 +27,7 @@ let default_libs = ref [] let cc = function | `MSVC -> Version.msvc | `MSVC64 -> Version.msvc64 + | `CLANG64 -> Version.clang64 | `CYGWIN64 -> Version.cygwin64 | `MINGW -> Version.mingw | `MINGW64 -> Version.mingw64 @@ -193,7 +194,7 @@ let build_diversion lst = in let lst = match !toolchain with - | `MINGW | `MINGW64 | `GNAT | `GNAT64 | `CYGWIN64 -> lst + | `MINGW | `MINGW64 | `GNAT | `GNAT64 | `CYGWIN64 | `CLANG64 -> lst | `MSVC | `MSVC64 | `LIGHTLD -> (* UTF-16 response files required *) try @@ -342,7 +343,8 @@ let find_file_exn = let base = String.sub fn 2 (String.length fn - 2) in if String.length base > 0 && base.[0] = ':' then [String.sub base 1 (String.length base - 1)], [] - else if !toolchain = `MSVC || !toolchain = `MSVC64 then + else if !toolchain = `MSVC || !toolchain = `MSVC64 + || !toolchain = `CLANG64 then ["lib" ^ base; base], standard_suffixes else ["lib" ^ base], standard_suffixes @@ -631,7 +633,7 @@ let collect_dllexports obj = (List.find_all (fun (cmd,_args) -> String.uppercase_ascii cmd = "EXPORT") dirs) in match !toolchain with - | `MSVC | `MSVC64 -> List.map (drop_underscore obj) l + | `MSVC | `MSVC64 | `CLANG64 -> List.map (drop_underscore obj) l | _ -> l let collect f l = @@ -665,7 +667,7 @@ let parse_dll_exports fn = let dll_exports fn = match !toolchain with | `MSVC | `MSVC64 | `LIGHTLD -> failwith "Creation of import library not supported for this toolchain" - | `GNAT | `GNAT64 | `CYGWIN64 | `MINGW | `MINGW64 -> + | `GNAT | `GNAT64 | `CYGWIN64 | `MINGW | `MINGW64 | `CLANG64 -> let dmp = temp_file "dyndll" ".dmp" in if cmd_verbose (Printf.sprintf "%s -p %s > %s" !objdump fn dmp) <> 0 then failwith "Error while extracting exports from a DLL"; @@ -1188,6 +1190,51 @@ let build_dll link_exe output_file files exts extra_args = !subsystem files descr extra_args + | `CLANG64 -> + (* A Unix-CLI compiler driving an lld-link-style linker: linker options + keep the MSVC spelling and are passed with -Wl,. The driver is + responsible for the CRT libraries and for translating -L into + /libpath:. *) + let gnu_cli_linker = false in + begin match gnu_cli_linker with + | false -> + let implib = + if !implib then + Filename.chop_extension output_file ^ ".lib" + else + temp_file "dyndll_implib" ".lib" + in + let _impexp = + add_temp (Filename.chop_suffix implib ".lib" ^ ".exp") in + let extra_args = + if !custom_crt then + "-Wl,/nodefaultlib:LIBCMT -Wl,/nodefaultlib:MSVCRT " + ^ extra_args + else extra_args + in + (* See the MSVC chain for the purpose of /base (the comment below) + and for why the descriptor object is placed after the files. *) + Printf.sprintf + "%s -fuse-ld=%s %s%s%s-Wl,/implib:%s -Wl,/base:%s -Wl,/subsystem:%s -L. %s -o %s %s %s %s" + (cc !toolchain) + (* The default linker of MSVC-target compiler drivers is link.exe, + which is unlikely to be available (e.g. when cross-compiling) *) + (Option.value !Cmdline.use_linker ~default:"lld") + (if link_exe = `EXE then "" else "-shared ") + (if main_pgm then "" + else "-Wl,/export:symtbl -Wl,/export:reloctbl ") + (if main_pgm then "" else if !noentry then "-Wl,/noentry " + else "-Wl,/entry:FlexDLLiniter ") + (Filename.quote implib) + !base_addr + !subsystem + (mk_dirs_opt "-L") + (Filename.quote output_file) + files + descr + extra_args + | true -> assert false + end | `CYGWIN64 -> let def_file = if main_pgm then "" @@ -1450,6 +1497,18 @@ let setup_toolchain () = parse_libpath (try Sys.getenv "LIB" with Not_found -> ""); if not !custom_crt then default_libs := ["msvcrt.lib"] + | `CLANG64 -> + (* The WinSDK/MSVC library directories come either from the LIB + environment variable (as with the MSVC chains) or from the compiler + itself when it knows them (-winsysroot, -vctoolsdir, ... passed as part + of the compiler command). *) + objdump := "llvm-objdump"; + search_path := + !dirs + @ parse_libpath (try Sys.getenv "LIB" with Not_found -> "") + @ cc_lib_search_dirs (); + if not !custom_crt then + default_libs := ["msvcrt.lib"] | `MINGW -> mingw_libs Version.mingw_prefix | `MINGW64 -> @@ -1519,6 +1578,14 @@ let compile_if_needed file = (Filename.quote tmp_obj) (mk_dirs_opt "-I") (Filename.quote file) + | `CLANG64 -> + (* -fms-runtime-lib=dll matches the /MD of the MSVC chains *) + Printf.sprintf + "%s -c -fms-runtime-lib=dll -o %s %s %s" + (cc !toolchain) + (Filename.quote tmp_obj) + (mk_dirs_opt "-I") + (Filename.quote file) | `LIGHTLD -> failwith "Compilation of C code is not supported for this toolchain" in @@ -1560,6 +1627,7 @@ let all_files () = let tc = match !toolchain with | `MSVC -> "msvc.obj" | `MSVC64 -> "msvc64.obj" + | `CLANG64 -> "clang64.o" | `CYGWIN64 -> "cygwin64.o" | `MINGW64 -> "mingw64.o" | `GNAT -> "gnat.o" From 4bb8acd0f39cde258c60dc7a2db26801da58f232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonin=20D=C3=A9cimo?= Date: Mon, 14 Sep 2026 18:47:47 +0200 Subject: [PATCH 4/4] clang64: support GNU-CLI linkers via -use-linker By default the clang64 chain drives an lld-link-style linker through the C compiler. This cannot work for linkers with a GNU-style command line: for MSVC targets the compiler driver always constructs an lld-link-style command, whatever linker binary is selected. When -use-linker names a GNU-CLI linker (anything that is not lld, lld-link or link, e.g. "ld.lld -m i386pep" or a binutils ld targeting pep), invoke it directly, as the "ld" chain does, with GNU option spellings: --shared, -e, --image-base, --subsystem, --out-implib, a .def file for the exports, and the flexlink search path passed with -L. The CRT import libraries are spelled out inside --start-group/--end-group since there is no driver to add them and GNU binutils ld does not process the DEFAULTLIB directives embedded in the objects; vcruntime, ucrt and kernel32 are added to the chain's default libraries for this purpose. For the driver path, -use-linker values containing a directory separator are now passed with --ld-path instead of -fuse-ld, which only accepts linker flavors. Validated with ld.lld -m i386pep (demo runs under wine). GNU binutils ld currently mis-selects MSVC CRT archive members (msvcrt_md_kernel32 flavor) and cannot link executables against the MSVC CRT; this is a binutils limitation. --- CHANGES | 10 +++-- README.md | 5 ++- reloc.ml | 114 ++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 113 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 414541e..684ea1e 100644 --- a/CHANGES +++ b/CHANGES @@ -6,9 +6,13 @@ Next version (Antonin Décimo, review by David Allsopp) - New `clang64` chain for C compilers with a Unix-style command line targeting the MSVC ABI and runtime, such as `clang --target=x86_64-pc-windows-msvc` and - `zig cc -target x86_64-windows-msvc`. The linker (lld-link by default, - configurable with `-use-linker`) is driven through the C compiler; - `-Wl,/option`-style arguments are now recognized and forwarded. + `zig cc -target x86_64-windows-msvc`. By default the link is driven through + the C compiler with an lld-link-style linker (lld), and `-Wl,/option`-style + arguments are now recognized and forwarded. A linker with a GNU-style command + line given with `-use-linker` (e.g. `-use-linker "ld.lld -m i386pep"`) is + instead invoked directly with GNU option spellings. Note that GNU binutils ld + is currently unable to link the MSVC CRT (it selects the wrong members from + msvcrt.lib). (Antonin Décimo) Version 0.44 diff --git a/README.md b/README.md index a37205e..1b16272 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,10 @@ MSVC64: the 64-bit C compiler from Microsoft. CLANG64: a 64-bit C compiler with a Unix-style command line targeting the MSVC ABI and runtime (e.g. `clang --target=x86_64-pc-windows-msvc` or -`zig cc -target x86_64-windows-msvc`), driving an lld-link-style linker. +`zig cc -target x86_64-windows-msvc`). By default the link is driven +through the C compiler with an lld-link-style linker; a linker with a +GNU-style command line passed with `-use-linker` (e.g. +`-use-linker "ld.lld -m i386pep"`) is invoked directly instead. CYGWIN64: the 64-bit gcc compiler shipped with Cygwin. diff --git a/reloc.ml b/reloc.ml index 9455c7a..d9e68d7 100644 --- a/reloc.ml +++ b/reloc.ml @@ -1191,13 +1191,36 @@ let build_dll link_exe output_file files exts extra_args = files descr extra_args | `CLANG64 -> - (* A Unix-CLI compiler driving an lld-link-style linker: linker options - keep the MSVC spelling and are passed with -Wl,. The driver is - responsible for the CRT libraries and for translating -L into - /libpath:. *) - let gnu_cli_linker = false in + (* Two linker flavors are supported, depending on -use-linker: + - by default (or for an lld-link-style linker), the link is driven + through the C compiler: linker options keep the MSVC spelling and + are passed with -Wl,; the driver is responsible for the CRT + libraries and for translating -L into /libpath:. Note that the + driver cannot be used with a GNU-CLI linker: for MSVC targets it + always constructs an lld-link-style command line, whatever the + linker binary; + - a linker with the GNU CLI (GNU binutils ld targeting pep, + ld.lld -m i386pep, ...) is invoked directly, with GNU option + spellings, as with the "ld" chain. *) + let gnu_cli_linker = + match !Cmdline.use_linker with + | None -> None + | Some s -> + let prog = + match String.index_opt s ' ' with + | Some i -> String.sub s 0 i + | None -> s + in + let base = + String.lowercase_ascii + (Filename.remove_extension (Filename.basename prog)) + in + (match base with + | "lld" | "lld-link" | "link" -> None + | _ -> Some s) + in begin match gnu_cli_linker with - | false -> + | None -> let implib = if !implib then Filename.chop_extension output_file ^ ".lib" @@ -1212,14 +1235,24 @@ let build_dll link_exe output_file files exts extra_args = ^ extra_args else extra_args in + let fuse_ld = + (* The default linker of MSVC-target compiler drivers is link.exe, + which is unlikely to be available (e.g. when cross-compiling). + --ld-path is needed for paths: -fuse-ld only accepts linker + flavors. *) + match !Cmdline.use_linker with + | None -> "-fuse-ld=lld" + | Some s -> + if String.contains s '/' || String.contains s '\\' then + "--ld-path=" ^ Filename.quote s + else "-fuse-ld=" ^ s + in (* See the MSVC chain for the purpose of /base (the comment below) and for why the descriptor object is placed after the files. *) Printf.sprintf - "%s -fuse-ld=%s %s%s%s-Wl,/implib:%s -Wl,/base:%s -Wl,/subsystem:%s -L. %s -o %s %s %s %s" + "%s %s %s%s%s-Wl,/implib:%s -Wl,/base:%s -Wl,/subsystem:%s -L. %s -o %s %s %s %s" (cc !toolchain) - (* The default linker of MSVC-target compiler drivers is link.exe, - which is unlikely to be available (e.g. when cross-compiling) *) - (Option.value !Cmdline.use_linker ~default:"lld") + fuse_ld (if link_exe = `EXE then "" else "-shared ") (if main_pgm then "" else "-Wl,/export:symtbl -Wl,/export:reloctbl ") @@ -1233,7 +1266,59 @@ let build_dll link_exe output_file files exts extra_args = files descr extra_args - | true -> assert false + | Some ld -> + (* No manifest is generated by GNU-CLI linkers *) + no_merge_manifest := true; + let def_file = + if main_pgm then "" + else + let def_file, oc = open_temp_file "flexlink" ".def" in + Printf.fprintf oc "EXPORTS\n reloctbl\n symtbl\n"; + close_out oc; + Filename.quote def_file + in + (* The search path (which includes the LIB environment variable and + the C compiler's library search dirs) is passed with -L so that + the linker can resolve the -defaultlib directives embedded in the + objects and import libraries. *) + let search_dirs = + String.concat " " + (List.map (fun s -> "-L" ^ Filename.quote s) !search_path) + in + (* There is no C compiler driver to add the CRT libraries to the + command line, and GNU binutils ld does not process the DEFAULTLIB + directives embedded in the objects. The MSVC CRT import libraries + reference each other, hence the group. *) + let crt_libs = + if !use_default_libs then + String.concat " " + (["--start-group"] + @ List.map (fun l -> Filename.quote (find_file l)) + !default_libs + @ ["--end-group"]) + else "" + in + (* As in the lld-link case, the descriptor object is placed after + the files (see the MSVC chain) *) + Printf.sprintf + "%s %s%s--image-base %s --subsystem %s %s -o %s %s %s %s %s %s %s" + ld + (if link_exe = `EXE then "" else "--shared ") + (if main_pgm then "" else if !noentry then "-e0 " + else "-e FlexDLLiniter ") + !base_addr + !subsystem + search_dirs + (Filename.quote output_file) + files + descr + crt_libs + def_file + (if !implib then + "--out-implib " + ^ Filename.quote (Filename.chop_extension output_file ^ ".lib") + else "") + extra_args end | `CYGWIN64 -> let def_file = @@ -1507,8 +1592,13 @@ let setup_toolchain () = !dirs @ parse_libpath (try Sys.getenv "LIB" with Not_found -> "") @ cc_lib_search_dirs (); + (* vcruntime, ucrt and kernel32 would be pulled in by the DEFAULTLIB + directives of msvcrt.lib, but GNU binutils ld ignores those + directives, so spell them out (see the GNU-CLI linker case of the + link command) *) if not !custom_crt then - default_libs := ["msvcrt.lib"] + default_libs := + ["msvcrt.lib"; "vcruntime.lib"; "ucrt.lib"; "kernel32.lib"] | `MINGW -> mingw_libs Version.mingw_prefix | `MINGW64 ->