opt: add support for the new pass manager
+ add support for LLVM's NPM unit tests
MODIFY
README.md
README.md
@@ -104,8 +104,7 @@ Running Alive2 as a Clang Plugin
--------
This plugin tries to validate every IR-level transformation performed
-by LLVM. To build it, add `-DCLANG_PLUGIN=1` to the cmake
-command. Then, invoke the plugin like this:
+by LLVM. Invoke the plugin like this:
```
$ clang -O3 <src.c> -S -emit-llvm \

MODIFY
scripts/opt-alive.sh.in
scripts/opt-alive.sh.in
@@ -11,9 +11,28 @@ set -e
# metarenamer: anonymizes function names
# sample-profile: inlines functions
PASSES="-passes= argpromotion deadargelim globalopt hotcoldsplit inline ipconstprop ipsccp mergefunc partial-inliner tbaa loop-extract extract-blocks safe-stack place-safepoints attributor function-attrs metarenamer sample-profile lowertypetests extract-blocks openmpopt prune-eh tailcallelim -Os -Oz -O1 -O2 -O3"
+PASSREGISTRY="@LLVM_BUILD_MAIN_SRC_DIR@/lib/Passes/PassRegistry.def"
TV="-tv"
+NPM_TV=0
for arg in $@; do
+ # update the -passes argument only.
+ shift
+ if [[ $arg == "-passes="* ]]; then
+ passes=${arg#-passes=}
+ passes_new=`@CMAKE_SOURCE_DIR@/scripts/rewritepass.py "$PASSREGISTRY" $passes`
+ firstpass_level=`echo $passes_new | cut -d " " -f1`
+ passes_new=`echo $passes_new | cut -d " " -f2-`
+ if [[ ( $firstpass_level = "module" ) || ( $firstpass_level = "cgscc" ) ]]
+ then
+ set -- "$@" "$arg"
+ else
+ set -- "$@" "-passes=tv,$passes_new,tv"
+ NPM_TV=1
+ fi
+ else
+ set -- "$@" "$arg"
+ fi
for p in $PASSES; do
if [[ $arg == *"$p"* ]]; then
TV=""
@@ -40,4 +59,9 @@ if [[ @FOR_ALIVE2_TEST@ == 0 ]]; then
TV_SMT_TO=-tv-smt-to=10000
TV_SMT_STATS=-tv-smt-stats
fi
-$TIMEOUT @LLVM_BINARY_DIR@/bin/opt -load=@CMAKE_BINARY_DIR@/tv/$TV_SHAREDLIB -tv-exit-on-error $TV $@ $TV $TV_SMT_TO $TV_REPORT_DIR $TV_SMT_STATS
+NPM_PLUGIN=""
+# Without this guard, passes like default<Os> invokes whole-pass validation
+if [[ $NPM_TV == 1 ]]; then
+ NPM_PLUGIN="-load-pass-plugin=@CMAKE_BINARY_DIR@/tv/$TV_SHAREDLIB"
+fi
+$TIMEOUT @LLVM_BINARY_DIR@/bin/opt -load=@CMAKE_BINARY_DIR@/tv/$TV_SHAREDLIB $NPM_PLUGIN -tv-exit-on-error $TV $@ $TV $TV_SMT_TO $TV_REPORT_DIR $TV_SMT_STATS

ADD
scripts/rewritepass.py
+#!/usr/bin/python3
+import sys
+import os
+
+if len(sys.argv) != 3:
+ print("Use: %s <PassRegistry.def path> <passes>" % sys.argv[0])
+ exit(1)
+
+passregpath = sys.argv[1]
+passes = sys.argv[2].strip("'\"").split(',')
+
+levels = ["module", "cgscc", "function", "loop"]
+level_ancestors = {
+ "module":[],
+ "cgscc":["module"],
+ "function":["module"],
+ # don't use module(cgscc(function(...)), use module(function(...))
+ # the former one results in a different output.
+ # e.g. llvm/test/Transforms/LoopVectorize/reduction-order.ll
+ "loop":["function", "module"]
+}
+
+"""
+PassBuilder.cpp states that:
+ As a special shortcut, if the very first pass is not
+ a module pass (as a module pass manager is), this will automatically form
+ the shortest stack of pass managers that allow inserting that first pass.
+Build the stack of pass managers by finding the first pass from PassRegistry.def
+"""
+
+firstp_level = None
+# If the level is explicitly given, use it
+for k in levels:
+ if passes[0].startswith(k + "("):
+ firstp_level = k
+ break
+if passes[0].startswith("loop-mssa("):
+ firstp_level = "loop"
+
+for i in ["s", "z", "0", "1", "2", "3"]:
+ for pipeline in ["default", "thinlto-pre-link", "thinlto", "lto-pre-link",
+ "lto"]:
+ if passes[0] == "%s<O%s>" % (pipeline, i):
+ firstp_level = "module"
+ break
+ if firstp_level != None:
+ break
+
+# PassBuilder.cpp: isCGSCCPassName()
+if passes[0].startswith("devirt<") or passes[0].startswith("repeat<"):
+ firstp_level = "cgscc"
+
+if firstp_level == None:
+ # ask PassRegistry.def
+ firstpass = passes[0]
+ if passes[0].startswith("require<"):
+ firstpass = passes[0][len("require<"):]
+ firstpass = firstpass.replace("(", ",").replace("<",",").replace(">",",") \
+ .replace(";", ",").split(",")[0]
+
+ # There are passes like "verify", which can be either function or module
+ # level. We should allow them
+ found_levels = dict()
+ prefixes = ["MODULE_", "CGSCC_", "FUNCTION_", "LOOP_"]
+ for l in open(passregpath, "r").readlines():
+ if l.find('"%s"' % firstpass) == -1 and l.find('"%s<' % firstpass) == -1:
+ continue
+ for i in range(0, len(prefixes)):
+ if l.startswith(prefixes[i]):
+ found_levels[levels[i]] = True
+
+ if "module" in found_levels and len(found_levels) == 1:
+ # precisely a module level pass
+ firstp_level = "module"
+ else:
+ for i in range(1, len(prefixes)): # can be non-module (ex: verify)
+ if levels[i] in found_levels:
+ firstp_level = levels[i]
+ break
+
+prefix = ""
+suffix = ""
+
+if firstp_level != "module":
+ if not passes[0].startswith(firstp_level):
+ # don't print "function(function(passes))"
+ prefix = firstp_level + "("
+ suffix = ")"
+
+ for p in level_ancestors[firstp_level]:
+ prefix = p + "(" + prefix
+ suffix = suffix + ")"
+
+print(firstp_level + " " + prefix + ",".join(passes) + suffix)

ADD
tests/opt-tv/npm-Os.opt.ll
+; TEST-ARGS: -passes=default<Os>
+
+define i32 @f() {
+ ret i32 1
+}
+
+; src and tgt are identical, but opt-alive should not validate anything
+; CHECK-NOT: Transformation

ADD
tests/opt-tv/npm-globalopt.opt.ll
+; TEST-ARGS: -passes=globalopt
+
+define i32 @f() {
+ ret i32 1
+}
+
+; src and tgt are identical, but opt-alive should not validate anything
+; CHECK-NOT: Transformation

ADD
tests/opt-tv/npm-lu.opt.ll
+; TEST-ARGS: -passes='loop(unswitch<nontrivial>)' -tv-src-unroll=2 -tv-tgt-unroll=2
+
+define i32 @test(i1 %cond) {
+entry:
+ br label %loop
+loop:
+ %i = phi i32 [0, %entry], [%i.next, %loop.end]
+ br i1 %cond, label %bb1, label %bb2
+bb1:
+ call void @f()
+ br label %loop.end
+bb2:
+ call void @g()
+ br label %loop.end
+loop.end:
+ %i.next = add i32 %i, 1
+ %cmp = icmp eq i32 %i.next, 2
+ br i1 %cmp, label %exit, label %loop
+exit:
+ ret i32 0
+}
+
+declare void @f()
+declare void @g()

ADD
tests/opt-tv/npm-lu2.opt.ll
+; TEST-ARGS: -passes='unswitch<nontrivial>' -tv-src-unroll=2 -tv-tgt-unroll=2
+
+define i32 @test(i1 %cond) {
+entry:
+ br label %loop
+loop:
+ %i = phi i32 [0, %entry], [%i.next, %loop.end]
+ br i1 %cond, label %bb1, label %bb2
+bb1:
+ call void @f()
+ br label %loop.end
+bb2:
+ call void @g()
+ br label %loop.end
+loop.end:
+ %i.next = add i32 %i, 1
+ %cmp = icmp eq i32 %i.next, 2
+ br i1 %cmp, label %exit, label %loop
+exit:
+ ret i32 0
+}
+
+declare void @f()
+declare void @g()

ADD
tests/opt-tv/npm-verify.opt.ll
+; TEST-ARGS: -passes=verify,sroa,verify
+
+define i32 @f(i1 %cond) {
+ %p = alloca i32
+ br i1 %cond, label %A, label %B
+A:
+ store i32 1, i32* %p
+ br label %EXIT
+B:
+ store i32 2, i32* %p
+ br label %EXIT
+EXIT:
+ %v = load i32, i32* %p
+ ret i32 %v
+}

ADD
tests/opt-tv/npm.opt.ll
+; TEST-ARGS: -passes=sroa,instsimplify
+
+define i32 @f(i1 %cond) {
+ %p = alloca i32
+ br i1 %cond, label %A, label %B
+A:
+ store i32 1, i32* %p
+ br label %EXIT
+B:
+ store i32 2, i32* %p
+ br label %EXIT
+EXIT:
+ %v = load i32, i32* %p
+ ret i32 %v
+}

ADD
tests/opt-tv/npm2.opt.ll
+; TEST-ARGS: -passes="sroa,simplifycfg"
+
+define i32 @f(i1 %cond) {
+ %p = alloca i32
+ br i1 %cond, label %A, label %B
+A:
+ store i32 1, i32* %p
+ br label %EXIT
+B:
+ store i32 2, i32* %p
+ br label %EXIT
+EXIT:
+ %v = load i32, i32* %p
+ ret i32 %v
+}

ADD
tests/opt-tv/npm3.opt.ll
+; TEST-ARGS: -passes='sroa,simplifycfg'
+
+define i32 @f(i1 %cond) {
+ %p = alloca i32
+ br i1 %cond, label %A, label %B
+A:
+ store i32 1, i32* %p
+ br label %EXIT
+B:
+ store i32 2, i32* %p
+ br label %EXIT
+EXIT:
+ %v = load i32, i32* %p
+ ret i32 %v
+}

MODIFY
tv/CMakeLists.txt
tv/CMakeLists.txt
@@ -5,11 +5,6 @@ else()
add_llvm_library(tv MODULE tv.cpp)
endif()
-if (CLANG_PLUGIN)
- message(STATUS "Clang plugin support enabled")
- set_target_properties(tv PROPERTIES
- COMPILE_FLAGS "-DCLANG_PLUGIN"
- )
configure_file(
${CMAKE_SOURCE_DIR}/scripts/alivecc.in
${CMAKE_BINARY_DIR}/alivecc
@@ -25,9 +20,6 @@ if (CLANG_PLUGIN)
${CMAKE_BINARY_DIR}/benchmark_clang_tv
@ONLY
)
-else()
- message(STATUS "Skipping Clang plugin")
-endif()
target_link_libraries(tv PRIVATE ${ALIVE_LIBS_LLVM} ${Z3_LIBRARIES}
$<$<AND:$<CXX_COMPILER_ID:GNU>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.0>>:stdc++fs>)

MODIFY
tv/tv.cpp
tv/tv.cpp
@@ -204,6 +204,7 @@ unsigned initialized = 0;
bool showed_stats = false;
bool report_dir_created = false;
bool has_failure = false;
+// If is_clangtv is true, tv should exit with zero
bool is_clangtv = false;
fs::path opt_report_parallel_dir;
unique_ptr<parallel> parallelMgr;
@@ -232,7 +233,8 @@ string get_random_str() {
struct TVPass final : public llvm::FunctionPass {
static char ID;
bool skip_verify = false;
- bool encode_io_fns_as_unknown = false;
+ // Use TLI_override if it is set
+ llvm::TargetLibraryInfo *TLI_override = nullptr;
TVPass() : FunctionPass(ID) {}
@@ -249,15 +251,11 @@ struct TVPass final : public llvm::FunctionPass {
});
llvm::TargetLibraryInfo *TLI = nullptr;
- llvm::TargetLibraryInfoImpl TLIImpl;
- unique_ptr<llvm::TargetLibraryInfo> TLI_holder;
- if (is_clangtv) {
- // When used as a clang plugin, this is run as a plain function rather
- // than a registered pass, so getAnalysis() cannot be used.
- TLIImpl = llvm::TargetLibraryInfoImpl(
- llvm::Triple(F.getParent()->getTargetTriple()));
- TLI_holder = make_unique<llvm::TargetLibraryInfo>(TLIImpl, &F);
- TLI = TLI_holder.get();
+ if (TLI_override) {
+ // When used as a clang plugin or from the new pass manager, this is run
+ // as a plain function rather than a registered pass, so getAnalysis()
+ // cannot be used.
+ TLI = TLI_override;
} else {
TLI = &getAnalysis<llvm::TargetLibraryInfoWrapperPass>().getTLI(F);
}
@@ -523,9 +521,9 @@ llvm::RegisterPass<TVPass> X("tv", "Translation Validator", false, false);
-#ifdef CLANG_PLUGIN
-/// Classes and functions for running translation validation on clang
-/// Uses new pass manager's callback.
+/// Classes and functions for running translation validation on clang or
+/// opt with new pass manager
+/// Clang plugin uses new pass manager's callback.
struct TVInitPass : public llvm::PassInfoMixin<TVInitPass> {
llvm::PreservedAnalyses run(llvm::Module &M,
@@ -537,12 +535,16 @@ struct TVInitPass : public llvm::PassInfoMixin<TVInitPass> {
struct TVFinalizePass : public llvm::PassInfoMixin<TVFinalizePass> {
static bool finalized;
- llvm::PreservedAnalyses run(llvm::Function &F,
- llvm::FunctionAnalysisManager &FAM) {
+ static void finalize(llvm::Module &M) {
if (!finalized) {
finalized = true;
- TVPass().doFinalization(*F.getParent());
+ TVPass().doFinalization(M);
+ }
}
+
+ llvm::PreservedAnalyses run(llvm::Module &M,
+ llvm::ModuleAnalysisManager &MAM) {
+ finalize(M);
return llvm::PreservedAnalyses::all();
}
};
@@ -568,9 +570,10 @@ const llvm::Module * unwrapModule(llvm::Any IR) {
llvm_unreachable("Unknown IR unit");
}
+
const char* skip_pass_list[] = {
"{anonymous}::TVInitPass",
- "ModuleToFunctionPassAdaptor<{anonymous}::TVFinalizePass>",
+ "{anonymous}::TVFinalizePass",
"ArgumentPromotionPass",
"DeadArgumentEliminationPass",
"EliminateAvailableExternallyPass",
@@ -591,6 +594,74 @@ bool do_skip(const llvm::StringRef &pass0) {
[&](auto skip) { return pass == skip; });
}
+struct TVNewPass : public llvm::PassInfoMixin<TVNewPass> {
+ static bool skip_tv;
+ static string pass_name;
+ // The number of TVNewPass created.
+ static unsigned num_tv_pass_created;
+ bool print_pass_name = false;
+
+
+ llvm::PreservedAnalyses run(llvm::Module &M,
+ llvm::ModuleAnalysisManager &AM) {
+ auto &FAM = AM.getResult<llvm::FunctionAnalysisManagerModuleProxy>(M)
+ .getManager();
+ auto get_TLI = [&FAM](llvm::Function &F) {
+ return &FAM.getResult<llvm::TargetLibraryAnalysis>(F);
+ };
+ run(M, get_TLI);
+ return llvm::PreservedAnalyses::all();
+ }
+
+ void run(llvm::Module &M,
+ function<llvm::TargetLibraryInfo*(llvm::Function&)> get_TLI) {
+ // Check TVFinalizePass::finalized again because TVNewPass can be
+ // called after TVFinalizePass registered by
+ // registerOptimizerLastEPCallback.
+ // It can happen when a default pipeline (O3, O2, ...) is used.
+ if (TVFinalizePass::finalized)
+ return;
+
+ static unsigned count = 0;
+ if (!out) {
+ // TVInitPass is not called yet.
+ // This can happen at very early passes, such as
+ // ForceFunctionAttrsPass.
+ skip_tv = false;
+ return;
+ }
+
+ ++count;
+ if (print_pass_name) {
+ // print_pass_name is set only when running clang tv
+ *out << "-- " << count << ". " << pass_name
+ << (skip_tv ? " : Skipping\n" : "\n");
+ }
+
+ TVPass tv;
+ tv.skip_verify = skip_tv;
+
+ for (auto &F: M) {
+ tv.TLI_override = get_TLI(F);
+
+ // If skip_pass is true, this updates fns map only.
+ tv.runOnFunction(F);
+ }
+
+ skip_tv = false;
+ if (count == num_tv_pass_created) {
+ // If it is clang tv, num_tv_pass_created should be zero.
+ assert(!is_clangtv);
+ TVFinalizePass::finalize(M);
+ }
+ }
+};
+
+bool TVNewPass::skip_tv = false;
+string TVNewPass::pass_name;
+unsigned TVNewPass::num_tv_pass_created = 0;
+
+
// Entry point for this plugin
extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK
llvmGetPassPluginInfo() {
@@ -598,6 +669,32 @@ llvmGetPassPluginInfo() {
LLVM_PLUGIN_API_VERSION, "Alive2 Translation Validation", "",
[](llvm::PassBuilder &PB) {
is_clangtv = true;
+ PB.registerPipelineParsingCallback(
+ [](llvm::StringRef Name,
+ llvm::ModulePassManager &MPM,
+ llvm::ArrayRef<llvm::PassBuilder::PipelineElement>) {
+ static bool first_tv = true;
+ if (Name != "tv")
+ return false;
+
+ if (first_tv) {
+ // Assume that this plugin is loaded from opt when tv pass is
+ // explicitly given as an argument
+ is_clangtv = false;
+ first_tv = false;
+ MPM.addPass(TVInitPass());
+ }
+ MPM.addPass(TVNewPass());
+ // Finalization is done by the last TVNewPass
+ TVNewPass::num_tv_pass_created++;
+ return true;
+ });
+ // These two register*EPCallbacks are called when 'default' pipelines
+ // such as O2, O3 are used by either opt or clang.
+ // For clang -O2, these are needed. For opt, these are currently redundant
+ // because we don't support e.g. opt -passes="tv,default<O2>,tv".
+ // But there is no way to distinguish which one (clang vs. opt) is invoked
+ // so simply always add these.
PB.registerPipelineStartEPCallback(
[](llvm::ModulePassManager &MPM,
llvm::PassBuilder::OptimizationLevel) {
@@ -606,41 +703,38 @@ llvmGetPassPluginInfo() {
PB.registerOptimizerLastEPCallback(
[](llvm::ModulePassManager &MPM,
llvm::PassBuilder::OptimizationLevel) {
- MPM.addPass(createModuleToFunctionPassAdaptor(TVFinalizePass()));
+ MPM.addPass(TVFinalizePass());
});
auto f = [](llvm::StringRef P, llvm::Any IR,
const llvm::PreservedAnalyses &PA) {
- static int count = 0;
- if (!out) {
- // TVInitPass is not called yet.
- // This can happen at very early passes, such as
- // ForceFunctionAttrsPass.
+ if (TVFinalizePass::finalized)
return;
- }
- if (TVFinalizePass::finalized)
+ TVNewPass::pass_name = P.str();
+ TVNewPass::skip_tv |= do_skip(TVNewPass::pass_name);
+
+ if (!is_clangtv)
return;
- bool skip_pass = do_skip(P);
- *out << "-- " << ++count << ". " << P.str()
- << (skip_pass ? " : Skipping\n" : "\n");
+ // If it is clang tv, validate each pass
+ TVNewPass tv;
+ tv.print_pass_name = true;
- TVPass tv;
- tv.skip_verify = skip_pass;
- // For I/O known calls like printf, it is fine to regard them as valid
- // 'unknown calls' except when it is InstCombine.
- tv.encode_io_fns_as_unknown = P != "InstCombinePass" &&
- P != "AggressiveInstCombinePass";
-
- auto M = const_cast<llvm::Module *>(unwrapModule(IR));
- for (auto &F: *M)
- // If skip_pass is true, this updates fns map only.
- tv.runOnFunction(F);
+ llvm::TargetLibraryInfoImpl TLIImpl;
+ unique_ptr<llvm::TargetLibraryInfo> TLI_holder;
+
+ auto get_TLI = [&](llvm::Function &F) {
+ TLIImpl = llvm::TargetLibraryInfoImpl(
+ llvm::Triple(F.getParent()->getTargetTriple()));
+ TLI_holder = make_unique<llvm::TargetLibraryInfo>(TLIImpl, &F);
+ return TLI_holder.get();
+ };
+
+ tv.run(*const_cast<llvm::Module *>(unwrapModule(IR)), get_TLI);
};
PB.getPassInstrumentationCallbacks()->registerAfterPassCallback(move(f));
}
};
}
-#endif
}

opt: add support for the new pass manager (#616)

+ add support for LLVM's NPM unit tests