0 總結
llvmjit_types文件分三部分
- 類型定義:llvm通過變量找到對應結構體的定義,在通過結構體內的偏移量宏使用成員變量。
- 模版函數定義:
- 第一:AttributeTemplate被當做一個函數屬性的模板(例如nofree、nosync等clang前端為函數增加的屬性),AttributeTemplate是一個簡單函數被clang賦予了一套屬性,這些屬性在后續處理時傾向被內聯。所以在生成其他函數時,也想用這一套屬性,讓其他的函數(例如表達式計算函數)也能被內聯處理。
- 第二:作為一些入參是PG_FUNCTION_ARGS的PG函數做函數類型模版。
v_fn = LLVMAddFunction(mod, funcname, LLVMGetFunctionType(AttributeTemplate));
- 函數引用:這些函數是所有llvmjit會用到的函數,這里用數組引用后,會在llvmjit_types.bc文件中生成引用信息,在使用llvm調用函數時,可以從這里找到函數類型,用LLVMAddFunction增加函數到mod中。
PGFunction TypePGFunction;
size_t TypeSizeT;
bool TypeStorageBool;
...
...
==========================extern Datum AttributeTemplate(PG_FUNCTION_ARGS);
Datum
AttributeTemplate(PG_FUNCTION_ARGS)
{AssertVariableIsOfType(&AttributeTemplate, PGFunction);PG_RETURN_NULL();
}
...
...
==========================void *referenced_functions[] =
{ExecAggInitGroup,ExecAggCopyTransValue,ExecEvalPreOrderedDistinctSingle,ExecEvalPreOrderedDistinctMulti,ExecEvalAggOrderedTransDatum,ExecEvalAggOrderedTransTuple,ExecEvalArrayCoerce,...
}
1 類型同步到llvm
- 總結:類型同步。
- 解釋:在jit函數生成過程中,需要引用pg代碼中定義好的結構,正常的做法是在llvmjit_types中重新創建出來告訴llvm類型定義信息,但這樣做工作量很大且兩份相同的代碼也容易出錯。目前的做法是維護一個小文件llvmjit_types.c,引用了jit所需的每一種類型:
llvmjit_types.c:
*/
PGFunction TypePGFunction;
size_t TypeSizeT;
bool TypeStorageBool;ExecEvalSubroutine TypeExecEvalSubroutine;
ExecEvalBoolSubroutine TypeExecEvalBoolSubroutine;NullableDatum StructNullableDatum;
AggState StructAggState;
AggStatePerGroupData StructAggStatePerGroupData;
AggStatePerTransData StructAggStatePerTransData;
ExprContext StructExprContext;
ExprEvalStep StructExprEvalStep;
ExprState StructExprState;
FunctionCallInfoBaseData StructFunctionCallInfoData;
HeapTupleData StructHeapTupleData;
HeapTupleHeaderData StructHeapTupleHeaderData;
MemoryContextData StructMemoryContextData;
TupleTableSlot StructTupleTableSlot;
HeapTupleTableSlot StructHeapTupleTableSlot;
MinimalTupleTableSlot StructMinimalTupleTableSlot;
TupleDescData StructTupleDescData;
PlanState StructPlanState;
MinimalTupleData StructMinimalTupleData;
llvmjit_types.c里面定義了一些類型的變量,這些變量的bitcode在初始化時(llvm_create_types),會加載到module中(llvm_types_module)。然后再通過llvm_pg_var_type函數,把類型讀取出來保存到全局變量中:
static void
llvm_create_types(void)
{...snprintf(path, MAXPGPATH, "%s/%s", pkglib_path, "llvmjit_types.bc");if (LLVMCreateMemoryBufferWithContentsOfFile(path, &buf, &msg))...if (LLVMParseBitcodeInContext2(llvm_context, buf, &llvm_types_module))...LLVMDisposeMemoryBuffer(buf);TypeSizeT = llvm_pg_var_type("TypeSizeT");TypeParamBool = load_return_type(llvm_types_module, "FunctionReturningBool");TypeStorageBool = llvm_pg_var_type("TypeStorageBool");TypePGFunction = llvm_pg_var_type("TypePGFunction");StructNullableDatum = llvm_pg_var_type("StructNullableDatum");StructExprContext = llvm_pg_var_type("StructExprContext");StructExprEvalStep = llvm_pg_var_type("StructExprEvalStep");StructExprState = llvm_pg_var_type("StructExprState");StructFunctionCallInfoData = llvm_pg_var_type("StructFunctionCallInfoData");StructMemoryContextData = llvm_pg_var_type("StructMemoryContextData");StructTupleTableSlot = llvm_pg_var_type("StructTupleTableSlot");StructHeapTupleTableSlot = llvm_pg_var_type("StructHeapTupleTableSlot");StructMinimalTupleTableSlot = llvm_pg_var_type("StructMinimalTupleTableSlot");StructHeapTupleData = llvm_pg_var_type("StructHeapTupleData");StructHeapTupleHeaderData = llvm_pg_var_type("StructHeapTupleHeaderData");StructTupleDescData = llvm_pg_var_type("StructTupleDescData");StructAggState = llvm_pg_var_type("StructAggState");StructAggStatePerGroupData = llvm_pg_var_type("StructAggStatePerGroupData");StructAggStatePerTransData = llvm_pg_var_type("StructAggStatePerTransData");StructPlanState = llvm_pg_var_type("StructPlanState");StructMinimalTupleData = llvm_pg_var_type("StructMinimalTupleData");AttributeTemplate = LLVMGetNamedFunction(llvm_types_module, "AttributeTemplate");ExecEvalSubroutineTemplate = LLVMGetNamedFunction(llvm_types_module, "ExecEvalSubroutineTemplate");ExecEvalBoolSubroutineTemplate = LLVMGetNamedFunction(llvm_types_module, "ExecEvalBoolSubroutineTemplate");
}
這樣做可以很方便的同步類型定義,但這樣無法同步結構體內變量的偏移量,只能把偏移量維護在結構體中了,所以我們會看到結構體中多了一些宏來表示成員變量的位置:
typedef struct TupleTableSlot
{NodeTag type;
#define FIELDNO_TUPLETABLESLOT_FLAGS 1uint16 tts_flags; /* Boolean states */
#define FIELDNO_TUPLETABLESLOT_NVALID 2AttrNumber tts_nvalid; /* # of valid values in tts_values */const TupleTableSlotOps *const tts_ops; /* implementation of slot */
#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */
#define FIELDNO_TUPLETABLESLOT_VALUES 5Datum *tts_values; /* current per-attribute values */
#define FIELDNO_TUPLETABLESLOT_ISNULL 6bool *tts_isnull; /* current per-attribute isnull flags */MemoryContext tts_mcxt; /* slot itself is in this context */ItemPointerData tts_tid; /* stored tuple's tid */Oid tts_tableOid; /* table oid of tuple */
} TupleTableSlot;
1.1 類型同步使用實例
非JIT表達式計算EEOP_SCAN_FETCHSOME流程:
- 從econtext中拿到tts賦給scanslot。
- 走EEOP_SCAN_FETCHSOME分支計算econtext。
ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)TupleTableSlot *scanslot;...scanslot = econtext->ecxt_scantuple;...EEO_SWITCH(){...EEO_CASE(EEOP_SCAN_FETCHSOME){CheckOpSlotCompatibility(op, scanslot);slot_getsomeattrs(scanslot, op->d.fetch.last_var);EEO_NEXT();}...}
JIT表達式計算EEOP_SCAN_FETCHSOME流程:
eval_fn = LLVMAddFunction(mod, funcname,llvm_pg_var_func_type("ExecInterpExprStillValid"));v_econtext = LLVMGetParam(eval_fn, 1);LLVMValueRef v_scanslot;
- 下面執行的操作等價與
scanslot = econtext->ecxt_scantuple;
從結構體中拿一個成員變量的值。
IR中的結構體是不會記錄成員名稱的,所以需要告知llvm成員變量在結構體中的偏移位置FIELDNO_EXPRCONTEXT_SCANTUPLE = 1。 - LLVMBuildLoad從內存中加載值。
- LLVMStructGetTypeAtIndex拿到結構體指定位置的類型。
- LLVMBuildStructGEP拿到結構體1位置的成員地址(GEP=GetElementPtr)
/** l_load_struct_gep = * * LLVMBuildLoad(b,* LLVMStructGetTypeAtIndex(StructExprContext, 1),* LLVMBuildStructGEP(b, StructExprContext, v_econtext, 1, "")* "v_scanslot")*/v_scanslot = l_load_struct_gep(b,StructExprContext,v_econtext,FIELDNO_EXPRCONTEXT_SCANTUPLE,"v_scanslot");...case EEOP_SCAN_FETCHSOME:{TupleDesc desc = NULL;LLVMValueRef v_slot;LLVMBasicBlockRef b_fetch;LLVMValueRef v_nvalid;LLVMValueRef l_jit_deform = NULL;const TupleTableSlotOps *tts_ops = NULL;
- 前面已經為每一個case都創建了一個BasicBlock。
- l_bb_before_v在當前switch的BasicBlock前增加了一個新的Block。
- 新的Block的語義:
if (v_nvalid >= op->d.fetch.last_var) // 跳轉到下一個case的Block:opblocks[opno + 1]
else // 繼續執行 當前Block 中的代碼
b_fetch = l_bb_before_v(opblocks[opno + 1],"op.%d.fetch", opno);v_slot = v_scanslot;v_nvalid =l_load_struct_gep(b,StructTupleTableSlot,v_slot,FIELDNO_TUPLETABLESLOT_NVALID,"");LLVMBuildCondBr(b,LLVMBuildICmp(b, LLVMIntUGE, v_nvalid,l_int16_const(lc, op->d.fetch.last_var),""),opblocks[opno + 1], b_fetch);
- 將builder的插入點調整到b_fetch塊的末尾,繼續在b_fetch中增加代碼:
LLVMPositionBuilderAtEnd(b, b_fetch);{LLVMValueRef params[2];params[0] = v_slot;params[1] = l_int32_const(lc, op->d.fetch.last_var);
- 創建一個調用指令,等價與
slot_getsomeattrs(scanslot, op->d.fetch.last_var);
/** API調用:* LLVMBuildCall2(* b, * LLVMGetFunctionType(LLVMGetNamedFunction(llvm_types_module, "slot_getsomeattrs_int")), * LLVMAddFunction(mod, "slot_getsomeattrs_int", LLVMGetFunctionType(LLVMGetNamedFunction(llvm_types_module, "slot_getsomeattrs_int"))), * params, * 2,* "");*/l_call(b,llvm_pg_var_func_type("slot_getsomeattrs_int"),llvm_pg_func(mod, "slot_getsomeattrs_int"),params, lengthof(params), "");}
- 繼續到下一個Block執行。
LLVMBuildBr(b, opblocks[opno + 1]);break;}
2 AttributeTemplate函數的作用
- 第一:AttributeTemplate被當做一個函數屬性的模板(例如nofree、nosync等clang前端為函數增加的屬性),AttributeTemplate是一個簡單函數被clang賦予了一套屬性,這些屬性在后續處理時傾向被內聯。所以在生成其他函數時,也想用這一套屬性,讓其他的函數(例如表達式計算函數)也能被內聯處理。
- 第二:作為一些入參是PG_FUNCTION_ARGS的PG函數做函數類型模版。
v_fn = LLVMAddFunction(mod, funcname, LLVMGetFunctionType(AttributeTemplate));
下面看下AttributeTemplate有哪些屬性:(llvmjit_types.ll)
; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) uwtable
define dso_local i64 @AttributeTemplate(ptr nocapture noundef writeonly %0) local_unnamed_addr #0 {%2 = getelementptr inbounds %struct.FunctionCallInfoBaseData, ptr %0, i64 0, i32 4store i8 1, ptr %2, align 4ret i64 0
}
可以看到函數的屬性:
- mustprogress: 函數必須在有限的步驟內取得進展,不能無限循環。
- nofree: 函數內不會進行內存釋放操作。
- norecurse: 函數不會遞歸調用自己。
- nosync: 函數內不會進行同步操作,如互斥鎖。
- nounwind: 函數不會拋出異常。
- willreturn: 函數保證最終會返回。
- memory(argmem: write): 函數可能會寫入傳入的參數內存。
- memory(argmem: write): May only write argument memory.
- uwtable: 函數具有一個“Unwind Table”,在拋出異常時用于幫助恢復棧狀態。
函數參數的屬性:
- nocapture: 函數不會保存指針的副本,不會使指針逃逸到函數外部。
- noundef: 參數不會是一個未定義的值。
- writeonly: 函數只會寫入指向的內存,不會讀取它。
在構造表達式計算函數時,使用llvm_copy_attributes將AttributeTemplate函數的屬性拷貝到了表達式計算函數上面:【AttributeTemplate屬性】 → 【evalexpr_3_0屬性】
llvm_compile_expr/* create function */eval_fn = LLVMAddFunction(mod, funcname,llvm_pg_var_func_type("ExecInterpExprStillValid"));......llvm_copy_attributes(AttributeTemplate, eval_fn);
拷貝后的evalexpr_3_0函數,可以看到函數屬性和參數屬性都已經和AttributeTemplate一致的:
; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) uwtable
define i64 @evalexpr_3_0(ptr nocapture noundef writeonly %0, ptr %1, ptr %2) #0 {
entry:
3 函數指針數組的作用
void *referenced_functions[] =
{ExecAggInitGroup,ExecAggCopyTransValue,...ExecEvalJsonCoercionFinish,ExecEvalJsonExprPath,MakeExpandedObjectReadOnlyInternal,slot_getmissingattrs,slot_getsomeattrs_int,strlen,varsize_any,ExecInterpExprStillValid,
};
這些函數是所有llvmjit會用到的函數,這里用數組引用后,會在llvmjit_types.bc文件中生成引用信息:
^45 = gv: (name: "ExecEvalSubPlan") ; guid = 11106370218607637427
^46 = gv: (name: "ExecEvalCurrentOfExpr") ; guid = 11138569114739303931
^47 = gv: (name: "slot_getsomeattrs_int") ; guid = 11630412520694092271
^48 = gv: (name: "MakeExpandedObjectReadOnlyInternal") ; guid = 11922486409292019551
^49 = gv: (name: "ExecEvalFieldStoreDeForm") ; guid = 11938814657973506909
在使用llvm調用函數時,可以從這里找到函數類型,用LLVMAddFunction增加函數聲明到mod中。
LLVMValueRef
llvm_pg_func(LLVMModuleRef mod, const char *funcname)... v_srcfn = LLVMGetNamedFunction(llvm_types_module, funcname);...v_fn = LLVMAddFunction(mod,funcname,LLVMGetFunctionType(v_srcfn));...return v_fn;
}