Skip to content

Commit 5ecfcc0

Browse files
authored
make llvm backend do better dce and dae (#1144)
Previously, the llvm optimization passes weren't terribly effective at removing dead code and dead function arguments. We add some custom llvm passes that make this work a bit more effectively. There are two passes added: * RemoveDeadKFunctions deletes function call instructions which call side-effecting functions that are "effectively" pure, i.e., they exist only to construct pure terms and return them. * MustTailDeadArgElimination is a minorly modified version of the deadargelim LLVM pass which fixes a bug preventing it from applying dead argument elimination to musttail tail calls.
1 parent d7503bd commit 5ecfcc0

18 files changed

+1571
-89
lines changed

bin/llvm-kompile-clang

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ if ! $save_temps; then
152152
fi
153153

154154
if [[ "$OSTYPE" == "darwin"* ]]; then
155-
set_visibility_hidden="$LIBDIR/libSetVisibilityHidden.dylib"
155+
passes="$LIBDIR/libKLLVMPass.dylib"
156156
else
157-
set_visibility_hidden="$LIBDIR/libSetVisibilityHidden.so"
157+
passes="$LIBDIR/libKLLVMPass.so"
158158
fi
159159

160160
# On macOS, we get libunwind supplied as part of the developer tools in the OS,
@@ -176,7 +176,6 @@ fi
176176
if [ "$main" != "python_ast" ]; then
177177
if [ "$lto" = "lto" ]; then
178178
flags+=("-flto")
179-
files=("$LIBDIR"/llvm/*.ll)
180179
else
181180
files=()
182181
if $compile; then
@@ -186,7 +185,7 @@ if [ "$main" != "python_ast" ]; then
186185
fi
187186
if $visibility_hidden; then
188187
modhidden="$tmpdir/hidden.bc"
189-
run @OPT@ "$modopt" -load-pass-plugin "$set_visibility_hidden" -set-visibility-hidden -o "$modhidden"
188+
run @OPT@ "$modopt" -load-pass-plugin "$passes" -set-visibility-hidden -o "$modhidden"
190189
modopt="$modhidden"
191190
fi
192191
run @LLC@ \

include/kllvm/ast/attribute_set.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class attribute_set {
5151
Functional,
5252
Hook,
5353
Idem,
54+
Impure,
5455
Label,
5556
Left,
5657
Location,

include/kllvm/codegen/CreateTerm.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ class create_term {
8282
*/
8383
llvm::Value *create_function_call(
8484
std::string const &name, value_type return_cat,
85-
std::vector<llvm::Value *> const &args, bool sret, bool tailcc);
85+
std::vector<llvm::Value *> const &args, bool sret, bool tailcc,
86+
bool impure);
8687

8788
[[nodiscard]] llvm::BasicBlock *get_current_block() const {
8889
return current_block_;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//===- DeadArgumentElimination.h - Eliminate Dead Args ----------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
// Minor modification made by Pi Squared Inc to support tailcc musttail calls.
8+
//
9+
//===----------------------------------------------------------------------===//
10+
//
11+
// This pass deletes dead arguments from internal functions. Dead argument
12+
// elimination removes arguments which are directly dead, as well as arguments
13+
// only passed into function calls as dead arguments of other functions. This
14+
// pass also deletes dead return values in a similar way.
15+
//
16+
// This pass is often useful as a cleanup pass to run after aggressive
17+
// interprocedural passes, which add possibly-dead arguments or return values.
18+
//
19+
//===----------------------------------------------------------------------===//
20+
21+
// NOLINTBEGIN
22+
23+
#ifndef LLVM_TRANSFORMS_IPO_DEADARGUMENTELIMINATION_H
24+
#define LLVM_TRANSFORMS_IPO_DEADARGUMENTELIMINATION_H
25+
26+
#include "llvm/ADT/SmallVector.h"
27+
#include "llvm/ADT/Twine.h"
28+
#include "llvm/IR/Function.h"
29+
#include "llvm/IR/PassManager.h"
30+
#include "llvm/Pass.h"
31+
#include <map>
32+
#include <set>
33+
#include <string>
34+
#include <tuple>
35+
36+
using namespace llvm;
37+
38+
namespace llvm {
39+
40+
class Module;
41+
class Use;
42+
class Value;
43+
44+
} // namespace llvm
45+
46+
namespace kllvm {
47+
48+
/// Eliminate dead arguments (and return values) from functions.
49+
class DeadArgumentEliminationPass
50+
: public PassInfoMixin<DeadArgumentEliminationPass> {
51+
public:
52+
#if LLVM_VERSION_MAJOR == 16
53+
/// Struct that represents (part of) either a return value or a function
54+
/// argument. Used so that arguments and return values can be used
55+
/// interchangeably.
56+
struct RetOrArg {
57+
Function const *F;
58+
unsigned Idx;
59+
bool IsArg;
60+
61+
RetOrArg(Function const *F, unsigned Idx, bool IsArg)
62+
: F(F)
63+
, Idx(Idx)
64+
, IsArg(IsArg) { }
65+
66+
/// Make RetOrArg comparable, so we can put it into a map.
67+
bool operator<(RetOrArg const &O) const {
68+
return std::tie(F, Idx, IsArg) < std::tie(O.F, O.Idx, O.IsArg);
69+
}
70+
71+
/// Make RetOrArg comparable, so we can easily iterate the multimap.
72+
bool operator==(RetOrArg const &O) const {
73+
return F == O.F && Idx == O.Idx && IsArg == O.IsArg;
74+
}
75+
76+
std::string getDescription() const {
77+
return (Twine(IsArg ? "Argument #" : "Return value #") + Twine(Idx)
78+
+ " of function " + F->getName())
79+
.str();
80+
}
81+
};
82+
83+
/// During our initial pass over the program, we determine that things are
84+
/// either alive or maybe alive. We don't mark anything explicitly dead (even
85+
/// if we know they are), since anything not alive with no registered uses
86+
/// (in Uses) will never be marked alive and will thus become dead in the end.
87+
enum Liveness { Live, MaybeLive };
88+
89+
DeadArgumentEliminationPass(bool ShouldHackArguments = false)
90+
: ShouldHackArguments(ShouldHackArguments) { }
91+
#endif
92+
PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
93+
#if LLVM_VERSION_MAJOR == 16
94+
/// Convenience wrapper
95+
RetOrArg createRet(Function const *F, unsigned Idx) {
96+
return RetOrArg(F, Idx, false);
97+
}
98+
99+
/// Convenience wrapper
100+
RetOrArg createArg(Function const *F, unsigned Idx) {
101+
return RetOrArg(F, Idx, true);
102+
}
103+
104+
using UseMap = std::multimap<RetOrArg, RetOrArg>;
105+
106+
/// This maps a return value or argument to any MaybeLive return values or
107+
/// arguments it uses. This allows the MaybeLive values to be marked live
108+
/// when any of its users is marked live.
109+
/// For example (indices are left out for clarity):
110+
/// - Uses[ret F] = ret G
111+
/// This means that F calls G, and F returns the value returned by G.
112+
/// - Uses[arg F] = ret G
113+
/// This means that some function calls G and passes its result as an
114+
/// argument to F.
115+
/// - Uses[ret F] = arg F
116+
/// This means that F returns one of its own arguments.
117+
/// - Uses[arg F] = arg G
118+
/// This means that G calls F and passes one of its own (G's) arguments
119+
/// directly to F.
120+
UseMap Uses;
121+
122+
using LiveSet = std::set<RetOrArg>;
123+
using LiveFuncSet = std::set<Function const *>;
124+
125+
/// This set contains all values that have been determined to be live.
126+
LiveSet LiveValues;
127+
128+
/// This set contains all values that are cannot be changed in any way.
129+
LiveFuncSet LiveFunctions;
130+
131+
using UseVector = SmallVector<RetOrArg, 5>;
132+
133+
/// This allows this pass to do double-duty as the dead arg hacking pass
134+
/// (used only by bugpoint).
135+
bool ShouldHackArguments = false;
136+
137+
private:
138+
Liveness markIfNotLive(RetOrArg Use, UseVector &MaybeLiveUses);
139+
Liveness
140+
surveyUse(Use const *U, UseVector &MaybeLiveUses, unsigned RetValNum = -1U);
141+
Liveness surveyUses(Value const *V, UseVector &MaybeLiveUses);
142+
143+
void surveyFunction(Function const &F);
144+
bool isLive(RetOrArg const &RA);
145+
void
146+
markValue(RetOrArg const &RA, Liveness L, UseVector const &MaybeLiveUses);
147+
void markLive(RetOrArg const &RA);
148+
void markLive(Function const &F);
149+
void propagateLiveness(RetOrArg const &RA);
150+
bool removeDeadStuffFromFunction(Function *F);
151+
bool deleteDeadVarargs(Function &F);
152+
bool removeDeadArgumentsFromCallers(Function &F);
153+
#endif
154+
};
155+
156+
} // namespace kllvm
157+
158+
#endif // LLVM_TRANSFORMS_IPO_DEADARGUMENTELIMINATION_H
159+
160+
// NOLINTEND
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#ifndef REMOVE_DEAD_K_FUNCTIONS_H
2+
#define REMOVE_DEAD_K_FUNCTIONS_H
3+
4+
#include "llvm/IR/Function.h"
5+
#include "llvm/IR/LegacyPassManager.h"
6+
#include "llvm/Pass.h"
7+
#include "llvm/Passes/PassBuilder.h"
8+
#include "llvm/Passes/PassPlugin.h"
9+
#include "llvm/Support/CommandLine.h"
10+
#include "llvm/Support/raw_ostream.h"
11+
12+
using namespace llvm;
13+
14+
namespace kllvm {
15+
16+
bool run_remove_dead_k_functions(llvm::Function &f, TargetLibraryInfo *tli);
17+
18+
struct legacy_remove_dead_k_functions : llvm::FunctionPass {
19+
// NOLINTNEXTLINE(*-identifier-naming)
20+
static char ID;
21+
legacy_remove_dead_k_functions()
22+
: llvm::FunctionPass(ID) { }
23+
bool runOnFunction(llvm::Function &f) override {
24+
TargetLibraryInfo *tli
25+
= &getAnalysis<TargetLibraryInfoWrapperPass>().getTLI(f);
26+
return run_remove_dead_k_functions(f, tli);
27+
}
28+
};
29+
30+
struct remove_dead_k_functions : llvm::PassInfoMixin<remove_dead_k_functions> {
31+
static llvm::PreservedAnalyses
32+
run(llvm::Function &f, llvm::FunctionAnalysisManager &am) {
33+
if (!run_remove_dead_k_functions(
34+
f, &am.getResult<TargetLibraryAnalysis>(f))) {
35+
return llvm::PreservedAnalyses::all();
36+
}
37+
llvm::PreservedAnalyses pa;
38+
pa.preserveSet<llvm::CFGAnalyses>();
39+
return pa;
40+
}
41+
};
42+
43+
} // namespace kllvm
44+
45+
#endif

lib/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ add_subdirectory(ast)
33
add_subdirectory(binary)
44
add_subdirectory(codegen)
55
add_subdirectory(printer)
6-
add_subdirectory(set-visibility-hidden)
6+
add_subdirectory(passes)
77

88
separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS})
99
add_definitions(${LLVM_DEFINITIONS_LIST})

lib/ast/attribute_set.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ std::unordered_map<attribute_set::key, std::string> const &attribute_table() {
2727
{attribute_set::key::Functional, "functional"},
2828
{attribute_set::key::Hook, "hook"},
2929
{attribute_set::key::Idem, "idem"},
30+
{attribute_set::key::Impure, "impure"},
3031
{attribute_set::key::Label, "label"},
3132
{attribute_set::key::Left, "left"},
3233
{attribute_set::key::Location,

lib/codegen/ApplyPasses.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include <kllvm/codegen/ApplyPasses.h>
2+
#include <kllvm/codegen/MustTailDeadArgElimination.h>
23
#include <kllvm/codegen/Options.h>
4+
#include <kllvm/codegen/RemoveDeadKFunctions.h>
35
#include <kllvm/codegen/SetVisibilityHidden.h>
46

57
#include "runtime/alloc_cpp.h"
@@ -93,6 +95,16 @@ void apply_kllvm_opt_passes(llvm::Module &mod, bool hidden_visibility) {
9395
pm.addPass(set_visibility_hidden());
9496
}
9597
});
98+
pb.registerScalarOptimizerLateEPCallback(
99+
[](llvm::FunctionPassManager &pm, OptimizationLevel level) {
100+
pm.addPass(remove_dead_k_functions());
101+
});
102+
pb.registerOptimizerEarlyEPCallback(
103+
[](llvm::ModulePassManager &pm, OptimizationLevel level) {
104+
pm.addPass(DeadArgumentEliminationPass());
105+
pm.addPass(
106+
llvm::createModuleToFunctionPassAdaptor(remove_dead_k_functions()));
107+
});
96108

97109
// Create the pass manager.
98110
ModulePassManager mpm

lib/codegen/CreateTerm.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,13 @@ llvm::Value *create_term::create_function_call(
795795
auto *return_sort = dynamic_cast<kore_composite_sort *>(
796796
pattern->get_constructor()->get_sort().get());
797797
auto return_cat = return_sort->get_category(definition_);
798+
auto const &att = definition_->get_symbol_declarations()
799+
.at(pattern->get_constructor()->get_name())
800+
->attributes();
801+
802+
bool impure = att.contains(attribute_set::key::Impure)
803+
|| !att.contains(attribute_set::key::Total);
804+
798805
int i = 0;
799806
for (auto const &sort : pattern->get_constructor()->get_arguments()) {
800807
auto *concrete_sort = dynamic_cast<kore_composite_sort *>(sort.get());
@@ -832,12 +839,13 @@ llvm::Value *create_term::create_function_call(
832839
current_block_ = e.function_event_post(current_block_);
833840
}
834841

835-
return create_function_call(name, return_cat, args, sret, tailcc);
842+
return create_function_call(name, return_cat, args, sret, tailcc, impure);
836843
}
837844

838845
llvm::Value *create_term::create_function_call(
839846
std::string const &name, value_type return_cat,
840-
std::vector<llvm::Value *> const &args, bool sret, bool tailcc) {
847+
std::vector<llvm::Value *> const &args, bool sret, bool tailcc,
848+
bool impure) {
841849
llvm::Type *return_type = getvalue_type(return_cat, module_);
842850
std::vector<llvm::Type *> types;
843851
bool collection = false;
@@ -872,6 +880,9 @@ llvm::Value *create_term::create_function_call(
872880
llvm::FunctionType *func_type
873881
= llvm::FunctionType::get(return_type, types, false);
874882
llvm::Function *func = get_or_insert_function(module_, name, func_type);
883+
if (!impure) {
884+
func->addFnAttr("kllvm-pure");
885+
}
875886

876887
auto *call = llvm::CallInst::Create(func, real_args, "", current_block_);
877888
set_debug_loc(call);
@@ -1173,6 +1184,7 @@ bool make_function(
11731184
llvm::FunctionType *func_type
11741185
= llvm::FunctionType::get(return_type, param_types, false);
11751186
llvm::Function *apply_rule = get_or_insert_function(module, name, func_type);
1187+
apply_rule->setLinkage(llvm::GlobalValue::InternalLinkage);
11761188
init_debug_axiom(axiom->attributes());
11771189
std::string debug_name = name;
11781190
if (axiom->attributes().contains(attribute_set::key::Label)) {

lib/codegen/Decision.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ void function_node::codegen(decision *d) {
440440
final_subst, d->definition_, d->current_block_, d->module_, false);
441441
auto *call = creator.create_function_call(
442442
function_, cat_, args, function_.substr(0, 5) == "hook_",
443-
is_side_condition);
443+
is_side_condition, false);
444444
call->setName(name_.substr(0, max_name_length));
445445
d->store(std::make_pair(name_, type_), call);
446446

@@ -806,6 +806,13 @@ void make_eval_or_anywhere_function(
806806
// have one correct version of the function body after code generation
807807
// finishes.
808808
match_func->deleteBody();
809+
auto const &att = definition->get_symbol_declarations()
810+
.at(function->get_name())
811+
->attributes();
812+
if (!att.contains(attribute_set::key::Impure)
813+
&& att.contains(attribute_set::key::Total)) {
814+
match_func->addFnAttr("kllvm-pure");
815+
}
809816
[[maybe_unused]] kore_symbol_declaration *symbol_decl
810817
= definition->get_symbol_declarations().at(function->get_name());
811818
init_debug_axiom(symbol_decl->attributes());

0 commit comments

Comments
 (0)