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
73 changes: 73 additions & 0 deletions clang/docs/UsersManual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5457,3 +5457,76 @@ Restrictions and Limitations compared to Clang
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

While ``clang-cl`` emulates the MSVC compiler interface, users may prefer to invoke ``clang`` or ``clang++`` directly for greater control and platform consistency. When targeting MSVC environments, Clang supports the use of ``--target=`` and ``--sysroot=`` following the same conventions as Unix-style cross-compilation.

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

Headers
-------

- Windows + CRT headers: ``include/``

- C++ standard library headers (selected via ``-stdlib=``):

- ``-stdlib=msstl`` → ``include/c++/msstl``
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. ``16.0.0``)
GNU libstdc++ (GCC's standard library implementation)

Library Naming Conventions
--------------------------

When targeting ``${cpu}-unknown-windows-msvc``, the naming of runtime libraries 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``

These naming conventions reflect platform-specific linker and runtime expectations. The MSVC target omits the ``lib`` prefix and uses `.lib` import libraries, while GNU targets retain the 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 allows toolchains to support both target-specific and shared libraries, enabling clean fallback behavior and simplifying multi-target packaging.

Binaries
--------

The sysroot must also 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.

For a reference implementation, see `windows-msvc-sysroot <https://github.com/trcrsired/windows-msvc-sysroot>`_.
16 changes: 5 additions & 11 deletions clang/include/clang/Driver/ToolChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,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_Msstl, // 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 class UnwindTableLevel {
None,
Expand Down
102 changes: 60 additions & 42 deletions clang/lib/Driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2588,6 +2588,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 @@ -6609,63 +6611,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
10 changes: 10 additions & 0 deletions clang/lib/Driver/ToolChain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,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 @@ -1368,6 +1370,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 @@ -1391,6 +1395,8 @@ ToolChain::CXXStdlibType ToolChain::GetCXXStdlibType(const ArgList &Args) const{
cxxStdlibType = ToolChain::CST_Libcxx;
else if (LibName == "libstdc++")
cxxStdlibType = ToolChain::CST_Libstdcxx;
else if (LibName == "msstl")
cxxStdlibType = ToolChain::CST_Msstl;
else if (LibName == "platform")
cxxStdlibType = GetDefaultCXXStdlibType();
else {
Expand Down Expand Up @@ -1546,6 +1552,10 @@ void ToolChain::AddCXXStdlibLibArgs(const ArgList &Args,
case ToolChain::CST_Libstdcxx:
CmdArgs.push_back("-lstdc++");
break;

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

Expand Down
6 changes: 5 additions & 1 deletion clang/lib/Driver/ToolChains/AIX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,8 @@ void AIX::AddClangCXXStdlibIncludeArgs(
CC1Args.push_back("-D__LIBC_NO_CPP_MATH_OVERLOADS__");
return;
}
default:
break;
}

llvm_unreachable("Unexpected C++ library type; only libc++ is supported.");
Expand All @@ -473,9 +475,11 @@ void AIX::AddCXXStdlibLibArgs(const llvm::opt::ArgList &Args,
CmdArgs.push_back("-lc++experimental");
CmdArgs.push_back("-lc++abi");
return;
default:
break;
}

llvm_unreachable("Unexpected C++ library type; only libc++ is supported.");
llvm_unreachable("Unexpected C++ library type");
}

// This function processes all the mtocdata options to build the final
Expand Down
10 changes: 9 additions & 1 deletion clang/lib/Driver/ToolChains/BareMetal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,14 @@ void BareMetal::AddClangCXXStdlibIncludeArgs(const ArgList &DriverArgs,
AddCXXIncludePath(P);
break;
}
case ToolChain::CST_Libstdcxx:
case ToolChain::CST_Libstdcxx: {
addLibStdCxxIncludePaths(DriverArgs, CC1Args);
break;
}
default: {
break;
}
}

std::string SysRootDir(computeSysRoot());
if (SysRootDir.empty())
Expand Down Expand Up @@ -526,6 +530,8 @@ void BareMetal::AddClangCXXStdlibIncludeArgs(const ArgList &DriverArgs,
}
break;
}
default:
break;
}
}
}
Expand Down Expand Up @@ -648,6 +654,8 @@ void baremetal::Linker::ConstructJob(Compilation &C, const JobAction &JA,
TC.getCompilerRTArgString(Args, "crtend", ToolChain::FT_Object);
break;
}
default:
return;
}
CmdArgs.push_back(Args.MakeArgString(TC.GetFilePath(CRTBegin)));
}
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Driver/ToolChains/CommonArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2398,6 +2398,8 @@ static void AddUnwindLibrary(const ToolChain &TC, const Driver &D,
CmdArgs.push_back("-lunwind");
}
break;
default:
break;
}

if (AsNeeded)
Expand Down Expand Up @@ -2446,6 +2448,8 @@ void tools::AddRunTimeLibs(const ToolChain &TC, const Driver &D,
} else
AddLibgcc(TC, D, CmdArgs, Args);
break;
default:
break;
}

// On Android, the unwinder uses dl_iterate_phdr (or one of
Expand Down
Loading