4.3. 转移指令

转移指令用于实现有条件或无条件分支、函数调用、函数返回以及循环控制等操作。LoongArch支持的转移指令如表3-9所示。

LoongArch支持的转移指令

表3-9中,转移指令助记符有两类命名方式:相对跳转的地址计算依赖PC值,称为“分支”(Branch),助记符通常以b开头;绝对跳转的地址计算不依赖PC值,称为“跳转”(Jump),助记符通常以j开头。

PC是程序计数器,用来控制程序中指令的执行顺序。程序正常运行时,PC指向CPU将要执行的下一条指令。CPU取出该指令后,会自动更新PC,使其指向再下一条指令,从而保证程序顺序执行。当程序需要改变执行顺序,也就是发生跳转时,就必须提前修改PC,使它指向目标指令地址。

表3-9中的转移指令可从有条件分支、无条件分支和跳转、跳转范围几个角度理解。

4.3.1. 有条件的分支指令

表3-9中的beqbnebltbgebltubgeubeqzbnez都是有条件相对跳转指令。条件成立时,程序跳转到目标地址;条件不成立时,继续顺序执行。目标地址的计算方式为:先将指令码中的立即数(offs16或offs21)逻辑左移2位并符号扩展,再把得到的偏移值加到该分支指令的PC上。各条件分别表示等于(eq)、不等于(ne)、小于(lt/ltu)、大于或等于(ge/geu)、等于零(eqz)和不等于零(nez);其中ltu、geu表示按无符号操作数进行比较。

有条件分支指令常用于函数内部控制程序流程,作用类似C语言中的if-elsedo-whilefor等语句。

4.3.2. 无条件分支指令和跳转指令

表3-9中的bbl都是无条件分支指令,用于直接跳转到目标地址。目标地址的计算方式为:将指令码中的26位立即数offs26逻辑左移2位并符号扩展,再把得到的偏移值加到该分支指令的PC上。bbl的区别在于,bl是带链接的无条件分支指令(Branch and Link),跳转前会把该指令PC值加4的结果写入寄存器r1。

表3-9中的jirl是无条件跳转指令,同样用于直接跳转到目标地址。它的目标地址计算方式为:先将指令码中的16位立即数offs16逻辑左移2位并符号扩展,再与寄存器rj中的值相加。指令名中的ir分别表示立即数(Immediate)和寄存器(Register),说明跳转目标基于“寄存器+立即数”,而不是基于PC。l表示带链接跳转(Jump and Link),即跳转时还会把该指令PC值加4的结果写入寄存器rd。

在LoongArch ABI中,寄存器r1被定义为返回地址寄存器ra。关于LoongArch ABI,后续章节会详细说明。

分支指令bl通常用于函数调用,跳转指令jirl通常用于函数返回。

4.3.3. 跳转范围

表3-9中,分支指令beqbnebltbltubgebgeu使用offs16作为偏移量。地址计算时还会左移2位,因此实际偏移宽度为18位,相对跳转范围为[PC-128K, PC+128K]。无条件分支指令bbl使用offs26作为地址偏移量,左移2位后实际偏移宽度为28位,相对跳转范围为[PC-128M, PC+128M]。这一范围与ARM架构的相对跳转范围相同,比部分早期架构更大。例如,MIPS架构的无条件相对跳转范围为[-128K,128K]。

更大的跳转范围可以减少加载地址所需的额外指令。假设程序需要无条件跳转到[PC-128M, PC+128M]范围内的某个地址,例如偏移量为0x40,则只需一条分支指令b即可完成:

b 	0x10 	//	跳转到目标地址PC+0x40(0x10<<2)

如果目标地址偏移超出该范围,一条分支指令就无法完成跳转。此时需要先把目标地址(PC+偏移量)加载到寄存器,再用绝对跳转指令jirl跳转:

li 	$r7, (PC+offset)	#	加载目标地址到寄存器r7
jirl 	$r0, $r7, 0 	#	跳转到目标地址(r7+0)

根据常量(PC+offset)的大小不同,宏指令li最终会被编译器扩展为1~4条汇编指令。