469 lines · cpp
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-exception6//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#else33#define NULL_FILE "/dev/null"34#endif35 36void AMDGCN::Linker::constructLlvmLinkCommand(Compilation &C,37 const JobAction &JA,38 const InputInfoList &Inputs,39 const InputInfo &Output,40 const llvm::opt::ArgList &Args) const {41 // Construct llvm-link command.42 // The output from llvm-link is a bitcode file.43 ArgStringList LlvmLinkArgs;44 45 assert(!Inputs.empty() && "Must have at least one input.");46 47 LlvmLinkArgs.append({"-o", Output.getFilename()});48 for (auto Input : Inputs)49 LlvmLinkArgs.push_back(Input.getFilename());50 51 // Look for archive of bundled bitcode in arguments, and add temporary files52 // for the extracted archive of bitcode to inputs.53 auto TargetID = Args.getLastArgValue(options::OPT_mcpu_EQ);54 AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LlvmLinkArgs, "amdgcn",55 TargetID, /*IsBitCodeSDL=*/true);56 57 const char *LlvmLink =58 Args.MakeArgString(getToolChain().GetProgramPath("llvm-link"));59 C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),60 LlvmLink, LlvmLinkArgs, Inputs,61 Output));62}63 64void AMDGCN::Linker::constructLldCommand(Compilation &C, const JobAction &JA,65 const InputInfoList &Inputs,66 const InputInfo &Output,67 const llvm::opt::ArgList &Args) const {68 // Construct lld command.69 // The output from ld.lld is an HSA code object file.70 ArgStringList LldArgs{"-flavor",71 "gnu",72 "-m",73 "elf64_amdgpu",74 "--no-undefined",75 "-shared",76 "-plugin-opt=-amdgpu-internalize-symbols"};77 if (Args.hasArg(options::OPT_hipstdpar))78 LldArgs.push_back("-plugin-opt=-amdgpu-enable-hipstdpar");79 80 auto &TC = getToolChain();81 auto &D = TC.getDriver();82 bool IsThinLTO = D.getOffloadLTOMode() == LTOK_Thin;83 addLTOOptions(TC, Args, LldArgs, Output, Inputs, IsThinLTO);84 85 // Extract all the -m options86 std::vector<llvm::StringRef> Features;87 amdgpu::getAMDGPUTargetFeatures(D, TC.getTriple(), Args, Features);88 89 // Add features to mattr such as cumode90 std::string MAttrString = "-plugin-opt=-mattr=";91 for (auto OneFeature : unifyTargetFeatures(Features)) {92 MAttrString.append(Args.MakeArgString(OneFeature));93 if (OneFeature != Features.back())94 MAttrString.append(",");95 }96 if (!Features.empty())97 LldArgs.push_back(Args.MakeArgString(MAttrString));98 99 // ToDo: Remove this option after AMDGPU backend supports ISA-level linking.100 // Since AMDGPU backend currently does not support ISA-level linking, all101 // called functions need to be imported.102 if (IsThinLTO) {103 LldArgs.push_back(Args.MakeArgString("-plugin-opt=-force-import-all"));104 LldArgs.push_back(Args.MakeArgString("-plugin-opt=-avail-extern-to-local"));105 LldArgs.push_back(Args.MakeArgString(106 "-plugin-opt=-avail-extern-gv-in-addrspace-to-local=3"));107 }108 109 for (const Arg *A : Args.filtered(options::OPT_mllvm)) {110 LldArgs.push_back(111 Args.MakeArgString(Twine("-plugin-opt=") + A->getValue(0)));112 }113 114 if (C.getDriver().isSaveTempsEnabled())115 LldArgs.push_back("-save-temps");116 117 addLinkerCompressDebugSectionsOption(TC, Args, LldArgs);118 119 // Given that host and device linking happen in separate processes, the device120 // linker doesn't always have the visibility as to which device symbols are121 // needed by a program, especially for the device symbol dependencies that are122 // introduced through the host symbol resolution.123 // For example: host_A() (A.obj) --> host_B(B.obj) --> device_kernel_B()124 // (B.obj) In this case, the device linker doesn't know that A.obj actually125 // depends on the kernel functions in B.obj. When linking to static device126 // library, the device linker may drop some of the device global symbols if127 // they aren't referenced. As a workaround, we are adding to the128 // --whole-archive flag such that all global symbols would be linked in.129 LldArgs.push_back("--whole-archive");130 131 for (auto *Arg : Args.filtered(options::OPT_Xoffload_linker)) {132 StringRef ArgVal = Arg->getValue(1);133 auto SplitArg = ArgVal.split("-mllvm=");134 if (!SplitArg.second.empty()) {135 LldArgs.push_back(136 Args.MakeArgString(Twine("-plugin-opt=") + SplitArg.second));137 } else {138 LldArgs.push_back(Args.MakeArgString(ArgVal));139 }140 Arg->claim();141 }142 143 LldArgs.append({"-o", Output.getFilename()});144 for (auto Input : Inputs)145 LldArgs.push_back(Input.getFilename());146 147 // Look for archive of bundled bitcode in arguments, and add temporary files148 // for the extracted archive of bitcode to inputs.149 auto TargetID = Args.getLastArgValue(options::OPT_mcpu_EQ);150 AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LldArgs, "amdgcn",151 TargetID, /*IsBitCodeSDL=*/true);152 153 LldArgs.push_back("--no-whole-archive");154 155 const char *Lld = Args.MakeArgString(getToolChain().GetProgramPath("lld"));156 C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),157 Lld, LldArgs, Inputs, Output));158}159 160// For SPIR-V the inputs for the job are device AMDGCN SPIR-V flavoured bitcode161// and the output is either a compiled SPIR-V binary or bitcode (-emit-llvm). It162// calls llvm-link and then the llvm-spirv translator or the SPIR-V BE.163// TODO: consider if we want to run any targeted optimisations over IR here,164// over generic SPIR-V.165void AMDGCN::Linker::constructLinkAndEmitSpirvCommand(166 Compilation &C, const JobAction &JA, const InputInfoList &Inputs,167 const InputInfo &Output, const llvm::opt::ArgList &Args) const {168 assert(!Inputs.empty() && "Must have at least one input.");169 170 std::string LinkedBCFilePrefix(171 Twine(llvm::sys::path::stem(Output.getFilename()), "-linked").str());172 const char *LinkedBCFilePath = HIP::getTempFile(C, LinkedBCFilePrefix, "bc");173 InputInfo LinkedBCFile(&JA, LinkedBCFilePath, Output.getBaseInput());174 175 bool UseSPIRVBackend =176 Args.hasFlag(options::OPT_use_spirv_backend,177 options::OPT_no_use_spirv_backend, /*Default=*/false);178 179 constructLlvmLinkCommand(C, JA, Inputs, LinkedBCFile, Args);180 181 if (UseSPIRVBackend) {182 // This code handles the case in the new driver when --offload-device-only183 // is unset and clang-linker-wrapper forwards the bitcode that must be184 // compiled to SPIR-V.185 186 llvm::opt::ArgStringList CmdArgs;187 const char *Triple =188 C.getArgs().MakeArgString("-triple=spirv64-amd-amdhsa");189 190 CmdArgs.append({"-cc1", Triple, "-emit-obj", "-disable-llvm-optzns",191 LinkedBCFile.getFilename(), "-o", Output.getFilename()});192 193 const Driver &Driver = getToolChain().getDriver();194 const char *Exec = Driver.getClangProgramPath();195 C.addCommand(std::make_unique<Command>(196 JA, *this, ResponseFileSupport::None(), Exec, CmdArgs, LinkedBCFile,197 Output, Driver.getPrependArg()));198 } else {199 // Emit SPIR-V binary using the translator200 llvm::opt::ArgStringList TrArgs{201 "--spirv-max-version=1.6",202 "--spirv-ext=+all",203 "--spirv-allow-unknown-intrinsics",204 "--spirv-lower-const-expr",205 "--spirv-preserve-auxdata",206 "--spirv-debug-info-version=nonsemantic-shader-200"};207 SPIRV::constructTranslateCommand(C, *this, JA, Output, LinkedBCFile,208 TrArgs);209 }210}211 212// For amdgcn the inputs of the linker job are device bitcode and output is213// either an object file or bitcode (-emit-llvm). It calls llvm-link, opt,214// llc, then lld steps.215void AMDGCN::Linker::ConstructJob(Compilation &C, const JobAction &JA,216 const InputInfo &Output,217 const InputInfoList &Inputs,218 const ArgList &Args,219 const char *LinkingOutput) const {220 if (Inputs.size() > 0 &&221 Inputs[0].getType() == types::TY_Image &&222 JA.getType() == types::TY_Object)223 return HIP::constructGenerateObjFileFromHIPFatBinary(C, Output, Inputs,224 Args, JA, *this);225 226 if (JA.getType() == types::TY_HIP_FATBIN)227 return HIP::constructHIPFatbinCommand(C, JA, Output.getFilename(), Inputs,228 Args, *this);229 230 if (JA.getType() == types::TY_LLVM_BC)231 return constructLlvmLinkCommand(C, JA, Inputs, Output, Args);232 233 if (getToolChain().getEffectiveTriple().isSPIRV())234 return constructLinkAndEmitSpirvCommand(C, JA, Inputs, Output, Args);235 236 return constructLldCommand(C, JA, Inputs, Output, Args);237}238 239HIPAMDToolChain::HIPAMDToolChain(const Driver &D, const llvm::Triple &Triple,240 const ToolChain &HostTC, const ArgList &Args)241 : ROCMToolChain(D, Triple, Args), HostTC(HostTC) {242 // Lookup binaries into the driver directory, this is used to243 // discover the clang-offload-bundler executable.244 getProgramPaths().push_back(getDriver().Dir);245}246 247void HIPAMDToolChain::addClangTargetOptions(248 const llvm::opt::ArgList &DriverArgs, llvm::opt::ArgStringList &CC1Args,249 Action::OffloadKind DeviceOffloadingKind) const {250 HostTC.addClangTargetOptions(DriverArgs, CC1Args, DeviceOffloadingKind);251 252 assert(DeviceOffloadingKind == Action::OFK_HIP &&253 "Only HIP offloading kinds are supported for GPUs.");254 255 CC1Args.append({"-fcuda-is-device", "-fno-threadsafe-statics"});256 257 if (!DriverArgs.hasFlag(options::OPT_fgpu_rdc, options::OPT_fno_gpu_rdc,258 false)) {259 CC1Args.append({"-mllvm", "-amdgpu-internalize-symbols"});260 if (DriverArgs.hasArgNoClaim(options::OPT_hipstdpar))261 CC1Args.append({"-mllvm", "-amdgpu-enable-hipstdpar"});262 }263 264 StringRef MaxThreadsPerBlock =265 DriverArgs.getLastArgValue(options::OPT_gpu_max_threads_per_block_EQ);266 if (!MaxThreadsPerBlock.empty()) {267 std::string ArgStr =268 (Twine("--gpu-max-threads-per-block=") + MaxThreadsPerBlock).str();269 CC1Args.push_back(DriverArgs.MakeArgStringRef(ArgStr));270 }271 272 CC1Args.push_back("-fcuda-allow-variadic-functions");273 274 // Default to "hidden" visibility, as object level linking will not be275 // supported for the foreseeable future.276 if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ,277 options::OPT_fvisibility_ms_compat)) {278 CC1Args.append({"-fvisibility=hidden"});279 CC1Args.push_back("-fapply-global-visibility-to-externs");280 }281 282 if (getEffectiveTriple().isSPIRV()) {283 // For SPIR-V we embed the command-line into the generated binary, in order284 // to retrieve it at JIT time and be able to do target specific compilation285 // with options that match the user-supplied ones.286 if (!DriverArgs.hasArg(options::OPT_fembed_bitcode_marker))287 CC1Args.push_back("-fembed-bitcode=marker");288 // For SPIR-V we want to retain the pristine output of Clang CodeGen, since289 // optimizations might lose structure / information that is necessary for290 // generating optimal concrete AMDGPU code. We duplicate this because the291 // HIP TC doesn't invoke the base AMDGPU TC addClangTargetOptions.292 if (!DriverArgs.hasArg(options::OPT_disable_llvm_passes))293 CC1Args.push_back("-disable-llvm-passes");294 return; // No DeviceLibs for SPIR-V.295 }296 297 for (auto BCFile : getDeviceLibs(DriverArgs, DeviceOffloadingKind)) {298 CC1Args.push_back(BCFile.ShouldInternalize ? "-mlink-builtin-bitcode"299 : "-mlink-bitcode-file");300 CC1Args.push_back(DriverArgs.MakeArgString(BCFile.Path));301 }302}303 304llvm::opt::DerivedArgList *305HIPAMDToolChain::TranslateArgs(const llvm::opt::DerivedArgList &Args,306 StringRef BoundArch,307 Action::OffloadKind DeviceOffloadKind) const {308 DerivedArgList *DAL =309 HostTC.TranslateArgs(Args, BoundArch, DeviceOffloadKind);310 if (!DAL)311 DAL = new DerivedArgList(Args.getBaseArgs());312 313 const OptTable &Opts = getDriver().getOpts();314 315 for (Arg *A : Args) {316 // Filter unsupported sanitizers passed from the HostTC.317 if (!handleSanitizeOption(*this, *DAL, Args, BoundArch, A))318 DAL->append(A);319 }320 321 if (!BoundArch.empty()) {322 DAL->eraseArg(options::OPT_mcpu_EQ);323 DAL->AddJoinedArg(nullptr, Opts.getOption(options::OPT_mcpu_EQ), BoundArch);324 checkTargetID(*DAL);325 }326 327 if (!Args.hasArg(options::OPT_flto_partitions_EQ))328 DAL->AddJoinedArg(nullptr, Opts.getOption(options::OPT_flto_partitions_EQ),329 "8");330 331 return DAL;332}333 334Tool *HIPAMDToolChain::buildLinker() const {335 assert(getTriple().isAMDGCN() ||336 getTriple().getArch() == llvm::Triple::spirv64);337 return new tools::AMDGCN::Linker(*this);338}339 340void HIPAMDToolChain::addClangWarningOptions(ArgStringList &CC1Args) const {341 AMDGPUToolChain::addClangWarningOptions(CC1Args);342 HostTC.addClangWarningOptions(CC1Args);343}344 345ToolChain::CXXStdlibType346HIPAMDToolChain::GetCXXStdlibType(const ArgList &Args) const {347 return HostTC.GetCXXStdlibType(Args);348}349 350void HIPAMDToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs,351 ArgStringList &CC1Args) const {352 HostTC.AddClangSystemIncludeArgs(DriverArgs, CC1Args);353}354 355void HIPAMDToolChain::AddClangCXXStdlibIncludeArgs(356 const ArgList &Args, ArgStringList &CC1Args) const {357 HostTC.AddClangCXXStdlibIncludeArgs(Args, CC1Args);358}359 360void HIPAMDToolChain::AddIAMCUIncludeArgs(const ArgList &Args,361 ArgStringList &CC1Args) const {362 HostTC.AddIAMCUIncludeArgs(Args, CC1Args);363}364 365void HIPAMDToolChain::AddHIPIncludeArgs(const ArgList &DriverArgs,366 ArgStringList &CC1Args) const {367 RocmInstallation->AddHIPIncludeArgs(DriverArgs, CC1Args);368}369 370SanitizerMask HIPAMDToolChain::getSupportedSanitizers() const {371 // The HIPAMDToolChain only supports sanitizers in the sense that it allows372 // sanitizer arguments on the command line if they are supported by the host373 // toolchain. The HIPAMDToolChain will later filter unsupported sanitizers374 // from the command line arguments.375 //376 // This behavior is necessary because the host and device toolchains377 // invocations often share the command line, so the device toolchain must378 // tolerate flags meant only for the host toolchain.379 return HostTC.getSupportedSanitizers();380}381 382VersionTuple HIPAMDToolChain::computeMSVCVersion(const Driver *D,383 const ArgList &Args) const {384 return HostTC.computeMSVCVersion(D, Args);385}386 387llvm::SmallVector<ToolChain::BitCodeLibraryInfo, 12>388HIPAMDToolChain::getDeviceLibs(const llvm::opt::ArgList &DriverArgs,389 Action::OffloadKind DeviceOffloadingKind) const {390 llvm::SmallVector<BitCodeLibraryInfo, 12> BCLibs;391 if (!DriverArgs.hasFlag(options::OPT_offloadlib, options::OPT_no_offloadlib,392 true) ||393 getGPUArch(DriverArgs) == "amdgcnspirv")394 return {};395 ArgStringList LibraryPaths;396 397 // Find in --hip-device-lib-path and HIP_LIBRARY_PATH.398 for (StringRef Path : RocmInstallation->getRocmDeviceLibPathArg())399 LibraryPaths.push_back(DriverArgs.MakeArgString(Path));400 401 addDirectoryList(DriverArgs, LibraryPaths, "", "HIP_DEVICE_LIB_PATH");402 403 // Maintain compatability with --hip-device-lib.404 auto BCLibArgs = DriverArgs.getAllArgValues(options::OPT_hip_device_lib_EQ);405 if (!BCLibArgs.empty()) {406 for (StringRef BCName : BCLibArgs) {407 StringRef FullName;408 bool Found = false;409 for (StringRef LibraryPath : LibraryPaths) {410 SmallString<128> Path(LibraryPath);411 llvm::sys::path::append(Path, BCName);412 FullName = Path;413 if (llvm::sys::fs::exists(FullName)) {414 BCLibs.emplace_back(FullName);415 Found = true;416 break;417 }418 }419 if (!Found)420 getDriver().Diag(diag::err_drv_no_such_file) << BCName;421 }422 } else {423 if (!RocmInstallation->hasDeviceLibrary()) {424 getDriver().Diag(diag::err_drv_no_rocm_device_lib) << 0;425 return {};426 }427 StringRef GpuArch = getGPUArch(DriverArgs);428 assert(!GpuArch.empty() && "Must have an explicit GPU arch.");429 430 // Add common device libraries like ocml etc.431 for (auto N : getCommonDeviceLibNames(DriverArgs, GpuArch.str(),432 DeviceOffloadingKind))433 BCLibs.emplace_back(N);434 435 // Add instrument lib.436 auto InstLib =437 DriverArgs.getLastArgValue(options::OPT_gpu_instrument_lib_EQ);438 if (InstLib.empty())439 return BCLibs;440 if (llvm::sys::fs::exists(InstLib))441 BCLibs.emplace_back(InstLib);442 else443 getDriver().Diag(diag::err_drv_no_such_file) << InstLib;444 }445 446 return BCLibs;447}448 449void HIPAMDToolChain::checkTargetID(450 const llvm::opt::ArgList &DriverArgs) const {451 auto PTID = getParsedTargetID(DriverArgs);452 if (PTID.OptionalTargetID && !PTID.OptionalGPUArch &&453 PTID.OptionalTargetID != "amdgcnspirv")454 getDriver().Diag(clang::diag::err_drv_bad_target_id)455 << *PTID.OptionalTargetID;456}457 458SPIRVAMDToolChain::SPIRVAMDToolChain(const Driver &D,459 const llvm::Triple &Triple,460 const ArgList &Args)461 : ROCMToolChain(D, Triple, Args) {462 getProgramPaths().push_back(getDriver().Dir);463}464 465Tool *SPIRVAMDToolChain::buildLinker() const {466 assert(getTriple().getArch() == llvm::Triple::spirv64);467 return new tools::AMDGCN::Linker(*this);468}469