(1)要理解链接器松弛优化,需要先理解:RISC-V架构的寻址方式、编译和链接过程;
(2)链接器松弛优化在《RISC-V 体系结构编程与实践》6.4节有详细介绍;
(1)作用:通常RISC架构处理器访问符号地址需要两条指令,一条指令处理地址高位,一条指令处理地址地位,但是在链接阶段,通过链接松弛优化,使用一条指令实现对符号地址的访问;
(2)链接松弛优化主要涉及两方面:函数跳转优化、符号地址访问优化;
(1)当程序需要跳转时,符号地址与当前PC值相差有多有少,可能几百字节,也可能几百兆,这里就涉及到长跳转和短跳转;
(2)RISC-V有短跳转指令,比如jal、jalr、beq等等,但是这些指令都是短跳转,指令跳转到当前PC值前后几KB或者1MB的范围,当要跳转的地址与当前PC值相差几百MB的时候,这些短跳转指令就没法实现跳转;
(3)RISC-V也可以实现长跳转,使用auipc+jalr能够跳转到当前PC值前后2GB范围的范围,能实现所有地址的跳转;
(4)短跳转和长跳转的区别:长跳转可以实现所有地址的跳转,但是需要两条指令才能实现;短跳转只能在当前PC值地址前后小范围跳转,但是只需要一条指令就能跳转;
总结:链接器松弛优化就是把能够使用短跳转的地方都使用短跳转,这样可以减少汇编指令;
void foo(void)
{
}
int main(void)
{
foo();
}
# -mno-relax是关闭链接器松弛优化,默认是打开的
riscv64-linux-gnu-gcc -mno-relax main.c -o test.elf
riscv64-linux-gnu-objdump -D test.elf > test.dis
······
00000000000005fc :
5fc: 1141 addi sp,sp,-16
5fe: e422 sd s0,8(sp)
600: 0800 addi s0,sp,16
602: 0001 nop
604: 6422 ld s0,8(sp)
606: 0141 addi sp,sp,16
608: 8082 ret
000000000000060a :
60a: 1141 addi sp,sp,-16
60c: e406 sd ra,8(sp)
60e: e022 sd s0,0(sp)
610: 0800 addi s0,sp,16
612: 00000097 auipc ra,0x0
616: fea080e7 jalr -22(ra) # 5fc
······
从反汇编文件可知,main函数跳转到foo函数,使用两条指令实现:auipc ra,0x0;jalr -22(ra) # 5fc ;
# 打开链接器松弛优化
riscv64-linux-gnu-gcc main.c -o test.elf
riscv64-linux-gnu-objdump -D test.elf > test.dis
······
00000000000005ea :
5ea: 1141 addi sp,sp,-16
5ec: e422 sd s0,8(sp)
5ee: 0800 addi s0,sp,16
5f0: 0001 nop
5f2: 6422 ld s0,8(sp)
5f4: 0141 addi sp,sp,16
5f6: 8082 ret
00000000000005f8 :
5f8: 1141 addi sp,sp,-16
5fa: e406 sd ra,8(sp)
5fc: e022 sd s0,0(sp)
5fe: 0800 addi s0,sp,16
600: febff0ef jal ra,5ea
604: 4781 li a5,0
606: 853e mv a0,a5
608: 60a2 ld ra,8(sp)
60a: 6402 ld s0,0(sp)
60c: 0141 addi sp,sp,16
60e: 8082 ret
······
只使用一条短跳转指令就跳转到foo函数处执行:jal ra,5ea ;因为foo所在地址在jal短跳转指令的寻址范围内,这样就减少一条汇编指令;
int a =5;
int foo(void)
{
return a;
}
int main(void)
{
foo();
}
# 打开链接器松弛优化
riscv64-linux-gnu-gcc main.c -o test.elf
riscv64-linux-gnu-objdump -D test.elf > test.dis
...
00000000000005ea :
5ea: 1141 addi sp,sp,-16
5ec: e422 sd s0,8(sp)
5ee: 0800 addi s0,sp,16
# 实现对全局变量a访问
5f0: 00002797 auipc a5,0x2
5f4: a1878793 addi a5,a5,-1512 # 2008
5f8: 439c lw a5,0(a5)
5fa: 853e mv a0,a5
5fc: 6422 ld s0,8(sp)
5fe: 0141 addi sp,sp,16
600: 8082 ret
0000000000000602 :
602: 1141 addi sp,sp,-16
604: e406 sd ra,8(sp)
606: e022 sd s0,0(sp)
608: 0800 addi s0,sp,16
60a: fe1ff0ef jal ra,5ea
60e: 4781 li a5,0
610: 853e mv a0,a5
612: 60a2 ld ra,8(sp)
614: 6402 ld s0,0(sp)
616: 0141 addi sp,sp,16
618: 8082 ret
...
0000000000002008 :
2008: 0005 c.nop 1
...
# 新增.sdata段和设置_global_pointer$符号
.sdata : {
_global_pointer$ = . + 0x800;
*(.sdata)
*(.sbss)
}
···
# 新增以下代码
.option push
.option norelax
la gp,_global_pointer$
.option pop
······
addi a5,gp,xxx
最后对全局变量的寻址会被替换成一条指令,相对于gp寄存器的值为基地址进行寻址;
(1)在链接时把全局变量等符号放到.sdata段,然后把.sdata段的中间地址赋值给gp寄存器;
(2)访问符号时,可以基于gp寄存器的值为基地址进行寻址,进行短跳转;
(3)这里addi的寻址范围只能在gp寄存器的值为基地址的前后2KB;