1//===--- HIPSPV.cpp - HIPSPV ToolChain Implementation -----------*- 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#include "HIPSPV.h"
10#include "HIPUtility.h"
11#include "clang/Driver/CommonArgs.h"
12#include "clang/Driver/Compilation.h"
13#include "clang/Driver/Driver.h"
14#include "clang/Driver/InputInfo.h"
15#include "clang/Options/Options.h"
16#include "llvm/MC/TargetRegistry.h"
17#include "llvm/Support/FileSystem.h"
18#include "llvm/Support/Path.h"
19
20using namespace clang::driver;
21using namespace clang::driver::toolchains;
22using namespace clang::driver::tools;
23using namespace clang;
24using namespace llvm::opt;
25
26// Locates HIP pass plugin.
27static std::string findPassPlugin(const Driver &D,
28 const llvm::opt::ArgList &Args) {
29 StringRef Path = Args.getLastArgValue(Id: options::OPT_hipspv_pass_plugin_EQ);
30 if (!Path.empty()) {
31 if (llvm::sys::fs::exists(Path))
32 return Path.str();
33 D.Diag(DiagID: diag::err_drv_no_such_file) << Path;
34 }
35
36 StringRef hipPath = Args.getLastArgValue(Id: options::OPT_hip_path_EQ);
37 if (!hipPath.empty()) {
38 SmallString<128> PluginPath(hipPath);
39 llvm::sys::path::append(path&: PluginPath, a: "lib", b: "libLLVMHipSpvPasses.so");
40 if (llvm::sys::fs::exists(Path: PluginPath))
41 return PluginPath.str().str();
42 PluginPath.assign(RHS: hipPath);
43 llvm::sys::path::append(path&: PluginPath, a: "lib", b: "llvm",
44 c: "libLLVMHipSpvPasses.so");
45 if (llvm::sys::fs::exists(Path: PluginPath))
46 return PluginPath.str().str();
47 }
48
49 return std::string();
50}
51
52// Is the in-tree SPIR-V backend built into this clang?
53static bool isSPIRVBackendAvailable(const llvm::Triple &T) {
54 std::string IgnoredError;
55 return llvm::TargetRegistry::lookupTarget(TheTriple: T, Error&: IgnoredError);
56}
57
58// Runs the HipSpvPasses plugin via `opt` on TempFile when the plugin is found.
59// Returns the lowered bitcode path, or TempFile unchanged if no plugin exists.
60static const char *runHipSpvPasses(Compilation &C, const JobAction &JA,
61 const Tool &Creator, const ToolChain &TC,
62 const InputInfoList &Inputs,
63 const InputInfo &Output,
64 const llvm::opt::ArgList &Args,
65 StringRef Name, const char *TempFile) {
66 auto PassPluginPath = findPassPlugin(D: C.getDriver(), Args);
67 if (PassPluginPath.empty())
68 return TempFile;
69 const char *PassPathCStr = C.getArgs().MakeArgString(Str: PassPluginPath);
70 const char *OptOutput = HIP::getTempFile(C, Prefix: Name.str() + "-lower", Extension: "bc");
71 ArgStringList OptArgs{TempFile, "-load-pass-plugin",
72 PassPathCStr, "-passes=hip-post-link-passes",
73 "-o", OptOutput};
74 const char *Opt = Args.MakeArgString(Str: TC.GetProgramPath(Name: "opt"));
75 C.addCommand(Cmd: std::make_unique<Command>(
76 args: JA, args: Creator, args: ResponseFileSupport::None(), args&: Opt, args&: OptArgs, args: Inputs, args: Output));
77 return OptOutput;
78}
79
80void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
81 Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
82 const InputInfo &Output, const llvm::opt::ArgList &Args) const {
83
84 assert(!Inputs.empty() && "Must have at least one input.");
85 std::string Name = std::string(llvm::sys::path::stem(path: Output.getFilename()));
86 const char *TempFile = HIP::getTempFile(C, Prefix: Name + "-link", Extension: "bc");
87
88 // Link LLVM bitcode.
89 ArgStringList LinkArgs{};
90
91 for (auto Input : Inputs)
92 if (Input.isFilename())
93 LinkArgs.push_back(Elt: Input.getFilename());
94
95 tools::constructLLVMLinkCommand(C, T: *this, JA, JobInputs: Inputs, LinkerInputs: LinkArgs, Output, Args,
96 OutputFilename: TempFile);
97
98 auto T = getToolChain().getTriple();
99
100 if (T.getOS() == llvm::Triple::ChipStar) {
101 // chipStar: run HipSpvPasses via opt, then emit SPIR-V with the in-tree
102 // SPIR-V backend by default, or with the external llvm-spirv translator
103 // when -fno-integrated-objemitter is given (or the backend is not built).
104
105 // Run HipSpvPasses plugin via opt (must run on LLVM IR before
106 // the SPIR-V backend lowers to MIR).
107 TempFile = runHipSpvPasses(C, JA, Creator: *this, TC: getToolChain(), Inputs, Output,
108 Args, Name, TempFile);
109
110 // Note that useIntegratedBackend() is consulted first so that an explicit
111 // -f(no-)integrated-objemitter still gets diagnosed against this toolchain.
112 if (!getToolChain().useIntegratedBackend() || !isSPIRVBackendAvailable(T)) {
113 // External translator path: BC -> SPIR-V via llvm-spirv.
114 llvm::opt::ArgStringList TrArgs;
115 if (T.getSubArch() == llvm::Triple::NoSubArch)
116 TrArgs.push_back(Elt: "--spirv-max-version=1.2");
117 // Keep this extension list in sync with the in-tree backend fallback
118 // below.
119 TrArgs.push_back(Elt: "--spirv-ext=-all"
120 ",+SPV_INTEL_function_pointers"
121 ",+SPV_INTEL_subgroups"
122 ",+SPV_KHR_bit_instructions"
123 ",+SPV_EXT_shader_atomic_float_add");
124
125 // Preserve debug info in the NonSemantic.Shader.DebugInfo form (see the
126 // comment on the equivalent block in the non-chipStar path below).
127 // These flags are passed unconditionally instead of gating on -g: in
128 // RDC-mode links this job runs in a clang invoked by
129 // clang-linker-wrapper where the original -g is not visible, but the
130 // debug info itself travels in the bitcode. SPV_KHR_non_semantic_info
131 // and the debug info version only take effect when the bitcode carries
132 // debug info. SPV_INTEL_optnone is not tied to debug info: clang emits
133 // optnone at -O0 even without -g, and the emitter needs the extension
134 // allowed to encode it.
135 TrArgs.push_back(Elt: "--spirv-ext=+SPV_KHR_non_semantic_info"
136 ",+SPV_INTEL_optnone");
137 TrArgs.push_back(Elt: "--spirv-debug-info-version=nonsemantic-shader-200");
138
139 InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
140 SPIRV::constructTranslateCommand(C, T: *this, JA, Output, Input: TrInput, Args: TrArgs);
141 return;
142 }
143
144 // Default: compile the lowered bitcode to SPIR-V with the in-tree backend.
145 // Invoke `clang -cc1` directly rather than the clang driver: the driver
146 // would re-run config-file loading, toolchain detection and argument
147 // translation over an input that is already device-compiled and lowered,
148 // which is both wasteful and fragile. This mirrors how HIPAMD drives its
149 // SPIR-V backend emission (see HIPAMD::constructLinkAndEmitSpirvCommand).
150 // Keep the default -O0 backend pipeline (i.e. no -disable-llvm-optzns) so
151 // the mandatory lowering passes still run, matching the previously
152 // validated driver `-c` behavior.
153 ArgStringList Cc1Args;
154 Cc1Args.push_back(Elt: "-cc1");
155 Cc1Args.push_back(Elt: "-triple");
156 Cc1Args.push_back(Elt: C.getArgs().MakeArgString(Str: T.getTriple()));
157 Cc1Args.push_back(Elt: "-emit-obj");
158
159 // SPIR-V extensions the chipStar runtime relies on. Keep in sync with the
160 // llvm-spirv translator path above. SPV_KHR_non_semantic_info and
161 // SPV_INTEL_optnone let the backend emit NonSemantic.Shader.DebugInfo and
162 // the OptNoneINTEL function control when the bitcode carries debug info /
163 // optnone attributes (the backend's debug handler is a no-op otherwise).
164 Cc1Args.push_back(Elt: "-mllvm");
165 Cc1Args.push_back(Elt: "-spirv-ext=+SPV_INTEL_function_pointers"
166 ",+SPV_INTEL_subgroups"
167 ",+SPV_KHR_bit_instructions"
168 ",+SPV_EXT_shader_atomic_float_add"
169 ",+SPV_KHR_non_semantic_info"
170 ",+SPV_INTEL_optnone");
171
172 Cc1Args.push_back(Elt: TempFile);
173 Cc1Args.push_back(Elt: "-o");
174 Cc1Args.push_back(Elt: Output.getFilename());
175
176 const Driver &Drv = C.getDriver();
177 const char *Clang = Drv.getDriverProgramPath();
178 C.addCommand(Cmd: std::make_unique<Command>(
179 args: JA, args: *this, args: ResponseFileSupport::None(), args&: Clang, args&: Cc1Args, args: Inputs, args: Output,
180 args: Drv.getPrependArg()));
181 return;
182 }
183
184 // Non-chipStar: run HIP passes via opt, then translate with llvm-spirv.
185 TempFile = runHipSpvPasses(C, JA, Creator: *this, TC: getToolChain(), Inputs, Output, Args,
186 Name, TempFile);
187
188 // Emit SPIR-V binary via llvm-spirv translator (non-chipStar targets).
189 llvm::opt::ArgStringList TrArgs;
190 if (T.getSubArch() == llvm::Triple::NoSubArch)
191 TrArgs.push_back(Elt: "--spirv-max-version=1.1");
192 TrArgs.push_back(Elt: "--spirv-ext=+all");
193
194 // Preserve debug info requested via -g into the emitted SPIR-V using the
195 // NonSemantic.Shader.DebugInfo form. Downstream consumers such as Intel's IGC
196 // and gdb-oneapi use it to map device code back to source lines and local
197 // variables; the translator's default OpenCL.DebugInfo.100 form is not
198 // sufficient for that. Emitting the NonSemantic debug instructions requires
199 // the SPV_KHR_non_semantic_info extension.
200 //
201 // SPV_INTEL_optnone carries the optnone function attribute, which Clang
202 // attaches to every function at -O0, through to the consumer as the
203 // OptNoneEXT function control. Without it the attribute is silently dropped
204 // in translation and the device compiler is free to optimize the kernel, so a
205 // debugger reports arguments and locals as <optimized out> even though the
206 // debug info itself is present. At -O1 and above no optnone attribute exists
207 // and the extension has no effect. It is redundant for the +all list above
208 // but required for the restricted chipStar one.
209 //
210 // The translator accumulates --spirv-ext across occurrences, so this augments
211 // the list set above.
212 if (const Arg *A = Args.getLastArg(Ids: options::OPT_g_Group);
213 A && !A->getOption().matches(ID: options::OPT_g0)) {
214 TrArgs.push_back(Elt: "--spirv-ext=+SPV_KHR_non_semantic_info"
215 ",+SPV_INTEL_optnone");
216 TrArgs.push_back(Elt: "--spirv-debug-info-version=nonsemantic-shader-200");
217 }
218
219 InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
220 SPIRV::constructTranslateCommand(C, T: *this, JA, Output, Input: TrInput, Args: TrArgs);
221}
222
223void HIPSPV::Linker::ConstructJob(Compilation &C, const JobAction &JA,
224 const InputInfo &Output,
225 const InputInfoList &Inputs,
226 const ArgList &Args,
227 const char *LinkingOutput) const {
228 if (JA.getType() == types::TY_HIP_FATBIN)
229 return HIP::constructHIPFatbinCommand(C, JA, OutputFileName: Output.getFilename(), Inputs,
230 TCArgs: Args, T: *this);
231
232 constructLinkAndEmitSpirvCommand(C, JA, Inputs, Output, Args);
233}
234
235HIPSPVToolChain::HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple,
236 const ToolChain &HostTC, const ArgList &Args)
237 : ToolChain(D, Triple, Args), HostTC(&HostTC) {
238 // Lookup binaries into the driver directory, this is used to
239 // discover the clang-offload-bundler executable.
240 getProgramPaths().push_back(Elt: getDriver().Dir);
241}
242
243// Non-offloading toolchain. Primaly used by clang-offload-linker.
244HIPSPVToolChain::HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple,
245 const ArgList &Args)
246 : ToolChain(D, Triple, Args), HostTC(nullptr) {
247 // Lookup binaries into the driver directory, this is used to
248 // discover the clang-offload-bundler executable.
249 getProgramPaths().push_back(Elt: getDriver().Dir);
250}
251
252bool HIPSPVToolChain::IsIntegratedBackendSupported() const {
253 // The in-tree SPIR-V backend can only be requested when it is built.
254 return isSPIRVBackendAvailable(T: getTriple());
255}
256
257void HIPSPVToolChain::addClangTargetOptions(
258 const llvm::opt::ArgList &DriverArgs, llvm::opt::ArgStringList &CC1Args,
259 BoundArch BA, Action::OffloadKind DeviceOffloadingKind) const {
260
261 if (!HostTC) {
262 assert(DeviceOffloadingKind == Action::OFK_None &&
263 "Need host toolchain for offloading!");
264 return;
265 }
266
267 HostTC->addClangTargetOptions(DriverArgs, CC1Args, BA, DeviceOffloadKind: DeviceOffloadingKind);
268
269 assert(DeviceOffloadingKind == Action::OFK_HIP &&
270 "Only HIP offloading kinds are supported for GPUs.");
271
272 CC1Args.append(
273 IL: {"-fcuda-is-device",
274 // A crude workaround for llvm-spirv which does not handle the
275 // autovectorized code well (vector reductions, non-i{8,16,32,64} types).
276 // TODO: Allow autovectorization when SPIR-V backend arrives.
277 "-mllvm", "-vectorize-loops=false", "-mllvm", "-vectorize-slp=false"});
278
279 // Default to "hidden" visibility, as object level linking will not be
280 // supported for the foreseeable future.
281 if (!DriverArgs.hasArg(Ids: options::OPT_fvisibility_EQ,
282 Ids: options::OPT_fvisibility_ms_compat))
283 CC1Args.append(
284 IL: {"-fvisibility=hidden", "-fapply-global-visibility-to-externs"});
285
286 for (const BitCodeLibraryInfo &BCFile :
287 getDeviceLibs(Args: DriverArgs, BA, DeviceOffloadKind: DeviceOffloadingKind))
288 CC1Args.append(
289 IL: {"-mlink-builtin-bitcode", DriverArgs.MakeArgString(Str: BCFile.Path)});
290}
291
292Tool *HIPSPVToolChain::buildLinker() const {
293 assert(getTriple().getArch() == llvm::Triple::spirv64);
294 return new tools::HIPSPV::Linker(*this);
295}
296
297void HIPSPVToolChain::addClangWarningOptions(ArgStringList &CC1Args) const {
298 if (HostTC)
299 HostTC->addClangWarningOptions(CC1Args);
300 ToolChain::addClangWarningOptions(CC1Args);
301}
302
303ToolChain::CXXStdlibType
304HIPSPVToolChain::GetCXXStdlibType(const ArgList &Args) const {
305 if (HostTC)
306 return HostTC->GetCXXStdlibType(Args);
307 return ToolChain::GetCXXStdlibType(Args);
308}
309
310void HIPSPVToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
311 ArgStringList &CC1Args) const {
312 if (HostTC)
313 HostTC->AddClangSystemIncludeArgs(DriverArgs, CC1Args);
314 ToolChain::AddClangSystemIncludeArgs(DriverArgs, CC1Args);
315}
316
317void HIPSPVToolChain::AddClangCXXStdlibIncludeArgs(
318 const ArgList &Args, ArgStringList &CC1Args) const {
319 if (HostTC)
320 HostTC->AddClangCXXStdlibIncludeArgs(DriverArgs: Args, CC1Args);
321 ToolChain::AddClangCXXStdlibIncludeArgs(DriverArgs: Args, CC1Args);
322}
323
324void HIPSPVToolChain::AddIAMCUIncludeArgs(const ArgList &Args,
325 ArgStringList &CC1Args) const {
326 if (HostTC)
327 HostTC->AddIAMCUIncludeArgs(DriverArgs: Args, CC1Args);
328 ToolChain::AddIAMCUIncludeArgs(DriverArgs: Args, CC1Args);
329}
330
331void HIPSPVToolChain::AddHIPIncludeArgs(const ArgList &DriverArgs,
332 ArgStringList &CC1Args) const {
333 if (!DriverArgs.hasFlag(Pos: options::OPT_offload_inc, Neg: options::OPT_no_offload_inc,
334 Default: true))
335 return;
336
337 StringRef hipPath = DriverArgs.getLastArgValue(Id: options::OPT_hip_path_EQ);
338 if (hipPath.empty()) {
339 getDriver().Diag(DiagID: diag::err_drv_hipspv_no_hip_path);
340 return;
341 }
342 SmallString<128> P(hipPath);
343 llvm::sys::path::append(path&: P, a: "include");
344 CC1Args.append(IL: {"-isystem", DriverArgs.MakeArgString(Str: P)});
345}
346
347llvm::SmallVector<ToolChain::BitCodeLibraryInfo, 12>
348HIPSPVToolChain::getDeviceLibs(
349 const llvm::opt::ArgList &DriverArgs, BoundArch BA,
350 const Action::OffloadKind DeviceOffloadingKind) const {
351 llvm::SmallVector<ToolChain::BitCodeLibraryInfo, 12> BCLibs;
352 if (!DriverArgs.hasFlag(Pos: options::OPT_offloadlib, Neg: options::OPT_no_offloadlib,
353 Default: true))
354 return {};
355
356 ArgStringList LibraryPaths;
357 // Find device libraries in --hip-device-lib-path and HIP_DEVICE_LIB_PATH.
358 auto HipDeviceLibPathArgs = DriverArgs.getAllArgValues(
359 // --hip-device-lib-path is alias to this option.
360 Id: options::OPT_rocm_device_lib_path_EQ);
361 for (auto Path : HipDeviceLibPathArgs)
362 LibraryPaths.push_back(Elt: DriverArgs.MakeArgString(Str: Path));
363
364 StringRef HipPath = DriverArgs.getLastArgValue(Id: options::OPT_hip_path_EQ);
365 if (!HipPath.empty()) {
366 SmallString<128> Path(HipPath);
367 llvm::sys::path::append(path&: Path, a: "lib", b: "hip-device-lib");
368 LibraryPaths.push_back(Elt: DriverArgs.MakeArgString(Str: Path));
369 }
370
371 addDirectoryList(Args: DriverArgs, CmdArgs&: LibraryPaths, ArgName: "", EnvVar: "HIP_DEVICE_LIB_PATH");
372
373 // Maintain compatability with --hip-device-lib.
374 auto BCLibArgs = DriverArgs.getAllArgValues(Id: options::OPT_hip_device_lib_EQ);
375 if (!BCLibArgs.empty()) {
376 bool Found = false;
377 for (StringRef BCName : BCLibArgs) {
378 StringRef FullName;
379 for (std::string LibraryPath : LibraryPaths) {
380 SmallString<128> Path(LibraryPath);
381 llvm::sys::path::append(path&: Path, a: BCName);
382 FullName = Path;
383 if (llvm::sys::fs::exists(Path: FullName)) {
384 BCLibs.emplace_back(Args: FullName.str());
385 Found = true;
386 break;
387 }
388 }
389 if (!Found)
390 getDriver().Diag(DiagID: diag::err_drv_no_such_file) << BCName;
391 }
392 } else {
393 // Search device library named as 'hipspv-<triple>.bc'.
394 auto TT = getTriple().normalize();
395 std::string BCName = "hipspv-" + TT + ".bc";
396 for (auto *LibPath : LibraryPaths) {
397 SmallString<128> Path(LibPath);
398 llvm::sys::path::append(path&: Path, a: BCName);
399 if (llvm::sys::fs::exists(Path)) {
400 BCLibs.emplace_back(Args: Path.str().str());
401 return BCLibs;
402 }
403 }
404 getDriver().Diag(DiagID: diag::err_drv_no_hipspv_device_lib)
405 << 1 << ("'" + TT + "' target");
406 return {};
407 }
408
409 return BCLibs;
410}
411
412SanitizerMask HIPSPVToolChain::getSupportedSanitizers(
413 BoundArch BA, Action::OffloadKind DeviceOffloadKind) const {
414 // The HIPSPVToolChain only supports sanitizers in the sense that it allows
415 // sanitizer arguments on the command line if they are supported by the host
416 // toolchain. The HIPSPVToolChain will actually ignore any command line
417 // arguments for any of these "supported" sanitizers. That means that no
418 // sanitization of device code is actually supported at this time.
419 //
420 // This behavior is necessary because the host and device toolchains
421 // invocations often share the command line, so the device toolchain must
422 // tolerate flags meant only for the host toolchain.
423
424 // FIXME: Be accurate and use DeviceOffloadKind.
425 if (HostTC)
426 return HostTC->getSupportedSanitizers(BA, DeviceOffloadKind);
427 return ToolChain::getSupportedSanitizers(BA, DeviceOffloadKind);
428}
429
430VersionTuple HIPSPVToolChain::computeMSVCVersion(const Driver *D,
431 const ArgList &Args) const {
432 if (HostTC)
433 return HostTC->computeMSVCVersion(D, Args);
434 return ToolChain::computeMSVCVersion(D, Args);
435}
436
437void HIPSPVToolChain::adjustDebugInfoKind(
438 llvm::codegenoptions::DebugInfoKind &DebugInfoKind,
439 const llvm::opt::ArgList &Args) const {
440 // Historically device debug info was force-disabled here because the
441 // SPIRV-LLVM-Translator aborted on DW_OP_LLVM_convert debug expressions. The
442 // translator now lowers that operation, so honor the debug level the user
443 // requested (e.g. via -g) and let it flow into the emitted SPIR-V.
444 // constructLinkAndEmitSpirvCommand() enables the NonSemantic.Shader.DebugInfo
445 // form at translation time so downstream tools (e.g. gdb-oneapi) can consume
446 // it. Leaving DebugInfoKind untouched keeps the default (no -g) behavior,
447 // since the driver defaults it to NoDebugInfo.
448 (void)DebugInfoKind;
449 (void)Args;
450}
451