//===-- llvm-split: command line tool for testing module splitting --------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This program can be used to test the llvm::SplitModule and // TargetMachine::splitModule functions. // //===----------------------------------------------------------------------===// #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/PassInstrumentation.h" #include "llvm/IR/PassManager.h" #include "llvm/IR/Verifier.h" #include "llvm/IRReader/IRReader.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Target/TargetMachine.h" #include "llvm/TargetParser/Triple.h" #include "llvm/Transforms/IPO/GlobalDCE.h" #include "llvm/Transforms/Utils/SplitModule.h" #include "llvm/Transforms/Utils/SplitModuleByCategory.h" using namespace llvm; static cl::OptionCategory SplitCategory("Split Options"); static cl::opt InputFilename(cl::Positional, cl::desc(""), cl::init("-"), cl::value_desc("filename"), cl::cat(SplitCategory)); static cl::opt OutputFilename("o", cl::desc("Override output filename"), cl::value_desc("filename"), cl::cat(SplitCategory)); static cl::opt NumOutputs("j", cl::Prefix, cl::init(2), cl::desc("Number of output files"), cl::cat(SplitCategory)); static cl::opt PreserveLocals("preserve-locals", cl::Prefix, cl::init(false), cl::desc("Split without externalizing locals"), cl::cat(SplitCategory)); static cl::opt RoundRobin("round-robin", cl::Prefix, cl::init(false), cl::desc("Use round-robin distribution of functions to " "modules instead of the default name-hash-based one"), cl::cat(SplitCategory)); static cl::opt MTriple("mtriple", cl::desc("Target triple. When present, a TargetMachine is created " "and TargetMachine::splitModule is used instead of the " "common SplitModule logic."), cl::value_desc("triple"), cl::cat(SplitCategory)); static cl::opt MCPU("mcpu", cl::desc("Target CPU, ignored if --mtriple is not used"), cl::value_desc("cpu"), cl::cat(SplitCategory)); enum class SplitByCategoryType { SBCT_ByModuleId, SBCT_ByKernel, SBCT_None, }; static cl::opt SplitByCategory( "split-by-category", cl::desc("Split by category. If present, splitting by category is used " "with the specified categorization type."), cl::Optional, cl::init(SplitByCategoryType::SBCT_None), cl::values(clEnumValN(SplitByCategoryType::SBCT_ByModuleId, "module-id", "one output module per translation unit marked with " "\"module-id\" attribute"), clEnumValN(SplitByCategoryType::SBCT_ByKernel, "kernel", "one output module per kernel")), cl::cat(SplitCategory)); static cl::opt OutputAssembly{ "S", cl::desc("Write output as LLVM assembly"), cl::cat(SplitCategory)}; void writeStringToFile(StringRef Content, StringRef Path) { std::error_code EC; raw_fd_ostream OS(Path, EC); if (EC) { errs() << formatv("error opening file: {0}, error: {1}\n", Path, EC.message()); exit(1); } OS << Content << "\n"; } void writeModuleToFile(const Module &M, StringRef Path, bool OutputAssembly) { int FD = -1; if (std::error_code EC = sys::fs::openFileForWrite(Path, FD)) { errs() << formatv("error opening file: {0}, error: {1}", Path, EC.message()) << '\n'; exit(1); } raw_fd_ostream OS(FD, /*ShouldClose*/ true); if (OutputAssembly) M.print(OS, /*AssemblyAnnotationWriter*/ nullptr); else WriteBitcodeToFile(M, OS); } /// EntryPointCategorizer is used for splitting by category either by module-id /// or by kernels. It doesn't provide categories for functions other than /// kernels. Categorizer computes a string key for the given Function and /// records the association between the string key and an integer category. If a /// string key is already belongs to some category than the corresponding /// integer category is returned. class EntryPointCategorizer { public: EntryPointCategorizer(SplitByCategoryType Type) : Type(Type) {} EntryPointCategorizer() = delete; EntryPointCategorizer(EntryPointCategorizer &) = delete; EntryPointCategorizer &operator=(const EntryPointCategorizer &) = delete; EntryPointCategorizer(EntryPointCategorizer &&) = default; EntryPointCategorizer &operator=(EntryPointCategorizer &&) = default; /// Returns integer specifying the category for the given \p F. /// If the given function isn't a kernel then returns std::nullopt. std::optional operator()(const Function &F) { if (!isEntryPoint(F)) return std::nullopt; // skip the function. auto StringKey = computeFunctionCategory(Type, F); if (auto it = StrKeyToID.find(StringRef(StringKey)); it != StrKeyToID.end()) return it->second; int ID = static_cast(StrKeyToID.size()); return StrKeyToID.try_emplace(std::move(StringKey), ID).first->second; } private: static bool isEntryPoint(const Function &F) { if (F.isDeclaration()) return false; return F.getCallingConv() == CallingConv::SPIR_KERNEL || F.getCallingConv() == CallingConv::AMDGPU_KERNEL || F.getCallingConv() == CallingConv::PTX_Kernel; } static SmallString<0> computeFunctionCategory(SplitByCategoryType Type, const Function &F) { static constexpr char ATTR_MODULE_ID[] = "module-id"; SmallString<0> Key; switch (Type) { case SplitByCategoryType::SBCT_ByKernel: Key = F.getName().str(); break; case SplitByCategoryType::SBCT_ByModuleId: Key = F.getFnAttribute(ATTR_MODULE_ID).getValueAsString().str(); break; default: llvm_unreachable("unexpected mode."); } return Key; } private: struct KeyInfo { static SmallString<0> getEmptyKey() { return SmallString<0>(""); } static SmallString<0> getTombstoneKey() { return SmallString<0>("-"); } static bool isEqual(const SmallString<0> &LHS, const SmallString<0> &RHS) { return LHS == RHS; } static unsigned getHashValue(const SmallString<0> &S) { return llvm::hash_value(StringRef(S)); } }; SplitByCategoryType Type; DenseMap, int, KeyInfo> StrKeyToID; }; void cleanupModule(Module &M) { ModuleAnalysisManager MAM; MAM.registerPass([&] { return PassInstrumentationAnalysis(); }); ModulePassManager MPM; MPM.addPass(GlobalDCEPass()); // Delete unreachable globals. MPM.run(M, MAM); } Error runSplitModuleByCategory(std::unique_ptr M) { size_t OutputID = 0; auto PostSplitCallback = [&](std::unique_ptr MPart) { if (verifyModule(*MPart)) { errs() << "Broken Module!\n"; exit(1); } // TODO: DCE is a crucial pass since it removes unused declarations. // At the moment, LIT checking can't be perfomed without DCE. cleanupModule(*MPart); size_t ID = OutputID; ++OutputID; StringRef ModuleSuffix = OutputAssembly ? ".ll" : ".bc"; std::string ModulePath = (Twine(OutputFilename) + "_" + Twine(ID) + ModuleSuffix).str(); writeModuleToFile(*MPart, ModulePath, OutputAssembly); }; auto Categorizer = EntryPointCategorizer(SplitByCategory); splitModuleTransitiveFromEntryPoints(std::move(M), Categorizer, PostSplitCallback); return Error::success(); } int main(int argc, char **argv) { InitLLVM X(argc, argv); LLVMContext Context; SMDiagnostic Err; cl::HideUnrelatedOptions({&SplitCategory, &getColorCategory()}); cl::ParseCommandLineOptions(argc, argv, "LLVM module splitter\n"); std::unique_ptr TM; if (!MTriple.empty()) { InitializeAllTargets(); InitializeAllTargetMCs(); std::string Error; const Target *T = TargetRegistry::lookupTarget(MTriple, Error); if (!T) { errs() << "unknown target '" << MTriple << "': " << Error << "\n"; return 1; } TargetOptions Options; TM = std::unique_ptr(T->createTargetMachine( Triple(MTriple), MCPU, /*FS*/ "", Options, std::nullopt, std::nullopt)); } std::unique_ptr M = parseIRFile(InputFilename, Err, Context); if (!M) { Err.print(argv[0], errs()); return 1; } unsigned I = 0; const auto HandleModulePart = [&](std::unique_ptr MPart) { std::error_code EC; std::unique_ptr Out( new ToolOutputFile(OutputFilename + utostr(I++), EC, sys::fs::OF_None)); if (EC) { errs() << EC.message() << '\n'; exit(1); } if (verifyModule(*MPart, &errs())) { errs() << "Broken module!\n"; exit(1); } WriteBitcodeToFile(*MPart, Out->os()); // Declare success. Out->keep(); }; if (SplitByCategory != SplitByCategoryType::SBCT_None) { auto E = runSplitModuleByCategory(std::move(M)); if (E) { errs() << E << "\n"; Err.print(argv[0], errs()); return 1; } return 0; } if (TM) { if (PreserveLocals) { errs() << "warning: --preserve-locals has no effect when using " "TargetMachine::splitModule\n"; } if (RoundRobin) errs() << "warning: --round-robin has no effect when using " "TargetMachine::splitModule\n"; if (TM->splitModule(*M, NumOutputs, HandleModulePart)) return 0; errs() << "warning: " "TargetMachine::splitModule failed, falling back to default " "splitModule implementation\n"; } SplitModule(*M, NumOutputs, HandleModulePart, PreserveLocals, RoundRobin); return 0; }