掌桥专利:专业的专利平台
掌桥专利
首页

一种提高RISC-V二进制代码密度的寄存器分配方法

文献发布时间:2023-06-19 16:08:01



技术领域

本发明涉及一种计算机编译领域的寄存器分配方法,尤指一种编译过程中能够提高RISC-V二进制代码密度的寄存器分配方法。

背景技术

目前,RISC-V指令集架构(其中RISC表示精简指令集计算机,全称ReducedInstruction Set Computer,RISC-V是基于RISC的一个开源指令集架构)以其开放、简洁、模块化等特点受到业界广泛关注,编译器对RISC-V指令集架构的支持直接影响到其发展与推广。二进制代码密度是衡量一款芯片特别是嵌入式芯片开发环境的重要指标之一,直接影响着嵌入式芯片的面积和成本。如何提高最终生成的二进制目标代码的代码密度一直是本领域技术人员极为关注的技术问题。

RISC-V指令集是模块化的指令集,以RV32I整数指令集为基础,根据不同的设计需求,可以通过扩展得到特定的扩展指令集:如RV32M(乘法指令集)、RV32F/RV32D(单精度/双精度浮点指令集)、RVC(压缩指令集)等。

为了提高二进制代码密度,一般指令集架构通常采用复合指令和压缩指令方式实现。而RISC-V指令集在常用标准操作中加入16位指令,即RISC-V压缩指令,以减少二进制代码长度。RISC-V压缩指令集中,每条16位压缩指令都和一条标准的32位指令一一对应。一条指令是否编码为压缩指令由汇编器和链接器决定,程序员编写汇编时可以完全忽略RISC-V压缩指令及其格式。

RISC-V压缩指令具有以下特点:

1)压缩指令的操作数是访问频率最高的十个常用寄存器(即RISC-V指令集架构中的a0-a5,s0-s1,sp以及ra);

2)压缩指令的目的操作数和源操作数是同一个寄存器;

3)压缩指令的立即数的位数很小,大部分指令只有6位立即数。

因此,在支持RISC-V压缩指令集(即RVC)的RISC-V平台(指采用RISC-V指令集架构的计算机系统)上,汇编生成的二进制代码长度大大缩短,代码密度比标准指令集有所提高。

处理器在运行软件程序时,变量必须存放在硬件寄存器。而每个变量应该存放在哪个硬件寄存器,由编译器在编译阶段进行分配,这个过程叫做编译器的寄存器分配。在生成中间代码的过程中,编译器首先假定有无限个虚拟寄存器用以存放临时变量,但是实际的机器体系结构中硬件寄存器是有限且少量的。因此,编译器寄存器分配的任务就是将大量的虚拟寄存器映射到机器实际的体系结构中的真实硬件寄存器上。编译器的寄存器分配是影响代码密度的关键因素。如何在寄存器分配过程中最大程度地减少代码溢出所产生的代价,是提高代码密度必须考虑的因素之一。现有编译器中的寄存器分配方法没有考虑到在RISC-V指令集架构上跨函数活跃变量的寄存器分配方案会导致不同的二进制指令数目的情况,不利于RISC-V二进制代码密度优化,具有优化空间。

一般来说,编译器会将跨函数调用活跃的临时变量分配到由被调用者保存的寄存器,此时编译器只需要在被调用函数的开始和结束处做一次保存和恢复该类寄存器即可;编译器会将局部变量或任何跨函数调用都不活跃的临时变量分配到由调用者保存的寄存器,由于局部变量或任何跨函数调用都不活跃的临时变量未跨任何函数调用,因此在这种情况下完全不需要保存和恢复这些寄存器。

在现有的通用编译平台中,根据RISC-V指令集架构的应用二进制接口ABI(application binary interface)规则,将RISC-V指令集架构的寄存器划分为调用者寄存器即caller-saved寄存器和被调用者寄存器即callee-saved寄存器。caller-saved寄存器是由调用者(Caller)保存的寄存器,当发生函数调用时,由调用者在函数调用前将此类寄存器保存至堆栈中,函数调用后恢复并使用;callee-saved寄存器是由被调用者(Callee)保存的寄存器,当发生函数调用时,由被调用者负责保存与恢复。

在通用编译平台中,caller-saved寄存器和callee-saved寄存器的划分如表1所示。根据表1中的ABI规则,参数寄存器a0~a7和临时寄存器t0~t6(浮点寄存器分别对应fa0~fa7和ft0~ft11)属于caller-saved寄存器,而寄存器s0~s11(浮点寄存器对应fs0~fs11)属于callee-saved的寄存器。表1中寄存器Type为“--”的表示该寄存器既不作为caller寄存器、也不作为callee寄存器使用。根据上述寄存器分配的规则,编译器将跨函数调用活跃的临时变量分配到s0~s11寄存器,并由编译器在被调用函数的开始和结束处分别生成保存和恢复该寄存器的代码;将局部变量或任何跨函数调用都不活跃的临时变量分配到a0~a7或t0~t6寄存器,编译器依据该临时变量在跨函数时是否活跃来决定是否保存和恢复该寄存器。

表1RISC-V指令集架构寄存器别名及函数调用规则

目前已有的编译器的RISC-V后端的寄存器分配方法是:将跨函数调用中的活跃变量对应的虚拟寄存器优先分配到callee-saved寄存器即s0-s11寄存器中,再将剩余的跨函数调用中的活跃变量对应的虚拟寄存器分配到caller-saved寄存器即a0-a7或t0-t6寄存器中。但是分配到这两种不同类型寄存器所生成的代码长度不同。比如,活跃变量在跨一个函数调用时,分配到caller-saved寄存器时,只需要生成sw指令(将寄存器中的值写入数据存储器)和lw指令(从数据存储器中取数据写进寄存器)两条指令,而分配到callee-saved寄存器则需要sw指令、lw指令和mv指令(为文件或目录改名或将文件由一个目录移入到另一个目录中)共三条指令,这样跟一个变量相关的代码将额外多出mv指令这4个字节。因此,跨函数调用中的活跃变量对于ing的虚拟寄存器分配到callee-saved寄存器或者caller-saved寄存器会给最终的二进制代码密度带来影响,但是现有的编译器的寄存器分配方法是固定的,即将跨函数活跃的变量对应的虚拟寄存器优先分配给callee-saved寄存器,如果callee-saved寄存器不可用了才分配到caller-saved寄存器,无法最优化地进行callee-saved寄存器或者caller-saved寄存器两种分配的选择,导致额外的指令操作,使得最终的程序代码密度降低。

发明内容

本发明要解决的技术问题是:针对现有编译器的RISC-V后端寄存器分配方法将跨函数调用中活跃变量对应的虚拟寄存器固定地优先分配到callee-saved寄存器,在某些情况下这将导致额外指令操作使得程序代码密度降低的问题,提出RISC-V代码密度优先的寄存器分配方法,采用该方法能够准确判断将跨函数调用中活跃变量对应的虚拟寄存器分配到caller-saved寄存器还是callee-saved寄存器更有利于减少指令条数,有效提高编译器生成的RISC-V二进制代码的密度。

本发明总体技术方案是:提出活跃变量寄存器分配代价模型,基于该代价模型对跨区域活跃变量的寄存器进行分配,从而减少编译生成的指令条数,提高编译器生成的二进制代码密度。

具体技术方案是:

第一步,构建活跃变量寄存器分配代价模型:

根据跨区域活跃变量不同寄存器的分配方式,构建以函数为单位的二进制代码密度代价公式即活跃变量寄存器分配代价模型,如下:

callee_cost(r)=callee_save_cost+callee_restore_cost+callee_move_costr∈所有待分配硬件寄存器的虚拟寄存器集合,公式(1);

caller_cost(r)=(caller_save_cost+caller_restore_cost)×n(r)+caller_move_cost r∈所有待分配硬件寄存器的虚拟寄存器集合,公式(2);

公式中caller_save_cost表示当把跨区域活跃变量对应的虚拟寄存器分配到caller类寄存器时在跨函数调用前后或者函数开始和结束处保存寄存器的代价;callee_save_cost表示当把跨区域活跃变量对应的虚拟寄存器分配到callee类寄存器时在跨函数调用前后或者函数开始和结束处保存寄存器的代价;caller_restore_cost表示当把跨区域活跃变量对应的虚拟寄存器分配到caller类寄存器时在跨函数调用前后或者函数开始和结束处恢复寄存器的代价;callee_restore_cost表示当把跨区域活跃变量对应的虚拟寄存器分配到callee类寄存器时在跨函数调用前后或者函数开始和结束处恢复寄存器的代价。在寄存器分配过程中,如果一个变量对应的虚拟寄存器已经被分配了寄存器,caller_move_cost表示在保存caller-saved寄存器之前,将已分配寄存器的变量的值放到caller-saved寄存器的代价;callee_move_cost表示在保存callee-saved寄存器之前,将已分配寄存器的变量的值放到callee-saved寄存器的代价。代价仅指对二进制代码密度的影响,不包括对二进制代码运行速度的影响。参数r为所有待分配硬件寄存器的虚拟寄存器中任意一个。n(r)是r跨函数调用活跃的次数。

公式(1)中callee_cost(r)表示分配callee-saved寄存器后编译器需要额外保存和恢复的代价。分配callee-saved寄存器不用考虑变量跨函数调用的活跃区间范围,仅需考虑在函数的开始和结束处保存和恢复寄存器的代价,以及已分配寄存器的变量的值放到callee-saved寄存器的代价。

公式(2)中caller_cost(r)表示分配caller-saved寄存器后编译器需要额外保存和恢复的代价。因为caller-saved寄存器每次在跨函数调用活跃时,都需要保存和恢复一次寄存器,因此,需要计算r跨函数调用活跃的次数,即n(r)的值;由于在开启编译器的-Os优化选项(当编译一个源程序,目的要获得较小的二进制代码的情况下,一定是会开启编译器的-Os优化选项)的前提下,变量的值放到caller-saved寄存器的操作将会在后续优化的寄存器重名和死代码删除中优化删除掉,在开启编译器的-Os优化的情况下caller_move_cost为0。

第二步,判定被编译程序的函数列表中有没有未进行寄存器分配的函数,若有,令函数列表中第一个未进行寄存器分配的函数为当前被分析函数,转第三步;若没有,转第九步;

第三步,分析当前被分析函数的每一条指令,建立第一指令链表和第二指令链表。将每条指令中所含虚拟寄存器的定值信息和活跃信息存储到第一指令链表中。第一指令链表中共有K个元素,K为当前被分析函数的指令中所含的虚拟寄存器个数;第k个元素包括当前被分析函数中第k个虚拟寄存器R

第四步,将第一指令链表和第二指令链表进行对比,得到当前被分析函数中跨函数调用活跃的虚拟寄存器的个数N和这N个虚拟寄存器跨函数调用活跃的次数,构造跨函数调用活跃的虚拟寄存器列表,方法是:

4.1分析第一指令链表中的虚拟寄存器中哪些是跨函数调用活跃的虚拟寄存器。若第一指令链表中出现的虚拟寄存器同时出现在第二链表中,则该虚拟寄存器为跨函数调用活跃的虚拟寄存器。记跨函数调用活跃的虚拟寄存器的个数为N。

4.2在第二指令链表里寻找4.1获得的N个跨函数调用活跃的虚拟寄存器,这N个跨函数调用活跃的虚拟寄存器分别在第二指令链表中出现的次数即为当前被分析函数中这N个虚拟寄存器跨函数调用活跃的次数,令这N个跨函数调用活跃的虚拟寄存器中的第i个跨函数调用活跃的虚拟寄存器为R

4.3构造跨函数调用活跃的虚拟寄存器列表,跨函数调用活跃的虚拟寄存器列表包含N个表项,每个表项包含2个域,第i个表项的第一个域为虚拟寄存器名称R

第五步,令循环控制变量i=1;

第六步,若i≤N,将跨函数调用活跃的虚拟寄存器列表中第i个未分析的虚拟寄存器作为当前分析虚拟寄存器r,即令r=R

第七步,基于活跃变量寄存器分配代价模型计算当前分析虚拟寄存器指派给caller-saved寄存器的收益caller_benefit,并根据caller_benefit将当前被分析函数中跨函数调用的虚拟寄存器分配给caller-saved寄存器或callee-saved寄存器,方法是:

7.1若目标平台不支持压缩指令,转7.2;若目标平台支持压缩指令,转7.4;

7.2采用公式(3)计算当前被分析函数中跨函数调用的虚拟寄存器优先分配给caller-saved寄存器后能缩减代码空间的收益caller_benefit。

caller_benifit=callee_cost(r)-caller_cost(r)

r∈所有待分配寄存器组成的虚拟寄存器集合,公式(3)

7.3通过caller_benefit判断当前被分析函数中跨函数调用的虚拟寄存器是否应该优先分配给caller-saved寄存器,方法是:若caller_benefit≥零,表示优先分配给callee-saved寄存器的代价大于优先分配给caller-saved寄存器的代价,将当前虚拟寄存器优先指派给caller-saved寄存器;若caller_benefit<0,表示优先分配给callee-saved寄存器的代价小于优先分配给caller-saved寄存器的代价,将当前虚拟寄存器优先指派给callee-saved寄存器(编译器的默认操作)。转第八步;

7.4此时目标平台支持压缩指令,依据RVC压缩指令集的编码格式,当sw指令的rs2操作数为s0或s1,且lw指令的rd操作数为s0或s1寄存器时,sw指令和lw指令可以汇编生成16位压缩指令,因此在支持RVC压缩指令的32位架构中一个分配成s0或s1的callee-saved寄存器也能节省4个字节的代码空间。因此,若目标平台支持压缩指令,采用公式(4)计算在支持RVC压缩指令的RISC-V指令集架构中当前函数中跨函数调用的虚拟寄存器优先分配给caller-saved寄存器后能缩减代码空间的收益caller_benefit。

caller_benifit=callee_cost(r)-caller_cost(r),

r∈优先分配s0或s1后待分配寄存器的虚拟寄存器集合,公式(4)

7.5判断当前函数中跨函数调用的虚拟寄存器在优先分配s0或s1寄存器后是否应该再分配给caller-saved寄存器,方法是:如果caller_benifit≥零,表明所有跨函数调用的虚拟寄存器优先分配到callee-saved寄存器的代价大于等于分配到caller saved寄存器的代价,会使当前函数对应的二进制代码字节数减小,将当前虚拟寄存器优先指派给caller-saved寄存器;如果caller_benifit<零,则表明所有跨函数调用的虚拟寄存器优先分配到callee-saved寄存器的代价小于分配到caller-saved寄存器的代价,跨函数调用的虚拟寄存器不适合分配到caller-saved寄存器中,将当前虚拟寄存器优先指派给callee-saved寄存器(编译器的默认操作)。

第八步,令i=i+1,转第六步;

第九步,结束,输出对当前程序中所有函数中的跨函数调用活跃虚拟寄存器的寄存器分配结果,即每个跨函数调用活跃虚拟寄存器应优先分配给caller-saved寄存器还是优先分配给callee-saved寄存器的结果。

采用本发明可以达到以下技术效果:

本发明结合RISC-V指令集的特点(涉及到sw指令、lw指令、mv指令等,以及RISC-V压缩指令集)和编译器针对RISC-V指令集架构的寄存器分配方案的不足,基于活跃变量寄存器分配代价模型进行寄存器分配,可减少编译生成的指令条数,提高生成的二进制代码密度。

附图说明

图1是本发明总体流程图。

图2是使用本发明的llvm编译器和未使用本发明的llvm编译器编译replaypc-0.4.0测试套件中的31个测试程序所生成二进制代码密度的提升比例柱状图。

具体实施方式

如图1所示,本发明包括以下步骤:

第一步,构建活跃变量寄存器分配代价模型:

根据跨区域活跃变量不同寄存器的分配方式,构建以函数为单位的二进制代码密度代价公式即活跃变量寄存器分配代价模型,如下:

callee_cost(r)=callee_save_cost+callee_restore_cost+callee_move_costr∈所有待分配硬件寄存器的虚拟寄存器集合,公式(1);

caller_cost(r)=(caller_save_cost+caller_restore_cost)×n(r)+caller_move_cost r∈所有待分配硬件寄存器的虚拟寄存器集合,公式(2);

公式中caller_save_cost表示当把跨区域活跃变量对应的虚拟寄存器分配到caller类寄存器时在跨函数调用前后或者函数开始和结束处保存寄存器的代价;callee_save_cost表示当把跨区域活跃变量对应的虚拟寄存器分配到callee类寄存器时在跨函数调用前后或者函数开始和结束处保存寄存器的代价;caller_restore_cost表示当把跨区域活跃变量对应的虚拟寄存器分配到caller类寄存器时在跨函数调用前后或者函数开始和结束处恢复寄存器的代价;callee_restore_cost表示当把跨区域活跃变量对应的虚拟寄存器分配到callee类寄存器时在跨函数调用前后或者函数开始和结束处恢复寄存器的代价。caller_move_cost表示在保存caller-saved寄存器之前,将已分配寄存器的变量的值放到caller-saved寄存器的代价;callee_move_cost表示在保存callee-saved寄存器之前,将已分配寄存器的变量的值放到callee-saved寄存器的代价。参数r为所有待分配硬件寄存器的虚拟寄存器中任意一个。n(r)是r跨函数调用活跃的次数。

公式(1)中callee_cost(r)表示分配callee-saved寄存器后编译器需要额外保存和恢复的代价。公式(2)中caller_cost(r)表示分配caller-saved寄存器后编译器需要额外保存和恢复的代价。在开启编译器的-Os优化的情况下caller_move_cost为0。

第二步,判定被编译程序的函数列表中有没有未进行寄存器分配的函数,若有,令函数列表中第一个未进行寄存器分配的函数为当前被分析函数,转第三步;若没有,转第九步;

第三步,分析当前被分析函数的每一条指令,建立第一指令链表和第二指令链表。将每条指令中所含虚拟寄存器的定值信息和活跃信息存储到第一指令链表中。第一指令链表中共有K个元素,K为当前被分析函数的指令中所含的虚拟寄存器个数;第k个元素包括当前被分析函数中第k个虚拟寄存器R

第四步,将第一指令链表和第二指令链表进行对比,得到当前被分析函数中跨函数调用活跃的虚拟寄存器的个数N和这N个虚拟寄存器跨函数调用活跃的次数,构造跨函数调用活跃的虚拟寄存器列表,方法是:

4.1分析第一指令链表中的虚拟寄存器中哪些是跨函数调用活跃的虚拟寄存器。若第一指令链表中出现的虚拟寄存器同时出现在第二链表中,则该虚拟寄存器为跨函数调用活跃的虚拟寄存器。记跨函数调用活跃的虚拟寄存器的个数为N。

4.2在第二指令链表里寻找4.1获得的N个跨函数调用活跃的虚拟寄存器,这N个跨函数调用活跃的虚拟寄存器分别在第二指令链表中出现的次数即为当前被分析函数中这N个虚拟寄存器跨函数调用活跃的次数,令这N个跨函数调用活跃的虚拟寄存器中的第i个跨函数调用活跃的虚拟寄存器为R

4.3构造跨函数调用活跃的虚拟寄存器列表,跨函数调用活跃的虚拟寄存器列表包含N个表项,每个表项包含2个域,第i个表项的第一个域为虚拟寄存器名称R

第五步,令循环控制变量i=1;

第六步,若i≤N,将跨函数调用活跃的虚拟寄存器列表中第i个未分析的虚拟寄存器作为当前分析虚拟寄存器r,即令r=R

第七步,基于活跃变量寄存器分配代价模型计算当前分析虚拟寄存器指派给caller-saved寄存器的收益caller_benefit,并根据caller_benefit将当前被分析函数中跨函数调用的虚拟寄存器分配给caller-saved寄存器或callee-saved寄存器,方法是:

7.1若目标平台不支持压缩指令,转7.2;若目标平台支持压缩指令,转7.4;

7.2采用公式(3)计算当前被分析函数中跨函数调用的虚拟寄存器优先分配给caller-saved寄存器后能缩减代码空间的收益caller_benefit。

caller_benifit=callee_cost(r)-caller_cost(r)

r∈所有待分配寄存器组成的虚拟寄存器集合,公式(3)

7.3通过caller_benefit判断当前被分析函数中跨函数调用的虚拟寄存器是否应该优先分配给caller-saved寄存器,方法是:若caller_benefit≥零,表示优先分配给callee-saved寄存器的代价大于优先分配给caller-saved寄存器的代价,将当前虚拟寄存器优先指派给caller-saved寄存器;若caller_benefit<0,表示优先分配给callee-saved寄存器的代价小于优先分配给caller-saved寄存器的代价,将当前虚拟寄存器优先指派给callee-saved寄存器(编译器的默认操作)。转第八步;

7.4此时目标平台支持压缩指令,依据RVC压缩指令集的编码格式,当sw指令的rs2操作数为s0或s1,且lw指令的rd操作数为s0或s1寄存器时,sw指令和lw指令可以汇编生成16位压缩指令,因此在支持RVC压缩指令的32位架构中一个分配成s0或s1的callee-saved寄存器也能节省4个字节的代码空间。因此,若目标平台支持压缩指令,采用公式(4)计算在支持RVC压缩指令的RISC-V指令集架构中当前函数中跨函数调用的虚拟寄存器优先分配给caller-saved寄存器后能缩减代码空间的收益caller_benefit。

caller_benifit=callee_cost(r)-caller_cost(r),

r∈优先分配s0或s1后待分配寄存器的虚拟寄存器集合,公式(4)

7.5判断当前函数中跨函数调用的虚拟寄存器在优先分配s0或s1寄存器后是否应该再分配给caller-saved寄存器,方法是:如果caller_benifit≥零,表明所有跨函数调用的虚拟寄存器优先分配到callee-saved寄存器的代价大于等于分配到caller saved寄存器的代价,会使当前函数对应的二进制代码字节数减小,将当前虚拟寄存器优先指派给caller-saved寄存器;如果caller_benifit<零,则表明所有跨函数调用的虚拟寄存器优先分配到callee-saved寄存器的代价小于分配到caller-saved寄存器的代价,跨函数调用的虚拟寄存器不适合分配到caller-saved寄存器中,将当前虚拟寄存器优先指派给callee-saved寄存器(编译器的默认操作)。

第八步,令i=i+1,转第六步;

第九步,结束,输出对当前程序中所有函数中的跨函数调用活跃虚拟寄存器的寄存器分配结果,即每个跨函数调用活跃虚拟寄存器应优先分配给caller-saved寄存器还是优先分配给callee-saved寄存器的结果。

为了验证本发明的效果,将本发明应用于llvm-10.0编译器,生成了基于本发明的RISC-V指令集架构的llvm编译器。采用该编译器,对标准的benchmark工具CSiBE(GCCCode-Size Benchmark Environment)中的replaypc-0.4.0测试套件中的31个测试程序进行实验。在开启“-Os”优化选项的情况下,对比未采用本发明的llvm-10.0编译器,对生成的二进制代码密度进行对比,结果如图2所示,柱状图表示各个源文件编译生成二进制文件后代码密度的提升比例,可以看出,源程序dump-programset和find-GOPs提升比例较大(因为这2个程序跨函数调用活跃的变量比较多),分别达到了11.33%和12.17%,而其他源程序对应的提升也达到了0.1-10%。因此本发明对于RISC-V二进制代码的代码密度提高具有明显效果。

相关技术
  • 一种提高RISC-V二进制代码密度的寄存器分配方法
  • 动态二进制翻译系统中超级块的寄存器分配方法
技术分类

06120114714501