// 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/io_util.h>
#include <arrow/util/logging_internal.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/Orc/LLJIT.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/Transforms/Utils/Cloning.h>
#if LLVM_VERSION_MAJOR >= 17
#  include <llvm/TargetParser/SubtargetFeature.h>
#else
#  include <llvm/MC/SubtargetFeature.h>
#endif
#include <llvm/Passes/PassBuilder.h>
#include <llvm/Support/DynamicLibrary.h>
#if LLVM_VERSION_MAJOR >= 18
#  include <llvm/TargetParser/Host.h>
#else
#  include <llvm/Support/Host.h>
#endif
#include <llvm/Transforms/IPO/GlobalDCE.h>
#include <llvm/Transforms/IPO/Internalize.h>
#if LLVM_VERSION_MAJOR >= 14
#  include <llvm/IR/PassManager.h>
#  include <llvm/MC/TargetRegistry.h>
#  include <llvm/Passes/PassPlugin.h>
#  include <llvm/Transforms/IPO/GlobalOpt.h>
#  include <llvm/Transforms/Scalar/NewGVN.h>
#  include <llvm/Transforms/Scalar/SimplifyCFG.h>
#  include <llvm/Transforms/Utils/Mem2Reg.h>
#  include <llvm/Transforms/Vectorize/LoopVectorize.h>
#  include <llvm/Transforms/Vectorize/SLPVectorizer.h>
#else
#  include <llvm/Support/TargetRegistry.h>
#  include <llvm/Transforms/IPO/PassManagerBuilder.h>
#endif
#include <llvm/Support/TargetSelect.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/InstCombine/InstCombine.h>
#include <llvm/Transforms/Scalar.h>
#include <llvm/Transforms/Scalar/GVN.h>
#include <llvm/Transforms/Utils.h>
#if LLVM_VERSION_MAJOR <= 17
#  include <llvm/Transforms/Vectorize.h>
#endif

// JITLink is available in LLVM 9+
// but the `InProcessMemoryManager::Create` API was added since LLVM 14
#if LLVM_VERSION_MAJOR >= 14 && !defined(_WIN32)
#  define JIT_LINK_SUPPORTED
#  include <llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h>
#endif

#if defined(_MSC_VER)
#  pragma warning(pop)
#endif

#include "gandiva/configuration.h"
#include "gandiva/decimal_ir.h"
#include "gandiva/exported_funcs.h"
#include "gandiva/exported_funcs_registry.h"

namespace gandiva {

extern const unsigned char kPrecompiledBitcode[];
extern const size_t kPrecompiledBitcodeSize;

namespace {

std::once_flag llvm_init_once_flag;
bool llvm_init = false;
llvm::StringRef cpu_name;
std::vector<std::string> cpu_attrs;
std::once_flag register_exported_funcs_flag;

template <typename T>
arrow::Result<T> AsArrowResult(llvm::Expected<T>& expected,
                               const std::string& error_context) {
  if (!expected) {
    return Status::CodeGenError(error_context, llvm::toString(expected.takeError()));
  }
  return std::move(expected.get());
}

Result<llvm::orc::JITTargetMachineBuilder> MakeTargetMachineBuilder(
    const Configuration& conf) {
  llvm::orc::JITTargetMachineBuilder jtmb(
      (llvm::Triple(llvm::sys::getDefaultTargetTriple())));
  if (conf.target_host_cpu()) {
    jtmb.setCPU(cpu_name.str());
    jtmb.addFeatures(cpu_attrs);
  }
#if LLVM_VERSION_MAJOR >= 18
  using CodeGenOptLevel = llvm::CodeGenOptLevel;
#else
  using CodeGenOptLevel = llvm::CodeGenOpt::Level;
#endif
  const auto opt_level =
      conf.optimize() ? CodeGenOptLevel::Aggressive : CodeGenOptLevel::None;
  jtmb.setCodeGenOptLevel(opt_level);
  return jtmb;
}

std::string DumpModuleIR(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<uint64_t>(function_ptr)),
      llvm::JITSymbolFlags::Exported);
#else
  llvm::JITEvaluatedSymbol symbol(reinterpret_cast<llvm::JITTargetAddress>(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
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(ADDRESS_SANITIZER) && LLVM_VERSION_MAJOR < 18
  if (!lljit.lookup("atexit")) {
    AddAbsoluteSymbol(lljit, "atexit", reinterpret_cast<void*>(atexit));
  }
#endif
}

#ifdef JIT_LINK_SUPPORTED
Result<std::unique_ptr<llvm::jitlink::InProcessMemoryManager>> CreateMemmoryManager() {
  auto maybe_mem_manager = llvm::jitlink::InProcessMemoryManager::Create();
  return AsArrowResult(maybe_mem_manager, "Could not create 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());
#  if LLVM_VERSION_MAJOR >= 21
    jit_builder.setObjectLinkingLayerCreator([&](llvm::orc::ExecutionSession& ES) {
      return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES, *memory_manager);
    });
#  else
    jit_builder.setObjectLinkingLayerCreator(
        [&](llvm::orc::ExecutionSession& ES, const llvm::Triple& TT) {
          return std::make_unique<llvm::orc::ObjectLinkingLayer>(ES, *memory_manager);
        });
#  endif
  }
  return Status::OK();
}
#endif

Result<std::unique_ptr<llvm::orc::LLJIT>> BuildJIT(
    llvm::orc::JITTargetMachineBuilder jtmb,
    std::optional<std::reference_wrapper<GandivaObjectCache>>& object_cache) {
  llvm::orc::LLJITBuilder jit_builder;

#ifdef 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)
            -> llvm::Expected<std::unique_ptr<llvm::orc::IRCompileLayer::IRCompiler>> {
          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<llvm::orc::TMOwningSimpleCompiler>(
              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;
}

arrow::Status VerifyAndLinkModule(
    llvm::Module& dest_module,
    llvm::Expected<std::unique_ptr<llvm::Module>> src_module_or_error) {
  ARROW_ASSIGN_OR_RAISE(
      auto src_ir_module,
      AsArrowResult(src_module_or_error, "Failed to verify and link module: "));

  src_ir_module->setDataLayout(dest_module.getDataLayout());

  std::string error_info;
  llvm::raw_string_ostream error_stream(error_info);
  ARROW_RETURN_IF(
      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)),
                  Status::CodeGenError("failed to link IR Modules"));

  return Status::OK();
}

}  // namespace

Status Engine::SetLLVMObjectCache(GandivaObjectCache& object_cache) {
  auto cached_buffer = object_cache.getObject(nullptr);
  if (cached_buffer) {
    auto error = lljit_->addObjectFile(std::move(cached_buffer));
    if (error) {
      return Status::CodeGenError("Failed to add cached object file to LLJIT: ",
                                  llvm::toString(std::move(error)));
    }
  }
  return Status::OK();
}

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();
#if LLVM_VERSION_MAJOR >= 19
  auto host_features = llvm::sys::getHostCPUFeatures();
  const bool have_host_features = true;
#else
  llvm::StringMap<bool> host_features;
  const auto have_host_features = llvm::sys::getHostCPUFeatures(host_features);
#endif
  std::string cpu_attrs_str;
  if (have_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::orc::LLJIT> lljit,
               std::unique_ptr<llvm::TargetMachine> target_machine, bool cached)
    : context_(std::make_unique<llvm::LLVMContext>()),
      lljit_(std::move(lljit)),
      ir_builder_(std::make_unique<llvm::IRBuilder<>>(*context_)),
      types_(*context_),
      optimize_(conf->optimize()),
      cached_(cached),
      function_registry_(conf->function_registry()),
      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<uintptr_t>(this));
  module_ = std::make_unique<llvm::Module>(module_id, *context_);
  module_->setDataLayout(target_machine_->createDataLayout());
}

Engine::~Engine() {}

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());
  return Status::OK();
}

Status Engine::LoadFunctionIRs() {
  if (!functions_loaded_) {
    ARROW_RETURN_NOT_OK(LoadPreCompiledIR());
    ARROW_RETURN_NOT_OK(DecimalIR::AddFunctions(this));
    ARROW_RETURN_NOT_OK(LoadExternalPreCompiledIR());
    functions_loaded_ = true;
  }
  return Status::OK();
}

llvm::Constant* Engine::CreateGlobalStringPtr(const std::string& string) {
  auto gloval_variable = ir_builder()->CreateGlobalString(string);
  auto zero = llvm::ConstantInt::get(llvm::Type::getInt32Ty(*context()), 0);
  llvm::Constant* indices[] = {zero, zero};
  return llvm::ConstantExpr::getInBoundsGetElementPtr(gloval_variable->getValueType(),
                                                      gloval_variable, indices);
}

/// factory method to construct the engine.
Result<std::unique_ptr<Engine>> Engine::Make(
    const std::shared_ptr<Configuration>& conf, bool cached,
    std::optional<std::reference_wrapper<GandivaObjectCache>> object_cache) {
  std::call_once(llvm_init_once_flag, InitOnce);

  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,
                        AsArrowResult(maybe_tm, "Could not create target machine: "));

  std::unique_ptr<Engine> engine{
      new Engine(conf, std::move(jit), std::move(target_machine), cached)};

  ARROW_RETURN_NOT_OK(engine->Init());
  return engine;
}

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() {
  const 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 = std::move(buffer_or_error.get());

  /// Parse the IR module.
  llvm::Expected<std::unique_ptr<llvm::Module>> module_or_error =
      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)));
  return Status::OK();
}

static llvm::MemoryBufferRef AsLLVMMemoryBuffer(const arrow::Buffer& arrow_buffer) {
  const auto data = reinterpret_cast<const char*>(arrow_buffer.data());
  const auto size = arrow_buffer.size();
  return {llvm::StringRef(data, size), "external_bitcode"};
}

Status Engine::LoadExternalPreCompiledIR() {
  const auto& buffers = function_registry_->GetBitcodeBuffers();
  for (const auto& 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)));
  }

  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
  llvm::PassBuilder pass_builder;
  llvm::ModuleAnalysisManager module_am;

  pass_builder.registerModuleAnalyses(module_am);
  llvm::ModulePassManager module_pm;

  std::unordered_set<std::string> used_functions;
  used_functions.insert(functions_to_compile_.begin(), functions_to_compile_.end());

  module_pm.addPass(
      llvm::InternalizePass([&used_functions](const llvm::GlobalValue& variable) -> bool {
        return used_functions.find(variable.getName().str()) != used_functions.end();
      }));
  module_pm.addPass(llvm::GlobalDCEPass());

  module_pm.run(*module_, module_am);
  return Status::OK();
}

// several passes requiring LLVM 14+ that are not available in the legacy pass manager
#if LLVM_VERSION_MAJOR >= 14
static void OptimizeModuleWithNewPassManager(llvm::Module& module,
                                             llvm::TargetIRAnalysis target_analysis) {
  // Setup an optimiser pipeline
  llvm::PassBuilder pass_builder;
  llvm::LoopAnalysisManager loop_am;
  llvm::FunctionAnalysisManager function_am;
  llvm::CGSCCAnalysisManager cgscc_am;
  llvm::ModuleAnalysisManager module_am;

  function_am.registerPass([&] { return target_analysis; });

  // Register required analysis managers
  pass_builder.registerModuleAnalyses(module_am);
  pass_builder.registerCGSCCAnalyses(cgscc_am);
  pass_builder.registerFunctionAnalyses(function_am);
  pass_builder.registerLoopAnalyses(loop_am);
  pass_builder.crossRegisterProxies(loop_am, function_am, cgscc_am, module_am);

  pass_builder.registerPipelineStartEPCallback([&](llvm::ModulePassManager& module_pm,
                                                   llvm::OptimizationLevel Level) {
    module_pm.addPass(llvm::ModuleInlinerPass());

    llvm::FunctionPassManager function_pm;
    function_pm.addPass(llvm::InstCombinePass());
    function_pm.addPass(llvm::PromotePass());
    function_pm.addPass(llvm::GVNPass());
    function_pm.addPass(llvm::NewGVNPass());
    function_pm.addPass(llvm::SimplifyCFGPass());
    function_pm.addPass(llvm::LoopVectorizePass());
    function_pm.addPass(llvm::SLPVectorizerPass());
    module_pm.addPass(llvm::createModuleToFunctionPassAdaptor(std::move(function_pm)));

    module_pm.addPass(llvm::GlobalOptPass());
  });

  pass_builder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O3)
      .run(module, module_am);
}
#else
static void OptimizeModuleWithLegacyPassManager(llvm::Module& module,
                                                llvm::TargetIRAnalysis target_analysis) {
  std::unique_ptr<llvm::legacy::PassManager> pass_manager(
      new llvm::legacy::PassManager());

  pass_manager->add(
      llvm::createTargetTransformInfoWrapperPass(std::move(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);
}
#endif

// Optimise and compile the module.
Status Engine::FinalizeModule() {
  if (!cached_) {
    ARROW_RETURN_NOT_OK(RemoveUnusedFunctions());

    if (optimize_) {
      auto target_analysis = target_machine_->getTargetIRAnalysis();
// misc passes to allow for inlining, vectorization, ..
#if LLVM_VERSION_MAJOR >= 14
      OptimizeModuleWithNewPassManager(*module_, std::move(target_analysis));
#else
      OptimizeModuleWithLegacyPassManager(*module_, std::move(target_analysis));
#endif
    }

    ARROW_RETURN_IF(llvm::verifyModule(*module_, &llvm::errs()),
                    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_->dump_ir()) {
      module_ir_ = DumpModuleIR(*module_);
    }

    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 IR module to LLJIT: ",
                                  llvm::toString(std::move(error)));
    }
  }
  module_finalized_ = true;

  return Status::OK();
}

Result<void*> 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 +
                                " 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<void*>(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,
                                     const std::vector<llvm::Type*>& args, void* func) {
  const auto prototype = llvm::FunctionType::get(ret_type, args, /*is_var_arg*/ false);
  llvm::Function::Create(prototype, llvm::GlobalValue::ExternalLinkage, name, module());
  AddAbsoluteSymbol(*lljit_, name, func);
}

arrow::Status Engine::AddGlobalMappings() {
  ARROW_RETURN_NOT_OK(ExportedFuncsRegistry::AddMappings(this));
  ExternalCFunctions c_funcs(function_registry_);
  return c_funcs.AddMappings(this);
}

const std::string& Engine::ir() {
  DCHECK(!module_ir_.empty()) << "dump_ir in Configuration must be set for dumping IR";
  return module_ir_;
}

}  // namespace gandiva
