Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions clang/docs/UsersManual.md
Original file line number Diff line number Diff line change
Expand Up @@ -6405,3 +6405,66 @@ The Visual C++ Toolset has a slightly more elaborate mechanism for detection.
Strict aliasing (TBAA) is always off by default in clang-cl whereas in clang,
strict aliasing is turned on by default for all optimization levels. For more
details, see {ref}`Strict aliasing <strict_aliasing>`.

## Using clang/clang++ with MSVC Targets

Clang can use the generic, GCC-style driver command line syntax to generate native Windows artifacts, and users porting a build from a POSIX environment may prefer this interface for consistency across platforms.

When targeting MSVC environments, Clang supports `--target=` and `--sysroot=` following Unix-style cross‑compilation conventions. `--sysroot=` accepts both Unix-style paths and `clang-cl /winsysroot` style paths.

This approach avoids reliance on a Windows environment, Wine, or environment variables, instead using a predictable and portable sysroot layout.

### Headers

- Windows + CRT headers: `include/`

- C++ standard library headers (selected via `-stdlib=`):
- `-stdlib=msvcstl` → `include/c++/msvcstl`
Microsoft STL (MSVC's standard library implementation)
- `-stdlib=libc++` → `include/c++/v1`
LLVM libc++ (Clang's standard library implementation)
- `-stdlib=libstdc++` → `include/c++/<version>` (e.g. `17.0.0`)
GNU libstdc++ (GCC's standard library implementation)

### Library Naming Conventions

When targeting `${cpu}-unknown-windows-msvc`, runtime library naming differs from GNU-style targets:

- **LLVM libc++**
- MSVC target: `c++.dll`, `c++.lib`
- GNU target: `libc++.dll`, `libc++.a`

- **GNU libstdc++**
- MSVC target: `stdc++-6.dll`, `stdc++.lib`
- GNU target: `libstdc++-6.dll`, `libstdc++.a`

MSVC targets omit the `lib` prefix and use `.lib` import libraries, while GNU targets retain traditional Unix-style naming.

### Libraries

The sysroot must contain libraries in the following fallback order:

1. `lib/${cpu}-unknown-windows-msvc`
2. `lib/`

Example for `x86_64-unknown-windows-msvc`:
lib/x86_64-unknown-windows-msvc → lib/
This structure supports both target-specific and shared libraries.

### Binaries

The sysroot must contain binaries in the following fallback order:

1. `bin/${cpu}-unknown-windows-msvc`
2. `bin/`

Example for `x86_64-unknown-windows-msvc`:
bin/x86_64-unknown-windows-msvc → bin/

This layout supports future scenarios such as universal binaries and ensures consistent tool resolution across architectures.

### Case Sensitivity

All header and library paths must use lowercase file names. This ensures compatibility across case-sensitive filesystems such as Linux and macOS, and matches the behavior of `mingw-w64-crt`. Windows itself is case-insensitive, but relying on mixed-case paths can lead to portability issues.

This layout is fully compatible with Clang’s standard sysroot resolution logic and requires no MSVC-specific flags. It enables clean cross-compilation workflows and portable toolchain packaging.
20 changes: 9 additions & 11 deletions clang/include/clang/Driver/ToolChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,14 @@ class ToolChain {
using path_list = SmallVector<std::string, 16>;

enum CXXStdlibType {
CST_Libcxx,
CST_Libstdcxx
CST_Libcxx, // LLVM libc++
CST_Libstdcxx, // GNU libstdc++
CST_MSVCSTL, // MSVC STL
};

enum RuntimeLibType {
RLT_CompilerRT,
RLT_Libgcc
};
enum RuntimeLibType { RLT_CompilerRT, RLT_Libgcc, RLT_VCRuntime };

enum UnwindLibType {
UNW_None,
UNW_CompilerRT,
UNW_Libgcc
};
enum UnwindLibType { UNW_None, UNW_CompilerRT, UNW_Libgcc, UNW_VCRuntime };

enum CStdlibType {
CST_Newlib,
Expand Down Expand Up @@ -299,6 +293,10 @@ class ToolChain {
/// GPUs.
virtual std::string getInputFilename(const InputInfo &Input) const;

/// for printing C++ standard library include dirs
virtual llvm::SmallVector<std::string>
getCXXStdlibIncludeDirs(const llvm::opt::ArgList &DriverArgs) const;

llvm::Triple::ArchType getArch() const { return Triple.getArch(); }
StringRef getArchName() const { return Triple.getArchName(); }
StringRef getPlatform() const { return Triple.getVendorName(); }
Expand Down
119 changes: 62 additions & 57 deletions clang/lib/Driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,23 +136,10 @@ template <typename F> static bool usesInput(const ArgList &Args, F &&Fn) {
});
}

static bool isIncludeDirArg(StringRef Arg) {
return Arg == "-internal-isystem" || Arg == "-internal-externc-isystem" ||
Arg == "-isystem" || Arg == "-cxx-isystem" || Arg == "-idirafter";
}

static void printCXXStdlibIncludeDirs(const ToolChain &TC,
const ArgList &Args) {
ArgStringList CC1Args;
if (Args.hasArg(options::OPT_stdlibxx_isystem))
TC.AddClangCXXStdlibIsystemArgs(Args, CC1Args);
else
TC.AddClangCXXStdlibIncludeArgs(Args, CC1Args);

for (size_t I = 0; I < CC1Args.size(); ++I) {
StringRef Arg(CC1Args[I]);
if (isIncludeDirArg(Arg) && I + 1 < CC1Args.size())
llvm::outs() << CC1Args[++I] << '\n';
for (const auto &str : TC.getCXXStdlibIncludeDirs(Args)) {
llvm::outs() << str << '\n';
}
}

Expand Down Expand Up @@ -2804,6 +2791,8 @@ bool Driver::HandleImmediateArgs(Compilation &C) {
case ToolChain::RLT_Libgcc:
llvm::outs() << GetFilePath("libgcc.a", TC) << "\n";
break;
default:
break;
}
return false;
}
Expand Down Expand Up @@ -7027,63 +7016,79 @@ std::string Driver::GetProgramPath(StringRef Name, const ToolChain &TC) const {
return std::string(Name);
}

static std::optional<std::string>
CxxModulePathEvaluate(const Driver &D, const ToolChain &TC,
ToolChain::CXXStdlibType cxxstdlib, const char *library) {
const char *modulejsonfilename = "modules.json";
switch (cxxstdlib) {
case ToolChain::CST_Libcxx: {
// Note when there are multiple flavours of libc++ the module json needs
// to look at the command-line arguments for the proper json. These
// flavours do not exist at the moment, but there are plans to provide a
// variant that is built with sanitizer instrumentation enabled.

// For example
// const SanitizerArgs &Sanitize = TC.getSanitizerArgs(C.getArgs());
// if (Sanitize.needsAsanRt())
// modulejsonfilename = "libc++.modules-asan.json";
// modulejsonfilename = "libc++.modules.json";
modulejsonfilename = "libc++.modules.json";
break;
}
case ToolChain::CST_Libstdcxx: {
modulejsonfilename = "libstdc++.modules.json";
break;
}
default: {
break;
}
}

if (library == nullptr) {
library = modulejsonfilename;
}
std::string lib = D.GetFilePath(library, TC);

SmallString<128> path(lib.begin(), lib.end());
llvm::sys::path::remove_filename(path);
llvm::sys::path::append(path, modulejsonfilename);
if (TC.getVFS().exists(path))
return static_cast<std::string>(path);

return {};
}

std::string Driver::GetStdModuleManifestPath(const Compilation &C,
const ToolChain &TC) const {
std::string error = "<NOT PRESENT>";

if (C.getArgs().hasArg(options::OPT_nostdlib))
return error;

switch (TC.GetCXXStdlibType(C.getArgs())) {
auto cxxstdlib = TC.GetCXXStdlibType(C.getArgs());
switch (cxxstdlib) {
case ToolChain::CST_Libcxx: {
auto evaluate = [&](const char *library) -> std::optional<std::string> {
std::string lib = GetFilePath(library, TC);

// Note when there are multiple flavours of libc++ the module json needs
// to look at the command-line arguments for the proper json. These
// flavours do not exist at the moment, but there are plans to provide a
// variant that is built with sanitizer instrumentation enabled.

// For example
// StringRef modules = [&] {
// const SanitizerArgs &Sanitize = TC.getSanitizerArgs(C.getArgs());
// if (Sanitize.needsAsanRt())
// return "libc++.modules-asan.json";
// return "libc++.modules.json";
// }();

SmallString<128> path(lib.begin(), lib.end());
llvm::sys::path::remove_filename(path);
llvm::sys::path::append(path, "libc++.modules.json");
if (TC.getVFS().exists(path))
return static_cast<std::string>(path);

return {};
};

if (std::optional<std::string> result = evaluate("libc++.so"); result)
if (std::optional<std::string> result =
CxxModulePathEvaluate(*this, TC, cxxstdlib, "libc++.so");
result)
return *result;

return evaluate("libc++.a").value_or(error);
return CxxModulePathEvaluate(*this, TC, cxxstdlib, "libc++.a")
.value_or(error);
}

case ToolChain::CST_Libstdcxx: {
auto evaluate = [&](const char *library) -> std::optional<std::string> {
std::string lib = GetFilePath(library, TC);

SmallString<128> path(lib.begin(), lib.end());
llvm::sys::path::remove_filename(path);
llvm::sys::path::append(path, "libstdc++.modules.json");
if (TC.getVFS().exists(path))
return static_cast<std::string>(path);

return {};
};

if (std::optional<std::string> result = evaluate("libstdc++.so"); result)
if (std::optional<std::string> result =
CxxModulePathEvaluate(*this, TC, cxxstdlib, "libstdc++.so");
result)
return *result;

return evaluate("libstdc++.a").value_or(error);
return CxxModulePathEvaluate(*this, TC, cxxstdlib, "libstdc++.a")
.value_or(error);
}

default: {
return CxxModulePathEvaluate(*this, TC, cxxstdlib, nullptr).value_or(error);
}
}

Expand Down
35 changes: 35 additions & 0 deletions clang/lib/Driver/ToolChain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,29 @@ std::string ToolChain::getInputFilename(const InputInfo &Input) const {
return Input.getFilename();
}

static bool isIncludeDirArg(StringRef Arg) {
return Arg == "-internal-isystem" || Arg == "-internal-externc-isystem" ||
Arg == "-isystem" || Arg == "-cxx-isystem" || Arg == "-idirafter";
}

llvm::SmallVector<std::string>
ToolChain::getCXXStdlibIncludeDirs(const ArgList &Args) const {
ArgStringList CC1Args;
if (Args.hasArg(options::OPT_stdlibxx_isystem))
AddClangCXXStdlibIsystemArgs(Args, CC1Args);
else
AddClangCXXStdlibIncludeArgs(Args, CC1Args);

llvm::SmallVector<std::string> Rets;
for (size_t I = 0; I < CC1Args.size(); ++I) {
StringRef Arg(CC1Args[I]);
if (isIncludeDirArg(Arg) && I + 1 < CC1Args.size()) {
Rets.emplace_back(CC1Args[++I]);
}
}
return Rets;
}

ToolChain::UnwindTableLevel
ToolChain::getDefaultUnwindTableLevel(const ArgList &Args) const {
return UnwindTableLevel::None;
Expand Down Expand Up @@ -1578,6 +1601,8 @@ ToolChain::RuntimeLibType ToolChain::GetRuntimeLibType(
runtimeLibType = ToolChain::RLT_CompilerRT;
else if (LibName == "libgcc")
runtimeLibType = ToolChain::RLT_Libgcc;
else if (LibName == "vcruntime")
runtimeLibType = ToolChain::RLT_VCRuntime;
else if (LibName == "platform")
runtimeLibType = GetDefaultRuntimeLibType();
else {
Expand Down Expand Up @@ -1617,6 +1642,8 @@ ToolChain::UnwindLibType ToolChain::GetUnwindLibType(
unwindLibType = ToolChain::UNW_CompilerRT;
} else if (LibName == "libgcc")
unwindLibType = ToolChain::UNW_Libgcc;
else if (LibName == "vcruntime")
unwindLibType = ToolChain::UNW_VCRuntime;
else {
if (A)
getDriver().Diag(diag::err_drv_invalid_unwindlib_name)
Expand All @@ -1640,6 +1667,8 @@ ToolChain::CXXStdlibType ToolChain::GetCXXStdlibType(const ArgList &Args) const{
cxxStdlibType = ToolChain::CST_Libcxx;
else if (LibName == "libstdc++")
cxxStdlibType = ToolChain::CST_Libstdcxx;
else if (LibName == "msvcstl")
cxxStdlibType = ToolChain::CST_MSVCSTL;
else if (LibName == "platform")
cxxStdlibType = GetDefaultCXXStdlibType();
else {
Expand All @@ -1659,6 +1688,8 @@ StringRef ToolChain::GetCXXStdlibName(const ArgList &Args) const {
return "libc++";
case ToolChain::CST_Libstdcxx:
return "libstdc++";
case ToolChain::CST_MSVCSTL:
return "msvcstl";
}
llvm_unreachable("unknown C++ standard library type");
}
Expand Down Expand Up @@ -1830,6 +1861,10 @@ void ToolChain::AddCXXStdlibLibArgs(const ArgList &Args,
case ToolChain::CST_Libstdcxx:
CmdArgs.push_back("-lstdc++");
break;

case ToolChain::CST_MSVCSTL:
// MSVC STL does not need to add -l
break;
}
}

Expand Down
6 changes: 4 additions & 2 deletions clang/lib/Driver/ToolChains/AIX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,9 @@ void AIX::AddClangCXXStdlibIncludeArgs(

switch (GetCXXStdlibType(DriverArgs)) {
case ToolChain::CST_Libstdcxx:
case ToolChain::CST_MSVCSTL:
llvm::report_fatal_error(
"picking up libstdc++ headers is unimplemented on AIX");
"picking up non-libc++ headers is unimplemented on AIX");
case ToolChain::CST_Libcxx: {
llvm::StringRef Sysroot = GetHeaderSysroot(DriverArgs);
SmallString<128> PathCPP(Sysroot);
Expand Down Expand Up @@ -468,7 +469,8 @@ void AIX::AddCXXStdlibLibArgs(const llvm::opt::ArgList &Args,
llvm::opt::ArgStringList &CmdArgs) const {
switch (GetCXXStdlibType(Args)) {
case ToolChain::CST_Libstdcxx:
llvm::report_fatal_error("linking libstdc++ unimplemented on AIX");
case ToolChain::CST_MSVCSTL:
llvm::report_fatal_error("linking non-libc++ unimplemented on AIX");
case ToolChain::CST_Libcxx:
CmdArgs.push_back("-lc++");
if (Args.hasArg(options::OPT_fexperimental_library))
Expand Down
Loading
Loading