blob: f0b768f5f43cd7cdde7fa472aa96fe0de1664b2c [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// TODO(wesm): LLVM 7 produces pesky C4244 that disable pragmas around the LLVM
// includes seem to not fix as with LLVM 6
#if defined(_MSC_VER)
#pragma warning(disable : 4244)
#endif
#include "gandiva/engine.h"
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_set>
#include <utility>
#include "arrow/util/logging.h"
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4141)
#pragma warning(disable : 4146)
#pragma warning(disable : 4244)
#pragma warning(disable : 4267)
#pragma warning(disable : 4624)
#endif
#include <llvm/Analysis/Passes.h>
#include <llvm/Analysis/TargetTransformInfo.h>
#include <llvm/Bitcode/BitcodeReader.h>
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/MCJIT.h>
#include <llvm/IR/DataLayout.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/IR/Verifier.h>
#include <llvm/Linker/Linker.h>
#include <llvm/MC/SubtargetFeature.h>
#include <llvm/Support/DynamicLibrary.h>
#include <llvm/Support/Host.h>
#include <llvm/Support/TargetRegistry.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
#include <llvm/Transforms/InstCombine/InstCombine.h>
#include <llvm/Transforms/Scalar.h>
#include <llvm/Transforms/Scalar/GVN.h>
#include <llvm/Transforms/Utils.h>
#include <llvm/Transforms/Vectorize.h>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#include "arrow/util/make_unique.h"
#include "gandiva/configuration.h"
#include "gandiva/decimal_ir.h"
#include "gandiva/exported_funcs_registry.h"
namespace gandiva {
extern const unsigned char kPrecompiledBitcode[];
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<std::string, 10> cpu_attrs;
void Engine::InitOnce() {
DCHECK_EQ(llvm_init, false);
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
llvm::InitializeNativeTargetAsmParser();
llvm::InitializeNativeTargetDisassembler();
llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr);
cpu_name = llvm::sys::getHostCPUName();
llvm::StringMap<bool> host_features;
std::string cpu_attrs_str;
if (llvm::sys::getHostCPUFeatures(host_features)) {
for (auto& f : host_features) {
std::string attr = f.second ? std::string("+") + f.first().str()
: std::string("-") + f.first().str();
cpu_attrs.push_back(attr);
cpu_attrs_str += " " + attr;
}
}
ARROW_LOG(INFO) << "Detected CPU Name : " << cpu_name.str();
ARROW_LOG(INFO) << "Detected CPU Features:" << cpu_attrs_str;
llvm_init = true;
}
Engine::Engine(const std::shared_ptr<Configuration>& conf,
std::unique_ptr<llvm::LLVMContext> ctx,
std::unique_ptr<llvm::ExecutionEngine> engine, llvm::Module* module)
: context_(std::move(ctx)),
execution_engine_(std::move(engine)),
ir_builder_(arrow::internal::make_unique<llvm::IRBuilder<>>(*context_)),
module_(module),
types_(*context_),
optimize_(conf->optimize()) {}
Status Engine::Init() {
// Add mappings for functions that can be accessed from LLVM/IR module.
AddGlobalMappings();
ARROW_RETURN_NOT_OK(LoadPreCompiledIR());
ARROW_RETURN_NOT_OK(DecimalIR::AddFunctions(this));
return Status::OK();
}
/// factory method to construct the engine.
Status Engine::Make(const std::shared_ptr<Configuration>& conf,
std::unique_ptr<Engine>* out) {
std::call_once(llvm_init_once_flag, InitOnce);
auto ctx = arrow::internal::make_unique<llvm::LLVMContext>();
auto module = arrow::internal::make_unique<llvm::Module>("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);
}
std::unique_ptr<llvm::ExecutionEngine> exec_engine{engine_builder.create()};
if (exec_engine == nullptr) {
return Status::CodeGenError("Could not instantiate llvm::ExecutionEngine: ",
builder_error);
}
std::unique_ptr<Engine> engine{
new Engine(conf, std::move(ctx), std::move(exec_engine), module_ptr)};
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<bool> host_features;
if (llvm::sys::getHostCPUFeatures(host_features)) {
for (auto& f : host_features) {
features.AddFeature(f.first(), f.second);
}
}
std::unique_ptr<llvm::TargetMachine> machine(
target->createTargetMachine(target_triple, cpu, features.getString(), {}, {}));
module->setDataLayout(machine->createDataLayout());
}
// end of the mofified method from MLIR
// Handling for pre-compiled IR libraries.
Status Engine::LoadPreCompiledIR() {
auto bitcode = llvm::StringRef(reinterpret_cast<const char*>(kPrecompiledBitcode),
kPrecompiledBitcodeSize);
/// Read from file into memory buffer.
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer_or_error =
llvm::MemoryBuffer::getMemBuffer(bitcode, "precompiled", false);
ARROW_RETURN_IF(!buffer_or_error,
Status::CodeGenError("Could not load module from IR: ",
buffer_or_error.getError().message()));
std::unique_ptr<llvm::MemoryBuffer> buffer = move(buffer_or_error.get());
/// Parse the IR module.
llvm::Expected<std::unique_ptr<llvm::Module>> module_or_error =
llvm::getOwningLazyBitcodeModule(move(buffer), *context());
if (!module_or_error) {
// NOTE: llvm::handleAllErrors() fails linking with RTTI-disabled LLVM builds
// (ARROW-5148)
std::string str;
llvm::raw_string_ostream stream(str);
stream << module_or_error.takeError();
return Status::CodeGenError(stream.str());
}
std::unique_ptr<llvm::Module> ir_module = move(module_or_error.get());
// set dataLayout
SetDataLayout(ir_module.get());
ARROW_RETURN_IF(llvm::verifyModule(*ir_module, &llvm::errs()),
Status::CodeGenError("verify of IR Module failed"));
ARROW_RETURN_IF(llvm::Linker::linkModules(*module_, move(ir_module)),
Status::CodeGenError("failed to link IR Modules"));
return Status::OK();
}
// Get rid of all functions that don't need to be compiled.
// This helps in reducing the overall compilation time. This pass is trivial,
// and is always done since the number of functions in gandiva is very high.
// (Adapted from Apache Impala)
//
// Done by marking all the unused functions as internal, and then, running
// a pass for dead code elimination.
Status Engine::RemoveUnusedFunctions() {
// Setup an optimiser pipeline
std::unique_ptr<llvm::legacy::PassManager> pass_manager(
new llvm::legacy::PassManager());
std::unordered_set<std::string> used_functions;
used_functions.insert(functions_to_compile_.begin(), functions_to_compile_.end());
pass_manager->add(
llvm::createInternalizePass([&used_functions](const llvm::GlobalValue& func) {
return (used_functions.find(func.getName().str()) != used_functions.end());
}));
pass_manager->add(llvm::createGlobalDCEPass());
pass_manager->run(*module_);
return Status::OK();
}
// Optimise and compile the module.
Status Engine::FinalizeModule() {
ARROW_RETURN_NOT_OK(RemoveUnusedFunctions());
if (optimize_) {
// misc passes to allow for inlining, vectorization, ..
std::unique_ptr<llvm::legacy::PassManager> pass_manager(
new llvm::legacy::PassManager());
llvm::TargetIRAnalysis target_analysis =
execution_engine_->getTargetMachine()->getTargetIRAnalysis();
pass_manager->add(llvm::createTargetTransformInfoWrapperPass(target_analysis));
pass_manager->add(llvm::createFunctionInliningPass());
pass_manager->add(llvm::createInstructionCombiningPass());
pass_manager->add(llvm::createPromoteMemoryToRegisterPass());
pass_manager->add(llvm::createGVNPass());
pass_manager->add(llvm::createNewGVNPass());
pass_manager->add(llvm::createCFGSimplificationPass());
pass_manager->add(llvm::createLoopVectorizePass());
pass_manager->add(llvm::createSLPVectorizerPass());
pass_manager->add(llvm::createGlobalOptimizerPass());
// run the optimiser
llvm::PassManagerBuilder pass_builder;
pass_builder.OptLevel = 3;
pass_builder.populateModulePassManager(*pass_manager);
pass_manager->run(*module_);
}
ARROW_RETURN_IF(llvm::verifyModule(*module_, &llvm::errs()),
Status::CodeGenError("Module verification failed after optimizer"));
// do the compilation
execution_engine_->finalizeObject();
module_finalized_ = true;
return Status::OK();
}
void* Engine::CompiledFunction(llvm::Function* irFunction) {
DCHECK(module_finalized_);
return execution_engine_->getPointerToFunction(irFunction);
}
void Engine::AddGlobalMappingForFunc(const std::string& name, llvm::Type* ret_type,
const std::vector<llvm::Type*>& 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;
auto fn = llvm::Function::Create(prototype, linkage, name, module());
execution_engine_->addGlobalMapping(fn, function_ptr);
}
void Engine::AddGlobalMappings() { ExportedFuncsRegistry::AddMappings(this); }
std::string Engine::DumpIR() {
std::string ir;
llvm::raw_string_ostream stream(ir);
module_->print(stream, nullptr);
return ir;
}
} // namespace gandiva