diff --git a/CHANGES b/CHANGES index 257253c..684ea1e 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,16 @@ 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`. 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 - GPR#127: Recognise hyphens in option names in the COFF .drectve section. 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 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..1b16272 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,13 @@ 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`). 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. 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 070fe17..d9e68d7 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,136 @@ let build_dll link_exe output_file files exts extra_args = !subsystem files descr extra_args + | `CLANG64 -> + (* 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 + | None -> + 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 + 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 %s %s%s%s-Wl,/implib:%s -Wl,/base:%s -Wl,/subsystem:%s -L. %s -o %s %s %s %s" + (cc !toolchain) + fuse_ld + (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 + | 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 = if main_pgm then "" @@ -1363,8 +1495,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 +1535,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" ]; @@ -1447,6 +1582,23 @@ 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 (); + (* 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"; "vcruntime.lib"; "ucrt.lib"; "kernel32.lib"] | `MINGW -> mingw_libs Version.mingw_prefix | `MINGW64 -> @@ -1516,6 +1668,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 @@ -1557,6 +1717,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"