自学内容网 自学内容网

Postgresql源码(141)JIT系列分析汇总

JIT的东西比较零散,本篇对之前的一些列分析做个汇总、整理。
涉及:
《Postgresql源码(113)表达式JIT计算简单分析》
《Postgresql源码(127)投影ExecProject的表达式执行分析》
《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》
《LLVM(1)Fibonacci实例》
《LLVM(5)ORC实例分析》
《LLVM的ThinLTO编译优化技术在Postgresql中的应用》
《Postgresql中JIT函数能否inline的依据function_inlinable》

0 前言

从JIT的编译、加载,到使用方法、原理,整体对PG 引入JIT的细节做一些总结。

1 c/c++混合编译

PG编译时配置 --with-llvm可整合llvm到PG编译过程中。

./configure --prefix=/data/mingjie/pgroot99/pghome --enable-tap-tests --with-tcl \
 --enable-depend--enable-cassert --enable-debug --with-perl --with-openssl --with-libxml \
 --with-llvm \
 CFLAGS="-ggdb3 -O0 -g3 -fno-omit-frame-pointer" \
 CXXFLAGS="-ggdb3 -O0 -g3 -fno-omit-frame-pointer"

涉及JIT的几个源文件

[mingjie@pgev01 ~/pgroot99/pgsrc/src/backend/jit][PGROOT99:9901]$ tree
.
├── jit.bc
├── jit.c
├── jit.o
├── llvm
│   ├── llvmjit.c                -- C编译器
│   ├── llvmjit_deform.c         -- C编译器
│   ├── llvmjit_error.cpp        -- C++编译器
│   ├── llvmjit_expr.c           -- C编译器
│   ├── llvmjit_inline.cpp       -- C++编译器
│   ├── llvmjit_types.c          -- 后面单独讲
│   ├── llvmjit_wrap.cpp         -- C++编译器
│   ├── Makefile
├── Makefile
└── README

1.1 cc

  • gcc 编译的三个.o文件 llvmjit、llvmjit_deform、llvmjit_expr
  • 生成 llvmjit.o、llvmjit_deform.o、llvmjit_expr.c。
  • 注意 gcc -c 先不错 link,等后面一起做。
llvmjit.c        -> llvmjit.o
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2  -Wno-deprecated-declarations -fPIC -fvisibility=hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit.o llvmjit.c -MMD -MP -MF .deps/llvmjit.Po

llvmjit_deform.c -> llvmjit_deform.o
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2  -Wno-deprecated-declarations -fPIC -fvisibility=hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_deform.o llvmjit_deform.c -MMD -MP -MF .deps/llvmjit_deform.Po

llvmjit_expr.c   -> llvmjit_expr.o
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2  -Wno-deprecated-declarations -fPIC -fvisibility=hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_expr.o llvmjit_expr.c -MMD -MP -MF .deps/llvmjit_expr.Po

编译命令拼接:

  1. 第一段:gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2 -Wno-deprecated-declarations -fPIC -fvisibility=hidden

  2. 第二段:llvm-config --cflags: -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include

  3. 第三段:-I…/…/…/…/src/include -D_GNU_SOURCE -I/usr/include/libxml2 -c -o llvmjit.o llvmjit.c -MMD -MP -MF .deps/llvmjit.Po

1.2 c++

  • g++ 编译的三个.o文件 llvmjit_error、llvmjit_inline、llvmjit_wrap
  • 生成 llvmjit_error.o、llvmjit_inline.o、llvmjit_wrap.o。
  • 也没有做 link。
llvmjit_error.cpp     ->   llvmjit_error.o
g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -g -O2 -std=c++14 -fno-rtti -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_error.o llvmjit_error.cpp -MMD -MP -MF .deps/llvmjit_error.Po

llvmjit_inline.cpp    ->   llvmjit_inline.o
g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -g -O2 -std=c++14 -fno-rtti -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_inline.o llvmjit_inline.cpp -MMD -MP -MF .deps/llvmjit_inline.Po

llvmjit_wrap.cpp      ->   llvmjit_wrap.o
g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -g -O2 -std=c++14 -fno-rtti -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include  -I../../../../src/include  -D_GNU_SOURCE -I/usr/include/libxml2 \
-c -o llvmjit_wrap.o llvmjit_wrap.cpp -MMD -MP -MF .deps/llvmjit_wrap.Po
  • g++ 六个文件汇总到 llvmjit.so
  • g++ 做 link
llvmjit.so

g++ -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation \
-g -ggdb -O0 -g3 -gdwarf-2  -Wno-deprecated-declarations \
-fPIC -fvisibility=hidden \
-shared -o llvmjit.so  llvmjit.o llvmjit_error.o llvmjit_inline.o llvmjit_wrap.o llvmjit_deform.o llvmjit_expr.o \
-L../../../../src/port -L../../../../src/common  \
-L/data02/mingjie/bin/llvm15/lib \
-Wl,--as-needed -Wl,-rpath,'/data02/mingjie/pgroot99/pghome/lib',--enable-new-dtags  -fvisibility=hidden \
-lLLVMOrcJIT \
-lLLVMPasses \
-lLLVMObjCARCOpts \
-lLLVMCoroutines \
-lLLVMipo \
-lLLVMVectorize -lLLVMLinker -lLLVMIRReader -lLLVMAsmParser -lLLVMFrontendOpenMP -lLLVMJITLink -lLLVMX86TargetMCA -lLLVMMCA -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMX86Desc -lLLVMX86Info -lLLVMMCDisassembler -lLLVMSelectionDAG -lLLVMInstrumentation -lLLVMAsmPrinter -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMOrcTargetProcess -lLLVMOrcShared -lLLVMCodeGen -lLLVMTarget -lLLVMScalarOpts -lLLVMInstCombine -lLLVMAggressiveInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMSymbolize -lLLVMDebugInfoPDB -lLLVMDebugInfoMSF -lLLVMDebugInfoDWARF -lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMSupport -lLLVMDemangle -lrt -ldl -lpthread -lm -lz -lzstd -ltinfo -lxml2

llvmjit_error编译命令拼接

  1. 第一段:g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -g -O2 -std=c++14 -fno-rtti -fPIC -fvisibility=hidden -fvisibility-inlines-hidden

  2. 第二段: llvm-config --cflags: -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -D_GNU_SOURCE -DBUILD_EXAMPLES -I/data02/mingjie/bin/llvm15/include

  3. 第三段:-I…/…/…/…/src/include -D_GNU_SOURCE -I/usr/include/libxml2 -c -o llvmjit_error.o llvmjit_error.cpp -MMD -MP -MF .deps/llvmjit_error.Po

llvmjit.so编译命令拼接

  1. 第一段:g++ -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -ggdb -O0 -g3 -gdwarf-2 -Wno-deprecated-declarations -fPIC -fvisibility=hidden -shared -o llvmjit.so llvmjit.o llvmjit_error.o llvmjit_inline.o llvmjit_wrap.o llvmjit_deform.o llvmjit_expr.o -L…/…/…/…/src/port -L…/…/…/…/src/common
  2. 第二段: llvm-config --ldflags: -L/data02/mingjie/bin/llvm15/lib
  3. 第三段:-Wl,–as-needed -Wl,-rpath,‘/data02/mingjie/pgroot99/pghome/lib’,–enable-new-dtags -fvisibility=hidden
  4. 第四段: llvm-config --libs: -lLLVMOrcJIT -lLLVMPasses -lLLVMObjCARCOpts -lLLVMCoroutines -lLLVMipo -lLLVMVectorize -lLLVMLinker -lLLVMIRReader -lLLVMAsmParser -lLLVMFrontendOpenMP -lLLVMJITLink -lLLVMX86TargetMCA -lLLVMMCA -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMX86Desc -lLLVMX86Info -lLLVMMCDisassembler -lLLVMSelectionDAG -lLLVMInstrumentation -lLLVMAsmPrinter -lLLVMInterpreter -lLLVMExecutionEngine -lLLVMRuntimeDyld -lLLVMOrcTargetProcess -lLLVMOrcShared -lLLVMCodeGen -lLLVMTarget -lLLVMScalarOpts -lLLVMInstCombine -lLLVMAggressiveInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMSymbolize -lLLVMDebugInfoPDB -lLLVMDebugInfoMSF -lLLVMDebugInfoDWARF -lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMSupport -lLLVMDemangle
  5. 第五段: llvm-config --system-libs: -lrt -ldl -lpthread -lm -lz -lzstd -ltinfo -lxml2

1.3 编译总览

整体上看使用了GCC、G++编译文件,最后用G++汇总:
在这里插入图片描述

2 加载llvmjit.so

编译后生成llvmjit.so,在程序启动时不做加载,在运行时按需加载。

加载位置:jit.c提供的provider_init函数中,对llvmjit.so进行动态加载:

provider_init
load_external_function(path, "_PG_jit_provider_init", true, NULL)
internal_load_library
dlopen(file_scanner->filename, RTLD_NOW | RTLD_GLOBAL)
dlsym(lib_handle, funcname);

2.1 如何使用llvmjit.so的符号表?

G++编译的三个cpp文件会依赖llvm库,llvm是用C++实现的,所以llvmjit.so中会存在大量经过mangling的符号,GCC无法识别。

例如:readelf -Ws llvmjit.so | sort -k2 | more
在这里插入图片描述
de-mangling后:nm -C llvmjit.so | sort -k1 | more
在这里插入图片描述
对应:

  • mangling:_Z10isSoftFP16IN4llvm3MVTEEbT_RKNS0_12X86SubtargetE
    • de-mangling:bool isSoftFP16<llvm::MVT>(llvm::MVT, llvm::X86Subtarget const&)
  • mangling:_ZNK4llvm17X86TargetLowering23getStackProbeSymbolNameERKNS_15MachineFunctionE
    • de-mangling:llvm::X86TargetLowering::getStackProbeSymbolName(llvm::MachineFunction const&) const

所以llvmjit.so被动态加载到PG中时,mangling的符号是无法被调用的。那怎么让进程找到符号呢:

用extern "C"标注那些不要做mangling的符号。

例如:llvmjit.h(需要避免mangling的在.h中声明即可)

#ifdef __cplusplus
extern "C"
{
#endif

...

// llvmjit.c
extern LLVMJitContext *llvm_create_context(int jitFlags);
extern char *llvm_expand_funcname(LLVMJitContext *context, const char *basename);
extern void *llvm_get_function(LLVMJitContext *context, const char *funcname);

// llvmjit_error.cpp
extern void llvm_enter_fatal_on_oom(void);


// llvmjit_inline.cpp
extern void llvm_inline_reset_caches(void);
extern void llvm_inline(LLVMModuleRef mod);

...

#ifdef __cplusplus
} /* extern "C" */
#endif

在符号表中可以看到这几个符号:

在这里插入图片描述

这类符号就可以被GCC编译的程序正常调用,例如:llvm_enter_fatal_on_oom函数在llvmjit_error.cpp中定义,在llvmjit_expr.c中使用。

2.2 load llvmjit.so

调用堆栈

#0  jit_compile_expr (state=0x1deae18) at jit.c:180
#1  0x000000000071fa6b in ExecReadyExpr (state=0x1deae18) at execExpr.c:874
#2  0x000000000071e60b in ExecInitExpr (node=0x1dfabb8, parent=0x0) at execExpr.c:152
#3  0x00000000008b3395 in evaluate_expr (expr=0x1dfabb8, result_type=23, result_typmod=-1, result_collation=0) at clauses.c:4892
#4  0x00000000008b26f8 in evaluate_function (funcid=1397, result_type=23, result_typmod=-1, result_collid=0, input_collid=0, args=0x1dfab68, funcvariadic=false, func_tuple=0x7fd9588871a8, context=0x7ffdd8867f20) at clauses.c:4409

...

  • 加载llvmjit.so。
  • 可以由GUC参数制定文件名jit_provider。
jit_compile_expr
provider_init
snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path, jit_provider, DLSUFFIX);
init = (JitProviderInit)
load_external_function(path, "_PG_jit_provider_init", true, NULL);

3 llvm_inline流程分析

《Postgresql源码(128)深入分析JIT中的函数内联llvm_inline》

3.1 例子

drop table t1;
create table t1(i int primary key, j int, k int);
insert into t1 select i, i % 10, i % 100 from generate_series(1,100000000) t(i);


set jit_optimize_above_cost to -1;
set jit_above_cost to 0;
set jit_inline_above_cost to 0;
set jit_tuple_deforming to off;
-- jit_inline_above_cost depend on jit_expressions
set jit_expressions to on;

postgres=# explain select abs(k),abs(k),abs(k),abs(k),abs(k),exp(k),exp(k),exp(k),exp(k),exp(k) from t1;
                                   QUERY PLAN
---------------------------------------------------------------------------------
 Seq Scan on t1  (cost=0.00..529063.31 rows=10000175 width=60)
 JIT:
   Functions: 1
   Options: Inlining true, Optimization false, Expressions true, Deforming false


数据对比

3.2 Inlining与Expressions两种JIT优化分析

上面给的用例函数的编译执行是在投影列中的(无JIT投影列执行可以参考这篇《Postgresql源码(127)投影ExecProject的表达式执行分析》)。

LLVM的执行计划,投影列的计算会进入LLVM堆栈:
在这里插入图片描述

static Datum
ExecRunCompiledExpr(ExprState *state, ExprContext *econtext, bool *isNull)
{
CompiledExprState *cstate = state->evalfunc_private;
ExprStateEvalFunc func;

CheckExprStillValid(state, econtext);

llvm_enter_fatal_on_oom();
func = (ExprStateEvalFunc) llvm_get_function(cstate->context,
 cstate->funcname);
llvm_leave_fatal_on_oom();
Assert(func);

/* remove indirection via this function for future calls */
state->evalfunc = func;

return func(state, econtext, isNull);
}

llvm的总入口是llvm_get_function,llvm_get_function→llvm_compile_module→llvm_inline。

下面重点分析inline的过程:

static void
llvm_compile_module(LLVMJitContext *context)
{
...
if (context->base.flags & PGJIT_INLINE)
{
...
llvm_inline(context->module);
...
}
...

3.2.1 llvm_inline:入参context->module

module的含义:《LLVM(5)ORC实例分析》
在这里插入图片描述
一个更好理解的例子:《LLVM(1)Fibonacci实例》

3.2.2 module在哪里构造?

llvm_mutable_module创建一个名称为pg的空module:
在这里插入图片描述

LLVMModuleRef
llvm_mutable_module(LLVMJitContext *context)
{
llvm_assert_in_fatal_section();

/*
 * If there's no in-progress module, create a new one.
 */
if (!context->module)
{
context->compiled = false;
context->module_generation = llvm_generation++;
context->module = LLVMModuleCreateWithNameInContext("pg", llvm_context);
LLVMSetTarget(context->module, llvm_triple);
LLVMSetDataLayout(context->module, llvm_layout);
}

return context->module;
}

然后再llvm_compile_expr中构造表达式计算函数,加到module里面:
在这里插入图片描述

注意这里通过LLVMAddFunction会把表达式计算用到的函数都加进去(只加用到的)。

本例中:

explain analyze select abs(k),abs(k),abs(k),abs(k),abs(k),exp(k),exp(k),exp(k),exp(k),exp(k) from t1;

llvm_compile_expr函数中LLVMAddFunctionLLVMAddFunction位置
evalexpr_0_0llvm_compile_expr
slot_getsomeattrs_intllvm_compile_expr case EEOP_SCAN_FETCHSOME
int4absllvm_compile_expr case EEOP_FUNCEXPR_STRICT
llvm.lifetime.end.p0i8llvm_compile_expr case EEOP_FUNCEXPR_STRICT
i4todllvm_compile_expr case EEOP_FUNCEXPR_STRICT
dexpllvm_compile_expr case EEOP_FUNCEXPR_STRICT

只有上面5个函数被加到module了,所以后面inline只需要考虑这后四个个函数就好了,第一个是表达式计算入口。

llvm_compile_expr是怎么构造llvm函数的?

《Postgresql源码(113)表达式JIT计算简单分析》

llvm_compile_expr函数是严格对应ExecInterpExpr表达式计算函数,例如计算函数表达式的结果EEOP_FUNCEXPR_STRICT分支:

  • 右侧是PG的表达式计算逻辑。
  • 左侧是对应的JIT build逻辑。
  • 注意,右侧会真正执行,左侧只是build不执行。两边所有分支是一一对应的。
    在这里插入图片描述

3.2.3 llvm_inline函数

void
llvm_inline(LLVMModuleRef M)
{
LLVMContextRef lc = LLVMGetModuleContext(M);
// ? 下面有解释
llvm::Module *mod = llvm::unwrap(M);
  • llvm::unwrap 是一个辅助函数,用于将 C 语言风格的接口转换为 C++ 风格的接口。M是一个来自 LLVM C API 的类型(LLVMModuleRef),这是一个指向 LLVM 模块的指针,但它被封装为一个不透明的指针类型以便在 C 环境中使用。llvm::unwrap 函数将这个不透明的指针转换为一个指向 llvm::Module 的指针,这样就可以在 C++ 代码中使用 LLVM 的 C++ API 来操作这个模块了。
  • (llvm::module 是 LLVM 中的一个类,它代表了一个完整的 LLVM IR模块,这个模块可以包含函数、全局变量、符号等。在 LLVM 的 C++ API 中可以直接使用 llvm::Module 类型的对象)

std::unique_ptr<ImportMapTy> globalsToInline = llvm_build_inline_plan(lc, mod);
if (!globalsToInline)
return;
llvm_execute_inline_plan(mod, globalsToInline.get());
}

3.2.4 准备inline需要执行的部分:lvm_inline→llvm_build_inline_plan

新问题:

  1. 前面 llvm_compile_expr是怎么构造llvm函数的 这部分内容解释了Expressions表达式jit优化的方法。那么下一步就是要执行构造好的module了。
  2. 这里有几个新的问题
    • 问题一:表达式计算已经被剪枝,只保留了需要计算的几个分支,并构造func已经保存module中。但留下的分支中还是需要调用其他PG的C函数才能完成计算,例如abs的计算函数int8abs(),这些函数是C语言编译的,而jit如果需要inline这个int8abs函数的话,是无法直接引用C语言函数的,这里必须把int8abs函数编译成.bc也就是IR CODE才可以整合到llvm的编译中,inline才有可能发生。表达式计算可能会用到很多函数,所以全部的.c文件除了正常cc编译之外,还需要clang编译为.bc文件,保存IR CORE提供给JIT使用。
    • 问题二:拿到函数IR后,是直接无脑inline还是做一些判断再inline?如果目前函数逻辑复杂,调用层次较深,inline的代价是不是比直接执行更大?(决定是否inline,是需要单独计算的)
    • 问题三:inline是PG来做,还是交给LLVM编译器去做?如果交给LLVM编译器做,怎么告知编译器某个函数需要inline?(LLVM编译器做,在IR中配置available_externally标记)

llvm_build_inline_plan 执行流程

static std::unique_ptr<ImportMapTy>
llvm_build_inline_plan(LLVMContextRef lc, llvm::Module *mod)
{
std::unique_ptr<ImportMapTy> globalsToInline(new ImportMapTy());
FunctionInlineStates functionStates;
InlineWorkList worklist;

InlineSearchPath defaultSearchPath;
add_module_to_inline_search_path(defaultSearchPath, "$libdir/postgres");
...
for (const llvm::Function &funcDecl : mod->functions())
{
InlineWorkListItem item = {};
FunctionInlineState inlineState = {};

  • 只ADD了还没BUILD所以有的函数只有定义,例如evalexpr_0_0llvm.lifetime.end.p0i8
  • 其他函数会正常加入worklist,例如int4absdexp
if (!funcDecl.isDeclaration())
continue;
...

item.symbolName = funcDecl.getName();
item.searchpath = defaultSearchPath;
worklist.push_back(item);
inlineState.costLimit = inline_initial_cost;
inlineState.processed = false;
inlineState.inlined = false;
inlineState.allowReconsidering = false;
functionStates[funcDecl.getName()] = inlineState;
}
  • 本案例中会有四个函数加入worklist:slot_getsomeattrs_int、int4abs、i4tod、dexp
while (!worklist.empty())
{
InlineWorkListItem item = worklist.pop_back_val();
llvm::StringRef symbolName = item.symbolName;
char *cmodname;
char *cfuncname;
FunctionInlineState &inlineState = functionStates[symbolName];
llvm::GlobalValue::GUID funcGUID;

llvm_split_symbol_name(symbolName.data(), &cmodname, &cfuncname);

funcGUID = llvm::GlobalValue::getGUID(cfuncname);

for (const auto &gvs : summaries_for_guid(item.searchpath, funcGUID))
{
  • 从searchpath也就是postgresql.index.bc里面搜索funcGUID,找到modPath。
  • 例如modPath=postgres/utils/adt/float.bc
const llvm::FunctionSummary *fs;
llvm::StringRef modPath = gvs->modulePath();
llvm::Module *defMod;
llvm::Function *funcDef;

fs = llvm::cast<llvm::FunctionSummary>(gvs);

if ((int) fs->instCount() > inlineState.costLimit)
{
ilog(DEBUG1, "ineligibile to import %s due to early threshold: %u vs %u",
 symbolName.data(), fs->instCount(), inlineState.costLimit);
inlineState.allowReconsidering = true;
continue;
}
  • modPath是函数所在的bc文件路径。
  • 在load_module_cached中调用load_module,调用LLVMCreateMemoryBufferWithContentsOfFile、LLVMGetBitcodeModuleInContext2拿到module。
defMod = load_module_cached(lc, modPath);
if (defMod->materializeMetadata())
elog(FATAL, "failed to materialize metadata");

funcDef = defMod->getFunction(cfuncname);
  • 到这里,通过thinlto生成的index文件的指引,找到函数所在的bc文件,最后拿到了func的bitcode。
...

llvm::StringSet<> importVars;
llvm::SmallPtrSet<const llvm::Function *, 8> visitedFunctions;
int running_instcount = 0;

在这里插入图片描述

if (function_inlinable(*funcDef,
   inlineState.costLimit,
   functionStates,
   worklist,
   item.searchpath,
   visitedFunctions,
   running_instcount,
   importVars))
{
  • 整体指令数不能超过150个,超过就不在inline了,Inline太多会造成代码膨胀。
if (running_instcount > inlineState.costLimit)
{
ilog(DEBUG1, "skipping inlining of %s due to late threshold %d vs %d",
 symbolName.data(), running_instcount, inlineState.costLimit);
inlineState.allowReconsidering = true;
continue;
}

ilog(DEBUG1, "inline top function %s total_instcount: %d, partial: %d",
 symbolName.data(), running_instcount, fs->instCount());

  • 这里把当前函数,例如dexp放到数组中。
  • function_inlinable中没放吗?因为function_inlinable只把dexp调用到的函数放进去了。
  • 把全部需要inline的函数名、全局变量名记录到modGlobalsToInline中。
importVars.insert(symbolName);

{
llvm::StringSet<> &modGlobalsToInline = (*globalsToInline)[modPath];
for (auto& importVar : importVars)
modGlobalsToInline.insert(importVar.first());
}
  • 标记已经Inline
inlineState.inlined = true;

break;
}
else
{
ilog(DEBUG1, "had to skip inlining %s",
 symbolName.data());
}
}

  • 更新FunctionInlineState。
  • 注意:
    • functionStates数组存放了所有inline的函数的信息。
    • globalsToInline是map结构{modPath, StringSet},根据"postgres/utils/adt/float.bc"找到StringSet,在StringSet中保存了dexp、dexp函数调用到的其他函数、dexp和调用到函数所有使用到的全局变量。
inlineState.processed = true;
}

return globalsToInline;
}

3.2.5 执行inline的部分:lvm_inline→llvm_execute_inline_plan

遍历上述结果集中的函数,如果确定最终需要inline的,配置AvailableExternallyLinkage标记即可。其他留给llvm编译器去做。

static void
llvm_execute_inline_plan(llvm::Module *mod, ImportMapTy *globalsToInline)
{
...
for (const auto& toInline : *globalsToInline)
{
const llvm::StringRef& modPath = toInline.first();
const llvm::StringSet<>& modGlobalsToInline = toInline.second;

std::unique_ptr<llvm::Module> importMod(std::move((*module_cache)[modPath]));
module_cache->erase(modPath);

for (auto &glob: modGlobalsToInline)
{
llvm::StringRef SymbolName = glob.first();

llvm::GlobalValue *valueToImport = importMod->getNamedValue(funcname);

if (llvm::isa<llvm::Function>(valueToImport))
{
llvm::Function *F = llvm::dyn_cast<llvm::Function>(valueToImport);
typedef llvm::GlobalValue::LinkageTypes LinkageTypes;

...

if (valueToImport->hasExternalLinkage())
{
valueToImport->setLinkage(LinkageTypes::AvailableExternallyLinkage);
}
}
}
}
}

https://llvm.org/docs/LangRef.html#linkage-types

在这里插入图片描述
在这里插入图片描述

  • 默认link方式 ExternalLinkage: 符号可能在其他模块中被引用,LLVM 在决定是否内联会更加谨慎。可能不会内联这些符号,因为它需要确保符号在模块外部仍然可用。
  • 主动修改为 AvailableExternallyLinkage: 从链接器的角度来看,available_externally 全局变量相当于外部声明。存在的目的是进行内联和其他优化。

GlobalValue.h

class GlobalValue : public Constant {
public:
  /// An enumeration for the kinds of linkage for global values.
  enum LinkageTypes {
    ExternalLinkage = 0,///< Externally visible function
    AvailableExternallyLinkage, ///< Available for inspection, not emission.    <<<<<<<<<
    LinkOnceAnyLinkage, ///< Keep one copy of function when linking (inline)
    LinkOnceODRLinkage, ///< Same, but only replaced by something equivalent.
    WeakAnyLinkage,     ///< Keep one copy of named function when linking (weak)
    WeakODRLinkage,     ///< Same, but only replaced by something equivalent.
    AppendingLinkage,   ///< Special purpose, only applies to global arrays
    InternalLinkage,    ///< Rename collisions when linking (static functions).
    PrivateLinkage,     ///< Like Internal, but omit from symbol table.
    ExternalWeakLinkage,///< ExternalWeak linkage description.
    CommonLinkage       ///< Tentative definitions.
  };

当函数被标记为AvailableExternallyLinkage时,LLVM优化器有可能会内联这些函数。但不是一定会发生,内联决策是llvm内联启发式算法做出的,会考虑很多因素:函数的大小、复杂性、调用频率、调用上下文等等。如果llvm决定内联一个函数,它会将函数的代码直接插入到每个调用点。

从module里面读取一下inline后的IR代码,发现函数已经有了available_externally标记。
在这里插入图片描述

3.2.6 内联后的效果

决定不做内联
在这里插入图片描述
优化后,还是正常调用:
在这里插入图片描述

决定内联
在这里插入图片描述

原来调用的位置变成什么了?
在这里插入图片描述


原文地址:https://blog.csdn.net/jackgo73/article/details/145255807

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!