本文在mips和Loongarch架构上实现GCC-12.0中的一些内建函数作为基础,介绍内建函数的实现的过程,此内建函数的实现并没有直接的实现参考,所以其总结的实现分析可能不完整,希望大家的批评指正,共同学习。

一、内建函数的介绍

1、什么是内建函数

GCC编译器中提供了一系列的buitin函数,可以实现一些简单快捷的功能方便程序的编写。这些函数大多数都是以“__builtin_”作为前缀使用的,在编译器内部有许多标准C库相对应的builtin函数,如__builtin_strcmp()、__builtin_ffs()等,在编译器内部通过在指令模板MD文件中添加指令的的实现和一些中间过程的转换,在编译阶段直接生成目标机器所支持的汇编指令来执行函数的功能,这可以用来优化编译的结果,同时也可以充分发挥机器的性能。

2、内建函数的使用

builtin函数是一种在编译器内部实现的,使用它并不需要包含#include<>头文件的支持,在编译的时候,直接变为指令块的替换操作,根据函数的所描述的功能匹配机器文件(MD)中的函数 对应的指令模板,如果找到合适的指令模板就会输出其中的汇编指令,否则就会调用标准库函数。如果编译器内部实现内建函数,那么在从汇编代码生成ELF可执行文件时,就不需要库函数的链接过程。

一些常见的builtin函数及其作用描述如下表1所示:

表1 gcc中builtin函数及作用
函数名称 功能简介
__builtin_clz(x) 计算x前导0的个数。x=0时结果未定义
__builtin_ctz(x) 计算x末尾0的个数。x=0时结果未定义
__builtin_ffs(x) 返回x中最后一个为1的位是从后向前的第几位,如__builtin_ffs(0x789)=1, __builtin_ffs(0x78c)=3
__builtin_popcount(x) 计算x中1的个数
__builtin_parity(x) 计算x中1个数的奇偶性
__builtin_nearbyint(x) 计算参数x经过四舍五入后的值
__builtin_floor(x) 向下取整,既取不大于x的最大整数
__builtin_ceil(x) 向上取整,既返回不小于x的最小整数
__builtin_trunc(x) 将数字x的小数部分直接去掉,返回整数
__builtin_sqrt(x) 求x的开平方根,返回结果

表1 中的内建函数在mips或loongarch上都有其对应的后端汇编指令,可以通过修改汇代码,实现其对应的功能。

对于一些经常使用的函数,可以直接使用linux系统上的maninfo帮助手册,更快速的理解他们的含义,对于floor函数的介绍如下所示:

注意:这里需要介绍的是以__builtin_开头的内建函数,但是在编译器内部实现的函数和普通C标准库函数的功能没有区别,只是其实现的方式不同。在GCC编译器内部实现时,选取对应机器支持的汇编代码完全可以根据传入和传入的参数类型进行选择。

对于builtin函数中传入和传入参数类型以及相关的属性,可以参考GCC源码中gcc/builtins.def文件中定义的宏,如下

要想继续的查看源码内部是如何实现的,可以在 文件中找到答案builtin-attrs.def。此部分内容后面在详细的说明。

二、内建函数的相关参考资料

在说明内建函数实现的过程之前,先来了解一下相关的参考资料和文献

资料1:主要考虑到gcc后端的分析GCC Backend (dogdog.run)

资料2:gcc官网关于各种架构上builtin函数的介绍Top (Using the GNU Compiler Collection (GCC))

资料3:(30条消息) gcc内置函数_淡泊的猪的博客-CSDN博客_gcc函数

资料4:GCC中内嵌函数实现剖析 - 中国知网 (cnki.net)

资料5:GCC中内建函数在项目中的使用情况以及重要性的介绍Understanding GCC builtins to develop better tools | Proceedings of the 2019 27th ACM Joint Meeting on European Software Engineering Conference and Symposium on the Foundations of Software Engineering

三、GCC中内建函数的调用过程

GCC中内建函数虽然使用方便,但是对于mips或者loongarch架构上还有许多未实现。目前还没有直接实现的方法,需要对其在编译器中的实现机制进行分析,并完成某一个架构上函数的实现。最后可以总结一条比较通用的实现流程,可以对后续的开发或者维护有一定的参考价值。

通过在LoongArch上的内建函数的实现,大概总结出了一条内建函数调用的流程图如下图3所示:

                                        图1 内建函数的调用流程图

在图3内建函数的调用过程中,需要关注的是如何从GIMPLE_CODE语句到RTL,以及从RTL到汇编代码生成的这两个阶段,其中GIMPLE是一个与目标机器无关的表示,而RTL则是insn的双链表的表示形式。

第一个阶段,从GIMPLE表示的内建函数转换成对应的RTX。利用函数gimple_code()获取gimple语句的类型,对GIMPLE_CALL的分支进行调用。

首先,必须要同时满足内建函数以下条件:利用函数gimple_call_fndecl()获取函数声明,判断函数是否具有前缀”__builtin_”的形式或者有编译的优化(如-O2);利用函数gimple_has_side_effects()判断gimple语句的改变是否对边不会产生影响;如果同时满足以上两个条件,则满足内建函数的要求。

其次,在判断满足了内建函数的要求后就可以使用 replacement_internal_fn()函数去替换gimple语句,替换需同时满足的条件:利用函数gimple_call_builtin_p判断gimple_call是否属于内建函数的BUILT_IN_NORMAL类型;利用函数associated_internal_fn判断gimple_call是否在internal-fn.def文件中有对应的宏定义,若有则返回IFN_NEARBYINT;判断返回的ifn和类型是否有直接的目标函数支持。如果函数的类别是NOT_BUILT_IN,表示它不是编译器内部的内建函数,则会调用expand_call()进行call指令的处理;如果函数类别是BUILT_IN_MD,表示它是目标机器所支持的函数,则调用expand_builtin函数,专门处理目标后端所支持的内建函数;如果为BUILT_IN_FRONTEND,则会调用expand_expr函数进行处理。

最后,在满足内建函数和替换的要求下,通过在文件internal-fn.def中定义的宏产生expand_##CODE函数,从而调用GIMPLE_CODE对应的的操作表查找适合目标机器的icode(如CODE_FOR_nearbyintdf2)。最后通过执行模板产生的构造函数调用函数emit_insn生成函数对应的insn,并加入到双链表RTL中。

第二个阶段,从RTL到汇编代码的输出。在这个过程中内建函数与库函数的处理方式是一致的,区别在于内建函数对应的insn的类型是非函数跳转的INSN,而库函数的insn对应的是类型函数跳转的CALL_INSN,都会使用final.c文件中的函数final_scan_insn逐一对每一条insn进行处理。产生汇编代码的一般流程是:首先通过函数recog_memoized()查看对应机器模板的icode,然后通过函数get_insn_template()获取insn对应的输出字符串,最后通过函数output_asm_insn()获取对应的汇编代码。

四、GCC中内建函数的实现过程

通过实现一些浮点函数__builtin_nearbyint()、__buiiltin_floor()、__builtin_trunc()函数和整型函数__buitlin_abs()等,总结GCC后中内建函数的实现过程。以下以__builtin_nearbyint()函数的实现为例,简单说明一下其实现过程。

1、builtin函数的形式定义

DEF_C99_BUILTIN        (BUILT_IN_NEARBYINT, "nearbyint", BT_FN_DOUBLE_DOUBLE, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_C99_BUILTIN        (BUILT_IN_NEARBYINTF, "nearbyintf", BT_FN_FLOAT_FLOAT, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_C99_BUILTIN        (BUILT_IN_NEARBYINTL, "nearbyintl", BT_FN_LONGDOUBLE_LONGDOUBLE, ATTR_CONST_NOTHROW_LEAF_LIST)

在使用文件builtins.def时,必须首先定义DEF_BUILTIN宏,因为DEF_C99_BUILTIN最后都是通过DEF_BUILTIN实现对应的功能。DEF_C99_BUILTIN包含四个参数,分别是内建函数的枚举ENUM、名字NAME、类型TYPE和属性ATTRS。
2、internal函数的定义

DEF_INTERNAL_FLT_FLOATN_FN (NEARBYINT, ECF_CONST, nearbyint, unary)

定义宏DEF_INTERNAL_FLT_FLOATN_FN的含义就是进行浮点间数据类型的转换,包含四个参数,分别是internal函数的名称name、标志flags、对应的操作表的名称optab和类型type,它的返回值是IFN_##NAME。除了浮点类型的转换,此文件中还定义了其他internal函数的操作,如对整数转换builtin函数的定义DEF_INTERNAL_INT_FN(NAME, FLAGS, OPTAB, TYPE)等。

3、optab操作表的定义

OPTAB_D (nearbyint_optab, "nearbyint$a2")

对于以OPTAB_D定义的宏,第一个参数表示标准操作表的名称,如nearbyint_optab,第二参数”nearbyint$2”中$a就会根据模式去匹配名称,表示操作表的主体pattern。对于optabs.def中定义OPTAB_*宏会被逐一展开并保存在optabs[]中,文件gcc/gensupport.c定义需要的保存的一些信息放在结构体optab_def中,如操作表的名称name、匹配指令模板的字符串pattern、函数调用的名称base、操作表的枚举值op、rtx_code对应的fcode和rcode、操作表的类型kind。

4、rtx表达式的定义

DEF_RTL_EXPR(NEARBYINT, "nearbyint", "e", RTX_UNARY)

对于定义每一个RTX的表达式,都有四个参数。第一个参数是internal的名称(rtx_code);第二个参数表示rtx操作数的输出格式,会保存到rtx_name[]中,可以在调试时通过print_rtx()函数来打印对应的名称;第三个参数表示RTX中操作数的输出形式,如’e’表示一元操作;第四个参数表示RTX的类型,此处”RTX_UNARY”表示一元操作的表达式类型。

5、在机器描述文件MD中添加函数的指令模板

每个机器描述文件对应一个机器架构,它属于编译器的后端,一般不能对其进行随意的修改,否则会影响编译器的正常使用。在loongarch.md文件中,描述类目标机器所支持的每条指令模板,指导GIMPLE向RTL转换,使得生成的RTL能够表达机器的特点,对于函数__builtin_nearbyint的模板实现过程如下:

(define_insn "nearbyint<mode>2"[(set (match_operand:ANYF 0 "register_operand" "=f")(nearbyint:ANYF (match_operand:ANYF 1 "register_operand" "f")))]"TARGET_DOUBLE_FLOAT""frint.<fmt>\t%0,%1"[(set_attr "type" "fcvt")(set_attr "mode" "<MODE>")]
)

指令模板主要由指令模板名称、RTL模板、条件、输出模板及属性五部分组成。以nearbyint(f)添加指令的模板为例,详细介绍如下:

(1)指令模板的名称

指令模板的名称为”nearbyint<mode>2”,在编译过程中就会展开为"nearbyintsf2"和"nearbyintdf2",其中sf和df分别表示32位和64位的机器模式,数组’2’表示后端实现的指令只有一个原操作数和目的操作数。指令模板的名称会在生成构造函数的时候使用,如gen_nearbyintsf2和gen_nearbyintf2。

(2) RTL模板表达式

[(set (match_operand:ANYF 0 "register_operand" "=f")(nearbyint:ANYF(match_operand:ANYF 1 "register_operand" "f")))]

上面给出了rtl模板的表达式,RTX_CODE为set,包含第0和第1操作数。set的第0操作数是match_operand,也就是匹配条件,包含操作数的机器模式、编号、断言函数和约束条件。机器模式是ANYF,它是一个浮点类型的迭代器,包含32位的SF和64位的DF;断言函数为寄存器断言register_operand,表示它是REG或SUBRE表达式;约束条件为”=f”,其中约束修饰符号为”=”,表示操作数0为输出操作数,约束字符”f”表示它是使用浮点寄存器的一个操作;同理,set的第1操作也是如此,只是约束字符表示它是一个输入操作。

(3) RTL模板的输出条件

此模板的输出条件是"TARGET_DOUBLE_FLOAT",在文件loongarch-opts.h中定义模板所需要的条件,如下所示

#define TARGET_DOUBLE_FLOAT (la_target.isa.fpu == ISA_EXT_FPU64)#define TARGET_HARD_FLOAT (la_target.isa.fpu != ISA_EXT_NOFPU)

其中"TARGET_DOUBLE_FLOAT"表示它是使用了64位的浮点寄存器,而”TARGET_HARD_FLOAT”表示它使用的是浮点类型的寄存器。

(4) RTL模板的输出模板

"frint.<fmt>\t%0,%1"

此输出模板是一条龙芯机器架构上支持的浮点转换指令的frint,选择此指令的原因是根据nearbyint函数的作用而定的。指令需要使用双引号括起来,形如"frint.<fmt>\t%0,%1"。FRINT.{S/D} 指令会根据FCSR中不同的状态,选择浮点寄存器fj中的单精度/双精度浮点数转换为整数值的单精度/双精度浮点数,得到的单精度/双精度浮点数写入到浮点寄存器fd。其它硬件支持浮点相关的指令可以参照龙芯提供的LoongsonArch64指令手册。

(5)RTL模板的属性

在机器描述文件中RTL指令属性的设置如下所示

[(set_attr "type" "fcvt")(set_attr "mode" "<MODE>")]

此模板定义的属性有指令的类型fcvt,在md文件中定义了它是关于浮点指令转换的,属性的模式则根据机器模式的选择进行匹配的。

注意:以上内建函数的实现过程是个人总结,由于篇幅的原因,只能粗略的介绍一下,要是有总结不对的地方,希望大家可以提出来,一起学习呀!

对于GCC中的实现过程还需要不断的完善,分析其内建函数的实现机制,梳理各个代码转换阶段的功能,完全整理出从builtin函数属性的设置,internal函数的替换、optab的映射、rtx的转换以及指令模板中汇编代码的输出是非常有必要的。

接下来我会完整的整理一下他们之间的关系,可能会花费较多的时间,但是这是非常有意义的一件事,尽快分享给大家。

GCC种builtin函数的介绍以及实现过程(1)相关推荐

  1. gcc的__builtin_函数介绍

    GCC提供了一系列的builtin函数,可以实现一些简单快捷的功能来方便程序编写,另外,很多builtin函数可用来优化编译结果.这些函数以"__builtin_"作为函数名前缀. ...

  2. GCC提供的builtin函数

    GCC提供了一系列的builtin函数,可以实现一些简单快捷的功能来方便程序编写,另外,很多builtin函数可用来优化编译结果.这些函数以"_builtin"作为函数名前缀. 很 ...

  3. 十分钟成为 Contributor 系列 | 为 TiDB 重构 built-in 函数

    2019独角兽企业重金招聘Python工程师标准>>> 这是十分钟成为 TiDB Contributor 系列的第二篇文章,让大家可以无门槛参与大型开源项目,感谢社区为 TiDB 带 ...

  4. GCC for Win32开发环境介绍

    GCC for Win32开发环境介绍(1) 第一章 在视窗操作系统下的GCC 第一节GCC家族概览 GCC是一个原本用于Unix-like系统下编程的编译器.不过,现在GCC也有了许多Win32下的 ...

  5. python介绍和用途-python匿名函数的介绍及用途

    匿名函数 用lambda能够创建一个匿名函数,这种函数得名于省略了用def声明函数的标准步骤. 语法 lambda [arg1 [,arg2,.....argn]]:expression 如何使用 我 ...

  6. setsockopt()函数功能介绍

    setsockopt()函数功能介绍 功能描述:获取或者设置与某个套接字关联的选项.选项可能存在于多层协议中,它们总会出现在最上面的套接字层. 用法: #include <sys/types.h ...

  7. Python中函数的介绍以及用法

    1.函数的介绍 在开发程序时,需要某块代码多次,但是为了提高编写的效率以及代码的重用,所以把具有独立功能的代码块组织为一个小模块,这就是函数 定义函数的规则: 函数代码块以 def 关键词开头,后接函 ...

  8. 5种网络IO模型介绍

    5种网络IO模型介绍 IO 模型分为以下几种: 阻塞IO 非阻塞IO 信号驱动IO IO多路复用 异步IO 前四个为同步IO 1 阻塞IO 一个IO操作需要两步: 等待数据和拷贝数据. blockin ...

  9. WebGL three.js学习笔记 6种类型的纹理介绍及应用

    WebGL three.js学习笔记 6种类型的纹理介绍及应用 本文所使用到的demo演示: 高光贴图Demo演示 反光效果Demo演示(因为是加载的模型,所以速度会慢) (一)普通纹理 计算机图形学 ...

最新文章

  1. 用 Flask 来写个轻博客 (28) — 使用 Flask-Assets 压缩 CSS/JS 提升网页加载速度
  2. 重定向dup2的本质
  3. 是时候为编程界做点贡献了
  4. Oracle Recursive Calls 说明
  5. 台式计算机时间不能同步,电脑时间不能同步的原因和图文解决方法
  6. I/O流(万流齐发、万流归宗) 本章目标: 掌握 讲  解:★★★★★ http://kuaibao.qq.com/s/20200527A0LR3000?refer=spider 1.I/O流概
  7. 适合开发人员看的鸿蒙OS介绍~
  8. React Reconciler
  9. java 实体字段校验@Valid - @NotNull @NotEmpty @NotBlank - ValidExceptionHandler
  10. SQUID工作原理是什么
  11. 【历史上的今天】10 月 26 日:NetBSD 系统发布;Windows 8 诞生;微软推出 Surface 系列
  12. coodblock调试_code::blocks调试
  13. Latex插入多张图片及图片图题的间隔位置等问题处理
  14. 2023年的春招,java怎么搞??
  15. leetcode136---异或运算的交换律
  16. 频谱扩展 matlab,使用MATLAB进行频谱分析
  17. python显示汉字_python如何显示中文字体
  18. 【Python】道格拉斯-普克抽稀算法
  19. 龙芯处理器下面的golang第三方库或框架实地编译与运行测试
  20. 帕金森病患者可以做什么康复运动?

热门文章

  1. 列表页进入详情页再返回列表页时,显示默认第一页的bug修复
  2. Python实例29:利用python自动创建多个Excel表格
  3. CSS基础:margin在行内元素及行级块元素失效两个元素之间margin重叠
  4. 华硕Android原始密码,华硕路由器默认密码是多少?ASUS路由器初始密码介绍
  5. Linux连接蓝牙键盘
  6. 微信公众号推送功能代码及详解
  7. Consul微服务注册与发现
  8. 炸⾦花棋牌游戏Python
  9. MVVM和MCV模式
  10. 传统行业如何在互联网时代转型