Skip to content
This repository was archived by the owner on Oct 21, 2024. It is now read-only.

Commit 52255a1

Browse files
fsaintjacqueswesm
authored andcommitted
ARROW-7819: [C++][Gandiva] Add DumpIR to Filter/Projector object
The following patch exposes the generated IR as a method of the objects for further inspection. This is a breaking change for the internal method `FinalizeModule` which doesn't take the dump_ir and optimize flags, it receives `optimize` from Configuration now. - Refactored Engine, notably removed dead code, organized init in a single function and simplified LLVMGenerator. - Dumping IR should not write to stdout, but instead return it as a string in the `DumpIR` method. - Refactored Types, fixing some bad methods type. - Added the optimize field to `Configuration` class. - Simplified some unit tests. But more importantly, we can now inspect dynamically: ```python >>> filter = gandiva.make_filter(table.schema, condition) >>> print(filter.ir) ; ModuleID = 'codegen' source_filename = "codegen" target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" @llvm.global_ctors = appending global [0 x { i32, void ()*, i8* }] zeroinitializer @_ZN5arrow7BitUtilL8kBitmaskE = internal unnamed_addr constant [8 x i8] c"\01\02\04\08\10 @\80", align 1 ; Function Attrs: norecurse nounwind define i32 @expr_0_0(i64* nocapture readonly %args, i64* nocapture readonly %arg_addr_offsets, i64* nocapture readnone %local_bitmaps, i16* nocapture readnone %selection_vector, i64 %context_ptr, i64 %nrecords) local_unnamed_addr #0 { entry: %0 = bitcast i64* %args to i8** %cond_mem56 = load i8*, i8** %0, align 8 %1 = getelementptr i64, i64* %arg_addr_offsets, i64 3 %2 = load i64, i64* %1, align 8 %a_mem_addr = getelementptr i64, i64* %args, i64 3 %3 = bitcast i64* %a_mem_addr to double** %a_mem7 = load double*, double** %3, align 8 %scevgep = getelementptr double, double* %a_mem7, i64 %2 br label %loop loop: ; preds = %loop, %entry %loop_var = phi i64 [ 0, %entry ], [ %"loop_var+1", %loop ] %scevgep8 = getelementptr double, double* %scevgep, i64 %loop_var %a = load double, double* %scevgep8, align 8 %4 = fcmp olt double %a, 1.000000e+03 %5 = sext i1 %4 to i8 ``` Closes apache#6417 from fsaintjacques/ARROW-7819-gandiva-dump-ir-tool and squashes the following commits: c8d274f <François Saint-Jacques> Address comments 0bcebc8 <François Saint-Jacques> ARROW-7819: Add DumpIR to Filter/Projector object Authored-by: François Saint-Jacques <fsaintjacques@gmail.com> Signed-off-by: Wes McKinney <wesm+git@apache.org>
1 parent 6e14384 commit 52255a1

File tree

16 files changed

+286
-285
lines changed

16 files changed

+286
-285
lines changed

cpp/src/gandiva/arrow.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ using ArrayDataVector = std::vector<ArrayDataPtr>;
5151
using Status = arrow::Status;
5252
using StatusCode = arrow::StatusCode;
5353

54+
template <typename T>
55+
using Result = arrow::Result<T>;
56+
5457
static inline bool is_decimal_128(DataTypePtr type) {
5558
if (type->id() == arrow::Type::DECIMAL) {
5659
auto decimal_type = arrow::internal::checked_cast<arrow::DecimalType*>(type.get());

cpp/src/gandiva/configuration.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ class GANDIVA_EXPORT Configuration {
3838
std::size_t Hash() const;
3939
bool operator==(const Configuration& other) const;
4040
bool operator!=(const Configuration& other) const;
41+
42+
bool optimize() const { return optimize_; }
43+
void set_optimize(bool optimize) { optimize_ = optimize; }
44+
45+
private:
46+
bool optimize_ = true;
4147
};
4248

4349
/// \brief configuration builder for gandiva

cpp/src/gandiva/engine.cc

Lines changed: 75 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include "gandiva/engine.h"
2525

2626
#include <iostream>
27+
#include <memory>
28+
#include <mutex>
2729
#include <sstream>
2830
#include <string>
2931
#include <unordered_set>
@@ -41,8 +43,11 @@
4143
#include <llvm/Analysis/Passes.h>
4244
#include <llvm/Analysis/TargetTransformInfo.h>
4345
#include <llvm/Bitcode/BitcodeReader.h>
46+
#include <llvm/ExecutionEngine/ExecutionEngine.h>
4447
#include <llvm/ExecutionEngine/MCJIT.h>
4548
#include <llvm/IR/DataLayout.h>
49+
#include <llvm/IR/IRBuilder.h>
50+
#include <llvm/IR/LLVMContext.h>
4651
#include <llvm/IR/LegacyPassManager.h>
4752
#include <llvm/IR/Verifier.h>
4853
#include <llvm/Linker/Linker.h>
@@ -61,72 +66,87 @@
6166
#pragma warning(pop)
6267
#endif
6368

69+
#include "gandiva/configuration.h"
6470
#include "gandiva/decimal_ir.h"
6571
#include "gandiva/exported_funcs_registry.h"
6672

73+
#include "arrow/util/make_unique.h"
74+
6775
namespace gandiva {
6876

6977
extern const unsigned char kPrecompiledBitcode[];
7078
extern const size_t kPrecompiledBitcodeSize;
7179

72-
std::once_flag init_once_flag;
73-
74-
bool Engine::init_once_done_ = false;
75-
std::set<std::string> Engine::loaded_libs_ = {};
76-
std::mutex Engine::mtx_;
80+
std::once_flag llvm_init_once_flag;
81+
static bool llvm_init = false;
7782

78-
// One-time initializations.
7983
void Engine::InitOnce() {
80-
DCHECK_EQ(init_once_done_, false);
84+
DCHECK_EQ(llvm_init, false);
8185

8286
llvm::InitializeNativeTarget();
8387
llvm::InitializeNativeTargetAsmPrinter();
8488
llvm::InitializeNativeTargetAsmParser();
8589
llvm::InitializeNativeTargetDisassembler();
86-
8790
llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr);
8891

89-
init_once_done_ = true;
92+
llvm_init = true;
9093
}
9194

92-
/// factory method to construct the engine.
93-
Status Engine::Make(std::shared_ptr<Configuration> config,
94-
std::unique_ptr<Engine>* engine) {
95-
static auto host_cpu_name = llvm::sys::getHostCPUName();
96-
std::unique_ptr<Engine> engine_obj(new Engine());
97-
98-
std::call_once(init_once_flag, [&engine_obj] { engine_obj->InitOnce(); });
99-
engine_obj->context_.reset(new llvm::LLVMContext());
100-
engine_obj->ir_builder_.reset(new llvm::IRBuilder<>(*(engine_obj->context())));
101-
engine_obj->types_.reset(new LLVMTypes(*(engine_obj->context())));
102-
103-
// Create the execution engine
104-
std::unique_ptr<llvm::Module> cg_module(
105-
new llvm::Module("codegen", *(engine_obj->context())));
106-
engine_obj->module_ = cg_module.get();
107-
108-
llvm::EngineBuilder engineBuilder(std::move(cg_module));
109-
engineBuilder.setMCPU(host_cpu_name);
110-
engineBuilder.setEngineKind(llvm::EngineKind::JIT);
111-
engineBuilder.setOptLevel(llvm::CodeGenOpt::Aggressive);
112-
engineBuilder.setErrorStr(&(engine_obj->llvm_error_));
113-
engine_obj->execution_engine_.reset(engineBuilder.create());
114-
if (engine_obj->execution_engine_ == NULL) {
115-
engine_obj->module_ = NULL;
116-
return Status::CodeGenError(engine_obj->llvm_error_);
117-
}
118-
95+
Engine::Engine(const std::shared_ptr<Configuration>& conf,
96+
std::unique_ptr<llvm::LLVMContext> ctx,
97+
std::unique_ptr<llvm::ExecutionEngine> engine, llvm::Module* module)
98+
: context_(std::move(ctx)),
99+
execution_engine_(std::move(engine)),
100+
ir_builder_(arrow::internal::make_unique<llvm::IRBuilder<>>(*context_)),
101+
module_(module),
102+
types_(*context_),
103+
optimize_(conf->optimize()) {}
104+
105+
Status Engine::Init() {
119106
// Add mappings for functions that can be accessed from LLVM/IR module.
120-
engine_obj->AddGlobalMappings();
107+
AddGlobalMappings();
121108

122-
auto status = engine_obj->LoadPreCompiledIR();
123-
ARROW_RETURN_NOT_OK(status);
109+
ARROW_RETURN_NOT_OK(LoadPreCompiledIR());
110+
ARROW_RETURN_NOT_OK(DecimalIR::AddFunctions(this));
124111

125-
// Add decimal functions
126-
status = DecimalIR::AddFunctions(engine_obj.get());
127-
ARROW_RETURN_NOT_OK(status);
112+
return Status::OK();
113+
}
128114

129-
*engine = std::move(engine_obj);
115+
/// factory method to construct the engine.
116+
Status Engine::Make(const std::shared_ptr<Configuration>& conf,
117+
std::unique_ptr<Engine>* out) {
118+
std::call_once(llvm_init_once_flag, InitOnce);
119+
120+
auto ctx = arrow::internal::make_unique<llvm::LLVMContext>();
121+
auto module = arrow::internal::make_unique<llvm::Module>("codegen", *ctx);
122+
123+
// Capture before moving, ExceutionEngine does not allow retrieving the
124+
// original Module.
125+
auto module_ptr = module.get();
126+
127+
auto opt_level =
128+
conf->optimize() ? llvm::CodeGenOpt::Aggressive : llvm::CodeGenOpt::None;
129+
// Note that the lifetime of the error string is not captured by the
130+
// ExecutionEngine but only for the lifetime of the builder. Found by
131+
// inspecting LLVM sources.
132+
std::string builder_error;
133+
std::unique_ptr<llvm::ExecutionEngine> exec_engine{
134+
llvm::EngineBuilder(std::move(module))
135+
.setMCPU(llvm::sys::getHostCPUName())
136+
.setEngineKind(llvm::EngineKind::JIT)
137+
.setOptLevel(opt_level)
138+
.setErrorStr(&builder_error)
139+
.create()};
140+
141+
if (exec_engine == nullptr) {
142+
return Status::CodeGenError("Could not instantiate llvm::ExecutionEngine: ",
143+
builder_error);
144+
}
145+
146+
std::unique_ptr<Engine> engine{
147+
new Engine(conf, std::move(ctx), std::move(exec_engine), module_ptr)};
148+
ARROW_RETURN_NOT_OK(engine->Init());
149+
*out = std::move(engine);
130150
return Status::OK();
131151
}
132152

@@ -191,15 +211,10 @@ Status Engine::RemoveUnusedFunctions() {
191211
}
192212

193213
// Optimise and compile the module.
194-
Status Engine::FinalizeModule(bool optimise_ir, bool dump_ir, std::string* final_ir) {
195-
auto status = RemoveUnusedFunctions();
196-
ARROW_RETURN_NOT_OK(status);
197-
198-
if (dump_ir) {
199-
DumpIR("Before optimise");
200-
}
214+
Status Engine::FinalizeModule() {
215+
ARROW_RETURN_NOT_OK(RemoveUnusedFunctions());
201216

202-
if (optimise_ir) {
217+
if (optimize_) {
203218
// misc passes to allow for inlining, vectorization, ..
204219
std::unique_ptr<llvm::legacy::PassManager> pass_manager(
205220
new llvm::legacy::PassManager());
@@ -222,15 +237,8 @@ Status Engine::FinalizeModule(bool optimise_ir, bool dump_ir, std::string* final
222237
pass_builder.OptLevel = 3;
223238
pass_builder.populateModulePassManager(*pass_manager);
224239
pass_manager->run(*module_);
225-
226-
if (dump_ir) {
227-
DumpIR("After optimise");
228-
}
229-
}
230-
if (final_ir != nullptr) {
231-
llvm::raw_string_ostream stream(*final_ir);
232-
module_->print(stream, nullptr);
233240
}
241+
234242
ARROW_RETURN_IF(llvm::verifyModule(*module_, &llvm::errs()),
235243
Status::CodeGenError("Module verification failed after optimizer"));
236244

@@ -249,20 +257,20 @@ void* Engine::CompiledFunction(llvm::Function* irFunction) {
249257
void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type,
250258
const std::vector<llvm::Type*>& args,
251259
void* function_ptr) {
252-
auto prototype = llvm::FunctionType::get(ret_type, args, false /*isVarArg*/);
253-
auto fn = llvm::Function::Create(prototype, llvm::GlobalValue::ExternalLinkage, name,
254-
module());
260+
constexpr bool is_var_arg = false;
261+
auto prototype = llvm::FunctionType::get(ret_type, args, is_var_arg);
262+
constexpr auto linkage = llvm::GlobalValue::ExternalLinkage;
263+
auto fn = llvm::Function::Create(prototype, linkage, name, module());
255264
execution_engine_->addGlobalMapping(fn, function_ptr);
256265
}
257266

258267
void Engine::AddGlobalMappings() { ExportedFuncsRegistry::AddMappings(this); }
259268

260-
void Engine::DumpIR(std::string prefix) {
261-
std::string str;
262-
263-
llvm::raw_string_ostream stream(str);
269+
std::string Engine::DumpIR() {
270+
std::string ir;
271+
llvm::raw_string_ostream stream(ir);
264272
module_->print(stream, nullptr);
265-
std::cout << "====" << prefix << "===" << str << "\n";
273+
return ir;
266274
}
267275

268276
} // namespace gandiva

cpp/src/gandiva/engine.h

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
#include <string>
2424
#include <vector>
2525

26-
#include "arrow/status.h"
2726
#include "arrow/util/macros.h"
2827

2928
#include "gandiva/configuration.h"
@@ -34,21 +33,19 @@
3433

3534
namespace gandiva {
3635

37-
class FunctionIRBuilder;
38-
3936
/// \brief LLVM Execution engine wrapper.
4037
class GANDIVA_EXPORT Engine {
4138
public:
4239
llvm::LLVMContext* context() { return context_.get(); }
4340
llvm::IRBuilder<>* ir_builder() { return ir_builder_.get(); }
44-
LLVMTypes* types() { return types_.get(); }
41+
LLVMTypes* types() { return &types_; }
4542
llvm::Module* module() { return module_; }
4643

4744
/// Factory method to create and initialize the engine object.
4845
///
4946
/// \param[in] config the engine configuration
5047
/// \param[out] engine the created engine
51-
static Status Make(std::shared_ptr<Configuration> config,
48+
static Status Make(const std::shared_ptr<Configuration>& config,
5249
std::unique_ptr<Engine>* engine);
5350

5451
/// Add the function to the list of IR functions that need to be compiled.
@@ -59,7 +56,7 @@ class GANDIVA_EXPORT Engine {
5956
}
6057

6158
/// Optimise and compile the module.
62-
Status FinalizeModule(bool optimise_ir, bool dump_ir, std::string* final_ir = NULLPTR);
59+
Status FinalizeModule();
6360

6461
/// Get the compiled function corresponding to the irfunction.
6562
void* CompiledFunction(llvm::Function* irFunction);
@@ -68,16 +65,20 @@ class GANDIVA_EXPORT Engine {
6865
void AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type,
6966
const std::vector<llvm::Type*>& args, void* func);
7067

68+
/// Return the generated IR for the module.
69+
std::string DumpIR();
70+
7171
private:
72-
/// private constructor to ensure engine is created
73-
/// only through the factory.
74-
Engine() : module_finalized_(false) {}
72+
Engine(const std::shared_ptr<Configuration>& conf,
73+
std::unique_ptr<llvm::LLVMContext> ctx,
74+
std::unique_ptr<llvm::ExecutionEngine> engine, llvm::Module* module);
75+
76+
// Post construction init. This _must_ be called after the constructor.
77+
Status Init();
7578

76-
/// do one time inits.
7779
static void InitOnce();
78-
static bool init_once_done_;
7980

80-
llvm::ExecutionEngine& execution_engine() { return *execution_engine_.get(); }
81+
llvm::ExecutionEngine& execution_engine() { return *execution_engine_; }
8182

8283
/// load pre-compiled IR modules from precompiled_bitcode.cc and merge them into
8384
/// the main module.
@@ -89,23 +90,16 @@ class GANDIVA_EXPORT Engine {
8990
// Remove unused functions to reduce compile time.
9091
Status RemoveUnusedFunctions();
9192

92-
/// dump the IR code to stdout with the prefix string.
93-
void DumpIR(std::string prefix);
94-
9593
std::unique_ptr<llvm::LLVMContext> context_;
9694
std::unique_ptr<llvm::ExecutionEngine> execution_engine_;
97-
std::unique_ptr<LLVMTypes> types_;
9895
std::unique_ptr<llvm::IRBuilder<>> ir_builder_;
99-
llvm::Module* module_; // This is owned by the execution_engine_, so doesn't need to be
100-
// explicitly deleted.
96+
llvm::Module* module_;
97+
LLVMTypes types_;
10198

10299
std::vector<std::string> functions_to_compile_;
103100

104-
bool module_finalized_;
105-
std::string llvm_error_;
106-
107-
static std::set<std::string> loaded_libs_;
108-
static std::mutex mtx_;
101+
bool optimize_ = true;
102+
bool module_finalized_ = false;
109103
};
110104

111105
} // namespace gandiva

0 commit comments

Comments
 (0)