diff --git a/llvm/include/llvm/Transforms/IPO/FuncExtractor.h b/llvm/include/llvm/Transforms/IPO/FuncExtractor.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Transforms/IPO/FuncExtractor.h @@ -0,0 +1,36 @@ +//===- FuncExtractor.h - Function extraction pass ----------------*- C++ +//-*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +/// \file +/// This file extracts necessary functions to favor -single-func complication. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_IPO_EXTRACTGV_H +#define LLVM_TRANSFORMS_IPO_EXTRACTGV_H + +#include "llvm/IR/PassManager.h" +#include + +namespace llvm { + +class Module; + +class FuncExtractorPass : public PassInfoMixin { + // Recursively extract possible callees. + bool Recursive; + +public: + FuncExtractorPass(bool Recursive = false) : Recursive(Recursive) {} + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); +}; + +} // end namespace llvm + +#endif // LLVM_TRANSFORMS_IPO_EXTRACTGV_H diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp --- a/llvm/lib/Passes/PassBuilder.cpp +++ b/llvm/lib/Passes/PassBuilder.cpp @@ -96,6 +96,7 @@ #include "llvm/Transforms/IPO/DeadArgumentElimination.h" #include "llvm/Transforms/IPO/ElimAvailExtern.h" #include "llvm/Transforms/IPO/ForceFunctionAttrs.h" +#include "llvm/Transforms/IPO/FuncExtractor.h" #include "llvm/Transforms/IPO/FunctionAttrs.h" #include "llvm/Transforms/IPO/FunctionImport.h" #include "llvm/Transforms/IPO/GlobalDCE.h" @@ -238,6 +239,8 @@ extern cl::opt MaxDevirtIterations; +extern cl::list SingleFuncList; + static cl::opt UseInlineAdvisor( "enable-ml-inliner", cl::init(InliningAdvisorMode::Default), cl::Hidden, cl::desc("Enable ML policy for inliner. Currently trained for -Oz only"), @@ -1080,6 +1083,9 @@ true /* SamplePGO */)); } + if (!SingleFuncList.empty()) + MPM.addPass(FuncExtractorPass(true)); + if (AttributorRun & AttributorRunOption::MODULE) MPM.addPass(AttributorPass()); @@ -1535,6 +1541,9 @@ MPM.addPass(RequireAnalysisPass()); } + if (!SingleFuncList.empty()) + MPM.addPass(FuncExtractorPass(true)); + // Remove unused virtual tables to improve the quality of code generated by // whole-program devirtualization and bitset lowering. MPM.addPass(GlobalDCEPass()); diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -52,6 +52,7 @@ MODULE_PASS("deadargelim", DeadArgumentEliminationPass()) MODULE_PASS("elim-avail-extern", EliminateAvailableExternallyPass()) MODULE_PASS("extract-blocks", BlockExtractorPass()) +MODULE_PASS("extract-funcs", FuncExtractorPass()) MODULE_PASS("forceattrs", ForceFunctionAttrsPass()) MODULE_PASS("function-import", FunctionImportPass()) MODULE_PASS("globaldce", GlobalDCEPass()) diff --git a/llvm/lib/Transforms/IPO/ExtractGV.cpp b/llvm/lib/Transforms/IPO/ExtractGV.cpp --- a/llvm/lib/Transforms/IPO/ExtractGV.cpp +++ b/llvm/lib/Transforms/IPO/ExtractGV.cpp @@ -11,13 +11,29 @@ //===----------------------------------------------------------------------===// #include "llvm/ADT/SetVector.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/Pass.h" +#include "llvm/ProfileData/InstrProf.h" #include "llvm/Transforms/IPO.h" +#include "llvm/Transforms/IPO/FuncExtractor.h" #include using namespace llvm; +cl::list + SingleFuncList("single-func", cl::value_desc("function names"), + cl::desc("Only compile functions whose name match this"), + cl::CommaSeparated, cl::Hidden); + +static cl::opt + SingleFuncRecursive("single-func-recursive", + cl::desc("Recursively extract all called functions")); + +static cl::opt + EmitSingleFunc("emit-single-func", cl::init(true), cl::Hidden, + cl::desc("Only emit user-specified functions")); + /// Make sure GV is visible from both modules. Delete is true if it is /// being deleted from this module. /// This also makes sure GV cannot be dropped so that references from @@ -50,115 +66,213 @@ } namespace { - /// A pass to extract specific global values and their dependencies. - class GVExtractorPass : public ModulePass { - SetVector Named; - bool deleteStuff; - bool keepConstInit; - public: - static char ID; // Pass identification, replacement for typeid - - /// If deleteS is true, this pass deletes the specified global values. - /// Otherwise, it deletes as much of the module as possible, except for the - /// global values specified. - explicit GVExtractorPass(std::vector &GVs, - bool deleteS = true, bool keepConstInit = false) - : ModulePass(ID), Named(GVs.begin(), GVs.end()), deleteStuff(deleteS), - keepConstInit(keepConstInit) {} +class GVExtractor { + SetVector &Named; + bool deleteStuff; + bool keepConstInit; - bool runOnModule(Module &M) override { - if (skipModule(M)) - return false; - - // Visit the global inline asm. - if (!deleteStuff) - M.setModuleInlineAsm(""); - - // For simplicity, just give all GlobalValues ExternalLinkage. A trickier - // implementation could figure out which GlobalValues are actually - // referenced by the Named set, and which GlobalValues in the rest of - // the module are referenced by the NamedSet, and get away with leaving - // more internal and private things internal and private. But for now, - // be conservative and simple. - - // Visit the GlobalVariables. - for (Module::global_iterator I = M.global_begin(), E = M.global_end(); - I != E; ++I) { - bool Delete = - deleteStuff == (bool)Named.count(&*I) && !I->isDeclaration() && - (!I->isConstant() || !keepConstInit); - if (!Delete) { - if (I->hasAvailableExternallyLinkage()) - continue; - if (I->getName() == "llvm.global_ctors") - continue; - } +public: + /// If deleteS is true, this pass deletes the specified global values. + /// Otherwise, it deletes as much of the module as possible, except for the + /// global values specified. + GVExtractor(SetVector &GVs, bool deleteS, bool keepConstInit) + : Named(GVs), deleteStuff(deleteS), keepConstInit(keepConstInit) {} - makeVisible(*I, Delete); + bool run(Module &M) { + // Visit the global inline asm. + if (!deleteStuff) + M.setModuleInlineAsm(""); - if (Delete) { - // Make this a declaration and drop it's comdat. - I->setInitializer(nullptr); - I->setComdat(nullptr); - } + // For simplicity, just give all GlobalValues ExternalLinkage. A trickier + // implementation could figure out which GlobalValues are actually + // referenced by the Named set, and which GlobalValues in the rest of + // the module are referenced by the NamedSet, and get away with leaving + // more internal and private things internal and private. But for now, + // be conservative and simple. + + // Visit the GlobalVariables. + for (Module::global_iterator I = M.global_begin(), E = M.global_end(); + I != E; ++I) { + bool Delete = deleteStuff == (bool)Named.count(&*I) && + !I->isDeclaration() && (!I->isConstant() || !keepConstInit); + if (!Delete) { + if (I->hasAvailableExternallyLinkage()) + continue; + if (I->getName() == "llvm.global_ctors") + continue; } - // Visit the Functions. - for (Function &F : M) { - bool Delete = - deleteStuff == (bool)Named.count(&F) && !F.isDeclaration(); - if (!Delete) { - if (F.hasAvailableExternallyLinkage()) - continue; - } + makeVisible(*I, Delete); - makeVisible(F, Delete); + if (Delete) { + // Make this a declaration and drop it's comdat. + I->setInitializer(nullptr); + I->setComdat(nullptr); + } + } - if (Delete) { - // Make this a declaration and drop it's comdat. - F.deleteBody(); - F.setComdat(nullptr); - } + // Visit the Functions. + for (Function &F : M) { + bool Delete = deleteStuff == (bool)Named.count(&F) && !F.isDeclaration(); + if (!Delete) { + if (F.hasAvailableExternallyLinkage()) + continue; } - // Visit the Aliases. - for (Module::alias_iterator I = M.alias_begin(), E = M.alias_end(); - I != E;) { - Module::alias_iterator CurI = I; - ++I; + makeVisible(F, Delete); - bool Delete = deleteStuff == (bool)Named.count(&*CurI); - makeVisible(*CurI, Delete); + if (Delete) { + // Make this a declaration and drop it's comdat. + F.deleteBody(); + F.setComdat(nullptr); + } + } - if (Delete) { - Type *Ty = CurI->getValueType(); + // Visit the Aliases. + for (Module::alias_iterator I = M.alias_begin(), E = M.alias_end(); + I != E;) { + Module::alias_iterator CurI = I; + ++I; - CurI->removeFromParent(); - llvm::Value *Declaration; - if (FunctionType *FTy = dyn_cast(Ty)) { - Declaration = Function::Create(FTy, GlobalValue::ExternalLinkage, - CurI->getAddressSpace(), - CurI->getName(), &M); + bool Delete = deleteStuff == (bool)Named.count(&*CurI); + makeVisible(*CurI, Delete); - } else { - Declaration = + if (Delete) { + Type *Ty = CurI->getValueType(); + + CurI->removeFromParent(); + llvm::Value *Declaration; + if (FunctionType *FTy = dyn_cast(Ty)) { + Declaration = + Function::Create(FTy, GlobalValue::ExternalLinkage, + CurI->getAddressSpace(), CurI->getName(), &M); + + } else { + Declaration = new GlobalVariable(M, Ty, false, GlobalValue::ExternalLinkage, nullptr, CurI->getName()); - - } - CurI->replaceAllUsesWith(Declaration); - delete &*CurI; } + CurI->replaceAllUsesWith(Declaration); + delete &*CurI; } - - return true; } - }; - char GVExtractorPass::ID = 0; + return true; + } +}; + +/// A pass to extract specific global values and their dependencies. +class GVExtractorPass : public ModulePass { + SetVector Named; + bool deleteStuff; + bool keepConstInit; + +public: + static char ID; // Pass identification, replacement for typeid + + /// If deleteS is true, this pass deletes the specified global values. + /// Otherwise, it deletes as much of the module as possible, except for the + /// global values specified. + explicit GVExtractorPass(std::vector &GVs, bool deleteS = true, + bool keepConstInit = false) + : ModulePass(ID), Named(GVs.begin(), GVs.end()), deleteStuff(deleteS), + keepConstInit(keepConstInit) {} + + bool runOnModule(Module &M) override { + if (skipModule(M)) + return false; + + GVExtractor Extractor(Named, deleteStuff, keepConstInit); + Extractor.run(M); + return true; + } +}; + +char GVExtractorPass::ID = 0; } ModulePass *llvm::createGVExtractionPass(std::vector &GVs, bool deleteFn, bool keepConstInit) { return new GVExtractorPass(GVs, deleteFn, keepConstInit); } + +PreservedAnalyses FuncExtractorPass::run(Module &M, ModuleAnalysisManager &AM) { + if (SingleFuncList.empty()) + return PreservedAnalyses::all(); + + // Figure out seed functions to compile. + SetVector Seeds; + for (size_t i = 0, e = SingleFuncList.size(); i != e; ++i) { + if (Function *F = M.getFunction(SingleFuncList[i])) + if (!F->isDeclaration()) + Seeds.insert(F); + } + + SetVector Functions(Seeds); + + // Collect possible callee functions of the seed functions. + if (Recursive || SingleFuncRecursive) { + InstrProfSymtab Symtab; + (void)(bool) Symtab.create(M); + + std::vector Workqueue; + for (GlobalValue *GV : Seeds) { + if (auto *F = dyn_cast(GV)) { + Workqueue.push_back(F); + } + } + + auto AddFunc = [&](Function *CF) { + if (CF && !CF->isDeclaration() && !Functions.count(CF)) { + Functions.insert(CF); + Workqueue.push_back(CF); + } + }; + + while (!Workqueue.empty()) { + Function *F = &*Workqueue.back(); + Workqueue.pop_back(); + for (auto &BB : *F) { + for (auto &I : BB) { + CallBase *CB = dyn_cast(&I); + if (!CB) + continue; + if (Function *CF = CB->getCalledFunction()) { + AddFunc(CF); + } else { + // Collect potential indirect call targets from the profile. + InstrProfValueData ValueData[8]; + uint32_t ActualNumValueData; + uint64_t TotalC; + if (getValueProfDataFromInst(*CB, IPVK_IndirectCallTarget, 8, + ValueData, ActualNumValueData, + TotalC)) { + for (const auto &VD : ArrayRef( + ValueData, ActualNumValueData)) { + AddFunc(Symtab.getFunction(VD.Value)); + } + } + } + } + } + } + } + + for (GlobalValue *GV : Functions) { + if (auto *F = dyn_cast(GV)) { + outs() << "Found Function " << F->getName() << " to compile.\n"; + } + } + + if (EmitSingleFunc) { + for (GlobalValue *GV : Functions) { + if (!Seeds.contains(GV)) { + GV->setLinkage(GlobalValue::AvailableExternallyLinkage); + } + } + } + + GVExtractor Extractor(Functions, false, true); + Extractor.run(M); + return PreservedAnalyses::all(); +} diff --git a/llvm/test/Other/extract-func.ll b/llvm/test/Other/extract-func.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Other/extract-func.ll @@ -0,0 +1,55 @@ +; RUN: opt < %s -passes=extract-funcs -single-func=foo -S -o %t1 | FileCheck %s --check-prefix=FOO +; RUN: opt < %s -passes=extract-funcs -single-func=foo -single-func-recursive -S -o %t2 | FileCheck %s --check-prefix=RECUR-FOO + +define dso_local i32 @main(i32 %x, i32 (i32)* %f) { + call i32 @foo(i32 %x, i32 (i32)* %f) + ret i32 1 +} + +define dso_local i32 @foo(i32 %x, i32 (i32)* %f) { +entry: + %retval = alloca i32, align 4 + %x.addr = alloca i32, align 4 + store i32 %x, i32* %x.addr, align 4 + %0 = load i32, i32* %x.addr, align 4 + %cmp = icmp eq i32 %0, 0 + br i1 %cmp, label %if.then, label %if.else + +if.then: + call i32 @bar() + store i32 1, i32* %retval, align 4 + br label %return + +if.else: + call i32 %f(i32 1), !prof !0 + store i32 2, i32* %retval, align 4 + br label %return + +return: + %3 = load i32, i32* %retval, align 4 + ret i32 %3 +} + +define dso_local i32 @bar() { +entry: + ret i32 8 +} + +define dso_local i32 @go() { +entry: + ret i32 6 +} + +!0 = !{!"VP", i32 0, i64 7, i64 -5182264717993193164, i64 5, i64 -1069303473483922844, i64 2} + +;; Check only function foo is compiled. +; FOO: Found Function foo to compile. +; FOO-NOT: main +; FOO-NOT: bar +; FOO-NOT: go + +;; Check only function foo, bar and go are compiled. +; RECUR-FOO: Found Function foo to compile. +; RECUR-FOO: Found Function bar to compile. +; RECUR-FOO: Found Function go to compile. +; RECUR-FOO-NOT: main