blob: d7f6d79b135df458ca3ce56eb8e4830a1dc4f455 [file] [log] [blame] [edit]
//===- AMDGPUArchByHIP.cpp - list AMDGPU installed ----------*- C++ -*-----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements a tool for detecting name of AMDGPU installed in system
// using HIP runtime. This tool is used by AMDGPU OpenMP and HIP driver.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/DynamicLibrary.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <string>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#endif
using namespace llvm;
// R0600 struct layout (HIP 6.x+)
typedef struct alignas(8) {
char padding[1160];
char gcnArchName[256];
char padding2[56];
} hipDeviceProp_tR0600;
// R0000 struct layout (legacy)
typedef struct alignas(8) {
char padding[396];
char gcnArchName[256];
char padding2[1024];
} hipDeviceProp_tR0000;
typedef enum {
hipSuccess = 0,
} hipError_t;
typedef hipError_t (*hipGetDeviceCount_t)(int *);
typedef hipError_t (*hipGetDevicePropertiesR0600_t)(hipDeviceProp_tR0600 *,
int);
typedef hipError_t (*hipGetDevicePropertiesR0000_t)(hipDeviceProp_tR0000 *,
int);
typedef hipError_t (*hipGetDeviceProperties_t)(hipDeviceProp_tR0000 *, int);
typedef hipError_t (*hipRuntimeGetVersion_t)(int *);
typedef const char *(*hipGetErrorString_t)(hipError_t);
extern cl::opt<bool> Verbose;
cl::OptionCategory AMDGPUArchByHIPCategory("amdgpu-arch (HIP) options");
enum class HipApiVersion {
Auto, // Automatic fallback (R0600 -> R0000 -> unversioned)
R0600, // Force R0600 API (HIP 6.x+)
R0000, // Force R0000 API (legacy HIP)
Unversioned // Force unversioned API (very old HIP)
};
static cl::opt<HipApiVersion> HipApi(
"hip-api-version", cl::desc("Select HIP API version for device properties"),
cl::values(clEnumValN(HipApiVersion::Auto, "auto",
"Auto-detect (R0600 -> R0000 -> unversioned)"),
clEnumValN(HipApiVersion::R0600, "r0600", "Force R0600 API"),
clEnumValN(HipApiVersion::R0000, "r0000", "Force R0000 API"),
clEnumValN(HipApiVersion::Unversioned, "unversioned",
"Force unversioned API")),
cl::init(HipApiVersion::Auto), cl::cat(AMDGPUArchByHIPCategory));
#ifdef _WIN32
static std::vector<std::string> getSearchPaths() {
std::vector<std::string> Paths;
// Get the directory of the current executable
if (auto MainExe = sys::fs::getMainExecutable(nullptr, nullptr);
!MainExe.empty())
Paths.push_back(sys::path::parent_path(MainExe).str());
// Get the system directory
wchar_t SystemDirectory[MAX_PATH];
if (GetSystemDirectoryW(SystemDirectory, MAX_PATH) > 0) {
std::string Utf8SystemDir;
if (convertUTF16ToUTF8String(
ArrayRef<UTF16>(reinterpret_cast<const UTF16 *>(SystemDirectory),
wcslen(SystemDirectory)),
Utf8SystemDir))
Paths.push_back(Utf8SystemDir);
}
// Get the Windows directory
wchar_t WindowsDirectory[MAX_PATH];
if (GetWindowsDirectoryW(WindowsDirectory, MAX_PATH) > 0) {
std::string Utf8WindowsDir;
if (convertUTF16ToUTF8String(
ArrayRef<UTF16>(reinterpret_cast<const UTF16 *>(WindowsDirectory),
wcslen(WindowsDirectory)),
Utf8WindowsDir))
Paths.push_back(Utf8WindowsDir);
}
// Get the current working directory
SmallVector<char, 256> CWD;
if (sys::fs::current_path(CWD))
Paths.push_back(std::string(CWD.begin(), CWD.end()));
// Get the PATH environment variable
if (std::optional<std::string> PathEnv = sys::Process::GetEnv("PATH")) {
SmallVector<StringRef, 16> PathList;
StringRef(*PathEnv).split(PathList, sys::EnvPathSeparator);
for (auto &Path : PathList)
Paths.push_back(Path.str());
}
return Paths;
}
// Custom comparison function for dll name
static bool compareVersions(StringRef A, StringRef B) {
auto ParseVersion = [](StringRef S) -> VersionTuple {
StringRef Filename = sys::path::filename(S);
size_t Pos = Filename.find_last_of('_');
if (Pos == StringRef::npos)
return VersionTuple();
StringRef VerStr = Filename.substr(Pos + 1);
size_t DotPos = VerStr.find('.');
if (DotPos != StringRef::npos)
VerStr = VerStr.substr(0, DotPos);
VersionTuple Vt;
(void)Vt.tryParse(VerStr);
return Vt;
};
VersionTuple VtA = ParseVersion(A);
VersionTuple VtB = ParseVersion(B);
return VtA > VtB;
}
#endif
// On Windows, prefer amdhip64_n.dll where n is ROCm major version and greater
// value of n takes precedence. If amdhip64_n.dll is not found, fall back to
// amdhip64.dll. The reason is that a normal driver installation only has
// amdhip64_n.dll but we do not know what n is since this program may be used
// with a future version of HIP runtime.
//
// On Linux, always use default libamdhip64.so.
static std::pair<std::string, bool> findNewestHIPDLL() {
#ifdef _WIN32
StringRef HipDLLPrefix = "amdhip64_";
StringRef HipDLLSuffix = ".dll";
std::vector<std::string> SearchPaths = getSearchPaths();
std::vector<std::string> DLLNames;
for (const auto &Dir : SearchPaths) {
std::error_code EC;
for (sys::fs::directory_iterator DirIt(Dir, EC), DirEnd;
DirIt != DirEnd && !EC; DirIt.increment(EC)) {
StringRef Filename = sys::path::filename(DirIt->path());
if (Filename.starts_with(HipDLLPrefix) &&
Filename.ends_with(HipDLLSuffix))
DLLNames.push_back(sys::path::convert_to_slash(DirIt->path()));
}
}
if (DLLNames.empty())
return {"amdhip64.dll", true};
llvm::sort(DLLNames, compareVersions);
return {DLLNames[0], false};
#else
// On Linux, fallback to default shared object
return {"libamdhip64.so", true};
#endif
}
int printGPUsByHIP() {
auto [DynamicHIPPath, IsFallback] = findNewestHIPDLL();
if (Verbose) {
if (IsFallback)
outs() << "Using default HIP runtime: " << DynamicHIPPath << '\n';
else
outs() << "Found HIP runtime: " << DynamicHIPPath << '\n';
}
std::string ErrMsg;
auto DynlibHandle = std::make_unique<llvm::sys::DynamicLibrary>(
llvm::sys::DynamicLibrary::getPermanentLibrary(DynamicHIPPath.c_str(),
&ErrMsg));
if (!DynlibHandle->isValid()) {
if (Verbose)
llvm::errs() << "Failed to load " << DynamicHIPPath << ": " << ErrMsg
<< '\n';
return 1;
}
if (Verbose)
outs() << "Successfully loaded HIP runtime library\n";
#define DYNAMIC_INIT_HIP(SYMBOL) \
{ \
void *SymbolPtr = DynlibHandle->getAddressOfSymbol(#SYMBOL); \
if (!SymbolPtr) { \
llvm::errs() << "Failed to find symbol " << #SYMBOL << '\n'; \
return 1; \
} \
if (Verbose) \
outs() << "Found symbol: " << #SYMBOL << '\n'; \
SYMBOL = reinterpret_cast<decltype(SYMBOL)>(SymbolPtr); \
}
hipGetDeviceCount_t hipGetDeviceCount;
hipRuntimeGetVersion_t hipRuntimeGetVersion = nullptr;
hipGetDevicePropertiesR0600_t hipGetDevicePropertiesR0600 = nullptr;
hipGetDevicePropertiesR0000_t hipGetDevicePropertiesR0000 = nullptr;
hipGetDeviceProperties_t hipGetDeviceProperties = nullptr;
hipGetErrorString_t hipGetErrorString = nullptr;
DYNAMIC_INIT_HIP(hipGetDeviceCount);
#undef DYNAMIC_INIT_HIP
auto LoadSymbol = [&](const char *Name, auto &FuncPtr,
const char *Desc = "") {
void *Sym = DynlibHandle->getAddressOfSymbol(Name);
if (Sym) {
FuncPtr = reinterpret_cast<decltype(FuncPtr)>(Sym);
if (Verbose)
outs() << "Found symbol: " << Name << (Desc[0] ? " " : "") << Desc
<< '\n';
return true;
}
return false;
};
LoadSymbol("hipGetErrorString", hipGetErrorString);
if (LoadSymbol("hipRuntimeGetVersion", hipRuntimeGetVersion)) {
int RuntimeVersion = 0;
if (hipRuntimeGetVersion(&RuntimeVersion) == hipSuccess) {
int Major = RuntimeVersion / 10000000;
int Minor = (RuntimeVersion / 100000) % 100;
int Patch = RuntimeVersion % 100000;
if (Verbose)
outs() << "HIP Runtime Version: " << Major << "." << Minor << "."
<< Patch << '\n';
}
}
LoadSymbol("hipGetDevicePropertiesR0600", hipGetDevicePropertiesR0600,
"(HIP 6.x+ API)");
LoadSymbol("hipGetDevicePropertiesR0000", hipGetDevicePropertiesR0000,
"(legacy API)");
if (!hipGetDevicePropertiesR0600 && !hipGetDevicePropertiesR0000)
LoadSymbol("hipGetDeviceProperties", hipGetDeviceProperties,
"(unversioned legacy API)");
int DeviceCount;
if (Verbose)
outs() << "Calling hipGetDeviceCount...\n";
hipError_t Err = hipGetDeviceCount(&DeviceCount);
if (Err != hipSuccess) {
llvm::errs() << "Failed to get device count";
if (hipGetErrorString) {
llvm::errs() << ": " << hipGetErrorString(Err);
}
llvm::errs() << " (error code: " << Err << ")\n";
return 1;
}
if (Verbose)
outs() << "Found " << DeviceCount << " device(s)\n";
auto TryGetProperties = [&](auto *ApiFunc, auto *DummyProp, const char *Name,
int DeviceId) -> std::string {
if (!ApiFunc)
return "";
if (Verbose)
outs() << "Using " << Name << "...\n";
using PropType = std::remove_pointer_t<decltype(DummyProp)>;
PropType Prop;
hipError_t Err = ApiFunc(&Prop, DeviceId);
if (Err == hipSuccess) {
if (Verbose) {
outs() << Name << " struct: sizeof = " << sizeof(PropType)
<< " bytes, offsetof(gcnArchName) = "
<< offsetof(PropType, gcnArchName) << " bytes\n";
}
return Prop.gcnArchName;
}
if (Verbose)
llvm::errs() << Name << " failed (error code: " << Err << ")\n";
return "";
};
for (auto I : llvm::seq(DeviceCount)) {
if (Verbose)
outs() << "Processing device " << I << "...\n";
std::string ArchName;
auto TryR0600 = [&](int Dev) -> bool {
if (!hipGetDevicePropertiesR0600)
return false;
ArchName = TryGetProperties(hipGetDevicePropertiesR0600,
(hipDeviceProp_tR0600 *)nullptr,
"R0600 API (HIP 6.x+)", Dev);
return !ArchName.empty();
};
auto TryR0000 = [&](int Dev) -> bool {
if (!hipGetDevicePropertiesR0000)
return false;
ArchName = TryGetProperties(hipGetDevicePropertiesR0000,
(hipDeviceProp_tR0000 *)nullptr,
"R0000 API (legacy HIP)", Dev);
return !ArchName.empty();
};
auto TryUnversioned = [&](int Dev) -> bool {
if (!hipGetDeviceProperties)
return false;
ArchName = TryGetProperties(hipGetDeviceProperties,
(hipDeviceProp_tR0000 *)nullptr,
"unversioned API (very old HIP)", Dev);
return !ArchName.empty();
};
[[maybe_unused]] bool OK;
switch (HipApi) {
case HipApiVersion::Auto:
OK = TryR0600(I) || TryR0000(I) || TryUnversioned(I);
break;
case HipApiVersion::R0600:
OK = TryR0600(I);
break;
case HipApiVersion::R0000:
OK = TryR0000(I);
break;
case HipApiVersion::Unversioned:
OK = TryUnversioned(I);
}
if (ArchName.empty()) {
llvm::errs() << "Failed to get device properties for device " << I
<< " - no APIs available or all failed\n";
return 1;
}
if (Verbose)
outs() << "Device " << I << " arch name: ";
llvm::outs() << ArchName << '\n';
}
return 0;
}