链接器松弛优化原理介绍

1、前言

(1)要理解链接器松弛优化,需要先理解:RISC-V架构的寻址方式、编译和链接过程;

(2)链接器松弛优化在《RISC-V 体系结构编程与实践》6.4节有详细介绍;

2、链接器松弛优化介绍

(1)作用:通常RISC架构处理器访问符号地址需要两条指令,一条指令处理地址高位,一条指令处理地址地位,但是在链接阶段,通过链接松弛优化,使用一条指令实现对符号地址的访问;

(2)链接松弛优化主要涉及两方面:函数跳转优化、符号地址访问优化;

3、汇编实现程序跳转的过程

(1)当程序需要跳转时,符号地址与当前PC值相差有多有少,可能几百字节,也可能几百兆,这里就涉及到长跳转和短跳转;

(2)RISC-V有短跳转指令,比如jal、jalr、beq等等,但是这些指令都是短跳转,指令跳转到当前PC值前后几KB或者1MB的范围,当要跳转的地址与当前PC值相差几百MB的时候,这些短跳转指令就没法实现跳转;

(3)RISC-V也可以实现长跳转,使用auipc+jalr能够跳转到当前PC值前后2GB范围的范围,能实现所有地址的跳转;

(4)短跳转和长跳转的区别:长跳转可以实现所有地址的跳转,但是需要两条指令才能实现;短跳转只能在当前PC值地址前后小范围跳转,但是只需要一条指令就能跳转;

总结:链接器松弛优化就是把能够使用短跳转的地方都使用短跳转,这样可以减少汇编指令;

3、函数跳转优化

3.1、main .c

void foo(void)
{
	
}

int main(void)
{
	foo();
}

3.2、不使用链接器松弛优化

3.2.1、编译指令

# -mno-relax是关闭链接器松弛优化,默认是打开的
riscv64-linux-gnu-gcc -mno-relax main.c -o test.elf
riscv64-linux-gnu-objdump -D test.elf > test.dis

3.2.2、反汇编文件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 ;

3.3、使用链接器松弛优化

3.3.1、编译指令

# 打开链接器松弛优化
riscv64-linux-gnu-gcc  main.c -o test.elf
riscv64-linux-gnu-objdump -D test.elf > test.dis

3.3.2、反汇编文件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短跳转指令的寻址范围内,这样就减少一条汇编指令;

4、符号地址访问优化

4.1、main.c

int a =5;

int foo(void)
{
	return a;
}

int main(void)
{
	foo();
}

4.2、不使用编译器松弛优化

4.2.1、编译指令

# 打开链接器松弛优化
riscv64-linux-gnu-gcc  main.c -o test.elf
riscv64-linux-gnu-objdump -D test.elf > test.dis

4.2.2、反汇编文件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 ...

4.2、使用编译器松弛优化

4.2.1、修改链接脚本

# 新增.sdata段和设置_global_pointer$符号
.sdata : {
		_global_pointer$ = . + 0x800;
		*(.sdata) 
		*(.sbss)
		}

4.2.2、在启动代码里初始化GP寄存器

···
# 新增以下代码
.option push
.option norelax
la gp,_global_pointer$
.option pop
······

4.3.3、反汇编代码

addi a5,gp,xxx

最后对全局变量的寻址会被替换成一条指令,相对于gp寄存器的值为基地址进行寻址;

4.3.4、原理分析

(1)在链接时把全局变量等符号放到.sdata段,然后把.sdata段的中间地址赋值给gp寄存器;
(2)访问符号时,可以基于gp寄存器的值为基地址进行寻址,进行短跳转;
(3)这里addi的寻址范围只能在gp寄存器的值为基地址的前后2KB;

你可能感兴趣的:(RISC-V架构,RISC)