Skip to content

Commit

Permalink
[PM] Wire up optimization levels and default pipeline construction APIs
Browse files Browse the repository at this point in the history
in the PassBuilder.

These are really just stubs for now, but they give a nice API surface
that Clang or other tools can start learning about and enabling for
experimentation.

I've also wired up parsing various synthetic module pass names to
generate these set pipelines. This allows the pipelines to be combined
with other passes and have their order controlled, with clear separation
between the *kind* of canned pipeline, and the *level* of optimization
to be used within that canned pipeline.

The most interesting part of this patch is almost certainly the spec for
the different optimization levels. I don't think we can ever have hard
and fast rules that would make it easy to determine whether a particular
optimization makes sense at a particular level -- it will always be in
large part a judgement call. But hopefully this will outline the
expected rationale that should be used, and the direction that the
pipelines should be taken. Much of this was based on a long llvm-dev
discussion I started years ago to try and crystalize the intent behind
these pipelines, and now, at long long last I'm returning to the task of
actually writing it down somewhere that we can cite and try to be
consistent with.

Differential Revision: http://reviews.llvm.org/D12826

llvm-svn: 262196
  • Loading branch information
chandlerc committed Feb 28, 2016
1 parent be5e0b0 commit 8b5a741
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 3 deletions.
119 changes: 118 additions & 1 deletion llvm/include/llvm/Passes/PassBuilder.h
Expand Up @@ -35,6 +35,92 @@ class PassBuilder {
TargetMachine *TM;

public:
/// \brief LLVM-provided high-level optimization levels.
///
/// This enumerates the LLVM-provided high-level optimization levels. Each
/// level has a specific goal and rationale.
enum OptimizationLevel {
/// Disable as many optimizations as possible. This doesn't completely
/// disable the optimizer in many cases as there are correctness issues
/// such as always_inline functions.
O0,

/// Optimize quickly without destroying debuggability.
///
/// FIXME: The current and historical behavior of this level does *not*
/// agree with this goal, but we would like to move toward this goal in the
/// future.
///
/// This level is tuned to produce a result from the optimizer as quickly
/// as possible and to avoid destroying debuggability. This tends to result
/// in a very good development mode where the compiled code will be
/// immediately executed as part of testing. As a consequence, where
/// possible, we would like to produce efficient-to-execute code, but not
/// if it significantly slows down compilation or would prevent even basic
/// debugging of the resulting binary.
///
/// As an example, complex loop transformations such as versioning,
/// vectorization, or fusion might not make sense here due to the degree to
/// which the executed code would differ from the source code, and the
/// potential compile time cost.
O1,

/// Optimize for fast execution as much as possible without triggering
/// significant incremental compile time or code size growth.
///
/// The key idea is that optimizations at this level should "pay for
/// themselves". So if an optimization increases compile time by 5% or
/// increases code size by 5% for a particular benchmark, that benchmark
/// should also be one which sees a 5% runtime improvement. If the compile
/// time or code size penalties happen on average across a diverse range of
/// LLVM users' benchmarks, then the improvements should as well.
///
/// And no matter what, the compile time needs to not grow superlinearly
/// with the size of input to LLVM so that users can control the runtime of
/// the optimizer in this mode.
///
/// This is expected to be a good default optimization level for the vast
/// majority of users.
O2,

/// Optimize for fast execution as much as possible.
///
/// This mode is significantly more aggressive in trading off compile time
/// and code size to get execution time improvements. The core idea is that
/// this mode should include any optimization that helps execution time on
/// balance across a diverse collection of benchmarks, even if it increases
/// code size or compile time for some benchmarks without corresponding
/// improvements to execution time.
///
/// Despite being willing to trade more compile time off to get improved
/// execution time, this mode still tries to avoid superlinear growth in
/// order to make even significantly slower compile times at least scale
/// reasonably. This does not preclude very substantial constant factor
/// costs though.
O3,

/// Similar to \c O2 but tries to optimize for small code size instead of
/// fast execution without triggering significant incremental execution
/// time slowdowns.
///
/// The logic here is exactly the same as \c O2, but with code size and
/// execution time metrics swapped.
///
/// A consequence of the different core goal is that this should in general
/// produce substantially smaller executables that still run in
/// a reasonable amount of time.
Os,

/// A very specialized mode that will optimize for code size at any and all
/// costs.
///
/// This is useful primarily when there are absolute size limitations and
/// any effort taken to reduce the size is worth it regardless of the
/// execution time impact. You should expect this level to produce rather
/// slow, but very small, code.
Oz
};

explicit PassBuilder(TargetMachine *TM = nullptr) : TM(TM) {}

/// \brief Registers all available module analysis passes.
Expand Down Expand Up @@ -68,6 +154,36 @@ class PassBuilder {
/// additional analyses.
void registerLoopAnalyses(LoopAnalysisManager &LAM);

/// \brief Add a per-module default optimization pipeline to a pass manager.
///
/// This provides a good default optimization pipeline for per-module
/// optimization and code generation without any link-time optimization. It
/// typically correspond to frontend "-O[123]" options for optimization
/// levels \c O1, \c O2 and \c O3 resp.
void addPerModuleDefaultPipeline(ModulePassManager &MPM,
OptimizationLevel Level,
bool DebugLogging = false);

/// \brief Add a pre-link, LTO-targeting default optimization pipeline to
/// a pass manager.
///
/// This adds the pre-link optimizations tuned to work well with a later LTO
/// run. It works to minimize the IR which needs to be analyzed without
/// making irreversible decisions which could be made better during the LTO
/// run.
void addLTOPreLinkDefaultPipeline(ModulePassManager &MPM,
OptimizationLevel Level,
bool DebugLogging = false);

/// \brief Add an LTO default optimization pipeline to a pass manager.
///
/// This provides a good default optimization pipeline for link-time
/// optimization and code generation. It is particularly tuned to fit well
/// when IR coming into the LTO phase was first run through \c
/// addPreLinkLTODefaultPipeline, and the two coordinate closely.
void addLTODefaultPipeline(ModulePassManager &MPM, OptimizationLevel Level,
bool DebugLogging = false);

/// \brief Parse a textual pass pipeline description into a \c ModulePassManager.
///
/// The format of the textual pass pipeline description looks something like:
Expand Down Expand Up @@ -117,7 +233,8 @@ class PassBuilder {
bool parseAAPipeline(AAManager &AA, StringRef PipelineText);

private:
bool parseModulePassName(ModulePassManager &MPM, StringRef Name);
bool parseModulePassName(ModulePassManager &MPM, StringRef Name,
bool DebugLogging);
bool parseCGSCCPassName(CGSCCPassManager &CGPM, StringRef Name);
bool parseFunctionPassName(FunctionPassManager &FPM, StringRef Name);
bool parseLoopPassName(LoopPassManager &LPM, StringRef Name);
Expand Down
70 changes: 68 additions & 2 deletions llvm/lib/Passes/PassBuilder.cpp
Expand Up @@ -16,6 +16,7 @@
//===----------------------------------------------------------------------===//

#include "llvm/Passes/PassBuilder.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/AliasAnalysisEvaluator.h"
#include "llvm/Analysis/AssumptionCache.h"
Expand All @@ -38,6 +39,7 @@
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Regex.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Transforms/IPO/ForceFunctionAttrs.h"
#include "llvm/Transforms/IPO/FunctionAttrs.h"
Expand All @@ -53,6 +55,8 @@

using namespace llvm;

static Regex DefaultAliasRegex("^(default|lto-pre-link|lto)<(O[0123sz])>$");

namespace {

/// \brief No-op module pass which does nothing.
Expand Down Expand Up @@ -135,8 +139,43 @@ void PassBuilder::registerLoopAnalyses(LoopAnalysisManager &LAM) {
#include "PassRegistry.def"
}

void PassBuilder::addPerModuleDefaultPipeline(ModulePassManager &MPM,
OptimizationLevel Level,
bool DebugLogging) {
// FIXME: Finish fleshing this out to match the legacy pipelines.
FunctionPassManager EarlyFPM(DebugLogging);
EarlyFPM.addPass(SimplifyCFGPass());
EarlyFPM.addPass(SROA());
EarlyFPM.addPass(EarlyCSEPass());
EarlyFPM.addPass(LowerExpectIntrinsicPass());

MPM.addPass(createModuleToFunctionPassAdaptor(std::move(EarlyFPM)));
}

void PassBuilder::addLTOPreLinkDefaultPipeline(ModulePassManager &MPM,
OptimizationLevel Level,
bool DebugLogging) {
// FIXME: We should use a customized pre-link pipeline!
addPerModuleDefaultPipeline(MPM, Level, DebugLogging);
}

void PassBuilder::addLTODefaultPipeline(ModulePassManager &MPM,
OptimizationLevel Level,
bool DebugLogging) {
// FIXME: Finish fleshing this out to match the legacy LTO pipelines.
FunctionPassManager LateFPM(DebugLogging);
LateFPM.addPass(InstCombinePass());
LateFPM.addPass(SimplifyCFGPass());

MPM.addPass(createModuleToFunctionPassAdaptor(std::move(LateFPM)));
}

#ifndef NDEBUG
static bool isModulePassName(StringRef Name) {
// Manually handle aliases for pre-configured pipeline fragments.
if (Name.startswith("default") || Name.startswith("lto"))
return DefaultAliasRegex.match(Name);

#define MODULE_PASS(NAME, CREATE_PASS) if (Name == NAME) return true;
#define MODULE_ANALYSIS(NAME, CREATE_PASS) \
if (Name == "require<" NAME ">" || Name == "invalidate<" NAME ">") \
Expand Down Expand Up @@ -177,7 +216,34 @@ static bool isLoopPassName(StringRef Name) {
return false;
}

bool PassBuilder::parseModulePassName(ModulePassManager &MPM, StringRef Name) {
bool PassBuilder::parseModulePassName(ModulePassManager &MPM, StringRef Name,
bool DebugLogging) {
// Manually handle aliases for pre-configured pipeline fragments.
if (Name.startswith("default") || Name.startswith("lto")) {
SmallVector<StringRef, 3> Matches;
if (!DefaultAliasRegex.match(Name, &Matches))
return false;
assert(Matches.size() == 3 && "Must capture two matched strings!");

auto L = StringSwitch<OptimizationLevel>(Matches[2])
.Case("O0", O0)
.Case("O1", O1)
.Case("O2", O2)
.Case("O3", O3)
.Case("Os", Os)
.Case("Oz", Oz);

if (Matches[1] == "default") {
addPerModuleDefaultPipeline(MPM, L, DebugLogging);
} else if (Matches[1] == "lto-pre-link") {
addLTOPreLinkDefaultPipeline(MPM, L, DebugLogging);
} else {
assert(Matches[1] == "lto" && "Not one of the matched options!");
addLTODefaultPipeline(MPM, L, DebugLogging);
}
return true;
}

#define MODULE_PASS(NAME, CREATE_PASS) \
if (Name == NAME) { \
MPM.addPass(CREATE_PASS); \
Expand Down Expand Up @@ -475,7 +541,7 @@ bool PassBuilder::parseModulePassPipeline(ModulePassManager &MPM,
} else {
// Otherwise try to parse a pass name.
size_t End = PipelineText.find_first_of(",)");
if (!parseModulePassName(MPM, PipelineText.substr(0, End)))
if (!parseModulePassName(MPM, PipelineText.substr(0, End), DebugLogging))
return false;
if (VerifyEachPass)
MPM.addPass(VerifierPass());
Expand Down
31 changes: 31 additions & 0 deletions llvm/test/Other/new-pass-manager.ll
Expand Up @@ -315,6 +315,37 @@
; CHECK-AA: Running analysis: BasicAA
; CHECK-AA: Finished llvm::Module pass manager run

; RUN: opt -disable-output -disable-verify -debug-pass-manager \
; RUN: -passes='default<O0>' %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=CHECK-O2
; RUN: opt -disable-output -disable-verify -debug-pass-manager \
; RUN: -passes='default<O1>' %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=CHECK-O2
; RUN: opt -disable-output -disable-verify -debug-pass-manager \
; RUN: -passes='default<O2>' %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=CHECK-O2
; RUN: opt -disable-output -disable-verify -debug-pass-manager \
; RUN: -passes='default<Os>' %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=CHECK-O2
; RUN: opt -disable-output -disable-verify -debug-pass-manager \
; RUN: -passes='default<Oz>' %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=CHECK-O2
; RUN: opt -disable-output -disable-verify -debug-pass-manager \
; RUN: -passes='lto-pre-link<O2>' %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=CHECK-O2
; CHECK-O2: Starting llvm::Module pass manager run
; CHECK-O2: Running pass: SimplifyCFGPass
; CHECK-O2: Running pass: SROA
; CHECK-O2: Running pass: EarlyCSEPass
; CHECK-O2: Running pass: LowerExpectIntrinsicPass

; RUN: opt -disable-output -disable-verify -debug-pass-manager \
; RUN: -passes='lto<O2>' %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=CHECK-LTO-O2
; CHECK-LTO-O2: Starting llvm::Module pass manager run
; CHECK-LTO-O2: Running pass: InstCombinePass
; CHECK-LTO-O2: Running pass: SimplifyCFGPass

define void @foo() {
ret void
}
Expand Down

0 comments on commit 8b5a741

Please sign in to comment.