diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -215,6 +215,9 @@ /// function instead of to trap instructions. std::string TrapFuncName; + /// Specific functions to compile. + std::vector FunctionsToCompile; + /// A list of dependent libraries. std::vector DependentLibraries; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1163,6 +1163,9 @@ def forder_file_instrumentation : Flag<["-"], "forder-file-instrumentation">, Group, Flags<[CC1Option, CoreOption]>, HelpText<"Generate instrumented code to collect order file into default.profraw file (overridden by '=' form of option or LLVM_PROFILE_FILE env var)">; +def fsingle_func : Joined<["-"], "fsingle-func=">, + Group, Flags<[CC1Option, CoreOption]>, + HelpText<"Specify a single function to compile in non-LTO mode, for debugging only">; defm addrsig : BoolFOption<"addrsig", "CodeGenOpts.Addrsig", DefaultsToFalse, diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -1185,6 +1185,7 @@ PB.registerFunctionAnalyses(FAM); PB.registerLoopAnalyses(LAM); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); + PB.registerFunctionsToCompile(CodeGenOpts.FunctionsToCompile); ModulePassManager MPM(CodeGenOpts.DebugPassManager); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -5911,6 +5911,13 @@ options::OPT_fno_sized_deallocation, false)) CmdArgs.push_back("-fsized-deallocation"); + if (Args.hasArg(options::OPT_fsingle_func)) { + std::vector Names = + Args.getAllArgValues(options::OPT_fsingle_func); + for (const auto &Name : Names) + CmdArgs.push_back(Args.MakeArgString("-fsingle-func=" + Name)); + } + // -faligned-allocation is on by default in C++17 onwards and otherwise off // by default. if (Arg *A = Args.getLastArg(options::OPT_faligned_allocation, diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1340,6 +1340,7 @@ } } + Opts.FunctionsToCompile = Args.getAllArgValues(OPT_fsingle_func); Opts.DependentLibraries = Args.getAllArgValues(OPT_dependent_lib); Opts.LinkerOptions = Args.getAllArgValues(OPT_linker_option); bool NeedLocTracking = false; diff --git a/clang/test/CodeGen/single-func.c b/clang/test/CodeGen/single-func.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/single-func.c @@ -0,0 +1,59 @@ +// RUN: %clang %s -O0 -fexperimental-new-pass-manager -fsingle-func=foo -S -o %t0 | FileCheck %s --check-prefix=FOO +// RUN: FileCheck %s < %t0 --check-prefix=ASM-FOO +// RUN: %clang %s -O1 -fexperimental-new-pass-manager -fsingle-func=foo -S -o %t1 | FileCheck %s --check-prefix=RECUR-FOO +// RUN: FileCheck %s < %t1 --check-prefix=ASM-FOO +// RUN: %clang %s -O2 -fexperimental-new-pass-manager -fsingle-func=bar -fsingle-func=go -S -o %t2 | FileCheck %s --check-prefix=BAR-GO +// RUN: FileCheck %s < %t2 --check-prefix=ASM-BAR-GO + +int g; + +void bar() { + g = 0; +} + +void go() { + g = 1; +} + +void foo(int x) { + if (x == 0) + bar(); + else + go(); +} + +int main() +{ + foo(g); + return 0; +} + +// 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 has code generated. +// ASM-FOO: foo: +// ASM-FOO-NOT: main: +// ASM-FOO-NOT: bar: +// ASM-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 + +// Check only function bar and go are compiled. +// BAR-GO: Found Function bar to compile. +// BAR-GO: Found Function go to compile. +// FOO-NOT: main +// FOO-NOT: foo + +// Check only function foo has code generated. +// ASM-BAR-GO: bar: +// ASM-BAR-GO: go: +// ASM-BAR-GO-NOT: foo: +// ASM-BAR-GO-NOT: main: diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -130,6 +130,7 @@ std::vector filterList; std::vector searchPaths; std::vector symbolOrderingFile; + std::vector LTOFunctionsToCompile; std::vector thinLTOModulesToCompile; std::vector undefined; std::vector dynamicList; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1077,6 +1077,8 @@ getOldNewOptions(args, OPT_thinlto_prefix_replace_eq); config->thinLTOModulesToCompile = args::getStrings(args, OPT_thinlto_single_module_eq); + config->LTOFunctionsToCompile = + args::getStrings(args, OPT_lto_single_func_eq); config->timeTraceEnabled = args.hasArg(OPT_time_trace); config->timeTraceGranularity = args::getInteger(args, OPT_time_trace_granularity, 500); @@ -2189,10 +2191,12 @@ // Likewise, --plugin-opt=emit-llvm and --plugin-opt=emit-asm are the // options to create output files in bitcode or assembly code // repsectively. No object files are generated. - // Also bail out here when only certain thinLTO modules are specified for - // compilation. The intermediate object file are the expected output. + // Also bail out here when only certain functions or thinLTO modules are + // specified for compilation. The intermediate object file are the expected + // output. if (config->thinLTOIndexOnly || config->emitLLVM || config->ltoEmitAsm || - !config->thinLTOModulesToCompile.empty()) + !config->thinLTOModulesToCompile.empty() || + !config->LTOFunctionsToCompile.empty()) return; // Apply symbol renames for -wrap and combine foo@v1 and foo@@v1. diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -157,6 +157,9 @@ for (const llvm::StringRef &name : config->thinLTOModulesToCompile) c.ThinLTOModulesToCompile.emplace_back(name); + for (const llvm::StringRef &name : config->LTOFunctionsToCompile) + c.LTOFunctionsToCompile.emplace_back(name); + c.TimeTraceEnabled = config->timeTraceEnabled; c.TimeTraceGranularity = config->timeTraceGranularity; diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -582,7 +582,9 @@ def thinlto_object_suffix_replace_eq: JJ<"thinlto-object-suffix-replace=">; def thinlto_prefix_replace_eq: JJ<"thinlto-prefix-replace=">; def thinlto_single_module_eq: JJ<"thinlto-single-module=">, - HelpText<"Specific a single module to compile in ThinLTO mode, for debugging only">; + HelpText<"Specify a single module to compile in ThinLTO mode, for debugging only">; +def lto_single_func_eq: JJ<"lto-single-func=">, + HelpText<"Specify a single function to compile in LTO mode, for debugging only">; def: J<"plugin-opt=O">, Alias, HelpText<"Alias for --lto-O">; def: F<"plugin-opt=debug-pass-manager">, diff --git a/lld/test/ELF/lto/lto-single-func.ll b/lld/test/ELF/lto/lto-single-func.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/lto-single-func.ll @@ -0,0 +1,90 @@ +; REQUIRES: x86 +; RUN: rm -fr %t && mkdir %t && cd %t + +; RUN: opt -thinlto-bc -o thin1.o %s +; RUN: opt -thinlto-bc -o thin2.o %S/Inputs/thin2.ll +; RUN: ld.lld thin1.o thin2.o --lto-single-func=foo --lto-obj-path=thin.o -plugin-opt=new-pass-manager | FileCheck %s --check-prefix=RECUR-FOO +; RUN: llvm-readelf -S -s thin.o | FileCheck %s --check-prefix=THIN-DEFAULT +; RUN: llvm-readelf -S -s thin.o1 | FileCheck %s --check-prefix=THIN-FOO +; RUN: not ls thin.o2 + +; RUN: ld.lld thin1.o thin2.o --lto-single-func=foo --lto-single-func=blah --lto-obj-path=thin.o -plugin-opt=new-pass-manager | FileCheck %s --check-prefix=RECUR-FOO-BLAH +; RUN: llvm-readelf -S -s thin.o | FileCheck %s --check-prefix=THIN-DEFAULT +; RUN: llvm-readelf -S -s thin.o1 | FileCheck %s --check-prefix=THIN-FOO +; RUN: llvm-readelf -S -s thin.o2 | FileCheck %s --check-prefix=THIN-BLAH + +; RUN: opt -o full1.o %s +; RUN: opt -o full2.o %S/Inputs/thin2.ll +; RUN: ld.lld full1.o full2.o --lto-single-func=foo --lto-obj-path=full.o -plugin-opt=new-pass-manager | FileCheck %s --check-prefix=RECUR-FOO +; RUN: llvm-readelf -S -s full.o | FileCheck %s --check-prefix=LTO-FOO + +;; 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 + +;; Check only function foo and bar are compiled. +; RECUR-FOO-BLAH: Found Function blah to compile. +; RECUR-FOO-BLAH: Found Function foo to compile. +; RECUR-FOO-BLAH: Found Function bar to compile. +; RECUR-FOO-BLAH: Found Function go to compile. +; RECUR-FOO-BLAH-NOT: main + +; THIN-DEFAULT: Value Size Type Bind Vis Ndx Name +; THIN-DEFAULT: 0000000000000000 0 FILE LOCAL DEFAULT ABS ld-temp.o +; THIN-FOO: Value Size Type Bind Vis Ndx Name +; THIN-FOO: 0000000000000000 0 FILE LOCAL DEFAULT ABS lto-single-func.ll +; THIN-FOO: 0000000000000000 28 FUNC GLOBAL HIDDEN 3 foo +; THIN-FOO-NOT: {{.*}} bar + +; THIN-BLAH 0000000000000000 0 FILE LOCAL DEFAULT ABS thin2.ll +; THIN-BLAH: 0000000000000000 4 FUNC GLOBAL HIDDEN 3 blah + +; LTO-FOO: 0000000000000000 0 FILE LOCAL DEFAULT ABS ld-temp.o +; LTO-FOO: 0000000000000000 28 FUNC GLOBAL HIDDEN 3 foo +; LTO-FOO-NOT: {{.*}} bar + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-scei-ps4" + +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} diff --git a/llvm/include/llvm/LTO/Config.h b/llvm/include/llvm/LTO/Config.h --- a/llvm/include/llvm/LTO/Config.h +++ b/llvm/include/llvm/LTO/Config.h @@ -149,6 +149,9 @@ /// Specific thinLTO modules to compile. std::vector ThinLTOModulesToCompile; + /// Specific functions to compile. + std::vector LTOFunctionsToCompile; + /// Time trace enabled. bool TimeTraceEnabled = false; diff --git a/llvm/include/llvm/Passes/PassBuilder.h b/llvm/include/llvm/Passes/PassBuilder.h --- a/llvm/include/llvm/Passes/PassBuilder.h +++ b/llvm/include/llvm/Passes/PassBuilder.h @@ -141,6 +141,8 @@ PipelineTuningOptions PTO; Optional PGOOpt; PassInstrumentationCallbacks *PIC; + /// Specific functions to compile. + std::vector FunctionsToCompile; public: /// A struct to capture parsed pass pipeline names. @@ -323,6 +325,9 @@ /// additional analyses. void registerLoopAnalyses(LoopAnalysisManager &LAM); + /// Registers specific functions to compile. + void registerFunctionsToCompile(const std::vector &Names); + /// Construct the core LLVM function canonicalization and simplification /// pipeline. /// 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,41 @@ +//===- 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_FUNCEXTRACTOR_H +#define LLVM_TRANSFORMS_IPO_FUNCEXTRACTOR_H + +#include "llvm/IR/PassManager.h" +#include + +namespace llvm { + +class Module; + +class FuncExtractorPass : public PassInfoMixin { + std::vector FunctionsToCompile; + bool InLTO; + // Recursively extract possible callees. + bool Recursive; + +public: + FuncExtractorPass(const std::vector &FunctionsToCompile, + bool InLTO, bool Recursive = true) + : FunctionsToCompile(FunctionsToCompile), InLTO(InLTO), + Recursive(Recursive) {} + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); +}; + +} // end namespace llvm + +#endif // LLVM_TRANSFORMS_IPO_FUNCEXTRACTOR_H diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp --- a/llvm/lib/LTO/LTO.cpp +++ b/llvm/lib/LTO/LTO.cpp @@ -835,6 +835,7 @@ ThinLTO.ModuleMap.size())) return Err; + const SymbolResolution *OrigResI = ResI; for (const InputFile::Symbol &Sym : Syms) { assert(ResI != ResE); SymbolResolution Res = *ResI++; @@ -885,6 +886,22 @@ } } + if (!Conf.LTOFunctionsToCompile.empty()) { + assert(Conf.ThinLTOModulesToCompile.empty() && + "Single function mode should not be combined with single " + "module mode."); + if (!ThinLTO.ModulesToCompile) + ThinLTO.ModulesToCompile = ModuleMapType(); + std::set FuncNames(Conf.LTOFunctionsToCompile.begin(), + Conf.LTOFunctionsToCompile.end()); + for (const InputFile::Symbol &Sym : Syms) { + SymbolResolution Res = *OrigResI++; + // Only compile a name-matched function that is prevailing. + if (Res.Prevailing && FuncNames.count(Sym.getIRName())) + ThinLTO.ModulesToCompile->insert({BM.getModuleIdentifier(), BM}); + } + } + return Error::success(); } diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp --- a/llvm/lib/LTO/LTOBackend.cpp +++ b/llvm/lib/LTO/LTOBackend.cpp @@ -247,6 +247,7 @@ PB.registerFunctionAnalyses(FAM); PB.registerLoopAnalyses(LAM); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); + PB.registerFunctionsToCompile(Conf.LTOFunctionsToCompile); ModulePassManager MPM(Conf.DebugPassManager); @@ -312,6 +313,7 @@ PB.registerFunctionAnalyses(FAM); PB.registerLoopAnalyses(LAM); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); + PB.registerFunctionsToCompile(Conf.LTOFunctionsToCompile); ModulePassManager MPM; 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 @@ -97,6 +97,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" @@ -435,7 +436,8 @@ PassBuilder::PassBuilder(bool DebugLogging, TargetMachine *TM, PipelineTuningOptions PTO, Optional PGOOpt, PassInstrumentationCallbacks *PIC) - : DebugLogging(DebugLogging), TM(TM), PTO(PTO), PGOOpt(PGOOpt), PIC(PIC) { + : DebugLogging(DebugLogging), TM(TM), PTO(PTO), PGOOpt(PGOOpt), PIC(PIC), + FunctionsToCompile() { if (TM) TM->registerPassBuilderCallbacks(*this, DebugLogging); if (PIC && shouldPopulateClassToPassNames()) { @@ -509,6 +511,12 @@ C(LAM); } +void PassBuilder::registerFunctionsToCompile( + const std::vector &Names) { + FunctionsToCompile.insert(FunctionsToCompile.end(), Names.begin(), + Names.end()); +} + // Helper to add AnnotationRemarksPass. static void addAnnotationRemarksPass(ModulePassManager &MPM) { FunctionPassManager FPM; @@ -1079,6 +1087,10 @@ true /* SamplePGO */)); } + if (!FunctionsToCompile.empty()) + MPM.addPass( + FuncExtractorPass(FunctionsToCompile, Phase == ThinLTOPhase::PostLink)); + if (AttributorRun & AttributorRunOption::MODULE) MPM.addPass(AttributorPass()); @@ -1538,6 +1550,9 @@ MPM.addPass(RequireAnalysisPass()); } + if (!FunctionsToCompile.empty()) + MPM.addPass(FuncExtractorPass(FunctionsToCompile, true)); + // Remove unused virtual tables to improve the quality of code generated by // whole-program devirtualization and bitset lowering. MPM.addPass(GlobalDCEPass()); @@ -1764,6 +1779,9 @@ ModulePassManager MPM(DebugLogging); + if (!FunctionsToCompile.empty()) + MPM.addPass(FuncExtractorPass(FunctionsToCompile, false, false)); + if (PGOOpt && (PGOOpt->Action == PGOOptions::IRInstr || PGOOpt->Action == PGOOptions::IRUse)) addPGOInstrPassesForO0( 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,10 +11,13 @@ //===----------------------------------------------------------------------===// #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; @@ -50,115 +53,218 @@ } 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) {} - - 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; - } +class GVExtractor { + SetVector &Named; + bool DeleteStuff; + bool KeepConstInit; - makeVisible(*I, Delete); +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) {} - if (Delete) { - // Make this a declaration and drop it's comdat. - I->setInitializer(nullptr); - I->setComdat(nullptr); - } + bool run(Module &M) { + // 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; } - // 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; + } + } + + 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; +} // namespace + +ModulePass *llvm::createGVExtractionPass(std::vector &GVs, + bool deleteFn, bool KeepConstInit) { + return new GVExtractorPass(GVs, deleteFn, KeepConstInit); +} + +PreservedAnalyses FuncExtractorPass::run(Module &M, ModuleAnalysisManager &AM) { + if (FunctionsToCompile.empty()) + return PreservedAnalyses::all(); + + // Figure out seed functions to compile. + SetVector Seeds; + for (const auto &Name : FunctionsToCompile) { + if (Function *F = M.getFunction(Name)) + if (!F->isDeclaration()) + Seeds.insert(F); + } + + SetVector Functions(Seeds); + + // Collect possible callee functions of the seed functions. + if (Recursive) { + InstrProfSymtab Symtab; + (void)(bool) Symtab.create(M, InLTO); + + 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)); + } + } } - CurI->replaceAllUsesWith(Declaration); - delete &*CurI; } } + } + } - return true; + for (GlobalValue *GV : Functions) { + if (auto *F = dyn_cast(GV)) { + outs() << "Found Function " << F->getName() << " to compile.\n"; } - }; + } - char GVExtractorPass::ID = 0; -} + // Assign a linker declaration linkage to functions that are not + // user-specified to avoid emitting object code for them. + for (GlobalValue *GV : Functions) { + if (!Seeds.contains(GV)) { + GV->setLinkage(GlobalValue::AvailableExternallyLinkage); + auto *F = dyn_cast(GV); + assert(F); + // Reset comdat for linker declaration functions to favor the IR + // verifier. + F->setComdat(nullptr); + } + } -ModulePass *llvm::createGVExtractionPass(std::vector &GVs, - bool deleteFn, bool keepConstInit) { - return new GVExtractorPass(GVs, deleteFn, keepConstInit); + GVExtractor Extractor(Functions, false, true); + Extractor.run(M); + return PreservedAnalyses::none(); } diff --git a/llvm/test/Other/Inputs/single-func.ll b/llvm/test/Other/Inputs/single-func.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Other/Inputs/single-func.ll @@ -0,0 +1,7 @@ +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-scei-ps4" + +define dso_local i32 @other() { +entry: + ret i32 16 +} diff --git a/llvm/test/Other/single-func.ll b/llvm/test/Other/single-func.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Other/single-func.ll @@ -0,0 +1,75 @@ +; REQUIRES: x86_64-linux +; RUN: opt < %s -passes='default' -single-func=foo -S -o %t0 | FileCheck %s --check-prefix=FOO +; RUN: opt < %s -passes='default' -single-func=foo -S -o %t1 | FileCheck %s --check-prefix=RECUR-FOO +; RUN: opt < %s -passes='default' -single-func=foo -single-func=bar -S -o %t2 | FileCheck %s --check-prefix=FOO-BAR +; RUN: opt < %s -passes='default' -single-func=foo -single-func=bar -S -o %t2 | FileCheck %s --check-prefix=FOO-BAR + +; RUN: opt -thinlto-bc -o %t3.bc %s +; RUN: opt -thinlto-bc -o %t4.bc %S/Inputs/single-func.ll +; RUN: llvm-lto2 run -use-new-pm -single-func=foo %t3.bc %t4.bc -o %t5 -r %t3.bc,main,px -r %t3.bc,foo,px -r %t3.bc,bar,px -r %t3.bc,go,px -r %t4.bc,other,px | FileCheck %s --check-prefix=RECUR-FOO + +; RUN: opt -o %t3.bc %s +; RUN: opt -o %t4.bc %S/Inputs/single-func.ll +; RUN: llvm-lto2 run -use-new-pm -single-func=foo %t3.bc %t4.bc -o %t5 -r %t3.bc,main,px -r %t3.bc,foo,px -r %t3.bc,bar,px -r %t3.bc,go,px -r %t4.bc,other,px | FileCheck %s --check-prefix=RECUR-FOO + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-scei-ps4" + +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 + +;; Check only function foo and bar are compiled. +; FOO-BAR: Found Function foo to compile. +; FOO-BAR: Found Function bar to compile. +; FOO-NOT: main +; FOO-NOT: go diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp b/llvm/tools/llvm-lto2/llvm-lto2.cpp --- a/llvm/tools/llvm-lto2/llvm-lto2.cpp +++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp @@ -158,6 +158,11 @@ PassPlugins("load-pass-plugin", cl::desc("Load passes from plugin library")); +static cl::list + SingleFuncList("single-func", cl::value_desc("function names"), + cl::desc("Only compile functions whose name match this"), + cl::CommaSeparated, cl::Hidden); + static void check(Error E, std::string Msg) { if (!E) return; @@ -297,6 +302,7 @@ Conf.StatsFile = StatsFile; Conf.PTO.LoopVectorization = Conf.OptLevel > 1; Conf.PTO.SLPVectorization = Conf.OptLevel > 1; + Conf.LTOFunctionsToCompile = SingleFuncList; ThinBackend Backend; if (ThinLTODistributedIndexes) diff --git a/llvm/tools/opt/NewPMDriver.cpp b/llvm/tools/opt/NewPMDriver.cpp --- a/llvm/tools/opt/NewPMDriver.cpp +++ b/llvm/tools/opt/NewPMDriver.cpp @@ -117,6 +117,11 @@ "the OptimizerLast extension point into default pipelines"), cl::Hidden); +static cl::list SingleFuncList( + "single-func", cl::value_desc("function names"), + cl::desc("Only compile functions whose names are in the list"), + cl::CommaSeparated, cl::Hidden); + // Individual pipeline tuning options. extern cl::opt DisableLoopUnrolling; @@ -384,6 +389,7 @@ PB.registerFunctionAnalyses(FAM); PB.registerLoopAnalyses(LAM); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); + PB.registerFunctionsToCompile(SingleFuncList); ModulePassManager MPM(DebugPM); if (VK > VK_NoVerifier)