343 changes: 343 additions & 0 deletions clang/lib/Driver/ToolChains/HIP.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
//===--- HIP.cpp - HIP Tool and ToolChain Implementations -------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "HIP.h"
#include "CommonArgs.h"
#include "InputInfo.h"
#include "clang/Basic/Cuda.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Options.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"

using namespace clang::driver;
using namespace clang::driver::toolchains;
using namespace clang::driver::tools;
using namespace clang;
using namespace llvm::opt;

namespace {

static void addBCLib(Compilation &C, const ArgList &Args,
ArgStringList &CmdArgs, ArgStringList LibraryPaths,
StringRef BCName) {
StringRef FullName;
for (std::string LibraryPath : LibraryPaths) {
SmallString<128> Path(LibraryPath);
llvm::sys::path::append(Path, BCName);
FullName = Path;
if (llvm::sys::fs::exists(FullName)) {
CmdArgs.push_back(Args.MakeArgString(FullName));
return;
}
}
C.getDriver().Diag(diag::err_drv_no_such_file) << BCName;
}

} // namespace

const char *AMDGCN::Linker::constructLLVMLinkCommand(
Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
const ArgList &Args, StringRef SubArchName,
StringRef OutputFilePrefix) const {
ArgStringList CmdArgs;
// Add the input bc's created by compile step.
for (const auto &II : Inputs)
CmdArgs.push_back(II.getFilename());

ArgStringList LibraryPaths;

// Find in --hip-device-lib-path and HIP_LIBRARY_PATH.
for (auto Path : Args.getAllArgValues(options::OPT_hip_device_lib_path_EQ))
LibraryPaths.push_back(Args.MakeArgString(Path));

addDirectoryList(Args, LibraryPaths, "-L", "HIP_DEVICE_LIB_PATH");

llvm::SmallVector<std::string, 10> BCLibs;

// Add bitcode library in --hip-device-lib.
for (auto Lib : Args.getAllArgValues(options::OPT_hip_device_lib_EQ)) {
BCLibs.push_back(Args.MakeArgString(Lib));
}

// If --hip-device-lib is not set, add the default bitcode libraries.
if (BCLibs.empty()) {
// Get the bc lib file name for ISA version. For example,
// gfx803 => oclc_isa_version_803.amdgcn.bc.
std::string ISAVerBC =
"oclc_isa_version_" + SubArchName.drop_front(3).str() + ".amdgcn.bc";

BCLibs.append({"hip.amdgcn.bc", "hc.amdgcn.bc", "opencl.amdgcn.bc",
"ockl.amdgcn.bc", "irif.amdgcn.bc", "ocml.amdgcn.bc",
"oclc_finite_only_off.amdgcn.bc",
"oclc_daz_opt_off.amdgcn.bc",
"oclc_correctly_rounded_sqrt_on.amdgcn.bc",
"oclc_unsafe_math_off.amdgcn.bc", "hc.amdgcn.bc", ISAVerBC});
}
for (auto Lib : BCLibs)
addBCLib(C, Args, CmdArgs, LibraryPaths, Lib);

// Add an intermediate output file.
CmdArgs.push_back("-o");
std::string TmpName =
C.getDriver().GetTemporaryPath(OutputFilePrefix.str() + "-linked", "bc");
const char *OutputFileName =
C.addTempFile(C.getArgs().MakeArgString(TmpName));
CmdArgs.push_back(OutputFileName);
SmallString<128> ExecPath(C.getDriver().Dir);
llvm::sys::path::append(ExecPath, "llvm-link");
const char *Exec = Args.MakeArgString(ExecPath);
C.addCommand(llvm::make_unique<Command>(JA, *this, Exec, CmdArgs, Inputs));
return OutputFileName;
}

const char *AMDGCN::Linker::constructOptCommand(
Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
const llvm::opt::ArgList &Args, llvm::StringRef SubArchName,
llvm::StringRef OutputFilePrefix, const char *InputFileName) const {
// Construct opt command.
ArgStringList OptArgs;
// The input to opt is the output from llvm-link.
OptArgs.push_back(InputFileName);
// Pass optimization arg to opt.
if (Arg *A = Args.getLastArg(options::OPT_O_Group)) {
StringRef OOpt = "3";
if (A->getOption().matches(options::OPT_O4) ||
A->getOption().matches(options::OPT_Ofast))
OOpt = "3";
else if (A->getOption().matches(options::OPT_O0))
OOpt = "0";
else if (A->getOption().matches(options::OPT_O)) {
// -Os, -Oz, and -O(anything else) map to -O2
OOpt = llvm::StringSwitch<const char *>(A->getValue())
.Case("1", "1")
.Case("2", "2")
.Case("3", "3")
.Case("s", "2")
.Case("z", "2")
.Default("2");
}
OptArgs.push_back(Args.MakeArgString("-O" + OOpt));
}
OptArgs.push_back("-mtriple=amdgcn-amd-amdhsa");
OptArgs.push_back(Args.MakeArgString("-mcpu=" + SubArchName));
OptArgs.push_back("-o");
std::string TmpFileName = C.getDriver().GetTemporaryPath(
OutputFilePrefix.str() + "-optimized", "bc");
const char *OutputFileName =
C.addTempFile(C.getArgs().MakeArgString(TmpFileName));
OptArgs.push_back(OutputFileName);
SmallString<128> OptPath(C.getDriver().Dir);
llvm::sys::path::append(OptPath, "opt");
const char *OptExec = Args.MakeArgString(OptPath);
C.addCommand(llvm::make_unique<Command>(JA, *this, OptExec, OptArgs, Inputs));
return OutputFileName;
}

const char *AMDGCN::Linker::constructLlcCommand(
Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
const llvm::opt::ArgList &Args, llvm::StringRef SubArchName,
llvm::StringRef OutputFilePrefix, const char *InputFileName) const {
// Construct llc command.
ArgStringList LlcArgs{InputFileName, "-mtriple=amdgcn-amd-amdhsa",
"-filetype=obj",
Args.MakeArgString("-mcpu=" + SubArchName), "-o"};
std::string LlcOutputFileName =
C.getDriver().GetTemporaryPath(OutputFilePrefix, "o");
const char *LlcOutputFile =
C.addTempFile(C.getArgs().MakeArgString(LlcOutputFileName));
LlcArgs.push_back(LlcOutputFile);
SmallString<128> LlcPath(C.getDriver().Dir);
llvm::sys::path::append(LlcPath, "llc");
const char *Llc = Args.MakeArgString(LlcPath);
C.addCommand(llvm::make_unique<Command>(JA, *this, Llc, LlcArgs, Inputs));
return LlcOutputFile;
}

void AMDGCN::Linker::constructLldCommand(Compilation &C, const JobAction &JA,
const InputInfoList &Inputs,
const InputInfo &Output,
const llvm::opt::ArgList &Args,
const char *InputFileName) const {
// Construct lld command.
// The output from ld.lld is an HSA code object file.
ArgStringList LldArgs{"-flavor", "gnu", "--no-undefined",
"-shared", "-o", Output.getFilename(),
InputFileName};
SmallString<128> LldPath(C.getDriver().Dir);
llvm::sys::path::append(LldPath, "lld");
const char *Lld = Args.MakeArgString(LldPath);
C.addCommand(llvm::make_unique<Command>(JA, *this, Lld, LldArgs, Inputs));
}

// For amdgcn the inputs of the linker job are device bitcode and output is
// object file. It calls llvm-link, opt, llc, then lld steps.
void AMDGCN::Linker::ConstructJob(Compilation &C, const JobAction &JA,
const InputInfo &Output,
const InputInfoList &Inputs,
const ArgList &Args,
const char *LinkingOutput) const {

const auto &TC =
static_cast<const toolchains::HIPToolChain &>(getToolChain());
assert(TC.getTriple().getArch() == llvm::Triple::amdgcn &&
"Unsupported target");

std::string SubArchName = JA.getOffloadingArch();
assert(StringRef(SubArchName).startswith("gfx") && "Unsupported sub arch");

// Prefix for temporary file name.
std::string Prefix =
llvm::sys::path::stem(Inputs[0].getFilename()).str() + "-" + SubArchName;

// Each command outputs different files.
const char *LLVMLinkCommand =
constructLLVMLinkCommand(C, JA, Inputs, Args, SubArchName, Prefix);
const char *OptCommand = constructOptCommand(C, JA, Inputs, Args, SubArchName,
Prefix, LLVMLinkCommand);
const char *LlcCommand =
constructLlcCommand(C, JA, Inputs, Args, SubArchName, Prefix, OptCommand);
constructLldCommand(C, JA, Inputs, Output, Args, LlcCommand);
}

HIPToolChain::HIPToolChain(const Driver &D, const llvm::Triple &Triple,
const ToolChain &HostTC, const ArgList &Args)
: ToolChain(D, Triple, Args), HostTC(HostTC) {
// Lookup binaries into the driver directory, this is used to
// discover the clang-offload-bundler executable.
getProgramPaths().push_back(getDriver().Dir);
}

void HIPToolChain::addClangTargetOptions(
const llvm::opt::ArgList &DriverArgs,
llvm::opt::ArgStringList &CC1Args,
Action::OffloadKind DeviceOffloadingKind) const {
HostTC.addClangTargetOptions(DriverArgs, CC1Args, DeviceOffloadingKind);

StringRef GpuArch = DriverArgs.getLastArgValue(options::OPT_march_EQ);
assert(!GpuArch.empty() && "Must have an explicit GPU arch.");
assert(DeviceOffloadingKind == Action::OFK_HIP &&
"Only HIP offloading kinds are supported for GPUs.");

CC1Args.push_back("-fcuda-is-device");

if (DriverArgs.hasFlag(options::OPT_fcuda_flush_denormals_to_zero,
options::OPT_fno_cuda_flush_denormals_to_zero, false))
CC1Args.push_back("-fcuda-flush-denormals-to-zero");

if (DriverArgs.hasFlag(options::OPT_fcuda_approx_transcendentals,
options::OPT_fno_cuda_approx_transcendentals, false))
CC1Args.push_back("-fcuda-approx-transcendentals");

if (DriverArgs.hasFlag(options::OPT_fcuda_rdc, options::OPT_fno_cuda_rdc,
false))
CC1Args.push_back("-fcuda-rdc");
}

llvm::opt::DerivedArgList *
HIPToolChain::TranslateArgs(const llvm::opt::DerivedArgList &Args,
StringRef BoundArch,
Action::OffloadKind DeviceOffloadKind) const {
DerivedArgList *DAL =
HostTC.TranslateArgs(Args, BoundArch, DeviceOffloadKind);
if (!DAL)
DAL = new DerivedArgList(Args.getBaseArgs());

const OptTable &Opts = getDriver().getOpts();

for (Arg *A : Args) {
if (A->getOption().matches(options::OPT_Xarch__)) {
// Skip this argument unless the architecture matches BoundArch
if (BoundArch.empty() || A->getValue(0) != BoundArch)
continue;

unsigned Index = Args.getBaseArgs().MakeIndex(A->getValue(1));
unsigned Prev = Index;
std::unique_ptr<Arg> XarchArg(Opts.ParseOneArg(Args, Index));

// If the argument parsing failed or more than one argument was
// consumed, the -Xarch_ argument's parameter tried to consume
// extra arguments. Emit an error and ignore.
//
// We also want to disallow any options which would alter the
// driver behavior; that isn't going to work in our model. We
// use isDriverOption() as an approximation, although things
// like -O4 are going to slip through.
if (!XarchArg || Index > Prev + 1) {
getDriver().Diag(diag::err_drv_invalid_Xarch_argument_with_args)
<< A->getAsString(Args);
continue;
} else if (XarchArg->getOption().hasFlag(options::DriverOption)) {
getDriver().Diag(diag::err_drv_invalid_Xarch_argument_isdriver)
<< A->getAsString(Args);
continue;
}
XarchArg->setBaseArg(A);
A = XarchArg.release();
DAL->AddSynthesizedArg(A);
}
DAL->append(A);
}

if (!BoundArch.empty()) {
DAL->eraseArg(options::OPT_march_EQ);
DAL->AddJoinedArg(nullptr, Opts.getOption(options::OPT_march_EQ), BoundArch);
}

return DAL;
}

Tool *HIPToolChain::buildLinker() const {
assert(getTriple().getArch() == llvm::Triple::amdgcn);
return new tools::AMDGCN::Linker(*this);
}

void HIPToolChain::addClangWarningOptions(ArgStringList &CC1Args) const {
HostTC.addClangWarningOptions(CC1Args);
}

ToolChain::CXXStdlibType
HIPToolChain::GetCXXStdlibType(const ArgList &Args) const {
return HostTC.GetCXXStdlibType(Args);
}

void HIPToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
ArgStringList &CC1Args) const {
HostTC.AddClangSystemIncludeArgs(DriverArgs, CC1Args);
}

void HIPToolChain::AddClangCXXStdlibIncludeArgs(const ArgList &Args,
ArgStringList &CC1Args) const {
HostTC.AddClangCXXStdlibIncludeArgs(Args, CC1Args);
}

void HIPToolChain::AddIAMCUIncludeArgs(const ArgList &Args,
ArgStringList &CC1Args) const {
HostTC.AddIAMCUIncludeArgs(Args, CC1Args);
}

SanitizerMask HIPToolChain::getSupportedSanitizers() const {
// The HIPToolChain only supports sanitizers in the sense that it allows
// sanitizer arguments on the command line if they are supported by the host
// toolchain. The HIPToolChain will actually ignore any command line
// arguments for any of these "supported" sanitizers. That means that no
// sanitization of device code is actually supported at this time.
//
// This behavior is necessary because the host and device toolchains
// invocations often share the command line, so the device toolchain must
// tolerate flags meant only for the host toolchain.
return HostTC.getSupportedSanitizers();
}

VersionTuple HIPToolChain::computeMSVCVersion(const Driver *D,
const ArgList &Args) const {
return HostTC.computeMSVCVersion(D, Args);
}
123 changes: 123 additions & 0 deletions clang/lib/Driver/ToolChains/HIP.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//===--- HIP.h - HIP ToolChain Implementations ------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_HIP_H
#define LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_HIP_H

#include "clang/Driver/ToolChain.h"
#include "clang/Driver/Tool.h"

namespace clang {
namespace driver {

namespace tools {

namespace AMDGCN {
// Runs llvm-link/opt/llc/lld, which links multiple LLVM bitcode, together with
// device library, then compiles it to ISA in a shared object.
class LLVM_LIBRARY_VISIBILITY Linker : public Tool {
public:
Linker(const ToolChain &TC) : Tool("AMDGCN::Linker", "amdgcn-link", TC) {}

bool hasIntegratedCPP() const override { return false; }

void ConstructJob(Compilation &C, const JobAction &JA,
const InputInfo &Output, const InputInfoList &Inputs,
const llvm::opt::ArgList &TCArgs,
const char *LinkingOutput) const override;

private:
/// \return llvm-link output file name.
const char *constructLLVMLinkCommand(Compilation &C, const JobAction &JA,
const InputInfoList &Inputs,
const llvm::opt::ArgList &Args,
llvm::StringRef SubArchName,
llvm::StringRef OutputFilePrefix) const;

/// \return opt output file name.
const char *constructOptCommand(Compilation &C, const JobAction &JA,
const InputInfoList &Inputs,
const llvm::opt::ArgList &Args,
llvm::StringRef SubArchName,
llvm::StringRef OutputFilePrefix,
const char *InputFileName) const;

/// \return llc output file name.
const char *constructLlcCommand(Compilation &C, const JobAction &JA,
const InputInfoList &Inputs,
const llvm::opt::ArgList &Args,
llvm::StringRef SubArchName,
llvm::StringRef OutputFilePrefix,
const char *InputFileName) const;

void constructLldCommand(Compilation &C, const JobAction &JA,
const InputInfoList &Inputs, const InputInfo &Output,
const llvm::opt::ArgList &Args,
const char *InputFileName) const;
};

} // end namespace AMDGCN
} // end namespace tools

namespace toolchains {

class LLVM_LIBRARY_VISIBILITY HIPToolChain : public ToolChain {
public:
HIPToolChain(const Driver &D, const llvm::Triple &Triple,
const ToolChain &HostTC, const llvm::opt::ArgList &Args);

const llvm::Triple *getAuxTriple() const override {
return &HostTC.getTriple();
}

llvm::opt::DerivedArgList *
TranslateArgs(const llvm::opt::DerivedArgList &Args, StringRef BoundArch,
Action::OffloadKind DeviceOffloadKind) const override;
void addClangTargetOptions(const llvm::opt::ArgList &DriverArgs,
llvm::opt::ArgStringList &CC1Args,
Action::OffloadKind DeviceOffloadKind) const override;

bool useIntegratedAs() const override { return true; }
bool isCrossCompiling() const override { return true; }
bool isPICDefault() const override { return false; }
bool isPIEDefault() const override { return false; }
bool isPICDefaultForced() const override { return false; }
bool SupportsProfiling() const override { return false; }
bool IsMathErrnoDefault() const override { return false; }

void addClangWarningOptions(llvm::opt::ArgStringList &CC1Args) const override;
CXXStdlibType GetCXXStdlibType(const llvm::opt::ArgList &Args) const override;
void
AddClangSystemIncludeArgs(const llvm::opt::ArgList &DriverArgs,
llvm::opt::ArgStringList &CC1Args) const override;
void AddClangCXXStdlibIncludeArgs(
const llvm::opt::ArgList &Args,
llvm::opt::ArgStringList &CC1Args) const override;
void AddIAMCUIncludeArgs(const llvm::opt::ArgList &DriverArgs,
llvm::opt::ArgStringList &CC1Args) const override;

SanitizerMask getSupportedSanitizers() const override;

VersionTuple
computeMSVCVersion(const Driver *D,
const llvm::opt::ArgList &Args) const override;

unsigned GetDefaultDwarfVersion() const override { return 2; }

const ToolChain &HostTC;

protected:
Tool *buildLinker() const override;
};

} // end namespace toolchains
} // end namespace driver
} // end namespace clang

#endif // LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_HIP_H
Empty file.
Empty file.
84 changes: 84 additions & 0 deletions clang/test/Driver/hip-toolchain.hip
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// REQUIRES: clang-driver
// REQUIRES: x86-registered-target
// REQUIRES: amdgpu-registered-target

// RUN: %clang -### -target x86_64-linux-gnu \
// RUN: -x hip --cuda-gpu-arch=gfx803 --cuda-gpu-arch=gfx900 \
// RUN: --hip-device-lib=lib1.bc --hip-device-lib=lib2.bc \
// RUN: --hip-device-lib-path=%S/Inputs/hip_multiple_inputs/lib1 \
// RUN: --hip-device-lib-path=%S/Inputs/hip_multiple_inputs/lib2 \
// RUN: -fuse-ld=lld \
// RUN: %S/Inputs/hip_multiple_inputs/a.cu \
// RUN: %S/Inputs/hip_multiple_inputs/b.hip \
// RUN: 2>&1 | FileCheck %s

// CHECK: [[CLANG:".*clang.*"]] "-cc1" "-triple" "amdgcn-amd-amdhsa"
// CHECK-SAME: "-aux-triple" "x86_64--linux-gnu" "-emit-llvm-bc"
// CHECK-SAME: {{.*}} "-main-file-name" "a.cu" {{.*}} "-fcuda-is-device"
// CHECK-SAME: {{.*}} "-o" [[A_BC:".*bc"]] "-x" "hip"
// CHECK-SAME: {{.*}} [[A_SRC:".*a.cu"]]

// CHECK: [[CLANG]] "-cc1" "-triple" "amdgcn-amd-amdhsa"
// CHECK-SAME: "-aux-triple" "x86_64--linux-gnu" "-emit-llvm-bc"
// CHECK-SAME: {{.*}} "-main-file-name" "b.hip" {{.*}} "-fcuda-is-device"
// CHECK-SAME: {{.*}} "-o" [[B_BC:".*bc"]] "-x" "hip"
// CHECK-SAME: {{.*}} [[B_SRC:".*b.hip"]]

// CHECK: [[LLVM_LINK:"*.llvm-link"]] [[A_BC]] [[B_BC]]
// CHECK-SAME: "{{.*}}lib1.bc" "{{.*}}lib2.bc"
// CHECK-SAME: "-o" [[LINKED_BC_DEV1:".*-gfx803-linked-.*bc"]]

// CHECK: [[OPT:".*opt"]] [[LINKED_BC_DEV1]] "-mtriple=amdgcn-amd-amdhsa"
// CHECK-SAME: "-mcpu=gfx803"
// CHECK-SAME: "-o" [[OPT_BC_DEV1:".*-gfx803-optimized.*bc"]]

// CHECK: [[LLC: ".*llc"]] [[OPT_BC_DEV1]] "-mtriple=amdgcn-amd-amdhsa"
// CHECK-SAME: "-filetype=obj" "-mcpu=gfx803" "-o" [[OBJ_DEV1:".*-gfx803-.*o"]]

// CHECK: [[LLD: ".*lld"]] "-flavor" "gnu" "--no-undefined" "-shared"
// CHECK-SAME: "-o" "[[IMG_DEV1:.*out]]" [[OBJ_DEV1]]

// CHECK: [[CLANG]] "-cc1" "-triple" "amdgcn-amd-amdhsa"
// CHECK-SAME: "-aux-triple" "x86_64--linux-gnu" "-emit-llvm-bc"
// CHECK-SAME: {{.*}} "-main-file-name" "a.cu" {{.*}} "-fcuda-is-device"
// CHECK-SAME: {{.*}} "-o" [[A_BC:".*bc"]] "-x" "hip"
// CHECK-SAME: {{.*}} [[A_SRC]]

// CHECK: [[CLANG]] "-cc1" "-triple" "amdgcn-amd-amdhsa"
// CHECK-SAME: "-aux-triple" "x86_64--linux-gnu" "-emit-llvm-bc"
// CHECK-SAME: {{.*}} "-main-file-name" "b.hip" {{.*}} "-fcuda-is-device"
// CHECK-SAME: {{.*}} "-o" [[B_BC:".*bc"]] "-x" "hip"
// CHECK-SAME: {{.*}} [[B_SRC]]

// CHECK: [[LLVM_LINK]] [[A_BC]] [[B_BC]]
// CHECK-SAME: "{{.*}}lib1.bc" "{{.*}}lib2.bc"
// CHECK-SAME: "-o" [[LINKED_BC_DEV2:".*-gfx900-linked-.*bc"]]

// CHECK: [[OPT]] [[LINKED_BC_DEV2]] "-mtriple=amdgcn-amd-amdhsa"
// CHECK-SAME: "-mcpu=gfx900"
// CHECK-SAME: "-o" [[OPT_BC_DEV2:".*-gfx900-optimized.*bc"]]

// CHECK: [[LLC]] [[OPT_BC_DEV2]] "-mtriple=amdgcn-amd-amdhsa"
// CHECK-SAME: "-filetype=obj" "-mcpu=gfx900" "-o" [[OBJ_DEV2:".*-gfx900-.*o"]]

// CHECK: [[LLD]] "-flavor" "gnu" "--no-undefined" "-shared"
// CHECK-SAME: "-o" "[[IMG_DEV2:.*out]]" [[OBJ_DEV2]]

// CHECK: [[CLANG]] "-cc1" "-triple" "x86_64--linux-gnu"
// CHECK-SAME: "-aux-triple" "amdgcn-amd-amdhsa" "-emit-obj"
// CHECK-SAME: {{.*}} "-main-file-name" "a.cu"
// CHECK-SAME: {{.*}} "-o" [[A_OBJ_HOST:".*o"]] "-x" "hip"
// CHECK-SAME: {{.*}} [[A_SRC]]

// CHECK: [[CLANG]] "-cc1" "-triple" "x86_64--linux-gnu"
// CHECK-SAME: "-aux-triple" "amdgcn-amd-amdhsa" "-emit-obj"
// CHECK-SAME: {{.*}} "-main-file-name" "b.hip"
// CHECK-SAME: {{.*}} "-o" [[B_OBJ_HOST:".*o"]] "-x" "hip"
// CHECK-SAME: {{.*}} [[B_SRC]]

// CHECK: [[BUNDLER:".*clang-offload-bundler"]] "-type=o"
// CHECK-SAME: "-targets={{.*}},hip-amdgcn-amd-amdhsa-gfx803,hip-amdgcn-amd-amdhsa-gfx900"
// CHECK-SAME: "-inputs={{.*}},[[IMG_DEV1]],[[IMG_DEV2]]" "-outputs=[[BUNDLE:.*o]]"

// CHECK: [[LD:".*ld.lld"]] {{.*}} [[A_OBJ_HOST]] [[B_OBJ_HOST]]
// CHECK-SAME: {{.*}} "-T" "{{.*}}.lk"