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
152 changes: 136 additions & 16 deletions amd/comgr/src/comgr-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ AMDGPUCompiler::executeInProcessDriver(ArrayRef<const char *> Args) {

ProcessWarningOptions(Diags, *DiagOpts, *OverlayFS, /*ReportDiags=*/false);

Driver TheDriver((Twine(env::getLLVMPath()) + "/bin/clang").str(),
Driver TheDriver(env::getClangBinaryPath(),
llvm::sys::getDefaultTargetTriple(), Diags,
"AMDGPU Code Object Manager", OverlayFS);
TheDriver.setCheckInputsExist(false);
Expand Down Expand Up @@ -1031,6 +1031,127 @@ amd_comgr_status_t AMDGPUCompiler::removeTmpDirs() {
#endif
}

// Probe a single directory tree for system C++ headers:
// <Root>/include/c++/v1/__config_site (libc++)
// <Root>/include/c++/<gcc-version>/cstddef (libstdc++)
// On hit, writes the matching path to `FoundPath` (if non-null) and returns
// true. Does not model Debian multiarch `g++-multiarch-incdir` layout, but
// `cstddef` itself lives in the common version dir on all major distros, so
// the libstdc++ probe still triggers there.
static bool probeCxxHeadersUnder(StringRef Root, std::string *FoundPath) {
auto Hit = [&](const Twine &P) {
if (FoundPath)
*FoundPath = P.str();
return true;
};

SmallString<256> LibCxx(Root);
sys::path::append(LibCxx, "include", "c++", "v1", "__config_site");
if (sys::fs::exists(LibCxx))
return Hit(LibCxx);

SmallString<256> CxxRoot(Root);
sys::path::append(CxxRoot, "include", "c++");
std::error_code EC;
for (sys::fs::directory_iterator DI(CxxRoot, EC), End; DI != End && !EC;
DI.increment(EC)) {
if (DI->type() != sys::fs::file_type::directory_file)
continue;
SmallString<256> Probe(DI->path());
sys::path::append(Probe, "cstddef");
if (sys::fs::exists(Probe))
return Hit(Probe);
}
return false;
}

// Filesystem probe for system libstdc++ / libc++ headers. Honors user args
// that redirect clang's header search:
// --sysroot=<dir> → probe <dir>/usr{,/local}/...
// --gcc-toolchain=<dir> → probe <dir>/...
// Without a full Generic_GCC search-path model this misses exotic layouts
// (Gentoo crossdev, multilib biarch tuples, --gcc-install-dir relative
// climbs); in those cases we fall through to "no system headers detected"
// and inject embedded — same behavior as before this fix.
//
// On hit, `FoundPath` (if non-null) receives the matching header path for
// diagnostic logging.
static bool detectSystemCxxHeadersOnDisk(ArrayRef<const char *> Argv,
std::string *FoundPath) {
std::string SysRoot;
std::string GccToolchain;
for (size_t I = 0; I < Argv.size(); ++I) {
StringRef A(Argv[I] ? Argv[I] : "");
if (A == "--sysroot" && I + 1 < Argv.size() && Argv[I + 1])
SysRoot = Argv[I + 1];
else if (A.starts_with("--sysroot="))
SysRoot = A.drop_front(StringRef("--sysroot=").size()).str();
else if (A == "--gcc-toolchain" && I + 1 < Argv.size() && Argv[I + 1])
GccToolchain = Argv[I + 1];
else if (A.starts_with("--gcc-toolchain="))
GccToolchain = A.drop_front(StringRef("--gcc-toolchain=").size()).str();
}
if (SysRoot.empty())
SysRoot = "/";

// GCC toolchain wins if specified — that's what clang's driver would
// resolve C++ headers under.
if (!GccToolchain.empty() && probeCxxHeadersUnder(GccToolchain, FoundPath))
return true;

SmallString<256> SysUsr(SysRoot);
sys::path::append(SysUsr, "usr");
if (probeCxxHeadersUnder(SysUsr, FoundPath))
return true;

SmallString<256> SysUsrLocal(SysRoot);
sys::path::append(SysUsrLocal, "usr", "local");
if (probeCxxHeadersUnder(SysUsrLocal, FoundPath))
return true;

return false;
}

bool AMDGPUCompiler::shouldSkipEmbeddedHeaders(ArrayRef<const char *> Argv) {
if (SkipEmbeddedHeadersCache)
return *SkipEmbeddedHeadersCache;

bool Verbose = env::shouldEmitVerboseLogs();
auto Decide = [&](bool Skip, const Twine &Reason) {
SkipEmbeddedHeadersCache = Skip;
if (Verbose)
LogS << "\t Embedded libc++ headers: " << (Skip ? "skipped" : "active")
<< " (" << Reason << ")\n";
return Skip;
};

// Env override takes precedence.
switch (env::getEmbeddedLibcxxMode()) {
case env::EmbeddedLibcxxMode::Force:
return Decide(false, "AMD_COMGR_USE_EMBEDDED_LIBCXX=force");
case env::EmbeddedLibcxxMode::Disable:
return Decide(true, "AMD_COMGR_USE_EMBEDDED_LIBCXX=disable");
case env::EmbeddedLibcxxMode::Auto:
break;
}

// User explicitly took control of C++ include search — don't second-guess.
for (const char *A : Argv) {
if (!A)
continue;
StringRef S(A);
if (S == "-nostdinc++" || S == "-nostdinc" || S == "-nostdlibinc")
return Decide(true, Twine("user passed ") + S);
}

// System C++ headers found → skip embedded to avoid the partial-overlay
// mixing bug (ROCm-issue-2445).
std::string FoundPath;
if (detectSystemCxxHeadersOnDisk(Argv, &FoundPath))
return Decide(true, Twine("system C++ headers found at ") + FoundPath);
return Decide(false, "no system C++ headers found, falling back to embedded");
}

amd_comgr_status_t AMDGPUCompiler::processFile(DataObject *Input,
const char *InputFilePath,
const char *OutputFilePath) {
Expand All @@ -1049,14 +1170,18 @@ amd_comgr_status_t AMDGPUCompiler::processFile(DataObject *Input,
Argv.push_back("-nogpulib");
}

// Auto-inject embedded libc++ headers as a fallback include path.
// Using -idirafter places them AFTER all other include paths, so:
// - System libstdc++ or libc++ headers take priority when available
// - User-provided -I paths take priority
// - Embedded headers only kick in when no other C++ headers are found
// This ensures backward compatibility while providing headers on systems
// without C++ development headers (e.g., driver-only installs).
if (HasEmbeddedHeaders && getLanguage() == AMD_COMGR_LANGUAGE_HIP) {
// Auto-inject embedded libc++ headers as a fallback include path when no
// system C++ headers are available. We deliberately skip injection when the
// host has libstdc++ or libc++ installed, because the embedded set is
// partial (LIBCXX_USER_HEADERS in cmake/LibcxxHeaders.cmake) — leaving the
// host's headers in the search path alongside an `-idirafter` to embedded
// libc++ produces a hybrid include chain that breaks `#include_next`
// resolution from libstdc++ (ROCm-issue-2445).
//
// Using -idirafter places them AFTER all other include paths, so when we
// do inject, user `-I`, system headers, and `-isystem` still take priority.
if (HasEmbeddedHeaders && getLanguage() == AMD_COMGR_LANGUAGE_HIP &&
!shouldSkipEmbeddedHeaders(Argv)) {
SmallString<256> LibcxxPath(env::getLLVMPath());
sys::path::append(LibcxxPath, "include", "c++", "v1");
Argv.push_back("-idirafter");
Expand Down Expand Up @@ -1314,10 +1439,7 @@ amd_comgr_status_t AMDGPUCompiler::outputResource(llvm::StringRef Path,
}

amd_comgr_status_t AMDGPUCompiler::addDeviceLibraries() {
SmallString<256> ClangBinaryPath(env::getLLVMPath());
sys::path::append(ClangBinaryPath, "bin", "clang");

std::string ClangResourceDir = GetResourcesPath(ClangBinaryPath);
std::string ClangResourceDir = GetResourcesPath(env::getClangBinaryPath());

NoGpuLib = false;

Expand Down Expand Up @@ -2417,9 +2539,7 @@ AMDGPUCompiler::AMDGPUCompiler(DataAction *ActionInfo, DataSet *InSet,
OverlayFS->pushOverlay(InMemoryFS);
}

SmallString<256> ClangBinaryPath(env::getLLVMPath());
sys::path::append(ClangBinaryPath, "bin", "clang");
std::string ResourceDir = GetResourcesPath(ClangBinaryPath);
std::string ResourceDir = GetResourcesPath(env::getClangBinaryPath());

// libc++ headers → <install>/include/c++/v1/<relative-path>
SmallString<256> LibcxxBase(env::getLLVMPath());
Expand Down
9 changes: 9 additions & 0 deletions amd/comgr/src/comgr-compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class AMDGPUCompiler {
bool UseVFS = false;
/// Whether embedded libc++ headers were loaded into the VFS.
bool HasEmbeddedHeaders = false;
/// Cached result of `shouldSkipEmbeddedHeaders`, computed once per compiler
/// instance from user args + filesystem probe + env override.
std::optional<bool> SkipEmbeddedHeadersCache;

llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS;
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFS;
Expand All @@ -64,6 +67,12 @@ class AMDGPUCompiler {

amd_comgr_status_t executeInProcessDriver(llvm::ArrayRef<const char *> Args);

/// Decide whether to bypass embedded libc++ headers (skip `-idirafter`
/// injection) for this compilation. Returns true when system C++ headers
/// are available, when the user passed `-nostdinc++`, or when overridden
/// via AMD_COMGR_USE_EMBEDDED_LIBCXX=disable. Cached after first call.
bool shouldSkipEmbeddedHeaders(llvm::ArrayRef<const char *> Argv);

amd_comgr_status_t translateSpirvToBitcodeImpl(DataSet *SpirvInSet,
DataSet *BcOutSet);

Expand Down
71 changes: 71 additions & 0 deletions amd/comgr/src/comgr-env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@

#include "comgr-env.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/VirtualFileSystem.h"

#ifndef _WIN32
#include <dlfcn.h>
#endif

using namespace llvm;

namespace COMGR {
Expand Down Expand Up @@ -69,6 +75,59 @@ llvm::StringRef getLLVMPath() {
return EnvLLVMPath;
}

// Probe whether path P names a clang binary whose derived resource directory
// (parent_path(parent_path(P)) / "lib" / "clang" / <ver>) exists on disk.
// We don't require the binary itself to be executable — clang's Driver only
// uses the path to derive the resource dir.
static bool probeClangResourceDir(StringRef P) {
SmallString<256> ResourceDir(sys::path::parent_path(sys::path::parent_path(P)));
// CLANG_INSTALL_LIBDIR_BASENAME is "lib" by default; we don't link clang, so
// hardcode the common case here. The version directory is what matters for
// existence — its parent will exist on any layout we care about.
sys::path::append(ResourceDir, "lib", "clang");
return sys::fs::is_directory(ResourceDir);
}

std::string getClangBinaryPath() {
// Cache the resolved path: this is called from multiple sites per
// compilation and the resolution involves filesystem probes.
static const std::string Cached = []() -> std::string {
// 1. Honor LLVM_PATH explicitly when set.
if (!getLLVMPath().empty())
return (Twine(getLLVMPath()) + "/bin/clang").str();

#ifndef _WIN32
// 2. Locate libamd_comgr via dladdr and probe sibling layouts. Comgr is
// a shared library; argv[0] of the host process is unrelated to where
// clang lives. dladdr on any symbol in this translation unit yields the
// path of the loaded .so.
Dl_info Info;
if (dladdr(reinterpret_cast<void *>(&getClangBinaryPath), &Info) &&
Info.dli_fname) {
StringRef SoDir = sys::path::parent_path(Info.dli_fname);
// ROCm packaging: <prefix>/lib/libamd_comgr.so + <prefix>/llvm/bin/clang
SmallString<256> RocmLayout(sys::path::parent_path(SoDir));
sys::path::append(RocmLayout, "llvm", "bin", "clang");
if (probeClangResourceDir(RocmLayout))
return std::string(RocmLayout);

// Standard install: <prefix>/lib/libamd_comgr.so + <prefix>/bin/clang
SmallString<256> StandardLayout(sys::path::parent_path(SoDir));
sys::path::append(StandardLayout, "bin", "clang");
if (probeClangResourceDir(StandardLayout))
return std::string(StandardLayout);
}
#endif

// 3. Fallback: synthesize an absolute "/bin/clang". Resource-dir lookup
// will resolve to "/lib/clang/<ver>", which won't exist on disk but
// matches what comgr's VFS embeds — keeping Driver and VFS in sync even
// when the install layout can't be located.
return std::string("/bin/clang");
}();
return Cached;
}

StringRef getCachePolicy() {
static const char *EnvCachePolicy = std::getenv("AMD_COMGR_CACHE_POLICY");
return EnvCachePolicy;
Expand Down Expand Up @@ -103,5 +162,17 @@ StringRef getDriverOptionsAppend() {
return Options ? Options : "";
}

EmbeddedLibcxxMode getEmbeddedLibcxxMode() {
static const char *V = std::getenv("AMD_COMGR_USE_EMBEDDED_LIBCXX");
if (!V)
return EmbeddedLibcxxMode::Auto;
StringRef S(V);
if (S.equals_insensitive("force") || S == "1")
return EmbeddedLibcxxMode::Force;
if (S.equals_insensitive("disable") || S == "0")
return EmbeddedLibcxxMode::Disable;
return EmbeddedLibcxxMode::Auto;
}

} // namespace env
} // namespace COMGR
18 changes: 18 additions & 0 deletions amd/comgr/src/comgr-env.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ bool needTimeStatistics();
/// otherwise return the default LLVM path.
llvm::StringRef getLLVMPath();

/// Return the clang binary path "<LLVM_PATH>/bin/clang", constructed via
/// Twine concatenation. This matches the path passed to clang's Driver
/// constructor so that clang::GetResourcesPath() yields a resource-dir path
/// matching what comgr embeds into the VFS, regardless of whether LLVM_PATH
/// is set. Using sys::path::append on an empty base produces a relative
/// "bin/clang" instead of "/bin/clang", which would cause the resource dir
/// to drift between Driver and embed code.
std::string getClangBinaryPath();

/// If environment variable AMD_COMGR_CACHE_POLICY is set, return the
/// environment variable, otherwise return empty
llvm::StringRef getCachePolicy();
Expand All @@ -46,6 +55,15 @@ llvm::StringRef getCacheDirectory();
/// space-separated options to append to clang driver invocations.
llvm::StringRef getDriverOptionsAppend();

/// Override for embedded libc++ header injection.
/// Auto — detect system C++ headers and skip embedded if found (default).
/// Force — always inject embedded headers, ignore detection.
/// Disable — never inject embedded headers, regardless of detection.
enum class EmbeddedLibcxxMode { Auto, Force, Disable };

/// Read AMD_COMGR_USE_EMBEDDED_LIBCXX. Defaults to Auto.
EmbeddedLibcxxMode getEmbeddedLibcxxMode();

} // namespace env
} // namespace COMGR

Expand Down
3 changes: 3 additions & 0 deletions amd/comgr/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -247,5 +247,8 @@ add_comgr_test(name_expression_map_test c)
add_comgr_test(compile_hip_test c)
add_comgr_test(compile_hip_to_relocatable c)
add_comgr_test(compile_hip_with_libcxx_test c)
add_comgr_test(compile_hip_stdlibcxx_conflict_test c)
add_comgr_test(compile_hip_with_system_libcxx_test c)
add_comgr_test(compile_hip_distroless_test c)
add_comgr_test(mangled_names_hip_test c)
#add_comgr_test(unbundle_hip_test c)
Loading