• 中国精品科技期刊
  • CCF推荐A类中文期刊
  • 计算领域高质量科技期刊T1类
高级检索

面向LoongArch边界检查访存指令的GCC优化实现

舒燕君, 郑翔宇, 徐成华, 黄沛, 王永琪, 周凡, 张展, 左德承

舒燕君, 郑翔宇, 徐成华, 黄沛, 王永琪, 周凡, 张展, 左德承. 面向LoongArch边界检查访存指令的GCC优化实现[J]. 计算机研究与发展. DOI: 10.7544/issn1000-1239.202440100
引用本文: 舒燕君, 郑翔宇, 徐成华, 黄沛, 王永琪, 周凡, 张展, 左德承. 面向LoongArch边界检查访存指令的GCC优化实现[J]. 计算机研究与发展. DOI: 10.7544/issn1000-1239.202440100
Shu Yanjun, Zheng Xiangyu, Xu Chenghua, Huang Pei, Wang Yongqi, Zhou Fan, Zhang Zhan, Zuo Decheng. GCC Optimization for LoongArch Memory Accessing Instructions with Bound-Checking[J]. Journal of Computer Research and Development. DOI: 10.7544/issn1000-1239.202440100
Citation: Shu Yanjun, Zheng Xiangyu, Xu Chenghua, Huang Pei, Wang Yongqi, Zhou Fan, Zhang Zhan, Zuo Decheng. GCC Optimization for LoongArch Memory Accessing Instructions with Bound-Checking[J]. Journal of Computer Research and Development. DOI: 10.7544/issn1000-1239.202440100

面向LoongArch边界检查访存指令的GCC优化实现

基金项目: 国家自然科学基金项目(61202091,62171155)
详细信息
    作者简介:

    舒燕君: 1981年生. 博士,副教授. CCF会员. 主要研究方向为计算机体系结构、容错计算、高性能计算

    郑翔宇: 2001年生. 硕士研究生. 主要研究方向为计算机体系结构、编译器设计与优化

    徐成华: 1989年生. 博士研究生. 主要研究方向为处理器性能分析、编译器设计与优化

    黄沛: 1983年生. 工程师. 主要研究方向为计算机体系结构、操作系统

    王永琪: 2001年生. 硕士研究生. 主要研究方向为性能分析、操作系统、虚拟化

    周凡: 2001年生. 本科生. 主要研究方向为计算机体系结构、操作系统、虚拟化

    张展: 1978年生. 博士,教授. CCF高级会员. 主要研究方向为计算机体系结构、边缘计算、移动计算

    左德承: 1972年生. 博士,教授. CCF高级会员. 主要研究方向为计算机体系结构、容错计算、高性能计算

    通讯作者:

    舒燕君(yjshu@hit.edu.cn

  • 中图分类号: TP391

GCC Optimization for LoongArch Memory Accessing Instructions with Bound-Checking

Funds: This work was supported by the National Natural Science Foundation of China (61202091, 62171155).
More Information
    Author Bio:

    Shu Yanjun: born in 1981. PhD, associate professor. Member of CCF. Her main research interests include computer architecture, fault-tolerant computing, and high-performance computing

    Zheng Xiangyu: born in 2001. Master candidate. His main research interests include computer architecture, complier design and optimization

    Xu Chenghua: born in 1989. PhD candidate. His main research interests include performance analysis, complier design and optimization

    Huang Pei: born in 1983. Engineer. His main research interests include computer architecture and operating system

    Wang Yongqi: born in 2001. Master candidate. His main research interests include performance analysis, operating system, and virtualization

    Zhou Fan: born in 2001. Undergraduate. His main research interests include computer architecture, operating system, and virtualization

    Zhang Zhan: born in 1978. PhD, professor. Senior member of CCF. His main research interests include computer architecture, edge computing, and mobile computing

    Zuo Decheng: born in 1972. PhD, professor. Senior member of CCF. His main research interests include computer architecture, fault-tolerant computing, and high-performance computing

  • 摘要:

    为了减少内存安全检查的开销,LoongArch指令集架构引入了边界检查访存类指令. 然而,作为一种新的内存访问指令,目前GCC(GNU compiler collection)编译器不支持该类指令,LoongArch硬件能力不能得到充分利用. 针对此LoongArch边界检查访存指令改进了GCC编译器,实现利用该类指令优化程序的内存安全检查. 具体而言,完成了3个方面的工作:1)设计实现了针对边界检查访存指令的内建函数;2)改进GCC RTL(register transfer language)阶段的优化器,使其能够识别无异常处理和带异常处理2种情况的边界检查访存语义,并自动优化;3)面向LoongArch边界检查访存指令触发的边界检查异常(bound check exception,BCE),设计了新的Linux内核异常信号SIGBCE和相应的运行时库glibc(GNU C library)的信号处理函数,实现了边界检查异常处理. 通过在GCC 12.2.0和龙芯3C5000L服务器进行实验,验证了改进后编译器不仅能正确使用新引入的边界检查访存指令,并在某些安全函数中带来接近20%的性能提升. 完善了LoongArch生态、推进了LoongArch指令集发展. 对此类特定指令编译器优化工作有一定的借鉴意义.

    Abstract:

    LoongArch ISA (instruction set architecture) introduces new memory accessing instructions with bound-checking to decrease the overhead of memory security check. However, as a new type of memory accessing instruction, the existing GCC (GNU compiler collection) compiler tools cannot support it and thus LoongArch based hardware remains underutilized. Therefore, in this paper, we revise the GCC compiler with the LoongArch memory accessing instructions to optimize the memory security check. Specifically, our work is divided into three parts: 1) designs built-in functions for the memory accessing instructions; 2) improves the RTL (register transfer language) optimizer of GCC to recognize two kinds of semantic patterns of memory accessing instructions with bound-checking, which are non-exception handling and exception handling; 3) implements a new exception signal SIGBCE for the bound check exception BCE that is raised by CPU in Linux kernel, and implements the corresponding signal handling function in glibc (GNU C library) to deal with the bound check exception. The experiments on GCC 12.2.0 and Loongson 3C5000L server show that the revised compiler is able to correctly employ the new memory accessing instructions and bring an acceleration of approximately 20% in some security routines. Our work improves the ecosystem of LoongArch and boost the development of LoongArch ISA. It will also be referential to GCC optimization for the specialized instructions.

  • 内存安全在软件开发和程序设计中有着重要地位,和系统安全密切相关. 内存安全问题根源在于对内存的非法读取与篡改,例如缓冲区溢出攻击[1]等攻击手段. 现代高级语言中,提供了一系列安全机制,对访存操作进行安全检查,以保障内存安全. 然而安全检查会引入较大的时间开销,特别是一些基于软件的安全机制[2]会使程序性能严重下降,无法应用到实际系统中.

    对此,研究者们提出在指令集架构层面引入访存安全的相关指令,基于硬件来实现内存操作的安全性检查,减小由于内存安全检查所带来的开销. 比较著名的设计有Intel MPX(memory protection extension)[3],ARM MTE(memory tagging extension)[4]等. LoongArch[5]是一种新型的精简指令集计算机(reduced instruction set computer,RISC)架构[6]. 在LoongArch架构中,针对访存操作的安全性检查设计了一类边界检查访存指令. 这类指令在访存之前检查访存地址与边界地址之间的关系,当边界检查条件满足时正常访存,否则触发访存异常. 通过将安全检查和访存操作合并在1条指令中完成,LoongArch边界检查访存指令可以减少程序的指令数量,降低安全检查的开销.

    然而,在指令集中引入新指令,会对编译器优化提出新的要求,需要对编译器进行改进以支持新指令. 例如针对Intel MPX,GCC(GNU compiler collection)编译器实现了一套边界信息管理机制,在指针解引用的代码中插入检查指令[7];针对ARM MTE,Clang编译器也增加了相应的指针检查器,优化了安全机制[8]. 由于LoongArch指令集架构较新,而且LoongArch边界检查访存指令采取了一些明显不同于已有指令集的设计,目前GCC等编译器并不支持该类指令,从而导致基于LoongArch指令集架构的龙芯处理器能力不能充分发挥.

    编译器优化是利用计算机硬件能力的根本手段,也是对指令集架构设计的最终检验. 为此,本文围绕LoongArch边界检查访存指令的编译优化展开一系列工作:首先分析了LoongArch边界检查访存指令的语义特征,结合该指令的实际应用场景,提出了区分无异常处理和带异常处理2种情况来设计边界检查访存指令编译优化方案. 然后基于GCC编译器,设计实现了内建函数用于提示编译器进行优化,并分别实现了无异常处理和带异常处理2种情况下的语义识别算法,用于自动优化含有边界检查访存语义的C语言程序. 此外,针对带异常处理的情况,本文设计了一种嵌入异常处理所需信息的方法,形式上扩展了指令操作数. 通过以上改进GCC的工作,无论是在无异常处理还是带异常处理的情况,对用户程序而言,边界检查访存指令的行为都是简单而明确的,从而可以比较方便地在GCC实现LoongArch边界检查访存指令匹配.

    由于LoongArch边界检查访存指令在边界检查条件不满足时会触发边界检查异常(bound check exception,BCE),而Linux内核和程序运行环境不支持该异常,本文还设计实现了Linux内核和运行环境的边界检查异常BCE处理机制. 在Linux内核中设计了新的边界异常信号(signal of BCE,SIGBCE),并基于glibc(GNU C library)实现了SIGBCE的信号处理函数. 通过以上异常处理流程,确保程序执行边界检查访存指令的实际语义和编译器优化实现的语义相一致.

    为了检验本文改进的GCC编译器的优化效果,在3个使用边界检查访存的安全函数上进行测试. 实验结果表明,改进后的GCC编译器能够正确识别和利用LoongArch边界检查访存指令实现安全函数的优化,减小安全检查带来的开销,其性能改善显著,最大接近20%. 此外,本文提出的编译优化方案不需要重新编写程序,只需要使用改进后的编译器重新编译程序,即可得到LoongArch边界检查访存指令带来的性能优化效果.

    本文的主要贡献如下:

    1)研究分析了LoongArch边界检查访存指令的语义特征,结合该指令的实际应用场景,提出了区分无异常处理和带异常处理2种情况,确定了边界检查异常的编译处理方法,便于编译器实现用户程序语义与边界检查访存指令语义匹配,简化编译优化过程.

    2)设计实现了一套基于GCC编译器的LoongArch边界检查访存指令的编译优化方法,包括内建函数和语义自动识别优化,其实现思路可作为类似指令优化的参考.

    3)设计实现了一种在程序中嵌入异常处理信息的方法,并在Linux内核和glibc库中实现了对边界检查异常BCE的处理机制,为在GCC编译器、Linux内核以及glibc库实现新的异常处理提供借鉴.

    LoongArch指令集针对访存操作的安全性检查设计了一类边界检查访存指令[9],其具体行为定义如下:检查访存地址与边界地址的关系是否符合检查条件,如果不符合条件则触发边界检查异常并终止访存操作;如果符合条件则使用访存地址进行访存操作,并进行访存操作中的其他地址合法性检查.

    LoongArch边界检查访存指令支持多种访存数据类型、访存操作类型和边界检查条件,其指令汇编记号格式写作{op}{cond}. {width},指令中各部分记号的具体内容如表1所示:

    表  1  边界检查访存指令汇编记号
    Table  1.  Assembly Mnemonics of Memory Accessing Instructions with Bound-Checking
    op cond width
    ld,st,fld,fst le,gt b,h,w,d,s
    注:width类型记号d可表示8 B整数或双精度浮点数.
    下载: 导出CSV 
    | 显示表格

    记号{op}为访存操作的操作符,包括整数读内存ld、整数写内存st、浮点读内存fld、浮点写内存fst. 记号{cond}为边界检查条件,可以是访存地址小于等于边界地址le、访存地址大于边界地址gt. 记号{width}表示数据宽度类型,包括1 B整数(b),2 B整数(h),4 B整数(w),8 B整数(d),单精度浮点(s),双精度浮点(d).

    LoongArch边界检查访存指令的编码格式如图1所示,指令的第15~31 b是指令的操作码,对应了汇编记号. 其中第20~31 b指明该条指令为边界访存指令,值为001110000111,第15~19 b是字段F,编码了边界检查访存指令的数据类型、访存操作以及检查条件,例如第19 b编码指令是浮点指令还是整数指令. 指令的第0~14 b表示3个通用寄存器字段rdrjrk. 其中,字段rd是访存数据寄存器,读操作将读取到的内容写入此寄存器,写操作则将此寄存器的内容写到内存;字段rj是访存地址寄存器,指定了访存地址,所有指令访存地址均要求自然对齐,否则将触发对齐异常;字段rk是边界地址寄存器,指定参与安全检查的边界地址,指令执行时,比较字段rj和字段rk索引的寄存器的值是否满足条件要求.

    图  1  LoongArch边界检查访存指令格式
    Figure  1.  The format of LoongArch memory accessing instructions with bound-checking

    GCC编译器的整体优化流程如图2所示,可以分为前端和后端2个部分[10]. 图2的上半部分是前端,包括对C语言程序的词法分析、语法分析、语义分析,以及中间表示的转换和优化过程. GCC支持多种高级语言,因此首先由Generic转换过程将高级语言的语法分析树翻译为通用的中间表示Generic,然后通过Gimple转换过程将Generic中间表示翻译为Gimple中间表示. 前端会在Generic中间表示和Gimple中间表示上做许多优化.

    图  2  GCC优化的工作过程
    Figure  2.  The workflow of GCC optimization

    图2下半部分是GCC优化的后端,首先将优化后的Gimple中间表示展开为RTL(register transfer language)序列. 后端实现RTL序列的优化并解决RTL中未解决的问题. 所有环节结束后,将RTL序列翻译为汇编文本. 然后编译命令还会自动调用汇编器、链接器等生成最终的二进制文件.

    LoongArch边界检查访存指令在条件不满足时会触发BCE异常. 在现代计算机系统中,处理器硬件触发的异常通常由操作系统内核直接处理,用户程序需要通过内核提供的机制接收和处理异常. Linux信号机制[11]就是这样一种软件层面对于中断异常的处理机制.

    为了确保程序执行边界检查访存指令的实际语义和编译器优化的语义相一致,必须在运行时库中处理异常信号. glibc库是GNU组织开发的、适用于一类POSIX(portable operating system interface)环境的C运行时库. 在GNU/Linux操作系统上,一般的C语言程序都会链接到glibc. glibc提供各种封装好的库函数如函数printf、函数strncpy等供用户程序使用,并在执行用户程序主函数之前进行各种初始化工作. glibc库是操作系统中必不可少的组件.

    鉴于系统安全的重要性和纯软件实现安全检查性能影响较大的现实问题[2],使用硬件加速安全检查十分必要.

    针对边界检查访存,Intel曾向x86指令集引入Intel MPX扩展[3],包括边界寄存器和边界检查指令. GCC编译器于5.0版本开始支持Intel MPX. 然而由于Intel MPX的设计受到x86指令集兼容性包袱的约束,通用寄存器数量很少导致需要引入额外的专用寄存器保存边界信息,许多指令都能访存导致不适合直接在访存指令上增加检查功能而需要额外的检查指令. 因此,实际Intel MPX扩展加速效果不理想,优化效果不佳[12]. Intel处理器、GCC编译器、Linux内核以及glibc库等目前已经删除MPX支持.

    在AArch64指令集中,通过MTE扩展[4]引入了边界检查访存的安全机制. MTE包括内存标签技术和标签操作指令,使用带标签的指针(也称为染色指针)访问内存,硬件自动检查指针标签与内存块标签是否一致. 这种技术可阻止堆栈溢出攻击以及释放后使用等攻击和误用.

    LoongArch指令集中,边界检查访存指令是基础指令集的一部分. LoongArch不使用专用的边界寄存器,而是将边界信息保存在通用寄存器中. 边界检查访存指令有效利用RISC架构的Load-Store特点,将安全检查和访问内存的功能合并在1条指令中完成,在访存之前检查访存地址与边界地址是否满足要求,避免边界检查对程序引入额外开销.

    此外,LoongArch边界检查访存指令充分利用RISC指令集通用寄存器数量多的特点,使用通用寄存器保存边界信息. 通用寄存器数量多、灵活性好、效率较高,此类指令执行简单,有助于减少安全检查对程序执行的影响. 与已经被删除的Intel MPX相比,作为新型指令集,LoongArch边界检查访存指令发挥RISC指令集的优点,避免了不必要的兼容性包袱,更简明高效.

    利用特殊指令优化程序的直接方法是编写对应的汇编程序[13],常见于系统库、密码算法加速库、多媒体应用程序等. 例如:赵翔等人[14]深入分析图像处理、数据压缩等领域中的实数快速傅里叶变换(fast Fourier transform,FFT)算法并行性,使用ARM Neon汇编开发高性能的实数FFT算法库;Cai等人[15]面向LoongArch架构,为最常用的glibc运行库的库函数编写汇编语言版本;杨昊等人[16]采用AVX2指令设计实现现代加解密标准算法,减少加解密算法的运算时间和开销;赵龙等人[17]采用SIMD(single instruction multiple data)指令集设计实现高强度ECC(elliptic curve cryptography)攻击算法;沈洁等人[18]基于飞腾处理器,利用SIMD向量指令和部件,优化了向量三角函数. 鉴于汇编程序需要针对特定平台、特定场景,编写不方便,研究人员通常在设计实现运算库时将底层资源加以抽象,并将各种常用加速算法实现为模块.

    相比于编写汇编程序,利用特殊指令优化程序更好的方法是改进编译器. 一般来说,改进编译器以利用特殊指令,主要有2种手段. 一种手段是引入内建函数作为C语言的语义扩展[19],内建函数直接提示执行某类优化. 例如对于SIMD指令,单纯的C语言难以描述向量类型和相应的并行操作,因之引入了大量的内建函数,被称为intrinsics函数[20]. 内建函数具有一定的通用性,相比直接使用汇编语言可移植性更好. 另一种手段是语义识别和优化,通过一类匹配算法,分析程序结构和变量之间的关系,明确某一段C语言程序隐含的语义,并实现特殊指令的优化. 例如对于位操作指令(bit manipulation instruction,BMI)的优化. Koppelmann 等人[21]扩展RISC-V指令集,通过改进GCC等编译器使其能够识别适合使用BMI优化的代码模式. 研究表明,通过引入BMI指令并实现面向BMI的编译器优化,可以有效提高RISC-V的性能并减小代码体积[22]. 针对更为复杂的SIMD指令,研究者提出了大量自动向量化方法进行相关的语义识别和优化[23-25]. ARM等指令集还引入了一类特殊的指令形式,即条件执行(又称为谓词执行). 针对条件执行,编译器可采用IF转化方法进行优化[26],将一部分分支指令消除,从而提高性能. 所消除的分支指令可包括安全检查带来的分支指令. 并且,条件执行优化还可以带来更多优化机会,例如寄存器分配方面的优化[27].

    针对Intel MPX,GCC-5.0编译器引入了一套称作指针边界检查器(pointer bounds checker)的安全机制. 编译器首先利用扩展的数据流分析方法分析程序中各个指针的边界. 对于能够确定边界的指针,增加额外的、程序中不可见的数据结构记录边界信息,在解引用代码之前插入边界检查代码阻止对这些指针越界访问. Intel MPX使用硬件机制加速上述实现,即使用边界寄存器bnd0-bnd3保存边界信息、引入边界检查指令如bndcu等用于实现解引用之前的检查. 此外Intel MPX修改Linux内核以支持处理新的检查异常,修改glibc以维护边界寄存器的信息.

    对于ARM MTE,目前已在Clang/LLVM编译器中为既有的地址检查机制(address sanitize,ASan)增加了基于MTE的实现. 编译器在分配内存的代码中插入分配标签的代码,将所需要的边界信息等通过ARM MTE引入的专用指令写入标签对应的影子内存中,并确保指针所带的标签正确传播. 访存时,若使用带标签的指针,则安全检查由硬件实现无需额外的检查指令.

    无论是GCC编译器对Intel MPX的支持还是LLVM编译器对ARM MTE的支持,都是利用硬件来加速既有的检查机制. 与这些工作不同的是,本文聚焦于优化程序中显式存在的安全检查. 基于对LoongArch边界检查访存语义的分析,本文引入内建函数提示编译器针对边界检查访存语义进行优化. 本文还采用RTL中间表示上的优化算法来识别程序中的边界检查访存语义并自动优化. 总的来说,本文工作针对LoongArch边界检查访存指令的特点,直接识别程序中显式的边界检查访存语义、降低程序中此类显式检查的开销,方法简明有效.

    图3是本文基于GCC编译器实现LoongArch边界检查访存指令优化的总体设计,主要包括3个部分:GCC编译器优化用户程序,LoongArch处理器执行边界检查访存指令,Linux内核与glibc运行时库在发生边界检查异常BCE时进行异常处理.

    图  3  LoongArch边界检查访存指令GCC优化的总体设计
    Figure  3.  The design of GCC optimization for LoongArch memory accessing instructions with bound-checking

    图3的第2个方框是GCC编译器对用户程序的优化. 首先对C语言编写的用户程序进行分析,识别出使用if语句、三目运算符或内建函数等描述的对访存地址进行的安全检查. 这里的内建函数是本文针对边界检查访存指令设计实现的. 由于内建函数需要程序员手动调用,因此本文进一步改进RTL优化器,使之能进行语义识别并自动开展优化. 改进后的RTL优化器会匹配安全检查对应的内建函数或特殊代码模式,在生成汇编代码时生成LoongArch边界检查访存指令优化代码,并根据情况嵌入异常处理所需信息,最终得到使用边界检查访存指令优化的可执行用户程序.

    包含LoongArch边界检查访存指令的用户程序在运行环境中执行. 在检查访存地址时,若满足访存条件要求,处理器正常执行用户态程序,若不满足访存条件要求,则触发边界检查异常BCE并陷入内核态执行,如图3的第3个方框所示.

    由于目前Linux内核并不支持LoongArch边界检查访存指令触发的边界检查异常BCE,本文修改了Linux内核和运行时库glibc,如图3的最右侧方框所示. 为了能够捕获和处理硬件抛出的BCE异常,本文扩展了Linux内核,设计实现了函数do_bce以解析BCE异常发生时的处理器状态信息,包括寄存器状态、异常PC(program counter)、指令字等,并将这些信息封装到一个新的信号SIGBCE发送给程序运行时glibc以完成异常处理. 为了处理内核发送的SIGBCE信号,本文还进一步改进了运行环境的运行时库glibc,设计实现了SIGBCE信号处理函数,使程序对边界异常信号SIGBCE的处理无感知,并避免程序错误地修改边界检查异常的信号处理函数. 通过这样一系列异常处理流程,确保对程序而言,边界检查访存指令的实际语义和编译器优化过程中所实现的语义一致.

    本节详细介绍GCC编译器优化相关内容. 4.1节首先针对边界检查不满足条件时,是否影响程序的执行流程,设计2种边界检查访存指令语义,以便于编译器利用该指令进行程序优化. 4.2节针对边界检查访存指令设计实现内建函数,直接提示编译器进行优化. 由于内建函数还需要程序员手动调用,4.3节进一步改进RTL优化器,使GCC编译器能够自动识别语义并进行优化.

    LoongArch指令集的边界检查访存指令语义如图4所示:访存之前首先检查访存地址与边界地址之间是否满足边界检查条件,如果满足则正常进行访存;如果边界检查条件不满足,则不访存,并触发边界检查异常BCE. 图4中的操作MemoryLoad表示以rj为访存地址,读取8 B的数据,if-else语句说明了边界检查操作,#BCE操作表示条件不满足则触发一个边界检查异常.

    图  4  边界检查访存指令语义
    Figure  4.  Semantic of memory accessing instruction with bound-checking

    然而在C语言中,并不支持类似Java语言的异常及捕获语法,LoongArch边界检查访存指令的BCE异常语义很难直接应用在C语言程序中,编译器也很难利用边界检查访存指令进行程序优化. 因此,需要针对边界检查访存指令设计更为简单而明确的语义,便于编译器实现用户程序与该指令的匹配.

    LoongArch边界检查访存指令在检查条件不满足时触发BCE异常,通过采用不同的异常处理方式可以灵活实现用户程序的任意语义. 然而,过于复杂的设计难以进行编译优化,而且也可能并不会被用户程序所使用,通过分析一些典型的带有安全检查的程序代码可以发现,在边界检查不满足条件时,依据程序的执行流程是否发生改变,主要分为2种场景,如图5(a) (b)所示.

    图  5  C语言用户程序示例与边界检查访存语义设计
    Figure  5.  Examples in C language and semantic designs of memory access with bound-checking

    场景1图5(a):在解引用指针ptr之前检查访存地址是否小于等于边界地址,当不满足条件则不进行访存,接着顺序执行程序,不因边界检查异常对用户程序执行流程产生影响.

    场景2图5(b):在解引用指针ptr之前检查访存地址是否小于等于边界地址,若不满足条件也不进行访存,但是会因边界检查异常而改变用户程序的执行流程,跳转执行特定的程序逻辑,如打印错误信息等.

    基于以上2种场景,本文分别设计了无异常处理的边界检查访存和带异常处理的边界检查访存2种语义,如图5(c) (d)所示. 图5(c)是无异常处理的边界检查访存语义说明,在这种情况下边界检查异常对程序执行流程没有影响,可以解释为一个条件传送形式的指令,rd是访存数据寄存器,rj是访存地址寄存器,rk是边界地址寄存器. 从用户程序来看,如果边界检查条件不满足,则不访存、不改变访存数据寄存器的内容,并继续执行后续指令.

    图5(d)是带异常处理的边界检查访存语义说明,当边界检查异常发生时,跳转到目标地址处执行特定的用户程序逻辑. 形式上是一个带有分支跳转操作的指令,不满足访存条件时将进行分支跳转,rd是访存数据寄存器,rj是访存地址寄存器,rk是边界地址寄存器,offset是分支跳转的PC相对偏移量. 从用户程序来看,如果边界检查访存条件不满足,则不访存并跳转到一个指定地址处继续执行,相当于访存指令和分支指令合二为一.

    通过将LoongArch边界检查访存指令实际应用场景分为无异常处理和带异常处理2种情况,确定了边界检查异常BCE在编译过程的处理方法,能够极大地方便编译器实现用户程序与该指令的匹配、利用该指令进行程序优化.

    为特殊指令设计内建函数是一种常见的优化手段,程序员调用内建函数可直接提示编译器进行优化. 内建函数具有一定的通用性,相比直接使用汇编语言可移植性更好.

    GCC的内建函数原型框架非常完善,所有内建函数统一以__builtin_开头,作为特殊记号. 内建函数本质上借助函数的形式去描述一种运算或者操作,通常标记为相应的内部函数,即一种通用表达式,其具体实现由RTL序列描述. 一般来说,编译器识别了内建函数后,最终都能将代码在相应位置翻译成优化的机器指令片段,免除了函数调用开销,与内联函数类似.

    对于无异常处理的边界检查访存,在边界检查条件不满足时不做任何操作. 对于带异常处理的边界检查访存,需要建立一种方式描述“边界检查不满足时跳转到指定位置继续执行”. 本文使用if-else语句描述这种语义.

    为了实现适合于无异常和带异常2种边界检查访存情况的设计,本文分析GCC编译器的内建函数,发现形式上访存边界检查与有符号数加法溢出检查类似. 参考“带溢出检查的算术操作”相关的内建函数,本文设计了适用于各种情况的边界检查访存内建函数原型,下面以4种数据类型的边界检查读内存为例进行说明,如图6所示:

    图  6  内建函数原型
    Figure  6.  Prototypes of built-in functions

    图6的边界检查访存内建函数原型中,指针ptr是欲访问数据的地址,指针bnd是参与安全检查的地址,指针res指向一个保存访存结果的变量. 内建函数的语义为:判断指针ptr是否小于等于指针bnd,如果边界检查条件满足时,正常访存,从指针ptr指向处读取一个数据然后将结果保存到*res,接着返回false表示顺利访存;如果边界检查条件不满足,则返回true,表示边界检查有错误. 边界检查访存内建函数可直接与if语句配合表示异常处理语义. 若不使用if语句判断内建函数返回值,则意味着采用无异常处理.

    由于内建函数需要程序员手动调用,因此有必要进一步改进编译器,使之能进行语义识别并自动开展优化. GCC编译器的许多优化都是在RTL阶段实现的,例如条件执行(条件传送)、分支指令和基本块的重排、延迟槽调度、窥孔优化等,优化算法的基础是匹配RTL指令模式. 本文也选择在RTL阶段实现边界检查访存指令的优化,主要由于以下3个方面的原因:

    1)边界检查访存指令是LoongArch指令集特有的指令类型.

    2)其他指令集架构不能就优化改动获得收益,那么优化改动不应该影响其他架构的程序执行.

    3)GCC编译器的匹配-简化框架不能很好支持指针变量.

    LoongArch边界检查访存指令的BCE异常语义使该指令难以直接在程序中使用. 根据4.1节的设计,边界检查访存的语义分为无异常处理和带异常处理2种情况,由于这2种情况的语义差异较大,本文采用不同的方式进行优化.

    1)无异常处理语义.

    图5(c)所示,LoongArch边界检查访存指令在无异常处理场景下可以直接看作条件传送指令,边界检查异常发生对控制流无影响. 一般认为,条件传送指令可以减少分支指令的使用,进而减少分支指令控制相关带来的开销,适合优化一类短分支. 短分支的特点是其分割产生的基本块只包含很少的几条指令.

    GCC编译器中条件执行优化阶段名称为ce3,在机器描述中引入了语句cond_exec,用于描述条件执行语义. 在ce3优化阶段,首先寻找短分支,然后尝试匹配所有的cond_exec描述模板,检查基本块内的指令是否满足模板的约束. 如果成功匹配,就将相应的RTL序列替换为条件执行的RTL指令.

    在ce3阶段之前,边界检查访存的安全检查基于分支指令实现. 此类分支是典型的短分支,由其分割产生的基本块只会包含1条访存指令. 在ce3优化阶段首先发现这类短分支,然后根据cond_exec的描述,检查基本块中的指令是否为访存指令、条件是否是访存地址和边界地址构成的关系等.

    2)带异常处理语义.

    图5(d)所示,LoongArch边界检查访存指令在带异常处理场景下是条件分支指令和访存指令的复合,边界检查异常BCE发生将会改变控制流. 由于带异常处理的边界检查访存指令比较复杂,本文选择在窥孔优化阶段实现优化,因为窥孔优化允许灵活地匹配指令序列中由多条指令表示的各种各样的复杂语义,能够有效地发现带异常处理的边界检查访存.

    GCC编译器的窥孔优化阶段名称为peephole. 对于窥孔优化,GCC在机器描述中引入了define_peephole语句,用来描述能够执行窥孔优化的RTL指令序列. 在peephole优化阶段,依次匹配当前位置之后的几条指令是否按照顺序满足peephole模板中描述的指令要求,确定是否可实施窥孔优化. 如果匹配成功,将相应的RTL序列替换为另一段更优化的RTL序列.

    在peephole阶段之前,边界检查访存的安全检查基于分支指令,这条分支指令之后接着是1条访存指令. 因此,在peephole优化阶段,根据窥孔优化模板,首先尝试匹配分支指令,然后匹配第2条指令是否为访存指令,如果匹配,则进一步检查分支条件是否为访存地址与边界地址构成的边界检查条件.

    值得注意的是,带异常处理的边界检查访存在形式上需要4个操作数,分别是访存数据、访存地址、边界地址、异常跳转目标. 这里的异常跳转目标是指边界检查条件不满足时的分支跳转目标,一般来说分支跳转目标采用PC相对偏移量来记录. 但是LoongArch定义的边界检查访存指令只有3个操作数字段,需要一种机制编码第4个操作数,存放跳转偏移量.

    LoongArch指令集架构有恒零寄存器,利用恒零寄存器可以构造出各种nop指令,并嵌入各种数据. 本文选择以LoongArch指令集的pcaddi指令来实现nop指令的构造,并做出相应约定:在边界检查访存指令之后增加pcaddi指令,带异常处理的第4操作数(异常跳转目标)将被编码在该pcaddi指令之中. 这种嵌入异常跳转目标信息的方法,相比于额外增设ELF(executable linking format)节或数据指令混排,不仅方便编译器生成指令,避免了对编译器以外的链接器等进行修改,而且可简化异常处理的实现.

    图7中是带异常处理的第4操作数的嵌入方法,当边界检查条件不满足时,指令触发边界检查异常BCE,后续将由内核、运行时库接管处理逻辑:从pcaddi指令中取出第4操作数,计算出指定的异常跳转目标,并使程序跳转到目标地址继续执行. 当边界检查条件满足时,正常访存不发生异常,此时第4操作数无效. 由于嵌入在nop指令中,除了需要额外发射1条指令之外不对程序执行有任何影响.

    图  7  第4操作数嵌入方法
    Figure  7.  Encoding the fourth operand

    图7的汇编代码展示了1个使用带异常处理边界检查访存的函数. 0x2e0处使用ldle.d指令检查访存地址以及进行访存操作. 为了编码边界检查条件不满足时的分支跳转地址,0x2e4处使用了pcaddi形式的nop指令,第4操作数编码在字段offset中为2,在图7中用矩形框出,表示指向相对于pcaddi指令的PC加2处的指令. 根据LoongArch指令字4 B对齐的特点,可计算出实际所指向的地址是0x2ec,即图7中箭头指向的 +2处的指令. 因此,调用此函数后,首先执行0x2e0处的边界检查访存指令,若访存地址满足条件,不触发BCE异常,则继续顺序执行0x2e4处的pcaddi指令,作为一个nop指令无实际影响;若访存地址不满足条件,则触发BCE异常,经过一系列异常处理后,返回程序时将跳转到箭头指向的0x2ec处继续执行.

    LoongArch架构下访存边界不满足要求时会触发边界检查异常BCE,其编号为0xA. 然而,目前Linux内核源码中没有对应的边界检查异常的处理函数,将使用默认的处理函数do_reserved直接终止进程并返回SIGUNUSED信号(等同于SIGSYS信号),如此无法实现在带异常处理场景下(图5(b)所示)异常发生时用户程序分支跳转执行特定程序逻辑.

    为此,本文设计并实现了边界检查异常BCE的Linux内核处理机制,主要包括2部分工作:

    1)设计实现异常处理函数do_bce解析BCE异常发生时的寄存器状态、异常PC、指令字等信息;

    2)设计实现BCE异常信号SIGBCE携带程序运行时库glibc进行信号处理所需的状态信息.

    由于异常处理函数do_bce发送的信号需要携带寄存器状态、异常PC、指令字等信息,那么有必要设计实现一种信号,与Linux通用的段错误信号SIGSEGV相区分. SIGSEGV信号通常会导致进程被终止,因为有关情况通常是程序编写存在错误导致的. 而当内核捕获到BCE异常后,希望用户进程接收有关信息并跳转至特定的用户程序逻辑而非终止,所以内核需要为边界检查访存指令触发的异常分配一个新的信号.

    为了减少对已有Linux信号系统的影响,本文通过扩展已有的自定义SIGUSR1信号来实现新信号的分配,并将其命名为SIGBCE信号. 这样的设计并不会导致使用SIGUSR1信号的程序出现异常,系统会根据接收到的信号信息结构体kernel_siginfo中的信号代码si_code来识别是具体是哪一种信号. 类似这种共用信号的设计如Linux中SIGSYS和SIGUNUSED信号,内核发送的SIGSYS信号可能携带值为SYS_SECCOMP,SYS_USER_DISPATCH的si_code信息,而SIGUNUSED信号不会携带si_code信息,从而区分.

    Linux中异常处理函数一般通过调用函数force_sig_fault来对出错进程发送信号,而函数force_sig_fault不具有发送访存边界值的功能. 为此,本文还实现了一种新的对进程发送指定信号的函数force_sig_bcebnderr,此函数利用Linux提供的信号信息结构体kernel_siginfo,将其成员变量si_code赋值为SEGV_BNDERR. 同时,扩展结构体kernel_siginfo新增成员变量lowerupper,分别用于存储访存时边界检查访存指令可能存在的下界与上界,之后通告信号至出错进程,glibc中实现的信号处理函数会根据约定处理.

    当用户程序中的边界检查访存指令触发越界访存地址异常时,内核向该进程发送SIGBCE信号,而用户程序的运行时库gilbc信号处理函数将捕获该信号,若其携带的结构体kernel_siginfo中的成员变量si_code值为SEGV_BNDERR,则认为此信号为SIGBCE,glibc将接管其处理. 否则按SIGUSR1信号进行处理,保证了其他正常使用SIGUSR1信号的程序不会受到影响.

    为了识别和处理内核发出的边界检查异常信号SIGBCE,本文在运行时库glibc中设计并实现了SIGBCE信号处理函数sigbce_sigaction,其功能主要是在用户进程捕获到SIGBCE信号之后,解析GCC嵌入的pcaddi指令获得跳转的目标地址,引导用户程序跳转到该目标地址去执行相关逻辑.

    信号处理函数sigbce_sigaction的伪代码如函数1所示. 无论是无异常处理还是带异常处理的边界检查访存,在条件不满足时都不进行访存操作、不修改数据寄存器的内容,因此信号处理函数sigbce_sigaction首先需要将PC寄存器加4跳过边界检查访存指令. 然后,信号处理函数获取紧随在边界检查访存指令之后的那条指令并进行解析,如果是嵌入了信息的pcaddi形式的nop指令(如图7所示),则计算其编码的相对跳转目标地址,并将PC寄存器的值设为此地址. 信号处理函数结束后,更新上下文,之后回到用户程序从新的PC开始执行.

    函数1. sigbce_sigaction.

    输入:用户程序上下文ctx

    输出:更新的用户程序上下文ctx.

    ctxpc += 4;

    inst = get_inst_word(ctxpc);

    ③ if (is_inst_pcaddi(inst)) then /*判断是否为nop*/

    ④ if (is_inst_rd_zero(inst)) then /*计算PC相对  寻址*/

    ⑤  target = get_pcaddi_pcrel(ctxpcinst);

    ⑥  ctxpc = target

    ⑦ end if

    ⑧ end if   /*结束信号处理,更新上下文*/

    signal_return(ctx).

    信号处理函数sigbce_sigaction为了获得带异常处理情况下指令的相对跳转目标地址,通过解析函数get_pcaddi_pcrel解析GCC编译嵌入的pcaddi指令. 如图8所示,嵌入的pcaddi指令最高25~31 b的操作码字段为0001100,5~24 b为异常处理程序的相对地址,即立即数si20,最低0~4 b寄存器rd为00000. 因此只需将该条指令字左移7 b,再逻辑右移12 b,即可得到相对跳转地址.

    图  8  pcaddi指令编码
    Figure  8.  Encoding format of the pcaddi instruction

    执行完信号处理函数后,将通过系统调用sigreturn返回到内核,从内核再返回到用户程序中断处继续运行. 系统调用sigreturn的程序上下文实际保存在当前信号处理函数的栈中,包含了系统调用sigreturn执行完成返回用户态的返回地址. 因此设置新的PC需要在当前栈中找到系统调用sigreturn使用的返回地址并将其修改为新的PC.

    为了使用户程序对边界检查访存指令所触发异常的处理无感知,SIGBCE信号处理函数的实现与注册应当对用户透明. 为此,函数sigbce_sigaction注册使用标准的sigaction方式,并添加到glibc对用户程序运行环境的初始化步骤当中. 此外,为了避免用户程序修改信号处理函数而导致错误,在glibc导出给用户程序的函数sigaction等中,检查用户程序是否试图修改处理SIGBCE信号的信号处理函数,如果是,则阻止相应的操作. 对于希望自己灵活处理SIGBCE信号的用户程序,可直接使用Linux内核系统调用而不使用glibc导出的函数sigaction.

    本节对改进后的GCC编译器优化效果进行实验分析,首先检查编译器是否正确使用LoongArch边界检查访存指令,然后分析改进后的编译器编译用户程序是否获得了性能提升.

    本文采用GCC-12.2.0编译器作为基础,针对LoongArch边界检查访存指令进行改进. GCC-12.2.0支持LoongArch架构基本指令集,已经并入主线并发布,但是还不支持LoongArch边界检查访存指令. 本文采用的实验平台为龙芯3C5000L服务器,所修改Linux内核版本为6.3.0. 为了分析实验结果,采用Linux perf工具读取性能监测单元PMU(performance monitor unit)记录的细节信息.

    本文通过设计内建函数和改进RTL优化器实现LoongArch边界检查访存指令的编译优化. 对于内建函数的正确性检查,只需使用改进后GCC编译器编译涉及边界检查访存指令内建函数的代码,检查编译器是否正确识别内建函数即可. 图9(a) 是使用本文设计的内建函数编写的C语言程序片段,其含义是读取数组元素、检查下标是否存在超出上界的情况. 图9(b) 是改进后的GCC编译器编译该程序片段得到的汇编代码. 从图9(b) 可以看出改进后的GCC编译器能够正确识别图9(a) 中的内建函数,使用LoongArch边界检查访存指令ldle.d实现访存地址小于等于边界地址的检查和访存,并通过pcaddi指令正确嵌入了跳转地址信息.

    图  9  基于LoongArch边界检查访存指令的内建函数GCC编译结果
    Figure  9.  GCC compilation result for built-in function based on LoongArch memory accessing instruction with bound-checking

    对于RTL优化器的改进,则需要检查改进后的编译器是否能正确识别出代码中隐含的无异常处理边界检查访存和带异常处理边界检查访存. 此外,对于带异常处理的情况,还需要检查所嵌入的信息是否正确. 图10(a)与图10(b)是改进后的GCC编译器分别编译图5(a)与图5(b)所示2种场景的C语言程序片段所得汇编代码. 图10(a)表明改进后的编译器能够基于LoongArch边界检查访存指令编译优化无异常处理的边界检查访存程序;图10(b)表明改进后的编译器能够基于LoongArch边界检查访存指令编译优化带异常处理的边界检查访存程序,且通过pcaddi指令正确嵌入了跳转地址信息. 本文改进的GCC编译器成功识别了用户程序隐含的边界检查访存语义,并基于边界检查访存指令正确实现了程序的编译.

    图  10  基于LoongArch边界检查访存指令的RTL优化的GCC编译结果
    Figure  10.  GCC compilation result of RTL optimization based on LoongArch memory accessing instruction with bound-checking

    LoongArch边界检查访存指令主要是针对需要检查访存地址的一类安全函数的优化而设计的,这类安全函数通过检查访存地址来阻止程序访问不应当访问的内存区域,例如阻止缓冲区溢出攻击. 为了检验改进后的编译器对于程序优化的效果,本文选取了以下3个典型的安全函数:

    1)函数strncat,glibc的一种安全的C字符串操作函数,用于将2个字符串连接为1个. 相比于函数strcat,函数strncat指定了用于保存连接结果的字符串缓冲区的大小,在复制字符时检查指针是否超过缓冲区上界. 此安全函数可阻止缓冲区溢出攻击.

    2)函数strncpy,glibc的一种安全的C字符串操作函数,用于将1个字符串复制到另1个缓冲区. 相比于函数strcpy,函数strncpy指定了目标字符串缓冲区的大小,在复制字符时检查指针是否超过缓冲区上界. 此安全函数可阻止缓冲区溢出攻击.

    3)函数vector_get,此安全函数安全地获取数组元素,检查所欲访问的下标是否在合法范围内.

    针对以上3个函数,对比相应的前后实现版本,即直接使用改进前未优化的GCC-12.2.0编译生成的版本、使用改进后利用边界检查访存指令优化的GCC-12.2.0编译生成的版本. 由于单独的安全函数不能直接执行,本文为上述安全函数分别编写了相应的测试程序,每一个安全函数是测试程序的一部分. 如图11所示,每个测试程序按各自的程序逻辑调用安全函数实现其功能.

    图  11  测试程序组织结构
    Figure  11.  Framework of testing programs

    为了消除其他因素可能对实验结果带来的影响,需要控制其他代码不变、仅改变安全函数的编译实现,对比改进前后编译器对安全函数的优化效果. 通过采用动态链接技术,将安全函数单独在动态库中实现. 位于主可执行文件的用户程序逻辑调用位于动态库中的安全函数,执行安全检查并完成相应的功能. 通过替换动态库文件,选择在运行时使用或不使用优化版本的安全函数. 如此,所对比运行的程序中,其他程序逻辑的二进制代码实现完全一致,只有安全函数因为编译器优化而有不同的实现.

    1)执行时间对比.

    表2是测试程序strncat、测试程序strncpy以及测试程序vector_get执行多次的平均CPU执行时间的比较. 每个测试程序包含使用未改进的GCC编译版本,以及使用基于LoongArch边界检查访存指令改进后的GCC编译优化版本(在表中带星号*注明),时间单位均为CPU周期数,性能提升比例是指优化后的GCC版本的测试程序执行时间减少的比例.

    表  2  执行时间优化对比
    Table  2.  Optimized Execution Time Comparison
    测试程序 CPU时间/周期数 性能提升比例/%
    strncat 14 528 340 679
    strncat* 11 710 354 480 19.4
    strncpy 7 555 284 838
    strncpy* 6 265 294 025 17.1
    vector_get 13 360 290 911
    vector_get* 12 080 285 716 9.58
    注:原版程序性能提升比例表项无意义,在表中记为“—”.
    下载: 导出CSV 
    | 显示表格

    表2可以看出,测试程序strncat和测试程序strncpy,程序执行时间分别从1.45×1010周期数改进到1.17×1010周期数、以及从7.56×109周期数改进到6.27×109周期数,改进效果非常显著,性能提升比例均超过15%,最高接近20%. 测试程序vector_get的程序执行时间从1.37×1010周期数改进到1.21×1010周期数,性能提升比例也接近10%.

    通过表2可以得出,3个测试程序的改进后GCC编译器所编译版本的执行时间均有所减小,性能得到了提升. 因此,本文改进的GCC编译器能够有效地支持LoongArch边界检查访存指令,并且可以显著提升安全函数的性能.

    2)程序指令数对比.

    使用LoongArch边界访存指令直接在访存指令中实现安全检查,省去了分支指令,减小了执行指令总数. 为了考察改进后的GCC编译器对于测试程序的指令数的影响,本文进一步分析对比测试程序的执行指令数.

    图12是使用Linux perf记录的测试程序strncat、测试程序strncpy以及测试程序vector_get使用改进前后GCC编译器编译所得版本的执行指令数,包括执行指令总数以及执行分支指令数. 其中无纹样条代表执行分支指令数,斜纹样条代表非分支指令的执行指令数,由执行指令总数减去执行分支指令数得到.

    图  12  测试程序的动态指令数比较
    Figure  12.  Run-time instructions counts of testing programs

    依据图12统计的指令数,对于测试程序strncpy与测试程序strncat,执行指令总数的减少主要是分支指令数减少贡献的. 可以推测,由于使用LoongArch边界访存指令消除了循环体中安全检查的分支指令,减小了总体分支不命中次数,允许处理器更好地动态循环展开,显著提高了性能.

    此外,从图12还可以看出,在改进后的GCC编译器优化下,测试程序vector_get不仅所执行的分支指令减少了,所执行的其他指令也有所减少. 这说明改进后的GCC编译器发现了新的优化机会,实现了更充分的优化. 可以推测,在消除了安全检查的分支指令后,不必要的基本块也被消除,从而使部分被基本块隔断而不能进行的优化能够进行.

    3)起始地址的影响.

    根据2)的程序指令数分析,使用LoongArch边界检查访存指令的编译优化会减少用户程序的分支指令、消除基本块,使测试程序的静态指令数发生变化. 这使得优化前后安全函数的指令内存布局发生变化,可能会影响指令缓存的利用,进而影响程序执行的性能. 因此,本文以测试程序strncpy为例,进一步分析在改进前后GCC编译器优化下,安全函数strncpy放置在不同的起始地址处对程序执行的影响.

    图13是安全函数strncpy放置在不同的起始地址处、使用或不使用边界检查访存指令的GCC编译器优化的测试程序strncpy的执行时间,仍以CPU时钟周期数为单位. 所记录的起始地址从某个随机选择的地址A开始,按照LoongArch架构指令4B对齐的要求,之后每个地址依次加4,共记录32个地址. 图13中实线表示优化前的GCC编译器所编译测试程序的执行时间,虚线表示使用边界检查访存指令优化的GCC编译器所编译测试程序的执行时间.

    图  13  strncpystrncpy*在不同起始地址的执行时间比较
    Figure  13.  Execution time of strncpy and strncpy* with different start addresses

    图13可以看出,测试程序执行时间对安全函数strncpy放置的起始地址呈现出周期性变化,每8个地址为1个周期,虚线整体在实线之下. 由于实验采用动态链接技术,测试程序strncpy和测试程序strncpy*只有安全函数strncpy因为编译器优化而有不同的实现,其他程序逻辑的二进制代码实现完全一致. 故由图13可得,即使在指令内存布局发生变化的情况下,使用LoongArch边界检查访存指令编译优化后得到的安全函数strncpy,相比于优化前,总体上也可认为具有更好的性能.

    4)实验结果分析.

    综合以上实验结果可以看出,改进后的GCC编译器不仅能够成功识别出用户程序代码中隐含的无异常处理边界检查访存语义和带异常处理边界检查访存语义,并且在这2种异常处理的语义场景下都正确使用了LoongArch边界检查访存指令实现相应的编译优化.

    LoongArch边界检查访存指令可以消除安全检查的分支指令,减少安全函数的静态指令数,进而减少程序动态执行指令数. 从实验结果可以看出,改进后的GCC编译器有效利用了LoongArch边界检查访存指令,通过硬件实现访存的安全检查操作,显著优化了安全函数strncpystrncatvector_get,且不会影响指令缓存的利用和程序执行的性能.

    理论上来说,使用本文改进的GCC编译器编译程序不会导致性能下降,因为在不触发边界检查异常的一般情况下边界检查访存指令没有额外的性能开销且不会增加程序执行的指令数.

    本文基于LoongArch指令集,针对其特有的边界检查访存指令改进了GCC编译器、Linux内核以及glibc运行库,所做优化设计特点总结如下:1)对LoongArch边界检查访存指令的各种配置组合(读写操作、整数浮点、数据大小、上界下界)均有效可行;2)区分了无异常处理与带异常处理2种边界检查访存指令应用场景,确定边界检查异常的处理,便于语义识别;3)设计实现了边界检查访存内建函数,提示程序优化;4)改进了RTL优化器,在RTL优化阶段识别边界检查访存语义,实现自动编译优化;5)改进了边界检查异常处理机制和运行环境,确保程序实际执行语义和编译优化语义相一致,并使用户对编译优化改进无感知;6)对一些典型的安全函数,减小了安全检查带来的开销,性能改善可接近20%. 总的来说,本文工作完善LoongArch生态、有助于推进LoongArch指令集发展,文中提出的特定指令编译优化方法对于此类工作有一定的参考价值.

    作者贡献声明:舒燕君提出了主要思路和论文的整体框架;郑翔宇撰写了第1节及第2节内容;徐成华和黄沛撰写了第3节及第4节内容;王永琪和周凡撰写了第5节内容;郑翔宇和张展撰写了第6节及第7节内容;左德承提出指导意见并修改论文.

  • 图  1   LoongArch边界检查访存指令格式

    Figure  1.   The format of LoongArch memory accessing instructions with bound-checking

    图  2   GCC优化的工作过程

    Figure  2.   The workflow of GCC optimization

    图  3   LoongArch边界检查访存指令GCC优化的总体设计

    Figure  3.   The design of GCC optimization for LoongArch memory accessing instructions with bound-checking

    图  4   边界检查访存指令语义

    Figure  4.   Semantic of memory accessing instruction with bound-checking

    图  5   C语言用户程序示例与边界检查访存语义设计

    Figure  5.   Examples in C language and semantic designs of memory access with bound-checking

    图  6   内建函数原型

    Figure  6.   Prototypes of built-in functions

    图  7   第4操作数嵌入方法

    Figure  7.   Encoding the fourth operand

    图  8   pcaddi指令编码

    Figure  8.   Encoding format of the pcaddi instruction

    图  9   基于LoongArch边界检查访存指令的内建函数GCC编译结果

    Figure  9.   GCC compilation result for built-in function based on LoongArch memory accessing instruction with bound-checking

    图  10   基于LoongArch边界检查访存指令的RTL优化的GCC编译结果

    Figure  10.   GCC compilation result of RTL optimization based on LoongArch memory accessing instruction with bound-checking

    图  11   测试程序组织结构

    Figure  11.   Framework of testing programs

    图  12   测试程序的动态指令数比较

    Figure  12.   Run-time instructions counts of testing programs

    图  13   strncpystrncpy*在不同起始地址的执行时间比较

    Figure  13.   Execution time of strncpy and strncpy* with different start addresses

    表  1   边界检查访存指令汇编记号

    Table  1   Assembly Mnemonics of Memory Accessing Instructions with Bound-Checking

    op cond width
    ld,st,fld,fst le,gt b,h,w,d,s
    注:width类型记号d可表示8 B整数或双精度浮点数.
    下载: 导出CSV

    表  2   执行时间优化对比

    Table  2   Optimized Execution Time Comparison

    测试程序 CPU时间/周期数 性能提升比例/%
    strncat 14 528 340 679
    strncat* 11 710 354 480 19.4
    strncpy 7 555 284 838
    strncpy* 6 265 294 025 17.1
    vector_get 13 360 290 911
    vector_get* 12 080 285 716 9.58
    注:原版程序性能提升比例表项无意义,在表中记为“—”.
    下载: 导出CSV
  • [1] 蒋卫华,李伟华,杜君. 缓冲区溢出攻击:原理,防御及检测[J]. 计算机工程,2003,29(10):5−7 doi: 10.3969/j.issn.1000-3428.2003.10.003

    Jiang Weihua, Li Weihua, Du Jun. Buffer overflow attack: Theory, recovery and detection[J]. Computer Engineering, 2003, 29(10): 5−7 (in Chinese) doi: 10.3969/j.issn.1000-3428.2003.10.003

    [2] 李亚伟,章隆兵,张福新,等. 基于软硬协同的程序运行时安全保护机制[J]. 计算机学报,2023,46(1):180−201 doi: 10.11897/SP.J.1016.2023.00180

    Li Yawei, Zhang Longbing, Zhang Fuxin, et al. A security protection mechanism on program runtime based on software and hardware cooperation[J]. Chinese Journal of Computers, 2023, 46(1): 180−201 (in Chinese) doi: 10.11897/SP.J.1016.2023.00180

    [3]

    Otterstad C W. A brief evaluation of intel®mpx[C]//Proc of the 9th Annual IEEE Systems Conf. Piscataway, NJ: IEEE, 2015: 1−7

    [4]

    Serebryany K. ARM memory tagging extension and how it improves C/C++ memory safety[J]. Login The USENIX Magazine, 2019, 44(2): 12−16

    [5] 胡伟武,汪文祥,吴瑞阳,等. 龙芯指令系统架构技术[J]. 计算机研究与发展,2023,60(1):2−16 doi: 10.7544/issn1000-1239.202220196

    Hu Weiwu, Wang Wenxiang, Wu Ruiyang, et al. Loongson instruction set architecture technology[J]. Journal of Computer Research and Development, 2023, 60(1): 2−16 (in Chinese) doi: 10.7544/issn1000-1239.202220196

    [6]

    Patterson D A, Ditzel D R. The case for the reduced instruction set computer[J]. SIGARCH Computer Architecture News, 1980, 8(6): 25−33 doi: 10.1145/641914.641917

    [7]

    GCC Team. GCC wiki [EB/OL]. [2024-02-05]. https://gcc.gnu.org/wiki/Intel MPX support in the GCC compiler

    [8]

    Clang Team. Clang documentation [EB/OL]. [2024-02-05]. https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html

    [9]

    Loongson Technology Corporation. LoongArch architecture manual [EB/OL]. [2024-02-05]. https://www.loongson.cn/system/loongarch

    [10]

    Diego N. Tree SSA ― A new high-level optimization framework for the GNU compiler collection[C/OL]//Proc of the 5th NordU/USENIX Users Conf. Berkeley, CA: USENIX Association, 2003[2024-06-15]. https://www.airs.com/dnovillo/Papers/nordu2003.pdf

    [11] 王文义,武华北. Linux中进程间信号通信机制的分析及其应用[J]. 计算机工程与应用,2005,41(3):108−110,115 doi: 10.3321/j.issn:1002-8331.2005.03.035

    Wang Wenyi, Wu Huabei. Analysis and application of the signal communication mechanism of Linux[J]. Computer Engineering and Applications, 2005, 41(3): 108−110, 115 (in Chinese) doi: 10.3321/j.issn:1002-8331.2005.03.035

    [12]

    Oleksenko O, Kuvaiskii D, Bhatotia P, et al. Intel MPX explained: A cross-layer analysis of the Intel MPX system stack[J]. Proceedings of the ACM on Measurement and Analysis of Computing Systems, 2018, 2(2): 28: 1−28: 30

    [13]

    Rigger M, Marr S, Kell S, et al. An analysis of x86−64 inline assembly in C programs[C]//Proc of the 14th ACM SIGPLAN/SIGOPS Int Conf on Virtual Execution Environments. New York: ACM, 2018: 84−99

    [14] 赵翔,贾海鹏,张云泉,等. 基于ARMv8处理器的实数FFT实现与性能优化研究[J]. 计算机学报,2023,46(5):1003−1018 doi: 10.11897/SP.J.1016.2023.01003

    Zhao Xiang, Jia Haipeng, Zhang Yunquan, et al. Real FFT implementation and performance optimization based on ARMv8 CPUs[J]. Chinese Journal of Computers, 2023, 46(5): 1003−1018 (in Chinese) doi: 10.11897/SP.J.1016.2023.01003

    [15]

    Cai Lulu, Wang Yagang, Chen Xiaolong. Glibc hot spot function assembly optimization for loongarch[C]//Proc of the 41st Chinese Control Conf. Piscataway, NJ: IEEE, 2022: 2053−2058

    [16] 杨昊,刘哲,黄军浩,等. AKCN-MLWE算法AVX2高效实现[J]. 计算机学报,2021,44(12):2560−2572 doi: 10.11897/SP.J.1016.2021.02560

    Yang Hao, Liu Zhe, Huang Junhao, et al. High-speed AVX2 implementation of AKCN-MLWE[J]. Chinese Journal of Computers, 2021, 44(12): 2560−2572 (in Chinese) doi: 10.11897/SP.J.1016.2021.02560

    [17] 赵龙,韩文报,杨宏志. 基于SIMD指令的ECC攻击算法研究[J]. 计算机研究与发展,2012,49(7):1553−1559

    Zhao Long, Han Wenbao, Yang Hongzhi. Research on ECC attacking algorithm based on SIMD instructions[J]. Journal of Computer Research and Development, 2012, 49(7): 1553−1559 (in Chinese)

    [18] 沈洁,龙标,姜浩,等. 飞腾处理器上向量三角函数的设计实现与优化[J]. 计算机研究与发展,2020,57(12):2610−2620 doi: 10.7544/issn1000-1239.2020.20190721

    Shen Jie, Long Biao, Jiang Hao, et al. Implementation and optimization of vector trigonometric functions on Phytium processors[J]. Journal of Computer Research and Development, 2020, 57(12): 2610−2620 (in Chinese) doi: 10.7544/issn1000-1239.2020.20190721

    [19]

    Rigger M, Marr S, Adams B, et al. Understanding GCC builtins to develop better tools[C]//Proc of the ACM Joint Meeting on European Software Engineering Conf and Symp on the Foundations of Software Engineering, 18th ESEC/27th SIGSOFT FSE 2019. New York: ACM, 2019: 74−85

    [20]

    Chen Xiaolong, Wang Yagang, Cai Lulu. GCC built-in function mechanism analysis and LoongArch-based implementation[C]//Proc of the 41st Chinese Control Conf. Piscataway, NJ: IEEE, 2022: 2046−2052

    [21]

    Koppelmann B, Adelt P, Mueller W, et al. RISC-V extensions for bit manipulation instructions[C]//Proc of the 29th Int Symp on Power and Timing Modeling, Optimization and Simulation. Piscataway, NJ: IEEE, 2019: 41−48

    [22]

    Babu P S, Sivaraman S, Sarma D N, et al. Evaluation of bit manipulation instructions in optimization of size and speed in RISC-V[C]//Proc of the 34th Int Conf on VLSI Design and 20th Int Conf on Embedded Systems, VLSID 2021. Piscataway, NJ: IEEE, 2021: 54−59

    [23]

    Levy M, Olson R. Autovectorization for GCC compiler[J]. Electrical Design News: The Magazine of the Electronics Industry, 2007, 52(15): 69−70, 72, 74

    [24] 姜伟华,梅超,郭一,等. 一种针对多媒体扩展指令集和实际多媒体程序的自动向量化方法[J]. 计算机学报,2005,28(8):1255−1266 doi: 10.3321/j.issn:0254-4164.2005.08.002

    Jiang Weihua, Mei Chao, Guo Yi, et al. Vectorization for real-life multimedia applications on processors’ multimedia extensions[J]. Chinese Journal of Computers, 2005, 28(8): 1255−1266 (in Chinese) doi: 10.3321/j.issn:0254-4164.2005.08.002

    [25] 冯竞舸,贺也平,陶秋铭,等. 基于多种同构化变换的SLP向量化方法[J]. 计算机研究与发展,2023,60(12):2907−2927 doi: 10.7544/issn1000-1239.202220354

    Feng Jingge, He Yeping, Tao Qiuming, et al. SLP vectorization method based on multiple isomorphic transformations[J]. Chinese Journal of Computers, 2023, 60(12): 2907−2927 (in Chinese) doi: 10.7544/issn1000-1239.202220354

    [26] 田祖伟,孙光. 基于谓词代码的编译优化技术研究[J]. 计算机科学,2010,37(5):130−133,138 doi: 10.3969/j.issn.1002-137X.2010.05.031

    Tian Zuwei, Sun Guang. Research of compiler optimization technology based on predicated code[J]. Computer Science, 2010, 37(5): 130−133,138 (in Chinese) doi: 10.3969/j.issn.1002-137X.2010.05.031

    [27] 王凤芹,胡定磊,刘春林. 一种基于谓词执行优化技术的寄存器分配算法[J]. 计算机研究与发展,2006,43(8):1471−1476 doi: 10.1360/crad20060824

    Wang Fengqin, Hu Dinglei, Liu Chunlin. A register allocation algorithm for predicated code[J]. Journal of Computer Research and Development, 2006, 43(8): 1471−1476 (in Chinese) doi: 10.1360/crad20060824

图(13)  /  表(2)
计量
  • 文章访问数:  55
  • HTML全文浏览量:  13
  • PDF下载量:  9
  • 被引次数: 0
出版历程
  • 收稿日期:  2024-02-20
  • 录用日期:  2024-09-02
  • 网络出版日期:  2024-09-12

目录

/

返回文章
返回