From 336a69daeefa8be24018ec4aaa7862a5a0afa230 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sun, 26 Nov 2023 13:12:07 +0800 Subject: [PATCH 01/28] Switch Gandiva JIT engine from MCJIT to ORC v2 with LLJIT API. --- cpp/cmake_modules/FindLLVMAlt.cmake | 2 +- cpp/src/gandiva/engine.cc | 139 ++++++++++++++++------------ cpp/src/gandiva/engine.h | 21 +++-- 3 files changed, 92 insertions(+), 70 deletions(-) diff --git a/cpp/cmake_modules/FindLLVMAlt.cmake b/cpp/cmake_modules/FindLLVMAlt.cmake index 69f680824b082..2730f829817f6 100644 --- a/cpp/cmake_modules/FindLLVMAlt.cmake +++ b/cpp/cmake_modules/FindLLVMAlt.cmake @@ -93,8 +93,8 @@ if(LLVM_FOUND) debuginfodwarf ipo linker - mcjit native + orcjit target) if(LLVM_VERSION_MAJOR GREATER_EQUAL 14) list(APPEND LLVM_TARGET_COMPONENTS passes) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 1cea1fd2cbf30..5d714981cac9c 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -46,7 +46,6 @@ #include #include #include -#include #include #include #include @@ -103,9 +102,44 @@ extern const size_t kPrecompiledBitcodeSize; std::once_flag llvm_init_once_flag; static bool llvm_init = false; static llvm::StringRef cpu_name; -static llvm::SmallVector cpu_attrs; +static std::vector cpu_attrs; std::once_flag register_exported_funcs_flag; +template +static arrow::Result AsArrowResult(llvm::Expected& expected) { + if (!expected) { + std::string str; + llvm::raw_string_ostream stream(str); + stream << expected.takeError(); + return Status::CodeGenError(stream.str()); + } + return std::move(expected.get()); +} + +static Result GetTargetMachineBuilder( + const Configuration& conf) { + llvm::orc::JITTargetMachineBuilder jtmb((llvm::Triple(llvm::sys::getProcessTriple()))); + if (conf.target_host_cpu()) { + jtmb.setCPU(cpu_name.str()); + jtmb.addFeatures(cpu_attrs); + } + auto opt_level = + conf.optimize() ? llvm::CodeGenOpt::Aggressive : llvm::CodeGenOpt::None; + jtmb.setCodeGenOptLevel(opt_level); + return jtmb; +} + +static Result GetTargetIRAnalysis( + llvm::orc::JITTargetMachineBuilder& jtmb) { + auto maybe_tm = jtmb.createTargetMachine(); + if (auto err = maybe_tm.takeError()) { + return Status::CodeGenError("Could not create target machine: ", + llvm::toString(std::move(err))); + } + auto target_machine = cantFail(std::move(maybe_tm)); + return target_machine->getTargetIRAnalysis(); +} + void Engine::InitOnce() { DCHECK_EQ(llvm_init, false); @@ -133,16 +167,18 @@ void Engine::InitOnce() { Engine::Engine(const std::shared_ptr& conf, std::unique_ptr ctx, - std::unique_ptr engine, llvm::Module* module, - bool cached) + std::unique_ptr lljit, + llvm::TargetIRAnalysis ir_analysis, bool cached) : context_(std::move(ctx)), - execution_engine_(std::move(engine)), + lljit_(std::move(lljit)), ir_builder_(std::make_unique>(*context_)), - module_(module), + module_(std::make_unique("codegen", *context_)), types_(*context_), optimize_(conf->optimize()), cached_(cached), - function_registry_(conf->function_registry()) {} + function_registry_(conf->function_registry()), + target_ir_analysis_(std::move(ir_analysis)), + conf_(conf) {} Status Engine::Init() { std::call_once(register_exported_funcs_flag, gandiva::RegisterExportedFuncs); @@ -168,39 +204,20 @@ Status Engine::Make(const std::shared_ptr& conf, bool cached, std::call_once(llvm_init_once_flag, InitOnce); auto ctx = std::make_unique(); - auto module = std::make_unique("codegen", *ctx); - // Capture before moving, ExecutionEngine does not allow retrieving the - // original Module. - auto module_ptr = module.get(); - - auto opt_level = - conf->optimize() ? llvm::CodeGenOpt::Aggressive : llvm::CodeGenOpt::None; - - // Note that the lifetime of the error string is not captured by the - // ExecutionEngine but only for the lifetime of the builder. Found by - // inspecting LLVM sources. - std::string builder_error; - - llvm::EngineBuilder engine_builder(std::move(module)); - - engine_builder.setEngineKind(llvm::EngineKind::JIT) - .setOptLevel(opt_level) - .setErrorStr(&builder_error); - - if (conf->target_host_cpu()) { - engine_builder.setMCPU(cpu_name); - engine_builder.setMAttrs(cpu_attrs); + auto jit_builder = llvm::orc::LLJITBuilder(); + ARROW_ASSIGN_OR_RAISE(auto jtmb, GetTargetMachineBuilder(*conf)); + auto maybe_jit = llvm::orc::LLJITBuilder().setJITTargetMachineBuilder(jtmb).create(); + if (auto err = maybe_jit.takeError()) { + return Status::CodeGenError("Could not create LLJIT instance: ", + llvm::toString(maybe_jit.takeError())); } - std::unique_ptr exec_engine{engine_builder.create()}; + auto jit = llvm::cantFail(std::move(maybe_jit)); - if (exec_engine == nullptr) { - return Status::CodeGenError("Could not instantiate llvm::ExecutionEngine: ", - builder_error); - } + ARROW_ASSIGN_OR_RAISE(auto target_ir_analysis, GetTargetIRAnalysis(jtmb)); + std::unique_ptr engine{new Engine(conf, std::move(ctx), std::move(jit), + std::move(target_ir_analysis), cached)}; - std::unique_ptr engine{ - new Engine(conf, std::move(ctx), std::move(exec_engine), module_ptr, cached)}; ARROW_RETURN_NOT_OK(engine->Init()); *out = std::move(engine); return Status::OK(); @@ -240,19 +257,8 @@ static void SetDataLayout(llvm::Module* module) { } // end of the modified method from MLIR -template -static arrow::Result AsArrowResult(llvm::Expected& expected) { - if (!expected) { - std::string str; - llvm::raw_string_ostream stream(str); - stream << expected.takeError(); - return Status::CodeGenError(stream.str()); - } - return std::move(expected.get()); -} - static arrow::Status VerifyAndLinkModule( - llvm::Module* dest_module, + llvm::Module& dest_module, llvm::Expected> src_module_or_error) { ARROW_ASSIGN_OR_RAISE(auto src_ir_module, AsArrowResult(src_module_or_error)); @@ -265,7 +271,7 @@ static arrow::Status VerifyAndLinkModule( llvm::verifyModule(*src_ir_module, &error_stream), Status::CodeGenError("verify of IR Module failed: " + error_stream.str())); - ARROW_RETURN_IF(llvm::Linker::linkModules(*dest_module, std::move(src_ir_module)), + ARROW_RETURN_IF(llvm::Linker::linkModules(dest_module, std::move(src_ir_module)), Status::CodeGenError("failed to link IR Modules")); return Status::OK(); @@ -291,7 +297,7 @@ Status Engine::LoadPreCompiledIR() { llvm::getOwningLazyBitcodeModule(std::move(buffer), *context()); // NOTE: llvm::handleAllErrors() fails linking with RTTI-disabled LLVM builds // (ARROW-5148) - ARROW_RETURN_NOT_OK(VerifyAndLinkModule(module_, std::move(module_or_error))); + ARROW_RETURN_NOT_OK(VerifyAndLinkModule(*module_, std::move(module_or_error))); return Status::OK(); } @@ -306,7 +312,7 @@ Status Engine::LoadExternalPreCompiledIR() { for (auto const& buffer : buffers) { auto llvm_memory_buffer_ref = AsLLVMMemoryBuffer(*buffer); auto module_or_error = llvm::parseBitcodeFile(llvm_memory_buffer_ref, *context()); - ARROW_RETURN_NOT_OK(VerifyAndLinkModule(module_, std::move(module_or_error))); + ARROW_RETURN_NOT_OK(VerifyAndLinkModule(*module_, std::move(module_or_error))); } return Status::OK(); @@ -386,7 +392,8 @@ static void OptimizeModuleWithLegacyPassManager(llvm::Module& module, std::unique_ptr pass_manager( new llvm::legacy::PassManager()); - pass_manager->add(llvm::createTargetTransformInfoWrapperPass(target_analysis)); + pass_manager->add( + llvm::createTargetTransformInfoWrapperPass(std::move(target_analysis))); pass_manager->add(llvm::createFunctionInliningPass()); pass_manager->add(llvm::createInstructionCombiningPass()); pass_manager->add(llvm::createPromoteMemoryToRegisterPass()); @@ -411,13 +418,11 @@ Status Engine::FinalizeModule() { ARROW_RETURN_NOT_OK(RemoveUnusedFunctions()); if (optimize_) { - auto target_analysis = execution_engine_->getTargetMachine()->getTargetIRAnalysis(); - // misc passes to allow for inlining, vectorization, .. #if LLVM_VERSION_MAJOR >= 14 - OptimizeModuleWithNewPassManager(*module_, target_analysis); + OptimizeModuleWithNewPassManager(*module_, target_ir_analysis_); #else - OptimizeModuleWithLegacyPassManager(*module_, target_analysis); + OptimizeModuleWithLegacyPassManager(*module_, target_ir_analysis_); #endif } @@ -426,7 +431,12 @@ Status Engine::FinalizeModule() { } // do the compilation - execution_engine_->finalizeObject(); + llvm::orc::ThreadSafeModule tsm(std::move(module_), std::move(context_)); + auto error = lljit_->addIRModule(std::move(tsm)); + if (error) { + return Status::CodeGenError("Failed to add module to LLJIT: ", + llvm::toString(std::move(error))); + } module_finalized_ = true; return Status::OK(); @@ -434,7 +444,10 @@ Status Engine::FinalizeModule() { void* Engine::CompiledFunction(std::string& function) { DCHECK(module_finalized_); - return reinterpret_cast(execution_engine_->getFunctionAddress(function)); + auto sym = lljit_->lookup(function); + DCHECK(sym) << "Failed to look up function: " << function; + + return (void*)sym.get().getAddress(); } void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type, @@ -443,8 +456,14 @@ void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_ty constexpr bool is_var_arg = false; auto prototype = llvm::FunctionType::get(ret_type, args, is_var_arg); constexpr auto linkage = llvm::GlobalValue::ExternalLinkage; - auto fn = llvm::Function::Create(prototype, linkage, name, module()); - execution_engine_->addGlobalMapping(fn, function_ptr); + llvm::Function::Create(prototype, linkage, name, module()); + + llvm::JITEvaluatedSymbol symbol((llvm::JITTargetAddress)function_ptr, + llvm::JITSymbolFlags::Exported); + llvm::orc::MangleAndInterner mangle(lljit_->getExecutionSession(), + lljit_->getDataLayout()); + cantFail(lljit_->getMainJITDylib().define( + llvm::orc::absoluteSymbols({{mangle(name), symbol}}))); } arrow::Status Engine::AddGlobalMappings() { diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index df2d8b36d9260..cd5ad14e081b6 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -22,6 +22,9 @@ #include #include +#include +#include + #include "arrow/util/logging.h" #include "arrow/util/macros.h" #include "gandiva/configuration.h" @@ -38,7 +41,7 @@ class GANDIVA_EXPORT Engine { llvm::LLVMContext* context() { return context_.get(); } llvm::IRBuilder<>* ir_builder() { return ir_builder_.get(); } LLVMTypes* types() { return &types_; } - llvm::Module* module() { return module_; } + llvm::Module* module() { return module_.get(); } /// Factory method to create and initialize the engine object. /// @@ -60,7 +63,8 @@ class GANDIVA_EXPORT Engine { /// Set LLVM ObjectCache. void SetLLVMObjectCache(GandivaObjectCache& object_cache) { - execution_engine_->setObjectCache(&object_cache); + // FIXME: support object cache + // execution_engine_->setObjectCache(&object_cache); } /// Get the compiled function corresponding to the irfunction. @@ -78,17 +82,14 @@ class GANDIVA_EXPORT Engine { private: Engine(const std::shared_ptr& conf, - std::unique_ptr ctx, - std::unique_ptr engine, llvm::Module* module, - bool cached); + std::unique_ptr ctx, std::unique_ptr lljit, + llvm::TargetIRAnalysis ir_analysis, bool cached); // Post construction init. This _must_ be called after the constructor. Status Init(); static void InitOnce(); - llvm::ExecutionEngine& execution_engine() { return *execution_engine_; } - /// load pre-compiled IR modules from precompiled_bitcode.cc and merge them into /// the main module. Status LoadPreCompiledIR(); @@ -103,9 +104,9 @@ class GANDIVA_EXPORT Engine { Status RemoveUnusedFunctions(); std::unique_ptr context_; - std::unique_ptr execution_engine_; + std::unique_ptr lljit_; std::unique_ptr> ir_builder_; - llvm::Module* module_; + std::unique_ptr module_; LLVMTypes types_; std::vector functions_to_compile_; @@ -115,6 +116,8 @@ class GANDIVA_EXPORT Engine { bool cached_; bool functions_loaded_ = false; std::shared_ptr function_registry_; + llvm::TargetIRAnalysis target_ir_analysis_; + const std::shared_ptr conf_; }; } // namespace gandiva From 06ed4b20e3d77baa987ee11412ac5981e0657b56 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sun, 26 Nov 2023 15:24:03 +0800 Subject: [PATCH 02/28] Store target machine instead of target ir analysi in the engine to avoid crash. --- cpp/src/gandiva/engine.cc | 39 ++++++++++++++++----------------------- cpp/src/gandiva/engine.h | 4 ++-- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 5d714981cac9c..2e7affea43eed 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -106,10 +106,12 @@ static std::vector cpu_attrs; std::once_flag register_exported_funcs_flag; template -static arrow::Result AsArrowResult(llvm::Expected& expected) { +static arrow::Result AsArrowResult(llvm::Expected& expected, + const std::string& error_context = "") { if (!expected) { std::string str; llvm::raw_string_ostream stream(str); + stream << error_context; stream << expected.takeError(); return Status::CodeGenError(stream.str()); } @@ -129,17 +131,6 @@ static Result GetTargetMachineBuilder( return jtmb; } -static Result GetTargetIRAnalysis( - llvm::orc::JITTargetMachineBuilder& jtmb) { - auto maybe_tm = jtmb.createTargetMachine(); - if (auto err = maybe_tm.takeError()) { - return Status::CodeGenError("Could not create target machine: ", - llvm::toString(std::move(err))); - } - auto target_machine = cantFail(std::move(maybe_tm)); - return target_machine->getTargetIRAnalysis(); -} - void Engine::InitOnce() { DCHECK_EQ(llvm_init, false); @@ -168,7 +159,7 @@ void Engine::InitOnce() { Engine::Engine(const std::shared_ptr& conf, std::unique_ptr ctx, std::unique_ptr lljit, - llvm::TargetIRAnalysis ir_analysis, bool cached) + std::unique_ptr target_machine, bool cached) : context_(std::move(ctx)), lljit_(std::move(lljit)), ir_builder_(std::make_unique>(*context_)), @@ -177,7 +168,7 @@ Engine::Engine(const std::shared_ptr& conf, optimize_(conf->optimize()), cached_(cached), function_registry_(conf->function_registry()), - target_ir_analysis_(std::move(ir_analysis)), + target_machine_(std::move(target_machine)), conf_(conf) {} Status Engine::Init() { @@ -208,15 +199,14 @@ Status Engine::Make(const std::shared_ptr& conf, bool cached, auto jit_builder = llvm::orc::LLJITBuilder(); ARROW_ASSIGN_OR_RAISE(auto jtmb, GetTargetMachineBuilder(*conf)); auto maybe_jit = llvm::orc::LLJITBuilder().setJITTargetMachineBuilder(jtmb).create(); - if (auto err = maybe_jit.takeError()) { - return Status::CodeGenError("Could not create LLJIT instance: ", - llvm::toString(maybe_jit.takeError())); - } - auto jit = llvm::cantFail(std::move(maybe_jit)); + ARROW_ASSIGN_OR_RAISE(auto jit, + AsArrowResult(maybe_jit, "Could not create LLJIT instance: ")); - ARROW_ASSIGN_OR_RAISE(auto target_ir_analysis, GetTargetIRAnalysis(jtmb)); + auto maybe_tm = jtmb.createTargetMachine(); + ARROW_ASSIGN_OR_RAISE(auto target_machine, + AsArrowResult(maybe_tm, "Could not create target machine: ")); std::unique_ptr engine{new Engine(conf, std::move(ctx), std::move(jit), - std::move(target_ir_analysis), cached)}; + std::move(target_machine), cached)}; ARROW_RETURN_NOT_OK(engine->Init()); *out = std::move(engine); @@ -260,7 +250,9 @@ static void SetDataLayout(llvm::Module* module) { static arrow::Status VerifyAndLinkModule( llvm::Module& dest_module, llvm::Expected> src_module_or_error) { - ARROW_ASSIGN_OR_RAISE(auto src_ir_module, AsArrowResult(src_module_or_error)); + ARROW_ASSIGN_OR_RAISE( + auto src_ir_module, + AsArrowResult(src_module_or_error, "Failed to verify and link module: ")); // set dataLayout SetDataLayout(src_ir_module.get()); @@ -418,9 +410,10 @@ Status Engine::FinalizeModule() { ARROW_RETURN_NOT_OK(RemoveUnusedFunctions()); if (optimize_) { + auto target_ir_analysis = target_machine_->getTargetIRAnalysis(); // misc passes to allow for inlining, vectorization, .. #if LLVM_VERSION_MAJOR >= 14 - OptimizeModuleWithNewPassManager(*module_, target_ir_analysis_); + OptimizeModuleWithNewPassManager(*module_, target_ir_analysis); #else OptimizeModuleWithLegacyPassManager(*module_, target_ir_analysis_); #endif diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index cd5ad14e081b6..01da231a47eff 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -83,7 +83,7 @@ class GANDIVA_EXPORT Engine { private: Engine(const std::shared_ptr& conf, std::unique_ptr ctx, std::unique_ptr lljit, - llvm::TargetIRAnalysis ir_analysis, bool cached); + std::unique_ptr target_machine, bool cached); // Post construction init. This _must_ be called after the constructor. Status Init(); @@ -116,7 +116,7 @@ class GANDIVA_EXPORT Engine { bool cached_; bool functions_loaded_ = false; std::shared_ptr function_registry_; - llvm::TargetIRAnalysis target_ir_analysis_; + std::unique_ptr target_machine_; const std::shared_ptr conf_; }; From 5c310d063746ee9418ab08a88de7482c63dbc700 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sun, 26 Nov 2023 15:44:22 +0800 Subject: [PATCH 03/28] Use a new configuration flag to indicate Gandiva engine to dump IR. --- cpp/src/gandiva/configuration.h | 19 +++++++++++++++++-- cpp/src/gandiva/engine.cc | 20 ++++++++++++++++---- cpp/src/gandiva/engine.h | 1 + cpp/src/gandiva/llvm_generator_test.cc | 2 +- cpp/src/gandiva/tests/test_util.cc | 4 ++++ cpp/src/gandiva/tests/test_util.h | 2 ++ 6 files changed, 41 insertions(+), 7 deletions(-) diff --git a/cpp/src/gandiva/configuration.h b/cpp/src/gandiva/configuration.h index f43a2b190731f..321ddbabd6e32 100644 --- a/cpp/src/gandiva/configuration.h +++ b/cpp/src/gandiva/configuration.h @@ -37,10 +37,12 @@ class GANDIVA_EXPORT Configuration { explicit Configuration(bool optimize, std::shared_ptr function_registry = - gandiva::default_function_registry()) + gandiva::default_function_registry(), + bool needs_ir_dumping = false) : optimize_(optimize), target_host_cpu_(true), - function_registry_(function_registry) {} + function_registry_(std::move(function_registry)), + needs_ir_dumping_(needs_ir_dumping) {} Configuration() : Configuration(true) {} @@ -50,11 +52,15 @@ class GANDIVA_EXPORT Configuration { bool optimize() const { return optimize_; } bool target_host_cpu() const { return target_host_cpu_; } + bool needs_ir_dumping() const { return needs_ir_dumping_; } std::shared_ptr function_registry() const { return function_registry_; } void set_optimize(bool optimize) { optimize_ = optimize; } + void set_needs_ir_dumping(bool needs_ir_dumping) { + needs_ir_dumping_ = needs_ir_dumping; + } void target_host_cpu(bool target_host_cpu) { target_host_cpu_ = target_host_cpu; } void set_function_registry(std::shared_ptr function_registry) { function_registry_ = std::move(function_registry); @@ -65,6 +71,9 @@ class GANDIVA_EXPORT Configuration { bool target_host_cpu_; /* set the mcpu flag to host cpu while compiling llvm ir */ std::shared_ptr function_registry_; /* function registry that may contain external functions */ + bool needs_ir_dumping_ = + false; /* flag indicating if IR dumping is needed, defaults to false, and turning it + on will negatively affect performance */ }; /// \brief configuration builder for gandiva @@ -83,6 +92,12 @@ class GANDIVA_EXPORT ConfigurationBuilder { return configuration; } + std::shared_ptr build_with_ir_dumping(bool needs_ir_dumping) { + std::shared_ptr configuration( + new Configuration(true, gandiva::default_function_registry(), needs_ir_dumping)); + return configuration; + } + std::shared_ptr build( std::shared_ptr function_registry) { std::shared_ptr configuration( diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 2e7affea43eed..54f223748322c 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -52,6 +52,7 @@ #include #include #include +#include #if LLVM_VERSION_MAJOR >= 17 #include #else @@ -131,6 +132,13 @@ static Result GetTargetMachineBuilder( return jtmb; } +static std::string GetModuleIR(const llvm::Module& module) { + std::string ir; + llvm::raw_string_ostream stream(ir); + module.print(stream, nullptr); + return ir; +} + void Engine::InitOnce() { DCHECK_EQ(llvm_init, false); @@ -423,6 +431,11 @@ Status Engine::FinalizeModule() { Status::CodeGenError("Module verification failed after optimizer")); } + // copy the module if IR dumping is needed since the module will be moved to construct + // LLJIT instance, and it is not available after LLJIT instance is constructed + if (conf_->needs_ir_dumping()) { + module_ir_ = GetModuleIR(*module_); + } // do the compilation llvm::orc::ThreadSafeModule tsm(std::move(module_), std::move(context_)); auto error = lljit_->addIRModule(std::move(tsm)); @@ -466,10 +479,9 @@ arrow::Status Engine::AddGlobalMappings() { } std::string Engine::DumpIR() { - std::string ir; - llvm::raw_string_ostream stream(ir); - module_->print(stream, nullptr); - return ir; + DCHECK(!module_ir_.empty()) + << "needs_ir_dumping in Configuration must be set for dumping IR"; + return module_ir_; } } // namespace gandiva diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index 01da231a47eff..d06df65d91f61 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -116,6 +116,7 @@ class GANDIVA_EXPORT Engine { bool cached_; bool functions_loaded_ = false; std::shared_ptr function_registry_; + std::string module_ir_; std::unique_ptr target_machine_; const std::shared_ptr conf_; }; diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index 853d8ae6c3b8d..6cb04a1f7342c 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -71,7 +71,7 @@ TEST_F(TestLLVMGenerator, VerifyPCFunctions) { TEST_F(TestLLVMGenerator, TestAdd) { // Setup LLVM generator to do an arithmetic add of two vectors std::unique_ptr generator; - ASSERT_OK(LLVMGenerator::Make(TestConfiguration(), false, &generator)); + ASSERT_OK(LLVMGenerator::Make(TestConfigWithIrDumping(), false, &generator)); Annotator annotator; auto field0 = std::make_shared("f0", arrow::int32()); diff --git a/cpp/src/gandiva/tests/test_util.cc b/cpp/src/gandiva/tests/test_util.cc index 959ea3cd7a446..2ee49ffae0ed6 100644 --- a/cpp/src/gandiva/tests/test_util.cc +++ b/cpp/src/gandiva/tests/test_util.cc @@ -30,6 +30,10 @@ std::shared_ptr TestConfiguration() { return ConfigurationBuilder::DefaultConfiguration(); } +std::shared_ptr TestConfigWithIrDumping() { + return ConfigurationBuilder().build_with_ir_dumping(true); +} + #ifndef GANDIVA_EXTENSION_TEST_DIR #define GANDIVA_EXTENSION_TEST_DIR "." #endif diff --git a/cpp/src/gandiva/tests/test_util.h b/cpp/src/gandiva/tests/test_util.h index 69d63732aeeaa..d8181fe67516c 100644 --- a/cpp/src/gandiva/tests/test_util.h +++ b/cpp/src/gandiva/tests/test_util.h @@ -98,6 +98,8 @@ static inline ArrayPtr MakeArrowTypeArray(const std::shared_ptr std::shared_ptr TestConfiguration(); +std::shared_ptr TestConfigWithIrDumping(); + // helper function to create a Configuration with an external function registered to the // given function registry std::shared_ptr TestConfigWithFunctionRegistry( From 772b5fdaab226c0b9493dac268a4ee1336002bfe Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sun, 26 Nov 2023 16:27:49 +0800 Subject: [PATCH 04/28] Enhance module api of gandiva engine to ensure it is not called after FinalizeModule. --- cpp/src/gandiva/engine.cc | 5 +++++ cpp/src/gandiva/engine.h | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 54f223748322c..38a8163d7231d 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -277,6 +277,11 @@ static arrow::Status VerifyAndLinkModule( return Status::OK(); } +llvm::Module* Engine::module() { + DCHECK(!module_finalized_) << "module cannot be accessed after finalized"; + return module_.get(); +} + // Handling for pre-compiled IR libraries. Status Engine::LoadPreCompiledIR() { auto bitcode = llvm::StringRef(reinterpret_cast(kPrecompiledBitcode), diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index d06df65d91f61..82c4390becf8d 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -41,7 +41,10 @@ class GANDIVA_EXPORT Engine { llvm::LLVMContext* context() { return context_.get(); } llvm::IRBuilder<>* ir_builder() { return ir_builder_.get(); } LLVMTypes* types() { return &types_; } - llvm::Module* module() { return module_.get(); } + + /// Retrieve LLVM module in the engine. + /// This should only be called before `FinalizeModule` is called + llvm::Module* module(); /// Factory method to create and initialize the engine object. /// From 7a935f0e8152e3821210407785fef15253f42cf5 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sun, 26 Nov 2023 20:33:15 +0800 Subject: [PATCH 05/28] Add optional object cache for gandiva engine creation API. --- cpp/src/gandiva/engine.cc | 43 +++++++++++++++++++------- cpp/src/gandiva/engine.h | 15 +++++---- cpp/src/gandiva/engine_llvm_test.cc | 18 ++++++----- cpp/src/gandiva/filter.cc | 6 ++-- cpp/src/gandiva/llvm_generator.cc | 13 ++++---- cpp/src/gandiva/llvm_generator.h | 11 ++++--- cpp/src/gandiva/llvm_generator_test.cc | 7 +++-- cpp/src/gandiva/projector.cc | 6 ++-- 8 files changed, 69 insertions(+), 50 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 38a8163d7231d..248b1c5575d3a 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -106,15 +106,21 @@ static llvm::StringRef cpu_name; static std::vector cpu_attrs; std::once_flag register_exported_funcs_flag; +template +static std::string ErrorToString(llvm::Expected& expected, + const std::string& error_context = "") { + std::string str; + llvm::raw_string_ostream stream(str); + stream << error_context; + stream << expected.takeError(); + return stream.str(); +} + template static arrow::Result AsArrowResult(llvm::Expected& expected, const std::string& error_context = "") { if (!expected) { - std::string str; - llvm::raw_string_ostream stream(str); - stream << error_context; - stream << expected.takeError(); - return Status::CodeGenError(stream.str()); + return Status::CodeGenError(ErrorToString(expected, error_context)); } return std::move(expected.get()); } @@ -198,15 +204,29 @@ Status Engine::LoadFunctionIRs() { } /// factory method to construct the engine. -Status Engine::Make(const std::shared_ptr& conf, bool cached, - std::unique_ptr* out) { +Status Engine::Make( + const std::shared_ptr& conf, bool cached, + std::optional> object_cache, + std::unique_ptr* out) { std::call_once(llvm_init_once_flag, InitOnce); auto ctx = std::make_unique(); - auto jit_builder = llvm::orc::LLJITBuilder(); ARROW_ASSIGN_OR_RAISE(auto jtmb, GetTargetMachineBuilder(*conf)); - auto maybe_jit = llvm::orc::LLJITBuilder().setJITTargetMachineBuilder(jtmb).create(); + auto lljit_builder = llvm::orc::LLJITBuilder(); + if (object_cache.has_value()) { + lljit_builder.setCompileFunctionCreator( + [&object_cache](llvm::orc::JITTargetMachineBuilder JTMB) + -> llvm::Expected> { + auto target_machine = JTMB.createTargetMachine(); + if (!target_machine) { + return target_machine.takeError(); + } + return std::make_unique( + std::move(*target_machine), &object_cache.value().get()); + }); + } + auto maybe_jit = lljit_builder.setJITTargetMachineBuilder(jtmb).create(); ARROW_ASSIGN_OR_RAISE(auto jit, AsArrowResult(maybe_jit, "Could not create LLJIT instance: ")); @@ -309,7 +329,7 @@ Status Engine::LoadPreCompiledIR() { static llvm::MemoryBufferRef AsLLVMMemoryBuffer(const arrow::Buffer& arrow_buffer) { auto data = reinterpret_cast(arrow_buffer.data()); auto size = arrow_buffer.size(); - return llvm::MemoryBufferRef(llvm::StringRef(data, size), "external_bitcode"); + return {llvm::StringRef(data, size), "external_bitcode"}; } Status Engine::LoadExternalPreCompiledIR() { @@ -456,7 +476,8 @@ Status Engine::FinalizeModule() { void* Engine::CompiledFunction(std::string& function) { DCHECK(module_finalized_); auto sym = lljit_->lookup(function); - DCHECK(sym) << "Failed to look up function: " << function; + DCHECK(sym) << "Failed to look up function: " << function + << " error: " << ErrorToString(sym); return (void*)sym.get().getAddress(); } diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index 82c4390becf8d..ad5a90646fe81 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -17,7 +17,9 @@ #pragma once +#include #include +#include #include #include #include @@ -50,9 +52,12 @@ class GANDIVA_EXPORT Engine { /// /// \param[in] config the engine configuration /// \param[in] cached flag to mark if the module is already compiled and cached + /// \param[in] object_cache an optional object_cache used for building the module /// \param[out] engine the created engine - static Status Make(const std::shared_ptr& config, bool cached, - std::unique_ptr* engine); + static Status Make( + const std::shared_ptr& config, bool cached, + std::optional> object_cache, + std::unique_ptr* engine); /// Add the function to the list of IR functions that need to be compiled. /// Compiling only the functions that are used by the module saves time. @@ -64,12 +69,6 @@ class GANDIVA_EXPORT Engine { /// Optimise and compile the module. Status FinalizeModule(); - /// Set LLVM ObjectCache. - void SetLLVMObjectCache(GandivaObjectCache& object_cache) { - // FIXME: support object cache - // execution_engine_->setObjectCache(&object_cache); - } - /// Get the compiled function corresponding to the irfunction. void* CompiledFunction(std::string& function); diff --git a/cpp/src/gandiva/engine_llvm_test.cc b/cpp/src/gandiva/engine_llvm_test.cc index 9baaa82d2e0d3..881e5bf500897 100644 --- a/cpp/src/gandiva/engine_llvm_test.cc +++ b/cpp/src/gandiva/engine_llvm_test.cc @@ -24,14 +24,14 @@ namespace gandiva { -typedef int64_t (*add_vector_func_t)(int64_t* data, int n); +using add_vector_func_t = int64_t (*)(int64_t*, int); class TestEngine : public ::testing::Test { protected: - std::string BuildVecAdd(Engine* engine) { - auto types = engine->types(); - llvm::IRBuilder<>* builder = engine->ir_builder(); - llvm::LLVMContext* context = engine->context(); + static std::string BuildVecAdd(Engine* gdv_engine) { + auto types = gdv_engine->types(); + llvm::IRBuilder<>* builder = gdv_engine->ir_builder(); + llvm::LLVMContext* context = gdv_engine->context(); // Create fn prototype : // int64_t add_longs(int64_t *elements, int32_t nelements) @@ -43,9 +43,9 @@ class TestEngine : public ::testing::Test { // Create fn std::string func_name = "add_longs"; - engine->AddFunctionToCompile(func_name); + gdv_engine->AddFunctionToCompile(func_name); llvm::Function* fn = llvm::Function::Create( - prototype, llvm::GlobalValue::ExternalLinkage, func_name, engine->module()); + prototype, llvm::GlobalValue::ExternalLinkage, func_name, gdv_engine->module()); assert(fn != nullptr); // Name the arguments @@ -99,7 +99,9 @@ class TestEngine : public ::testing::Test { return func_name; } - void BuildEngine() { ASSERT_OK(Engine::Make(TestConfiguration(), false, &engine)); } + void BuildEngine() { + ASSERT_OK(Engine::Make(TestConfiguration(), false, std::nullopt, &engine)); + } std::unique_ptr engine; std::shared_ptr configuration = TestConfiguration(); diff --git a/cpp/src/gandiva/filter.cc b/cpp/src/gandiva/filter.cc index 416d97b5dbd1d..fc50e3df5449a 100644 --- a/cpp/src/gandiva/filter.cc +++ b/cpp/src/gandiva/filter.cc @@ -66,7 +66,8 @@ Status Filter::Make(SchemaPtr schema, ConditionPtr condition, // Build LLVM generator, and generate code for the specified expression std::unique_ptr llvm_gen; - ARROW_RETURN_NOT_OK(LLVMGenerator::Make(configuration, is_cached, &llvm_gen)); + ARROW_RETURN_NOT_OK( + LLVMGenerator::Make(configuration, is_cached, obj_cache, &llvm_gen)); if (!is_cached) { // Run the validation on the expression. @@ -76,9 +77,6 @@ Status Filter::Make(SchemaPtr schema, ConditionPtr condition, ARROW_RETURN_NOT_OK(expr_validator.Validate(condition)); } - // Set the object cache for LLVM - llvm_gen->SetLLVMObjectCache(obj_cache); - ARROW_RETURN_NOT_OK(llvm_gen->Build({condition}, SelectionVector::Mode::MODE_NONE)); // Instantiate the filter with the completely built llvm generator diff --git a/cpp/src/gandiva/llvm_generator.cc b/cpp/src/gandiva/llvm_generator.cc index 41cbe0ffe3a3a..a8680ff521f02 100644 --- a/cpp/src/gandiva/llvm_generator.cc +++ b/cpp/src/gandiva/llvm_generator.cc @@ -42,12 +42,15 @@ LLVMGenerator::LLVMGenerator(bool cached, function_registry_(std::move(function_registry)), enable_ir_traces_(false) {} -Status LLVMGenerator::Make(const std::shared_ptr& config, bool cached, - std::unique_ptr* llvm_generator) { +Status LLVMGenerator::Make( + const std::shared_ptr& config, bool cached, + std::optional> object_cache, + std::unique_ptr* llvm_generator) { std::unique_ptr llvmgen_obj( new LLVMGenerator(cached, config->function_registry())); - ARROW_RETURN_NOT_OK(Engine::Make(config, cached, &(llvmgen_obj->engine_))); + ARROW_RETURN_NOT_OK( + Engine::Make(config, cached, object_cache, &(llvmgen_obj->engine_))); *llvm_generator = std::move(llvmgen_obj); return Status::OK(); @@ -62,10 +65,6 @@ LLVMGenerator::GetCache() { return shared_cache; } -void LLVMGenerator::SetLLVMObjectCache(GandivaObjectCache& object_cache) { - engine_->SetLLVMObjectCache(object_cache); -} - Status LLVMGenerator::Add(const ExpressionPtr expr, const FieldDescriptorPtr output) { int idx = static_cast(compiled_exprs_.size()); // decompose the expression to separate out value and validities. diff --git a/cpp/src/gandiva/llvm_generator.h b/cpp/src/gandiva/llvm_generator.h index 250ab78fbfe28..460cbaae46b1a 100644 --- a/cpp/src/gandiva/llvm_generator.h +++ b/cpp/src/gandiva/llvm_generator.h @@ -18,7 +18,9 @@ #pragma once #include +#include #include +#include #include #include @@ -47,16 +49,15 @@ class FunctionHolder; class GANDIVA_EXPORT LLVMGenerator { public: /// \brief Factory method to initialize the generator. - static Status Make(const std::shared_ptr& config, bool cached, - std::unique_ptr* llvm_generator); + static Status Make( + const std::shared_ptr& config, bool cached, + std::optional> object_cache, + std::unique_ptr* llvm_generator); /// \brief Get the cache to be used for LLVM ObjectCache. static std::shared_ptr>> GetCache(); - /// \brief Set LLVM ObjectCache. - void SetLLVMObjectCache(GandivaObjectCache& object_cache); - /// \brief Build the code for the expression trees for default mode with a LLVM /// ObjectCache. Each element in the vector represents an expression tree Status Build(const ExpressionVector& exprs, SelectionVector::Mode mode); diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index 6cb04a1f7342c..7149d510f0e08 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -48,7 +48,7 @@ class TestLLVMGenerator : public ::testing::Test { auto config = config_factory(std::move(external_registry)); std::unique_ptr generator; - ASSERT_OK(LLVMGenerator::Make(config, false, &generator)); + ASSERT_OK(LLVMGenerator::Make(config, false, std::nullopt, &generator)); auto module = generator->module(); ASSERT_OK(generator->engine_->LoadFunctionIRs()); @@ -59,7 +59,7 @@ class TestLLVMGenerator : public ::testing::Test { // Verify that a valid pc function exists for every function in the registry. TEST_F(TestLLVMGenerator, VerifyPCFunctions) { std::unique_ptr generator; - ASSERT_OK(LLVMGenerator::Make(TestConfiguration(), false, &generator)); + ASSERT_OK(LLVMGenerator::Make(TestConfiguration(), false, std::nullopt, &generator)); llvm::Module* module = generator->module(); ASSERT_OK(generator->engine_->LoadFunctionIRs()); @@ -71,7 +71,8 @@ TEST_F(TestLLVMGenerator, VerifyPCFunctions) { TEST_F(TestLLVMGenerator, TestAdd) { // Setup LLVM generator to do an arithmetic add of two vectors std::unique_ptr generator; - ASSERT_OK(LLVMGenerator::Make(TestConfigWithIrDumping(), false, &generator)); + ASSERT_OK( + LLVMGenerator::Make(TestConfigWithIrDumping(), false, std::nullopt, &generator)); Annotator annotator; auto field0 = std::make_shared("f0", arrow::int32()); diff --git a/cpp/src/gandiva/projector.cc b/cpp/src/gandiva/projector.cc index e717e825dfc71..c63c11bd3b4f2 100644 --- a/cpp/src/gandiva/projector.cc +++ b/cpp/src/gandiva/projector.cc @@ -81,7 +81,8 @@ Status Projector::Make(SchemaPtr schema, const ExpressionVector& exprs, // Build LLVM generator, and generate code for the specified expressions std::unique_ptr llvm_gen; - ARROW_RETURN_NOT_OK(LLVMGenerator::Make(configuration, is_cached, &llvm_gen)); + ARROW_RETURN_NOT_OK( + LLVMGenerator::Make(configuration, is_cached, obj_cache, &llvm_gen)); // Run the validation on the expressions. // Return if any of the expression is invalid since @@ -94,9 +95,6 @@ Status Projector::Make(SchemaPtr schema, const ExpressionVector& exprs, } } - // Set the object cache for LLVM - llvm_gen->SetLLVMObjectCache(obj_cache); - ARROW_RETURN_NOT_OK(llvm_gen->Build(exprs, selection_vector_mode)); // save the output field types. Used for validation at Evaluate() time. From 502bcc3e24f26e2cf032511f457adcb0f1af3584 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Mon, 27 Nov 2023 11:15:04 +0800 Subject: [PATCH 06/28] Add back the SetLLVMObjectCache API to addObjectFile to LLJIT instance. --- cpp/src/gandiva/engine.cc | 54 ++++++++++++++++++------------- cpp/src/gandiva/engine.h | 3 ++ cpp/src/gandiva/filter.cc | 3 ++ cpp/src/gandiva/llvm_generator.cc | 4 +++ cpp/src/gandiva/llvm_generator.h | 3 ++ cpp/src/gandiva/projector.cc | 3 ++ 6 files changed, 48 insertions(+), 22 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 248b1c5575d3a..d7cc485954b04 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -106,13 +106,12 @@ static llvm::StringRef cpu_name; static std::vector cpu_attrs; std::once_flag register_exported_funcs_flag; -template -static std::string ErrorToString(llvm::Expected& expected, +static std::string ErrorToString(const llvm::Error& error, const std::string& error_context = "") { std::string str; llvm::raw_string_ostream stream(str); stream << error_context; - stream << expected.takeError(); + stream << error; return stream.str(); } @@ -120,7 +119,7 @@ template static arrow::Result AsArrowResult(llvm::Expected& expected, const std::string& error_context = "") { if (!expected) { - return Status::CodeGenError(ErrorToString(expected, error_context)); + return Status::CodeGenError(ErrorToString(expected.takeError(), error_context)); } return std::move(expected.get()); } @@ -145,6 +144,14 @@ static std::string GetModuleIR(const llvm::Module& module) { return ir; } +void Engine::SetLLVMObjectCache(GandivaObjectCache& object_cache) { + auto cached_buffer = object_cache.getObject(nullptr); + if (cached_buffer) { + auto error = lljit_->addObjectFile(std::move(cached_buffer)); + DCHECK(!error) << "Failed to load object cache" << ErrorToString(error); + } +} + void Engine::InitOnce() { DCHECK_EQ(llvm_init, false); @@ -166,7 +173,7 @@ void Engine::InitOnce() { } } ARROW_LOG(INFO) << "Detected CPU Name : " << cpu_name.str(); - ARROW_LOG(INFO) << "Detected CPU Features:" << cpu_attrs_str; + ARROW_LOG(INFO) << "Detected CPU Features: [" << cpu_attrs_str << "]"; llvm_init = true; } @@ -212,8 +219,9 @@ Status Engine::Make( auto ctx = std::make_unique(); - ARROW_ASSIGN_OR_RAISE(auto jtmb, GetTargetMachineBuilder(*conf)); auto lljit_builder = llvm::orc::LLJITBuilder(); + ARROW_ASSIGN_OR_RAISE(auto jtmb, GetTargetMachineBuilder(*conf)); + lljit_builder.setJITTargetMachineBuilder(jtmb); if (object_cache.has_value()) { lljit_builder.setCompileFunctionCreator( [&object_cache](llvm::orc::JITTargetMachineBuilder JTMB) @@ -222,11 +230,12 @@ Status Engine::Make( if (!target_machine) { return target_machine.takeError(); } + // after compilation, the object code will be stored into the given object cache return std::make_unique( std::move(*target_machine), &object_cache.value().get()); }); } - auto maybe_jit = lljit_builder.setJITTargetMachineBuilder(jtmb).create(); + auto maybe_jit = lljit_builder.create(); ARROW_ASSIGN_OR_RAISE(auto jit, AsArrowResult(maybe_jit, "Could not create LLJIT instance: ")); @@ -448,25 +457,26 @@ Status Engine::FinalizeModule() { #if LLVM_VERSION_MAJOR >= 14 OptimizeModuleWithNewPassManager(*module_, target_ir_analysis); #else - OptimizeModuleWithLegacyPassManager(*module_, target_ir_analysis_); + OptimizeModuleWithLegacyPassManager(*module_, target_ir_analysis); #endif } ARROW_RETURN_IF(llvm::verifyModule(*module_, &llvm::errs()), Status::CodeGenError("Module verification failed after optimizer")); - } - // copy the module if IR dumping is needed since the module will be moved to construct - // LLJIT instance, and it is not available after LLJIT instance is constructed - if (conf_->needs_ir_dumping()) { - module_ir_ = GetModuleIR(*module_); - } - // do the compilation - llvm::orc::ThreadSafeModule tsm(std::move(module_), std::move(context_)); - auto error = lljit_->addIRModule(std::move(tsm)); - if (error) { - return Status::CodeGenError("Failed to add module to LLJIT: ", - llvm::toString(std::move(error))); + // copy the module if IR dumping is needed since the module will be moved to construct + // LLJIT instance, and it is not available after LLJIT instance is constructed + if (conf_->needs_ir_dumping()) { + module_ir_ = GetModuleIR(*module_); + ARROW_LOG(INFO) << "MODULE_IR : " << module_ir_; + } + // do the compilation + llvm::orc::ThreadSafeModule tsm(std::move(module_), std::move(context_)); + auto error = lljit_->addIRModule(std::move(tsm)); + if (error) { + return Status::CodeGenError("Failed to add module to LLJIT: ", + llvm::toString(std::move(error))); + } } module_finalized_ = true; @@ -477,7 +487,7 @@ void* Engine::CompiledFunction(std::string& function) { DCHECK(module_finalized_); auto sym = lljit_->lookup(function); DCHECK(sym) << "Failed to look up function: " << function - << " error: " << ErrorToString(sym); + << " error: " << ErrorToString(sym.takeError()); return (void*)sym.get().getAddress(); } @@ -488,7 +498,7 @@ void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_ty constexpr bool is_var_arg = false; auto prototype = llvm::FunctionType::get(ret_type, args, is_var_arg); constexpr auto linkage = llvm::GlobalValue::ExternalLinkage; - llvm::Function::Create(prototype, linkage, name, module()); + llvm::Function::Create(prototype, linkage, name); llvm::JITEvaluatedSymbol symbol((llvm::JITTargetAddress)function_ptr, llvm::JITSymbolFlags::Exported); diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index ad5a90646fe81..ff76d58355f91 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -69,6 +69,9 @@ class GANDIVA_EXPORT Engine { /// Optimise and compile the module. Status FinalizeModule(); + /// Set LLVM ObjectCache. + void SetLLVMObjectCache(GandivaObjectCache& object_cache); + /// Get the compiled function corresponding to the irfunction. void* CompiledFunction(std::string& function); diff --git a/cpp/src/gandiva/filter.cc b/cpp/src/gandiva/filter.cc index fc50e3df5449a..6bb8c2a906d21 100644 --- a/cpp/src/gandiva/filter.cc +++ b/cpp/src/gandiva/filter.cc @@ -77,6 +77,9 @@ Status Filter::Make(SchemaPtr schema, ConditionPtr condition, ARROW_RETURN_NOT_OK(expr_validator.Validate(condition)); } + // Set the object cache for LLVM + llvm_gen->SetLLVMObjectCache(obj_cache); + ARROW_RETURN_NOT_OK(llvm_gen->Build({condition}, SelectionVector::Mode::MODE_NONE)); // Instantiate the filter with the completely built llvm generator diff --git a/cpp/src/gandiva/llvm_generator.cc b/cpp/src/gandiva/llvm_generator.cc index a8680ff521f02..4016bf3e775b0 100644 --- a/cpp/src/gandiva/llvm_generator.cc +++ b/cpp/src/gandiva/llvm_generator.cc @@ -65,6 +65,10 @@ LLVMGenerator::GetCache() { return shared_cache; } +void LLVMGenerator::SetLLVMObjectCache(GandivaObjectCache& object_cache) { + engine_->SetLLVMObjectCache(object_cache); +} + Status LLVMGenerator::Add(const ExpressionPtr expr, const FieldDescriptorPtr output) { int idx = static_cast(compiled_exprs_.size()); // decompose the expression to separate out value and validities. diff --git a/cpp/src/gandiva/llvm_generator.h b/cpp/src/gandiva/llvm_generator.h index 460cbaae46b1a..ee321d9f203a1 100644 --- a/cpp/src/gandiva/llvm_generator.h +++ b/cpp/src/gandiva/llvm_generator.h @@ -58,6 +58,9 @@ class GANDIVA_EXPORT LLVMGenerator { static std::shared_ptr>> GetCache(); + /// \brief Set LLVM ObjectCache. + void SetLLVMObjectCache(GandivaObjectCache& object_cache); + /// \brief Build the code for the expression trees for default mode with a LLVM /// ObjectCache. Each element in the vector represents an expression tree Status Build(const ExpressionVector& exprs, SelectionVector::Mode mode); diff --git a/cpp/src/gandiva/projector.cc b/cpp/src/gandiva/projector.cc index c63c11bd3b4f2..93be623efcaae 100644 --- a/cpp/src/gandiva/projector.cc +++ b/cpp/src/gandiva/projector.cc @@ -95,6 +95,9 @@ Status Projector::Make(SchemaPtr schema, const ExpressionVector& exprs, } } + // Set the object cache for LLVM + llvm_gen->SetLLVMObjectCache(obj_cache); + ARROW_RETURN_NOT_OK(llvm_gen->Build(exprs, selection_vector_mode)); // save the output field types. Used for validation at Evaluate() time. From cb09cbf1f826334e023209e02a12d4da11e6671a Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Tue, 28 Nov 2023 18:03:52 +0800 Subject: [PATCH 07/28] Set generator for LLJIT so that current process symbol can be found. --- cpp/src/gandiva/engine.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index d7cc485954b04..4e79fbc0d1bf8 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -194,6 +194,7 @@ Engine::Engine(const std::shared_ptr& conf, Status Engine::Init() { std::call_once(register_exported_funcs_flag, gandiva::RegisterExportedFuncs); + // Add mappings for global functions that can be accessed from LLVM/IR module. ARROW_RETURN_NOT_OK(AddGlobalMappings()); @@ -451,6 +452,11 @@ Status Engine::FinalizeModule() { if (!cached_) { ARROW_RETURN_NOT_OK(RemoveUnusedFunctions()); + auto& data_layout = module_->getDataLayout(); + lljit_->getMainJITDylib().addGenerator( + cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + data_layout.getGlobalPrefix()))); + if (optimize_) { auto target_ir_analysis = target_machine_->getTargetIRAnalysis(); // misc passes to allow for inlining, vectorization, .. @@ -471,6 +477,7 @@ Status Engine::FinalizeModule() { ARROW_LOG(INFO) << "MODULE_IR : " << module_ir_; } // do the compilation + llvm::orc::ThreadSafeModule tsm(std::move(module_), std::move(context_)); auto error = lljit_->addIRModule(std::move(tsm)); if (error) { From 21bda887ee3a88aaf2312cc67900239f4f84fabe Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Tue, 28 Nov 2023 19:56:00 +0800 Subject: [PATCH 08/28] Set module data layout correctly and add back the module parameter wehn creating function so that symbols can be found. --- cpp/src/gandiva/engine.cc | 139 ++++++++++++++------------------------ cpp/src/gandiva/engine.h | 2 +- 2 files changed, 53 insertions(+), 88 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 4e79fbc0d1bf8..f44897886f05d 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -106,27 +106,19 @@ static llvm::StringRef cpu_name; static std::vector cpu_attrs; std::once_flag register_exported_funcs_flag; -static std::string ErrorToString(const llvm::Error& error, - const std::string& error_context = "") { - std::string str; - llvm::raw_string_ostream stream(str); - stream << error_context; - stream << error; - return stream.str(); -} - template static arrow::Result AsArrowResult(llvm::Expected& expected, const std::string& error_context = "") { if (!expected) { - return Status::CodeGenError(ErrorToString(expected.takeError(), error_context)); + return Status::CodeGenError(error_context, llvm::toString(expected.takeError())); } return std::move(expected.get()); } static Result GetTargetMachineBuilder( const Configuration& conf) { - llvm::orc::JITTargetMachineBuilder jtmb((llvm::Triple(llvm::sys::getProcessTriple()))); + llvm::orc::JITTargetMachineBuilder jtmb( + (llvm::Triple(llvm::sys::getDefaultTargetTriple()))); if (conf.target_host_cpu()) { jtmb.setCPU(cpu_name.str()); jtmb.addFeatures(cpu_attrs); @@ -144,11 +136,46 @@ static std::string GetModuleIR(const llvm::Module& module) { return ir; } +// add current process symbol to dylib +// LLVM >= 18 does this automatically +static void AddProcessSymbol(llvm::orc::LLJIT& lljit) { + lljit.getMainJITDylib().addGenerator( + cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + lljit.getDataLayout().getGlobalPrefix()))); +} + +static Result> BuildJIT( + llvm::orc::JITTargetMachineBuilder jtmb, + std::optional>& object_cache) { + auto jit_builder = llvm::orc::LLJITBuilder(); + + jit_builder.setJITTargetMachineBuilder(std::move(jtmb)); + if (object_cache.has_value()) { + jit_builder.setCompileFunctionCreator( + [&object_cache](llvm::orc::JITTargetMachineBuilder JTMB) + -> llvm::Expected> { + auto target_machine = JTMB.createTargetMachine(); + if (!target_machine) { + return target_machine.takeError(); + } + // after compilation, the object code will be stored into the given object cache + return std::make_unique( + std::move(*target_machine), &object_cache.value().get()); + }); + } + auto maybe_jit = jit_builder.create(); + ARROW_ASSIGN_OR_RAISE(auto jit, + AsArrowResult(maybe_jit, "Could not create LLJIT instance: ")); + + AddProcessSymbol(*jit); + return jit; +} + void Engine::SetLLVMObjectCache(GandivaObjectCache& object_cache) { auto cached_buffer = object_cache.getObject(nullptr); if (cached_buffer) { auto error = lljit_->addObjectFile(std::move(cached_buffer)); - DCHECK(!error) << "Failed to load object cache" << ErrorToString(error); + DCHECK(!error) << "Failed to load object cache" << llvm::toString(std::move(error)); } } @@ -178,10 +205,9 @@ void Engine::InitOnce() { } Engine::Engine(const std::shared_ptr& conf, - std::unique_ptr ctx, std::unique_ptr lljit, std::unique_ptr target_machine, bool cached) - : context_(std::move(ctx)), + : context_(std::make_unique()), lljit_(std::move(lljit)), ir_builder_(std::make_unique>(*context_)), module_(std::make_unique("codegen", *context_)), @@ -197,7 +223,6 @@ Status Engine::Init() { // Add mappings for global functions that can be accessed from LLVM/IR module. ARROW_RETURN_NOT_OK(AddGlobalMappings()); - return Status::OK(); } @@ -218,73 +243,20 @@ Status Engine::Make( std::unique_ptr* out) { std::call_once(llvm_init_once_flag, InitOnce); - auto ctx = std::make_unique(); - - auto lljit_builder = llvm::orc::LLJITBuilder(); ARROW_ASSIGN_OR_RAISE(auto jtmb, GetTargetMachineBuilder(*conf)); - lljit_builder.setJITTargetMachineBuilder(jtmb); - if (object_cache.has_value()) { - lljit_builder.setCompileFunctionCreator( - [&object_cache](llvm::orc::JITTargetMachineBuilder JTMB) - -> llvm::Expected> { - auto target_machine = JTMB.createTargetMachine(); - if (!target_machine) { - return target_machine.takeError(); - } - // after compilation, the object code will be stored into the given object cache - return std::make_unique( - std::move(*target_machine), &object_cache.value().get()); - }); - } - auto maybe_jit = lljit_builder.create(); - ARROW_ASSIGN_OR_RAISE(auto jit, - AsArrowResult(maybe_jit, "Could not create LLJIT instance: ")); - + ARROW_ASSIGN_OR_RAISE(auto jit, BuildJIT(jtmb, object_cache)); auto maybe_tm = jtmb.createTargetMachine(); ARROW_ASSIGN_OR_RAISE(auto target_machine, AsArrowResult(maybe_tm, "Could not create target machine: ")); - std::unique_ptr engine{new Engine(conf, std::move(ctx), std::move(jit), - std::move(target_machine), cached)}; + + std::unique_ptr engine{ + new Engine(conf, std::move(jit), std::move(target_machine), cached)}; ARROW_RETURN_NOT_OK(engine->Init()); *out = std::move(engine); return Status::OK(); } -// This method was modified from its original version for a part of MLIR -// Original source from -// https://github.com/llvm/llvm-project/blob/9f2ce5b915a505a5488a5cf91bb0a8efa9ddfff7/mlir/lib/ExecutionEngine/ExecutionEngine.cpp -// The original copyright notice follows. - -// 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 - -static void SetDataLayout(llvm::Module* module) { - auto target_triple = llvm::sys::getDefaultTargetTriple(); - std::string error_message; - auto target = llvm::TargetRegistry::lookupTarget(target_triple, error_message); - if (!target) { - return; - } - - std::string cpu(llvm::sys::getHostCPUName()); - llvm::SubtargetFeatures features; - llvm::StringMap host_features; - - if (llvm::sys::getHostCPUFeatures(host_features)) { - for (auto& f : host_features) { - features.AddFeature(f.first(), f.second); - } - } - - std::unique_ptr machine( - target->createTargetMachine(target_triple, cpu, features.getString(), {}, {})); - - module->setDataLayout(machine->createDataLayout()); -} -// end of the modified method from MLIR - static arrow::Status VerifyAndLinkModule( llvm::Module& dest_module, llvm::Expected> src_module_or_error) { @@ -292,8 +264,7 @@ static arrow::Status VerifyAndLinkModule( auto src_ir_module, AsArrowResult(src_module_or_error, "Failed to verify and link module: ")); - // set dataLayout - SetDataLayout(src_ir_module.get()); + src_ir_module->setDataLayout(dest_module.getDataLayout()); std::string error_info; llvm::raw_string_ostream error_stream(error_info); @@ -452,18 +423,13 @@ Status Engine::FinalizeModule() { if (!cached_) { ARROW_RETURN_NOT_OK(RemoveUnusedFunctions()); - auto& data_layout = module_->getDataLayout(); - lljit_->getMainJITDylib().addGenerator( - cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( - data_layout.getGlobalPrefix()))); - if (optimize_) { - auto target_ir_analysis = target_machine_->getTargetIRAnalysis(); + auto target_analysis = target_machine_->getTargetIRAnalysis(); // misc passes to allow for inlining, vectorization, .. #if LLVM_VERSION_MAJOR >= 14 - OptimizeModuleWithNewPassManager(*module_, target_ir_analysis); + OptimizeModuleWithNewPassManager(*module_, std::move(target_analysis)); #else - OptimizeModuleWithLegacyPassManager(*module_, target_ir_analysis); + OptimizeModuleWithLegacyPassManager(*module_, std::move(target_analysis)); #endif } @@ -474,14 +440,13 @@ Status Engine::FinalizeModule() { // LLJIT instance, and it is not available after LLJIT instance is constructed if (conf_->needs_ir_dumping()) { module_ir_ = GetModuleIR(*module_); - ARROW_LOG(INFO) << "MODULE_IR : " << module_ir_; } - // do the compilation + // do the compilation llvm::orc::ThreadSafeModule tsm(std::move(module_), std::move(context_)); auto error = lljit_->addIRModule(std::move(tsm)); if (error) { - return Status::CodeGenError("Failed to add module to LLJIT: ", + return Status::CodeGenError("Failed to add IR module to LLJIT: ", llvm::toString(std::move(error))); } } @@ -494,7 +459,7 @@ void* Engine::CompiledFunction(std::string& function) { DCHECK(module_finalized_); auto sym = lljit_->lookup(function); DCHECK(sym) << "Failed to look up function: " << function - << " error: " << ErrorToString(sym.takeError()); + << " error: " << llvm::toString(sym.takeError()); return (void*)sym.get().getAddress(); } @@ -505,7 +470,7 @@ void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_ty constexpr bool is_var_arg = false; auto prototype = llvm::FunctionType::get(ret_type, args, is_var_arg); constexpr auto linkage = llvm::GlobalValue::ExternalLinkage; - llvm::Function::Create(prototype, linkage, name); + llvm::Function::Create(prototype, linkage, name, module()); llvm::JITEvaluatedSymbol symbol((llvm::JITTargetAddress)function_ptr, llvm::JITSymbolFlags::Exported); diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index ff76d58355f91..3fc3e4145f608 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -87,7 +87,7 @@ class GANDIVA_EXPORT Engine { private: Engine(const std::shared_ptr& conf, - std::unique_ptr ctx, std::unique_ptr lljit, + std::unique_ptr lljit, std::unique_ptr target_machine, bool cached); // Post construction init. This _must_ be called after the constructor. From ffd322bcc0235505429e9df84b032665a780b73a Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Tue, 5 Dec 2023 12:50:31 +0800 Subject: [PATCH 09/28] Use reinterpret_cast instead of C-style static cast for converting function pointer. --- cpp/src/gandiva/engine.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index f44897886f05d..f2541fceb6c9f 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -461,7 +461,7 @@ void* Engine::CompiledFunction(std::string& function) { DCHECK(sym) << "Failed to look up function: " << function << " error: " << llvm::toString(sym.takeError()); - return (void*)sym.get().getAddress(); + return reinterpret_cast(sym.get().getAddress()); } void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type, @@ -472,7 +472,7 @@ void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_ty constexpr auto linkage = llvm::GlobalValue::ExternalLinkage; llvm::Function::Create(prototype, linkage, name, module()); - llvm::JITEvaluatedSymbol symbol((llvm::JITTargetAddress)function_ptr, + llvm::JITEvaluatedSymbol symbol(reinterpret_cast(function_ptr), llvm::JITSymbolFlags::Exported); llvm::orc::MangleAndInterner mangle(lljit_->getExecutionSession(), lljit_->getDataLayout()); From 6c28f4617b961f5b6ee93497a06247fc9ba70f04 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Wed, 6 Dec 2023 17:30:18 +0800 Subject: [PATCH 10/28] Change LLVMGenerator and Engine's Make factory method to return arrow Result instead of using out parameter so that it is simpler and optional parameter can be used as well. --- cpp/src/gandiva/engine.cc | 8 +++----- cpp/src/gandiva/engine.h | 7 +++---- cpp/src/gandiva/engine_llvm_test.cc | 4 ++-- cpp/src/gandiva/filter.cc | 5 ++--- cpp/src/gandiva/llvm_generator.cc | 15 ++++++--------- cpp/src/gandiva/llvm_generator.h | 6 +++--- cpp/src/gandiva/llvm_generator_test.cc | 11 ++++------- cpp/src/gandiva/projector.cc | 5 ++--- 8 files changed, 25 insertions(+), 36 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index f2541fceb6c9f..9ccf911f8b1d3 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -237,10 +237,9 @@ Status Engine::LoadFunctionIRs() { } /// factory method to construct the engine. -Status Engine::Make( +Result> Engine::Make( const std::shared_ptr& conf, bool cached, - std::optional> object_cache, - std::unique_ptr* out) { + std::optional> object_cache) { std::call_once(llvm_init_once_flag, InitOnce); ARROW_ASSIGN_OR_RAISE(auto jtmb, GetTargetMachineBuilder(*conf)); @@ -253,8 +252,7 @@ Status Engine::Make( new Engine(conf, std::move(jit), std::move(target_machine), cached)}; ARROW_RETURN_NOT_OK(engine->Init()); - *out = std::move(engine); - return Status::OK(); + return engine; } static arrow::Status VerifyAndLinkModule( diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index 3fc3e4145f608..4e6a6fe038c82 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -53,11 +53,10 @@ class GANDIVA_EXPORT Engine { /// \param[in] config the engine configuration /// \param[in] cached flag to mark if the module is already compiled and cached /// \param[in] object_cache an optional object_cache used for building the module - /// \param[out] engine the created engine - static Status Make( + /// \return arrow::Result containing the created engine + static Result> Make( const std::shared_ptr& config, bool cached, - std::optional> object_cache, - std::unique_ptr* engine); + std::optional> object_cache = std::nullopt); /// Add the function to the list of IR functions that need to be compiled. /// Compiling only the functions that are used by the module saves time. diff --git a/cpp/src/gandiva/engine_llvm_test.cc b/cpp/src/gandiva/engine_llvm_test.cc index 881e5bf500897..2d74275c049df 100644 --- a/cpp/src/gandiva/engine_llvm_test.cc +++ b/cpp/src/gandiva/engine_llvm_test.cc @@ -28,7 +28,7 @@ using add_vector_func_t = int64_t (*)(int64_t*, int); class TestEngine : public ::testing::Test { protected: - static std::string BuildVecAdd(Engine* gdv_engine) { + std::string BuildVecAdd(Engine* gdv_engine) { auto types = gdv_engine->types(); llvm::IRBuilder<>* builder = gdv_engine->ir_builder(); llvm::LLVMContext* context = gdv_engine->context(); @@ -100,7 +100,7 @@ class TestEngine : public ::testing::Test { } void BuildEngine() { - ASSERT_OK(Engine::Make(TestConfiguration(), false, std::nullopt, &engine)); + ASSERT_OK_AND_ASSIGN(engine, Engine::Make(TestConfiguration(), false)); } std::unique_ptr engine; diff --git a/cpp/src/gandiva/filter.cc b/cpp/src/gandiva/filter.cc index 6bb8c2a906d21..2e9952089e948 100644 --- a/cpp/src/gandiva/filter.cc +++ b/cpp/src/gandiva/filter.cc @@ -65,9 +65,8 @@ Status Filter::Make(SchemaPtr schema, ConditionPtr condition, GandivaObjectCache obj_cache(cache, cache_key); // Build LLVM generator, and generate code for the specified expression - std::unique_ptr llvm_gen; - ARROW_RETURN_NOT_OK( - LLVMGenerator::Make(configuration, is_cached, obj_cache, &llvm_gen)); + ARROW_ASSIGN_OR_RAISE(auto llvm_gen, + LLVMGenerator::Make(configuration, is_cached, obj_cache)); if (!is_cached) { // Run the validation on the expression. diff --git a/cpp/src/gandiva/llvm_generator.cc b/cpp/src/gandiva/llvm_generator.cc index 4016bf3e775b0..960fdb534def7 100644 --- a/cpp/src/gandiva/llvm_generator.cc +++ b/cpp/src/gandiva/llvm_generator.cc @@ -42,18 +42,15 @@ LLVMGenerator::LLVMGenerator(bool cached, function_registry_(std::move(function_registry)), enable_ir_traces_(false) {} -Status LLVMGenerator::Make( +Result> LLVMGenerator::Make( const std::shared_ptr& config, bool cached, - std::optional> object_cache, - std::unique_ptr* llvm_generator) { - std::unique_ptr llvmgen_obj( + std::optional> object_cache) { + std::unique_ptr llvm_generator( new LLVMGenerator(cached, config->function_registry())); - ARROW_RETURN_NOT_OK( - Engine::Make(config, cached, object_cache, &(llvmgen_obj->engine_))); - *llvm_generator = std::move(llvmgen_obj); - - return Status::OK(); + ARROW_ASSIGN_OR_RAISE(llvm_generator->engine_, + Engine::Make(config, cached, object_cache)); + return llvm_generator; } std::shared_ptr>> diff --git a/cpp/src/gandiva/llvm_generator.h b/cpp/src/gandiva/llvm_generator.h index ee321d9f203a1..a0e2dc712917d 100644 --- a/cpp/src/gandiva/llvm_generator.h +++ b/cpp/src/gandiva/llvm_generator.h @@ -49,10 +49,10 @@ class FunctionHolder; class GANDIVA_EXPORT LLVMGenerator { public: /// \brief Factory method to initialize the generator. - static Status Make( + static Result> Make( const std::shared_ptr& config, bool cached, - std::optional> object_cache, - std::unique_ptr* llvm_generator); + std::optional> object_cache = + std::nullopt); /// \brief Get the cache to be used for LLVM ObjectCache. static std::shared_ptr>> diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index 7149d510f0e08..f35ebdd701660 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -47,8 +47,7 @@ class TestLLVMGenerator : public ::testing::Test { auto external_registry = std::make_shared(); auto config = config_factory(std::move(external_registry)); - std::unique_ptr generator; - ASSERT_OK(LLVMGenerator::Make(config, false, std::nullopt, &generator)); + ASSERT_OK_AND_ASSIGN(auto generator, LLVMGenerator::Make(config, false)); auto module = generator->module(); ASSERT_OK(generator->engine_->LoadFunctionIRs()); @@ -58,8 +57,7 @@ class TestLLVMGenerator : public ::testing::Test { // Verify that a valid pc function exists for every function in the registry. TEST_F(TestLLVMGenerator, VerifyPCFunctions) { - std::unique_ptr generator; - ASSERT_OK(LLVMGenerator::Make(TestConfiguration(), false, std::nullopt, &generator)); + ASSERT_OK_AND_ASSIGN(auto generator, LLVMGenerator::Make(TestConfiguration(), false)); llvm::Module* module = generator->module(); ASSERT_OK(generator->engine_->LoadFunctionIRs()); @@ -70,9 +68,8 @@ TEST_F(TestLLVMGenerator, VerifyPCFunctions) { TEST_F(TestLLVMGenerator, TestAdd) { // Setup LLVM generator to do an arithmetic add of two vectors - std::unique_ptr generator; - ASSERT_OK( - LLVMGenerator::Make(TestConfigWithIrDumping(), false, std::nullopt, &generator)); + ASSERT_OK_AND_ASSIGN(auto generator, + LLVMGenerator::Make(TestConfigWithIrDumping(), false)); Annotator annotator; auto field0 = std::make_shared("f0", arrow::int32()); diff --git a/cpp/src/gandiva/projector.cc b/cpp/src/gandiva/projector.cc index 93be623efcaae..5008207e0eb45 100644 --- a/cpp/src/gandiva/projector.cc +++ b/cpp/src/gandiva/projector.cc @@ -80,9 +80,8 @@ Status Projector::Make(SchemaPtr schema, const ExpressionVector& exprs, GandivaObjectCache obj_cache(cache, cache_key); // Build LLVM generator, and generate code for the specified expressions - std::unique_ptr llvm_gen; - ARROW_RETURN_NOT_OK( - LLVMGenerator::Make(configuration, is_cached, obj_cache, &llvm_gen)); + ARROW_ASSIGN_OR_RAISE(auto llvm_gen, + LLVMGenerator::Make(configuration, is_cached, obj_cache)); // Run the validation on the expressions. // Return if any of the expression is invalid since From b715b8735829d24a7bc766accd5905fe1efaf881 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Wed, 6 Dec 2023 20:07:12 +0800 Subject: [PATCH 11/28] Make CompileFunction to return arrow Result to handle error better, and resolve the LLVM 15 symbol getAddress API change issue, and fix the ExecutorSymbolDef issue for LLVM 17. --- cpp/src/gandiva/configuration.h | 6 ++--- cpp/src/gandiva/engine.cc | 36 +++++++++++++++++++------- cpp/src/gandiva/engine.h | 6 +++-- cpp/src/gandiva/engine_llvm_test.cc | 6 +++-- cpp/src/gandiva/llvm_generator.cc | 3 ++- cpp/src/gandiva/llvm_generator_test.cc | 3 ++- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/cpp/src/gandiva/configuration.h b/cpp/src/gandiva/configuration.h index 321ddbabd6e32..6f1f5d1cf3fa6 100644 --- a/cpp/src/gandiva/configuration.h +++ b/cpp/src/gandiva/configuration.h @@ -71,9 +71,9 @@ class GANDIVA_EXPORT Configuration { bool target_host_cpu_; /* set the mcpu flag to host cpu while compiling llvm ir */ std::shared_ptr function_registry_; /* function registry that may contain external functions */ - bool needs_ir_dumping_ = - false; /* flag indicating if IR dumping is needed, defaults to false, and turning it - on will negatively affect performance */ + // flag indicating if IR dumping is needed, defaults to false, and turning it on will + // negatively affect performance + bool needs_ir_dumping_ = false; }; /// \brief configuration builder for gandiva diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 9ccf911f8b1d3..c2a4e363cfadb 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -453,13 +453,20 @@ Status Engine::FinalizeModule() { return Status::OK(); } -void* Engine::CompiledFunction(std::string& function) { +Result Engine::CompiledFunction(std::string& function) { DCHECK(module_finalized_); auto sym = lljit_->lookup(function); - DCHECK(sym) << "Failed to look up function: " << function - << " error: " << llvm::toString(sym.takeError()); - - return reinterpret_cast(sym.get().getAddress()); + if (!sym) { + return Status::CodeGenError("Failed to look up function: " + function + + " error: " + llvm::toString(sym.takeError())); + } + // Since LLVM 15, `LLJIT::lookup` returns ExecutorAddrs rather than JITEvaluatedSymbols +#if LLVM_VERSION_MAJOR >= 15 + auto fn_addr = sym->getValue(); +#else + auto fn_addr = sym->getAddress(); +#endif + return reinterpret_cast(fn_addr); } void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type, @@ -470,12 +477,23 @@ void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_ty constexpr auto linkage = llvm::GlobalValue::ExternalLinkage; llvm::Function::Create(prototype, linkage, name, module()); - llvm::JITEvaluatedSymbol symbol(reinterpret_cast(function_ptr), - llvm::JITSymbolFlags::Exported); llvm::orc::MangleAndInterner mangle(lljit_->getExecutionSession(), lljit_->getDataLayout()); - cantFail(lljit_->getMainJITDylib().define( - llvm::orc::absoluteSymbols({{mangle(name), symbol}}))); + + // https://github.com/llvm/llvm-project/commit/8b1771bd9f304be39d4dcbdcccedb6d3bcd18200#diff-77984a824d9182e5c67a481740f3bc5da78d5bd4cf6e1716a083ddb30a4a4931 + // LLVM 17 introduced ExecutorSymbolDef and move most of ORC APIs to ExecutorAddr +#if LLVM_VERSION_MAJOR >= 17 + llvm::orc::ExecutorSymbolDef symbol( + llvm::orc::ExecutorAddr(reinterpret_cast(function_ptr)), + llvm::JITSymbolFlags::Exported); +#else + llvm::JITEvaluatedSymbol symbol(reinterpret_cast(function_ptr), + llvm::JITSymbolFlags::Exported); +#endif + + auto error = lljit_->getMainJITDylib().define( + llvm::orc::absoluteSymbols({{mangle(name), symbol}})); + llvm::cantFail(std::move(error)); } arrow::Status Engine::AddGlobalMappings() { diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index 4e6a6fe038c82..aecc4ceeb64d4 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include #include @@ -56,7 +57,8 @@ class GANDIVA_EXPORT Engine { /// \return arrow::Result containing the created engine static Result> Make( const std::shared_ptr& config, bool cached, - std::optional> object_cache = std::nullopt); + std::optional> object_cache = + std::nullopt); /// Add the function to the list of IR functions that need to be compiled. /// Compiling only the functions that are used by the module saves time. @@ -72,7 +74,7 @@ class GANDIVA_EXPORT Engine { void SetLLVMObjectCache(GandivaObjectCache& object_cache); /// Get the compiled function corresponding to the irfunction. - void* CompiledFunction(std::string& function); + Result CompiledFunction(std::string& function); // Create and add a mapping for the cpp function to make it accessible from LLVM. void AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type, diff --git a/cpp/src/gandiva/engine_llvm_test.cc b/cpp/src/gandiva/engine_llvm_test.cc index 2d74275c049df..ae7585fcf8d12 100644 --- a/cpp/src/gandiva/engine_llvm_test.cc +++ b/cpp/src/gandiva/engine_llvm_test.cc @@ -113,7 +113,8 @@ TEST_F(TestEngine, TestAddUnoptimised) { std::string fn_name = BuildVecAdd(engine.get()); ASSERT_OK(engine->FinalizeModule()); - auto add_func = reinterpret_cast(engine->CompiledFunction(fn_name)); + EXPECT_OK_AND_ASSIGN(auto fn_ptr, engine->CompiledFunction(fn_name)); + auto add_func = reinterpret_cast(fn_ptr); int64_t my_array[] = {1, 3, -5, 8, 10}; EXPECT_EQ(add_func(my_array, 5), 17); @@ -125,7 +126,8 @@ TEST_F(TestEngine, TestAddOptimised) { std::string fn_name = BuildVecAdd(engine.get()); ASSERT_OK(engine->FinalizeModule()); - auto add_func = reinterpret_cast(engine->CompiledFunction(fn_name)); + EXPECT_OK_AND_ASSIGN(auto fn_ptr, engine->CompiledFunction(fn_name)); + auto add_func = reinterpret_cast(fn_ptr); int64_t my_array[] = {1, 3, -5, 8, 10}; EXPECT_EQ(add_func(my_array, 5), 17); diff --git a/cpp/src/gandiva/llvm_generator.cc b/cpp/src/gandiva/llvm_generator.cc index 960fdb534def7..33621c716072d 100644 --- a/cpp/src/gandiva/llvm_generator.cc +++ b/cpp/src/gandiva/llvm_generator.cc @@ -103,7 +103,8 @@ Status LLVMGenerator::Build(const ExpressionVector& exprs, SelectionVector::Mode // setup the jit functions for each expression. for (auto& compiled_expr : compiled_exprs_) { auto fn_name = compiled_expr->GetFunctionName(mode); - auto jit_fn = reinterpret_cast(engine_->CompiledFunction(fn_name)); + ARROW_ASSIGN_OR_RAISE(auto fn_ptr, engine_->CompiledFunction(fn_name)); + auto jit_fn = reinterpret_cast(fn_ptr); compiled_expr->SetJITFunction(selection_vector_mode_, jit_fn); } diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index f35ebdd701660..d1c8995e98068 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -108,7 +108,8 @@ TEST_F(TestLLVMGenerator, TestAdd) { auto ir = generator->engine_->DumpIR(); EXPECT_THAT(ir, testing::HasSubstr("vector.body")); - EvalFunc eval_func = (EvalFunc)generator->engine_->CompiledFunction(fn_name); + EXPECT_OK_AND_ASSIGN(auto fn_ptr, generator->engine_->CompiledFunction(fn_name)); + auto eval_func = reinterpret_cast(fn_ptr); constexpr size_t kNumRecords = 4; std::array a0{1, 2, 3, 4}; From 37ad65a9897ce6a115d9d5173c91aa7a7cc7499c Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Wed, 6 Dec 2023 20:49:24 +0800 Subject: [PATCH 12/28] Include inttypes.h to avoid PRIxPTR not defined issue in CI. --- cpp/src/gandiva/engine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index aecc4ceeb64d4..cecfa08d1c709 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -17,7 +17,7 @@ #pragma once -#include +#include #include #include #include From cf7fb7889c2f4888deb69dca617e5929f9b68f0e Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Wed, 6 Dec 2023 21:41:22 +0800 Subject: [PATCH 13/28] Hide LLJIT into implementation to try to address the compilation issue. --- cpp/src/gandiva/engine.cc | 3 +++ cpp/src/gandiva/engine.h | 8 ++++++-- cpp/src/gandiva/llvm_generator_test.cc | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index c2a4e363cfadb..920ed957a7d6f 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -218,6 +219,8 @@ Engine::Engine(const std::shared_ptr& conf, target_machine_(std::move(target_machine)), conf_(conf) {} +Engine::~Engine() {} + Status Engine::Init() { std::call_once(register_exported_funcs_flag, gandiva::RegisterExportedFuncs); diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index cecfa08d1c709..7543d72b80b26 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -17,7 +17,7 @@ #pragma once -#include +#include #include #include #include @@ -26,7 +26,6 @@ #include #include -#include #include "arrow/util/logging.h" #include "arrow/util/macros.h" @@ -36,11 +35,16 @@ #include "gandiva/llvm_types.h" #include "gandiva/visibility.h" +namespace llvm::orc { +class LLJIT; +} // namespace llvm::orc + namespace gandiva { /// \brief LLVM Execution engine wrapper. class GANDIVA_EXPORT Engine { public: + ~Engine(); llvm::LLVMContext* context() { return context_.get(); } llvm::IRBuilder<>* ir_builder() { return ir_builder_.get(); } LLVMTypes* types() { return &types_; } diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index d1c8995e98068..a0ba8926bf97d 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -109,7 +109,7 @@ TEST_F(TestLLVMGenerator, TestAdd) { EXPECT_THAT(ir, testing::HasSubstr("vector.body")); EXPECT_OK_AND_ASSIGN(auto fn_ptr, generator->engine_->CompiledFunction(fn_name)); - auto eval_func = reinterpret_cast(fn_ptr); + auto eval_func = (EvalFunc)fn_ptr; constexpr size_t kNumRecords = 4; std::array a0{1, 2, 3, 4}; From 6e9af3578fe61ff72917f3a8046a4356f0bc8878 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Wed, 6 Dec 2023 22:21:10 +0800 Subject: [PATCH 14/28] Remove LLVM IR API from Gandiva's pyarrow binding since the configuration is not exposed so LLVM IR cannot be called. --- python/pyarrow/gandiva.pyx | 8 -------- python/pyarrow/tests/test_gandiva.py | 5 ----- 2 files changed, 13 deletions(-) diff --git a/python/pyarrow/gandiva.pyx b/python/pyarrow/gandiva.pyx index 35bbf5018f08a..360eb4e8be758 100644 --- a/python/pyarrow/gandiva.pyx +++ b/python/pyarrow/gandiva.pyx @@ -186,10 +186,6 @@ cdef class Projector(_Weakrefable): self.pool = pool return self - @property - def llvm_ir(self): - return self.projector.get().DumpIR().decode() - def evaluate(self, RecordBatch batch, SelectionVector selection=None): """ Evaluate the specified record batch and return the arrays at the @@ -235,10 +231,6 @@ cdef class Filter(_Weakrefable): self.filter = filter return self - @property - def llvm_ir(self): - return self.filter.get().DumpIR().decode() - def evaluate(self, RecordBatch batch, MemoryPool pool, dtype='int32'): """ Evaluate the specified record batch and return a selection vector. diff --git a/python/pyarrow/tests/test_gandiva.py b/python/pyarrow/tests/test_gandiva.py index 241cac4d83db4..23c26a73fa7df 100644 --- a/python/pyarrow/tests/test_gandiva.py +++ b/python/pyarrow/tests/test_gandiva.py @@ -50,9 +50,6 @@ def test_tree_exp_builder(): projector = gandiva.make_projector( schema, [expr], pa.default_memory_pool()) - # Gandiva generates compute kernel function named `@expr_X` - assert projector.llvm_ir.find("@expr_") != -1 - a = pa.array([10, 12, -20, 5], type=pa.int32()) b = pa.array([5, 15, 15, 17], type=pa.int32()) e = pa.array([10, 15, 15, 17], type=pa.int32()) @@ -105,8 +102,6 @@ def test_filter(): assert condition.result().type == pa.bool_() filter = gandiva.make_filter(table.schema, condition) - # Gandiva generates compute kernel function named `@expr_X` - assert filter.llvm_ir.find("@expr_") != -1 result = filter.evaluate(table.to_batches()[0], pa.default_memory_pool()) assert result.to_array().equals(pa.array(range(1000), type=pa.uint32())) From 3f9fd5b42c77b53eed54d36d85500d4b76adf8dd Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Thu, 7 Dec 2023 09:46:08 +0800 Subject: [PATCH 15/28] Use full qualified API to avoid compilation issue. --- cpp/src/gandiva/engine.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 920ed957a7d6f..838e258acf1f7 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -141,7 +141,7 @@ static std::string GetModuleIR(const llvm::Module& module) { // LLVM >= 18 does this automatically static void AddProcessSymbol(llvm::orc::LLJIT& lljit) { lljit.getMainJITDylib().addGenerator( - cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + llvm::cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( lljit.getDataLayout().getGlobalPrefix()))); } From 9948762631f26b4afd975603004b9f3dad1ca558 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Thu, 7 Dec 2023 09:46:40 +0800 Subject: [PATCH 16/28] Troubleshoot CI failure. --- cpp/src/gandiva/engine.cc | 11 ++++++++--- cpp/src/gandiva/engine.h | 2 +- cpp/src/gandiva/llvm_generator_test.cc | 4 +++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 838e258acf1f7..2aa4ebfe50b07 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -456,8 +456,9 @@ Status Engine::FinalizeModule() { return Status::OK(); } -Result Engine::CompiledFunction(std::string& function) { - DCHECK(module_finalized_); +Result Engine::CompiledFunction(const std::string& function) { + DCHECK(module_finalized_) + << "module must be finalized before getting compiled function"; auto sym = lljit_->lookup(function); if (!sym) { return Status::CodeGenError("Failed to look up function: " + function + @@ -469,7 +470,11 @@ Result Engine::CompiledFunction(std::string& function) { #else auto fn_addr = sym->getAddress(); #endif - return reinterpret_cast(fn_addr); + auto fn_ptr = reinterpret_cast(fn_addr); + if (fn_ptr == nullptr) { + return Status::CodeGenError("Failed to get address for function: " + function); + } + return fn_ptr; } void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type, diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index 7543d72b80b26..ac771678cc280 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -78,7 +78,7 @@ class GANDIVA_EXPORT Engine { void SetLLVMObjectCache(GandivaObjectCache& object_cache); /// Get the compiled function corresponding to the irfunction. - Result CompiledFunction(std::string& function); + Result CompiledFunction(const std::string& function); // Create and add a mapping for the cpp function to make it accessible from LLVM. void AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type, diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index a0ba8926bf97d..f8842b2f457ca 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -109,8 +109,9 @@ TEST_F(TestLLVMGenerator, TestAdd) { EXPECT_THAT(ir, testing::HasSubstr("vector.body")); EXPECT_OK_AND_ASSIGN(auto fn_ptr, generator->engine_->CompiledFunction(fn_name)); - auto eval_func = (EvalFunc)fn_ptr; + EXPECT_NE(fn_ptr, nullptr); + auto eval_func = (EvalFunc)fn_ptr; constexpr size_t kNumRecords = 4; std::array a0{1, 2, 3, 4}; std::array a1{5, 6, 7, 8}; @@ -125,6 +126,7 @@ TEST_F(TestLLVMGenerator, TestAdd) { reinterpret_cast(out.data()), reinterpret_cast(&out_bitmap), }; std::array addr_offsets{0, 0, 0, 0, 0, 0}; + eval_func(addrs.data(), addr_offsets.data(), nullptr, nullptr, nullptr, 0 /* dummy context ptr */, kNumRecords); From ba5d8e375baeae7ef071f5971d348162b7898402 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Fri, 8 Dec 2023 23:18:46 +0800 Subject: [PATCH 17/28] Rename function name to be different with the module name used by LLJIT, so that LLVM 10 won't return a 0-address function pointer when doing lookup. --- cpp/src/gandiva/engine.cc | 7 +++++-- cpp/src/gandiva/engine_llvm_test.cc | 2 +- cpp/src/gandiva/llvm_generator_test.cc | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 2aa4ebfe50b07..e6d8b12189d64 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -211,13 +211,16 @@ Engine::Engine(const std::shared_ptr& conf, : context_(std::make_unique()), lljit_(std::move(lljit)), ir_builder_(std::make_unique>(*context_)), - module_(std::make_unique("codegen", *context_)), types_(*context_), optimize_(conf->optimize()), cached_(cached), function_registry_(conf->function_registry()), target_machine_(std::move(target_machine)), - conf_(conf) {} + conf_(conf) { + // LLVM 10 doesn't like the expr function name to be the same as the module name + auto module_id = "gdv_module_" + std::to_string(reinterpret_cast(this)); + module_ = std::make_unique(module_id, *context_); +} Engine::~Engine() {} diff --git a/cpp/src/gandiva/engine_llvm_test.cc b/cpp/src/gandiva/engine_llvm_test.cc index ae7585fcf8d12..56c5d63e14c69 100644 --- a/cpp/src/gandiva/engine_llvm_test.cc +++ b/cpp/src/gandiva/engine_llvm_test.cc @@ -42,7 +42,7 @@ class TestEngine : public ::testing::Test { llvm::FunctionType::get(types->i64_type(), arguments, false /*isVarArg*/); // Create fn - std::string func_name = "add_longs"; + std::string func_name = "add_longs_test_expr"; gdv_engine->AddFunctionToCompile(func_name); llvm::Function* fn = llvm::Function::Create( prototype, llvm::GlobalValue::ExternalLinkage, func_name, gdv_engine->module()); diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index f8842b2f457ca..dcb5e63895b3b 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -98,7 +98,9 @@ TEST_F(TestLLVMGenerator, TestAdd) { auto field_sum = std::make_shared("out", arrow::int32()); auto desc_sum = annotator.CheckAndAddInputFieldDescriptor(field_sum); - std::string fn_name = "codegen"; + // LLVM 10 doesn't like the expr function name to be the same as the module name when + // LLJIT is used + std::string fn_name = "llvm_gen_test_add_expr"; ASSERT_OK(generator->engine_->LoadFunctionIRs()); ASSERT_OK(generator->CodeGenExprValue(func_dex, 4, desc_sum, 0, fn_name, From 7c12fdf21fd004d88058ccc55c8d0d2149c1801f Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sat, 9 Dec 2023 00:07:04 +0800 Subject: [PATCH 18/28] Minor tweaks for llvm generator and engine to simplify the code. --- cpp/src/gandiva/engine.cc | 5 ++--- cpp/src/gandiva/llvm_generator.cc | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index e6d8b12189d64..f39bad8f743d7 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -484,9 +484,8 @@ void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_ty const std::vector& args, void* function_ptr) { constexpr bool is_var_arg = false; - auto prototype = llvm::FunctionType::get(ret_type, args, is_var_arg); - constexpr auto linkage = llvm::GlobalValue::ExternalLinkage; - llvm::Function::Create(prototype, linkage, name, module()); + auto const prototype = llvm::FunctionType::get(ret_type, args, is_var_arg); + llvm::Function::Create(prototype, llvm::GlobalValue::ExternalLinkage, name, module()); llvm::orc::MangleAndInterner mangle(lljit_->getExecutionSession(), lljit_->getDataLayout()); diff --git a/cpp/src/gandiva/llvm_generator.cc b/cpp/src/gandiva/llvm_generator.cc index 33621c716072d..f4d69ae830c58 100644 --- a/cpp/src/gandiva/llvm_generator.cc +++ b/cpp/src/gandiva/llvm_generator.cc @@ -73,7 +73,7 @@ Status LLVMGenerator::Add(const ExpressionPtr expr, const FieldDescriptorPtr out ValueValidityPairPtr value_validity; ARROW_RETURN_NOT_OK(decomposer.Decompose(*expr->root(), &value_validity)); // Generate the IR function for the decomposed expression. - std::unique_ptr compiled_expr(new CompiledExpr(value_validity, output)); + auto compiled_expr = std::make_unique(value_validity, output); std::string fn_name = "expr_" + std::to_string(idx) + "_" + std::to_string(static_cast(selection_vector_mode_)); if (!cached_) { From 9f556ca00699a119737be4279d6ce2c47d35ef5c Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sat, 9 Dec 2023 14:26:12 +0800 Subject: [PATCH 19/28] Add a minimum atexit symbol lookup test for troubleshooting. --- cpp/src/gandiva/engine.cc | 42 +++++++++++++++++++++++++++++ cpp/src/gandiva/engine.h | 1 + cpp/src/gandiva/engine_llvm_test.cc | 5 ++++ 3 files changed, 48 insertions(+) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index f39bad8f743d7..b07ce75d65786 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -242,6 +242,48 @@ Status Engine::LoadFunctionIRs() { return Status::OK(); } +Status Engine::LookupFunctionTest() { + std::call_once(llvm_init_once_flag, InitOnce); + // llvm::InitializeNativeTarget(); + // llvm::InitializeNativeTargetAsmPrinter(); + // llvm::InitializeNativeTargetAsmParser(); + // llvm::InitializeNativeTargetDisassembler(); + // llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr); + // + // auto cpu_name = llvm::sys::getHostCPUName(); + + llvm::orc::JITTargetMachineBuilder jtmb( + (llvm::Triple(llvm::sys::getDefaultTargetTriple()))); + jtmb.setCPU(cpu_name.str()); + jtmb.setCodeGenOptLevel(llvm::CodeGenOpt::Aggressive); + auto jit_builder = llvm::orc::LLJITBuilder(); + jit_builder.setJITTargetMachineBuilder(std::move(jtmb)); + auto maybe_jit = jit_builder.create(); + ARROW_ASSIGN_OR_RAISE(auto jit, + AsArrowResult(maybe_jit, "Could not create LLJIT instance: ")); + + jit->getMainJITDylib().addGenerator( + llvm::cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + jit->getDataLayout().getGlobalPrefix()))); + const std::string function = "atexit"; + auto sym = jit->lookup(function); + if (!sym) { + return Status::CodeGenError("Failed to look up function: " + function + + " error: " + llvm::toString(sym.takeError())); + } + // Since LLVM 15, `LLJIT::lookup` returns ExecutorAddrs rather than JITEvaluatedSymbols +#if LLVM_VERSION_MAJOR >= 15 + auto fn_addr = sym->getValue(); +#else + auto fn_addr = sym->getAddress(); +#endif + auto fn_ptr = reinterpret_cast(fn_addr); + if (fn_ptr == nullptr) { + return Status::CodeGenError("Failed to get address for function: " + function); + } + return Status::OK(); +} + /// factory method to construct the engine. Result> Engine::Make( const std::shared_ptr& conf, bool cached, diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index ac771678cc280..a2923293f3356 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -44,6 +44,7 @@ namespace gandiva { /// \brief LLVM Execution engine wrapper. class GANDIVA_EXPORT Engine { public: + static Status LookupFunctionTest(); ~Engine(); llvm::LLVMContext* context() { return context_.get(); } llvm::IRBuilder<>* ir_builder() { return ir_builder_.get(); } diff --git a/cpp/src/gandiva/engine_llvm_test.cc b/cpp/src/gandiva/engine_llvm_test.cc index 56c5d63e14c69..8dfee65e00c64 100644 --- a/cpp/src/gandiva/engine_llvm_test.cc +++ b/cpp/src/gandiva/engine_llvm_test.cc @@ -133,4 +133,9 @@ TEST_F(TestEngine, TestAddOptimised) { EXPECT_EQ(add_func(my_array, 5), 17); } +TEST_F(TestEngine, TestLookup) { + auto status = gandiva::Engine::LookupFunctionTest(); + ARROW_EXPECT_OK(status); +} + } // namespace gandiva From 5627ab43bd9641afe56e629ab82a8d64145eabc7 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sat, 9 Dec 2023 16:47:30 +0800 Subject: [PATCH 20/28] Remove the lookup test case used for troubleshooting. --- cpp/src/gandiva/engine.cc | 42 ----------------------------- cpp/src/gandiva/engine.h | 1 - cpp/src/gandiva/engine_llvm_test.cc | 5 ---- 3 files changed, 48 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index b07ce75d65786..f39bad8f743d7 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -242,48 +242,6 @@ Status Engine::LoadFunctionIRs() { return Status::OK(); } -Status Engine::LookupFunctionTest() { - std::call_once(llvm_init_once_flag, InitOnce); - // llvm::InitializeNativeTarget(); - // llvm::InitializeNativeTargetAsmPrinter(); - // llvm::InitializeNativeTargetAsmParser(); - // llvm::InitializeNativeTargetDisassembler(); - // llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr); - // - // auto cpu_name = llvm::sys::getHostCPUName(); - - llvm::orc::JITTargetMachineBuilder jtmb( - (llvm::Triple(llvm::sys::getDefaultTargetTriple()))); - jtmb.setCPU(cpu_name.str()); - jtmb.setCodeGenOptLevel(llvm::CodeGenOpt::Aggressive); - auto jit_builder = llvm::orc::LLJITBuilder(); - jit_builder.setJITTargetMachineBuilder(std::move(jtmb)); - auto maybe_jit = jit_builder.create(); - ARROW_ASSIGN_OR_RAISE(auto jit, - AsArrowResult(maybe_jit, "Could not create LLJIT instance: ")); - - jit->getMainJITDylib().addGenerator( - llvm::cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( - jit->getDataLayout().getGlobalPrefix()))); - const std::string function = "atexit"; - auto sym = jit->lookup(function); - if (!sym) { - return Status::CodeGenError("Failed to look up function: " + function + - " error: " + llvm::toString(sym.takeError())); - } - // Since LLVM 15, `LLJIT::lookup` returns ExecutorAddrs rather than JITEvaluatedSymbols -#if LLVM_VERSION_MAJOR >= 15 - auto fn_addr = sym->getValue(); -#else - auto fn_addr = sym->getAddress(); -#endif - auto fn_ptr = reinterpret_cast(fn_addr); - if (fn_ptr == nullptr) { - return Status::CodeGenError("Failed to get address for function: " + function); - } - return Status::OK(); -} - /// factory method to construct the engine. Result> Engine::Make( const std::shared_ptr& conf, bool cached, diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index a2923293f3356..ac771678cc280 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -44,7 +44,6 @@ namespace gandiva { /// \brief LLVM Execution engine wrapper. class GANDIVA_EXPORT Engine { public: - static Status LookupFunctionTest(); ~Engine(); llvm::LLVMContext* context() { return context_.get(); } llvm::IRBuilder<>* ir_builder() { return ir_builder_.get(); } diff --git a/cpp/src/gandiva/engine_llvm_test.cc b/cpp/src/gandiva/engine_llvm_test.cc index 8dfee65e00c64..56c5d63e14c69 100644 --- a/cpp/src/gandiva/engine_llvm_test.cc +++ b/cpp/src/gandiva/engine_llvm_test.cc @@ -133,9 +133,4 @@ TEST_F(TestEngine, TestAddOptimised) { EXPECT_EQ(add_func(my_array, 5), 17); } -TEST_F(TestEngine, TestLookup) { - auto status = gandiva::Engine::LookupFunctionTest(); - ARROW_EXPECT_OK(status); -} - } // namespace gandiva From e2d89b67f859704b2e58f882f12961a620a79147 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sat, 9 Dec 2023 21:10:29 +0800 Subject: [PATCH 21/28] Set several variables to be const. --- cpp/src/gandiva/engine.cc | 10 +++++----- cpp/src/gandiva/llvm_generator_test.cc | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index f39bad8f743d7..a36fb05b97b89 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -124,7 +124,7 @@ static Result GetTargetMachineBuilder( jtmb.setCPU(cpu_name.str()); jtmb.addFeatures(cpu_attrs); } - auto opt_level = + auto const opt_level = conf.optimize() ? llvm::CodeGenOpt::Aggressive : llvm::CodeGenOpt::None; jtmb.setCodeGenOptLevel(opt_level); return jtmb; @@ -289,8 +289,8 @@ llvm::Module* Engine::module() { // Handling for pre-compiled IR libraries. Status Engine::LoadPreCompiledIR() { - auto bitcode = llvm::StringRef(reinterpret_cast(kPrecompiledBitcode), - kPrecompiledBitcodeSize); + auto const bitcode = llvm::StringRef(reinterpret_cast(kPrecompiledBitcode), + kPrecompiledBitcodeSize); /// Read from file into memory buffer. llvm::ErrorOr> buffer_or_error = @@ -312,8 +312,8 @@ Status Engine::LoadPreCompiledIR() { } static llvm::MemoryBufferRef AsLLVMMemoryBuffer(const arrow::Buffer& arrow_buffer) { - auto data = reinterpret_cast(arrow_buffer.data()); - auto size = arrow_buffer.size(); + auto const data = reinterpret_cast(arrow_buffer.data()); + auto const size = arrow_buffer.size(); return {llvm::StringRef(data, size), "external_bitcode"}; } diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index dcb5e63895b3b..1f6d8965f2c9b 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -113,7 +113,7 @@ TEST_F(TestLLVMGenerator, TestAdd) { EXPECT_OK_AND_ASSIGN(auto fn_ptr, generator->engine_->CompiledFunction(fn_name)); EXPECT_NE(fn_ptr, nullptr); - auto eval_func = (EvalFunc)fn_ptr; + auto eval_func = reinterpret_cast(fn_ptr); constexpr size_t kNumRecords = 4; std::array a0{1, 2, 3, 4}; std::array a1{5, 6, 7, 8}; From f5356e56891ac0f4769daae35a6dcfc9b64348db Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Wed, 13 Dec 2023 11:18:31 +0800 Subject: [PATCH 22/28] Add atexit symbol if it doesn't exist so that ASAN won't complain. --- cpp/src/gandiva/engine.cc | 70 ++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index a36fb05b97b89..4012e32f4351c 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -108,15 +108,15 @@ static std::vector cpu_attrs; std::once_flag register_exported_funcs_flag; template -static arrow::Result AsArrowResult(llvm::Expected& expected, - const std::string& error_context = "") { +arrow::Result AsArrowResult(llvm::Expected& expected, + const std::string& error_context = "") { if (!expected) { return Status::CodeGenError(error_context, llvm::toString(expected.takeError())); } return std::move(expected.get()); } -static Result GetTargetMachineBuilder( +Result GetTargetMachineBuilder( const Configuration& conf) { llvm::orc::JITTargetMachineBuilder jtmb( (llvm::Triple(llvm::sys::getDefaultTargetTriple()))); @@ -130,19 +130,46 @@ static Result GetTargetMachineBuilder( return jtmb; } -static std::string GetModuleIR(const llvm::Module& module) { +std::string GetModuleIR(const llvm::Module& module) { std::string ir; llvm::raw_string_ostream stream(ir); module.print(stream, nullptr); return ir; } +void AddAbsoluteSymbol(llvm::orc::LLJIT& lljit, const std::string& name, + void* function_ptr) { + llvm::orc::MangleAndInterner mangle(lljit.getExecutionSession(), lljit.getDataLayout()); + + // https://github.com/llvm/llvm-project/commit/8b1771bd9f304be39d4dcbdcccedb6d3bcd18200#diff-77984a824d9182e5c67a481740f3bc5da78d5bd4cf6e1716a083ddb30a4a4931 + // LLVM 17 introduced ExecutorSymbolDef and move most of ORC APIs to ExecutorAddr +#if LLVM_VERSION_MAJOR >= 17 + llvm::orc::ExecutorSymbolDef symbol( + llvm::orc::ExecutorAddr(reinterpret_cast(function_ptr)), + llvm::JITSymbolFlags::Exported); +#else + llvm::JITEvaluatedSymbol symbol(reinterpret_cast(function_ptr), + llvm::JITSymbolFlags::Exported); +#endif + + auto error = lljit.getMainJITDylib().define( + llvm::orc::absoluteSymbols({{mangle(name), symbol}})); + llvm::cantFail(std::move(error)); +} + // add current process symbol to dylib // LLVM >= 18 does this automatically -static void AddProcessSymbol(llvm::orc::LLJIT& lljit) { +void AddProcessSymbol(llvm::orc::LLJIT& lljit) { lljit.getMainJITDylib().addGenerator( llvm::cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( lljit.getDataLayout().getGlobalPrefix()))); +// the `atexit` symbol cannot be found for ASAN +#if defined(__SANITIZE_ADDRESS__) || \ + (defined(__has_feature) && (__has_feature(address_sanitizer))) + if (!lljit.lookup("atexit")) { + AddAbsoluteSymbol(lljit, "atexit", reinterpret_cast(atexit)); + } +#endif } static Result> BuildJIT( @@ -176,7 +203,7 @@ void Engine::SetLLVMObjectCache(GandivaObjectCache& object_cache) { auto cached_buffer = object_cache.getObject(nullptr); if (cached_buffer) { auto error = lljit_->addObjectFile(std::move(cached_buffer)); - DCHECK(!error) << "Failed to load object cache" << llvm::toString(std::move(error)); + DCHECK(!error) << "Failed to load object cache: " << llvm::toString(std::move(error)); } } @@ -440,13 +467,13 @@ Status Engine::FinalizeModule() { ARROW_RETURN_IF(llvm::verifyModule(*module_, &llvm::errs()), Status::CodeGenError("Module verification failed after optimizer")); - // copy the module if IR dumping is needed since the module will be moved to construct - // LLJIT instance, and it is not available after LLJIT instance is constructed + // print the module IR and save it for later use if IR dumping is needed + // since the module will be moved to construct LLJIT instance, and it is not available + // after LLJIT instance is constructed if (conf_->needs_ir_dumping()) { module_ir_ = GetModuleIR(*module_); } - // do the compilation llvm::orc::ThreadSafeModule tsm(std::move(module_), std::move(context_)); auto error = lljit_->addIRModule(std::move(tsm)); if (error) { @@ -481,29 +508,10 @@ Result Engine::CompiledFunction(const std::string& function) { } void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type, - const std::vector& args, - void* function_ptr) { - constexpr bool is_var_arg = false; - auto const prototype = llvm::FunctionType::get(ret_type, args, is_var_arg); + const std::vector& args, void* func) { + auto const prototype = llvm::FunctionType::get(ret_type, args, /*is_var_arg*/ false); llvm::Function::Create(prototype, llvm::GlobalValue::ExternalLinkage, name, module()); - - llvm::orc::MangleAndInterner mangle(lljit_->getExecutionSession(), - lljit_->getDataLayout()); - - // https://github.com/llvm/llvm-project/commit/8b1771bd9f304be39d4dcbdcccedb6d3bcd18200#diff-77984a824d9182e5c67a481740f3bc5da78d5bd4cf6e1716a083ddb30a4a4931 - // LLVM 17 introduced ExecutorSymbolDef and move most of ORC APIs to ExecutorAddr -#if LLVM_VERSION_MAJOR >= 17 - llvm::orc::ExecutorSymbolDef symbol( - llvm::orc::ExecutorAddr(reinterpret_cast(function_ptr)), - llvm::JITSymbolFlags::Exported); -#else - llvm::JITEvaluatedSymbol symbol(reinterpret_cast(function_ptr), - llvm::JITSymbolFlags::Exported); -#endif - - auto error = lljit_->getMainJITDylib().define( - llvm::orc::absoluteSymbols({{mangle(name), symbol}})); - llvm::cantFail(std::move(error)); + AddAbsoluteSymbol(*lljit_, name, func); } arrow::Status Engine::AddGlobalMappings() { From 31a4b09279342f93e644933a47d7c453cc4dd936 Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Fri, 15 Dec 2023 18:42:53 +0800 Subject: [PATCH 23/28] Switch to use JITLink for LLVM 9+ to see if it addresses the ASAN issue. --- cpp/src/gandiva/engine.cc | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 4012e32f4351c..e2ac014884458 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -78,6 +78,7 @@ #include #include #endif +#include #include #include #include @@ -163,21 +164,37 @@ void AddProcessSymbol(llvm::orc::LLJIT& lljit) { lljit.getMainJITDylib().addGenerator( llvm::cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( lljit.getDataLayout().getGlobalPrefix()))); -// the `atexit` symbol cannot be found for ASAN -#if defined(__SANITIZE_ADDRESS__) || \ - (defined(__has_feature) && (__has_feature(address_sanitizer))) - if (!lljit.lookup("atexit")) { - AddAbsoluteSymbol(lljit, "atexit", reinterpret_cast(atexit)); - } +} + +// JITLink is available in LLVM 9+ +// but the `InProcessMemoryManager::Create` API was added since LLVM 14 +#if LLVM_VERSION_MAJOR >= 14 +#define USE_JIT_LINK 1 #endif + +Result> CreateMemmoryManager() { + auto maybe_mem_manager = llvm::jitlink::InProcessMemoryManager::Create(); + ARROW_ASSIGN_OR_RAISE( + auto memory_manager, + AsArrowResult(maybe_mem_manager, "Could not create memory manager: ")); + return memory_manager; } -static Result> BuildJIT( +Result> BuildJIT( llvm::orc::JITTargetMachineBuilder jtmb, std::optional>& object_cache) { auto jit_builder = llvm::orc::LLJITBuilder(); jit_builder.setJITTargetMachineBuilder(std::move(jtmb)); + +#if USE_JIT_LINK + ARROW_ASSIGN_OR_RAISE(static auto memory_manager, CreateMemmoryManager()); + jit_builder.setObjectLinkingLayerCreator( + [&](llvm::orc::ExecutionSession& ES, const llvm::Triple& TT) { + return std::make_unique(ES, *memory_manager); + }); +#endif + if (object_cache.has_value()) { jit_builder.setCompileFunctionCreator( [&object_cache](llvm::orc::JITTargetMachineBuilder JTMB) From 746247357285cc674fe64897bab625492c0c377f Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Fri, 15 Dec 2023 20:51:23 +0800 Subject: [PATCH 24/28] Make JITLink defaults to off, and add a benchmark for expression compilation. --- cpp/src/gandiva/engine.cc | 47 +++++++++++++++-------- cpp/src/gandiva/tests/micro_benchmarks.cc | 31 +++++++++++++++ 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index e2ac014884458..21f586638c083 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -31,6 +31,7 @@ #include #include +#include #include "arrow/util/logging.h" #if defined(_MSC_VER) @@ -78,7 +79,6 @@ #include #include #endif -#include #include #include #include @@ -88,6 +88,13 @@ #include #include +// JITLink is available in LLVM 9+ +// but the `InProcessMemoryManager::Create` API was added since LLVM 14 +#if LLVM_VERSION_MAJOR >= 14 && !defined(_WIN32) && !defined(_WIN64) +#define JIT_LINK_SUPPORTED 1 +#include +#endif + #if defined(_MSC_VER) #pragma warning(pop) #endif @@ -164,14 +171,16 @@ void AddProcessSymbol(llvm::orc::LLJIT& lljit) { lljit.getMainJITDylib().addGenerator( llvm::cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( lljit.getDataLayout().getGlobalPrefix()))); -} - -// JITLink is available in LLVM 9+ -// but the `InProcessMemoryManager::Create` API was added since LLVM 14 -#if LLVM_VERSION_MAJOR >= 14 -#define USE_JIT_LINK 1 + // the `atexit` symbol cannot be found for ASAN +#if defined(__SANITIZE_ADDRESS__) || \ + (defined(__has_feature) && (__has_feature(address_sanitizer))) + if (!lljit.lookup("atexit")) { + AddAbsoluteSymbol(lljit, "atexit", reinterpret_cast(atexit)); + } #endif +} +#if JIT_LINK_SUPPORTED Result> CreateMemmoryManager() { auto maybe_mem_manager = llvm::jitlink::InProcessMemoryManager::Create(); ARROW_ASSIGN_OR_RAISE( @@ -180,21 +189,29 @@ Result> CreateMemmoryMana return memory_manager; } +Status UseJitLinkIfEnabled(llvm::orc::LLJITBuilder& jit_builder) { + static auto maybe_use_jit_link = ::arrow::internal::GetEnvVar("GANDIVA_USE_JIT_LINK"); + if (maybe_use_jit_link.ok()) { + ARROW_ASSIGN_OR_RAISE(static auto memory_manager, CreateMemmoryManager()); + jit_builder.setObjectLinkingLayerCreator( + [&](llvm::orc::ExecutionSession& ES, const llvm::Triple& TT) { + return std::make_unique(ES, *memory_manager); + }); + } + return Status::OK(); +} +#endif + Result> BuildJIT( llvm::orc::JITTargetMachineBuilder jtmb, std::optional>& object_cache) { auto jit_builder = llvm::orc::LLJITBuilder(); - jit_builder.setJITTargetMachineBuilder(std::move(jtmb)); - -#if USE_JIT_LINK - ARROW_ASSIGN_OR_RAISE(static auto memory_manager, CreateMemmoryManager()); - jit_builder.setObjectLinkingLayerCreator( - [&](llvm::orc::ExecutionSession& ES, const llvm::Triple& TT) { - return std::make_unique(ES, *memory_manager); - }); +#if JIT_LINK_SUPPORTED + ARROW_RETURN_NOT_OK(UseJitLinkIfEnabled(jit_builder)); #endif + jit_builder.setJITTargetMachineBuilder(std::move(jtmb)); if (object_cache.has_value()) { jit_builder.setCompileFunctionCreator( [&object_cache](llvm::orc::JITTargetMachineBuilder JTMB) diff --git a/cpp/src/gandiva/tests/micro_benchmarks.cc b/cpp/src/gandiva/tests/micro_benchmarks.cc index f126b769b2010..450e691323cae 100644 --- a/cpp/src/gandiva/tests/micro_benchmarks.cc +++ b/cpp/src/gandiva/tests/micro_benchmarks.cc @@ -16,6 +16,7 @@ // under the License. #include + #include "arrow/memory_pool.h" #include "arrow/status.h" #include "arrow/testing/gtest_util.h" @@ -420,6 +421,35 @@ static void DoDecimalAdd2(benchmark::State& state, int32_t precision, int32_t sc ASSERT_OK(status); } +static void TimedTestExprCompilation(benchmark::State& state) { + int64_t iteration = 0; + for (auto _ : state) { + // schema for input fields + auto field0 = field("f0", int64()); + auto field1 = field("f1", int64()); + auto literal = TreeExprBuilder::MakeLiteral(iteration); + auto schema = arrow::schema({field0, field1}); + + // output field + auto field_add = field("c1", int64()); + auto field_less_than = field("c2", boolean()); + + // Build expression + auto add_func = TreeExprBuilder::MakeFunction( + "add", {TreeExprBuilder::MakeField(field0), literal}, int64()); + auto less_than_func = TreeExprBuilder::MakeFunction( + "less_than", {TreeExprBuilder::MakeField(field1), literal}, boolean()); + + auto expr_0 = TreeExprBuilder::MakeExpression(add_func, field_add); + auto expr_1 = TreeExprBuilder::MakeExpression(less_than_func, field_less_than); + + std::shared_ptr projector; + ASSERT_OK(Projector::Make(schema, {expr_0, expr_1}, TestConfiguration(), &projector)); + + ++iteration; + } +} + static void DecimalAdd2Fast(benchmark::State& state) { // use lesser precision to test the fast-path DoDecimalAdd2(state, DecimalTypeUtil::kMaxPrecision - 6, 18); @@ -460,6 +490,7 @@ static void DecimalAdd3Large(benchmark::State& state) { DoDecimalAdd3(state, DecimalTypeUtil::kMaxPrecision, 18, true); } +BENCHMARK(TimedTestExprCompilation)->Unit(benchmark::kMicrosecond); BENCHMARK(TimedTestAdd3)->Unit(benchmark::kMicrosecond); BENCHMARK(TimedTestBigNested)->Unit(benchmark::kMicrosecond); BENCHMARK(TimedTestExtractYear)->Unit(benchmark::kMicrosecond); From 3213dbf46d239d878d949dec07c06af162c1be6c Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sat, 16 Dec 2023 23:52:17 +0800 Subject: [PATCH 25/28] Rename configuration to `dump_ir` and return Status for the SetLLVMObjectCache API. --- cpp/src/gandiva/configuration.h | 16 ++++---- cpp/src/gandiva/engine.cc | 55 +++++++++++++------------- cpp/src/gandiva/engine.h | 4 +- cpp/src/gandiva/engine_llvm_test.cc | 2 +- cpp/src/gandiva/filter.cc | 4 +- cpp/src/gandiva/filter.h | 2 +- cpp/src/gandiva/llvm_generator.cc | 4 +- cpp/src/gandiva/llvm_generator.h | 4 +- cpp/src/gandiva/llvm_generator_test.cc | 6 +-- cpp/src/gandiva/projector.cc | 4 +- cpp/src/gandiva/projector.h | 2 +- 11 files changed, 51 insertions(+), 52 deletions(-) diff --git a/cpp/src/gandiva/configuration.h b/cpp/src/gandiva/configuration.h index 6f1f5d1cf3fa6..620c58537f963 100644 --- a/cpp/src/gandiva/configuration.h +++ b/cpp/src/gandiva/configuration.h @@ -38,11 +38,11 @@ class GANDIVA_EXPORT Configuration { explicit Configuration(bool optimize, std::shared_ptr function_registry = gandiva::default_function_registry(), - bool needs_ir_dumping = false) + bool dump_ir = false) : optimize_(optimize), target_host_cpu_(true), function_registry_(std::move(function_registry)), - needs_ir_dumping_(needs_ir_dumping) {} + dump_ir_(dump_ir) {} Configuration() : Configuration(true) {} @@ -52,15 +52,13 @@ class GANDIVA_EXPORT Configuration { bool optimize() const { return optimize_; } bool target_host_cpu() const { return target_host_cpu_; } - bool needs_ir_dumping() const { return needs_ir_dumping_; } + bool dump_ir() const { return dump_ir_; } std::shared_ptr function_registry() const { return function_registry_; } void set_optimize(bool optimize) { optimize_ = optimize; } - void set_needs_ir_dumping(bool needs_ir_dumping) { - needs_ir_dumping_ = needs_ir_dumping; - } + void set_dump_ir(bool dump_ir) { dump_ir_ = dump_ir; } void target_host_cpu(bool target_host_cpu) { target_host_cpu_ = target_host_cpu; } void set_function_registry(std::shared_ptr function_registry) { function_registry_ = std::move(function_registry); @@ -73,7 +71,7 @@ class GANDIVA_EXPORT Configuration { function_registry_; /* function registry that may contain external functions */ // flag indicating if IR dumping is needed, defaults to false, and turning it on will // negatively affect performance - bool needs_ir_dumping_ = false; + bool dump_ir_ = false; }; /// \brief configuration builder for gandiva @@ -92,9 +90,9 @@ class GANDIVA_EXPORT ConfigurationBuilder { return configuration; } - std::shared_ptr build_with_ir_dumping(bool needs_ir_dumping) { + std::shared_ptr build_with_ir_dumping(bool dump_ir) { std::shared_ptr configuration( - new Configuration(true, gandiva::default_function_registry(), needs_ir_dumping)); + new Configuration(true, gandiva::default_function_registry(), dump_ir)); return configuration; } diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 21f586638c083..048a54c2e721c 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -91,7 +91,7 @@ // JITLink is available in LLVM 9+ // but the `InProcessMemoryManager::Create` API was added since LLVM 14 #if LLVM_VERSION_MAJOR >= 14 && !defined(_WIN32) && !defined(_WIN64) -#define JIT_LINK_SUPPORTED 1 +#define JIT_LINK_SUPPORTED #include #endif @@ -124,7 +124,7 @@ arrow::Result AsArrowResult(llvm::Expected& expected, return std::move(expected.get()); } -Result GetTargetMachineBuilder( +Result MakeTargetMachineBuilder( const Configuration& conf) { llvm::orc::JITTargetMachineBuilder jtmb( (llvm::Triple(llvm::sys::getDefaultTargetTriple()))); @@ -138,7 +138,7 @@ Result GetTargetMachineBuilder( return jtmb; } -std::string GetModuleIR(const llvm::Module& module) { +std::string DumpModuleIR(const llvm::Module& module) { std::string ir; llvm::raw_string_ostream stream(ir); module.print(stream, nullptr); @@ -172,24 +172,20 @@ void AddProcessSymbol(llvm::orc::LLJIT& lljit) { llvm::cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( lljit.getDataLayout().getGlobalPrefix()))); // the `atexit` symbol cannot be found for ASAN -#if defined(__SANITIZE_ADDRESS__) || \ - (defined(__has_feature) && (__has_feature(address_sanitizer))) +#ifdef ADDRESS_SANITIZER if (!lljit.lookup("atexit")) { AddAbsoluteSymbol(lljit, "atexit", reinterpret_cast(atexit)); } #endif } -#if JIT_LINK_SUPPORTED +#ifdef JIT_LINK_SUPPORTED Result> CreateMemmoryManager() { auto maybe_mem_manager = llvm::jitlink::InProcessMemoryManager::Create(); - ARROW_ASSIGN_OR_RAISE( - auto memory_manager, - AsArrowResult(maybe_mem_manager, "Could not create memory manager: ")); - return memory_manager; + return AsArrowResult(maybe_mem_manager, "Could not create memory manager: "); } -Status UseJitLinkIfEnabled(llvm::orc::LLJITBuilder& jit_builder) { +Status UseJITLinkIfEnabled(llvm::orc::LLJITBuilder& jit_builder) { static auto maybe_use_jit_link = ::arrow::internal::GetEnvVar("GANDIVA_USE_JIT_LINK"); if (maybe_use_jit_link.ok()) { ARROW_ASSIGN_OR_RAISE(static auto memory_manager, CreateMemmoryManager()); @@ -205,10 +201,10 @@ Status UseJitLinkIfEnabled(llvm::orc::LLJITBuilder& jit_builder) { Result> BuildJIT( llvm::orc::JITTargetMachineBuilder jtmb, std::optional>& object_cache) { - auto jit_builder = llvm::orc::LLJITBuilder(); + llvm::orc::LLJITBuilder jit_builder; -#if JIT_LINK_SUPPORTED - ARROW_RETURN_NOT_OK(UseJitLinkIfEnabled(jit_builder)); +#ifdef JIT_LINK_SUPPORTED + ARROW_RETURN_NOT_OK(UseJITLinkIfEnabled(jit_builder)); #endif jit_builder.setJITTargetMachineBuilder(std::move(jtmb)); @@ -220,7 +216,8 @@ Result> BuildJIT( if (!target_machine) { return target_machine.takeError(); } - // after compilation, the object code will be stored into the given object cache + // after compilation, the object code will be stored into the given object + // cache return std::make_unique( std::move(*target_machine), &object_cache.value().get()); }); @@ -233,12 +230,16 @@ Result> BuildJIT( return jit; } -void Engine::SetLLVMObjectCache(GandivaObjectCache& object_cache) { +Status Engine::SetLLVMObjectCache(GandivaObjectCache& object_cache) { auto cached_buffer = object_cache.getObject(nullptr); if (cached_buffer) { auto error = lljit_->addObjectFile(std::move(cached_buffer)); - DCHECK(!error) << "Failed to load object cache: " << llvm::toString(std::move(error)); + if (error) { + return Status::CodeGenError("Failed to add cached object file to LLJIT: ", + llvm::toString(std::move(error))); + } } + return Status::OK(); } void Engine::InitOnce() { @@ -279,7 +280,7 @@ Engine::Engine(const std::shared_ptr& conf, target_machine_(std::move(target_machine)), conf_(conf) { // LLVM 10 doesn't like the expr function name to be the same as the module name - auto module_id = "gdv_module_" + std::to_string(reinterpret_cast(this)); + auto module_id = "gdv_module_" + std::to_string(reinterpret_cast(this)); module_ = std::make_unique(module_id, *context_); } @@ -309,7 +310,7 @@ Result> Engine::Make( std::optional> object_cache) { std::call_once(llvm_init_once_flag, InitOnce); - ARROW_ASSIGN_OR_RAISE(auto jtmb, GetTargetMachineBuilder(*conf)); + ARROW_ASSIGN_OR_RAISE(auto jtmb, MakeTargetMachineBuilder(*conf)); ARROW_ASSIGN_OR_RAISE(auto jit, BuildJIT(jtmb, object_cache)); auto maybe_tm = jtmb.createTargetMachine(); ARROW_ASSIGN_OR_RAISE(auto target_machine, @@ -502,10 +503,10 @@ Status Engine::FinalizeModule() { Status::CodeGenError("Module verification failed after optimizer")); // print the module IR and save it for later use if IR dumping is needed - // since the module will be moved to construct LLJIT instance, and it is not available - // after LLJIT instance is constructed - if (conf_->needs_ir_dumping()) { - module_ir_ = GetModuleIR(*module_); + // since the module will be moved to construct LLJIT instance, and it is not + // available after LLJIT instance is constructed + if (conf_->dump_ir()) { + module_ir_ = DumpModuleIR(*module_); } llvm::orc::ThreadSafeModule tsm(std::move(module_), std::move(context_)); @@ -528,7 +529,8 @@ Result Engine::CompiledFunction(const std::string& function) { return Status::CodeGenError("Failed to look up function: " + function + " error: " + llvm::toString(sym.takeError())); } - // Since LLVM 15, `LLJIT::lookup` returns ExecutorAddrs rather than JITEvaluatedSymbols + // Since LLVM 15, `LLJIT::lookup` returns ExecutorAddrs rather than + // JITEvaluatedSymbols #if LLVM_VERSION_MAJOR >= 15 auto fn_addr = sym->getValue(); #else @@ -554,9 +556,8 @@ arrow::Status Engine::AddGlobalMappings() { return c_funcs.AddMappings(this); } -std::string Engine::DumpIR() { - DCHECK(!module_ir_.empty()) - << "needs_ir_dumping in Configuration must be set for dumping IR"; +const std::string& Engine::ir() { + DCHECK(!module_ir_.empty()) << "dump_ir in Configuration must be set for dumping IR"; return module_ir_; } diff --git a/cpp/src/gandiva/engine.h b/cpp/src/gandiva/engine.h index ac771678cc280..565c3f142502d 100644 --- a/cpp/src/gandiva/engine.h +++ b/cpp/src/gandiva/engine.h @@ -75,7 +75,7 @@ class GANDIVA_EXPORT Engine { Status FinalizeModule(); /// Set LLVM ObjectCache. - void SetLLVMObjectCache(GandivaObjectCache& object_cache); + Status SetLLVMObjectCache(GandivaObjectCache& object_cache); /// Get the compiled function corresponding to the irfunction. Result CompiledFunction(const std::string& function); @@ -85,7 +85,7 @@ class GANDIVA_EXPORT Engine { const std::vector& args, void* func); /// Return the generated IR for the module. - std::string DumpIR(); + const std::string& ir(); /// Load the function IRs that can be accessed in the module. Status LoadFunctionIRs(); diff --git a/cpp/src/gandiva/engine_llvm_test.cc b/cpp/src/gandiva/engine_llvm_test.cc index 56c5d63e14c69..78f468d13fa1f 100644 --- a/cpp/src/gandiva/engine_llvm_test.cc +++ b/cpp/src/gandiva/engine_llvm_test.cc @@ -113,7 +113,7 @@ TEST_F(TestEngine, TestAddUnoptimised) { std::string fn_name = BuildVecAdd(engine.get()); ASSERT_OK(engine->FinalizeModule()); - EXPECT_OK_AND_ASSIGN(auto fn_ptr, engine->CompiledFunction(fn_name)); + ASSERT_OK_AND_ASSIGN(auto fn_ptr, engine->CompiledFunction(fn_name)); auto add_func = reinterpret_cast(fn_ptr); int64_t my_array[] = {1, 3, -5, 8, 10}; diff --git a/cpp/src/gandiva/filter.cc b/cpp/src/gandiva/filter.cc index 2e9952089e948..8a270cfdc06f2 100644 --- a/cpp/src/gandiva/filter.cc +++ b/cpp/src/gandiva/filter.cc @@ -77,7 +77,7 @@ Status Filter::Make(SchemaPtr schema, ConditionPtr condition, } // Set the object cache for LLVM - llvm_gen->SetLLVMObjectCache(obj_cache); + ARROW_RETURN_NOT_OK(llvm_gen->SetLLVMObjectCache(obj_cache)); ARROW_RETURN_NOT_OK(llvm_gen->Build({condition}, SelectionVector::Mode::MODE_NONE)); @@ -119,7 +119,7 @@ Status Filter::Evaluate(const arrow::RecordBatch& batch, return out_selection->PopulateFromBitMap(result, bitmap_size, num_rows - 1); } -std::string Filter::DumpIR() { return llvm_generator_->DumpIR(); } +const std::string& Filter::DumpIR() { return llvm_generator_->ir(); } void Filter::SetBuiltFromCache(bool flag) { built_from_cache_ = flag; } diff --git a/cpp/src/gandiva/filter.h b/cpp/src/gandiva/filter.h index cc536bca1bb3d..b4043d93c857a 100644 --- a/cpp/src/gandiva/filter.h +++ b/cpp/src/gandiva/filter.h @@ -76,7 +76,7 @@ class GANDIVA_EXPORT Filter { Status Evaluate(const arrow::RecordBatch& batch, std::shared_ptr out_selection); - std::string DumpIR(); + const std::string& DumpIR(); void SetBuiltFromCache(bool flag); diff --git a/cpp/src/gandiva/llvm_generator.cc b/cpp/src/gandiva/llvm_generator.cc index f4d69ae830c58..62ebab08f4d6b 100644 --- a/cpp/src/gandiva/llvm_generator.cc +++ b/cpp/src/gandiva/llvm_generator.cc @@ -62,8 +62,8 @@ LLVMGenerator::GetCache() { return shared_cache; } -void LLVMGenerator::SetLLVMObjectCache(GandivaObjectCache& object_cache) { - engine_->SetLLVMObjectCache(object_cache); +Status LLVMGenerator::SetLLVMObjectCache(GandivaObjectCache& object_cache) { + return engine_->SetLLVMObjectCache(object_cache); } Status LLVMGenerator::Add(const ExpressionPtr expr, const FieldDescriptorPtr output) { diff --git a/cpp/src/gandiva/llvm_generator.h b/cpp/src/gandiva/llvm_generator.h index a0e2dc712917d..0c532998e8b83 100644 --- a/cpp/src/gandiva/llvm_generator.h +++ b/cpp/src/gandiva/llvm_generator.h @@ -59,7 +59,7 @@ class GANDIVA_EXPORT LLVMGenerator { GetCache(); /// \brief Set LLVM ObjectCache. - void SetLLVMObjectCache(GandivaObjectCache& object_cache); + Status SetLLVMObjectCache(GandivaObjectCache& object_cache); /// \brief Build the code for the expression trees for default mode with a LLVM /// ObjectCache. Each element in the vector represents an expression tree @@ -83,7 +83,7 @@ class GANDIVA_EXPORT LLVMGenerator { SelectionVector::Mode selection_vector_mode() { return selection_vector_mode_; } LLVMTypes* types() { return engine_->types(); } llvm::Module* module() { return engine_->module(); } - std::string DumpIR() { return engine_->DumpIR(); } + const std::string& ir() { return engine_->ir(); } private: explicit LLVMGenerator(bool cached, diff --git a/cpp/src/gandiva/llvm_generator_test.cc b/cpp/src/gandiva/llvm_generator_test.cc index 1f6d8965f2c9b..79654e7b78c7e 100644 --- a/cpp/src/gandiva/llvm_generator_test.cc +++ b/cpp/src/gandiva/llvm_generator_test.cc @@ -107,11 +107,11 @@ TEST_F(TestLLVMGenerator, TestAdd) { SelectionVector::MODE_NONE)); ASSERT_OK(generator->engine_->FinalizeModule()); - auto ir = generator->engine_->DumpIR(); + auto const& ir = generator->engine_->ir(); EXPECT_THAT(ir, testing::HasSubstr("vector.body")); - EXPECT_OK_AND_ASSIGN(auto fn_ptr, generator->engine_->CompiledFunction(fn_name)); - EXPECT_NE(fn_ptr, nullptr); + ASSERT_OK_AND_ASSIGN(auto fn_ptr, generator->engine_->CompiledFunction(fn_name)); + ASSERT_TRUE(fn_ptr); auto eval_func = reinterpret_cast(fn_ptr); constexpr size_t kNumRecords = 4; diff --git a/cpp/src/gandiva/projector.cc b/cpp/src/gandiva/projector.cc index 5008207e0eb45..ec0302146fff5 100644 --- a/cpp/src/gandiva/projector.cc +++ b/cpp/src/gandiva/projector.cc @@ -95,7 +95,7 @@ Status Projector::Make(SchemaPtr schema, const ExpressionVector& exprs, } // Set the object cache for LLVM - llvm_gen->SetLLVMObjectCache(obj_cache); + ARROW_RETURN_NOT_OK(llvm_gen->SetLLVMObjectCache(obj_cache)); ARROW_RETURN_NOT_OK(llvm_gen->Build(exprs, selection_vector_mode)); @@ -281,7 +281,7 @@ Status Projector::ValidateArrayDataCapacity(const arrow::ArrayData& array_data, return Status::OK(); } -std::string Projector::DumpIR() { return llvm_generator_->DumpIR(); } +const std::string& Projector::DumpIR() { return llvm_generator_->ir(); } void Projector::SetBuiltFromCache(bool flag) { built_from_cache_ = flag; } diff --git a/cpp/src/gandiva/projector.h b/cpp/src/gandiva/projector.h index 6801a7c9f3f3c..f1ae7e4dc8ccd 100644 --- a/cpp/src/gandiva/projector.h +++ b/cpp/src/gandiva/projector.h @@ -118,7 +118,7 @@ class GANDIVA_EXPORT Projector { const SelectionVector* selection_vector, const ArrayDataVector& output) const; - std::string DumpIR(); + const std::string& DumpIR(); void SetBuiltFromCache(bool flag); From 7177d7f0c6a7c9c84d95b86ae08eafd657fef1fa Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Sun, 17 Dec 2023 11:25:59 +0800 Subject: [PATCH 26/28] Add a gandiva Configuration class for the python binding so that dumping IR can be done. --- python/pyarrow/gandiva.pyx | 67 ++++++++++++++++++++++++-- python/pyarrow/includes/libgandiva.pxd | 14 +++++- python/pyarrow/tests/test_gandiva.py | 11 ++++- 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/python/pyarrow/gandiva.pyx b/python/pyarrow/gandiva.pyx index 360eb4e8be758..2202ec64f2962 100644 --- a/python/pyarrow/gandiva.pyx +++ b/python/pyarrow/gandiva.pyx @@ -36,6 +36,7 @@ from pyarrow.includes.libgandiva cimport ( CNode, CProjector, CFilter, CSelectionVector, _ensure_selection_mode, + CConfiguration, CConfigurationBuilder, TreeExprBuilder_MakeExpression, TreeExprBuilder_MakeFunction, @@ -186,6 +187,10 @@ cdef class Projector(_Weakrefable): self.pool = pool return self + @property + def llvm_ir(self): + return self.projector.get().DumpIR().decode() + def evaluate(self, RecordBatch batch, SelectionVector selection=None): """ Evaluate the specified record batch and return the arrays at the @@ -231,6 +236,10 @@ cdef class Filter(_Weakrefable): self.filter = filter return self + @property + def llvm_ir(self): + return self.filter.get().DumpIR().decode() + def evaluate(self, RecordBatch batch, MemoryPool pool, dtype='int32'): """ Evaluate the specified record batch and return a selection vector. @@ -575,9 +584,47 @@ cdef class TreeExprBuilder(_Weakrefable): condition.node) return Condition.create(r) +cdef class Configuration(_Weakrefable): + cdef: + shared_ptr[CConfiguration] configuration + + def __cinit__(self, bint optimize=True, bint dump_ir=False): + """ + Initialize the configuration with specified options. + + Parameters + ---------- + optimize : bool, default True + Whether to enable optimizations. + dump_ir : bool, default False + Whether to dump LLVM IR. + """ + self.configuration = CConfigurationBuilder().build() + self.configuration.get().set_optimize(optimize) + self.configuration.get().set_dump_ir(dump_ir) + + @staticmethod + cdef create(shared_ptr[CConfiguration] configuration): + """ + Create a Configuration instance from an existing CConfiguration pointer. + + Parameters + ---------- + configuration : shared_ptr[CConfiguration] + Existing CConfiguration pointer. + + Returns + ------- + Configuration instance + """ + cdef Configuration self = Configuration.__new__(Configuration) + self.configuration = configuration + return self + cpdef make_projector(Schema schema, children, MemoryPool pool, - str selection_mode="NONE"): + str selection_mode="NONE", + Configuration configuration=None): """ Construct a projection using expressions. @@ -594,6 +641,8 @@ cpdef make_projector(Schema schema, children, MemoryPool pool, Memory pool used to allocate output arrays. selection_mode : str, default "NONE" Possible values are NONE, UINT16, UINT32, UINT64. + configuration : pyarrow.gandiva.Configuration, default None + Configuration for the projector. Returns ------- @@ -604,6 +653,9 @@ cpdef make_projector(Schema schema, children, MemoryPool pool, c_vector[shared_ptr[CGandivaExpression]] c_children shared_ptr[CProjector] result + if configuration is None: + configuration = Configuration() + for child in children: if child is None: raise TypeError("Expressions must not be None") @@ -612,12 +664,13 @@ cpdef make_projector(Schema schema, children, MemoryPool pool, check_status( Projector_Make(schema.sp_schema, c_children, _ensure_selection_mode(selection_mode), - CConfigurationBuilder.DefaultConfiguration(), + configuration.configuration, &result)) return Projector.create(result, pool) -cpdef make_filter(Schema schema, Condition condition): +cpdef make_filter(Schema schema, Condition condition, + Configuration configuration=None): """ Construct a filter based on a condition. @@ -630,6 +683,8 @@ cpdef make_filter(Schema schema, Condition condition): Schema for the record batches, and the condition. condition : pyarrow.gandiva.Condition Filter condition. + configuration : pyarrow.gandiva.Configuration, default None + Configuration for the filter. Returns ------- @@ -638,8 +693,12 @@ cpdef make_filter(Schema schema, Condition condition): cdef shared_ptr[CFilter] result if condition is None: raise TypeError("Condition must not be None") + + if configuration is None: + configuration = Configuration() + check_status( - Filter_Make(schema.sp_schema, condition.condition, &result)) + Filter_Make(schema.sp_schema, condition.condition, configuration.configuration, &result)) return Filter.create(result) diff --git a/python/pyarrow/includes/libgandiva.pxd b/python/pyarrow/includes/libgandiva.pxd index fa3b72bad61be..7d76576bef2b9 100644 --- a/python/pyarrow/includes/libgandiva.pxd +++ b/python/pyarrow/includes/libgandiva.pxd @@ -252,6 +252,7 @@ cdef extern from "gandiva/filter.h" namespace "gandiva" nogil: cdef CStatus Filter_Make \ "gandiva::Filter::Make"( shared_ptr[CSchema] schema, shared_ptr[CCondition] condition, + shared_ptr[CConfiguration] configuration, shared_ptr[CFilter]* filter) cdef extern from "gandiva/function_signature.h" namespace "gandiva" nogil: @@ -278,9 +279,20 @@ cdef extern from "gandiva/expression_registry.h" namespace "gandiva" nogil: cdef extern from "gandiva/configuration.h" namespace "gandiva" nogil: cdef cppclass CConfiguration" gandiva::Configuration": - pass + + CConfiguration() + + CConfiguration(bint optimize, bint dump_ir) + + void set_optimize(bint optimize) + + void set_dump_ir(bint dump_ir) cdef cppclass CConfigurationBuilder \ " gandiva::ConfigurationBuilder": @staticmethod shared_ptr[CConfiguration] DefaultConfiguration() + + CConfigurationBuilder() + + shared_ptr[CConfiguration] build() diff --git a/python/pyarrow/tests/test_gandiva.py b/python/pyarrow/tests/test_gandiva.py index 23c26a73fa7df..80d119a48530d 100644 --- a/python/pyarrow/tests/test_gandiva.py +++ b/python/pyarrow/tests/test_gandiva.py @@ -47,8 +47,12 @@ def test_tree_exp_builder(): assert expr.result().type == pa.int32() + config = gandiva.Configuration(dump_ir=True) projector = gandiva.make_projector( - schema, [expr], pa.default_memory_pool()) + schema, [expr], pa.default_memory_pool(), "NONE", config) + + # Gandiva generates compute kernel function named `@expr_X` + assert projector.llvm_ir.find("@expr_") != -1 a = pa.array([10, 12, -20, 5], type=pa.int32()) b = pa.array([5, 15, 15, 17], type=pa.int32()) @@ -101,7 +105,10 @@ def test_filter(): assert condition.result().type == pa.bool_() - filter = gandiva.make_filter(table.schema, condition) + config = gandiva.Configuration(dump_ir=True) + filter = gandiva.make_filter(table.schema, condition, config) + # Gandiva generates compute kernel function named `@expr_X` + assert filter.llvm_ir.find("@expr_") != -1 result = filter.evaluate(table.to_batches()[0], pa.default_memory_pool()) assert result.to_array().equals(pa.array(range(1000), type=pa.uint32())) From db819dcaf280be050941f4dee1a2fecb897464be Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Mon, 18 Dec 2023 14:52:02 +0800 Subject: [PATCH 27/28] Remove WIN64 macro since WIN32 could be enough to skip Windows. --- cpp/src/gandiva/engine.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 048a54c2e721c..28ab15d1e6e85 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -90,7 +90,7 @@ // JITLink is available in LLVM 9+ // but the `InProcessMemoryManager::Create` API was added since LLVM 14 -#if LLVM_VERSION_MAJOR >= 14 && !defined(_WIN32) && !defined(_WIN64) +#if LLVM_VERSION_MAJOR >= 14 && !defined(_WIN32) #define JIT_LINK_SUPPORTED #include #endif From 8b88f6d453b08bb29a56d279cc70bc4ee69458ad Mon Sep 17 00:00:00 2001 From: Yue Ni Date: Thu, 21 Dec 2023 18:13:22 +0800 Subject: [PATCH 28/28] Require error_context for AsArrowResult API. --- cpp/src/gandiva/engine.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/gandiva/engine.cc b/cpp/src/gandiva/engine.cc index 28ab15d1e6e85..fc047f2ac0763 100644 --- a/cpp/src/gandiva/engine.cc +++ b/cpp/src/gandiva/engine.cc @@ -32,7 +32,7 @@ #include #include -#include "arrow/util/logging.h" +#include #if defined(_MSC_VER) #pragma warning(push) @@ -117,7 +117,7 @@ std::once_flag register_exported_funcs_flag; template arrow::Result AsArrowResult(llvm::Expected& expected, - const std::string& error_context = "") { + const std::string& error_context) { if (!expected) { return Status::CodeGenError(error_context, llvm::toString(expected.takeError())); }