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