前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(129)JIT函数中如何使用PG的类型llvmjit_types

Postgresql源码(129)JIT函数中如何使用PG的类型llvmjit_types

作者头像
mingjie
发布2024-05-24 18:15:01
550
发布2024-05-24 18:15:01
举报
文章被收录于专栏:Postgresql源码分析

0 总结

llvmjit_types文件分三部分

  1. 类型定义:llvm通过变量找到对应结构体的定义,在通过结构体内的偏移量宏使用成员变量。
  2. 模版函数定义:
    • 第一:AttributeTemplate被当做一个函数属性的模板(例如nofree、nosync等clang前端为函数增加的属性),AttributeTemplate是一个简单函数被clang赋予了一套属性,这些属性在后续处理时倾向被内联。所以在生成其他函数时,也想用这一套属性,让其他的函数(例如表达式计算函数)也能被内联处理。
    • 第二:作为一些入参是PG_FUNCTION_ARGS的PG函数做函数类型模版。
      • v_fn = LLVMAddFunction(mod, funcname, LLVMGetFunctionType(AttributeTemplate));
  3. 函数引用:这些函数是所有llvmjit会用到的函数,这里用数组引用后,会在llvmjit_types.bc文件中生成引用信息,在使用llvm调用函数时,可以从这里找到函数类型,用LLVMAddFunction增加函数到mod中。
代码语言:javascript
复制
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:

代码语言:javascript
复制
 */
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函数,把类型读取出来保存到全局变量中:

代码语言:javascript
复制
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");
}

这样做可以很方便的同步类型定义,但这样无法同步结构体内变量的偏移量,只能把偏移量维护在结构体中了,所以我们会看到结构体中多了一些宏来表示成员变量的位置:

代码语言:javascript
复制
typedef struct TupleTableSlot
{
	NodeTag		type;
#define FIELDNO_TUPLETABLESLOT_FLAGS 1
	uint16		tts_flags;		/* Boolean states */
#define FIELDNO_TUPLETABLESLOT_NVALID 2
	AttrNumber	tts_nvalid;		/* # of valid values in tts_values */
	const TupleTableSlotOps *const tts_ops; /* implementation of slot */
#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4
	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
#define FIELDNO_TUPLETABLESLOT_VALUES 5
	Datum	   *tts_values;		/* current per-attribute values */
#define FIELDNO_TUPLETABLESLOT_ISNULL 6
	bool	   *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流程:

  1. 从econtext中拿到tts赋给scanslot。
  2. 走EEOP_SCAN_FETCHSOME分支计算econtext。
代码语言:javascript
复制
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流程:

代码语言:javascript
复制
	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)
代码语言:javascript
复制
	/*
	 * 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 中的代码
代码语言:javascript
复制
		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中增加代码:
代码语言:javascript
复制
		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);
代码语言:javascript
复制
/*
 * 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执行。
代码语言:javascript
复制
		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)

代码语言:javascript
复制
; 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 4
  store i8 1, ptr %2, align 4
  ret 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属性】

代码语言:javascript
复制
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一致的:

代码语言:javascript
复制
; 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 函数指针数组的作用

代码语言:javascript
复制
void	   *referenced_functions[] =
{
	ExecAggInitGroup,
	ExecAggCopyTransValue,
	...
	ExecEvalJsonCoercionFinish,
	ExecEvalJsonExprPath,
	MakeExpandedObjectReadOnlyInternal,
	slot_getmissingattrs,
	slot_getsomeattrs_int,
	strlen,
	varsize_any,
	ExecInterpExprStillValid,
};

这些函数是所有llvmjit会用到的函数,这里用数组引用后,会在llvmjit_types.bc文件中生成引用信息:

代码语言:javascript
复制
^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中。

代码语言:javascript
复制
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;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 总结
  • 1 类型同步到llvm
    • 1.1 类型同步使用实例
    • 2 AttributeTemplate函数的作用
    • 3 函数指针数组的作用
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档