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-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements a tool for detecting name of AMDGPU installed in system
10// 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/Support/CommandLine.h"
16#include "llvm/Support/ConvertUTF.h"
17#include "llvm/Support/DynamicLibrary.h"
18#include "llvm/Support/Error.h"
19#include "llvm/Support/FileSystem.h"
20#include "llvm/Support/Path.h"
21#include "llvm/Support/Process.h"
22#include "llvm/Support/Program.h"
23#include "llvm/Support/VersionTuple.h"
24#include "llvm/Support/raw_ostream.h"
25#include <algorithm>
26#include <string>
27#include <vector>
28
29#ifdef _WIN32
30#include <windows.h>
31#endif
32
33using namespace llvm;
34
35typedef struct {
36 char padding[396];
37 char gcnArchName[256];
38 char padding2[1024];
39} hipDeviceProp_t;
40
41typedef enum {
42 hipSuccess = 0,
43} hipError_t;
44
45typedef hipError_t (*hipGetDeviceCount_t)(int *);
46typedef hipError_t (*hipDeviceGet_t)(int *, int);
47typedef hipError_t (*hipGetDeviceProperties_t)(hipDeviceProp_t *, int);
48
49extern cl::opt<bool> Verbose;
50
51#ifdef _WIN32
52static std::vector<std::string> getSearchPaths() {
53 std::vector<std::string> Paths;
54
55 // Get the directory of the current executable
56 if (auto MainExe = sys::fs::getMainExecutable(nullptr, nullptr);
57 !MainExe.empty())
58 Paths.push_back(sys::path::parent_path(MainExe).str());
59
60 // Get the system directory
61 wchar_t SystemDirectory[MAX_PATH];
62 if (GetSystemDirectoryW(SystemDirectory, MAX_PATH) > 0) {
63 std::string Utf8SystemDir;
64 if (convertUTF16ToUTF8String(
65 ArrayRef<UTF16>(reinterpret_cast<const UTF16 *>(SystemDirectory),
66 wcslen(SystemDirectory)),
67 Utf8SystemDir))
68 Paths.push_back(Utf8SystemDir);
69 }
70
71 // Get the Windows directory
72 wchar_t WindowsDirectory[MAX_PATH];
73 if (GetWindowsDirectoryW(WindowsDirectory, MAX_PATH) > 0) {
74 std::string Utf8WindowsDir;
75 if (convertUTF16ToUTF8String(
76 ArrayRef<UTF16>(reinterpret_cast<const UTF16 *>(WindowsDirectory),
77 wcslen(WindowsDirectory)),
78 Utf8WindowsDir))
79 Paths.push_back(Utf8WindowsDir);
80 }
81
82 // Get the current working directory
83 SmallVector<char, 256> CWD;
84 if (sys::fs::current_path(CWD))
85 Paths.push_back(std::string(CWD.begin(), CWD.end()));
86
87 // Get the PATH environment variable
88 if (std::optional<std::string> PathEnv = sys::Process::GetEnv("PATH")) {
89 SmallVector<StringRef, 16> PathList;
90 StringRef(*PathEnv).split(PathList, sys::EnvPathSeparator);
91 for (auto &Path : PathList)
92 Paths.push_back(Path.str());
93 }
94
95 return Paths;
96}
97
98// Custom comparison function for dll name
99static bool compareVersions(StringRef A, StringRef B) {
100 auto ParseVersion = [](StringRef S) -> VersionTuple {
101 size_t Pos = S.find_last_of('_');
102 StringRef VerStr = (Pos == StringRef::npos) ? S : S.substr(Pos + 1);
103 VersionTuple Vt;
104 (void)Vt.tryParse(VerStr);
105 return Vt;
106 };
107
108 VersionTuple VtA = ParseVersion(A);
109 VersionTuple VtB = ParseVersion(B);
110 return VtA > VtB;
111}
112#endif
113
114// On Windows, prefer amdhip64_n.dll where n is ROCm major version and greater
115// value of n takes precedence. If amdhip64_n.dll is not found, fall back to
116// amdhip64.dll. The reason is that a normal driver installation only has
117// amdhip64_n.dll but we do not know what n is since this program may be used
118// with a future version of HIP runtime.
119//
120// On Linux, always use default libamdhip64.so.
121static std::pair<std::string, bool> findNewestHIPDLL() {
122#ifdef _WIN32
123 StringRef HipDLLPrefix = "amdhip64_";
124 StringRef HipDLLSuffix = ".dll";
125
126 std::vector<std::string> SearchPaths = getSearchPaths();
127 std::vector<std::string> DLLNames;
128
129 for (const auto &Dir : SearchPaths) {
130 std::error_code EC;
131 for (sys::fs::directory_iterator DirIt(Dir, EC), DirEnd;
132 DirIt != DirEnd && !EC; DirIt.increment(EC)) {
133 StringRef Filename = sys::path::filename(DirIt->path());
134 if (Filename.starts_with(HipDLLPrefix) &&
135 Filename.ends_with(HipDLLSuffix))
136 DLLNames.push_back(sys::path::convert_to_slash(DirIt->path()));
137 }
138 if (!DLLNames.empty())
139 break;
140 }
141
142 if (DLLNames.empty())
143 return {"amdhip64.dll", true};
144
145 llvm::sort(DLLNames, compareVersions);
146 return {DLLNames[0], false};
147#else
148 // On Linux, fallback to default shared object
149 return {"libamdhip64.so", true};
150#endif
151}
152
153int printGPUsByHIP() {
154 auto [DynamicHIPPath, IsFallback] = findNewestHIPDLL();
155
156 if (Verbose) {
157 if (IsFallback)
158 outs() << "Using default HIP runtime: " << DynamicHIPPath << '\n';
159 else
160 outs() << "Found HIP runtime: " << DynamicHIPPath << '\n';
161 }
162
163 std::string ErrMsg;
164 auto DynlibHandle = std::make_unique<llvm::sys::DynamicLibrary>(
165 args: llvm::sys::DynamicLibrary::getPermanentLibrary(filename: DynamicHIPPath.c_str(),
166 errMsg: &ErrMsg));
167 if (!DynlibHandle->isValid()) {
168 llvm::errs() << "Failed to load " << DynamicHIPPath << ": " << ErrMsg
169 << '\n';
170 return 1;
171 }
172
173#define DYNAMIC_INIT_HIP(SYMBOL) \
174 { \
175 void *SymbolPtr = DynlibHandle->getAddressOfSymbol(#SYMBOL); \
176 if (!SymbolPtr) { \
177 llvm::errs() << "Failed to find symbol " << #SYMBOL << '\n'; \
178 return 1; \
179 } \
180 SYMBOL = reinterpret_cast<decltype(SYMBOL)>(SymbolPtr); \
181 }
182
183 hipGetDeviceCount_t hipGetDeviceCount;
184 hipDeviceGet_t hipDeviceGet;
185 hipGetDeviceProperties_t hipGetDeviceProperties;
186
187 DYNAMIC_INIT_HIP(hipGetDeviceCount);
188 DYNAMIC_INIT_HIP(hipDeviceGet);
189 DYNAMIC_INIT_HIP(hipGetDeviceProperties);
190
191#undef DYNAMIC_INIT_HIP
192
193 int deviceCount;
194 hipError_t err = hipGetDeviceCount(&deviceCount);
195 if (err != hipSuccess) {
196 llvm::errs() << "Failed to get device count\n";
197 return 1;
198 }
199
200 for (int i = 0; i < deviceCount; ++i) {
201 int deviceId;
202 err = hipDeviceGet(&deviceId, i);
203 if (err != hipSuccess) {
204 llvm::errs() << "Failed to get device id for ordinal " << i << '\n';
205 return 1;
206 }
207
208 hipDeviceProp_t prop;
209 err = hipGetDeviceProperties(&prop, deviceId);
210 if (err != hipSuccess) {
211 llvm::errs() << "Failed to get device properties for device " << deviceId
212 << '\n';
213 return 1;
214 }
215 llvm::outs() << prop.gcnArchName << '\n';
216 }
217
218 return 0;
219}
220