[clang] [offload-arch] Fix amdgpu HIP DLL search path on Windows (PR #209898)
Scott Todd via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 20 08:14:18 PDT 2026
https://github.com/ScottTodd updated https://github.com/llvm/llvm-project/pull/209898
>From 81c581070eb82700ba431fad354b7ced801a712a Mon Sep 17 00:00:00 2001
From: Scott Todd <Scott.Todd at amd.com>
Date: Wed, 15 Jul 2026 11:44:12 -0700
Subject: [PATCH 1/4] [offload-arch] Fix amdgpu HIP DLL search path on Windows
---
clang/tools/offload-arch/AMDGPUArchByHIP.cpp | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/clang/tools/offload-arch/AMDGPUArchByHIP.cpp b/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
index 9cfccf416f783..db4387db3c0c7 100644
--- a/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
+++ b/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
@@ -229,8 +229,19 @@ static void primeLibraryLoad(StringRef Path) {
static HMODULE PinnedModule = nullptr;
if (PinnedModule || !sys::path::is_absolute(Path))
return;
+
+ // Other paths here use '/' for consistency, but LoadLibraryExW's altered
+ // search path needs '\\' to locate dependencies relative to the DLL.
+ //
+ // See https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw
+ // "If the string specifies a fully qualified path, the function searches only
+ // that path for the module. When specifying a path, be sure to use
+ // backslashes (\), not forward slashes (/)."
+ SmallString<256> NativePath;
+ sys::path::native(Path, NativePath, sys::path::Style::windows_backslash);
+
SmallVector<UTF16, 256> WPath;
- if (!convertUTF8ToUTF16String(Path, WPath))
+ if (!convertUTF8ToUTF16String(NativePath, WPath))
return;
WPath.push_back(0);
PinnedModule = LoadLibraryExW(reinterpret_cast<LPCWSTR>(WPath.data()),
>From eb808f79208862569915d3018294735a699c9d05 Mon Sep 17 00:00:00 2001
From: Scott Todd <Scott.Todd at amd.com>
Date: Wed, 15 Jul 2026 13:32:07 -0700
Subject: [PATCH 2/4] Add a unit test for primeLibraryLoad.
---
clang/tools/offload-arch/AMDGPUArchByHIP.cpp | 9 +++--
.../offload-arch/OffloadArchTest.cpp | 38 +++++++++++++++++++
2 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/clang/tools/offload-arch/AMDGPUArchByHIP.cpp b/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
index db4387db3c0c7..b75471fec1c88 100644
--- a/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
+++ b/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
@@ -221,9 +221,12 @@ static std::pair<std::string, bool> findNewestHIPDLL() {
}
#ifdef _WIN32
+using LoadLibraryExWFn = decltype(&::LoadLibraryExW);
+
// Pre-load DLL with LOAD_WITH_ALTERED_SEARCH_PATH so transitive deps
// resolve from its directory. Pinned so getPermanentLibrary reuses it.
-static void primeLibraryLoad(StringRef Path) {
+void primeLibraryLoad(StringRef Path,
+ LoadLibraryExWFn Loader = &::LoadLibraryExW) {
// One DLL primed per process; subsequent calls are no-ops.
// Not thread-safe, but offload-arch is single-threaded.
static HMODULE PinnedModule = nullptr;
@@ -244,8 +247,8 @@ static void primeLibraryLoad(StringRef Path) {
if (!convertUTF8ToUTF16String(NativePath, WPath))
return;
WPath.push_back(0);
- PinnedModule = LoadLibraryExW(reinterpret_cast<LPCWSTR>(WPath.data()),
- nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
+ PinnedModule = Loader(reinterpret_cast<LPCWSTR>(WPath.data()), nullptr,
+ LOAD_WITH_ALTERED_SEARCH_PATH);
DWORD Err = GetLastError();
if (!PinnedModule && Verbose)
WithColor::note() << "priming LoadLibraryExW failed for " << Path
diff --git a/clang/unittests/offload-arch/OffloadArchTest.cpp b/clang/unittests/offload-arch/OffloadArchTest.cpp
index 4b07af39a918a..e845e7c8b434d 100644
--- a/clang/unittests/offload-arch/OffloadArchTest.cpp
+++ b/clang/unittests/offload-arch/OffloadArchTest.cpp
@@ -13,10 +13,17 @@
#include <algorithm>
#include <string>
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
// Defined in AMDGPUArchByHIP.cpp (non-static, compiled into this test).
#ifdef _WIN32
+using LoadLibraryExWFn = decltype(&::LoadLibraryExW);
+
bool compareVersions(llvm::StringRef A, llvm::StringRef B);
llvm::SmallVector<std::string, 8> getCandidateBinPaths(llvm::StringRef ExeDir);
+void primeLibraryLoad(llvm::StringRef Path, LoadLibraryExWFn Loader);
#endif
using namespace llvm;
@@ -25,6 +32,37 @@ cl::opt<bool> Verbose("offload-arch-test-verbose", cl::Hidden, cl::init(false));
#ifdef _WIN32
+// --- primeLibraryLoad ---
+
+namespace {
+std::wstring CapturedPath;
+HANDLE CapturedFile;
+DWORD CapturedFlags;
+
+HMODULE WINAPI mockLoadLibraryExW(LPCWSTR Path, HANDLE File, DWORD Flags) {
+ CapturedPath = Path;
+ CapturedFile = File;
+ CapturedFlags = Flags;
+ return reinterpret_cast<HMODULE>(1);
+}
+} // namespace
+
+TEST(PrimeLibraryLoad, UsesWindowsBackslashes) {
+ CapturedPath.clear();
+ CapturedFile = reinterpret_cast<HANDLE>(1);
+ CapturedFlags = 0;
+
+ primeLibraryLoad("C:/rocm\\bin/amdhip64_7.dll", mockLoadLibraryExW);
+
+ // The underlying LoadLibraryExW function should be called with backslashes,
+ // not forward slashes to ensure that it can discover and load dependent
+ // DLLs in the same directory.
+ EXPECT_EQ(CapturedPath, L"C:\\rocm\\bin\\amdhip64_7.dll");
+ EXPECT_EQ(CapturedPath.find(L'/'), std::wstring::npos);
+ EXPECT_EQ(CapturedFile, nullptr);
+ EXPECT_EQ(CapturedFlags, LOAD_WITH_ALTERED_SEARCH_PATH);
+}
+
// --- compareVersions ---
TEST(CompareVersions, HigherVersionWins) {
>From 8c4adee01c76afbfaa8e894dfb4fabe90e73acbe Mon Sep 17 00:00:00 2001
From: Scott Todd <Scott.Todd at amd.com>
Date: Wed, 15 Jul 2026 14:05:54 -0700
Subject: [PATCH 3/4] Format
---
clang/tools/offload-arch/AMDGPUArchByHIP.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/tools/offload-arch/AMDGPUArchByHIP.cpp b/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
index b75471fec1c88..220fc81021fb7 100644
--- a/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
+++ b/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
@@ -236,7 +236,8 @@ void primeLibraryLoad(StringRef Path,
// Other paths here use '/' for consistency, but LoadLibraryExW's altered
// search path needs '\\' to locate dependencies relative to the DLL.
//
- // See https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw
+ // See
+ // https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw
// "If the string specifies a fully qualified path, the function searches only
// that path for the module. When specifying a path, be sure to use
// backslashes (\), not forward slashes (/)."
>From f214c2d7b0e5c810de31402fa1b36a68d11f8528 Mon Sep 17 00:00:00 2001
From: Scott Todd <Scott.Todd at amd.com>
Date: Mon, 20 Jul 2026 08:13:22 -0700
Subject: [PATCH 4/4] Revert "Add a unit test for primeLibraryLoad."
This reverts commit eb808f79208862569915d3018294735a699c9d05.
---
clang/tools/offload-arch/AMDGPUArchByHIP.cpp | 9 ++---
.../offload-arch/OffloadArchTest.cpp | 38 -------------------
2 files changed, 3 insertions(+), 44 deletions(-)
diff --git a/clang/tools/offload-arch/AMDGPUArchByHIP.cpp b/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
index 220fc81021fb7..36c3c98002a31 100644
--- a/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
+++ b/clang/tools/offload-arch/AMDGPUArchByHIP.cpp
@@ -221,12 +221,9 @@ static std::pair<std::string, bool> findNewestHIPDLL() {
}
#ifdef _WIN32
-using LoadLibraryExWFn = decltype(&::LoadLibraryExW);
-
// Pre-load DLL with LOAD_WITH_ALTERED_SEARCH_PATH so transitive deps
// resolve from its directory. Pinned so getPermanentLibrary reuses it.
-void primeLibraryLoad(StringRef Path,
- LoadLibraryExWFn Loader = &::LoadLibraryExW) {
+static void primeLibraryLoad(StringRef Path) {
// One DLL primed per process; subsequent calls are no-ops.
// Not thread-safe, but offload-arch is single-threaded.
static HMODULE PinnedModule = nullptr;
@@ -248,8 +245,8 @@ void primeLibraryLoad(StringRef Path,
if (!convertUTF8ToUTF16String(NativePath, WPath))
return;
WPath.push_back(0);
- PinnedModule = Loader(reinterpret_cast<LPCWSTR>(WPath.data()), nullptr,
- LOAD_WITH_ALTERED_SEARCH_PATH);
+ PinnedModule = LoadLibraryExW(reinterpret_cast<LPCWSTR>(WPath.data()),
+ nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
DWORD Err = GetLastError();
if (!PinnedModule && Verbose)
WithColor::note() << "priming LoadLibraryExW failed for " << Path
diff --git a/clang/unittests/offload-arch/OffloadArchTest.cpp b/clang/unittests/offload-arch/OffloadArchTest.cpp
index e845e7c8b434d..4b07af39a918a 100644
--- a/clang/unittests/offload-arch/OffloadArchTest.cpp
+++ b/clang/unittests/offload-arch/OffloadArchTest.cpp
@@ -13,17 +13,10 @@
#include <algorithm>
#include <string>
-#ifdef _WIN32
-#include <windows.h>
-#endif
-
// Defined in AMDGPUArchByHIP.cpp (non-static, compiled into this test).
#ifdef _WIN32
-using LoadLibraryExWFn = decltype(&::LoadLibraryExW);
-
bool compareVersions(llvm::StringRef A, llvm::StringRef B);
llvm::SmallVector<std::string, 8> getCandidateBinPaths(llvm::StringRef ExeDir);
-void primeLibraryLoad(llvm::StringRef Path, LoadLibraryExWFn Loader);
#endif
using namespace llvm;
@@ -32,37 +25,6 @@ cl::opt<bool> Verbose("offload-arch-test-verbose", cl::Hidden, cl::init(false));
#ifdef _WIN32
-// --- primeLibraryLoad ---
-
-namespace {
-std::wstring CapturedPath;
-HANDLE CapturedFile;
-DWORD CapturedFlags;
-
-HMODULE WINAPI mockLoadLibraryExW(LPCWSTR Path, HANDLE File, DWORD Flags) {
- CapturedPath = Path;
- CapturedFile = File;
- CapturedFlags = Flags;
- return reinterpret_cast<HMODULE>(1);
-}
-} // namespace
-
-TEST(PrimeLibraryLoad, UsesWindowsBackslashes) {
- CapturedPath.clear();
- CapturedFile = reinterpret_cast<HANDLE>(1);
- CapturedFlags = 0;
-
- primeLibraryLoad("C:/rocm\\bin/amdhip64_7.dll", mockLoadLibraryExW);
-
- // The underlying LoadLibraryExW function should be called with backslashes,
- // not forward slashes to ensure that it can discover and load dependent
- // DLLs in the same directory.
- EXPECT_EQ(CapturedPath, L"C:\\rocm\\bin\\amdhip64_7.dll");
- EXPECT_EQ(CapturedPath.find(L'/'), std::wstring::npos);
- EXPECT_EQ(CapturedFile, nullptr);
- EXPECT_EQ(CapturedFlags, LOAD_WITH_ALTERED_SEARCH_PATH);
-}
-
// --- compareVersions ---
TEST(CompareVersions, HigherVersionWins) {
More information about the cfe-commits
mailing list