跳转至

最小汇编程序与 RISC/CISC

汇编语言是机器码的可读表示。它没有消除 ISA 细节,只是用助记符和标签替代位串与裸地址。

最小完整程序

目标是计算 c = a + b。约定 x10 保存 a 的地址,abc 依次占 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

假设初始状态为:

x10 = 0x1000
Mem[0x1000] = 20
Mem[0x1004] = 22

逐条执行后:

时刻 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 中容易表达的操作也会影响编译器优化空间。软硬件接口不是墙,而是协同设计点。

自测

  1. 上面的数组求和循环有哪些数据相关和控制相关?
  2. 为什么“RISC 指令少、CISC 指令多”只是过度简化?
  3. 微操作如何让复杂外部 ISA 与规则内部后端共存?