| 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 | |
| 20 | using namespace clang::driver; |
| 21 | using namespace clang::driver::toolchains; |
| 22 | using namespace clang::driver::tools; |
| 23 | using namespace clang; |
| 24 | using namespace llvm::opt; |
| 25 | |
| 26 | // Locates HIP pass plugin. |
| 27 | static 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? |
| 53 | static 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. |
| 60 | static 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 | |
| 80 | void 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 | |
| 230 | void 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 | |
| 247 | HIPSPVToolChain::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. |
| 256 | HIPSPVToolChain::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 | |
| 264 | bool 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 | |
| 269 | void 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 | |
| 304 | Tool *HIPSPVToolChain::buildLinker() const { |
| 305 | assert(getTriple().getArch() == llvm::Triple::spirv64); |
| 306 | return new tools::HIPSPV::Linker(*this); |
| 307 | } |
| 308 | |
| 309 | void HIPSPVToolChain::addClangWarningOptions(ArgStringList &CC1Args) const { |
| 310 | if (HostTC) |
| 311 | HostTC->addClangWarningOptions(CC1Args); |
| 312 | ToolChain::addClangWarningOptions(CC1Args); |
| 313 | } |
| 314 | |
| 315 | ToolChain::CXXStdlibType |
| 316 | HIPSPVToolChain::GetCXXStdlibType(const ArgList &Args) const { |
| 317 | if (HostTC) |
| 318 | return HostTC->GetCXXStdlibType(Args); |
| 319 | return ToolChain::GetCXXStdlibType(Args); |
| 320 | } |
| 321 | |
| 322 | void HIPSPVToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs, |
| 323 | ArgStringList &CC1Args) const { |
| 324 | if (HostTC) |
| 325 | HostTC->AddClangSystemIncludeArgs(DriverArgs, CC1Args); |
| 326 | ToolChain::AddClangSystemIncludeArgs(DriverArgs, CC1Args); |
| 327 | } |
| 328 | |
| 329 | void 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 | |
| 336 | void 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 | |
| 343 | void 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 | |
| 359 | llvm::SmallVector<ToolChain::BitCodeLibraryInfo, 12> |
| 360 | HIPSPVToolChain::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 | |
| 424 | SanitizerMask 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 | |
| 442 | VersionTuple 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 | |
| 449 | void 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 | |
| 464 | LTOKind 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 | |