Skip to content

Commit

Permalink
Fix static linking when using MinGW-w64 (#15167)
Browse files Browse the repository at this point in the history
* The MinGW-w64 equivalent for MSVC's `libcmt.lib` or `msvcrt.lib` is provided by MinGW-w64's built-in spec files directly (see `cc -dumpspecs`), so we do not link against it.
* There cannot be static libraries for the Win32 APIs in MinGW-w64, because that would be proprietary code; all static libraries on MSYS2 link against the C runtimes dynamically, i.e. they behave like `/MD` in MSVC or `CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL` in CMake. We therefore never link against `libucrt`.
* Passing `-lucrt` explicitly may lead to a crash in the startup code when combined with `-static` and `-msvcrt`, depending on the relative link order. In MinGW-w64, [`-lmsvcrt` is already equivalent to `-lucrt` or `-lmsvcrt-os`](https://gcc.gnu.org/onlinedocs/gcc/Cygwin-and-MinGW-Options.html), depending on how it was built; in particular, `/ucrt64/bin/libmsvcrt.a` is a copy of `libucrt.a` in MSYS2, but `/mingw64/bin/libmsvcrt.a` is a copy of `libmsvcrt-os.a`. Thus we drop `-lucrt` entirely and rely on the MinGW-w64's build-time configuration to select the appropriate C runtime.
* `-mcrtdll` can be used to override the C runtime, and it _should_ be possible to cross-build binaries between the MINGW64 and the UCRT64 environments using this flag. The interpreter now imitates this linker behavior.
  • Loading branch information
HertzDevil authored Nov 8, 2024
1 parent d9cb484 commit 6caea6a
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/mingw-w64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
shell: msys2 {0}
run: |
mkdir bin
cc crystal.obj -o bin/crystal.exe \
cc crystal.obj -o bin/crystal.exe -municode \
$(pkg-config bdw-gc libpcre2-8 iconv zlib libffi --libs) \
$(llvm-config --libs --system-libs --ldflags) \
-lole32 -lWS2_32 -Wl,--stack,0x800000
Expand Down
15 changes: 13 additions & 2 deletions src/compiler/crystal/loader/mingw.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ require "crystal/system/win32/library_archive"
# The core implementation is derived from the MSVC loader. Main deviations are:
#
# - `.parse` follows GNU `ld`'s style, rather than MSVC `link`'s;
# - `.parse` automatically inserts a C runtime library if `-mcrtdll` isn't
# supplied;
# - `#library_filename` follows the usual naming of the MinGW linker: `.dll.a`
# for DLL import libraries, `.a` for other libraries;
# - `.default_search_paths` relies solely on `.cc_each_library_path`.
Expand All @@ -28,6 +30,11 @@ class Crystal::Loader
file_paths = [] of String
extra_search_paths = [] of String

# note that `msvcrt` is a default runtime chosen at MinGW-w64 build time,
# `ucrt` is always UCRT (even in a MINGW64 environment), and
# `msvcrt-os` is always MSVCRT (even in a UCRT64 environment)
crt_dll = "msvcrt"

OptionParser.parse(args.dup) do |parser|
parser.on("-L DIRECTORY", "--library-path DIRECTORY", "Add DIRECTORY to library search path") do |directory|
extra_search_paths << directory
Expand All @@ -39,17 +46,21 @@ class Crystal::Loader
raise LoadError.new "static libraries are not supported by Crystal's runtime loader"
end
parser.unknown_args do |args, after_dash|
file_paths.concat args
file_paths.concat args.reject(&.starts_with?("-mcrtdll="))
end

parser.invalid_option do |arg|
unless arg.starts_with?("-Wl,")
if crt_dll_arg = arg.lchop?("-mcrtdll=")
# the GCC spec is `%{!mcrtdll=*:-lmsvcrt} %{mcrtdll=*:-l%*}`
crt_dll = crt_dll_arg
elsif !arg.starts_with?("-Wl,")
raise LoadError.new "Not a recognized linker flag: #{arg}"
end
end
end

search_paths = extra_search_paths + search_paths
libnames << crt_dll

begin
loader = new(search_paths)
Expand Down
12 changes: 5 additions & 7 deletions src/crystal/system/win32/wmain.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ require "c/stringapiset"
require "c/winnls"
require "c/stdlib"

{% begin %}
# we have both `main` and `wmain`, so we must choose an unambiguous entry point
# we have both `main` and `wmain`, so we must choose an unambiguous entry point
{% if flag?(:msvc) %}
@[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})]
{% if flag?(:msvc) %}
@[Link(ldflags: "/ENTRY:wmainCRTStartup")]
{% elsif flag?(:gnu) && !flag?(:interpreted) %}
@[Link(ldflags: "-municode")]
{% end %}
@[Link(ldflags: "/ENTRY:wmainCRTStartup")]
{% elsif flag?(:gnu) && !flag?(:interpreted) %}
@[Link(ldflags: "-municode")]
{% end %}
lib LibCrystalMain
end
Expand Down
2 changes: 1 addition & 1 deletion src/empty.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require "primitives"

{% if flag?(:win32) %}
{% if flag?(:msvc) %}
@[Link({{ flag?(:static) ? "libcmt" : "msvcrt" }})] # For `mainCRTStartup`
{% end %}
lib LibCrystalMain
Expand Down
2 changes: 1 addition & 1 deletion src/lib_c.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% if flag?(:win32) %}
{% if flag?(:msvc) %}
@[Link({{ flag?(:static) ? "libucrt" : "ucrt" }})]
{% end %}
lib LibC
Expand Down

0 comments on commit 6caea6a

Please sign in to comment.