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

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中。
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流程:

  1. 从econtext中拿到tts赋给scanslot。
  2. 走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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/14562.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot项目中redis序列化和反序列化LocalDateTime失败

实体类中包含了LocalDateTime 类型的属性,把实体类数据存入Redis后变成这样: 此时,存入redis不会报错,但是从redis获取的时候,会报错: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Ca…

Springboot项目打包:将依赖的jar包输出到指定目录

场景 公司要对springboot项目依赖的jar包进行升级,但是遇到一个问题,项目打包之后,没办法看到他里面依赖的jar包,版本到底是不是升上去了,没办法看到。 下面是项目打的jar包 我们通过反编译工具jdgui,来…

VUE3和VUE2

VUE3和VUE2 上一篇文章中,我们对VUE3进行了一个初步的认识了解,本篇文章我们来进一步学习一下,顺便看一下VUE2的写法VUE3是否能做到兼容😀。 一、新建组件 我们在components中新建一个组件,名称为Peron,…

缓存降级

当Redis缓存出现问题或者无法正常工作时,需要有一种应对措施,避免直接访问数据库而导致整个系统瘫痪。缓存降级就是这样一种机制。 主要的缓存降级策略包括: 本地缓存降级 当Redis缓存不可用时,可以先尝试使用本地进程内缓存,如Guava Cache或Caffeine等。这样可以减少对Redis…

阴影映射(线段树)

实时阴影是电子游戏中最为重要的画面效果之一。在计算机图形学中,通常使用阴影映射方法来实现实时阴影。 游戏开发部正在开发一款 2D 游戏,同时希望能够在 2D 游戏中模仿 3D 游戏的光影效果,请帮帮游戏开发部! 给定 x-y 平面上的…

再次学习History.scrollRestoration

再次学习History.scrollRestoration 之前在react.dev的源代码中了解到了这个HIstory的属性,当时写了一篇笔记来记录我对它的理解,现在看来还是一知半解。所以今天打算重新学习一下这个属性,主要从属性以及所属对象的介绍、使用方法&#xff0…

每日一题(2)——100~200间的素数

方法一&#xff1a; public class suCount {public static void main(String[] args){int sum0;c1:for(int i100;i<200;i){for(int j2;j<i;j){if(i%j0)continue c1;//continue中断循环&#xff0c;且返回外层循环&#xff0c;进入下一次遍历else if(ji-1){System.out.pr…

Linux信号:信号的保存

目录 一、信号在内核中的表示 二、sigset_t 2.1sigset_t的概念和意义 2.2信号集操作数 三、信号集操作数的使用 3.1sigprocmask 3.2sigpending 3.3sigemptyset 四、代码演示 一、信号在内核中的表示 实际执行信号的处理动作称为信号 递达(Delivery) 。 信号从产生到递达…

Mysql数据库——DML操作

目录 添加数据&#xff08;INSERT&#xff09; 修改数据&#xff08;UPDATE&#xff09; 删除数据&#xff08;DELETE&#xff09; 添加数据&#xff1a; &#xff08;1). 给指定字段添加数据 &#xff08;2). 给全部字段添加数据 &#xff08;3). 批量添加数据 修改数据: 案例…

【STM32】HAL库点灯

【STM32】HAL库点灯 一、探究目标二、探究原理2.1 ST开发库2.1.1 直接配置寄存器2.1.2 标准外设库2.1.3 HAL库2.2 HAL开发2.2.1 环境配置2.2.2 时钟配置2.2.3 GPIO配置2.2.4 工程创建2.2.5 KEIL代码![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/bf1c95d5c6724a6a…

NextGen Mirth Connect XStream反序列化远程代码执行漏洞(CVE-2023-43208)

0x01 产品简介 NextGen Mirth Connect是是美国NextGen公司的一个医疗集成引擎,主要用于医疗领域的系统集成和数据交换,支持多种协议和标准。 0x02 漏洞概述 NextGen Mirth Connect 4.4.1之前版本存在远程代码执行漏洞,未经身份认证的攻击者可利用该漏洞远程执行代码。 0…

混合组网VS传统网络:智能硬件混合组网优劣势浅要解析

智能硬件混合组网是一种利用多种通信技术相结合的方法&#xff0c;以实现更灵活、更可靠的网络连接。通过蓝牙、Wi-Fi、LoRa、4G相互之间的不同通讯方式&#xff0c;根据应用场景的不同以及现场实际环境&#xff0c;优选最佳物联网混合组网方案&#xff0c;以达到部署最便捷性价…

一张SSL证书如何同时保护多个域名及其子域名?

在互联网时代&#xff0c;数据安全和隐私保护变得至关重要&#xff0c;而SSL证书作为确保网站安全的重要工具&#xff0c;其重要性不言而喻。本文将详细探讨一种特殊的SSL证书——多域名通配符SSL证书&#xff0c;它为网站管理员提供了一种高效、经济的方式来保护多个域名及其子…

学Java以及IDEA工具中遇到的常用单词

Arithmetic 算术 operator 运算符 relational 关系 logic 逻辑 assign 分配 TernaryOperator 三元运算符、 gender 性别 lebal 标签 array 数组 two dimesional 二维 object 对象 method 方法 row 行 column 列 parameter 参数 recursion 递归 overload 方法重载 calculate 计算…

MyBatis从入门到“入土“

&#x1f495;喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&#xff01;&#x1f495;(●◡●) 目录 一、Mybatis为何物&#xff1f;&#x1f44c; 二、快速入门&#x1f923; 1、新建项目&#x1f60a; 2、数据库建表&#x1f60a; 3、导入依赖的jar包&#x1f60a;…

Linux学习笔记6

TFTP 服务器搭建和测试 关于TFTP:TFTP&#xff08;Trivial File Transfer Protocol&#xff0c;简单文件传输协议&#xff09;&#xff0c;是一个基于UDP 协议实现 的用于在客户机和服务器之间进行简单文件传输的协议&#xff0c;适合于开销不大、不复杂的应用场合 搭建服务器…

后量子密码的发展和应用

后量子算法&#xff0c;特别是后量子密码(PQC)&#xff0c;是近年来密码学领域的一个热门话题。随着量子计算技术的快速发展&#xff0c;传统的公钥密码算法面临着被量子计算机破解的威胁。为了应对这一挑战&#xff0c;后量子密码应运而生&#xff0c;成为了一种能够抵抗量子计…

【论文笔记】| 蛋白质大模型ProLLaMA

【论文笔记】| 蛋白质大模型ProLLaMA ProLLaMA: A Protein Large Language Model for Multi-Task Protein Language Processing Peking University Theme: Domain Specific LLM Main work&#xff1a; 当前 ProLLM 的固有局限性&#xff1a;&#xff08;i&#xff09;缺乏自然…

Redis篇 在linux系统上安装Redis

安装Redis 在Ubuntu上安装Redis 在Ubuntu上安装Redis 在linux系统中,我们安装Redis,必须先使它有root权限. 那么在linux中,如何切换到root用户权限呢? sudo su 就可切换到用户权限了. 在切换到用户权限后,我们需要用一条命令来搜索Redis相关的软件包 apt search redis 会出现非…

ROS2学习——节点话题通信(2)

目录 一、ROS2节点 1.概念 2.实例 &#xff08;1&#xff09;ros2 run &#xff08;2&#xff09;ros2 node list &#xff08;3&#xff09;remapping重映射 &#xff08;4&#xff09;ros2 node info 二、话题 &#xff08;1&#xff09; ros2 topic list &#xf…