[clang] [Clang] [MinGW] Handle `-nolibc` argument (PR #182062)
Mateusz Mikuła via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 18 08:55:01 PST 2026
https://github.com/mati865 created https://github.com/llvm/llvm-project/pull/182062
This implementation differs from GCC, but arguably more in line with Unix systems, because it stops linking of default Win32 system libraries.
On GCC it works like this:
```
❯ /ucrt64/bin/gcc -### /dev/null -nolibc 2>&1 | tr ' ' '\n' | rg '^\-l' | sort -u
-lgcc
-lgcc_eh
-lkernel32
-lmingw32
-lmingwex
-lmsvcrt
❯ /ucrt64/bin/gcc -### /dev/null 2>&1 | tr ' ' '\n' | rg '^\-l' | sort -u
-ladvapi32
-lgcc
-lgcc_eh
-lkernel32
-lmingw32
-lmingwex
-lmsvcrt
-lpthread
-lshell32
-luser32
```
Clang with this PR:
```
❯ ./bin/clang -### /dev/null -nolibc 2>&1 | tr ' ' '\n' | rg '^"\-l' | sort -u
"-lgcc"
"-lgcc_eh"
```
The motivation for supporting this argument comes from Rust, which wants to control what is linked, but still utilize compiler builtins. With GCC that is done by supplying `-nodefaultlibs` and `-lgcc`, but LLVM's compiler-rt cannot be added back as easily.
`-nolibc --unwindlib=none` that skips Win32 libs would give the parity with GCC's `-nodefaultlibs -lgcc`.
>From 558200c1d15490e83dbfb4a4edb1ecd307096dfc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mateusz=20Miku=C5=82a?= <oss at mateuszmikula.dev>
Date: Wed, 18 Feb 2026 16:32:55 +0100
Subject: [PATCH] [Clang] [MinGW] Handle `-nolibc` argument
This implementation differs from GCC, but arguably
more in line with Unix systems, because it stops
linking of default Win32 system libraries.
---
clang/lib/Driver/ToolChains/MinGW.cpp | 37 +++++++++++++++------------
clang/test/Driver/mingw.cpp | 10 ++++++++
2 files changed, 31 insertions(+), 16 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/MinGW.cpp b/clang/lib/Driver/ToolChains/MinGW.cpp
index 2c9a174069f70..7c01d49dd778e 100644
--- a/clang/lib/Driver/ToolChains/MinGW.cpp
+++ b/clang/lib/Driver/ToolChains/MinGW.cpp
@@ -60,9 +60,11 @@ void tools::MinGW::Assembler::ConstructJob(Compilation &C, const JobAction &JA,
void tools::MinGW::Linker::AddLibGCC(const ArgList &Args,
ArgStringList &CmdArgs) const {
+ bool NoLibc = Args.hasArg(options::OPT_nolibc);
if (Args.hasArg(options::OPT_mthreads))
CmdArgs.push_back("-lmingwthrd");
- CmdArgs.push_back("-lmingw32");
+ if (!NoLibc)
+ CmdArgs.push_back("-lmingw32");
// Make use of compiler-rt if --rtlib option is used
ToolChain::RuntimeLibType RLT = getToolChain().GetRuntimeLibType(Args);
@@ -83,21 +85,23 @@ void tools::MinGW::Linker::AddLibGCC(const ArgList &Args,
AddRunTimeLibs(getToolChain(), getToolChain().getDriver(), CmdArgs, 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")) {
- std::string CRTLib = (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(CRTLib));
- return;
+ if (!NoLibc) {
+ 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")) {
+ std::string CRTLib = (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(CRTLib));
+ return;
+ }
}
+ CmdArgs.push_back("-lmsvcrt");
}
- CmdArgs.push_back("-lmsvcrt");
}
void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA,
@@ -289,6 +293,7 @@ void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA,
}
}
+ bool NoLibc = Args.hasArg(options::OPT_nolibc);
if (!Args.hasArg(options::OPT_nostdlib)) {
if (!Args.hasArg(options::OPT_nodefaultlibs)) {
if (Args.hasArg(options::OPT_static))
@@ -347,7 +352,7 @@ void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA,
TC.addProfileRTLibs(Args, CmdArgs);
- if (!HasWindowsApp) {
+ if (!HasWindowsApp && !NoLibc) {
// Add system libraries. If linking to libwindowsapp.a, that import
// library replaces all these and we shouldn't accidentally try to
// link to the normal desktop mode dlls.
@@ -365,7 +370,7 @@ void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back("--end-group");
} else {
AddLibGCC(Args, CmdArgs);
- if (!HasWindowsApp)
+ if (!HasWindowsApp && !NoLibc)
CmdArgs.push_back("-lkernel32");
}
}
diff --git a/clang/test/Driver/mingw.cpp b/clang/test/Driver/mingw.cpp
index f43fa177e2905..9affd3ba6d5db 100644
--- a/clang/test/Driver/mingw.cpp
+++ b/clang/test/Driver/mingw.cpp
@@ -96,3 +96,13 @@
// RUN: %clang --target=i686-windows-gnu -fms-hotpatch -### -- %s 2>&1 \
// RUN: | FileCheck %s --check-prefix=FUNCTIONPADMIN
// FUNCTIONPADMIN: "--functionpadmin"
+
+// RUN: %clang --target=x86_64-pc-windows-gnu -nolibc -### %s 2>&1 | FileCheck -check-prefix=CHECK_MINGW_NOLIBC %s
+// CHECK_MINGW_NOLIBC-NOT: "-ladvapi32"
+// CHECK_MINGW_NOLIBC-NOT: "-lkernel32"
+// CHECK_MINGW_NOLIBC-NOT: "-lmingw32"
+// CHECK_MINGW_NOLIBC-NOT: "-lmingwex"
+// CHECK_MINGW_NOLIBC-NOT: "-lmoldname"
+// CHECK_MINGW_NOLIBC-NOT: "-lmsvcrt"
+// CHECK_MINGW_NOLIBC-NOT: "-lshell32"
+// CHECK_MINGW_NOLIBC-NOT: "-luser32"
More information about the cfe-commits
mailing list