1//===--- HIPAMD.cpp - HIP Tool and ToolChain Implementations ----*- 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 "HIPAMD.h"
10#include "AMDGPU.h"
11#include "HIPUtility.h"
12#include "SPIRV.h"
13#include "clang/Basic/Cuda.h"
14#include "clang/Driver/CommonArgs.h"
15#include "clang/Driver/Compilation.h"
16#include "clang/Driver/Driver.h"
17#include "clang/Driver/InputInfo.h"
18#include "clang/Driver/SanitizerArgs.h"
19#include "clang/Options/Options.h"
20#include "llvm/Support/FileSystem.h"
21#include "llvm/Support/Path.h"
22#include "llvm/TargetParser/TargetParser.h"
23
24using namespace clang::driver;
25using namespace clang::driver::toolchains;
26using namespace clang::driver::tools;
27using namespace clang;
28using namespace llvm::opt;
29
30#if defined(_WIN32) || defined(_WIN64)
31#define NULL_FILE "nul"
32#else
33#define NULL_FILE "/dev/null"
34#endif
35
36void AMDGCN::Linker::constructLLVMLinkCommand(
37 Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
38 const InputInfo &Output, const llvm::opt::ArgList &Args) const {
39
40 ArgStringList LinkerInputs;
41
42 for (auto Input : Inputs)
43 if (Input.isFilename())
44 LinkerInputs.push_back(Elt: Input.getFilename());
45
46 tools::constructLLVMLinkCommand(C, T: *this, JA, JobInputs: Inputs, LinkerInputs, Output,
47 Args);
48}
49
50void AMDGCN::Linker::constructLldCommand(Compilation &C, const JobAction &JA,
51 const InputInfoList &Inputs,
52 const InputInfo &Output,
53 const llvm::opt::ArgList &Args) const {
54 // Construct lld command.
55 // The output from ld.lld is an HSA code object file.
56 ArgStringList LldArgs{"-flavor",
57 "gnu",
58 "-m",
59 "elf64_amdgpu",
60 "--no-undefined",
61 "-shared",
62 "-plugin-opt=-amdgpu-internalize-symbols"};
63 if (Args.hasArg(Ids: options::OPT_hipstdpar))
64 LldArgs.push_back(Elt: "-plugin-opt=-amdgpu-enable-hipstdpar");
65
66 auto &TC = getToolChain();
67 auto &D = TC.getDriver();
68 bool IsThinLTO = TC.getLTOMode(Args, Kind: Action::OFK_HIP) == LTOK_Thin;
69 addLTOOptions(ToolChain: TC, Args, CmdArgs&: LldArgs, Output, Inputs, IsThinLTO);
70
71 // Extract all the -m options
72 std::vector<llvm::StringRef> Features;
73 amdgpu::getAMDGPUTargetFeatures(D, Triple: TC.getEffectiveTriple(), Args, Features);
74
75 // Add features to mattr such as cumode
76 std::string MAttrString = "-plugin-opt=-mattr=";
77 for (auto OneFeature : unifyTargetFeatures(Features)) {
78 MAttrString.append(s: Args.MakeArgStringRef(Str: OneFeature));
79 if (OneFeature != Features.back())
80 MAttrString.append(s: ",");
81 }
82 if (!Features.empty())
83 LldArgs.push_back(Elt: Args.MakeArgString(Str: MAttrString));
84
85 // ToDo: Remove this option after AMDGPU backend supports ISA-level linking.
86 // Since AMDGPU backend currently does not support ISA-level linking, all
87 // called functions need to be imported.
88 if (IsThinLTO) {
89 LldArgs.push_back(Elt: "-plugin-opt=-force-import-all");
90 LldArgs.push_back(Elt: "-plugin-opt=-avail-extern-to-local");
91 LldArgs.push_back(Elt: "-plugin-opt=-avail-extern-gv-in-addrspace-to-local=3");
92 }
93
94 for (const Arg *A : Args.filtered(Ids: options::OPT_mllvm)) {
95 LldArgs.push_back(
96 Elt: Args.MakeArgString(Str: Twine("-plugin-opt=") + A->getValue(N: 0)));
97 }
98
99 if (C.getDriver().isSaveTempsEnabled())
100 LldArgs.push_back(Elt: "-save-temps");
101
102 addLinkerCompressDebugSectionsOption(TC, Args, CmdArgs&: LldArgs);
103
104 // Given that host and device linking happen in separate processes, the device
105 // linker doesn't always have the visibility as to which device symbols are
106 // needed by a program, especially for the device symbol dependencies that are
107 // introduced through the host symbol resolution.
108 // For example: host_A() (A.obj) --> host_B(B.obj) --> device_kernel_B()
109 // (B.obj) In this case, the device linker doesn't know that A.obj actually
110 // depends on the kernel functions in B.obj. When linking to static device
111 // library, the device linker may drop some of the device global symbols if
112 // they aren't referenced. As a workaround, we are adding to the
113 // --whole-archive flag such that all global symbols would be linked in.
114 LldArgs.push_back(Elt: "--whole-archive");
115
116 for (auto *Arg : Args.filtered(Ids: options::OPT_Xoffload_linker)) {
117 StringRef ArgVal = Arg->getValue(N: 1);
118 auto SplitArg = ArgVal.split(Separator: "-mllvm=");
119 if (!SplitArg.second.empty()) {
120 LldArgs.push_back(
121 Elt: Args.MakeArgString(Str: Twine("-plugin-opt=") + SplitArg.second));
122 } else {
123 LldArgs.push_back(Elt: Args.MakeArgStringRef(Str: ArgVal));
124 }
125 Arg->claim();
126 }
127
128 LldArgs.append(IL: {"-o", Output.getFilename()});
129 for (auto Input : Inputs)
130 LldArgs.push_back(Elt: Input.getFilename());
131 TC.addProfileRTLibs(Args, CmdArgs&: LldArgs);
132
133 LldArgs.push_back(Elt: "--no-whole-archive");
134
135 const char *Lld = Args.MakeArgStringRef(Str: getToolChain().GetProgramPath(Name: "lld"));
136 C.addCommand(Cmd: std::make_unique<Command>(args: JA, args: *this, args: ResponseFileSupport::None(),
137 args&: Lld, args&: LldArgs, args: Inputs, args: Output));
138}
139
140// For SPIR-V the inputs for the job are device AMDGCN SPIR-V flavoured bitcode
141// and the output is either a compiled SPIR-V binary or bitcode (-emit-llvm). It
142// calls llvm-link and then the llvm-spirv translator or the SPIR-V BE.
143// TODO: consider if we want to run any targeted optimisations over IR here,
144// over generic SPIR-V.
145void AMDGCN::Linker::constructLinkAndEmitSpirvCommand(
146 Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
147 const InputInfo &Output, const llvm::opt::ArgList &Args) const {
148 assert(!Inputs.empty() && "Must have at least one input.");
149
150 std::string LinkedBCFilePrefix(
151 Twine(llvm::sys::path::stem(path: Output.getFilename()), "-linked").str());
152 const char *LinkedBCFilePath = HIP::getTempFile(C, Prefix: LinkedBCFilePrefix, Extension: "bc");
153 InputInfo LinkedBCFile(&JA, LinkedBCFilePath, Output.getBaseInput());
154
155 bool UseSPIRVBackend = Args.hasFlag(Pos: options::OPT_use_spirv_backend,
156 Neg: options::OPT_no_use_spirv_backend,
157 /*Default=*/true);
158
159 constructLLVMLinkCommand(C, JA, Inputs, Output: LinkedBCFile, Args);
160
161 if (UseSPIRVBackend) {
162 // This code handles the case when --offload-device-only is unset and
163 // clang-linker-wrapper forwards the bitcode that must be compiled to
164 // SPIR-V.
165
166 llvm::opt::ArgStringList CmdArgs;
167
168 CmdArgs.append(IL: {"-cc1", "-triple=spirv64-amd-amdhsa", "-emit-obj",
169 "-disable-llvm-optzns", "-mllvm", "-spirv-preserve-auxdata",
170 LinkedBCFile.getFilename(), "-o", Output.getFilename()});
171
172 const Driver &Driver = getToolChain().getDriver();
173 const char *Exec = Driver.getDriverProgramPath();
174 C.addCommand(Cmd: std::make_unique<Command>(
175 args: JA, args: *this, args: ResponseFileSupport::None(), args&: Exec, args&: CmdArgs, args&: LinkedBCFile,
176 args: Output, args: Driver.getPrependArg()));
177 } else {
178 // Emit SPIR-V binary using the translator
179 llvm::opt::ArgStringList TrArgs{
180 "--spirv-max-version=1.6",
181 "--spirv-ext=+all",
182 "--spirv-allow-unknown-intrinsics",
183 "--spirv-lower-const-expr",
184 "--spirv-preserve-auxdata",
185 "--spirv-debug-info-version=nonsemantic-shader-200"};
186 SPIRV::constructTranslateCommand(C, T: *this, JA, Output, Input: LinkedBCFile,
187 Args: TrArgs);
188 }
189}
190
191// For amdgcn the inputs of the linker job are device bitcode and output is
192// either an object file or bitcode (-emit-llvm). It calls llvm-link, opt,
193// llc, then lld steps.
194void AMDGCN::Linker::ConstructJob(Compilation &C, const JobAction &JA,
195 const InputInfo &Output,
196 const InputInfoList &Inputs,
197 const ArgList &Args,
198 const char *LinkingOutput) const {
199 if (JA.getType() == types::TY_HIP_FATBIN)
200 return HIP::constructHIPFatbinCommand(C, JA, OutputFileName: Output.getFilename(), Inputs,
201 TCArgs: Args, T: *this);
202
203 if (JA.getType() == types::TY_LLVM_BC)
204 return constructLLVMLinkCommand(C, JA, Inputs, Output, Args);
205
206 if (getToolChain().getEffectiveTriple().isSPIRV())
207 return constructLinkAndEmitSpirvCommand(C, JA, Inputs, Output, Args);
208
209 return constructLldCommand(C, JA, Inputs, Output, Args);
210}
211
212SPIRVAMDToolChain::SPIRVAMDToolChain(const Driver &D,
213 const llvm::Triple &Triple,
214 const llvm::opt::ArgList &Args)
215 : AMDGPUToolChain(D, Triple, Args) {}
216
217Tool *SPIRVAMDToolChain::buildLinker() const {
218 return new tools::AMDGCN::Linker(*this);
219}
220