| //===-- 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<std::string> InputFilename(cl::Positional, |
| cl::desc("<input bitcode file>"), |
| cl::init("-"), |
| cl::value_desc("filename"), |
| cl::cat(SplitCategory)); |
| |
| static cl::opt<std::string> OutputFilename("o", |
| cl::desc("Override output filename"), |
| cl::value_desc("filename"), |
| cl::cat(SplitCategory)); |
| |
| static cl::opt<unsigned> NumOutputs("j", cl::Prefix, cl::init(2), |
| cl::desc("Number of output files"), |
| cl::cat(SplitCategory)); |
| |
| static cl::opt<bool> |
| PreserveLocals("preserve-locals", cl::Prefix, cl::init(false), |
| cl::desc("Split without externalizing locals"), |
| cl::cat(SplitCategory)); |
| |
| static cl::opt<bool> |
| 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<std::string> |
| 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<std::string> |
| 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<SplitByCategoryType> 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<bool> 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<int> 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<int>(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<SmallString<0>, 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<Module> M) { |
| size_t OutputID = 0; |
| auto PostSplitCallback = [&](std::unique_ptr<Module> 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<TargetMachine> 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<TargetMachine>(T->createTargetMachine( |
| Triple(MTriple), MCPU, /*FS*/ "", Options, std::nullopt, std::nullopt)); |
| } |
| |
| std::unique_ptr<Module> M = parseIRFile(InputFilename, Err, Context); |
| |
| if (!M) { |
| Err.print(argv[0], errs()); |
| return 1; |
| } |
| |
| unsigned I = 0; |
| const auto HandleModulePart = [&](std::unique_ptr<Module> MPart) { |
| std::error_code EC; |
| std::unique_ptr<ToolOutputFile> 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; |
| } |