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.Pollvmjit_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.Pollvmjit_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
编译命令拼接:
-
第一段: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
-
第二段: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
-
第三段:-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.Pollvmjit_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.Pollvmjit_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
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编译命令拼接
-
第一段: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
-
第二段: 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
-
第三段:-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编译命令拼接
- 第一段: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
- 第二段: llvm-config --ldflags :
-L/data02/mingjie/bin/llvm15/lib
- 第三段:-Wl,--as-needed -Wl,-rpath,'/data02/mingjie/pgroot99/pghome/lib',--enable-new-dtags -fvisibility=hidden
- 第四段: 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
- 第五段: 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&)
- de-mangling:
- mangling:
_ZNK4llvm17X86TargetLowering23getStackProbeSymbolNameERKNS_15MachineFunctionE
- de-mangling:
llvm::X86TargetLowering::getStackProbeSymbolName(llvm::MachineFunction const&) const
- de-mangling:
所以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。
c
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 例子
sql
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
关JIT:9.3秒
sql
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..529063.31 rows=10000175 width=60) (actual time=0.039..8863.584 rows=10000000 loops=1)
Planning Time: 0.081 ms
Execution Time: 9343.666 ms
开JIT:5.9秒
sql
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Seq Scan on t1 (cost=0.00..529063.31 rows=10000175 width=60) (actual time=12.100..5460.438 rows=10000000 loops=1)
Planning Time: 0.081 ms
JIT:
Functions: 1
Options: Inlining true, Optimization false, Expressions true, Deforming false
Timing: Generation 0.389 ms, Inlining 5.567 ms, Optimization 1.188 ms, Emission 5.301 ms, Total 12.445 ms
Execution Time: 5921.711 ms
(7 rows)
perf stat数据对比
事件 | no jit | jit | 分析 |
---|---|---|---|
task-clock | 3.92 | 4.32 | 执行程序的CPU时间 |
context-switches | 13 | 13 | 上下文切换次数,相同 |
cpu-migrations | 2 | 2 | CPU迁移次数,相同 |
page-faults | 282 | 282 | 内存缺页,相同 |
cycles | 9,896,281 | 10,763,253 | CPU花费的周期 |
instructions | 8,902,749(0.90 insn per cycle) | 9,573,998(0.89 insn per cycle | 这段时间内完成的CPU指令,每个指令都包含多个步骤,每个步骤由CPU的一个叫做功能单元组件处理,每个步骤至少花费一个cycle周期。insn per cycle表示一个CPU周期内完成几个指令,越高越好,这里没有太大差距。 |
branches | 1,719,940 | 1,833,970 | 分支预测的次数 |
branch-misses | 51,247 | 54,410 | 分支预测失败的次数 |
L1-dcache-loads | 1,976,835 | 3,067,449 | 一级数据缓存读取次数,差距非常大 |
L1-dcache-load-misses | 75,055 | 69,671 | 一级数据缓存读取失败次数 |
LLC-loads | 52,070 | 54,139 | last level cache读取次数 |
LLC-load-misses | 29,130 | 22,209 | last level cache读取失败次数,最后一级缓存未命中 |
总执行时间 | 46.686925078 | 29.740983598 |
3.2 Inlining与Expressions两种JIT优化分析
上面给的用例函数的编译执行是在投影列中的(无JIT投影列执行可以参考这篇《Postgresql源码(127)投影ExecProject的表达式执行分析》)。
LLVM的执行计划,投影列的计算会进入LLVM堆栈:
c
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的过程:
c
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:
c
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函数中LLVMAddFunction | LLVMAddFunction位置 |
---|---|
evalexpr_0_0 | llvm_compile_expr |
slot_getsomeattrs_int | llvm_compile_expr case EEOP_SCAN_FETCHSOME |
int4abs | llvm_compile_expr case EEOP_FUNCEXPR_STRICT |
llvm.lifetime.end.p0i8 | llvm_compile_expr case EEOP_FUNCEXPR_STRICT |
i4tod | llvm_compile_expr case EEOP_FUNCEXPR_STRICT |
dexp | llvm_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函数
c
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 类型的对象)
c
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
新问题:
- 前面 llvm_compile_expr是怎么构造llvm函数的 这部分内容解释了Expressions表达式jit优化的方法。那么下一步就是要执行构造好的module了。
- 这里有几个新的问题
- 问题一 :表达式计算已经被剪枝,只保留了需要计算的几个分支,并构造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标记)
- 问题一 :表达式计算已经被剪枝,只保留了需要计算的几个分支,并构造func已经保存module中。但留下的分支中还是需要调用其他PG的C函数才能完成计算,例如abs的计算函数int8abs(),这些函数是C语言编译的,而jit如果需要inline这个int8abs函数的话,是无法直接引用C语言函数的,这里必须把int8abs函数编译成
llvm_build_inline_plan 执行流程
c
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;
- 回答问题一 :注意这里的searchpath是什么,怎么来的请看这篇: 《LLVM的ThinLTO编译优化技术在Postgresql中的应用》
c
add_module_to_inline_search_path(defaultSearchPath, "$libdir/postgres");
...
for (const llvm::Function &funcDecl : mod->functions())
{
InlineWorkListItem item = {};
FunctionInlineState inlineState = {};
- 只ADD了还没BUILD所以有的函数只有定义,例如
evalexpr_0_0
、llvm.lifetime.end.p0i8
。 - 其他函数会正常加入worklist,例如
int4abs
、dexp
。
c
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
c
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
c
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。
c
defMod = load_module_cached(lc, modPath);
if (defMod->materializeMetadata())
elog(FATAL, "failed to materialize metadata");
funcDef = defMod->getFunction(cfuncname);
- 到这里,通过thinlto生成的index文件的指引,找到函数所在的bc文件,最后拿到了func的bitcode。
c
...
llvm::StringSet<> importVars;
llvm::SmallPtrSet<const llvm::Function *, 8> visitedFunctions;
int running_instcount = 0;
- 回答问题二:拿到了函数的bitcode后确认是否能inline?
- 能否inline是一系列规则、成本决定的,具体分析在这篇:《Postgresql中JIT函数能否inline的依据function_inlinable》
c
if (function_inlinable(*funcDef,
inlineState.costLimit,
functionStates,
worklist,
item.searchpath,
visitedFunctions,
running_instcount,
importVars))
{
- 整体指令数不能超过150个,超过就不在inline了,Inline太多会造成代码膨胀。
c
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中。
c
importVars.insert(symbolName);
{
llvm::StringSet<> &modGlobalsToInline = (*globalsToInline)[modPath];
for (auto& importVar : importVars)
modGlobalsToInline.insert(importVar.first());
}
- 标记已经Inline
c
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和调用到函数所有使用到的全局变量。
c
inlineState.processed = true;
}
return globalsToInline;
}
3.2.5 执行inline的部分:lvm_inline→llvm_execute_inline_plan
遍历上述结果集中的函数,如果确定最终需要inline的,配置AvailableExternallyLinkage标记即可。其他留给llvm编译器去做。
c
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 内联后的效果
决定不做内联
优化后,还是正常调用:
决定内联
原来调用的位置变成什么了?