最小汇编程序与 RISC/CISC¶
汇编语言是机器码的可读表示。它没有消除 ISA 细节,只是用助记符和标签替代位串与裸地址。
最小完整程序¶
目标是计算 c = a + b。约定 x10 保存 a 的地址,a、b、c 依次占 4 B:
lw x5, 0(x10) # x5 <- a
lw x6, 4(x10) # x6 <- b
add x7, x5, x6 # x7 <- x5 + x6
sw x7, 8(x10) # c <- x7
假设初始状态为:
逐条执行后:
| 时刻 | x5 | x6 | x7 | Mem[0x1008] |
|---|---|---|---|---|
| 初始 | 未指定 | 未指定 | 未指定 | 未指定 |
| 第 1 条后 | 20 | 未指定 | 未指定 | 未指定 |
| 第 2 条后 | 20 | 22 | 未指定 | 未指定 |
| 第 3 条后 | 20 | 22 | 42 | 未指定 |
| 第 4 条后 | 20 | 22 | 42 | 42 |
这就是架构语义。真实 CPU 可以让多条指令重叠执行,但不能让 add 使用尚未得到的旧值,也不能在异常时留下错误的已提交状态。
一个带循环的例子¶
把长度为 x11 的 32 位数组求和,x10 指向首元素:
addi x5, x0, 0 # sum = 0
loop:
beq x11, x0, done
lw x6, 0(x10)
add x5, x5, x6
addi x10, x10, 4
addi x11, x11, -1
jal x0, loop
done:
x0 恒为零,省去“加载常数零”的指令。这个循环同时包含数据相关、load-use、条件分支和回跳,是研究流水线与 cache 的最小基准。
RISC 的设计动机¶
RISC 倾向于:
- 规则、较固定的指令格式;
- load/store 结构,算术主要在寄存器间进行;
- 较少而清晰的寻址方式;
- 让常用简单操作走快速路径;
- 把复杂序列交给编译器组合。
好处是译码和流水化容易、验证边界清晰。代价是某些任务需要更多指令,代码密度和取指带宽可能受影响。
CISC 的设计动机¶
CISC 兴起时内存昂贵、编译器能力有限,复杂指令可用更少机器码表达高级操作,提供丰富寻址方式,并保持早期软件兼容。
它的优点是代码紧凑、单条指令表达力强;代价是变长译码、复杂异常边界和实现验证更困难。但“复杂指令必然慢”并不准确:高频复杂指令可由专门快速路径实现,冷门指令也可借助微码。
为什么今天不能二选一¶
现代 x86 前端会把宏指令翻译为较规则的微操作,后端再像 RISC 风格核心那样调度;RISC-V 已加入压缩、向量和原子扩展,Arm 也通过 Thumb、SVE/SME 等机制提高代码密度并支持向量、矩阵计算。外部 ISA 与内部执行形式已经分离。
判断设计优劣应看工作负载和指标:
| 关注点 | 更规则指令的倾向 | 更复杂指令的倾向 |
|---|---|---|
| 译码能耗 | 较低 | 较高或需缓存译码结果 |
| 代码密度 | 可能较低 | 往往较高 |
| 编译器责任 | 较大 | 部分转移给硬件 |
| 兼容负担 | 新 ISA 可较轻 | 成熟生态通常很重 |
与高级语言的联系¶
一个高级语句不对应固定的一条机器指令。优化级别、数据布局、调用约定和目标扩展都会改变生成代码。反过来,ISA 中容易表达的操作也会影响编译器优化空间。软硬件接口不是墙,而是协同设计点。
自测¶
- 上面的数组求和循环有哪些数据相关和控制相关?
- 为什么“RISC 指令少、CISC 指令多”只是过度简化?
- 微操作如何让复杂外部 ISA 与规则内部后端共存?