2.6 Logical Operations
简单
2.7 Instructions for Making Decisions
RISC-V汇编语言包含两个决策指令,类似于带有go-to的if语句
The first instruction is:
beq rs1, rs2, L1
此指令表示如果寄存器rs1中的值等于寄存器rs2中的值,则转到标记为L1的语句。
The second instruction is :
bne rs1, rs2, L1
如果寄存器rs1中的值不等于寄存器rs2中的值,则转到标记为L1的语句。
ex:
在下面的代码段中,f、g、h、i和j是变量。如果五个变量f到j对应于五个寄存器x19到x23,那么这个C If语句的编译RISC-V代码是什么?
if (i == j) f = g + h; else f = g −h;
bne x22, x23, Else // go to Else if i ≠ j
add x19, x20, x21 // f = g + h (skipped if i ≠ j)
Else:sub x19, x20, x21 // f = g -h (skipped if i = j)
Exit:
在RISC-V中表示无条件分支的一种方法是:要使用条件始终为true的条件分支,请执行以下操作:
beq x0,x0,Exit//如果0==0,转到Exit
Loops:
Ex:
Here is a traditional loop in C:
while (save[i] == k)
i += 1;
Assume that:
1、i correspond to registers x22
2、k correspond to registers x24
3、基址寄存器是 x25.
Answer:
Loop: slli x10, x22, 3 // Temp reg x10 = i * 8
解释:第一步是将save[i]加载到临时寄存器中。在将save[i]加载到临时寄存器之前,我们需要知道它的地址。Before we can add i to the base of array save to form the address,由于字节寻址问题,必须将索引i乘以8。幸运的是,我们可以使用左移,因为左3位(乘以8)。 We need to add the label Loop to it so that we can branch back to that instruction at the end of the loop。
add x10, x10, x25 // x10 = address of save[i]
解释:为了得到save[i]的地址,我们需要加上x10和基址寄存器x25里的基本地址
ld x9, 0(x10) // Temp reg x9 = save[i]
解释:现在我们可以用这个地址把save[i]加载到一个临时的reg x9中
bne x9, x24, Exit // go to Exit if save[i] ≠ k
解释:The next instruction performs the loop test, exiting if save[i] ≠
k:
addi x22, x22, 1 // i = i + 1
解释:The following instruction adds 1 to i:
beq x0, x0, Loop // go to Loop
Exit:
补充:
basic block:一种指令序列,没有分支(可能在末尾除外),没有分支目标或分支标签(可能在开头除外).编译最早动作之一是将程序分解为基本块。
blt:The branch if less than(blt) instruction compares the values in registers rs1 and rs2 and takes the branch if the value in rs1 is smaller, when they are treated as two’s complement numbers.
bge:Branch if greater or equal(bge)也就是说,如果rs1中的值至少是rs2中的值。
bltu:Branch if小于,unsigned(bltu)在将值视为无符号数时,如果rs1中的值小于rs2中的值,则采用分支。
bgeu:同理
边界检查快捷方式(Bounds Check Shortcut):
方式:Treating signed numbers as if they were unsigned
原理:The key is that negative integers in two’s complement notation look like large numbers in unsigned notation,负数的补码看起来是更大的无符号数
使用此快捷方式可减少索引越界检查:分支到索引自动边界
如果x20≥x11或x20为负。
检查代码只使用大于或等于的无符号来执行这两项检查:
bgeu x20,x11,索引自动边界
//如果x20>=x11或x20<0,转到indexoutbounds
Case/Switch Statement:
大多数编程语言都有一个case或switch语句,允许程序员根据一个值选择多个选项中的一个。
实现switch的最简单方法是通过一系列条件测试,将switch语句转换为if-then-else语句链。
有时,替代方案:有效地编码为替代指令序列的地址表,称为a branch地址表或分支表,并且程序只需要索引到表中,然后分支到适当的序列。因此,分支表只是包含地址的双字数组,这些地址与代码中的标签相对应。The program loads the appropriate entry from the branch table into a register. It then needs to branch using the address in the register. To support such situations, computers like RISC-V include an indirect jump instruction, which performs an unconditional branch to the address specified in a register. ——In RISC-V, the jump-and-link register instruction (jalr) serves this purpose.
Hardware/Software Interface:
尽管在C和Java等编程语言中有许多用于决策和循环的语句,但在指令集级别实现它们的基本语句是条件分支
2.8计算机硬件支持程序
函数(or 存储子程序:它根据所提供的参数执行特定的任务。)是程序员用来构造程序的一种工具,既使程序更容易理解,又允许代码被重用。函数允许程序员一次只专注于任务的一部分;
参数充当过程与程序和数据的其余部分之间的接口,因为它们可以传递值并返回结果。
You can think of a procedure like a spy who leaves with a secret plan, acquires resources, performs the task, covers his or her tracks, and then returns to the point of origin with the desired result,一旦任务完成,其他任何事情都不应该被打扰。此外,间谍只在“需要知道”的基础上进行操作,因此间谍不能对间谍主做出假设。Similarly, in the execution of a procedure, the program must follow these six steps:
1. Put parameters in a place where the procedure can access them.
2. Transfer control to the procedure.
3. Acquire the storage resources needed for the procedure.
4. Perform the desired task.
5. Put the result value in a place where the calling program can access it.
6. Return control to the point of origin, since a procedure can be called from several points in a program
如上所述,寄存器是计算机中保存数据最快的地方,因此我们希望尽可能多地使用它们。RISC-V软件在分配其32个寄存器时遵循以下程序调用约定:
x10–x17:八个参数寄存器,用于传递参数或返回值
x1: one return address register to return to the point of origin.
除了分配这些寄存器之外,RISC-V汇编语言包含一条只用于过程的指令:它分支到一个地址,同时将下条指令的地址保存到目标寄存器rd。
The jump-and-link instruction (jal) is written:
jal x1, ProcedureAddress// jump to ProcedureAddress and write return address to x1
一种分支到一个地址并同时将下条指令的地址保存在寄存器中的指令(in RISC-V,存在reg x1中,called the return address)
存储在寄存器x1中的“link”称为返回地址。返回地址是必需的,because the same procedure could be called from several parts of the program
To support the return from a procedure, computers like RISC-V use an indirect jump, like the jump-and-link instruction (jalr) introduced above to help with case statements:
jalr x0, 0(x1)
The jump-and-link register instruction branches to the address stored in register x1—which is just what we want. Thus, the calling program, or caller, puts the parameter values in x10–x17 and uses
jal x1, X to branch to procedure X (sometimes named the callee). The callee then performs the calculations, places the results in the same parameter registers, and returns control to the caller using:jalr x0, 0(x1)
在存储程序思想中隐含的是需要有一个寄存器来保存当前正在执行的指令的地址。由于历史原因,这个寄存器几乎总是被称为程序计数器,在RISC-V体系结构中缩写为PC,尽管更合理的名称应该是指令地址寄存器。jal指令实际上将PC+4保存在其指定寄存器(通常是x1)中, to link to the byte address of the following instruction to set up the procedure return.跳转和链接指令也可用于在过程中执行无条件分支,方法是使用x0作为目标寄存器。由于x0是硬连接到零的,因此其效果是放弃返回地址:jal x0, Label // unconditionally branch to Label
Using More Registers
假设编译器对一个过程需要的寄存器多于八个参数寄存器。
由于我们必须在任务完成后覆盖跟踪,调用方所需的任何寄存器都必须还原为调用过程之前包含的值。
The ideal data structure for spilling registers is a stack—a last-infirst-out queue.
堆栈需要一个指向堆栈中最近分配的地址的指针,以显示下一个过程应将要溢出的寄存器放置在何处
在RISC-V中,堆栈指针是寄存器x2,也称为sp。
堆栈非常流行,因此它们有自己的术语用于在堆栈之间传输数据:将数据放置到堆栈上称为推送push,从堆栈中移除数据称为pop
根据历史先例,堆栈从较高的地址“增长”到较低的地址。此约定意味着:push values onto the stack by subtracting from the stack pointer. pop values from the stack by adding to the stack pointer (shrinks the stack)
ex:
long long int leaf_example (long long int g, long long int
h, long long int i, long long int j)
{
long long int f;
f = (g + h) -(i + j);
return f;
}
假设:参数变量g、h、i和j对应于参数寄存器x10、x11、x12和x13,f对应于x20。编译的程序以过程的标签开始:leaf_示例:
下一步是保存过程使用的寄存器。过程体中的C赋值语句与第67页的示例相同,该示例使用两个临时寄存器(x5和x6)。因此,我们需要保存三个寄存器:x5、x6和x20
我们通过在堆栈上为三个双字(24字节)创建空间,将旧值“推”到堆栈上,然后存储它们:
addi sp,sp,-24//调整堆栈以留出3个项目的空间
sdx5,16(sp)//保存寄存器x5以备以后使用
sdx6,8(sp)//保存寄存器x6以备以后使用
sdx20,0(sp)//保存寄存器x20以备以后使用
接下来的三个语句对应于
程序,遵循第67页的示例:
add x5,x10,x11//register x5包含g+h
add x6,x12,x13//寄存器x6包含i+j
sub x20,x5,x6//f=x5−x6,即(g+h)—(i+j)
为了返回f的值,我们将其复制到参数寄存器中:
addi x10,x20,0//返回f(x10=x20+0)
Before returning, we restore the three old values of the registers
我们通过从堆栈中“弹出”它们来保存:
ld x20,0(sp)//还原调用者的寄存器x20
ld x6,8(sp)//还原调用者的寄存器x6
ld x5,16(sp)//还原调用者的寄存器x5
addi sp,sp,24//调整堆栈以删除3个项
该过程以使用返回地址的分支寄存器结束:
jalr x0,0(x1)//分支回调用例程