决策与循环
默认情况下,CPU 按指令出现的顺序从上到下执行。仅凭这个顺序,它永远无法做出选择或重复工作。分支是程序离开这条直线路径的方式:它测试一个条件,当条件成立时,引导 CPU 从另一个位置继续,而不是下一行。每一个决策和每一个循环都来自这一个机制。
没有 if 或 for。你用标签(命名的位置)和分支(当条件成立时跳转到某个位置)来构造它们。标签是一个名字后跟 :。
要理解分支如何执行,你需要了解程序计数器(PC)。PC 是一个特殊寄存器,对你的代码隐藏,保存正在执行的指令的内存地址。分支指令向 PC 写入一个新值,因此 CPU 从你的目标标签而不是代码的下一行获取下一条指令。
分支家族
每个分支比较两个寄存器,并且仅在测试成立时跳转到标签。当测试失败时,执行继续到下一行。六个测试构成核心:
beq a, b, label # 若 a == b 则跳转bne a, b, label # 若 a != b 则跳转blt a, b, label # 若 a < b 则跳转 (有符号)bge a, b, label # 若 a >= b 则跳转 (有符号)bltu a, b, label # 若 a < b 则跳转 (无符号)bgeu a, b, label # 若 a >= b 则跳转 (无符号)
和 测试相等性并忽略符号。 和 构成顺序对,小于和大于或等于,并将寄存器作为有符号数比较。 和 执行相同的比较,但将值视为无符号,这适合永远不会为负的地址或计数。
有符号和无符号是什么意思?一个寄存器保存 32 位,相同的位可以用两种方式读取。作为无符号读取时,它们形成从 0 到约 40 亿的普通整数。作为有符号读取时,最高位算作负号,因此一半的位模式表示负数。位本身不会改变。改变的只是你读取它们的方式。这就是为什么有两组比较:对可能为负的普通数字使用有符号的(、),对永远不会为负的值(如内存地址)使用无符号的(、)。
没有直接的“大于则分支”或“小于或等于则分支”。你通过交换两个寄存器来获得它们,汇编器提供 和 作为伪分支来为你执行交换,因此 变成 。还有两个快捷方式: 和 将一个寄存器与零比较, 无条件跳转。
li t0, 0 # 总和 = 0li t1, 1 # i = 1li t2, 6 # 上限 = 6 (当 i < 6 时循环)loop:bge t1, t2, done # 当 i >= 6 时退出循环add t0, t0, t1 # 总和 += iaddi t1, t1, 1 # i++j loop # 回去再次检查done:mv a0, t0 # 打印总和 (应为 15)li a7, 1ecallli a7, 10ecall
跟踪它。t0 保存运行总和(从 0 开始),t1 保存计数器 i(从 1 开始),t2 保存限制 6。每次通过 loop: 在 i 达到 6 时离开循环。否则 将 i 加到总和, 将 i 增加一, 跳回去再次测试。循环加 1、2、3、4 和 5,done 打印总和 15。
bge 比较 t1 与 t2,以决定循环是否继续。
t1 变为 6 时, 将控制转移到 done,而不是让循环体再次运行。那个测试就是区分重复的一轮和停止的一轮的东西。t1 增加,程序计数器(pc)跳回 loop。每次 pc 返回 loop,分支条件都是假的,所以循环再次运行。