Index: clang/include/clang/Driver/Action.h =================================================================== --- clang/include/clang/Driver/Action.h +++ clang/include/clang/Driver/Action.h @@ -73,9 +73,10 @@ OffloadBundlingJobClass, OffloadUnbundlingJobClass, OffloadWrapperJobClass, + StaticLibJobClass, JobClassFirst = PreprocessJobClass, - JobClassLast = OffloadWrapperJobClass + JobClassLast = StaticLibJobClass }; // The offloading kind determines if this action is binded to a particular @@ -637,6 +638,17 @@ } }; +class StaticLibJobAction : public JobAction { + void anchor() override; + +public: + StaticLibJobAction(ActionList &Inputs, types::ID Type); + + static bool classof(const Action *A) { + return A->getKind() == StaticLibJobClass; + } +}; + } // namespace driver } // namespace clang Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -580,6 +580,8 @@ def no_offload_arch_EQ : Joined<["--"], "no-offload-arch=">, Flags<[DriverOption]>, HelpText<"Remove CUDA/HIP offloading device architecture (e.g. sm_35, gfx906) from the list of devices to compile for. " "'all' resets the list to its default value.">; +def hip_emit_static_lib : Flag<["--"], "hip-emit-static-lib">, + HelpText<"Link clang-offload-bundler bundles and generate static library for HIP.">; def no_cuda_gpu_arch_EQ : Joined<["--"], "no-cuda-gpu-arch=">, Flags<[DriverOption]>, Alias; def cuda_noopt_device_debug : Flag<["--"], "cuda-noopt-device-debug">, Index: clang/include/clang/Driver/ToolChain.h =================================================================== --- clang/include/clang/Driver/ToolChain.h +++ clang/include/clang/Driver/ToolChain.h @@ -139,6 +139,7 @@ mutable std::unique_ptr Flang; mutable std::unique_ptr Assemble; mutable std::unique_ptr Link; + mutable std::unique_ptr StaticLibTool; mutable std::unique_ptr IfsMerge; mutable std::unique_ptr OffloadBundler; mutable std::unique_ptr OffloadWrapper; @@ -147,6 +148,7 @@ Tool *getFlang() const; Tool *getAssemble() const; Tool *getLink() const; + Tool *getStaticLibTool() const; Tool *getIfsMerge() const; Tool *getClangAs() const; Tool *getOffloadBundler() const; @@ -174,6 +176,7 @@ virtual Tool *buildAssembler() const; virtual Tool *buildLinker() const; + virtual Tool *buildStaticLibTool() const; virtual Tool *getTool(Action::ActionClass AC) const; /// \name Utilities for implementing subclasses. @@ -326,6 +329,8 @@ /// the linker suffix or name. std::string GetLinkerPath() const; + std::string GetStaticLibToolPath() const; + /// Dispatch to the specific toolchain for verbose printing. /// /// This is used when handling the verbose option to print detailed, Index: clang/lib/Driver/Action.cpp =================================================================== --- clang/lib/Driver/Action.cpp +++ clang/lib/Driver/Action.cpp @@ -43,6 +43,7 @@ return "clang-offload-unbundler"; case OffloadWrapperJobClass: return "clang-offload-wrapper"; + case StaticLibJobClass: return "static-lib-linker"; } llvm_unreachable("invalid class"); @@ -415,3 +416,8 @@ OffloadWrapperJobAction::OffloadWrapperJobAction(ActionList &Inputs, types::ID Type) : JobAction(OffloadWrapperJobClass, Inputs, Type) {} + +void StaticLibJobAction::anchor() {} + +StaticLibJobAction::StaticLibJobAction(ActionList &Inputs, types::ID Type) + : JobAction(StaticLibJobClass, Inputs, Type) {} Index: clang/lib/Driver/Driver.cpp =================================================================== --- clang/lib/Driver/Driver.cpp +++ clang/lib/Driver/Driver.cpp @@ -3493,7 +3493,12 @@ if (!LinkerInputs.empty()) { if (Action *Wrapper = OffloadBuilder.makeHostLinkAction()) LinkerInputs.push_back(Wrapper); - Action *LA = C.MakeAction(LinkerInputs, types::TY_Image); + Action *LA; + if (Args.hasArg(options::OPT_hip_emit_static_lib)) { + LA = C.MakeAction(LinkerInputs, types::TY_Image); + } else { + LA = C.MakeAction(LinkerInputs, types::TY_Image); + } LA = OffloadBuilder.processHostLinkAction(LA); Actions.push_back(LA); } Index: clang/lib/Driver/ToolChain.cpp =================================================================== --- clang/lib/Driver/ToolChain.cpp +++ clang/lib/Driver/ToolChain.cpp @@ -272,6 +272,10 @@ llvm_unreachable("Linking is not supported by this toolchain"); } +Tool *ToolChain::buildStaticLibTool() const { + llvm_unreachable("Creating static lib is not supported by this toolchain"); +} + Tool *ToolChain::getAssemble() const { if (!Assemble) Assemble.reset(buildAssembler()); @@ -290,6 +294,12 @@ return Link.get(); } +Tool *ToolChain::getStaticLibTool() const { + if (!StaticLibTool) + StaticLibTool.reset(buildStaticLibTool()); + return StaticLibTool.get(); +} + Tool *ToolChain::getIfsMerge() const { if (!IfsMerge) IfsMerge.reset(new tools::ifstool::Merger(*this)); @@ -319,6 +329,9 @@ case Action::LinkJobClass: return getLink(); + case Action::StaticLibJobClass: + return getStaticLibTool(); + case Action::InputClass: case Action::BindArchClass: case Action::OffloadClass: @@ -551,6 +564,10 @@ return GetProgramPath(getDefaultLinker()); } +std::string ToolChain::GetStaticLibToolPath() const { + return GetProgramPath("ar"); +} + types::ID ToolChain::LookupTypeForExtension(StringRef Ext) const { types::ID id = types::lookupTypeForExtension(Ext); Index: clang/lib/Driver/ToolChains/CommonArgs.h =================================================================== --- clang/lib/Driver/ToolChains/CommonArgs.h +++ clang/lib/Driver/ToolChains/CommonArgs.h @@ -51,6 +51,15 @@ llvm::opt::ArgStringList &CmdArgs, const JobAction &JA, const Tool &T); +void +AddGenerateObjFileFromHIPFatBinary(const ToolChain &TC, Compilation &C, + const InputInfo &Output, + const InputInfoList &Inputs, + const llvm::opt::ArgList &Args, + llvm::opt::ArgStringList &CmdArgs, + const JobAction &JA, + const Tool &T); + const char *SplitDebugName(const llvm::opt::ArgList &Args, const InputInfo &Input, const InputInfo &Output); Index: clang/lib/Driver/ToolChains/CommonArgs.cpp =================================================================== --- clang/lib/Driver/ToolChains/CommonArgs.cpp +++ clang/lib/Driver/ToolChains/CommonArgs.cpp @@ -1389,6 +1389,116 @@ Lksf << LksBuffer; } +void +tools::AddGenerateObjFileFromHIPFatBinary(const ToolChain &TC, Compilation &C, + const InputInfo &Output, + const InputInfoList &Inputs, + const ArgList &Args, + ArgStringList &CmdArgs, + const JobAction &JA, + const Tool &T) { + + // If this is not a HIP host toolchain, we don't need to do anything. + if (!JA.isHostOffloading(Action::OFK_HIP)) + return; + + InputInfoList DeviceInputs; + for (const auto &II : Inputs) { + const Action *A = II.getAction(); + // Is this a device linking action? + if (A && isa(A) && A->isDeviceOffloading(Action::OFK_HIP)) { + DeviceInputs.push_back(II); + } + } + + if (DeviceInputs.empty()) + return; + + std::string Name = + std::string(llvm::sys::path::filename(Output.getFilename())); + + // Get the HIP offload tool chain. + auto *HIPTC = static_cast( + C.getSingleOffloadToolChain()); + assert(HIPTC->getTriple().getArch() == llvm::Triple::amdgcn && + "Wrong platform"); + (void)HIPTC; + + // Create Offload Bundler File + const char *BundleFile; + if (C.getDriver().isSaveTempsEnabled()) { + BundleFile = C.getArgs().MakeArgString(Name + ".hipfb"); + } else { + auto TmpName = C.getDriver().GetTemporaryPath(Name, "hipfb"); + BundleFile = C.addTempFile(C.getArgs().MakeArgString(TmpName)); + } + AMDGCN::constructHIPFatbinCommand(C, JA, BundleFile, DeviceInputs, Args, T); + + // Create a buffer to write the contents of the temp obj generator. + std::string ObjBuffer; + llvm::raw_string_ostream ObjStream(ObjBuffer); + + // Create temp object file generator. Keep it if save-temps is enabled. + const char *Obj; + if (C.getDriver().isSaveTempsEnabled()) { + Obj = C.getArgs().MakeArgString(Name + ".mcin"); + } else { + auto TmpName = C.getDriver().GetTemporaryPath(Name, "mcin"); + Obj = C.addTempFile(C.getArgs().MakeArgString(TmpName)); + } + // Add MC directives to embed target binaries. We ensure that each + // section and image is 16-byte aligned. This is not mandatory, but + // increases the likelihood of data to be aligned with a cache block + // in several main host machines. + ObjStream << "# HIP Object Generator\n"; + ObjStream << "# *** Automatically generated by Clang ***\n"; + ObjStream << " .type __hip_fatbin,@object\n"; + ObjStream << " .section .hip_fatbin,\"aMS\",@progbits,1\n"; + ObjStream << " .data\n"; + ObjStream << " .globl __hip_fatbin\n"; + ObjStream << " .p2align 3\n"; + ObjStream << "__hip_fatbin:\n"; + ObjStream << " .incbin \"" << BundleFile << "\"\n"; + ObjStream.flush(); + + // Dump the contents of the linker script if the user requested that. We + // support this option to enable testing of behavior with -###. + if (C.getArgs().hasArg(options::OPT_fhip_dump_offload_linker_script)) + llvm::errs() << ObjBuffer; + + + const char *BundledObjFile; + if (C.getDriver().isSaveTempsEnabled()) { + BundledObjFile = C.getArgs().MakeArgString(Name + ".hipfbo"); + } else { + auto TmpName = C.getDriver().GetTemporaryPath(Name, "hipfbo"); + BundledObjFile = C.addTempFile(C.getArgs().MakeArgString(TmpName)); + } + + std::string Triple = TC.getTripleString(); + ArgStringList McArgs{"-triple", Args.MakeArgString(Triple), + "-o", BundledObjFile, Obj, "--filetype=obj"}; + const char *Mc = Args.MakeArgString(TC.GetProgramPath("llvm-mc")); + C.addCommand(std::make_unique(JA, T, Mc, McArgs, Inputs)); + + CmdArgs.push_back(BundledObjFile); + + // If this is a dry run, do not create the linker script file. + if (C.getArgs().hasArg(options::OPT__HASH_HASH_HASH)) + return; + + // Open script file and write the contents. + std::error_code EC; + llvm::raw_fd_ostream Objf(Obj, EC, llvm::sys::fs::OF_None); + + if (EC) { + C.getDriver().Diag(clang::diag::err_unable_to_make_temp) << EC.message(); + return; + } + + Objf << ObjBuffer; +} + SmallString<128> tools::getStatsFileName(const llvm::opt::ArgList &Args, const InputInfo &Output, const InputInfo &Input, Index: clang/lib/Driver/ToolChains/Gnu.h =================================================================== --- clang/lib/Driver/ToolChains/Gnu.h +++ clang/lib/Driver/ToolChains/Gnu.h @@ -71,6 +71,19 @@ const llvm::opt::ArgList &TCArgs, const char *LinkingOutput) const override; }; + +class LLVM_LIBRARY_VISIBILITY StaticLibTool : public GnuTool { +public: + StaticLibTool(const ToolChain &TC) : GnuTool("GNU::StaticLibTool", "static-lib-linker", TC) {} + + bool hasIntegratedCPP() const override { return false; } + bool isLinkJob() const override { return true; } + + void ConstructJob(Compilation &C, const JobAction &JA, + const InputInfo &Output, const InputInfoList &Inputs, + const llvm::opt::ArgList &TCArgs, + const char *LinkingOutput) const override; +}; } // end namespace gnutools /// gcc - Generic GCC tool implementations. Index: clang/lib/Driver/ToolChains/Gnu.cpp =================================================================== --- clang/lib/Driver/ToolChains/Gnu.cpp +++ clang/lib/Driver/ToolChains/Gnu.cpp @@ -340,6 +340,60 @@ !Args.hasArg(options::OPT_static_pie); } +void +tools::gnutools::StaticLibTool::ConstructJob(Compilation &C, const JobAction &JA, + const InputInfo &Output, + const InputInfoList &Inputs, + const ArgList &Args, + const char *LinkingOutput) const { + const toolchains::Generic_ELF &ToolChain = + static_cast(getToolChain()); + const Driver &D = ToolChain.getDriver(); + + // GNU ar tool command "ar ". + ArgStringList CmdArgs; + + // Silence warning for "clang -g foo.o -o foo" + Args.ClaimAllArgs(options::OPT_g_Group); + // and "clang -emit-llvm foo.o -o foo" + Args.ClaimAllArgs(options::OPT_emit_llvm); + // and for "clang -w foo.o -o foo". Other warning options are already + // handled somewhere else. + Args.ClaimAllArgs(options::OPT_w); + + // Create and insert file members with a deterministic index. + CmdArgs.push_back("rcsD"); + CmdArgs.push_back(Output.getFilename()); + + AddLinkerInputs(ToolChain, Inputs, Args, CmdArgs, JA); + + // Silence warnings when linking C code with a C++ '-stdlib' argument. + Args.ClaimAllArgs(options::OPT_stdlib_EQ); + + // Delete old output archive file if it already exists before generating a new + // archive file. + auto OutputFileName = Output.getFilename(); + if (Output.isFilename() && llvm::sys::fs::exists(OutputFileName)) { + if (!llvm::sys::fs::can_write(OutputFileName) + || !llvm::sys::fs::is_regular_file(OutputFileName)) { + D.Diag(diag::err_drv_unable_to_remove_file) + << "File cannot be written to or is not a regular file."; + return; + } + if (std::error_code EC = llvm::sys::fs::remove(OutputFileName)) { + D.Diag(diag::err_drv_unable_to_remove_file) << EC.message(); + return; + } + } + + // Add Generated HIP Bundled Object File. + AddGenerateObjFileFromHIPFatBinary(getToolChain(), C, Output, Inputs, Args, + CmdArgs, JA, *this); + + const char *Exec = Args.MakeArgString(ToolChain.GetStaticLibToolPath()); + C.addCommand(std::make_unique(JA, *this, Exec, CmdArgs, Inputs)); +} + void tools::gnutools::Linker::ConstructJob(Compilation &C, const JobAction &JA, const InputInfo &Output, const InputInfoList &Inputs, Index: clang/lib/Driver/ToolChains/Linux.h =================================================================== --- clang/lib/Driver/ToolChains/Linux.h +++ clang/lib/Driver/ToolChains/Linux.h @@ -55,6 +55,7 @@ protected: Tool *buildAssembler() const override; Tool *buildLinker() const override; + Tool *buildStaticLibTool() const override; std::string getMultiarchTriple(const Driver &D, const llvm::Triple &TargetTriple, Index: clang/lib/Driver/ToolChains/Linux.cpp =================================================================== --- clang/lib/Driver/ToolChains/Linux.cpp +++ clang/lib/Driver/ToolChains/Linux.cpp @@ -465,6 +465,8 @@ Tool *Linux::buildLinker() const { return new tools::gnutools::Linker(*this); } +Tool *Linux::buildStaticLibTool () const { return new tools::gnutools::StaticLibTool(*this); } + Tool *Linux::buildAssembler() const { return new tools::gnutools::Assembler(*this); } Index: clang/test/Driver/hip-static-lib-binding.hip =================================================================== --- /dev/null +++ clang/test/Driver/hip-static-lib-binding.hip @@ -0,0 +1,24 @@ +// REQUIRES: clang-driver +// REQUIRES: x86-registered-target +// REQUIRES: amdgpu-registered-target + +// RUN: touch %t.o +// RUN: %clang --hip-link -ccc-print-bindings -target x86_64-linux-gnu \ +// RUN: --hip-emit-static-lib \ +// RUN: --cuda-gpu-arch=gfx803 --cuda-gpu-arch=gfx900 -fgpu-rdc %t.o\ +// RUN: 2>&1 | FileCheck %s + +// CHECK: # "amdgcn-amd-amdhsa" - "offload bundler", inputs: ["[[IN:.*o]]"], outputs: ["[[OBJ1:.*o]]", "[[OBJ2:.*o]]", "[[OBJ3:.*o]]"] +// CHECK: # "amdgcn-amd-amdhsa" - "AMDGCN::Linker", inputs: ["[[OBJ2]]"], output: "[[IMG2:.*out]]" +// CHECK-NOT: offload bundler +// CHECK: # "amdgcn-amd-amdhsa" - "AMDGCN::Linker", inputs: ["[[OBJ3]]"], output: "[[IMG3:.*out]]" +// CHECK-NOT: offload bundler +// CHECK: # "x86_64-unknown-linux-gnu" - "GNU::StaticLibTool", inputs: ["[[OBJ1]]", "[[IMG2]]", "[[IMG3]]"], output: "a.out" + +// RUN: %clang --hip-link -ccc-print-bindings -target x86_64-linux-gnu \ +// RUN: --hip-emit-static-lib \ +// RUN: --cuda-gpu-arch=gfx803 --cuda-gpu-arch=gfx900 %t.o\ +// RUN: 2>&1 | FileCheck -check-prefix=NORDC %s + +// NORDC-NOT: offload bundler +// NORDC: # "x86_64-unknown-linux-gnu" - "GNU::StaticLibTool", inputs: ["{{.*o}}"], output: "a.out" Index: clang/test/Driver/hip-toolchain-rdc-static-lib.hip =================================================================== --- /dev/null +++ clang/test/Driver/hip-toolchain-rdc-static-lib.hip @@ -0,0 +1,94 @@ +// 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-emit-static-lib -nogpulib \ +// RUN: -fuse-ld=lld -fgpu-rdc \ +// 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-unknown-linux-gnu" +// CHECK-SAME: "-emit-llvm-bc" +// CHECK-SAME: {{.*}} "-main-file-name" "a.cu" {{.*}} "-target-cpu" "gfx803" +// CHECK-SAME: "-fcuda-is-device" "-fgpu-rdc" +// 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-unknown-linux-gnu" +// CHECK-SAME: "-emit-llvm-bc" +// CHECK-SAME: {{.*}} "-main-file-name" "b.hip" {{.*}} "-target-cpu" "gfx803" +// CHECK-SAME: "-fcuda-is-device" "-fgpu-rdc" +// 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: "-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: "-mcpu=gfx803" +// CHECK-SAME: "-filetype=obj" +// CHECK-SAME: "-o" [[OBJ_DEV1:".*-gfx803-.*o"]] + +// CHECK: [[LLD: ".*lld"]] {{.*}} "-o" "[[IMG_DEV1:.*out]]" [[OBJ_DEV1]] + +// CHECK: [[CLANG]] "-cc1" "-triple" "amdgcn-amd-amdhsa" +// CHECK-SAME: "-aux-triple" "x86_64-unknown-linux-gnu" +// CHECK-SAME: "-emit-llvm-bc" +// CHECK-SAME: {{.*}} "-main-file-name" "a.cu" {{.*}} "-target-cpu" "gfx900" +// CHECK-SAME: "-fcuda-is-device" "-fgpu-rdc" +// 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-unknown-linux-gnu" +// CHECK-SAME: "-emit-llvm-bc" +// CHECK-SAME: {{.*}} "-main-file-name" "b.hip" {{.*}} "-target-cpu" "gfx900" +// CHECK-SAME: "-fcuda-is-device" "-fgpu-rdc" +// CHECK-SAME: {{.*}} "-o" [[B_BC:".*bc"]] "-x" "hip" +// CHECK-SAME: {{.*}} [[B_SRC]] + +// CHECK: [[LLVM_LINK]] [[A_BC]] [[B_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: "-mcpu=gfx900" +// CHECK-SAME: "-filetype=obj" +// CHECK-SAME: "-o" [[OBJ_DEV2:".*-gfx900-.*o"]] + +// CHECK: [[LLD]] {{.*}} "-o" "[[IMG_DEV2:.*out]]" [[OBJ_DEV2]] + +// CHECK: [[CLANG]] "-cc1" "-triple" "x86_64-unknown-linux-gnu" +// CHECK-SAME: "-aux-triple" "amdgcn-amd-amdhsa" +// CHECK-SAME: "-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-unknown-linux-gnu" +// CHECK-SAME: "-aux-triple" "amdgcn-amd-amdhsa" +// CHECK-SAME: "-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:.*hipfb]]" + +// CHECK: [[MC:".*llvm-mc"]] "-triple" "x86_64-unknown-linux-gnu" +// CHECK-SAME: "-o" [[OBJBUNDLE:".*hipfbo"]] "{{.*}}.mcin" "--filetype=obj" + +// CHECK: [[AR:".*ar.*"]] "rcsD" "{{.*}}.out" [[A_OBJ_HOST]] [[B_OBJ_HOST]] [[OBJBUNDLE]]