LA32R中的用户态整型指令
分支跳转类指令
LA32R中的分支跳转类指令用于控制指令执行流流向(Instruction Flow Control),其中需要进行条件判断决定跳转(Taken)还是不跳转(Not Taken)的我们称为条件分支指令,余下的则是无条件跳转指令。
条件分支指令
条件分支指令速查表
LA32R中在指令集手册中定义的真实条件分支指令有如下6条:
| 指令 | 汇编表达 | 功能简释 |
|---|---|---|
| beq (branch if equal) | beq $rx, $ry, off16 | if (GR[x] == GR[y]) PC += sext32(off16 << 2) |
| bne (branch if not equal) | bne $rx, $ry, off16 | if (GR[x] != GR[y]) PC += sext32(off16 << 2) |
| blt (branch if less than signed) | blt $rx, $ry, off16 | if (GR[x] <s GR[y]) PC += sext32(off16 << 2) |
| bge (branch if greater than or equal signed) | bge $rx, $ry, off16 | if (GR[x] >=s GR[y]) PC += sext32(off16 << 2) |
| bltu (branch if less than unsigned) | bltu $rx, $ry, off16 | if (GR[x] <u GR[y]) PC += sext32(off16 << 2) |
| bgeu (branch if greater than or equal unsigned) | bgeu $rx, $ry, off16 | if (GR[x] >=u GR[y]) PC += sext32(off16 << 2) |
有爱好者问,不是说LA的分支指令支持所有的整数比较类型,那么为什么“小于等于”和“大于”没有呢。其实,A小于等于B等价于B大于等于A,A大于B等价于B小于A。调整被比较对象在分支指令中rd和rj域的位置,就能实现所有的整数比较类型。即便如此,LA32R的汇编器还是非常贴心的定义了下面一系列伪指令,让汇编代码更便于阅读。
| 指令 | 汇编表达 | 功能简释 |
|---|---|---|
| bgt (branch if greater than signed) | bgt $rx, $ry, off16 | if (GR[x] >s GR[y]) PC += sext32(off16 << 2) |
| bgtu (branch if greater than unsigned) | bgtu $rx, $ry, off16 | if (GR[x] >u GR[y]) PC += sext32(off16 << 2) |
| ble (branch if less than or equal signed) | ble $rx, $ry, off16 | if (GR[x] <=s GR[y]) PC += sext32(off16 << 2) |
| bleu (branch if less than or equal unsigned) | bleu $rx, $ry, off16 | if (GR[x] <=u GR[y]) PC += sext32(off16 << 2) |
| bltz (branch if less than zero signed) | bltz $rx, off16 | if (GR[x] <s 0) PC += sext32(off16 << 2) |
| bgtz (branch if greater than zero signed) | bgtz $rx, off16 | if (GR[x] >s 0) PC += sext32(off16 << 2) |
| blez (branch if less than or equal to zero signed) | blez $rx, off16 | if (GR[x] <=s 0) PC += sext32(off16 << 2) |
| bgez (branch if greater than or equal to zero signed) | bgez $rx, off16 | if (GR[x] >=s 0) PC += sext32(off16 << 2) |
尽管有上述伪指令,细心的读者可能又发现问题了,为什么没有beqz(branch if equal zero)和bnez(branch if not equal zero)?因为LA64和LA32的指令集中已经定义了beqz和bnez指令,所以LA32R如果定义同名的伪指令会给软件的维护带来困扰。在LA32R汇编编程中,请直接用beq $r##, $zero和bne $r##, $zero来实现所需的功能。
用标签(Label)来指示跳转目标(Taken Target)
分支指令的定义中都有一个立即数偏移值off16,那么汇编编程时是不是要自己计算出跳转目标处与这条分支指令的相对偏移量呢?我们强烈不推荐这样做。且不说由于伪指令和宏的存在为计算相对偏移量增加难度,就算可以算得准,如果代码有所增减岂不是又要重新计算一遍。通常我们在汇编编程时都是采用beq $r##, $r##, label这种写法,即在这条分支的跳转目标处设置一个标签,然后在分支指令中直接写这个标签的名字就可以了。分支指令中的off16参数具体该填什么,就交给工具链软件了。具体的用法在下一小节的例子中会有看到。
常见C语言控制结构对应的汇编表达
先来看if-else结构
上面示例中的b指令在LA32R中并不是一条伪指令,它实际上就是一条无条件的PC相对跳转指令,完成类似于C语言中goto语句的效果。
再来看看如何用分支指令构成循环结构。
test_for:
move $t0, $zero
move $t1, $zero
.L2:
blt $t0, $a0, .L3
move $a0, $t1
jr $ra
.L3:
add.w $t1, $t1, $t0
addi.w $t0, $t0, 1
b .L2
test_while:
move $t0, $zero
move $t1, $zero
.L2:
blt $t0, $a0, .L3
move $a0, $t1
jr $ra
.L3:
add.w $t1, $t1, $t0
addi.w $t0, $t0, 1
b .L2
int test_dowhile(int a) {
int sum = 0;
int i = 0;
do {
sum += i;
i++;
} while (i < a);
return sum;
}
test_dowhile:
move $t0, $zero
move $t3, $zero
.L2:
add.w $t1, $t3, $t0
addi.w $t2, $t0, 1
move $t3, $t1
move $t0, $t2
blt $t2, $a0, .L2
move $a0, $t1
jr $ra
上面的汇编代码中的label沿用了编译器生成出的汇编代码中的风格,在人工开发汇编程序时,如果跳转距离不太远,还有一种常用的命名和标识形式。我们来看下面两段代码:
上面两段汇编代码实际上是等价的。我们看到第二段汇编中两个标号都是“1”,那么两条分支指令是如何区分它们的呢?其中beq $t0, $zero, 1f中的1f是这条指令向下(也可以说是向前,forward)遇到的第一个标记为1的标号,而blt $t1, $a0, 1b中的1b是这条指令向上(也可以说是向后,backward)遇到的第一个标记为1的标号。可以看到,这对于汇编编程开发提供了便利,毕竟你不用费劲心思保证一个文件内所有的标号的名字都不同。
整数比较置结果的指令
尽管分支指令可以完成两个整型数之间的所有比较操作,但是当涉及多个变量间更复杂的条件表达式时,编译器或汇编语言程序员可以利用整数比较置结果的指令与逻辑指令计算出这类条件表达式的值。这类指令共4条,列举如下:
| 指令 | 汇编表达 | 功能简释 |
|---|---|---|
| slt (set less than) | slt $rx, $ry, $rz | GR[x] = (GR[y] <s GR[z]) ? 1 : 0 |
| sltu (set less than unsigned) | sltu $rx, $ry, $rz | GR[x] = (GR[y] <u GR[z]) ? 1 : 0 |
| slti (set less than immediate) | slt $rx, $ry, si12 | GR[x] = (GR[y] <s sext32(si12)) ? 1 : 0 |
| sltui (set less than unsigned immediate) | slt $rx, $ry, si12 | GR[x] = (GR[y] <u sext32(si12)) ? 1 : 0 |
无条件跳转指令
无条件跳转指令速查
LA32R中在指令集手册中定义的真实无条件跳转指令有如下3条:
| 指令 | 汇编表达 | 功能简释 |
|---|---|---|
| b (branch) | b offs26 | PC += sext32(offs26 << 2) |
| bl (branch and link) | bl offs26 | GR[1] = PC + 4; PC += sext32(offs26 << 2) |
| jirl (jump indirect register link) | jirl $rx, $ry, offs16 | GR[x] = PC + 4; PC = GR[y] + sext32(offs16 << 2) |
上面的jirl指令的功能比较多,既有基于寄存器的间接跳转功能,又有保存返回地址的Link功能。考虑到很多应用场景中只需要基于寄存器的间接跳转的功能(例如函数返回时的跳转),为了增加代码的可阅读性,LA32R的汇编器支持了jr这条汇编伪指令。
| 指令 | 汇编表达 | 功能简释 |
|---|---|---|
| jr (jump register) | jr $rx | PC = GR[x] |
函数调用与函数返回
无条件跳转指令最典型的应用场景是函数调用和函数返回。在LA32R中,完成函数调用功能的是bl指令,完成函数返回功能的是jr $ra伪指令。整个函数调用、执行并返回的过程所要进行的操作步骤如下:
- 调用者将参数(实参)存入寄存器或栈中;
- 调用者使用
bl指令调用被调用者; - 被调用者在栈中为自己所需要的局部变量分配空间(栈指针调整下移);
- 被调用者自身执行;
- 被调用者释放自身局部变量在栈中的空间(栈指针上移复原);
- 被调用者执行
jr $ra返回调用者。
接下来通过一个具体例子来加深上述过程的认识。
add:
add.w $a0, $a0, $a1 # a+b
jr $ra # return
ref:
addi.w $sp, $sp, -16 # stack allocate
li.w $a1, 34 # t2=34
li.w $a0, 12 # t1=12
st.w $ra, $sp, 4 # save $ra
bl add # call add()
ld.w $ra, $sp, 4 # restore $ra
addi.w $sp, $sp, 16 # stack release
jr $ra # return
bl指令的寻址范围是该指令前后各227字节的空间,尽管说这尚不能覆盖整个32位地址空间,但是对于绝大多数32位应用程序的text段来说,这个寻址距离是足够的了。所以汇编编程中的函数调用用bl指令基本就够用了,不再需要使用Link至$ra寄存器的间接跳转指令jirl $ra, $r##, ####。