本节摘要:基线整数指令集是 ISA 合约的正文,它只承诺三样东西——架构状态(寄存器堆与程序计数器)、指令语义(每条指令对状态的改变)、异常行为(出错时进入哪里);它刻意不承诺执行时序与微架构细节。本节逐条读这份正文,并解释恒零寄存器、无副作用、装入存储型架构这些条款如何直接简化流水线设计。
同样一条整数加法指令,在一颗标称高性能的核上,可能要等前一条装载指令的数据到位才能出结果;在一颗小核上,反而当拍就完成。两颗核都"完全合规"——因为合约里根本没有"加法必须几个周期完成"这一条。ISA 承诺的是架构状态的最终改变,不承诺路径与时序。这个现象是理解整章的钥匙:合约正文写得极短,兑现方式千变万化,这正是第一章说的"实现自由度"的法律基础。
那么正文到底写了什么?三样东西。架构状态:整数寄存器堆与程序计数器,外加后文会讲的控制状态寄存器——这是软件可见的全部状态,合约只认这些。指令语义:每条指令从输入状态到输出状态的映射。异常行为:非法指令、地址未对齐等情况发生时,处理器进入哪个处理流程。除这三样之外的一切——几级流水线、要不要缓存、预测器多聪明——合约统统不管。

合约正文里有几个看似平淡的条款,每一条都在给硬件设计师省真金白银。
恒零寄存器。整数寄存器堆的第零号寄存器永远读出零、写入被丢弃。别小看这一条:许多常见操作因此不再需要专门指令——寄存器传送可以用"加零"实现,寄存器清零也可以,条件置零同样可以。硬件上少造一批专用数据通路,编译器则获得了用普通指令拼出特殊语义的自由。这是一个把复杂度从硬件搬进编译器的经典设计。
无副作用的整数运算。除显式的存储指令与控制状态寄存器操作外,基本指令不产生架构状态之外的副作用。对流水线设计,这意味着一条整数运算指令在任何时刻被取消都不留痕迹——分支预测失败后冲刷流水线时,这一点是精确恢复的前提。第五章讲异常精确打断时,会再次用到这条性质。
装入存储型架构。只有装载与存储两类指令访问内存,运算指令一律在寄存器之间进行。访存地址计算因此集中、规整,访存单元的接口可以标准化,缓存与总线的设计对象也变得清晰。对比允许运算指令直接读内存的 ISA,这里省掉的是"运算结果可能要等内存"这种流水线噩梦的整类处理。
对齐与非对齐的态度。合约要求基本指令自然对齐,非对齐访问交给实现者选择:可以陷入异常,也可以由硬件或软件处理。这条"半开放条款"给了低成本核一条退路——小核不必为罕见的非对齐访问造专门通路。
看一段真实汇编,把条款落到代码上:
# 一段循环体:数组求和,展示恒零寄存器与装入存储分工 loop: lw a5, 0(a0) # 装载:从 a0 指向的地址读一个字到 a5 add a4, a4, a5 # 运算:寄存器间相加,a4 为累加器 addi a0, a0, 4 # 指针前移一个字的宽度 blt a0, a1, loop # 未到尾地址则跳回,条件分支 # 注意:全程没有一条指令既算术又访存——装入存储型架构的日常形态 # x0 在这段代码里若出现,多半用于比较或清零,不需要任何专门指令
把"不承诺"的部分列出来,恰好就是微架构设计的自由度清单,也是后续各章的目录预告。
| 合约不管的事 | 设计师的决策空间 | 展开位置 |
|---|---|---|
| 指令分几拍执行 | 流水线级数、乱序调度 | 第三章 |
| 装载延迟多少拍 | 缓存层级、命中路径设计 | 第四章 |
| 多核如何共享内存 | 一致性协议、内存模型 | 第四章 |
| 异常何时被响应 | 精确异常的实现窗口 | 第五章 |
| 要不要向量单元 | 扩展账单与取舍 | 第二、六章 |
| 主频定多少 | 时序收敛谈判 | 第八章 |
💡 关键直觉:读合约时拿两支笔——红笔圈"必须兑现"的条款,蓝笔圈"留白"的条款。红笔条款决定你的合规测试能不能过,蓝笔条款决定你的核有没有竞争力。新手常犯的错是把全部精力花在红笔上。
正文三件事之外,合约对"出错了怎么办"的约定也值得细读——它定义了硬件与软件在故障前的分工。非法指令:译码站发现编码不认识,抛出异常,处理程序决定模拟、终止还是上报——硬件不猜测语义,只如实报案。访存故障:地址翻译或权限检查不过(4.1 与 5.3 的机制),同样抛出异常并标注原因。断点与环境调用:自愿陷入的指令,是软件主动升权的正门(7.2 的系统调用走这里)。注意合约的态度:异常是正常机制而非事故——缺页、系统调用、调试断点都靠它运转。设计的含义是:流水线必须把"异常路径"当作一等公民来设计与验证,而不是事后补丁;第五章会看到精确异常对这条线路的严格要求。
合约的版本纪律。基线规范本身也在演进,但演进有铁律:已发布的指令语义不改。新版本只做"澄清"与"新增",绝不"改意"。这条纪律是二进制长寿的根基——十年前编译的程序今天还能跑,靠的就是合约文本的稳定性。评估任何"兼容性问题"时,先分清是合约变了(几乎不会)还是实现没兑现合约(绝大多数情况)。
问:三十二个架构寄存器够用吗? 对绝大多数代码够用,但压力确实存在——7.1 会讲编译器如何用溢出与生存期压缩应对。加寄存器要动指令编码与调用规约,代价是撕裂兼容性,所以历史上加宽寄存器的提案都异常谨慎。
问:读恒零寄存器和写它,哪个是合法操作? 都合法。读永远得零;写被安静忽略,不报错。这个"写丢弃"性质还有妙用——移动指令可以写成"把结果丢进零寄存器",配合其他技巧实现空操作或占位,编译器排布代码时用得上。
下一节看价目表:每勾选一个标准扩展,账单上会多出哪几行。