* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* gscodegen.cpp
* Gauss LLVM Bridge routine
*
* IDENTIFICATION
* src/gausskernel/runtime/codegen/gscodegen.cpp
*
* -------------------------------------------------------------------------
*/
#include "codegen/gscodegen.h"
#include <unordered_set>
#ifdef ENABLE_LLVM_COMPILE
#include "llvm/ADT/Triple.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Analysis/InstructionSimplify.h"
#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/Verifier.h"
#include "llvm/Linker/Linker.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Support/DynamicLibrary.h"
#include "llvm/Support/Host.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/NoFolder.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/Transforms/IPO.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm-c/Core.h"
#endif
#include "pgxc/pgxc.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "catalog/pg_type.h"
#include "postmaster/postmaster.h"
#include "optimizer/streamplan.h"
using namespace llvm;
using namespace std;
bool GlobalCodeGenEnvironmentSuccess = false;
static bool gscodegen_initialized = false;
typedef void (*SignalHandlerPtr)(int);
SignalHandlerPtr savedSIGSEGV = (SignalHandlerPtr)(0);
sigjmp_buf SEGSEGVjmp;
void llvm_start_multithreaded()
{
#undef LLVM_ENABLE_THREADS
#define LLVM_ENABLE_THREADS 1
return;
}
void llvm_stop_multithreaded()
{
#undef LLVM_ENABLE_THREADS
#define LLVM_ENABLE_THREADS 0
return;
}
extern void lock_codegen_process_sub(int count);
extern void lock_codegen_process_add();
namespace dorado {
void GsCodeGen::initialize()
{
m_codeGenContext = AllocSetContextCreate(CurrentMemoryContext,
"codegen memory context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
}
bool GsCodeGen::InitializeLlvm(bool load_backend)
{
if (gscodegen_initialized) {
return true;
}
LLVM_TRY()
{
* This allocates a global llvm struct and enables multithreading.
* There is no real good time to clean this up but we only make it once.
*/
llvm_start_multithreaded();
bool result = false;
* This can *only* be called once per process and is used to setup
* dynamically linking jitted code.
*/
result = llvm::InitializeNativeTarget();
if (true == result) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmodule(MOD_LLVM),
(errmsg("Failed to initialze NativeTarget for LLVM."))));
}
result = llvm::InitializeNativeTargetAsmPrinter();
if (true == result) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmodule(MOD_LLVM),
(errmsg("Failed to initialze NativeTargetAsmPrinter for LLVM."))));
}
result = llvm::InitializeNativeTargetAsmParser();
if (true == result) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmodule(MOD_LLVM),
(errmsg("Failed to initialze NativeTargetAsmParser for LLVM."))));
}
}
LLVM_CATCH("Failed to initialize LLVM enviroment!");
gscodegen_initialized = true;
return gscodegen_initialized;
}
void GsCodeGen::cleanUpLlvm()
{
LLVM_TRY()
{
llvm_stop_multithreaded();
}
LLVM_CATCH("Failed to stop llvm thread!");
}
GsCodeGen::GsCodeGen() : m_llvmIRLoaded(false), m_isCorrupt(false)
{
LLVM_TRY()
{
m_llvmContext = new llvm::LLVMContext();
}
LLVM_CATCH("Failed to allocate LLVM context!");
m_currentModule = NULL;
m_optimizations_enabled = false;
m_machineCodeJitCompiled = NIL;
m_currentEngine = NULL;
m_initialized = false;
m_moduleCompiled = false;
m_codeGenContext = NULL;
m_cfunction_calls = NIL;
}
GsCodeGen::~GsCodeGen()
{
if (m_llvmContext != NULL) {
LLVM_TRY()
{
delete m_llvmContext;
}
LLVM_CATCH("Failed to release LLVM context!");
}
m_currentModule = NULL;
m_llvmContext = NULL;
m_machineCodeJitCompiled = NULL;
m_currentEngine = NULL;
m_codeGenContext = NULL;
m_cfunction_calls = NULL;
}
void GsCodeGen::enableOptimizations(bool enable)
{
m_optimizations_enabled = enable;
}
llvm::LLVMContext& GsCodeGen::context()
{
* Get the memory context hold by llvm module, since all
* the llvm values and types are only valid in llvm module.
*/
return m_currentModule->getContext();
}
bool GsCodeGen::createNewModule()
{
* compiled the module or haven't created the module yet.
*/
if (m_currentModule != NULL) {
return true;
}
LLVM_TRY()
{
m_currentModule = new llvm::Module("LLVM_module01", *m_llvmContext);
}
LLVM_CATCH("Failed to create new module!");
if (!m_initialized) {
return init();
}
return true;
}
llvm::ExecutionEngine* GsCodeGen::createNewEngine(llvm::Module* module)
{
string errStr;
llvm::ExecutionEngine* execEngine = NULL;
SmallVector<std::string, 1> mattrs;
#ifdef __aarch64__
mattrs.push_back("+crc");
#else
mattrs.push_back("-avx2");
#endif
LLVM_TRY()
{
std::unique_ptr<llvm::Module> uni_module(module);
execEngine = llvm::EngineBuilder(std::move(uni_module))
.setErrorStr(&errStr)
.setEngineKind(EngineKind::JIT)
.setMCPU(sys::getHostCPUName())
.setMAttrs(mattrs)
.create();
}
LLVM_CATCH("Failed to create new execution engine!");
if (execEngine == NULL) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmodule(MOD_LLVM),
errmsg("Failed to create LLVM state object ExecutionEngine: %s", errStr.c_str())));
}
return execEngine;
}
bool GsCodeGen::CheckPreloadModule(llvm::Module* mod)
{
if (mod == NULL) {
ereport(LOG, (errmodule(MOD_LLVM), errmsg("Failed to ParseBitcodeFile.")));
return false;
}
const char* triple = mod->getTargetTriple().c_str();
#ifdef __aarch64__
const char* cpu = "aarch64";
#elif defined (__x86_64__)
const char* cpu = "x86_64";
#else
const char* cpu = "generic";
#endif
char* pos = strstr(const_cast<char*>(triple), cpu);
if (!pos) {
ereport(LOG, (errmodule(MOD_LLVM),
errmsg("Target triple (%s) was not matched on native CPU.", triple)));
return false;
}
return true;
}
bool GsCodeGen::parseIRFile(StringInfo filename)
{
llvm::Module* m_module = NULL;
if (m_llvmIRLoaded) {
return true;
}
LLVM_TRY()
{
* Loads an LLVM module. file should be the local path to the LLVM bitcode file.
*/
ErrorOr<std::unique_ptr<MemoryBuffer>> fileOrErr = MemoryBuffer::getFile(filename->data);
if (std::error_code fileErr = fileOrErr.getError()) {
ereport(LOG,
(errmodule(MOD_LLVM),
errmsg("Failed to get file:%s because of %s.",
filename->data,
fileOrErr.getError().message().c_str())));
return false;
}
t_thrd.codegen_cxt.codegen_IRload_thr_count++;
lock_codegen_process_add();
* Loads an LLVM module, the ir file is a reference to a memory buffer containing
* LLVM bitcode. To avoid memory leak, we should use to memory alloced at the
* begenning.
*/
Assert(m_llvmContext != NULL);
LWLockAcquire(LLVMParseIRLock, LW_EXCLUSIVE);
Expected<std::unique_ptr<Module>> moduleOrErr =
parseBitcodeFile(fileOrErr->get()->getMemBufferRef(), *(m_llvmContext));
LWLockRelease(LLVMParseIRLock);
if (Error moduleErr = moduleOrErr.takeError()) {
ereport(LOG, (errmodule(MOD_LLVM), errmsg("ParseBitcodeFile get error.")));
return false;
}
m_module = moduleOrErr.get().release();
if (!CheckPreloadModule(m_module))
return false;
}
LLVM_CATCH("Failed to parse IR file.");
m_currentModule = m_module;
m_moduleCompiled = false;
if (!m_llvmIRLoaded) {
m_llvmIRLoaded = true;
}
return init();
}
void GsCodeGen::loadIRFile()
{
dorado::GsCodeGen* llvmCodeGen = (GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj;
char* exec_path = NULL;
StringInfo filename = makeStringInfo();
exec_path = gs_getenv_r("GAUSSHOME");
if (NULL != exec_path && strcmp(exec_path, "\0") != 0) {
char* exec_path_r = realpath(exec_path, NULL);
if (exec_path_r) {
char *llvmIrFilePath = "share/llvmir/GaussDB_expr.ir";
#if (!defined(ENABLE_MULTIPLE_NODES)) && (!defined(ENABLE_PRIVATEGAUSS))
if (u_sess->attr.attr_sql.whale || u_sess->attr.attr_sql.dolphin) {
int id = GetCustomParserId();
if (id >= 0 && g_instance.llvmIrFilePath[id] != NULL) {
llvmIrFilePath = g_instance.llvmIrFilePath[id];
}
}
#endif
appendStringInfo(filename, "%s/%s", exec_path_r, llvmIrFilePath);
check_backend_env(exec_path);
free(exec_path_r);
} else {
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmodule(MOD_LLVM),
errmsg("Got illegal enviroment parameter $GAUSSHOME, please set $GAUSSHOME as your "
"installation directory!")));
}
} else {
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmodule(MOD_LLVM),
errmsg("Failed to get enviroment parameter $GAUSSHOME or it is NULL, please set $GAUSSHOME as your "
"installation directory!")));
}
exec_path = NULL;
if (!llvmCodeGen->parseIRFile(filename)) {
pfree_ext(filename->data);
pfree_ext(filename);
ereport(
ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmodule(MOD_LLVM), errmsg("Failed to load IR file!")));
}
pfree_ext(filename->data);
pfree_ext(filename);
}
void GsCodeGen::compileCurrentModule(bool enable_jitcache)
{
if (m_currentModule == NULL) {
return;
}
MemoryContext oldContext = MemoryContextSwitchTo(m_codeGenContext);
llvm::Module* module = m_currentModule;
llvm::ExecutionEngine* exectorEngine = compileModule(module, enable_jitcache);
if (NULL == exectorEngine) {
(void)MemoryContextSwitchTo(oldContext);
return;
}
ListCell* cell = NULL;
LLVM_TRY()
{
foreach (cell, m_machineCodeJitCompiled) {
Llvm_Map<llvm::Function*, void**>* map = (Llvm_Map<llvm::Function*, void**>*)lfirst(cell);
llvm::Function* func = map->key;
* If the machine code has been generated, do not make it again.
*/
if (NULL != *map->value) {
continue;
}
void* jittedFunction = exectorEngine->getPointerToFunction(func);
if (jittedFunction != NULL) {
*map->value = jittedFunction;
} else {
*map->value = NULL;
ereport(LOG,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmodule(MOD_LLVM),
errmsg("Failed to find jitted function \"%s\".", func->getName().data())));
}
}
}
LLVM_CATCH("Failed to find jitted LLVM function!");
m_moduleCompiled = true;
m_llvmIRLoaded = false;
(void)MemoryContextSwitchTo(oldContext);
}
llvm::ExecutionEngine* GsCodeGen::compileModule(llvm::Module* module, bool enable_jitcache)
{
string errStr;
llvm::ExecutionEngine* newEngine = createNewEngine(module);
if (likely(newEngine != NULL)) {
m_currentEngine = newEngine;
}
* Optimize the current module, which can greatly reduce the
* unused IR functions and inline all the IR functions.
*/
if (m_optimizations_enabled) {
optimizeModule(module);
}
LLVM_TRY()
{
m_currentEngine->finalizeObject();
}
LLVM_CATCH("Failed to compile LLVM module!");
if (u_sess->attr.attr_sql.enable_codegen_print) {
ereport(LOG, (errmodule(MOD_LLVM), errmsg("Begin dump all the IR function after optimization!")));
LWLockAcquire(LLVMDumpIRLock, LW_EXCLUSIVE);
module->print(llvm::outs(), nullptr);
LWLockRelease(LLVMDumpIRLock);
ereport(LOG, (errmodule(MOD_LLVM), errmsg("End of dumping all the IR function!")));
}
return newEngine;
}
void GsCodeGen::releaseResource()
{
if (m_codeGenContext) {
m_codeGenContext = NULL;
}
m_machineCodeJitCompiled = NIL;
* release llvm execution engine. since module is subordinate to
* execution engine, module memory can be released when execution
* enegine memory is released.
*/
if (NULL != m_currentEngine) {
LLVM_TRY()
{
delete m_currentEngine;
}
LLVM_CATCH("Failed to release LLVM engine!");
m_currentEngine = NULL;
m_currentModule = NULL;
}
if (NULL != m_currentModule) {
LLVM_TRY()
{
delete m_currentModule;
}
LLVM_CATCH("Failed to release LLVM module!");
m_currentModule = NULL;
}
lock_codegen_process_sub(t_thrd.codegen_cxt.codegen_IRload_thr_count);
t_thrd.codegen_cxt.codegen_IRload_thr_count = 0;
}
bool GsCodeGen::init()
{
m_initialized = true;
return true;
}
Type* GsCodeGen::getVoidType()
{
return Type::getVoidTy(context());
}
Type* GsCodeGen::getType(Oid TypeID)
{
switch (TypeID) {
case BITOID:
return Type::getInt1Ty(context());
case BOOLOID:
case CHAROID:
return Type::getInt8Ty(context());
case INT2OID:
return Type::getInt16Ty(context());
case INT4OID:
return Type::getInt32Ty(context());
case INT8OID:
return Type::getInt64Ty(context());
case FLOAT4OID:
return Type::getFloatTy(context());
case FLOAT8OID:
return Type::getDoubleTy(context());
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmodule(MOD_LLVM),
(errmsg("Invalid LLVM type %d", TypeID))));
return NULL;
}
}
PointerType* GsCodeGen::getPtrType(Oid TypeID)
{
return PointerType::get(getType(TypeID), 0);
}
Type* GsCodeGen::getType(const char* name)
{
Assert(NULL != m_currentModule && NULL != name);
#if LLVM_MAJOR_VERSION == 12
return StructType::getTypeByName(context(), StringRef(name, strlen(name)));
#else
return m_currentModule->getTypeByName(StringRef(name, strlen(name)));
#endif
}
PointerType* GsCodeGen::getPtrType(const char* name)
{
Assert(NULL != name);
Type* type = getType(name);
return PointerType::get(type, 0);
}
PointerType* GsCodeGen::getPtrPtrType(const char* name)
{
Assert(NULL != name);
Type* type = getType(name);
Type* typePtr = PointerType::get(type, 0);
return PointerType::get(typePtr, 0);
}
llvm::Value* GsCodeGen::CastPtrToLlvmPtr(Type* type, const void* ptr)
{
Constant* const_int = ConstantInt::get(Type::getInt64Ty(context()), (long long)ptr);
return ConstantExpr::getIntToPtr(const_int, type);
}
bool GsCodeGen::verifyFunction(Function* fn)
{
if (m_isCorrupt) {
return false;
}
LLVM_TRY()
{
Function::iterator iter;
for (iter = fn->begin(); iter != fn->end(); ++iter) {
if (iter->empty() || !iter->back().isTerminator()) {
m_isCorrupt = true;
break;
}
}
if (!m_isCorrupt) {
llvm::raw_fd_ostream rfdos(STDERR_FILENO, false);
m_isCorrupt = llvm::verifyFunction(*fn, &rfdos);
}
}
LLVM_CATCH("Failed to verify LLVM function!");
if (m_isCorrupt) {
return false;
}
return true;
}
void GsCodeGen::FinalizeFunction(Function* function, int plan_node_id)
{
bool is_valid = false;
is_valid = verifyFunction(function);
if (u_sess->attr.attr_sql.enable_codegen_print) {
ereport(LOG,
(errmodule(MOD_LLVM),
errmsg("The IR function %s generated with plan_node_id %d is:",
function->getName().data(),
plan_node_id)));
LWLockAcquire(LLVMDumpIRLock, LW_EXCLUSIVE);
function->print(llvm::outs());
LWLockRelease(LLVMDumpIRLock);
}
if (!is_valid) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmodule(MOD_LLVM),
errmsg("Codegen failed on verifying IR function %s.", function->getName().data())));
}
return;
}
PointerType* GsCodeGen::getPtrType(Type* type)
{
return PointerType::get(type, 0);
}
* Create a new APInt of numBits width, initialized as val with function
* APInt(numBits, val, isSigned). When isSigned is true, val is treated
* as int64_t and the appropriate sign extension to the bit width will be
* done. Otherwise, no sign extension occurs.
*/
llvm::Value* GsCodeGen::getIntConstant(Oid TypeID, int64_t val)
{
switch (TypeID) {
case BITOID:
return ConstantInt::get(context(), APInt(1, val, true));
case CHAROID:
return ConstantInt::get(context(), APInt(8, val, true));
case INT2OID:
return ConstantInt::get(context(), APInt(16, val, true));
case INT4OID:
return ConstantInt::get(context(), APInt(32, val, true));
case INT8OID:
return ConstantInt::get(context(), APInt(64, val, true));
default:
ereport(LOG, (errmodule(MOD_LLVM), errmsg("%d is not support yet.", TypeID)));
return NULL;
}
}
GsCodeGen::FnPrototype::FnPrototype(GsCodeGen* gen, const char* name, Type* fnReturnType)
: codeGen(gen), returnType(fnReturnType)
{
Assert(NULL != name);
int nameLen = strlen(name);
if (0 == nameLen) {
functionName = NULL;
} else {
errno_t rc = 0;
functionName = (char*)palloc0((nameLen + 1) * sizeof(char));
rc = strncpy_s(functionName, nameLen + 1, name, nameLen);
securec_check(rc, "\0", "\0");
}
}
Function* GsCodeGen::FnPrototype::generatePrototype(LlvmBuilder* builder, llvm::Value** params)
{
Vector<Type*> arguments;
for (int i = 0; i < functionArgs.size(); ++i) {
arguments.push_back(functionArgs[i].type);
}
FunctionType* prototype =
FunctionType::get(returnType, llvm::ArrayRef<Type*>(&arguments.front(), arguments.size()), false);
codeGen->createNewModule();
Function* fn = Function::Create(prototype, Function::ExternalLinkage, functionName, codeGen->m_currentModule);
if (NULL == fn) {
ereport(ERROR,
(errcode(ERRCODE_CODEGEN_ERROR), errmodule(MOD_LLVM), errmsg("Failed to create llvm function prototype.")));
}
int idx = 0;
for (Function::arg_iterator iter = fn->arg_begin(); iter != fn->arg_end(); ++iter, ++idx) {
iter->setName(functionArgs[idx].name);
if (params != NULL) {
params[idx] = &*iter;
}
}
if (builder != NULL) {
BasicBlock* entry_block = BasicBlock::Create(codeGen->context(), "entry", fn);
builder->SetInsertPoint(entry_block);
}
return fn;
}
void GsCodeGen::addFunctionToMCJit(llvm::Function* fn, void** machineCodeFuncPtr)
{
ListCell* cell = NULL;
ListCell* prev = NULL;
Assert(NULL != fn);
Assert(NULL != machineCodeFuncPtr);
*machineCodeFuncPtr = NULL;
* Allocate memory in codeGenContext during the process of LLVM functionality, since
* codeGenContext is valid during the whole query, and it is independent of m_llvmContext.
*/
MemoryContext oldContext = MemoryContextSwitchTo(m_codeGenContext);
Llvm_Map<llvm::Function*, void**>* map = New(CurrentMemoryContext) Llvm_Map<llvm::Function*, void**>;
* If there exitsts one llvm function that is the same as fn, we should first delete it.
*/
foreach (cell, m_machineCodeJitCompiled) {
Llvm_Map<llvm::Function*, void**>* tmpMap = (Llvm_Map<llvm::Function*, void**>*)lfirst(cell);
if (tmpMap->key == fn) {
m_machineCodeJitCompiled = list_delete_cell(m_machineCodeJitCompiled, cell, prev);
break;
}
prev = cell;
}
map->key = fn;
map->value = machineCodeFuncPtr;
m_machineCodeJitCompiled = lappend(m_machineCodeJitCompiled, map);
(void)MemoryContextSwitchTo(oldContext);
}
void GsCodeGen::recordCFunctionCalls(char* name)
{
m_cfunction_calls = lappend(m_cfunction_calls, name);
}
bool GsCodeGen::hasCFunctionCalls()
{
return m_cfunction_calls != NIL;
}
void GsCodeGen::clearCFunctionCalls()
{
list_free_ext(m_cfunction_calls);
m_cfunction_calls = NIL;
}
void GsCodeGen::dumpCFunctionCalls(const char* location, int plan_id)
{
if (m_cfunction_calls == NIL)
return;
ListCell* cell = NULL;
StringInfo log_info = makeStringInfo();
appendStringInfo(
log_info, "Encounted C functions which are not codegened in the %s of plan %d are: [", location, plan_id);
foreach (cell, m_cfunction_calls) {
appendStringInfoString(log_info, (char*)cell->data.ptr_value);
if (cell->next)
appendStringInfoString(log_info, ", ");
}
appendStringInfoString(log_info, "].");
ereport(DEBUG1, (errmodule(MOD_LLVM), errmsg("%s", log_info->data)));
pfree_ext(log_info->data);
pfree_ext(log_info);
}
void GsCodeGen::optimizeModule(llvm::Module* module)
{
ListCell* cell = NULL;
LLVM_TRY()
{
* Passmanager will be userd to construct optimizations passed that are 'typical'
* for c/c++ program. We're We're relying on llvm to pick the best passes for us.
*/
PassManagerBuilder pass_builder;
pass_builder.OptLevel = 2;
pass_builder.SizeLevel = 0;
pass_builder.Inliner = createFunctionInliningPass();
* Specifying the data layout is necessary for some optimizations
* e.g. : removing many of the loads/stores produced by structs.
*/
llvm::TargetIRAnalysis target_analysis = m_currentEngine->getTargetMachine()->getTargetIRAnalysis();
* Before running any other optimization passes, run the internalize pass, giving
* it the names of all functions registered by addFunctionToJIT(), followed by the
* global dead code elimination pass. This causes all functions not registered to be
* JIT'd to be marked as internal, and any internal functions that are not used are
* deleted by DCE pass. This greatly decreases compile time by removing unused code.
*/
unordered_set<string> exported_fn_names;
foreach (cell, m_machineCodeJitCompiled) {
Llvm_Map<llvm::Function*, void**>* tmpMap = (Llvm_Map<llvm::Function*, void**>*)lfirst(cell);
llvm::Function* func = tmpMap->key;
exported_fn_names.insert(func->getName().data());
}
legacy::PassManager* module_pass_manager(new legacy::PassManager());
module_pass_manager->add(createTargetTransformInfoWrapperPass(target_analysis));
module_pass_manager->add(llvm::createInternalizePass([&exported_fn_names](const llvm::GlobalValue& gv) {
return exported_fn_names.find(gv.getName().str()) != exported_fn_names.end();
}));
module_pass_manager->add(createGlobalDCEPass());
module_pass_manager->run(*module);
* Create and run function pass manager:
* boost::scoped_ptr<FunctionPassManager> fn_pass_manager(new FunctionPassManager(M));
*/
legacy::FunctionPassManager* fn_pass_manager = new legacy::FunctionPassManager(module);
fn_pass_manager->add(llvm::createTargetTransformInfoWrapperPass(target_analysis));
pass_builder.populateFunctionPassManager(*fn_pass_manager);
fn_pass_manager->doInitialization();
llvm::Module::iterator it = module->begin();
llvm::Module::iterator end = module->end();
while (it != end) {
if (!it->isDeclaration()) {
fn_pass_manager->run(*it);
}
++it;
}
fn_pass_manager->doFinalization();
delete module_pass_manager;
module_pass_manager = new legacy::PassManager();
module_pass_manager->add(llvm::createTargetTransformInfoWrapperPass(target_analysis));
pass_builder.populateModulePassManager(*module_pass_manager);
module_pass_manager->run(*module);
delete module_pass_manager;
delete fn_pass_manager;
}
LLVM_CATCH("Failed to optimize current module!");
}
}
static bool getCpuInfo(
unsigned cpu_info, unsigned* cpu_rEAX, unsigned* cpu_rEBX, unsigned* cpu_rECX, unsigned* cpu_rEDX)
{
#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)
#if defined(__GNUC__) || defined(__clang__)
#if defined(__x86_64__)
* gcc doesn't know cpuid would clobber ebx/rbx. Preserve it manually.
*/
__asm__("movq\t%%rbx, %%rsi\n\t cpuid\n\t xchgq\t%%rbx, %%rsi\n\t"
: "=a"(*cpu_rEAX), "=S"(*cpu_rEBX), "=c"(*cpu_rECX), "=d"(*cpu_rEDX)
: "a"(cpu_info));
#elif defined(__i386__)
__asm__("movl\t%%ebx, %%esi\n\t cpuid\n\t xchgl\t%%ebx, %%esi\n\t"
: "=a"(*cpu_rEAX), "=S"(*cpu_rEBX), "=c"(*cpu_rECX), "=d"(*cpu_rEDX)
: "a"(cpu_info));
#else
Assert(0 && "This method is defined only for x86.");
#endif
#elif defined(_MSC_VER)
#endif
return false;
#else
return true;
#endif
}
static bool isCPUFeatureSupportCodegen()
{
#ifdef __aarch64__
return true;
#endif
bool isSupportSSE42 = false;
unsigned EAX = 0;
unsigned EBX = 0;
unsigned ECX = 0;
unsigned EDX = 0;
unsigned MaxLevel = 0;
union {
unsigned u[3];
char c[12];
} text;
errno_t errorno = EOK;
errorno = memset_s(&text, sizeof(text), 0, sizeof(text));
securec_check(errorno, "\0", "\0");
if (getCpuInfo(0, &MaxLevel, text.u + 0, text.u + 2, text.u + 1) || MaxLevel < 1) {
ereport(LOG, (errmodule(MOD_LLVM), errmsg("Unable to get cpu info")));
} else {
(void)getCpuInfo(1, &EAX, &EBX, &ECX, &EDX);
isSupportSSE42 = (ECX >> 20) & 1;
if (!isSupportSSE42) {
ereport(LOG, (errmodule(MOD_LLVM), errmsg("SSE4.2 is not supported, disable codegen.")));
}
}
return isSupportSSE42;
}
bool canInitCodegenInvironment()
{
if (!IsInitdb && !GlobalCodeGenEnvironmentSuccess) {
return true;
} else
return false;
}
bool canInitThreadCodeGen()
{
bool canInit = false;
if (get_local_dbstate() != NORMAL_STATE) {
u_sess->attr.attr_sql.enable_codegen = false;
return canInit;
}
if (IS_PGXC_DATANODE && u_sess->attr.attr_sql.enable_codegen &&
!t_thrd.codegen_cxt.thr_codegen_obj) {
canInit = GlobalCodeGenEnvironmentSuccess;
if (!canInit) {
u_sess->attr.attr_sql.enable_codegen = false;
}
}
return canInit;
}
* @Description : Try to initializ the LLVM enviroment.
*/
void CodeGenProcessInitialize()
{
if (isCPUFeatureSupportCodegen() && canInitCodegenInvironment()) {
MemoryContext current_context = CurrentMemoryContext;
PG_TRY();
{
GlobalCodeGenEnvironmentSuccess = dorado::GsCodeGen::InitializeLlvm();
}
PG_CATCH();
{
(void)MemoryContextSwitchTo(current_context);
FlushErrorState();
ereport(LOG, (errmodule(MOD_LLVM), errmsg("Failed to initialze environment for codegen.")));
}
PG_END_TRY();
}
}
* @Description : Clean up LLVM enviroment resource
* before exit postmaster.
*/
void CodeGenProcessTearDown()
{
if (GlobalCodeGenEnvironmentSuccess) {
dorado::GsCodeGen::cleanUpLlvm();
}
}
* @Description : Initialize GsCodeGen Obj.
*/
void CodeGenThreadInitialize()
{
if (t_thrd.codegen_cxt.codegen_IRload_thr_count != 0)
ereport(LOG, (errmodule(MOD_LLVM), errmsg("Failed to release thread codegen")));
if (canInitThreadCodeGen()) {
MemoryContext current_context = CurrentMemoryContext;
PG_TRY();
{
t_thrd.codegen_cxt.thr_codegen_obj = New(CurrentMemoryContext) dorado::GsCodeGen();
}
PG_CATCH();
{
(void)MemoryContextSwitchTo(current_context);
FlushErrorState();
t_thrd.codegen_cxt.thr_codegen_obj = NULL;
ereport(LOG, (errmodule(MOD_LLVM), errmsg("Failed to initialze thread codegen.")));
}
PG_END_TRY();
}
}
bool CodeGenThreadObjectReady()
{
return t_thrd.codegen_cxt.thr_codegen_obj != NULL && !t_thrd.codegen_cxt.g_runningInFmgr
&& !((dorado::GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj)->IsCompiled();
}
* @Description : Allocate memory context for codegen.
*/
void CodeGenThreadRuntimeSetup()
{
if (CodeGenThreadObjectReady()) {
((dorado::GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj)->initialize();
}
}
* @Description : Compile all the IR function in module to
* get the machine code.
*/
void CodeGenThreadRuntimeCodeGenerate()
{
((dorado::GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj)->enableOptimizations(true);
((dorado::GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj)->compileCurrentModule(false);
}
* @Description : clean up all the thread LLVM resource according
* to the current query. This resource includes
* execution engine/module and GsCodeGen Obj.
*/
void CodeGenThreadTearDown()
{
if (t_thrd.codegen_cxt.thr_codegen_obj) {
((dorado::GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj)->releaseResource();
delete (dorado::GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj;
t_thrd.codegen_cxt.thr_codegen_obj = NULL;
}
}
* @Description : reset machine code in subtransaction
*/
void CodeGenThreadReset()
{
if (t_thrd.codegen_cxt.thr_codegen_obj)
((dorado::GsCodeGen*)t_thrd.codegen_cxt.thr_codegen_obj)->resetMCJittedFunc();
}
* @Description : Make sure if we have a GsCodeGen Obj or not.
*/
* @Description : Since it takes some time to compile the LLVM IR function to
* : machine code, we would not enjoy any benefit if the actual
* : execute time is very low. The simplest method is to check
* : if we have engough rows.
*
* @In rows : The number of estimated rows in each datanode.
* @Return :
* @Notes : It is better to compile the estimated compilation time and
* : the estimated exectution time, not only consider the rows.
*/
bool CodeGenPassThreshold(double rows, int dn_num, int dop)
{
if (isIntergratedMachine) {
if (dn_num <= 0) {
dn_num = 1;
}
Assert(dop > 0);
rows = rows / dn_num / dop;
}
if (rows >= u_sess->attr.attr_sql.codegen_cost_threshold)
return true;
return false;
}