//===- comgr-compiler.cpp - Comgr compiler Action internals ---------------===// // // Part of Comgr, under the Apache License v2.0 with LLVM Exceptions. See // amd/comgr/LICENSE.TXT in this repository for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// /// /// \file /// This file implements the compilation and compilation-adjacent /// AMD_COMGR_ACTIONs. Many of these leverage Comgr's AMDGPUCompiler class. /// //===----------------------------------------------------------------------===// #include "comgr-compiler.h" #include "comgr-cache.h" #include "comgr-clang-command.h" #include "comgr-device-libs.h" #include "comgr-diagnostic-handler.h" #include "comgr-env.h" #include "comgr-libcxx-headers.h" #include "comgr-resource-directory.h" #include "comgr-spirv-command.h" #include "comgr-unbundle-command.h" #include "lld/Common/CommonLinkerContext.h" #include "lld/Common/Driver.h" #include "clang/CodeGen/CodeGenAction.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/Job.h" #include "clang/Driver/OffloadBundler.h" #include "clang/Driver/Tool.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/FrontendTool/Utils.h" #include "clang/Options/Options.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/IR/Constants.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/IR/Verifier.h" #include "llvm/IRReader/IRReader.h" #include "llvm/Linker/Linker.h" #include "llvm/MC/MCAsmBackend.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCCodeEmitter.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCInstPrinter.h" #include "llvm/MC/MCInstrInfo.h" #include "llvm/MC/MCObjectFileInfo.h" #include "llvm/MC/MCObjectWriter.h" #include "llvm/MC/MCParser/MCAsmParser.h" #include "llvm/MC/MCParser/MCTargetAsmParser.h" #include "llvm/MC/MCRegisterInfo.h" #include "llvm/MC/MCStreamer.h" #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/MC/MCTargetOptions.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Object/Archive.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/WithColor.h" #include "llvm/TargetParser/Host.h" #include "llvm/Transforms/Utils/Cloning.h" #include "time-stat/ts-interface.h" #ifdef COMGR_SPIRV_TRANSLATOR_AVAILABLE #include #endif #include #include LLD_HAS_DRIVER(elf) using namespace llvm; using namespace llvm::opt; using namespace llvm::sys; using namespace clang; using namespace clang::driver; using namespace clang::options; using namespace COMGR::TimeStatistics; namespace COMGR { namespace { constexpr llvm::StringLiteral LinkerJobName = "amdgpu::Linker"; /// \brief Helper class for representing a single invocation of the assembler. struct AssemblerInvocation { /// @name Target Options /// @{ /// The name of the target triple to assemble for. std::string Triple; /// If given, the name of the target CPU to determine which instructions /// are legal. std::string CPU; /// The list of target specific features to enable or disable -- this should /// be a list of strings starting with '+' or '-'. std::vector Features; /// The list of symbol definitions. std::vector SymbolDefs; /// @} /// @name Language Options /// @{ std::vector IncludePaths; unsigned NoInitialTextSection : 1; unsigned SaveTemporaryLabels : 1; unsigned GenDwarfForAssembly : 1; unsigned RelaxELFRelocations : 1; unsigned DwarfVersion; std::string DwarfDebugFlags; std::string DwarfDebugProducer; std::string DebugCompilationDir; llvm::DebugCompressionType CompressDebugSections = llvm::DebugCompressionType::None; std::string MainFileName; /// @} /// @name Frontend Options /// @{ std::string InputFile; std::vector LLVMArgs; std::string OutputPath; enum FileType { FT_Asm, ///< Assembly (.s) output, transliterate mode. FT_Null, ///< No output, for timing purposes. FT_Obj ///< Object file output. }; FileType OutputType; unsigned ShowHelp : 1; unsigned ShowVersion : 1; /// @} /// @name Transliterate Options /// @{ unsigned OutputAsmVariant; unsigned ShowEncoding : 1; unsigned ShowInst : 1; /// @} /// @name Assembler Options /// @{ unsigned RelaxAll : 1; unsigned NoExecStack : 1; unsigned FatalWarnings : 1; unsigned IncrementalLinkerCompatible : 1; /// The name of the relocation model to use. std::string RelocationModel; /// @} public: AssemblerInvocation() { Triple = ""; NoInitialTextSection = 0; InputFile = "-"; OutputPath = "-"; OutputType = FT_Asm; OutputAsmVariant = 0; ShowInst = 0; ShowEncoding = 0; RelaxAll = 0; NoExecStack = 0; FatalWarnings = 0; IncrementalLinkerCompatible = 0; DwarfVersion = 0; } static bool createFromArgs(AssemblerInvocation &Res, ArrayRef Argv, DiagnosticsEngine &Diags); }; } // namespace bool AssemblerInvocation::createFromArgs(AssemblerInvocation &Opts, ArrayRef Argv, DiagnosticsEngine &Diags) { bool Success = true; // Parse the arguments. const OptTable &OptTbl = getDriverOptTable(); llvm::opt::Visibility VisibilityMask(options::CC1AsOption); unsigned MissingArgIndex, MissingArgCount; InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, VisibilityMask); // Check for missing argument error. if (MissingArgCount) { Diags.Report(diag::err_drv_missing_argument) << Args.getArgString(MissingArgIndex) << MissingArgCount; Success = false; } // Issue errors on unknown arguments. for (const Arg *A : Args.filtered(OPT_UNKNOWN)) { auto ArgString = A->getAsString(Args); std::string Nearest; if (OptTbl.findNearest(ArgString, Nearest, VisibilityMask) > 1) { Diags.Report(diag::err_drv_unknown_argument) << ArgString; } else { Diags.Report(diag::err_drv_unknown_argument_with_suggestion) << ArgString << Nearest; } Success = false; } // Construct the invocation. // Target Options Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple)); Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu)); Opts.Features = Args.getAllArgValues(OPT_target_feature); // Use the default target triple if unspecified. if (Opts.Triple.empty()) { Opts.Triple = llvm::sys::getDefaultTargetTriple(); } // Language Options Opts.IncludePaths = Args.getAllArgValues(OPT_I); Opts.NoInitialTextSection = Args.hasArg(OPT_n); Opts.SaveTemporaryLabels = Args.hasArg(OPT_msave_temp_labels); // Any DebugInfoKind implies GenDwarfForAssembly. Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ); if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections, OPT_compress_debug_sections_EQ)) { if (A->getOption().getID() == OPT_compress_debug_sections) { // TODO: be more clever about the compression type auto-detection Opts.CompressDebugSections = llvm::DebugCompressionType::Zlib; } else { Opts.CompressDebugSections = llvm::StringSwitch(A->getValue()) .Case("none", llvm::DebugCompressionType::None) .Case("zlib", llvm::DebugCompressionType::Zlib) .Default(llvm::DebugCompressionType::None); } } Opts.RelaxELFRelocations = !Args.hasArg(OPT_mrelax_relocations_no); Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags); Opts.DwarfDebugFlags = std::string(Args.getLastArgValue(OPT_dwarf_debug_flags)); Opts.DwarfDebugProducer = std::string(Args.getLastArgValue(OPT_dwarf_debug_producer)); Opts.DebugCompilationDir = std::string(Args.getLastArgValue(OPT_fdebug_compilation_dir)); Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name)); // Frontend Options if (Args.hasArg(OPT_INPUT)) { bool First = true; for (const Arg *A : Args.filtered(OPT_INPUT)) { if (First) { Opts.InputFile = A->getValue(); First = false; } else { Diags.Report(diag::err_drv_unknown_argument) << A->getAsString(Args); Success = false; } } } Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm); Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o)); if (Arg *A = Args.getLastArg(OPT_filetype)) { StringRef Name = A->getValue(); unsigned OutputType = StringSwitch(Name) .Case("asm", FT_Asm) .Case("null", FT_Null) .Case("obj", FT_Obj) .Default(~0U); if (OutputType == ~0U) { Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name; Success = false; } else { Opts.OutputType = FileType(OutputType); } } Opts.ShowHelp = Args.hasArg(OPT_help); Opts.ShowVersion = Args.hasArg(OPT_version); // Transliterate Options Opts.OutputAsmVariant = getLastArgIntValue(Args, OPT_output_asm_variant, 0, Diags); Opts.ShowEncoding = Args.hasArg(OPT_show_encoding); Opts.ShowInst = Args.hasArg(OPT_show_inst); // Assemble Options Opts.RelaxAll = Args.hasArg(OPT_mrelax_all); Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack); Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings); Opts.RelocationModel = std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic")); Opts.IncrementalLinkerCompatible = Args.hasArg(OPT_mincremental_linker_compatible); Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym); return Success; } namespace { bool needsPreprocessing(DataObject *O) { if (O->DataKind != AMD_COMGR_DATA_KIND_SOURCE) return false; StringRef Ext = path::extension(O->Name); bool IsPreprocessedSource = Ext == ".i"; return !IsPreprocessedSource; } std::unique_ptr getOutputStream(AssemblerInvocation &Opts, DiagnosticsEngine &Diags, bool Binary) { if (Opts.OutputPath.empty()) { Opts.OutputPath = "-"; } // Make sure that the Out file gets unlinked from the disk if we get a // SIGINT. if (Opts.OutputPath != "-") { sys::RemoveFileOnSignal(Opts.OutputPath); } std::error_code EC; auto Out = std::make_unique( Opts.OutputPath, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_Text)); if (EC) { Diags.Report(diag::err_fe_unable_to_open_output) << Opts.OutputPath << EC.message(); return nullptr; } return Out; } // clang/tools/driver/cc1as_main.cpp, ExecuteAssemblerImpl() bool executeAssemblerImpl(AssemblerInvocation &Opts, DiagnosticsEngine &Diags, raw_ostream &LogS) { // Get the target specific parser. std::string Error; const Target *TheTarget = TargetRegistry::lookupTarget( llvm::Triple(Opts.Triple), Error); if (!TheTarget) { return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple; } ErrorOr> Buffer = MemoryBuffer::getFileOrSTDIN(Opts.InputFile); if (std::error_code EC = Buffer.getError()) { Error = EC.message(); return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile; } SourceMgr SrcMgr; SrcMgr.setDiagHandler( [](const SMDiagnostic &SMDiag, void *LogS) { SMDiag.print("", *(raw_ostream *)LogS, /* ShowColors */ false); }, &LogS); // Tell SrcMgr about this buffer, which is what the parser will pick up. SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc()); // Record the location of the include directories so that the lexer can find // it later. SrcMgr.setIncludeDirs(Opts.IncludePaths); std::unique_ptr MRI(TheTarget->createMCRegInfo( llvm::Triple(Opts.Triple))); assert(MRI && "Unable to create target register info!"); llvm::MCTargetOptions MCOptions; MCOptions.X86RelaxRelocations = Opts.RelaxELFRelocations; MCOptions.CompressDebugSections = Opts.CompressDebugSections; std::unique_ptr MAI( TheTarget->createMCAsmInfo(*MRI, llvm::Triple(Opts.Triple), MCOptions)); assert(MAI && "Unable to create target asm info!"); // Ensure MCAsmInfo initialization occurs before any use, otherwise sections // may be created with a combination of default and explicit settings. bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj; std::unique_ptr FDOS = getOutputStream(Opts, Diags, IsBinary); if (!FDOS) { return true; } // Build up the feature string from the target feature list. std::string FS; if (!Opts.Features.empty()) { FS = Opts.Features[0]; for (unsigned I = 1, E = Opts.Features.size(); I != E; ++I) { FS += "," + Opts.Features[I]; } } std::unique_ptr MOFI(new MCObjectFileInfo()); std::unique_ptr STI( TheTarget->createMCSubtargetInfo(llvm::Triple(Opts.Triple), Opts.CPU, FS)); MCContext Ctx(Triple(Opts.Triple), *MAI, *MRI, *STI, &SrcMgr); Ctx.setObjectFileInfo(MOFI.get()); bool PIC = false; if (Opts.RelocationModel == "static") { PIC = false; } else if (Opts.RelocationModel == "pic") { PIC = true; } else { assert(Opts.RelocationModel == "dynamic-no-pic" && "Invalid PIC model!"); PIC = false; } MOFI->initMCObjectFileInfo(Ctx, PIC); if (Opts.GenDwarfForAssembly) { Ctx.setGenDwarfForAssembly(true); } if (!Opts.DwarfDebugFlags.empty()) { Ctx.setDwarfDebugFlags(StringRef(Opts.DwarfDebugFlags)); } if (!Opts.DwarfDebugProducer.empty()) { Ctx.setDwarfDebugProducer(StringRef(Opts.DwarfDebugProducer)); } if (!Opts.DebugCompilationDir.empty()) { Ctx.setCompilationDir(Opts.DebugCompilationDir); } if (!Opts.MainFileName.empty()) { Ctx.setMainFileName(StringRef(Opts.MainFileName)); } Ctx.setDwarfVersion(Opts.DwarfVersion); std::unique_ptr Str; std::unique_ptr MCII(TheTarget->createMCInstrInfo()); raw_pwrite_stream *Out = FDOS.get(); std::unique_ptr BOS; // FIXME: There is a bit of code duplication with addPassesToEmitFile. if (Opts.OutputType == AssemblerInvocation::FT_Asm) { std::unique_ptr InstructionPrinter( TheTarget->createMCInstPrinter( llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI)); std::unique_ptr MCE; std::unique_ptr MAB; if (Opts.ShowEncoding) { MCE.reset(TheTarget->createMCCodeEmitter(*MCII, Ctx)); MCTargetOptions Options; MAB.reset(TheTarget->createMCAsmBackend(*STI, *MRI, Options)); } auto FOut = std::make_unique(*Out); Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), std::move(InstructionPrinter), std::move(MCE), std::move(MAB))); } else if (Opts.OutputType == AssemblerInvocation::FT_Null) { Str.reset(createNullStreamer(Ctx)); } else { assert(Opts.OutputType == AssemblerInvocation::FT_Obj && "Invalid file type!"); if (!FDOS->supportsSeeking()) { BOS = std::make_unique(*FDOS); Out = BOS.get(); } MCCodeEmitter *CE = TheTarget->createMCCodeEmitter(*MCII, Ctx); MCTargetOptions Options; MCAsmBackend *MAB = TheTarget->createMCAsmBackend(*STI, *MRI, Options); Triple T(Opts.Triple); Str.reset(TheTarget->createMCObjectStreamer( T, Ctx, std::unique_ptr(MAB), MAB->createObjectWriter(*Out), std::unique_ptr(CE), *STI)); Str.get()->initSections(*STI); } bool Failed = false; std::unique_ptr Parser( createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI)); // FIXME: init MCTargetOptions from sanitizer flags here. MCTargetOptions Options; std::unique_ptr TAP( TheTarget->createMCAsmParser(*STI, *Parser, *MCII)); if (!TAP) { Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple; } // Set values for symbols, if any. for (auto &S : Opts.SymbolDefs) { auto Pair = StringRef(S).split('='); auto Sym = Pair.first; auto Val = Pair.second; int64_t Value; // We have already error checked this in the driver. if (!Val.getAsInteger(0, Value)) { Ctx.setSymbolValue(Parser->getStreamer(), Sym, Value); } } if (!Failed) { Parser->setTargetParser(*TAP.get()); Failed = Parser->Run(Opts.NoInitialTextSection); } return Failed; } bool executeAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags, raw_ostream &LogS) { bool Failed = executeAssemblerImpl(Opts, Diags, LogS); // Delete output file if there were errors. if (Failed && Opts.OutputPath != "-") { sys::fs::remove(Opts.OutputPath); } return Failed; } SmallString<128> getFilePath(DataObject *Object, StringRef Dir) { SmallString<128> Path(Dir); path::append(Path, Object->Name); // Create directories specified in the File Path so that the in-process driver // can successfully execute clang commands that use this file path as an // output argument if (fs::create_directories(path::parent_path(Path))) { return SmallString<128>(); } return Path; } // TODO: Move inputFromFile and outputToFile within AMDGPUCompiler // // Currently, we only invoke these two methods in the context of AMDGPUCompiler. // Moreover, member functions that deal with file I/O should not worry whether // the underlying filesystem being used is virtual or real. amd_comgr_status_t inputFromFile(DataObject *Object, StringRef Path) { ProfilePoint Point("FileIO"); auto BufOrError = MemoryBuffer::getFile(Path); if (std::error_code EC = BufOrError.getError()) { return AMD_COMGR_STATUS_ERROR; } Object->setData(BufOrError.get()->getBuffer()); return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t outputToFile(StringRef Data, StringRef Path) { SmallString<128> DirPath = Path; path::remove_filename(DirPath); { ProfilePoint Point("CreateDir"); if (fs::create_directories(DirPath)) { return AMD_COMGR_STATUS_ERROR; } } std::error_code EC; ProfilePoint Point("FileIO"); raw_fd_ostream OS(Path, EC, fs::OF_None); if (EC) { return AMD_COMGR_STATUS_ERROR; } OS << Data; OS.close(); if (OS.has_error()) { return AMD_COMGR_STATUS_ERROR; } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t outputToFile(DataObject *Object, StringRef Path) { return outputToFile(StringRef(Object->Data, Object->Size), Path); } void initializeCommandLineArgs(SmallVectorImpl &Args) { // Workaround for flawed Driver::BuildCompilation(...) implementation, // which eliminates 1st argument, cause it actually awaits argv[0]. Args.clear(); Args.push_back(""); } // Parse -mllvm options amd_comgr_status_t parseLLVMOptions(const std::vector &Options) { std::vector LLVMArgs; for (auto Option : Options) { LLVMArgs.push_back(""); LLVMArgs.push_back(Option.c_str()); if (!cl::ParseCommandLineOptions(LLVMArgs.size(), &LLVMArgs[0], "-mllvm options parsing")) { return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } LLVMArgs.clear(); } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t linkWithLLD(llvm::ArrayRef Args, llvm::raw_ostream &LogS, llvm::raw_ostream &LogE) { ArgStringList LLDArgs(llvm::iterator_range::iterator>( Args.begin(), Args.end())); LLDArgs.insert(LLDArgs.begin(), "ld.lld"); LLDArgs.push_back("--threads=1"); ArrayRef ArgRefs = llvm::ArrayRef(LLDArgs); lld::Result LLDRet = lld::lldMain(ArgRefs, LogS, LogE, {{lld::Gnu, &lld::elf::link}}); lld::CommonLinkerContext::destroy(); if (LLDRet.retCode || !LLDRet.canRunAgain) { return AMD_COMGR_STATUS_ERROR; } return AMD_COMGR_STATUS_SUCCESS; } // Execute llvm-link in-process using llvm::Linker // Args format: -o ... // TODO: refactor this implementation to use a shared infra with linkBitcodeToBitcode() amd_comgr_status_t executeLLVMLink(ArrayRef Args, raw_ostream &LogS) { // Parse args: find -o and collect input .bc files StringRef OutputPath; SmallVector InputPaths; for (size_t I = 0; I < Args.size(); ++I) { StringRef Arg(Args[I]); if (Arg == "-o" && I + 1 < Args.size()) { OutputPath = Args[++I]; } else if (Arg.ends_with(".bc")) { InputPaths.push_back(Arg); } } if (OutputPath.empty() || InputPaths.empty()) { LogS << "llvm-link: missing input or output files\n"; return AMD_COMGR_STATUS_ERROR; } // Create composite module and linker LLVMContext Context; auto Composite = std::make_unique("llvm-link", Context); Linker L(*Composite); // Link each input BC for (StringRef InputPath : InputPaths) { auto BufOrErr = MemoryBuffer::getFile(InputPath); if (!BufOrErr) { LogS << "llvm-link: failed to read: " << InputPath << "\n"; return AMD_COMGR_STATUS_ERROR; } SMDiagnostic Err; auto Mod = parseIR(BufOrErr->get()->getMemBufferRef(), Err, Context); if (!Mod) { Err.print("llvm-link", LogS); return AMD_COMGR_STATUS_ERROR; } if (L.linkInModule(std::move(Mod))) { LogS << "llvm-link: linking failed for: " << InputPath << "\n"; return AMD_COMGR_STATUS_ERROR; } } // Write linked BC to output file std::error_code EC; raw_fd_ostream OS(OutputPath, EC); if (EC) { LogS << "llvm-link: failed to open output: " << OutputPath << "\n"; return AMD_COMGR_STATUS_ERROR; } WriteBitcodeToFile(*Composite, OS); return AMD_COMGR_STATUS_SUCCESS; } #ifdef COMGR_SPIRV_TRANSLATOR_AVAILABLE namespace { // Map "SPV_…" extension name → SPIRV::ExtensionID using the x-macro shipped // with the translator. Mirrors the table that amd-llvm-spirv builds in // parseSPVExtOption (llvm-spirv.cpp). const llvm::DenseMap &spirvExtensionNameMap() { static const auto Map = []() { llvm::DenseMap M; #define EXT(X) M[#X] = SPIRV::ExtensionID::X; #include "LLVMSPIRVExtensions.inc" #undef EXT return M; }(); return Map; } bool parseSpirvExtList(StringRef Spec, SPIRV::TranslatorOpts::ExtensionsStatusMap &Status, raw_ostream &LogS) { const auto &Names = spirvExtensionNameMap(); SmallVector Items; Spec.split(Items, ',', -1, false); for (StringRef Item : Items) { if (Item.empty() || (Item.front() != '+' && Item.front() != '-')) { LogS << "spirv-translator: invalid --spirv-ext value '" << Item << "' (expected +EXT or -EXT)\n"; return false; } bool Allow = Item.front() == '+'; StringRef Name = Item.drop_front(); if (Name == "all") { for (const auto &E : Names) Status[E.second] = Allow; continue; } auto It = Names.find(Name); if (It == Names.end()) { LogS << "spirv-translator: unknown extension '" << Name << "' in --spirv-ext\n"; return false; } Status[It->second] = Allow; } return true; } // Parse the --spirv-* subset of the amd-llvm-spirv CLI that the HIPAMD clang // driver emits (see clang/lib/Driver/ToolChains/HIPAMD.cpp), populating Opts // to match. Also extracts InputPath (.bc) and OutputPath (-o argument). // Unrecognized --spirv-* flags are logged and ignored. bool parseSPIRVTranslatorArgs(ArrayRef Args, SPIRV::TranslatorOpts &Opts, StringRef &InputPath, StringRef &OutputPath, raw_ostream &LogS) { SPIRV::VersionNumber MaxVer = SPIRV::VersionNumber::MaximumVersion; SPIRV::TranslatorOpts::ExtensionsStatusMap ExtStatus; std::optional DebugEIS; std::optional AllowUnknownIntrinsics; bool PreserveAux = false; for (size_t I = 0, N = Args.size(); I < N; ++I) { if (!Args[I]) continue; StringRef Arg(Args[I]); if (Arg == "-o") { if (I + 1 >= N) { LogS << "spirv-translator: '-o' requires an argument\n"; return false; } OutputPath = Args[++I]; continue; } if (Arg.ends_with(".bc")) { InputPath = Arg; continue; } // Normalize: strip leading "--" or "-" so we can compare against bare // names. StringRef Body = Arg; if (!Body.consume_front("--") && !Body.consume_front("-")) continue; auto EqPos = Body.find('='); StringRef Name = Body.substr(0, EqPos); bool HasValue = EqPos != StringRef::npos; StringRef Value = HasValue ? Body.substr(EqPos + 1) : StringRef(); if (Name == "spirv-max-version" && HasValue) { using V_ = SPIRV::VersionNumber; auto Parsed = llvm::StringSwitch>(Value) .Case("1.0", V_::SPIRV_1_0) .Case("1.1", V_::SPIRV_1_1) .Case("1.2", V_::SPIRV_1_2) .Case("1.3", V_::SPIRV_1_3) .Case("1.4", V_::SPIRV_1_4) .Case("1.5", V_::SPIRV_1_5) .Case("1.6", V_::SPIRV_1_6) .Default(std::nullopt); if (!Parsed) { LogS << "spirv-translator: unknown --spirv-max-version '" << Value << "'\n"; return false; } MaxVer = *Parsed; } else if (Name == "spirv-ext" && HasValue) { if (!parseSpirvExtList(Value, ExtStatus, LogS)) return false; } else if (Name == "spirv-debug-info-version" && HasValue) { using EIS_ = SPIRV::DebugInfoEIS; auto Parsed = llvm::StringSwitch>(Value) .Case("legacy", EIS_::SPIRV_Debug) .Case("ocl-100", EIS_::OpenCL_DebugInfo_100) .Case("nonsemantic-shader-100", EIS_::NonSemantic_Shader_DebugInfo_100) .Case("nonsemantic-shader-200", EIS_::NonSemantic_Shader_DebugInfo_200) .Default(std::nullopt); if (!Parsed) { LogS << "spirv-translator: unknown --spirv-debug-info-version '" << Value << "'\n"; return false; } DebugEIS = *Parsed; } else if (Name == "spirv-allow-unknown-intrinsics") { // Bare flag → allow all unknown intrinsics. With =prefix1,prefix2 → // restrict to listed prefixes. Matches cl::ValueOptional semantics in // llvm-spirv.cpp. The translator's isUnknownIntrinsicAllowed only // returns true if some prefix matches; an empty prefix matches every // intrinsic name (StringRef::starts_with("") is true), so represent // "allow all" as a single empty prefix rather than an empty list. SPIRV::TranslatorOpts::ArgList Prefixes; if (HasValue) { SmallVector Parts; Value.split(Parts, ',', -1, false); for (StringRef P : Parts) Prefixes.push_back(P); } else { Prefixes.push_back(StringRef()); } AllowUnknownIntrinsics = std::move(Prefixes); } else if (Name == "spirv-preserve-auxdata") { PreserveAux = true; } else if (Name == "spirv-lower-const-expr") { // No-op: this is a global cl::opt with cl::init(true) in // SPIRVLowerConstExpr.cpp; the default already matches. Recognized so we // don't warn. } else if (Name.starts_with("spirv-")) { LogS << "spirv-translator: ignoring unrecognized flag '" << Arg << "'\n"; } } Opts = SPIRV::TranslatorOpts(MaxVer, ExtStatus); if (PreserveAux) Opts.setPreserveAuxData(true); if (DebugEIS) Opts.setDebugInfoEIS(*DebugEIS); if (AllowUnknownIntrinsics) Opts.setSPIRVAllowUnknownIntrinsics(*AllowUnknownIntrinsics); return true; } } // namespace // Execute amd-llvm-spirv in-process using writeSpirv. // Args format: [options...] -o amd_comgr_status_t executeSPIRVTranslator(ArrayRef Args, raw_ostream &LogS) { StringRef InputPath, OutputPath; SPIRV::TranslatorOpts Opts; if (!parseSPIRVTranslatorArgs(Args, Opts, InputPath, OutputPath, LogS)) return AMD_COMGR_STATUS_ERROR; if (InputPath.empty() || OutputPath.empty()) { LogS << "spirv-translator: missing input or output files\n"; return AMD_COMGR_STATUS_ERROR; } // Read input bitcode auto BufOrErr = MemoryBuffer::getFile(InputPath); if (!BufOrErr) { LogS << "spirv-translator: failed to read: " << InputPath << "\n"; return AMD_COMGR_STATUS_ERROR; } LLVMContext Context; SMDiagnostic Err; auto Mod = parseIR(BufOrErr->get()->getMemBufferRef(), Err, Context); if (!Mod) { Err.print("spirv-translator", LogS); return AMD_COMGR_STATUS_ERROR; } // Translate to SPIRV std::string ErrMsg; std::ostringstream OSS; if (!writeSpirv(Mod.get(), Opts, OSS, ErrMsg)) { LogS << "spirv-translator: translation failed: " << ErrMsg << "\n"; return AMD_COMGR_STATUS_ERROR; } // Write SPIRV to output file std::error_code EC; raw_fd_ostream OS(OutputPath, EC); if (EC) { LogS << "spirv-translator: failed to open output: " << OutputPath << "\n"; return AMD_COMGR_STATUS_ERROR; } std::string Result = OSS.str(); OS.write(Result.data(), Result.size()); return AMD_COMGR_STATUS_SUCCESS; } #endif void logArgv(raw_ostream &OS, StringRef ProgramName, ArrayRef Argv) { OS << " Driver Job Args: " << ProgramName; for (size_t I = 0; I < Argv.size(); ++I) { // Skip the first argument, which we replace with ProgramName, and the last // argument, which is a null terminator. if (I && Argv[I]) { OS << " \"" << Argv[I] << '\"'; } } OS << '\n'; OS.flush(); } amd_comgr_status_t executeCommand(const Command &Job, raw_ostream &LogS, DiagnosticOptions &DiagOpts, IntrusiveRefCntPtr FS) { TextDiagnosticPrinter DiagClient(LogS, DiagOpts); IntrusiveRefCntPtr DiagID(new DiagnosticIDs); DiagnosticsEngine Diags(DiagID, DiagOpts, &DiagClient, false); auto Arguments = Job.getArguments(); SmallVector Argv; initializeCommandLineArgs(Argv); Argv.append(Arguments.begin(), Arguments.end()); Argv.push_back(nullptr); clearLLVMOptions(); if (Argv[1] == StringRef("-cc1")) { if (env::shouldEmitVerboseLogs()) { logArgv(LogS, "clang", Argv); } std::unique_ptr Clang(new CompilerInstance()); Clang->setVerboseOutputStream(LogS); Clang->setVirtualFileSystem(FS); if (!Argv.back()) { Argv.pop_back(); } if (!CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv, Diags)) { return AMD_COMGR_STATUS_ERROR; } // Internally this call refers to the invocation created above, so at // this point the DiagnosticsEngine should accurately reflect all user // requested configuration from Argv. Clang->createDiagnostics(&DiagClient, /* ShouldOwnClient */ false); if (!Clang->hasDiagnostics()) { return AMD_COMGR_STATUS_ERROR; } if (!ExecuteCompilerInvocation(Clang.get())) { return AMD_COMGR_STATUS_ERROR; } } else if (Argv[1] == StringRef("-cc1as")) { if (env::shouldEmitVerboseLogs()) { logArgv(LogS, "clang", Argv); } Argv.erase(Argv.begin() + 1); if (!Argv.back()) { Argv.pop_back(); } AssemblerInvocation Asm; if (!AssemblerInvocation::createFromArgs(Asm, Argv, Diags)) { return AMD_COMGR_STATUS_ERROR; } if (auto Status = parseLLVMOptions(Asm.LLVMArgs)) { return Status; } if (executeAssembler(Asm, Diags, LogS)) { return AMD_COMGR_STATUS_ERROR; } } else if (Job.getCreator().getName() == LinkerJobName) { if (env::shouldEmitVerboseLogs()) { logArgv(LogS, "lld", Argv); } if (auto Status = linkWithLLD(Arguments, LogS, LogS)) { return Status; } } else { // Check executable name for additional tools (e.g., from AMDGCN::Linker) StringRef Executable = Job.getExecutable(); StringRef ExeName = sys::path::filename(Executable); if (ExeName.contains("llvm-link")) { if (env::shouldEmitVerboseLogs()) { logArgv(LogS, "llvm-link", Argv); } return executeLLVMLink(Arguments, LogS); } #ifdef COMGR_SPIRV_TRANSLATOR_AVAILABLE if (ExeName.contains("llvm-spirv")) { if (env::shouldEmitVerboseLogs()) { logArgv(LogS, "amd-llvm-spirv", Argv); } return executeSPIRVTranslator(Arguments, LogS); } #endif LogS << " Unhandled Job: " << Job.getCreator().getName() << " (executable: " << ExeName << ")\n"; return AMD_COMGR_STATUS_ERROR; } return AMD_COMGR_STATUS_SUCCESS; } std::string getStableCUID(const DataSet *InSet) { using Hash = CachedCommandAdaptor::HashAlgorithm; Hash H; for (const DataObject *Input : InSet->DataObjects) { CachedCommandAdaptor::addFileContents(H, StringRef{Input->Data, Input->Size}); } return toHex(H.final()); } } // namespace amd_comgr_status_t AMDGPUCompiler::executeInProcessDriver(ArrayRef Args) { // A DiagnosticsEngine is required at several points: // * By the Driver in order to diagnose option parsing. // * By the CompilerInvocation in order to diagnose option parsing. // * By the CompilerInstance in order to diagnose everything else. // It is a chicken-and-egg problem in that you need some form of diagnostics // in order to diagnose options which further influence diagnostics. The code // here is mostly copy-and-pasted from driver.cpp/cc1_main.cpp/various Clang // tests to try to approximate the same behavior as running the `clang` // executable. std::unique_ptr DiagOpts(new DiagnosticOptions); unsigned MissingArgIndex, MissingArgCount; InputArgList ArgList = getDriverOptTable().ParseArgs( Args.slice(1), MissingArgIndex, MissingArgCount); // We ignore MissingArgCount and the return value of ParseDiagnosticArgs. Any // errors that would be diagnosed here will also be diagnosed later, when the // DiagnosticsEngine actually exists. (void)ParseDiagnosticArgs(*DiagOpts, ArgList); TextDiagnosticPrinter *DiagClient = new TextDiagnosticPrinter(LogS, *DiagOpts); IntrusiveRefCntPtr DiagID(new DiagnosticIDs); DiagnosticsEngine Diags(DiagID, *DiagOpts, DiagClient); ProcessWarningOptions(Diags, *DiagOpts, *OverlayFS, /*ReportDiags=*/false); Driver TheDriver((Twine(env::getLLVMPath()) + "/bin/clang").str(), llvm::sys::getDefaultTargetTriple(), Diags, "AMDGPU Code Object Manager", OverlayFS); TheDriver.setCheckInputsExist(false); // We do not want the driver to promote -include into -include-pch. // Otherwise, the driver may pick PCH in the wrong format, without permissions, // in the process's CWD. TheDriver.setProbePrecompiled(false); // Log arguments used to build compilation if (env::shouldEmitVerboseLogs()) { LogS << " Compilation Args: "; for (size_t I = 1; I < Args.size(); ++I) { if (Args[I]) { LogS << " \"" << Args[I] << '\"'; } } LogS << '\n'; LogS.flush(); } std::unique_ptr C(TheDriver.BuildCompilation(Args)); if (!C || C->containsError()) { return AMD_COMGR_STATUS_ERROR; } auto Cache = CommandCache::get(LogS); for (auto &Job : C->getJobs()) { ClangCommand C(Job, *DiagOpts, OverlayFS, executeCommand); if (Cache) { if (auto Status = Cache->execute(C, LogS)) { return Status; } } else { if (auto Status = C.execute(LogS)) { return Status; } } } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::createTmpDirs() { static std::atomic Id = 0; static Process::Pid Pid = Process::getProcessId(); std::string TmpDirPrefix("comgr-" + std::to_string(Pid) + "-" + std::to_string(Id++)); ProfilePoint Point("CreateDir"); if (std::error_code EC = fs::createUniqueDirectory(TmpDirPrefix, TmpDir)) { if (env::shouldEmitVerboseLogs()) { LogS << "comgr-compiler: failed to create temporary directory '" << TmpDirPrefix << "': " << EC.message() << "\n"; const char *TmpDirEnv = std::getenv("TMPDIR"); if (TmpDirEnv) LogS << "comgr-compiler: TMPDIR='" << TmpDirEnv << "' may not exist or be writable\n"; } return AMD_COMGR_STATUS_ERROR; } InputDir = TmpDir; path::append(InputDir, "input"); if (fs::create_directory(InputDir)) { return AMD_COMGR_STATUS_ERROR; } OutputDir = TmpDir; path::append(OutputDir, "output"); if (fs::create_directory(OutputDir)) { return AMD_COMGR_STATUS_ERROR; } IncludeDir = TmpDir; path::append(IncludeDir, "include"); if (fs::create_directory(IncludeDir)) { return AMD_COMGR_STATUS_ERROR; } return AMD_COMGR_STATUS_SUCCESS; } // On windows fs::remove_directories takes huge time so use fs::remove. #ifdef _WIN32 amd_comgr_status_t removeDirectory(const StringRef DirName) { std::error_code EC; for (fs::directory_iterator Dir(DirName, EC), DirEnd; Dir != DirEnd && !EC; Dir.increment(EC)) { const StringRef Path = Dir->path(); fs::file_status Status; EC = fs::status(Path, Status); if (EC) { return AMD_COMGR_STATUS_ERROR; } switch (Status.type()) { case fs::file_type::regular_file: if (fs::remove(Path)) { return AMD_COMGR_STATUS_ERROR; } break; case fs::file_type::directory_file: if (removeDirectory(Path)) { return AMD_COMGR_STATUS_ERROR; } if (fs::remove(Path)) { return AMD_COMGR_STATUS_ERROR; } break; default: return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } } if (fs::remove(DirName)) { return AMD_COMGR_STATUS_ERROR; } return AMD_COMGR_STATUS_SUCCESS; } #endif amd_comgr_status_t AMDGPUCompiler::removeTmpDirs() { if (TmpDir.empty()) { return AMD_COMGR_STATUS_SUCCESS; } ProfilePoint Point("RemoveDir"); #ifndef _WIN32 if (fs::remove_directories(TmpDir)) { return AMD_COMGR_STATUS_ERROR; } return AMD_COMGR_STATUS_SUCCESS; #else return removeDirectory(TmpDir); #endif } amd_comgr_status_t AMDGPUCompiler::processFile(DataObject *Input, const char *InputFilePath, const char *OutputFilePath) { SmallVector Argv = Args; for (auto &Option : ActionInfo->getOptions()) { Argv.push_back(Option.c_str()); if (Option.rfind("--rocm-path", 0) == 0) { NoGpuLib = false; } } // The ROCm device library should be provided via --rocm-path. Otherwise // we can pass -nogpulib to build without the ROCm device library if (NoGpuLib) { Argv.push_back("-nogpulib"); } // Auto-inject embedded libc++ headers as a fallback include path. // Using -idirafter places them AFTER all other include paths, so: // - System libstdc++ or libc++ headers take priority when available // - User-provided -I paths take priority // - Embedded headers only kick in when no other C++ headers are found // This ensures backward compatibility while providing headers on systems // without C++ development headers (e.g., driver-only installs). if (HasEmbeddedHeaders && getLanguage() == AMD_COMGR_LANGUAGE_HIP) { SmallString<256> LibcxxPath(env::getLLVMPath()); sys::path::append(LibcxxPath, "include", "c++", "v1"); Argv.push_back("-idirafter"); Argv.push_back(Saver.save(StringRef(LibcxxPath)).data()); } // TODO: Enable this for OpenCL as well (SWDEV-377546) if (getLanguage() == AMD_COMGR_LANGUAGE_HIP && env::shouldSaveLLVMTemps()) { Argv.push_back("-save-temps=obj"); } // Add SPIR-V flags for (auto Flag : Input->SpirvFlags) { Argv.push_back("-Xclang"); Argv.push_back(Flag); } // By default clang driver will ask CC1 to leak memory. Argv.push_back("-Xclang"); Argv.push_back("-no-disable-free"); // Append options from AMD_COMGR_DRIVER_OPTIONS_APPEND environment variable. // Options are space-separated and appended after all other options. StringRef EnvOptions = env::getDriverOptionsAppend(); if (!EnvOptions.empty()) { SmallVector Options; EnvOptions.split(Options, ' ', /*MaxSplit=*/-1, /*KeepEmpty=*/false); for (StringRef Opt : Options) Argv.push_back(Saver.save(Opt).data()); } Argv.push_back(InputFilePath); Argv.push_back("-o"); Argv.push_back(OutputFilePath); return executeInProcessDriver(Argv); } amd_comgr_status_t AMDGPUCompiler::processFiles(amd_comgr_data_kind_t OutputKind, const char *OutputSuffix) { return processFiles(OutputKind, OutputSuffix, InSet); } amd_comgr_status_t AMDGPUCompiler::processFiles(amd_comgr_data_kind_t OutputKind, const char *OutputSuffix, DataSet *InSet) { for (auto *Input : InSet->DataObjects) { if (Input->DataKind != AMD_COMGR_DATA_KIND_INCLUDE) { continue; } auto IncludeFilePath = getFilePath(Input, IncludeDir); if (auto Status = outputToFile(Input, IncludeFilePath)) { return Status; } } for (auto *Input : InSet->DataObjects) { if (Input->DataKind != AMD_COMGR_DATA_KIND_SOURCE && Input->DataKind != AMD_COMGR_DATA_KIND_BC && Input->DataKind != AMD_COMGR_DATA_KIND_RELOCATABLE && Input->DataKind != AMD_COMGR_DATA_KIND_EXECUTABLE) { continue; } auto InputFilePath = getFilePath(Input, InputDir); if (auto Status = outputToFile(Input, InputFilePath)) { return Status; } amd_comgr_data_t OutputT; if (auto Status = amd_comgr_create_data(OutputKind, &OutputT)) { return Status; } // OutputT can be released after addition to the data_set ScopedDataObjectReleaser SDOR(OutputT); DataObject *Output = DataObject::convert(OutputT); SmallString<128> OutputName(Input->Name); sys::path::replace_extension(OutputName, OutputSuffix); Output->setName(OutputName); auto OutputFilePath = getFilePath(Output, OutputDir); if (auto Status = processFile(Input, InputFilePath.c_str(), OutputFilePath.c_str())) { return Status; } if (auto Status = inputFromFile(Output, OutputFilePath)) { return Status; } if (auto Status = amd_comgr_data_set_add(OutSetT, OutputT)) { return Status; } } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::addIncludeFlags() { if (none_of(InSet->DataObjects, needsPreprocessing)) return AMD_COMGR_STATUS_SUCCESS; amd_comgr_language_t Language = ActionInfo->Language; switch (Language) { case AMD_COMGR_LANGUAGE_OPENCL_1_2: case AMD_COMGR_LANGUAGE_OPENCL_2_0: { SmallString<128> OpenCLCBasePath = IncludeDir; sys::path::append(OpenCLCBasePath, "opencl-c-base.h"); if (auto Status = outputToFile(getOpenCLCBaseHeaderContents(), OpenCLCBasePath)) { return Status; } Args.push_back("-include"); Args.push_back(Saver.save(OpenCLCBasePath.c_str()).data()); Args.push_back("-Xclang"); Args.push_back("-fdeclare-opencl-builtins"); break; } default: break; } if (ActionInfo->Path) { Args.push_back("-I"); Args.push_back(ActionInfo->Path); } Args.push_back("-I"); Args.push_back(IncludeDir.c_str()); for (auto *Input : InSet->DataObjects) { if (Input->DataKind != AMD_COMGR_DATA_KIND_PRECOMPILED_HEADER) { continue; } PrecompiledHeaders.push_back(getFilePath(Input, IncludeDir)); auto &PrecompiledHeaderPath = PrecompiledHeaders.back(); if (auto Status = outputToFile(Input, PrecompiledHeaderPath)) { return Status; } Args.push_back("-include-pch"); Args.push_back(PrecompiledHeaderPath.c_str()); Args.push_back("-Xclang"); Args.push_back("-fno-validate-pch"); } bool CacheEnabled = CommandCache::get(LogS) != nullptr; if (PrecompiledHeaders.empty() && CacheEnabled) { // The -no-integrated-cpp is used to split the preprocessing stage from the // rest of the compilation jobs. The cache doesn't handle source-code input, // but can handle preprocessed input (to avoid dealing with includes). Args.push_back("-no-integrated-cpp"); // The -dD option is used to keep the #define directives in the preprocessed // output. When -fdeclare-opencl-builtins is used, the opencl builtin // semantic analysis queries the preprocessor for macro definitions that // signal that an OpenCL feature is enabled. After preprocessing these // #define are gone, so the semantic analysis during the compilation stage // fails. This flag is used to keep them such that they are present during // the compilation stage. // Additionally, we need to keep the definitions for #pragma directives. // The preprocessor doesn't expand macro identifiers in #pragmas, and if we // do not pass -dD the definitions would be missing when clang parses the // code Args.push_back("-dD"); } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::addTargetIdentifierFlags(llvm::StringRef IdentStr, bool CompilingSrc = false) { TargetIdentifier Ident; if (auto Status = parseTargetIdentifier(IdentStr, Ident)) { return Status; } std::string GPUArch = Twine(Ident.Processor).str(); if (!Ident.Features.empty()) { GPUArch += ":" + join(Ident.Features, ":"); } if (CompilingSrc && getLanguage() == AMD_COMGR_LANGUAGE_HIP) { // OffloadArch Args.push_back(Saver.save(Twine("--offload-arch=") + GPUArch).data()); } else { // Triple and CPU Args.push_back("-target"); Args.push_back( Saver.save(Twine(Ident.Arch) + "-" + Ident.Vendor + "-" + Ident.OS) .data()); Args.push_back(Saver.save(Twine("-mcpu=") + GPUArch).data()); } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::addCompilationFlags() { // Default to O3 for all contexts Args.push_back("-O3"); Args.push_back("-x"); bool NeedsPreprocessing = any_of(InSet->DataObjects, needsPreprocessing); switch (ActionInfo->Language) { case AMD_COMGR_LANGUAGE_LLVM_IR: Args.push_back("ir"); break; case AMD_COMGR_LANGUAGE_OPENCL_1_2: Args.push_back(NeedsPreprocessing ? "cl" : "cl-cpp-output"); Args.push_back("-std=cl1.2"); Args.push_back("-cl-no-stdinc"); break; case AMD_COMGR_LANGUAGE_OPENCL_2_0: Args.push_back(NeedsPreprocessing ? "cl" : "cl-cpp-output"); Args.push_back("-std=cl2.0"); Args.push_back("-cl-no-stdinc"); break; case AMD_COMGR_LANGUAGE_HIP: Args.push_back(NeedsPreprocessing ? "hip" : "hip-cpp-output"); Args.push_back("--offload-device-only"); // Pass a cuid that depends on the input files // Otherwise, a random (which depends on the /tmp/comgr-xxxxx path) cuid is // generated which causes a cache miss on every run. Args.push_back(Saver.save("-cuid=" + getStableCUID(InSet)).data()); break; default: return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::outputResource(llvm::StringRef Path, llvm::StringRef FileContent) { // TODO: We should abstract the logic of deciding whether to use the VFS // or the real file system within inputFromFile and outputToFile. if (UseVFS) { if (!InMemoryFS->addFile(Path, /* ModificationTime */ 0, llvm::MemoryBuffer::getMemBuffer(FileContent))) { return AMD_COMGR_STATUS_ERROR; } } else { if (auto Status = outputToFile(FileContent, Path)) { return Status; } } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::addDeviceLibraries() { SmallString<256> ClangBinaryPath(env::getLLVMPath()); sys::path::append(ClangBinaryPath, "bin", "clang"); std::string ClangResourceDir = GetResourcesPath(ClangBinaryPath); NoGpuLib = false; for (ResourceDirResource ResourceDirEntry : getResourceDirectoryFiles()) { llvm::SmallString<128> ResourcePath(ClangResourceDir); path::append(ResourcePath, ResourceDirEntry.RelativePath); amd_comgr_status_t Status = outputResource(ResourcePath, ResourceDirEntry.FileContent); if (Status != AMD_COMGR_STATUS_SUCCESS) { return Status; } } // TODO: This manual handling of device libs is redundant. Remove it in the // future when device-libs is converted to using the runtimes build to the // resource directory. SmallString<256> DeviceLibPath(ClangResourceDir); sys::path::append(DeviceLibPath, "lib"); SmallString<256> DeviceCodeDir(DeviceLibPath); sys::path::append(DeviceCodeDir, "amdgcn", "bitcode"); if (llvm::sys::fs::exists(DeviceCodeDir)) { Args.push_back(Saver.save(Twine("--rocm-path=") + DeviceLibPath).data()); } else { llvm::SmallString<128> FakeRocmDir = TmpDir; path::append(FakeRocmDir, "rocm"); llvm::SmallString<128> DeviceLibsDir = FakeRocmDir; path::append(DeviceLibsDir, "amdgcn", "bitcode"); if (fs::create_directory(InputDir)) { return AMD_COMGR_STATUS_ERROR; } Args.push_back(Saver.save(Twine("--rocm-path=") + FakeRocmDir).data()); for (auto DeviceLib : getDeviceLibraries()) { llvm::SmallString<128> DeviceLibPath = DeviceLibsDir; path::append(DeviceLibPath, std::get<0>(DeviceLib)); amd_comgr_status_t Status = outputResource(DeviceLibPath, std::get<1>(DeviceLib)); if (Status != AMD_COMGR_STATUS_SUCCESS) return Status; } } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::preprocessToSource() { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName, true)) { return Status; } } if (auto Status = addIncludeFlags()) { return Status; } if (auto Status = addCompilationFlags()) { return Status; } Args.push_back("-E"); return processFiles(AMD_COMGR_DATA_KIND_SOURCE, ".i"); } amd_comgr_status_t AMDGPUCompiler::compileToBitcode(bool WithDeviceLibs) { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName, true)) { return Status; } } if (auto Status = addIncludeFlags()) { return Status; } if (auto Status = addCompilationFlags()) { return Status; } Args.push_back("-c"); Args.push_back("-emit-llvm"); #if _WIN32 Args.push_back("-fshort-wchar"); #endif // TODO: Deprecate WithDeviceLibs in favor of ActionInfo->ShouldLinkDeviceLibs if (WithDeviceLibs || ActionInfo->ShouldLinkDeviceLibs) { if (auto Status = addDeviceLibraries()) { return Status; } // Currently linking postopt is only needed for OpenCL. If this becomes // necessary for HIP (for example if HIP adopts the same AMDGPUSimplifyLibs // strategy that potentially introduces undefined device-library symbols), // we will need also apply this option in compileToRelocatable(). Args.push_back("-Xclang"); Args.push_back("-mlink-builtin-bitcode-postopt"); } return processFiles(AMD_COMGR_DATA_KIND_BC, ".bc"); } amd_comgr_status_t AMDGPUCompiler::compileToExecutable() { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName, true)) { return Status; } } if (auto Status = addIncludeFlags()) { return Status; } if (auto Status = addCompilationFlags()) { return Status; } #if _WIN32 Args.push_back("-fshort-wchar"); #endif // TODO: Remove "true" conditional once dependent APIs have included new // new *_set_device_lib_linking API if (ActionInfo->ShouldLinkDeviceLibs || true) { if (auto Status = addDeviceLibraries()) { return Status; } } return processFiles(AMD_COMGR_DATA_KIND_EXECUTABLE, ".so"); } amd_comgr_status_t AMDGPUCompiler::compileToRelocatable() { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->Language != AMD_COMGR_LANGUAGE_HIP) { return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName, true)) { return Status; } } Args.push_back("-c"); Args.push_back("-fhip-emit-relocatable"); Args.push_back("-mllvm"); Args.push_back("-amdgpu-internalize-symbols"); if (auto Status = addIncludeFlags()) { return Status; } if (auto Status = addCompilationFlags()) { return Status; } #if _WIN32 Args.push_back("-fshort-wchar"); #endif // TODO: Remove "true" conditional once dependent APIs have included new // new *_set_device_lib_linking API if (ActionInfo->ShouldLinkDeviceLibs || true) { if (auto Status = addDeviceLibraries()) { return Status; } } return processFiles(AMD_COMGR_DATA_KIND_RELOCATABLE, ".o"); } amd_comgr_status_t AMDGPUCompiler::unbundle() { if (auto Status = createTmpDirs()) { return Status; } // Collect bitcode memory buffers from bitcodes, bundles, and archives auto Cache = CommandCache::get(LogS); for (auto *Input : InSet->DataObjects) { const char *FileExtension; amd_comgr_data_kind_t UnbundledDataKind; switch (Input->DataKind) { case AMD_COMGR_DATA_KIND_BC_BUNDLE: FileExtension = "bc"; UnbundledDataKind = AMD_COMGR_DATA_KIND_BC; break; case AMD_COMGR_DATA_KIND_AR_BUNDLE: FileExtension = "a"; UnbundledDataKind = AMD_COMGR_DATA_KIND_AR; break; case AMD_COMGR_DATA_KIND_OBJ_BUNDLE: FileExtension = "o"; UnbundledDataKind = AMD_COMGR_DATA_KIND_EXECUTABLE; break; default: return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } // Configure Offload Bundler OffloadBundlerConfig BundlerConfig; BundlerConfig.AllowMissingBundles = true; BundlerConfig.FilesType = FileExtension; BundlerConfig.HipOpenmpCompatible = 1; BundlerConfig.AllowNoHost = 1; // Generate random name if none provided if (!strcmp(Input->Name, "")) { const size_t BufSize = sizeof(char) * 30; char *Buf = (char *)malloc(BufSize); snprintf(Buf, BufSize, "comgr-bundle-%d.%s", std::rand() % 10000, FileExtension); Input->Name = Buf; } // Write input file system so that OffloadBundler API can process // TODO: Switch write to VFS SmallString<128> InputFilePath = getFilePath(Input, InputDir); if (auto Status = outputToFile(Input, InputFilePath)) { return Status; } // Bundler input name BundlerConfig.InputFileNames.emplace_back(InputFilePath); // Generate prefix for output files StringRef OutputPrefix = Input->Name; size_t Index = OutputPrefix.find_last_of("."); OutputPrefix = OutputPrefix.substr(0, Index); // TODO: Log Command (see linkBitcodeToBitcode() unbundling) if (env::shouldEmitVerboseLogs()) { LogS << " Extracting Bundle:\n" << " Input Filename: " << BundlerConfig.InputFileNames[0] << "\n" << " Unbundled Files Extension: ." << FileExtension << "\n"; } for (StringRef Entry : ActionInfo->BundleEntryIDs) { // Add an output file for each target SmallString<128> OutputFilePath = OutputDir; sys::path::append(OutputFilePath, OutputPrefix + "-" + Entry + "." + FileExtension); BundlerConfig.TargetNames.emplace_back(Entry); BundlerConfig.OutputFileNames.emplace_back(OutputFilePath); if (env::shouldEmitVerboseLogs()) { LogS << "\tBundle Entry ID: " << Entry << "\n" << "\tOutput Filename: " << OutputFilePath << "\n"; LogS.flush(); } } UnbundleCommand Unbundle(Input->DataKind, BundlerConfig); if (Cache) { if (auto Status = Cache->execute(Unbundle, LogS)) { return Status; } } else { if (auto Status = Unbundle.execute(LogS)) { return Status; } } // Add new bitcodes to OutSetT for (StringRef OutputFilePath : BundlerConfig.OutputFileNames) { amd_comgr_data_t ResultT; if (auto Status = amd_comgr_create_data(UnbundledDataKind, &ResultT)) return Status; // ResultT can be released after addition to the data_set ScopedDataObjectReleaser SDOR(ResultT); DataObject *Result = DataObject::convert(ResultT); if (auto Status = inputFromFile(Result, OutputFilePath)) return Status; StringRef OutputFileName = sys::path::filename(OutputFilePath); Result->setName(OutputFileName); if (auto Status = amd_comgr_data_set_add(OutSetT, ResultT)) { return Status; } } } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::linkBitcodeToBitcode() { if (auto Status = createTmpDirs()) { return Status; } SMDiagnostic SMDiag; LLVMContext Context; Context.setDiagnosticHandler( std::make_unique(this->LogS), true); auto Composite = std::make_unique("llvm-link", Context); Linker L(*Composite); unsigned ApplicableFlags = Linker::Flags::None; // Collect bitcode memory buffers from bitcodes, bundles, and archives for (auto *Input : InSet->DataObjects) { if (!strcmp(Input->Name, "")) { // If the calling API doesn't provide a DataObject name, generate a random // string to assign. This string is used when the DataObject is written // to the file system via SAVE_TEMPS, or if the object is a bundle which // also needs a file system write for unpacking std::string Name = "comgr-anon-bitcode-" + std::to_string(std::rand() % 10000) + ".bc"; Input->setName(Name); } if (env::shouldSaveTemps()) { if (auto Status = outputToFile(Input, getFilePath(Input, InputDir))) { return Status; } } if (Input->DataKind == AMD_COMGR_DATA_KIND_BC) { if (env::shouldEmitVerboseLogs()) { LogS << "\t Linking Bitcode: " << InputDir << path::get_separator() << Input->Name << "\n"; } // The data in Input outlives Mod, and the linker destructs Mod after // linking it into composite (i.e. ownership is not transferred to the // composite) so MemoryBuffer::getMemBuffer is sufficient. auto Mod = getLazyIRModule(MemoryBuffer::getMemBuffer( StringRef(Input->Data, Input->Size), "", false), SMDiag, Context, true); if (!Mod) { SMDiag.print(Input->Name, LogS, /* ShowColors */ false); return AMD_COMGR_STATUS_ERROR; } if (verifyModule(*Mod, &LogS)) return AMD_COMGR_STATUS_ERROR; if (L.linkInModule(std::move(Mod), ApplicableFlags)) return AMD_COMGR_STATUS_ERROR; } else if (Input->DataKind == AMD_COMGR_DATA_KIND_BC_BUNDLE) { if (env::shouldEmitVerboseLogs()) { LogS << " Linking Bundle: " << InputDir << path::get_separator() << Input->Name << "\n"; } // Determine desired bundle entry ID // TODO: Move away from using ActionInfo->IsaName // Use ActionInfo->BundleEntryIDs instead if (!ActionInfo->IsaName) return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; std::string IsaName = ActionInfo->IsaName; size_t Index = IsaName.find("gfx"); std::string BundleEntryId = "hip-amdgcn-amd-amdhsa--gfx" + IsaName.substr(Index + 3); // Write data to file system so that Offload Bundler can process, assuming // we didn't already write due to shouldSaveTemps() conditional above // TODO: Switch write to VFS if (!env::shouldSaveTemps()) { if (auto Status = outputToFile(Input, getFilePath(Input, InputDir))) { return Status; } } // Configure Offload Bundler OffloadBundlerConfig BundlerConfig; BundlerConfig.AllowMissingBundles = true; BundlerConfig.FilesType = "bc"; BundlerConfig.TargetNames.push_back(BundleEntryId); std::string InputFilePath = getFilePath(Input, InputDir).str().str(); BundlerConfig.InputFileNames.push_back(InputFilePath); // Generate prefix for output files std::string OutputPrefix = std::string(Input->Name); Index = OutputPrefix.find_last_of("."); OutputPrefix = OutputPrefix.substr(0, Index); std::string OutputFileName = OutputPrefix + '-' + BundleEntryId + ".bc"; // ISA name may contain ':', which is an invalid character in file names // on Windows. Replace with '_' std::replace(OutputFileName.begin(), OutputFileName.end(), ':', '_'); std::string OutputFilePath = OutputDir.str().str() + path::get_separator().str() + OutputFileName; BundlerConfig.OutputFileNames.push_back(OutputFilePath); OffloadBundler Bundler(BundlerConfig); // Execute unbundling if (env::shouldEmitVerboseLogs()) { LogS << "Extracting Bitcode Bundle:\n" << "\t Bundle Entry ID: " << BundlerConfig.TargetNames[0] << "\n" << "\t Input Filename: " << BundlerConfig.InputFileNames[0] << "\n" << "\t Output Filename: " << BundlerConfig.OutputFileNames[0] << "\n"; LogS << "\t Command: clang-offload-bundler -unbundle -type=bc" " -targets=" << BundleEntryId << " -input=" << InputFilePath << " -output=" << OutputFilePath << "\n"; LogS.flush(); } llvm::Error Err = Bundler.UnbundleFiles(); llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "UnbundleFiles error: "); // Read unbundled bitcode from file system in order to pass to linker amd_comgr_data_t ResultT; if (auto Status = amd_comgr_create_data(AMD_COMGR_DATA_KIND_BC, &ResultT)) return Status; // ResultT can be released after addition to the data_set ScopedDataObjectReleaser SDOR(ResultT); DataObject *Result = DataObject::convert(ResultT); if (auto Status = inputFromFile(Result, StringRef(OutputFilePath))) return Status; Result->Name = strdup(OutputFileName.c_str()); auto Mod = getLazyIRModule(MemoryBuffer::getMemBuffer( StringRef(Result->Data, Result->Size), "", false), SMDiag, Context, true); if (!Mod) { SMDiag.print(Result->Name, LogS, /* ShowColors */ false); return AMD_COMGR_STATUS_ERROR; } if (verifyModule(*Mod, &LogS)) return AMD_COMGR_STATUS_ERROR; if (L.linkInModule(std::move(Mod), ApplicableFlags)) return AMD_COMGR_STATUS_ERROR; } // Unbundle bitcode archive else if (Input->DataKind == AMD_COMGR_DATA_KIND_AR_BUNDLE) { if (env::shouldEmitVerboseLogs()) { LogS << "\t Linking Archive: " << InputDir << path::get_separator() << Input->Name << "\n"; } // Determine desired bundle entry ID // TODO: Move away from using ActionInfo->IsaName // Use ActionInfo->BundleEntryIDs instead if (!ActionInfo->IsaName) return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; std::string IsaName = ActionInfo->IsaName; size_t Index = IsaName.find("gfx"); std::string BundleEntryId = "hip-amdgcn-amd-amdhsa--gfx" + IsaName.substr(Index + 3); // Write data to file system so that Offload Bundler can process, assuming // we didn't already write due to shouldSaveTemps() conditional above // TODO: Switch write to VFS if (!env::shouldSaveTemps()) { if (auto Status = outputToFile(Input, getFilePath(Input, InputDir))) { return Status; } } // Configure Offload Bundler OffloadBundlerConfig BundlerConfig; BundlerConfig.AllowMissingBundles = true; BundlerConfig.FilesType = "a"; BundlerConfig.HipOpenmpCompatible = 1; BundlerConfig.AllowNoHost = 1; BundlerConfig.TargetNames.push_back(BundleEntryId); std::string InputFilePath = getFilePath(Input, InputDir).str().str(); BundlerConfig.InputFileNames.push_back(InputFilePath); // Generate prefix for output files std::string OutputPrefix = std::string(Input->Name); Index = OutputPrefix.find_last_of("."); OutputPrefix = OutputPrefix.substr(0, Index); std::string OutputFileName = OutputPrefix + '-' + BundleEntryId + ".a"; // ISA name may contain ':', which is an invalid character in file names // on Windows. Replace with '_' std::replace(OutputFileName.begin(), OutputFileName.end(), ':', '_'); std::string OutputFilePath = OutputDir.str().str() + path::get_separator().str() + OutputFileName; BundlerConfig.OutputFileNames.push_back(OutputFilePath); OffloadBundler Bundler(BundlerConfig); // Execute unbundling if (env::shouldEmitVerboseLogs()) { LogS << " Extracting Bitcode Archive:\n" << "\t Bundle Entry ID: " << BundlerConfig.TargetNames[0] << "\n" << "\t Input Filename: " << BundlerConfig.InputFileNames[0] << "\n" << "\t Output Filename: " << BundlerConfig.OutputFileNames[0] << "\n"; LogS << "\t Command: clang-offload-bundler -unbundle -type=a " " -targets=" << BundleEntryId << " -input=" << InputFilePath << " -output=" << OutputFilePath << "\n"; LogS.flush(); } llvm::Error Err = Bundler.UnbundleArchive(); llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "UnbundleArchive error: "); // Read archive back into Comgr amd_comgr_data_t ResultT; if (auto Status = amd_comgr_create_data(AMD_COMGR_DATA_KIND_AR, &ResultT)) return Status; // ResultT can be released after addition to the data_set ScopedDataObjectReleaser SDOR(ResultT); DataObject *Result = DataObject::convert(ResultT); if (auto Status = inputFromFile(Result, StringRef(OutputFilePath))) return Status; // Get memory buffer for each bitcode in archive file // Modeled after static loadArFile in llvm-link.cpp std::string ArchiveName = "comgr.ar"; llvm::StringRef ArchiveBuf = StringRef(Result->Data, Result->Size); auto ArchiveOrError = object::Archive::create(MemoryBufferRef(ArchiveBuf, ArchiveName)); if (!ArchiveOrError) { llvm::logAllUnhandledErrors(ArchiveOrError.takeError(), llvm::errs(), "Unpack Archives error: "); return AMD_COMGR_STATUS_ERROR; } auto Archive = std::move(ArchiveOrError.get()); Err = Error::success(); for (const object::Archive::Child &C : Archive->children(Err)) { // Get child name Expected Ename = C.getName(); if (Error E = Ename.takeError()) { errs() << ": "; WithColor::error() << " failed to read name of archive member" << ArchiveName << "'\n"; return AMD_COMGR_STATUS_ERROR; } std::string ChildName = Ename.get().str(); // Get memory buffer SMDiagnostic ParseErr; Expected MemBuf = C.getMemoryBufferRef(); if (Error E = MemBuf.takeError()) { errs() << ": "; WithColor::error() << " loading memory for member '" << "' of archive library failed'" << ArchiveName << "'\n"; return AMD_COMGR_STATUS_ERROR; }; // Link memory buffer into composite auto Mod = getLazyIRModule(MemoryBuffer::getMemBuffer(MemBuf.get()), SMDiag, Context, true); if (!Mod) { SMDiag.print(ChildName.c_str(), LogS, /* ShowColors */ false); return AMD_COMGR_STATUS_ERROR; } if (verifyModule(*Mod, &LogS)) return AMD_COMGR_STATUS_ERROR; if (L.linkInModule(std::move(Mod), ApplicableFlags)) return AMD_COMGR_STATUS_ERROR; } llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "Unpack Archives error: "); } else continue; } if (verifyModule(*Composite, &LogS)) { return AMD_COMGR_STATUS_ERROR; } SmallString<0> OutBuf; BitcodeWriter Writer(OutBuf); Writer.writeModule(*Composite, false, nullptr, false, nullptr); Writer.writeSymtab(); Writer.writeStrtab(); amd_comgr_data_t OutputT; if (auto Status = amd_comgr_create_data(AMD_COMGR_DATA_KIND_BC, &OutputT)) { return Status; } // OutputT can be released after addition to the data_set ScopedDataObjectReleaser SDOR(OutputT); DataObject *Output = DataObject::convert(OutputT); Output->setName("linked.bc"); Output->setData(OutBuf); return amd_comgr_data_set_add(OutSetT, OutputT); } amd_comgr_status_t AMDGPUCompiler::codeGenBitcodeToRelocatable() { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName)) { return Status; } } if (ActionInfo->ShouldLinkDeviceLibs) { if (auto Status = addDeviceLibraries()) { return Status; } } Args.push_back("-c"); Args.push_back("-mllvm"); Args.push_back("-amdgpu-internalize-symbols"); return processFiles(AMD_COMGR_DATA_KIND_RELOCATABLE, ".o"); } amd_comgr_status_t AMDGPUCompiler::codeGenBitcodeToAssembly() { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName)) { return Status; } } if (ActionInfo->ShouldLinkDeviceLibs) { if (auto Status = addDeviceLibraries()) { return Status; } } Args.push_back("-S"); return processFiles(AMD_COMGR_DATA_KIND_SOURCE, ".s"); } amd_comgr_status_t AMDGPUCompiler::assembleToRelocatable() { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName)) { return Status; } } if (auto Status = addIncludeFlags()) { return Status; } if (ActionInfo->ShouldLinkDeviceLibs) { if (auto Status = addDeviceLibraries()) { return Status; } } Args.push_back("-c"); Args.push_back("-x"); Args.push_back("assembler"); // -nogpulib option not needed for assembling to relocatable NoGpuLib = false; return processFiles(AMD_COMGR_DATA_KIND_RELOCATABLE, ".o"); } amd_comgr_status_t AMDGPUCompiler::linkToRelocatable() { if (auto Status = createTmpDirs()) { return Status; } for (auto &Option : ActionInfo->getOptions()) { Args.push_back(Option.c_str()); } SmallVector, 128> Inputs; for (auto *Input : InSet->DataObjects) { if (Input->DataKind != AMD_COMGR_DATA_KIND_RELOCATABLE) { continue; } Inputs.push_back(getFilePath(Input, InputDir)); if (auto Status = outputToFile(Input, Inputs.back())) { return Status; } Args.push_back(Inputs.back().c_str()); } amd_comgr_data_t OutputT; if (auto Status = amd_comgr_create_data(AMD_COMGR_DATA_KIND_RELOCATABLE, &OutputT)) { return Status; } // OutputT can be released after addition to the data_set ScopedDataObjectReleaser SDOR(OutputT); DataObject *Output = DataObject::convert(OutputT); Output->setName("a.o"); auto OutputFilePath = getFilePath(Output, OutputDir); Args.push_back("-o"); Args.push_back(OutputFilePath.c_str()); Args.push_back("-r"); if (auto Status = linkWithLLD(Args, LogS, LogS)) { return Status; } if (auto Status = inputFromFile(Output, OutputFilePath)) { return Status; } return amd_comgr_data_set_add(OutSetT, OutputT); } amd_comgr_status_t AMDGPUCompiler::linkToExecutable() { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName)) { return Status; } } for (auto &Option : ActionInfo->getOptions()) { Args.push_back(Option.c_str()); } SmallVector, 128> Inputs; for (auto *Input : InSet->DataObjects) { if (Input->DataKind != AMD_COMGR_DATA_KIND_RELOCATABLE) { continue; } Inputs.push_back(getFilePath(Input, InputDir)); if (auto Status = outputToFile(Input, Inputs.back())) { return Status; } Args.push_back(Inputs.back().c_str()); } if (ActionInfo->ShouldLinkDeviceLibs) { if (auto Status = addDeviceLibraries()) { return Status; } } amd_comgr_data_t OutputT; if (auto Status = amd_comgr_create_data(AMD_COMGR_DATA_KIND_EXECUTABLE, &OutputT)) { return Status; } // OutputT can be released after addition to the data_set ScopedDataObjectReleaser SDOR(OutputT); DataObject *Output = DataObject::convert(OutputT); Output->setName("a.so"); auto OutputFilePath = getFilePath(Output, OutputDir); Args.push_back("-o"); Args.push_back(OutputFilePath.c_str()); if (auto Status = executeInProcessDriver(Args)) { return Status; } if (auto Status = inputFromFile(Output, OutputFilePath)) { return Status; } return amd_comgr_data_set_add(OutSetT, OutputT); } // TODO: Generalize this list to include all -cc1 flags and arguments that are // still valid in a bitcode compilation context static inline const std::unordered_set ValidSpirvFlags{ "-fapprox-func", "-fcolor-diagnostics", "-fconvergent-functions", "-fexceptions", "-ffast-math", "-ffinite-math-only", "-ffp-contract=fast", "-ffp-contract=fast-honor-pragmas", "-ffp-contract=on", "-fgpu-rdc", "-finline-functions", "-fno-autolink", "-fno-experimental-relative-c++-abi-vtables", "-fno-rounding-math", "-fno-signed-zeros", "-fno-threadsafe-statics", "-freciprocal-math", "-funsafe-math-optimizations", "-fvisibility=hidden", "-O0", "-O1", "-O2", "-O3", "--save-temps"}; amd_comgr_status_t AMDGPUCompiler::extractSpirvFlags(DataSet *BcSet) { for (auto *Bc : BcSet->DataObjects) { // Create SPIR-V IR Module from Bitcode Buffer SMDiagnostic SMDiag; LLVMContext Context; Context.setDiagnosticHandler( std::make_unique(this->LogS), true); auto Mod = getLazyIRModule( MemoryBuffer::getMemBuffer(StringRef(Bc->Data, Bc->Size), "", false), SMDiag, Context, true); if (!Mod) { SMDiag.print("SPIR-V Bitcode", LogS, /* ShowColors */ false); return AMD_COMGR_STATUS_ERROR; } if (verifyModule(*Mod, &LogS)) return AMD_COMGR_STATUS_ERROR; // Fetch @llvm.cmdline GlobalVariable *CmdLine = Mod->getNamedGlobal("llvm.cmdline"); // Return if no @llvm.cmdline if (!CmdLine) return AMD_COMGR_STATUS_SUCCESS; if (ConstantDataSequential *CDS = dyn_cast(CmdLine->getInitializer())) { // Add each valid null-terminated '\0' string to Flags std::string Tmp; StringRef CmdLineRaw = CDS->getRawDataValues(); std::stringstream ss(CmdLineRaw.str()); while (getline(ss, Tmp, '\0')) { if (Tmp == "--hipstdpar" || Tmp == "-amdgpu-enable-hipstdpar") { Bc->SpirvFlags.push_back("-mllvm"); Bc->SpirvFlags.push_back("-amdgpu-enable-hipstdpar"); } else if (Tmp == "-amdgpu-spill-cfi-saved-regs") { Bc->SpirvFlags.push_back("-mllvm"); Bc->SpirvFlags.push_back("-amdgpu-spill-cfi-saved-regs"); } else if (ValidSpirvFlags.count(Tmp)) { Bc->SpirvFlags.push_back(Saver.save(Tmp.c_str()).data()); } } } if (env::shouldEmitVerboseLogs()) { LogS << " SPIR-V Flags: " << Bc->Name << "\n"; for (auto Flag : Bc->SpirvFlags) LogS << " " << Flag << "\n"; } } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::translateSpirvToBitcode() { return translateSpirvToBitcodeImpl(InSet, DataSet::convert(OutSetT)); } amd_comgr_status_t AMDGPUCompiler::translateSpirvToBitcodeImpl(DataSet *SpirvInSet, DataSet *BcOutSet) { #ifndef COMGR_SPIRV_TRANSLATOR_AVAILABLE #ifdef COMGR_DISABLE_SPIRV LogS << "Calling AMDGPUCompiler::translateSpirvToBitcodeImpl() not " << "supported. Comgr was built with -DCOMGR_DISABLE_SPIRV=ON.\n"; #else LogS << "Calling AMDGPUCompiler::translateSpirvToBitcodeImpl() not " << "supported. The LLVM-SPIRV-Translator was not found when Comgr " << "was configured.\n"; #endif return AMD_COMGR_STATUS_ERROR; #else if (auto Status = createTmpDirs()) { return Status; } // Extract GPU processor from ISA name if set, for SPIR-V feature predicate // resolution. TODO: Make ISA name required for this action once users have // migrated. StringRef OffloadArch; TargetIdentifier Ident; if (ActionInfo->IsaName) { if (auto Status = parseTargetIdentifier(ActionInfo->IsaName, Ident)) return Status; OffloadArch = Ident.Processor; } auto Cache = CommandCache::get(LogS); for (auto *Input : SpirvInSet->DataObjects) { if (env::shouldSaveTemps()) { if (auto Status = outputToFile(Input, getFilePath(Input, InputDir))) { return Status; } } if (Input->DataKind != AMD_COMGR_DATA_KIND_SPIRV) { return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } SmallString<0> OutBuf; SPIRVCommand SPIRV(Input, OutBuf, OffloadArch); amd_comgr_status_t Status; if (!Cache) { Status = SPIRV.execute(LogS); } else { Status = Cache->execute(SPIRV, LogS); } if (Status) { return Status; } amd_comgr_data_t OutputT; if (auto Status = amd_comgr_create_data(AMD_COMGR_DATA_KIND_BC, &OutputT)) { return Status; } // OutputT can be released after addition to the data_set ScopedDataObjectReleaser SDOR(OutputT); DataObject *Output = DataObject::convert(OutputT); Output->setName(std::string(Input->Name) + std::string(".bc")); Output->setData(OutBuf); if (auto Status = amd_comgr_data_set_add(DataSet::convert(BcOutSet), OutputT)) { return Status; } if (env::shouldEmitVerboseLogs()) { LogS << "SPIR-V Translation: amd-llvm-spirv -r --spirv-target-env=CL2.0 " "--spirv-preserve-auxdata " << getFilePath(Input, InputDir) << " -o " << getFilePath(Output, OutputDir) << " (command line equivalent)\n"; } if (env::shouldSaveTemps()) { if (auto Status = outputToFile(Output, getFilePath(Output, OutputDir))) { return Status; } } } // If block sizes are specified, clone kernels for each block size if (!ActionInfo->BlockSizes.empty()) { if (auto Status = cloneKernelsInBitcode(BcOutSet)) { return Status; } } return AMD_COMGR_STATUS_SUCCESS; #endif } amd_comgr_status_t AMDGPUCompiler::cloneKernelsInBitcode(DataSet *BcSet) { if (ActionInfo->BlockSizes.empty()) { // Nothing to do return AMD_COMGR_STATUS_SUCCESS; } if (env::shouldEmitVerboseLogs()) { LogS << "Cloning kernels for block sizes:"; for (size_t BlockSize : ActionInfo->BlockSizes) { LogS << " " << BlockSize; } LogS << "\n"; } // For each bitcode module, clone kernels for each block size LLVMContext Context; Context.setDiagnosticHandler( std::make_unique(LogS), true); // We need to clone all bitcode modules and replace them in the set SmallVector OriginalBitcodes = BcSet->DataObjects.takeVector(); for (auto *Input : OriginalBitcodes) { if (Input->DataKind != AMD_COMGR_DATA_KIND_BC) { LogS << "Unexpected input data kind for " << Input->Name << "\n"; return AMD_COMGR_STATUS_ERROR; } // Parse the bitcode module SMDiagnostic Err; MemoryBufferRef BufferRef(StringRef(Input->Data, Input->Size), Input->Name); auto M = parseIR(BufferRef, Err, Context); if (!M) { LogS << "Failed to parse bitcode module: " << Input->Name << "\n"; Err.print("comgr", LogS); return AMD_COMGR_STATUS_ERROR; } // Clone kernels for each block size SmallVector OriginalKernels; for (Function &F : *M) { // Check if this is a kernel function (SPIR-V kernels use SPIR_KERNEL // calling convention) if ((F.getCallingConv() == llvm::CallingConv::AMDGPU_KERNEL || F.getCallingConv() == llvm::CallingConv::SPIR_KERNEL) && !F.isDeclaration()) { OriginalKernels.push_back(&F); } } if (env::shouldEmitVerboseLogs()) { LogS << "Found " << OriginalKernels.size() << " kernel(s) in module " << Input->Name << "\n"; } for (Function *OrigKernel : OriginalKernels) { // Determine the bounds of the original kernel. auto FlatWGSizeAttr = OrigKernel->getFnAttribute("amdgpu-flat-work-group-size"); std::pair OriginalBlockSizeLimits{1ul, 1024ul}; if (FlatWGSizeAttr.isValid()) { StringRef Val = FlatWGSizeAttr.getValueAsString(); std::pair Sizes = Val.split(','); if (!Sizes.first.empty()) Sizes.first.getAsInteger(10, OriginalBlockSizeLimits.first); if (!Sizes.second.empty()) Sizes.second.getAsInteger(10, OriginalBlockSizeLimits.second); } std::string OriginalName = OrigKernel->getName().str(); std::string BlockSizeLowerBound = std::to_string(OriginalBlockSizeLimits.first); for (size_t BlockSize : ActionInfo->BlockSizes) { if (BlockSize == OriginalBlockSizeLimits.second) { // Keep the original kernel with its original name and block size continue; } if (BlockSize < OriginalBlockSizeLimits.first) { if (env::shouldEmitVerboseLogs()) { LogS << "Cannot clone kernel for block size " << BlockSize << " since it is smaller than the minimum block size of " << OriginalBlockSizeLimits.first << "\n"; } continue; } if (BlockSize > OriginalBlockSizeLimits.second) { if (env::shouldEmitVerboseLogs()) { LogS << "Cannot clone kernel for block size " << BlockSize << " since it is larger than the maximum block size of " << OriginalBlockSizeLimits.second << "\n"; } continue; } if (env::shouldEmitVerboseLogs()) { LogS << "Cloning " << OrigKernel->getName() << " for block size: " << BlockSize << "\n"; } // Create a clone of the kernel ValueToValueMapTy VMap; Function *ClonedKernel = CloneFunction(OrigKernel, VMap); // Ensure calling convention is preserved (should be AMDGPU_KERNEL) ClonedKernel->setCallingConv(OrigKernel->getCallingConv()); // Rename the cloned kernel with block size suffix std::string NewName = OriginalName + ".bs" + std::to_string(BlockSize); ClonedKernel->setName(NewName); // Remove the old amdgpu-flat-work-group-size attribute and add the new // one ClonedKernel->removeFnAttr("amdgpu-flat-work-group-size"); ClonedKernel->addFnAttr("amdgpu-flat-work-group-size", BlockSizeLowerBound + "," + std::to_string(BlockSize)); if (env::shouldEmitVerboseLogs()) { LogS << " Cloned " << OrigKernel->getName() << " -> " << NewName << "\n"; } } } // Write the modified module to a bitcode buffer SmallString<0> BitcodeBuffer; raw_svector_ostream OS(BitcodeBuffer); WriteBitcodeToFile(*M, OS); // Update the existing data object with the cloned bitcode Input->setData(BitcodeBuffer); if (env::shouldSaveTemps()) { if (auto Status = outputToFile(Input, getFilePath(Input, OutputDir))) { return Status; } } // Re-add the modified bitcode to the set BcSet->DataObjects.insert(Input); } return AMD_COMGR_STATUS_SUCCESS; } amd_comgr_status_t AMDGPUCompiler::compileSpirvToRelocatable() { if (auto Status = createTmpDirs()) { return Status; } for (auto *Input : InSet->DataObjects) { if (Input->DataKind != AMD_COMGR_DATA_KIND_SPIRV) return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } // Translate .spv to .bc amd_comgr_data_set_t TranslatedSpirvT; if (auto Status = amd_comgr_create_data_set(&TranslatedSpirvT)) return Status; ScopedDataSetReleaser SDSR(TranslatedSpirvT); DataSet *TranslatedSpirv = DataSet::convert(TranslatedSpirvT); if (auto Status = translateSpirvToBitcodeImpl(InSet, TranslatedSpirv)) return Status; // Extract relevant -cc1 flags from @llvm.cmdline if (auto Status = extractSpirvFlags(TranslatedSpirv)) return Status; // Compile bitcode to relocatable if (ActionInfo->IsaName) { if (auto Status = addTargetIdentifierFlags(ActionInfo->IsaName)) { return Status; } } if (ActionInfo->ShouldLinkDeviceLibs) { if (auto Status = addDeviceLibraries()) { return Status; } } Args.push_back("-c"); Args.push_back("-mllvm"); Args.push_back("-amdgpu-internalize-symbols"); return processFiles(AMD_COMGR_DATA_KIND_RELOCATABLE, ".o", TranslatedSpirv); } amd_comgr_status_t AMDGPUCompiler::compileSourceToSpirv() { if (auto Status = createTmpDirs()) { return Status; } if (ActionInfo->Language != AMD_COMGR_LANGUAGE_HIP) { return AMD_COMGR_STATUS_ERROR_INVALID_ARGUMENT; } if (auto Status = addIncludeFlags()) { return Status; } if (auto Status = addCompilationFlags()) { return Status; } // Add SPIRV-specific compilation flags Args.push_back("--offload-arch=amdgcnspirv"); Args.push_back("--no-gpu-bundle-output"); #if _WIN32 Args.push_back("-fshort-wchar"); #endif if (ActionInfo->ShouldLinkDeviceLibs) { if (auto Status = addDeviceLibraries()) { return Status; } } return processFiles(AMD_COMGR_DATA_KIND_SPIRV, ".spv"); } AMDGPUCompiler::AMDGPUCompiler(DataAction *ActionInfo, DataSet *InSet, DataSet *OutSet, raw_ostream &LogS) : ActionInfo(ActionInfo), InSet(InSet), OutSetT(DataSet::convert(OutSet)), LogS(LogS) { initializeCommandLineArgs(Args); // Initialize OverlayFS with the real file system which helps redirect // non-VFS reads and writes. OverlayFS = new vfs::OverlayFileSystem(vfs::getRealFileSystem()); std::optional VFSStatus = env::shouldUseVFS(); if ((VFSStatus.has_value() && *VFSStatus) || (!VFSStatus.has_value() && ActionInfo->ShouldUseVFS)) { if (env::shouldEmitVerboseLogs()) { LogS << "\t File System: VFS\n"; } UseVFS = true; InMemoryFS = new vfs::InMemoryFileSystem; OverlayFS->pushOverlay(InMemoryFS); } else { if (env::shouldEmitVerboseLogs()) { LogS << "\t File System: Real\n"; } } // Add embedded libc++ headers to VFS for HIP compilations. if (getLanguage() == AMD_COMGR_LANGUAGE_HIP && hasEmbeddedLibcxxHeaders()) { if (!InMemoryFS) { InMemoryFS = new vfs::InMemoryFileSystem; OverlayFS->pushOverlay(InMemoryFS); } SmallString<256> ClangBinaryPath(env::getLLVMPath()); sys::path::append(ClangBinaryPath, "bin", "clang"); std::string ResourceDir = GetResourcesPath(ClangBinaryPath); // libc++ headers → /include/c++/v1/ SmallString<256> LibcxxBase(env::getLLVMPath()); sys::path::append(LibcxxBase, "include", "c++", "v1"); for (const auto &Entry : getLibcxxHeaderFiles()) { SmallString<128> Path(LibcxxBase); path::append(Path, Entry.RelativePath); InMemoryFS->addFile(Path, 0, MemoryBuffer::getMemBuffer(Entry.FileContent, Path, false)); } // Clang builtin headers → /include/ SmallString<256> ClangBuiltinBase(ResourceDir); sys::path::append(ClangBuiltinBase, "include"); for (const auto &Entry : getClangBuiltinHeaderFiles()) { SmallString<128> Path(ClangBuiltinBase); path::append(Path, Entry.RelativePath); InMemoryFS->addFile(Path, 0, MemoryBuffer::getMemBuffer(Entry.FileContent, Path, false)); } HasEmbeddedHeaders = true; if (env::shouldEmitVerboseLogs()) { LogS << "\t Embedded " << getLibcxxHeaderFiles().size() << " libc++ headers at: " << LibcxxBase << "\n"; LogS << "\t Embedded " << getClangBuiltinHeaderFiles().size() << " clang builtins at: " << ClangBuiltinBase << "\n"; } } } AMDGPUCompiler::~AMDGPUCompiler() { // LLVM temps get saved in the same directory as regular temps. We can only // call `removeTmpDirs` if none of the env vars is enabled. if (!env::shouldSaveTemps() && !env::shouldSaveLLVMTemps()) { removeTmpDirs(); } } } // namespace COMGR