C语言等高级语言编写的程序必须经过编译器转换为汇编语言,再由汇编器转换为指令码才能在CPU上执行。本节简要介绍高级语言转换为指令码涉及的一些问题,为方便起见,选择C语言和LoongArch汇编码进行介绍。
2.6.1 过程调用
过程调用是高级语言程序中的一个关键特性,它可以让特定程序段的内容与其他程序和数据分离。过程接受参数输入,并通过参数返回执行结果。C语言中过程和函数的概念相同,本节后面也不进行区分。过程调用中,调用者和被调用者必须遵循同样的接口约定,包括寄存器使用、栈的使用和参数传递的约定等。这部分涉及内容较多,将在第4章中进行详细的介绍。本节中,主要介绍过程调用的流程和其中与指令集相关的内容。
在LoongArch指令集中,负责函数调用的指令是BL,这是一条相对转移指令。该指令在跳转的同时还将其下一条指令的地址放入1号通用寄存器(记为$ra)中,作为函数返回地址。负责函数返回的指令是JR2,属于间接跳转指令,该指令的操作数为寄存器,因此LoongArch汇编中最常见的函数返回指令是jr $ra。
除了调用和返回的指令外,函数调用和执行过程中还需要执行一系列操作:
- 调用者(S)将参数(实参)放入寄存器或栈中;
- 使用BL指令调用被调用者(R);
- R在栈中分配自己所需要的局部变量空间;
- 执行R过程;
- R释放局部变量空间(将栈指针还原);
- R使用JR指令返回调用者S。
默认情况下,通用寄存器$r4~$r11(记为$a0~$a7)作为参数输入,其中$r4和$r5同时也作为返回值,通用寄存器$r12~$r20(记为$t0~$t8)作为子程序的暂存器无须存储和恢复。LoongArch中没有专门的栈结构和栈指针,通用寄存器$r3(记为$sp)通常作为栈指针寄存器,指向栈顶。
一个简单的C语言过程调用程序及其LoongArch汇编码如表2.11所示。
表 2.11: 过程调用及其LoongArch机器表示
C代码 |
LoongArch汇编 |
int add(int a,int b) |
add: |
add程序是被调用的子程序,由于程序功能很简单,因此无须使用栈来存储任何信息,其输入参数存放在$a0、$a1两个寄存器中,计算的结果存放在$a0寄存器中。
ref程序是add程序的调用者,通过BL指令进行调用,BL指令会修改$ra寄存器的值,因此在ref中需要将$ra寄存器的值保存到栈中,栈顶指针和RA值存放的位置遵循LoongArch函数调用规范,这部分内容将在4.1节中进行介绍。add程序的返回值放在$a0寄存器中,这同时也是ref程序的返回值,因此无须进行更多搬运。
2.6.2 流程控制语句
C语言中的控制流语句共有9种,可分为三类:辅助控制语句、选择语句、循环语句,如表2.12所示。
表 2.12: C语言控制流语句
控制流语句 |
选择语句 |
if ~ else |
switch ~ case |
||
循环语句 |
for |
|
while |
||
do ~ while |
||
辅助控制语句 |
break |
|
continue |
||
goto |
||
return |
(1)辅助控制语句
goto语句无条件地跳转到程序中某标号处,其作用与无条件相对跳转指令相同,在LoongArch指令集中表示为B指令跳转到一个标号。break、continue语句的作用与goto类似,只是跳转的标号位置不同。return语句将过程中的变量作为返回值并直接返回,在编译器中对应于返回值写入和返回操作。
(2)选择语句
if~else语句及其对应的LoongArch汇编码如表2.13所示。
表 2.13: if~else语句及其LoongArch汇编表示
C代码 |
LoongArch汇编 |
if (cond_exp) |
move $t0, cond_exp |
这里的if ~ else实现采用了BEQZ指令,当$t0寄存器的值等于0时进行跳转,跳转到标号.L1执行“else”分支中的操作,当$t0寄存器的值不等于0时,则顺序执行“then”分支中的操作并在完成后无条件跳转到标号.L2处绕开“else”分支。
switch ~ case语句的结构更为复杂,由于可能的分支数较多,通常会被映射为跳转表的形式,如表2.14所示。如果在编译选项中加入-fno-jump-tables的选项,那么switch ~ case语句还可以被映射为跳转级联的形式,如表2.15所示。表中“alsl.d rd, rj, rk, sa”所进行的操作是:GR[rd] = (GR[rj] << sa) + GR[rk]。即将rj号通用寄存器中的值先左移sa位再与rk号通用寄存器中的值相加,结果写入rd号通用寄存器中。
表 2.14: switch~case语句及其跳转表形式的LoongArch机器表示
C代码 |
LoongArch汇编 |
int st(int a, int b, int c) |
st: |
表 2.15: switch-case语句及其跳转级联形式的LoongArch机器表示
C代码 |
LoongArch汇编 |
int st(int a, int b, int c) |
st: |
在这个例子中,$t0寄存器存放各case分支的值并依次与第一个参数a(存放在$a0寄存器中)进行比较,根据比较的结果分别跳转到指定标号。读者可自行分析各case分支的执行流。通过比较表2.14和2.15中的汇编代码可以看到,在case分支较多时,采用跳转表实现有助于减少级联的转移指令。
2.6.3 循环语句
循环语句均可映射为条件跳转指令,与选择语句的区别就在于跳转的目标标号在程序段已执行过的位置(backward)。三种循环语句的C语言及其对应的LoongArch汇编码如表2.16所示。
表 2.16: 循环语句及其LoongArch机器表示
C代码 |
LoongArch汇编 |
int test_for(int a) { |
test_for: |
2.7 本章小结
本章介绍了指令系统在整个计算机系统中位于软硬件界面的位置,讨论了指令系统设计的原则和影响因素,并从指令内容、存储管理、运行级别三个角度介绍指令系统的发展历程。
本章首先介绍了指令集的关键要素——地址空间定义、指令操作数、指令操作码,随后对几种不同的RISC指令集进行了比较,最后以LoongArch指令集为例给出了C语言和指令汇编码之间的对应关系。