374 lines · cpp
1//===- AMDGPUArchByHIP.cpp - list AMDGPU installed ----------*- C++ -*-----===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file implements a tool for detecting name of AMDGPU installed in system10// using HIP runtime. This tool is used by AMDGPU OpenMP and HIP driver.11//12//===----------------------------------------------------------------------===//13 14#include "llvm/ADT/STLExtras.h"15#include "llvm/ADT/Sequence.h"16#include "llvm/Support/CommandLine.h"17#include "llvm/Support/ConvertUTF.h"18#include "llvm/Support/DynamicLibrary.h"19#include "llvm/Support/Error.h"20#include "llvm/Support/FileSystem.h"21#include "llvm/Support/Path.h"22#include "llvm/Support/Process.h"23#include "llvm/Support/Program.h"24#include "llvm/Support/VersionTuple.h"25#include "llvm/Support/raw_ostream.h"26#include <algorithm>27#include <string>28#include <vector>29 30#ifdef _WIN3231#include <windows.h>32#endif33 34using namespace llvm;35 36// R0600 struct layout (HIP 6.x+)37typedef struct alignas(8) {38 char padding[1160];39 char gcnArchName[256];40 char padding2[56];41} hipDeviceProp_tR0600;42 43// R0000 struct layout (legacy)44typedef struct alignas(8) {45 char padding[396];46 char gcnArchName[256];47 char padding2[1024];48} hipDeviceProp_tR0000;49 50typedef enum {51 hipSuccess = 0,52} hipError_t;53 54typedef hipError_t (*hipGetDeviceCount_t)(int *);55typedef hipError_t (*hipGetDevicePropertiesR0600_t)(hipDeviceProp_tR0600 *,56 int);57typedef hipError_t (*hipGetDevicePropertiesR0000_t)(hipDeviceProp_tR0000 *,58 int);59typedef hipError_t (*hipGetDeviceProperties_t)(hipDeviceProp_tR0000 *, int);60typedef hipError_t (*hipRuntimeGetVersion_t)(int *);61typedef const char *(*hipGetErrorString_t)(hipError_t);62 63extern cl::opt<bool> Verbose;64 65cl::OptionCategory AMDGPUArchByHIPCategory("amdgpu-arch (HIP) options");66 67enum class HipApiVersion {68 Auto, // Automatic fallback (R0600 -> R0000 -> unversioned)69 R0600, // Force R0600 API (HIP 6.x+)70 R0000, // Force R0000 API (legacy HIP)71 Unversioned // Force unversioned API (very old HIP)72};73 74static cl::opt<HipApiVersion> HipApi(75 "hip-api-version", cl::desc("Select HIP API version for device properties"),76 cl::values(clEnumValN(HipApiVersion::Auto, "auto",77 "Auto-detect (R0600 -> R0000 -> unversioned)"),78 clEnumValN(HipApiVersion::R0600, "r0600", "Force R0600 API"),79 clEnumValN(HipApiVersion::R0000, "r0000", "Force R0000 API"),80 clEnumValN(HipApiVersion::Unversioned, "unversioned",81 "Force unversioned API")),82 cl::init(HipApiVersion::Auto), cl::cat(AMDGPUArchByHIPCategory));83 84#ifdef _WIN3285static std::vector<std::string> getSearchPaths() {86 std::vector<std::string> Paths;87 88 // Get the directory of the current executable89 if (auto MainExe = sys::fs::getMainExecutable(nullptr, nullptr);90 !MainExe.empty())91 Paths.push_back(sys::path::parent_path(MainExe).str());92 93 // Get the system directory94 wchar_t SystemDirectory[MAX_PATH];95 if (GetSystemDirectoryW(SystemDirectory, MAX_PATH) > 0) {96 std::string Utf8SystemDir;97 if (convertUTF16ToUTF8String(98 ArrayRef<UTF16>(reinterpret_cast<const UTF16 *>(SystemDirectory),99 wcslen(SystemDirectory)),100 Utf8SystemDir))101 Paths.push_back(Utf8SystemDir);102 }103 104 // Get the Windows directory105 wchar_t WindowsDirectory[MAX_PATH];106 if (GetWindowsDirectoryW(WindowsDirectory, MAX_PATH) > 0) {107 std::string Utf8WindowsDir;108 if (convertUTF16ToUTF8String(109 ArrayRef<UTF16>(reinterpret_cast<const UTF16 *>(WindowsDirectory),110 wcslen(WindowsDirectory)),111 Utf8WindowsDir))112 Paths.push_back(Utf8WindowsDir);113 }114 115 // Get the current working directory116 SmallVector<char, 256> CWD;117 if (sys::fs::current_path(CWD))118 Paths.push_back(std::string(CWD.begin(), CWD.end()));119 120 // Get the PATH environment variable121 if (std::optional<std::string> PathEnv = sys::Process::GetEnv("PATH")) {122 SmallVector<StringRef, 16> PathList;123 StringRef(*PathEnv).split(PathList, sys::EnvPathSeparator);124 for (auto &Path : PathList)125 Paths.push_back(Path.str());126 }127 128 return Paths;129}130 131// Custom comparison function for dll name132static bool compareVersions(StringRef A, StringRef B) {133 auto ParseVersion = [](StringRef S) -> VersionTuple {134 StringRef Filename = sys::path::filename(S);135 size_t Pos = Filename.find_last_of('_');136 if (Pos == StringRef::npos)137 return VersionTuple();138 139 StringRef VerStr = Filename.substr(Pos + 1);140 size_t DotPos = VerStr.find('.');141 if (DotPos != StringRef::npos)142 VerStr = VerStr.substr(0, DotPos);143 144 VersionTuple Vt;145 (void)Vt.tryParse(VerStr);146 return Vt;147 };148 149 VersionTuple VtA = ParseVersion(A);150 VersionTuple VtB = ParseVersion(B);151 return VtA > VtB;152}153#endif154 155// On Windows, prefer amdhip64_n.dll where n is ROCm major version and greater156// value of n takes precedence. If amdhip64_n.dll is not found, fall back to157// amdhip64.dll. The reason is that a normal driver installation only has158// amdhip64_n.dll but we do not know what n is since this program may be used159// with a future version of HIP runtime.160//161// On Linux, always use default libamdhip64.so.162static std::pair<std::string, bool> findNewestHIPDLL() {163#ifdef _WIN32164 StringRef HipDLLPrefix = "amdhip64_";165 StringRef HipDLLSuffix = ".dll";166 167 std::vector<std::string> SearchPaths = getSearchPaths();168 std::vector<std::string> DLLNames;169 170 for (const auto &Dir : SearchPaths) {171 std::error_code EC;172 for (sys::fs::directory_iterator DirIt(Dir, EC), DirEnd;173 DirIt != DirEnd && !EC; DirIt.increment(EC)) {174 StringRef Filename = sys::path::filename(DirIt->path());175 if (Filename.starts_with(HipDLLPrefix) &&176 Filename.ends_with(HipDLLSuffix))177 DLLNames.push_back(sys::path::convert_to_slash(DirIt->path()));178 }179 }180 181 if (DLLNames.empty())182 return {"amdhip64.dll", true};183 184 llvm::sort(DLLNames, compareVersions);185 return {DLLNames[0], false};186#else187 // On Linux, fallback to default shared object188 return {"libamdhip64.so", true};189#endif190}191 192int printGPUsByHIP() {193 auto [DynamicHIPPath, IsFallback] = findNewestHIPDLL();194 195 if (Verbose) {196 if (IsFallback)197 outs() << "Using default HIP runtime: " << DynamicHIPPath << '\n';198 else199 outs() << "Found HIP runtime: " << DynamicHIPPath << '\n';200 }201 202 std::string ErrMsg;203 auto DynlibHandle = std::make_unique<llvm::sys::DynamicLibrary>(204 llvm::sys::DynamicLibrary::getPermanentLibrary(DynamicHIPPath.c_str(),205 &ErrMsg));206 if (!DynlibHandle->isValid()) {207 if (Verbose)208 llvm::errs() << "Failed to load " << DynamicHIPPath << ": " << ErrMsg209 << '\n';210 return 1;211 }212 213 if (Verbose)214 outs() << "Successfully loaded HIP runtime library\n";215 216#define DYNAMIC_INIT_HIP(SYMBOL) \217 { \218 void *SymbolPtr = DynlibHandle->getAddressOfSymbol(#SYMBOL); \219 if (!SymbolPtr) { \220 llvm::errs() << "Failed to find symbol " << #SYMBOL << '\n'; \221 return 1; \222 } \223 if (Verbose) \224 outs() << "Found symbol: " << #SYMBOL << '\n'; \225 SYMBOL = reinterpret_cast<decltype(SYMBOL)>(SymbolPtr); \226 }227 228 hipGetDeviceCount_t hipGetDeviceCount;229 hipRuntimeGetVersion_t hipRuntimeGetVersion = nullptr;230 hipGetDevicePropertiesR0600_t hipGetDevicePropertiesR0600 = nullptr;231 hipGetDevicePropertiesR0000_t hipGetDevicePropertiesR0000 = nullptr;232 hipGetDeviceProperties_t hipGetDeviceProperties = nullptr;233 hipGetErrorString_t hipGetErrorString = nullptr;234 235 DYNAMIC_INIT_HIP(hipGetDeviceCount);236 237#undef DYNAMIC_INIT_HIP238 239 auto LoadSymbol = [&](const char *Name, auto &FuncPtr,240 const char *Desc = "") {241 void *Sym = DynlibHandle->getAddressOfSymbol(Name);242 if (Sym) {243 FuncPtr = reinterpret_cast<decltype(FuncPtr)>(Sym);244 if (Verbose)245 outs() << "Found symbol: " << Name << (Desc[0] ? " " : "") << Desc246 << '\n';247 return true;248 }249 return false;250 };251 252 LoadSymbol("hipGetErrorString", hipGetErrorString);253 254 if (LoadSymbol("hipRuntimeGetVersion", hipRuntimeGetVersion)) {255 int RuntimeVersion = 0;256 if (hipRuntimeGetVersion(&RuntimeVersion) == hipSuccess) {257 int Major = RuntimeVersion / 10000000;258 int Minor = (RuntimeVersion / 100000) % 100;259 int Patch = RuntimeVersion % 100000;260 if (Verbose)261 outs() << "HIP Runtime Version: " << Major << "." << Minor << "."262 << Patch << '\n';263 }264 }265 266 LoadSymbol("hipGetDevicePropertiesR0600", hipGetDevicePropertiesR0600,267 "(HIP 6.x+ API)");268 LoadSymbol("hipGetDevicePropertiesR0000", hipGetDevicePropertiesR0000,269 "(legacy API)");270 if (!hipGetDevicePropertiesR0600 && !hipGetDevicePropertiesR0000)271 LoadSymbol("hipGetDeviceProperties", hipGetDeviceProperties,272 "(unversioned legacy API)");273 274 int DeviceCount;275 if (Verbose)276 outs() << "Calling hipGetDeviceCount...\n";277 hipError_t Err = hipGetDeviceCount(&DeviceCount);278 if (Err != hipSuccess) {279 llvm::errs() << "Failed to get device count";280 if (hipGetErrorString) {281 llvm::errs() << ": " << hipGetErrorString(Err);282 }283 llvm::errs() << " (error code: " << Err << ")\n";284 return 1;285 }286 287 if (Verbose)288 outs() << "Found " << DeviceCount << " device(s)\n";289 290 auto TryGetProperties = [&](auto *ApiFunc, auto *DummyProp, const char *Name,291 int DeviceId) -> std::string {292 if (!ApiFunc)293 return "";294 295 if (Verbose)296 outs() << "Using " << Name << "...\n";297 298 using PropType = std::remove_pointer_t<decltype(DummyProp)>;299 PropType Prop;300 hipError_t Err = ApiFunc(&Prop, DeviceId);301 302 if (Err == hipSuccess) {303 if (Verbose) {304 outs() << Name << " struct: sizeof = " << sizeof(PropType)305 << " bytes, offsetof(gcnArchName) = "306 << offsetof(PropType, gcnArchName) << " bytes\n";307 }308 return Prop.gcnArchName;309 }310 311 if (Verbose)312 llvm::errs() << Name << " failed (error code: " << Err << ")\n";313 return "";314 };315 316 for (auto I : llvm::seq(DeviceCount)) {317 if (Verbose)318 outs() << "Processing device " << I << "...\n";319 320 std::string ArchName;321 auto TryR0600 = [&](int Dev) -> bool {322 if (!hipGetDevicePropertiesR0600)323 return false;324 ArchName = TryGetProperties(hipGetDevicePropertiesR0600,325 (hipDeviceProp_tR0600 *)nullptr,326 "R0600 API (HIP 6.x+)", Dev);327 return !ArchName.empty();328 };329 auto TryR0000 = [&](int Dev) -> bool {330 if (!hipGetDevicePropertiesR0000)331 return false;332 ArchName = TryGetProperties(hipGetDevicePropertiesR0000,333 (hipDeviceProp_tR0000 *)nullptr,334 "R0000 API (legacy HIP)", Dev);335 return !ArchName.empty();336 };337 auto TryUnversioned = [&](int Dev) -> bool {338 if (!hipGetDeviceProperties)339 return false;340 ArchName = TryGetProperties(hipGetDeviceProperties,341 (hipDeviceProp_tR0000 *)nullptr,342 "unversioned API (very old HIP)", Dev);343 return !ArchName.empty();344 };345 346 [[maybe_unused]] bool OK;347 switch (HipApi) {348 case HipApiVersion::Auto:349 OK = TryR0600(I) || TryR0000(I) || TryUnversioned(I);350 break;351 case HipApiVersion::R0600:352 OK = TryR0600(I);353 break;354 case HipApiVersion::R0000:355 OK = TryR0000(I);356 break;357 case HipApiVersion::Unversioned:358 OK = TryUnversioned(I);359 }360 361 if (ArchName.empty()) {362 llvm::errs() << "Failed to get device properties for device " << I363 << " - no APIs available or all failed\n";364 return 1;365 }366 367 if (Verbose)368 outs() << "Device " << I << " arch name: ";369 llvm::outs() << ArchName << '\n';370 }371 372 return 0;373}374