//===- 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 #include #include #ifdef _WIN32 #include #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 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 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 getSearchPaths() { std::vector 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(reinterpret_cast(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(reinterpret_cast(WindowsDirectory), wcslen(WindowsDirectory)), Utf8WindowsDir)) Paths.push_back(Utf8WindowsDir); } // Get the current working directory SmallVector CWD; if (sys::fs::current_path(CWD)) Paths.push_back(std::string(CWD.begin(), CWD.end())); // Get the PATH environment variable if (std::optional PathEnv = sys::Process::GetEnv("PATH")) { SmallVector 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 findNewestHIPDLL() { #ifdef _WIN32 StringRef HipDLLPrefix = "amdhip64_"; StringRef HipDLLSuffix = ".dll"; std::vector SearchPaths = getSearchPaths(); std::vector 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::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(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(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; 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; }