Skip to content

Conversation

Keno
Copy link
Member

@Keno Keno commented Jul 18, 2025

This implements the mingw -mcrtdll option recently added to gcc.
This option is useful for having the compiler be in charge of crt
version selection while only shipping a single copy of mingw for a
multi-ABI toolchain. That said, there are various ABI dependent compiler
libraries (e.g. libstdc++), so a certain degree of ABI awareness is
nevertheless required in order to use this option correctly.

See also #149434 (which this branch includes, since it touches the same code).

Keno added 2 commits July 18, 2025 07:43
I was attempting to build openblas with clang in msys2's `ucrt64`
environment (I'm aware of the `clang64` environment, but I wanted
libstdc++). The openblas link failed with the following:

```
clang -march=native -mtune=native -m64  -O2 -fno-asynchronous-unwind-tables -O2 -DSMALL_MATRIX_OPT -DMS_ABI -DMAX_STACK_ALLOC=2048 -Wall -m64 -DF_INTERFACE_GFORT -DDYNAMIC_ARCH -DSMP_SERVER -DNO_WARMUP -DMAX_CPU_NUMBER=512 -DMAX_PARALLEL_NUMBER=1 -DBUILD_SINGLE=1 -DBUILD_DOUBLE=1 -DBUILD_COMPLEX=1 -DBUILD_COMPLEX16=1 -DVERSION=\"0.3.29\" -UASMNAME -UASMFNAME -UNAME -UCNAME -UCHAR_NAME -UCHAR_CNAME -DASMNAME= -DASMFNAME=_ -DNAME=_ -DCNAME= -DCHAR_NAME=\"_\" -DCHAR_CNAME=\"\" -DNO_AFFINITY -I..  libopenblas64_.def dllinit.obj \
-shared -o ../libopenblas64_.dll -Wl,--out-implib,../libopenblas64_.dll.a \
-Wl,--whole-archive ../libopenblas64_p-r0.3.29.a -Wl,--no-whole-archive -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0 -LC:/msys64/ucrt64/bin/../lib/gcc -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0/../../../../x86_64-w64-mingw32/lib/../lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0/../../../../lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0/../../../../x86_64-w64-mingw32/lib -LC:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0/../../..  -lgfortran -lmingwex -lmsvcrt -lquadmath -lm -lpthread -lmingwex -lmsvcrt  -defaultlib:advapi32 -lgfortran -defaultlib:advapi32 -lgfortran

C:/msys64/ucrt64/bin/ld: C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0/../../../../lib/libmingw32.a(lib64_libmingw32_a-pseudo-reloc.o): in function `__report_error':
D:/W/B/src/mingw-w64/mingw-w64-crt/crt/pseudo-reloc.c:157:(.text+0x59): undefined reference to `abort'
C:/msys64/ucrt64/bin/ld: C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0/../../../../lib/libmingw32.a(lib64_libmingw32_a-tlsthrd.o): in function `___w64_mingwthr_add_key_dtor':
D:/W/B/src/mingw-w64/mingw-w64-crt/crt/tlsthrd.c:48:(.text+0xa5): undefined reference to `calloc'
C:/msys64/ucrt64/bin/ld: C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/15.1.0/../../../../lib/libmingw32.a(lib64_libmingw32_a-pesect.o): in function `_FindPESectionByName':
D:/W/B/src/mingw-w64/mingw-w64-crt/crt/pesect.c:79:(.text+0xfd): undefined reference to `strncmp'
```

These symbols come from the `-lmingw32` dep that the driver added
and are ordinarily found in `-lmsvcrt`, which got skipped here,
because openblas passed `-lmsvcrt` explicitly. Since we always
add these libraries at the end here, I think that clang is "at fault"
(as opposed to a user or packaging mistake) and should have added some crt here.

To preserve the intent of letting the user override which crt is chosen,
duplicate the (first) user chosen crt `-l` into this position,
although we should perhaps consider an explicit `-mcrtdll` like gcc has as well.
This implements the mingw `-mcrtdll` option recently added to gcc.
This option is useful for having the compiler be in charge of crt
version selection while only shipping a single copy of mingw for a
multi-ABI toolchain. That said, there are various ABI dependent compiler
libraries (e.g. libstdc++), so a certain degree of ABI awareness is
nevertheless required in order to use this option correctly.

See also llvm#149434
@Keno Keno requested a review from mstorsjo July 18, 2025 07:56
@Keno Keno added the julialang label Jul 18, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 18, 2025

@llvm/pr-subscribers-clang-driver

Author: Keno Fischer (Keno)

Changes

This implements the mingw -mcrtdll option recently added to gcc.
This option is useful for having the compiler be in charge of crt
version selection while only shipping a single copy of mingw for a
multi-ABI toolchain. That said, there are various ABI dependent compiler
libraries (e.g. libstdc++), so a certain degree of ABI awareness is
nevertheless required in order to use this option correctly.

See also #149434 (which this branch includes, since it touches the same code).


Full diff: https://github.com/llvm/llvm-project/pull/149469.diff

10 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticFrontendKinds.td (+3)
  • (modified) clang/include/clang/Basic/LangOptions.def (+3)
  • (modified) clang/include/clang/Basic/LangOptions.h (+17)
  • (modified) clang/include/clang/Driver/Options.td (+6)
  • (modified) clang/lib/Basic/Targets/OSTargets.cpp (+47-1)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+1)
  • (modified) clang/lib/Driver/ToolChains/MinGW.cpp (+18-6)
  • (modified) clang/lib/Frontend/CompilerInvocation.cpp (+38)
  • (added) clang/test/Driver/mingw-mcrtdll.c (+30)
  • (modified) clang/test/Driver/mingw-msvcrt.c (+4-4)
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 8a8db27490f06..3de97a0ec3955 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -393,6 +393,9 @@ def warn_hlsl_langstd_minimal :
           "recommend using %1 instead">,
   InGroup<HLSLDXCCompat>;
 
+def err_unknown_crtdll : Error<"unknown Windows/MinGW C runtime library '%0'">,
+                         DefaultFatal;
+
 // ClangIR frontend errors
 def err_cir_to_cir_transform_failed : Error<
     "CIR-to-CIR transformation failed">, DefaultFatal;
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index e43238ba683f2..46f03982a041b 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -493,6 +493,9 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety extension for C")
 
 LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type")
 
+ENUM_LANGOPT(MinGWCRTDll, WindowsCRTDLLVersion, 4, WindowsCRTDLLVersion::CRTDLL_Default, NotCompatible,
+             "MinGW specific. Controls the __MSVCRT_VERSION and related preprocessor defines.")
+
 #undef LANGOPT
 #undef ENUM_LANGOPT
 #undef VALUE_LANGOPT
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 4c642c9e10c91..a0160017b6813 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -164,6 +164,23 @@ class LangOptionsBase {
     MSVC2022_9 = 1939,
   };
 
+  enum WindowsCRTDLLVersion {
+    CRTDLL_Default,
+    CRTDLL,
+    MSVCRT10,
+    MSVCRT20,
+    MSVCRT40,
+    MSVCRTD,
+    MSVCR70,
+    MSVCR71,
+    MSVCR80,
+    MSVCR90,
+    MSVCR100,
+    MSVCR110,
+    MSVCR120,
+    UCRT
+  };
+
   enum SYCLMajorVersion {
     SYCL_None,
     SYCL_2017,
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index d0b54a446309b..6ad978c525812 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1625,6 +1625,12 @@ defm auto_import : BoolFOption<"auto-import",
   PosFlag<SetTrue, [], [], "MinGW specific. Enable code generation support for "
           "automatic dllimport, and enable support for it in the linker. "
           "Enabled by default.">>;
+def mcrtdll_EQ : Joined<["-"], "mcrtdll=">,
+                 Group<m_Group>,
+                 Visibility<[ClangOption, CC1Option]>,
+                 HelpText<"MinGW specific. Changes preprocessor flags and "
+                          "linker options to use the"
+                          "specified C runtime library.">;
 } // let Flags = [TargetSpecific]
 
 // In the future this option will be supported by other offloading
diff --git a/clang/lib/Basic/Targets/OSTargets.cpp b/clang/lib/Basic/Targets/OSTargets.cpp
index e744e84a5b079..8e48228d1220f 100644
--- a/clang/lib/Basic/Targets/OSTargets.cpp
+++ b/clang/lib/Basic/Targets/OSTargets.cpp
@@ -141,8 +141,54 @@ static void addMinGWDefines(const llvm::Triple &Triple, const LangOptions &Opts,
     DefineStd(Builder, "WIN64", Opts);
     Builder.defineMacro("__MINGW64__");
   }
-  Builder.defineMacro("__MSVCRT__");
   Builder.defineMacro("__MINGW32__");
+  if (Opts.getMinGWCRTDll() == LangOptions::WindowsCRTDLLVersion::CRTDLL) {
+    Builder.defineMacro("__CRTDLL__");
+  } else {
+    Builder.defineMacro("__MSVCRT__");
+    switch (Opts.getMinGWCRTDll()) {
+    case LangOptions::WindowsCRTDLLVersion::CRTDLL_Default:
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCRT10:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x100");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCRT20:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x200");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCRT40:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x400");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCRTD:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x600");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR70:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x700");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR71:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x701");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR80:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x800");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR90:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x900");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR100:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0xA00");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR110:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0xB00");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR120:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0xC00");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::UCRT:
+      Builder.defineMacro("_UCRT");
+      break;
+    default:
+      llvm_unreachable("Unknown MinGW CRT version");
+    }
+  }
   addCygMingDefines(Opts, Builder);
 }
 
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index fe1865888bdd0..79344f4e760d9 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -5970,6 +5970,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   if (Triple.isWindowsGNUEnvironment()) {
     Args.addOptOutFlag(CmdArgs, options::OPT_fauto_import,
                        options::OPT_fno_auto_import);
+    Args.addLastArg(CmdArgs, options::OPT_mcrtdll_EQ);
   }
 
   if (Args.hasFlag(options::OPT_fms_volatile, options::OPT_fno_ms_volatile,
diff --git a/clang/lib/Driver/ToolChains/MinGW.cpp b/clang/lib/Driver/ToolChains/MinGW.cpp
index 7d093d20b3dd9..7223cda83fa70 100644
--- a/clang/lib/Driver/ToolChains/MinGW.cpp
+++ b/clang/lib/Driver/ToolChains/MinGW.cpp
@@ -85,12 +85,24 @@ void tools::MinGW::Linker::AddLibGCC(const ArgList &Args,
 
   CmdArgs.push_back("-lmoldname");
   CmdArgs.push_back("-lmingwex");
-  for (auto Lib : Args.getAllArgValues(options::OPT_l))
-    if (StringRef(Lib).starts_with("msvcr") ||
-        StringRef(Lib).starts_with("ucrt") ||
-        StringRef(Lib).starts_with("crtdll"))
-      return;
-  CmdArgs.push_back("-lmsvcrt");
+
+  if (Arg *A = Args.getLastArg(options::OPT_mcrtdll_EQ)) {
+    std::string mcrtdll = (Twine("-l") + A->getValue()).str();
+    CmdArgs.push_back(Args.MakeArgStringRef(mcrtdll));
+  } else {
+    for (auto Lib : Args.getAllArgValues(options::OPT_l))
+      if (StringRef(Lib).starts_with("msvcr") ||
+          StringRef(Lib).starts_with("ucrt") ||
+          StringRef(Lib).starts_with("crtdll")) {
+        Lib = (llvm::Twine("-l") + Lib).str();
+        // Respect the user's chosen crt variant, but still provide it
+        // again as the last linker argument, because some of the libraries
+        // we added above may depend on it.
+        CmdArgs.push_back(Args.MakeArgStringRef(Lib));
+        return;
+      }
+    CmdArgs.push_back("-lmsvcrt");
+  }
 }
 
 void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA,
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index b9f75796ecc16..26d05bc419ccb 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -4705,6 +4705,44 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
     }
   }
 
+  // Process MinGW -mcrtdll option
+  if (Arg *A = Args.getLastArg(OPT_mcrtdll_EQ)) {
+    Opts.MinGWCRTDll =
+        llvm::StringSwitch<enum LangOptions::WindowsCRTDLLVersion>(
+            A->getValue())
+            .StartsWithLower("crtdll",
+                             LangOptions::WindowsCRTDLLVersion::CRTDLL)
+            .StartsWithLower("msvcrt10",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRT10)
+            .StartsWithLower("msvcrt20",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRT20)
+            .StartsWithLower("msvcrt40",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRT40)
+            .StartsWithLower("msvcr40",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRT40)
+            .StartsWithLower("msvcrtd",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRTD)
+            .StartsWithLower("msvcr70",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR70)
+            .StartsWithLower("msvcr71",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR71)
+            .StartsWithLower("msvcr80",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR80)
+            .StartsWithLower("msvcr90",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR90)
+            .StartsWithLower("msvcr100",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR100)
+            .StartsWithLower("msvcr110",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR110)
+            .StartsWithLower("msvcr120",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR120)
+            .StartsWithLower("ucrt", LangOptions::WindowsCRTDLLVersion::UCRT)
+            .Default(LangOptions::WindowsCRTDLLVersion::CRTDLL_Default);
+    if (Opts.MinGWCRTDll == LangOptions::WindowsCRTDLLVersion::CRTDLL_Default) {
+      Diags.Report(diag::err_unknown_crtdll) << A->getValue();
+    }
+  }
+
   return Diags.getNumErrors() == NumErrorsBefore;
 }
 
diff --git a/clang/test/Driver/mingw-mcrtdll.c b/clang/test/Driver/mingw-mcrtdll.c
new file mode 100644
index 0000000000000..4558628766169
--- /dev/null
+++ b/clang/test/Driver/mingw-mcrtdll.c
@@ -0,0 +1,30 @@
+// RUN: %clang -v --target=x86_64-w64-mingw32 -### %s 2>&1 | FileCheck -check-prefix=DEFAULT %s
+// RUN: %clang -v --target=x86_64-w64-mingw32 -mcrtdll=msvcr90 -### %s 2>&1 | FileCheck -check-prefix=MSVCR90 %s
+// RUN: %clang -v --target=x86_64-w64-mingw32 -mcrtdll=msvcr90_suffix -### %s 2>&1 | FileCheck -check-prefix=MSVCR90_SUFFIX %s
+// RUN: %clang -v --target=x86_64-w64-mingw32 -mcrtdll=ucrt -### %s 2>&1 | FileCheck -check-prefix=UCRT %s
+// RUN: %clang -v --target=x86_64-w64-mingw32 -mcrtdll=ucrtbase -### %s 2>&1 | FileCheck -check-prefix=UCRTBASE %s
+
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 %s 2>&1  | FileCheck -check-prefix=DEFINE_DEFAULT %s
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=msvcr90 %s 2>&1  | FileCheck -check-prefix=DEFINE_MSVCR90 %s
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=msvcr90_suffix %s 2>&1  | FileCheck -check-prefix=DEFINE_MSVCR90 %s
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=ucrt %s 2>&1  | FileCheck -check-prefix=DEFINE_UCRT %s
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=ucrtbase %s 2>&1  | FileCheck -check-prefix=DEFINE_UCRT %s
+// RUN: not %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=bad %s 2>&1  | FileCheck -check-prefix=BAD %s
+
+// DEFAULT: "-lmingwex" "-lmsvcrt"
+// DEFINE_DEFAULT: #define __MSVCRT__
+// MSVCR90: "-lmingwex" "-lmsvcr90"
+// DEFINE_MSVCR90: #define __MSVCRT_VERSION__ 0x900
+// DEFINE_MSVCR90: #define __MSVCRT__
+// MSVCR90-NOT: "-lmsvcrt"
+// MSVCR90_SUFFIX: "-lmingwex" "-lmsvcr90_suffix"
+// MSVCR90_SUFFIX-NOT: "-lmsvcrt"
+// UCRT: "-lmingwex" "-lucrt"
+// DEFINE_UCRT: #define _UCRT
+// DEFINE_UCRT-NOT: #define __MSVCRT_VERSION__
+// UCRT-NOT: "-lmsvcrt"
+// UCRTBASE: "-lmingwex" "-lucrtbase"
+// UCRTBASE-NOT: "-lmsvcrt"
+// DEFINE_CRTDLL: #define __CRTDLL__
+// DEFINE_CRTDLL-NOT: #define __MSVCRT__
+// BAD: error: unknown Windows/MinGW C runtime library 'bad'
diff --git a/clang/test/Driver/mingw-msvcrt.c b/clang/test/Driver/mingw-msvcrt.c
index 340ce1f57b0f8..e1648630476a0 100644
--- a/clang/test/Driver/mingw-msvcrt.c
+++ b/clang/test/Driver/mingw-msvcrt.c
@@ -7,10 +7,10 @@
 // CHECK_DEFAULT: "-lmingwex" "-lmsvcrt" "-ladvapi32"
 // CHECK_DEFAULT-SAME: "-lmsvcrt" "-lkernel32" "{{.*}}crtend.o"
 // CHECK_MSVCR120: "-lmsvcr120"
-// CHECK_MSVCR120-SAME: "-lmingwex" "-ladvapi32"
+// CHECK_MSVCR120-SAME: "-lmingwex" "-lmsvcr120" "-ladvapi32"
 // CHECK_UCRTBASE: "-lucrtbase"
-// CHECK_UCRTBASE-SAME: "-lmingwex" "-ladvapi32"
+// CHECK_UCRTBASE-SAME: "-lmingwex" "-lucrtbase" "-ladvapi32"
 // CHECK_UCRT: "-lucrt"
-// CHECK_UCRT-SAME: "-lmingwex" "-ladvapi32"
+// CHECK_UCRT-SAME: "-lmingwex" "-lucrt" "-ladvapi32"
 // CHECK_CRTDLL: "-lcrtdll"
-// CHECK_CRTDLL-SAME: "-lmingwex" "-ladvapi32"
+// CHECK_CRTDLL-SAME: "-lmingwex" "-lcrtdll" "-ladvapi32"

@llvmbot
Copy link
Member

llvmbot commented Jul 18, 2025

@llvm/pr-subscribers-clang

Author: Keno Fischer (Keno)

Changes

This implements the mingw -mcrtdll option recently added to gcc.
This option is useful for having the compiler be in charge of crt
version selection while only shipping a single copy of mingw for a
multi-ABI toolchain. That said, there are various ABI dependent compiler
libraries (e.g. libstdc++), so a certain degree of ABI awareness is
nevertheless required in order to use this option correctly.

See also #149434 (which this branch includes, since it touches the same code).


Full diff: https://github.com/llvm/llvm-project/pull/149469.diff

10 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticFrontendKinds.td (+3)
  • (modified) clang/include/clang/Basic/LangOptions.def (+3)
  • (modified) clang/include/clang/Basic/LangOptions.h (+17)
  • (modified) clang/include/clang/Driver/Options.td (+6)
  • (modified) clang/lib/Basic/Targets/OSTargets.cpp (+47-1)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+1)
  • (modified) clang/lib/Driver/ToolChains/MinGW.cpp (+18-6)
  • (modified) clang/lib/Frontend/CompilerInvocation.cpp (+38)
  • (added) clang/test/Driver/mingw-mcrtdll.c (+30)
  • (modified) clang/test/Driver/mingw-msvcrt.c (+4-4)
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 8a8db27490f06..3de97a0ec3955 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -393,6 +393,9 @@ def warn_hlsl_langstd_minimal :
           "recommend using %1 instead">,
   InGroup<HLSLDXCCompat>;
 
+def err_unknown_crtdll : Error<"unknown Windows/MinGW C runtime library '%0'">,
+                         DefaultFatal;
+
 // ClangIR frontend errors
 def err_cir_to_cir_transform_failed : Error<
     "CIR-to-CIR transformation failed">, DefaultFatal;
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index e43238ba683f2..46f03982a041b 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -493,6 +493,9 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety extension for C")
 
 LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type")
 
+ENUM_LANGOPT(MinGWCRTDll, WindowsCRTDLLVersion, 4, WindowsCRTDLLVersion::CRTDLL_Default, NotCompatible,
+             "MinGW specific. Controls the __MSVCRT_VERSION and related preprocessor defines.")
+
 #undef LANGOPT
 #undef ENUM_LANGOPT
 #undef VALUE_LANGOPT
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 4c642c9e10c91..a0160017b6813 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -164,6 +164,23 @@ class LangOptionsBase {
     MSVC2022_9 = 1939,
   };
 
+  enum WindowsCRTDLLVersion {
+    CRTDLL_Default,
+    CRTDLL,
+    MSVCRT10,
+    MSVCRT20,
+    MSVCRT40,
+    MSVCRTD,
+    MSVCR70,
+    MSVCR71,
+    MSVCR80,
+    MSVCR90,
+    MSVCR100,
+    MSVCR110,
+    MSVCR120,
+    UCRT
+  };
+
   enum SYCLMajorVersion {
     SYCL_None,
     SYCL_2017,
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index d0b54a446309b..6ad978c525812 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1625,6 +1625,12 @@ defm auto_import : BoolFOption<"auto-import",
   PosFlag<SetTrue, [], [], "MinGW specific. Enable code generation support for "
           "automatic dllimport, and enable support for it in the linker. "
           "Enabled by default.">>;
+def mcrtdll_EQ : Joined<["-"], "mcrtdll=">,
+                 Group<m_Group>,
+                 Visibility<[ClangOption, CC1Option]>,
+                 HelpText<"MinGW specific. Changes preprocessor flags and "
+                          "linker options to use the"
+                          "specified C runtime library.">;
 } // let Flags = [TargetSpecific]
 
 // In the future this option will be supported by other offloading
diff --git a/clang/lib/Basic/Targets/OSTargets.cpp b/clang/lib/Basic/Targets/OSTargets.cpp
index e744e84a5b079..8e48228d1220f 100644
--- a/clang/lib/Basic/Targets/OSTargets.cpp
+++ b/clang/lib/Basic/Targets/OSTargets.cpp
@@ -141,8 +141,54 @@ static void addMinGWDefines(const llvm::Triple &Triple, const LangOptions &Opts,
     DefineStd(Builder, "WIN64", Opts);
     Builder.defineMacro("__MINGW64__");
   }
-  Builder.defineMacro("__MSVCRT__");
   Builder.defineMacro("__MINGW32__");
+  if (Opts.getMinGWCRTDll() == LangOptions::WindowsCRTDLLVersion::CRTDLL) {
+    Builder.defineMacro("__CRTDLL__");
+  } else {
+    Builder.defineMacro("__MSVCRT__");
+    switch (Opts.getMinGWCRTDll()) {
+    case LangOptions::WindowsCRTDLLVersion::CRTDLL_Default:
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCRT10:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x100");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCRT20:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x200");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCRT40:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x400");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCRTD:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x600");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR70:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x700");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR71:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x701");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR80:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x800");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR90:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0x900");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR100:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0xA00");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR110:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0xB00");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::MSVCR120:
+      Builder.defineMacro("__MSVCRT_VERSION__", "0xC00");
+      break;
+    case LangOptions::WindowsCRTDLLVersion::UCRT:
+      Builder.defineMacro("_UCRT");
+      break;
+    default:
+      llvm_unreachable("Unknown MinGW CRT version");
+    }
+  }
   addCygMingDefines(Opts, Builder);
 }
 
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index fe1865888bdd0..79344f4e760d9 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -5970,6 +5970,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   if (Triple.isWindowsGNUEnvironment()) {
     Args.addOptOutFlag(CmdArgs, options::OPT_fauto_import,
                        options::OPT_fno_auto_import);
+    Args.addLastArg(CmdArgs, options::OPT_mcrtdll_EQ);
   }
 
   if (Args.hasFlag(options::OPT_fms_volatile, options::OPT_fno_ms_volatile,
diff --git a/clang/lib/Driver/ToolChains/MinGW.cpp b/clang/lib/Driver/ToolChains/MinGW.cpp
index 7d093d20b3dd9..7223cda83fa70 100644
--- a/clang/lib/Driver/ToolChains/MinGW.cpp
+++ b/clang/lib/Driver/ToolChains/MinGW.cpp
@@ -85,12 +85,24 @@ void tools::MinGW::Linker::AddLibGCC(const ArgList &Args,
 
   CmdArgs.push_back("-lmoldname");
   CmdArgs.push_back("-lmingwex");
-  for (auto Lib : Args.getAllArgValues(options::OPT_l))
-    if (StringRef(Lib).starts_with("msvcr") ||
-        StringRef(Lib).starts_with("ucrt") ||
-        StringRef(Lib).starts_with("crtdll"))
-      return;
-  CmdArgs.push_back("-lmsvcrt");
+
+  if (Arg *A = Args.getLastArg(options::OPT_mcrtdll_EQ)) {
+    std::string mcrtdll = (Twine("-l") + A->getValue()).str();
+    CmdArgs.push_back(Args.MakeArgStringRef(mcrtdll));
+  } else {
+    for (auto Lib : Args.getAllArgValues(options::OPT_l))
+      if (StringRef(Lib).starts_with("msvcr") ||
+          StringRef(Lib).starts_with("ucrt") ||
+          StringRef(Lib).starts_with("crtdll")) {
+        Lib = (llvm::Twine("-l") + Lib).str();
+        // Respect the user's chosen crt variant, but still provide it
+        // again as the last linker argument, because some of the libraries
+        // we added above may depend on it.
+        CmdArgs.push_back(Args.MakeArgStringRef(Lib));
+        return;
+      }
+    CmdArgs.push_back("-lmsvcrt");
+  }
 }
 
 void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA,
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index b9f75796ecc16..26d05bc419ccb 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -4705,6 +4705,44 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
     }
   }
 
+  // Process MinGW -mcrtdll option
+  if (Arg *A = Args.getLastArg(OPT_mcrtdll_EQ)) {
+    Opts.MinGWCRTDll =
+        llvm::StringSwitch<enum LangOptions::WindowsCRTDLLVersion>(
+            A->getValue())
+            .StartsWithLower("crtdll",
+                             LangOptions::WindowsCRTDLLVersion::CRTDLL)
+            .StartsWithLower("msvcrt10",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRT10)
+            .StartsWithLower("msvcrt20",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRT20)
+            .StartsWithLower("msvcrt40",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRT40)
+            .StartsWithLower("msvcr40",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRT40)
+            .StartsWithLower("msvcrtd",
+                             LangOptions::WindowsCRTDLLVersion::MSVCRTD)
+            .StartsWithLower("msvcr70",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR70)
+            .StartsWithLower("msvcr71",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR71)
+            .StartsWithLower("msvcr80",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR80)
+            .StartsWithLower("msvcr90",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR90)
+            .StartsWithLower("msvcr100",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR100)
+            .StartsWithLower("msvcr110",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR110)
+            .StartsWithLower("msvcr120",
+                             LangOptions::WindowsCRTDLLVersion::MSVCR120)
+            .StartsWithLower("ucrt", LangOptions::WindowsCRTDLLVersion::UCRT)
+            .Default(LangOptions::WindowsCRTDLLVersion::CRTDLL_Default);
+    if (Opts.MinGWCRTDll == LangOptions::WindowsCRTDLLVersion::CRTDLL_Default) {
+      Diags.Report(diag::err_unknown_crtdll) << A->getValue();
+    }
+  }
+
   return Diags.getNumErrors() == NumErrorsBefore;
 }
 
diff --git a/clang/test/Driver/mingw-mcrtdll.c b/clang/test/Driver/mingw-mcrtdll.c
new file mode 100644
index 0000000000000..4558628766169
--- /dev/null
+++ b/clang/test/Driver/mingw-mcrtdll.c
@@ -0,0 +1,30 @@
+// RUN: %clang -v --target=x86_64-w64-mingw32 -### %s 2>&1 | FileCheck -check-prefix=DEFAULT %s
+// RUN: %clang -v --target=x86_64-w64-mingw32 -mcrtdll=msvcr90 -### %s 2>&1 | FileCheck -check-prefix=MSVCR90 %s
+// RUN: %clang -v --target=x86_64-w64-mingw32 -mcrtdll=msvcr90_suffix -### %s 2>&1 | FileCheck -check-prefix=MSVCR90_SUFFIX %s
+// RUN: %clang -v --target=x86_64-w64-mingw32 -mcrtdll=ucrt -### %s 2>&1 | FileCheck -check-prefix=UCRT %s
+// RUN: %clang -v --target=x86_64-w64-mingw32 -mcrtdll=ucrtbase -### %s 2>&1 | FileCheck -check-prefix=UCRTBASE %s
+
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 %s 2>&1  | FileCheck -check-prefix=DEFINE_DEFAULT %s
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=msvcr90 %s 2>&1  | FileCheck -check-prefix=DEFINE_MSVCR90 %s
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=msvcr90_suffix %s 2>&1  | FileCheck -check-prefix=DEFINE_MSVCR90 %s
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=ucrt %s 2>&1  | FileCheck -check-prefix=DEFINE_UCRT %s
+// RUN: %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=ucrtbase %s 2>&1  | FileCheck -check-prefix=DEFINE_UCRT %s
+// RUN: not %clang -dM -E --target=x86_64-w64-mingw32 -mcrtdll=bad %s 2>&1  | FileCheck -check-prefix=BAD %s
+
+// DEFAULT: "-lmingwex" "-lmsvcrt"
+// DEFINE_DEFAULT: #define __MSVCRT__
+// MSVCR90: "-lmingwex" "-lmsvcr90"
+// DEFINE_MSVCR90: #define __MSVCRT_VERSION__ 0x900
+// DEFINE_MSVCR90: #define __MSVCRT__
+// MSVCR90-NOT: "-lmsvcrt"
+// MSVCR90_SUFFIX: "-lmingwex" "-lmsvcr90_suffix"
+// MSVCR90_SUFFIX-NOT: "-lmsvcrt"
+// UCRT: "-lmingwex" "-lucrt"
+// DEFINE_UCRT: #define _UCRT
+// DEFINE_UCRT-NOT: #define __MSVCRT_VERSION__
+// UCRT-NOT: "-lmsvcrt"
+// UCRTBASE: "-lmingwex" "-lucrtbase"
+// UCRTBASE-NOT: "-lmsvcrt"
+// DEFINE_CRTDLL: #define __CRTDLL__
+// DEFINE_CRTDLL-NOT: #define __MSVCRT__
+// BAD: error: unknown Windows/MinGW C runtime library 'bad'
diff --git a/clang/test/Driver/mingw-msvcrt.c b/clang/test/Driver/mingw-msvcrt.c
index 340ce1f57b0f8..e1648630476a0 100644
--- a/clang/test/Driver/mingw-msvcrt.c
+++ b/clang/test/Driver/mingw-msvcrt.c
@@ -7,10 +7,10 @@
 // CHECK_DEFAULT: "-lmingwex" "-lmsvcrt" "-ladvapi32"
 // CHECK_DEFAULT-SAME: "-lmsvcrt" "-lkernel32" "{{.*}}crtend.o"
 // CHECK_MSVCR120: "-lmsvcr120"
-// CHECK_MSVCR120-SAME: "-lmingwex" "-ladvapi32"
+// CHECK_MSVCR120-SAME: "-lmingwex" "-lmsvcr120" "-ladvapi32"
 // CHECK_UCRTBASE: "-lucrtbase"
-// CHECK_UCRTBASE-SAME: "-lmingwex" "-ladvapi32"
+// CHECK_UCRTBASE-SAME: "-lmingwex" "-lucrtbase" "-ladvapi32"
 // CHECK_UCRT: "-lucrt"
-// CHECK_UCRT-SAME: "-lmingwex" "-ladvapi32"
+// CHECK_UCRT-SAME: "-lmingwex" "-lucrt" "-ladvapi32"
 // CHECK_CRTDLL: "-lcrtdll"
-// CHECK_CRTDLL-SAME: "-lmingwex" "-ladvapi32"
+// CHECK_CRTDLL-SAME: "-lmingwex" "-lcrtdll" "-ladvapi32"

@@ -493,6 +493,9 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety extension for C")

LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type")

ENUM_LANGOPT(MinGWCRTDll, WindowsCRTDLLVersion, 4, WindowsCRTDLLVersion::CRTDLL_Default, NotCompatible,
"MinGW specific. Controls the __MSVCRT_VERSION and related preprocessor defines.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo, __MSVCRT_VERSION__ with two trailing underscores.

.StartsWithLower("msvcr110",
LangOptions::WindowsCRTDLLVersion::MSVCR110)
.StartsWithLower("msvcr120",
LangOptions::WindowsCRTDLLVersion::MSVCR120)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're missing a case (and enum) for msvcrt-os, if this is to mimic the GCC implementation.

@mstorsjo mstorsjo requested a review from cjacek July 28, 2025 09:43
@mstorsjo
Copy link
Member

This implements the mingw -mcrtdll option recently added to gcc. This option is useful for having the compiler be in charge of crt version selection while only shipping a single copy of mingw for a multi-ABI toolchain. That said, there are various ABI dependent compiler libraries (e.g. libstdc++), so a certain degree of ABI awareness is nevertheless required in order to use this option correctly.

Indeed - any precompiled static libraries are a problem. (And also shared libraries, if passing CRT resources over the DLL boundary, e.g. mallocing in one DLL and freeing in another.)

Between the various msvcr* libraries, the difference in ABI is usually quite small (if any difference at all), but there are some notable ABI differences between object files between UCRT and msvcrt. The difference used to be bigger; if you'd have an object file calling e.g. printf, built for UCRT but linked against msvcrt, or vice versa, you'd have linker failures right away. Many of such differences have been removed in mingw-w64 these days though, which may give a stronger sense that this really is working - but there are devious ABI differences in e.g. struct threadlocaleinfostruct, which is accessed directly from many ctype.h macros.

So in practice, using a non-default CRT is generally not a safe thing to do (and it kinda remains impossible due to such ABI differences). It's probably safe if linking a pure C executable, with compiler-rt builtins. It's also probably safe if linking a C executable with a static libgcc, assuming that none of the bits of libgcc that gets pulled in there have ABI variability. If linking a shared libgcc (which usually is the default on GCC iirc), I'm not confident in saying whether it is safe or not. If involving libstdc++ or libc++, then it's quite certainly not safe.

That's why I haven't really been in favour of adding this option to GCC in the first place (even if it makes things much nicer and more convenient for experimenting with switching between CRT implementations - which I do myself to ease some testing; in that case with just C executables and compiler-rt builtins). But when that patch to GCC was proposed, I at least opted for it to come with a disclaimer that this really isn't safe in general, see the last couple of sentences at https://gcc.gnu.org/onlinedocs/gcc/Cygwin-and-MinGW-Options.html#index-mcrtdll.

So while I wasn't keen on adding this in the first place, it does exist in GCC, so having feature parity is of course good, so I won't object to adding it here either. But I do think it needs to come with some sort of disclaimer, just like in GCC.

Because having one single toolchain, and being able to switch between targeting msvcrt and UCRT - which is the main thing people would want to do - really isn't safe to do unless you're aware of the exact limitations of what you can and can't do with it. (I presume you're intending to use it in Julia somewhere, and that may very well be safe enough though.)

@Keno
Copy link
Member Author

Keno commented Jul 28, 2025

I'm fine either way. I think having the option is marginally nicer, but but on the other hand, in every situation where you would use it, you can also use an explicit -D and -l to specify the crt you want explicitly, so I don't think it actually buys you anything other than gcc compatibility. I'll get the other PR merged and this one rebased with your comments addressed, but if we want to close this and leave this unimplemented, I'm ok with that to.

@mstorsjo
Copy link
Member

I'm fine either way. I think having the option is marginally nicer, but but on the other hand, in every situation where you would use it, you can also use an explicit -D and -l to specify the crt you want explicitly, so I don't think it actually buys you anything other than gcc compatibility.

Yeah it does give you a reasonable amount of extra convenience, not needing to come up with the exact __MSVCRT_VERSION__ or _UCRT defines to set, etc. So it's clearly convenient. But also easily gives you the impression you can do things you really can't.

I'll get the other PR merged and this one rebased with your comments addressed, but if we want to close this and leave this unimplemented, I'm ok with that to.

Thanks, I appreciate it!

I'm undecided so far, so let's keep it open, and see if we get feedback from others from the mingw ecosystem. (CC @cjacek, @jeremyd2019, @lazka, @mati865, @alvinhochun.)

@mati865
Copy link
Contributor

mati865 commented Jul 30, 2025

I'm not against adding either, but I agree the disclaimer is a must. Perhaps also with a warning anytime this argument is used for linking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category julialang
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants