扩展操作码指令格式

4.1.3 结尾撞的那堵墙是:操作码位数被指令总数绑架——只有 3 条指令需要两个地址,操作码却要按全部指令数来定位数,那几位在每条指令里都得占着。

扩展操作码的想法很直接:地址字段用得多的指令,操作码就短;地址字段用得少的指令,操作码就可以长。 反正指令字长固定,空出来的地址位正好拿去装操作码。

短地址多长地址少

但一旦操作码长度不再固定,一个新问题立刻出现:译码器怎么知道这条指令的操作码到哪一位结束? 本节的全部技术含量都在这个问题上,而且这个问题的答案会一直用到第 5 章的流水线。

机制

唯一的约束:短操作码不能是长操作码的前缀

这就是全部规则。 因为译码器是从最高位开始一位一位看下去的,如果 1111 既是一条三地址指令的完整操作码、又是某条二地址指令操作码的开头四位,译码器看到 1111 时就无法判断该停还是该继续。

满足”任何一个码字都不是另一个码字前缀”的编码叫前缀码,它的性质是唯一可译:一串比特只有一种切分方式,不需要任何分隔符。

扩展操作码 = 操作码用前缀码编码。 这一句把这节和 4.1.3 的差别讲完了。

经典例题:16 位指令字、4 位地址字段

设指令字长 16 位,每个地址码固定 4 位。留窗口的做法是:每一层都拿出最后一个编码(全 1)不作为本层指令,专门留给下一层扩展。

层操作码位数编码形式本层指令条数
三地址40000 ~ 1110 + 3×4 位地址15
二地址81111 0000 ~ 1111 1110 + 2×4 位地址15
一地址121111 1111 0000 ~ 1111 1111 1110 + 1×4 位地址15
零地址161111 1111 1111 0000 ~ 1111 1111 1111 111116
graph TD
    R["4 位编码空间<br/>共 16 个"] --> A["0000~1110<br/>15 条三地址指令"]
    R --> B["1111<br/>★窗口"]
    B --> C["1111 0000~1111 1110<br/>15 条二地址指令"]
    B --> D["1111 1111<br/>★窗口"]
    D --> E["…0000~…1110<br/>15 条一地址指令"]
    D --> F["1111 1111 1111<br/>★窗口"]
    F --> G["…0000~…1111<br/>16 条零地址指令"]
    classDef win fill:#ffe6cc,stroke:#d79b00,stroke-width:2px
    classDef ins fill:#d5e8d4,stroke:#82b366
    class B,D,F win
    class A,C,E,G ins

最后一层是 16 条不是 15 条,因为零地址之下没有再要扩展的层了,窗口不用留。这一个数是本题最常见的失分点。

通用计算模板

不要背 15/15/15/16,那只是”每层留一个窗口”这一种特殊分配。通用规则只有一句:

下一层的指令条数上限 = 上一层剩余的编码数 × ,其中 是本层释放出来的地址字段位数。

写成公式,设地址字段 位、三地址指令 条、二地址 条、一地址 条:

二地址最多一地址最多零地址最多

每一步都是同一个动作:(上层总数 − 上层已用) × 2^a。 会了这一个动作,题目怎么给条件都能往下推,包括反过来问”三地址指令最多几条”。

用上例验算:、 → 二地址最多 条。注意是 16 不是 15——如果二地址真用满 16 条,就没有窗口留给一地址了。教材取 15 是为了继续扩展,不是上限本身。 题目问”最多”就答 16,问”若还要有一地址指令则最多”才答 15。

边界辨析:一个能当场验算的自检不等式

把每条指令的操作码长度记作 ,则一组前缀码存在的充要条件是

取等号时编码空间恰好用尽,一位不剩。这条叫 Kraft 不等式,超出统考大纲,但作为验算工具极好用——它把”够不够、有没有冲突”变成一次加法。

验算上例:

正好等于 1——教材那套分配把 16 位编码空间用得一位不剩。若你算出来 ,方案一定冲突;若 ,还有余量可以再塞指令。

设计检查四条

拿到一个扩展操作码方案,按顺序过这四条:

  1. 前缀条件:每一层用掉的编码里,是否给下一层留了至少一个未用编码作窗口?
  2. 无重复:不同层的操作码,把短的按窗口展开后是否与长的重叠?
  3. 位数配平:每条指令的 OP 位数 + 地址位数 是否都等于指令字长?
  4. 总量:Kraft 和是否 ?

变长指令的长度,藏在哪里

疑问点:变长指令集编码直接乘概率算期望长度,不需要再区分指令长度吗?那怎么知道这条指令到哪里结束

不需要额外的位,因为长度信息已经在操作码里了——而且这不是巧合,正是前缀码性质的直接后果。

先把那道题算完(32 个寄存器 → 寄存器号 5 位;立即数 16 位;142 条指令 → 操作码 位;长度向上取整到 8 的倍数):

类型占比字段原始位数对齐后
1 输入 + 1 输出20%1824
2 输入 + 1 输出30%2324
1 输入 + 1 输出 + 立即数25%3440
立即数输入 + 1 输出25%2932
  • 定长编码:所有指令都得按最长的来 → 40 位
  • 变长编码: 位
  • 省下 的存储空间

现在回答”怎么知道到哪结束”。取指不是一次取完再译码,而是边取边译:

取回第 1 个字节 ──► 译码器看操作码 ──► "这是类型 3" ──► 长度 = 40 位
                                   └► 于是再取 4 个字节,指令结束

操作码是前缀码,所以看到操作码就已经唯一确定了指令类型,而指令类型唯一确定了格式,格式唯一确定了长度。 长度是被推出来的,不是被存储的——所以它一位空间都不占,那个”直接乘概率”的算法完全正确。

换个角度看更清楚:如果真的额外存一个”长度字段”,那才是浪费,因为那个字段的信息量已经被操作码携带了,等于把同一件事编码两遍。

所以”好粗糙啊”这个直觉里,该保留的部分是”这里一定有个机制没讲”,该修正的部分是”那个机制需要额外开销”。它不需要,代价在别处——见下面的边界。

疑问点:直接能隐含指令格式,需要译码

对,而且这句话点中的正是变长指令真正的代价所在。

长度不花存储空间,但花时序:

定长指令变长指令
第 2 条指令的起始位置取指时就知道(PC + 常数)必须先译完第 1 条才知道
取指与译码可完全并行、可预取任意多条串行依赖
一拍取多条(超标量)直接切分要先做”长度预译码”扫描

代价从空间转移到了时间,而且是转移到了流水线最不能容忍的地方——取指到译码这一段的串行依赖。 这条线索会在三个地方各兑现一次:

  • 4.4.1:x86 指令 1~15 字节,长度确定要经过前缀字节、ModRM、SIB 多级判断,这是 CISC 译码复杂的根源
  • 4.4.2:RISC 全部定长,取指部件不必等译码
  • 5.5 指令流水线(本地资料):为什么”取指”能成为一个独立的、延迟恒定的流水段

本章第一条主线由此立起来:一条指令必须自己携带自己的长度信息;用前缀码携带不花空间,但让取指和译码变成串行的。

边界

扩展操作码不改变指令字长。 教材例题里 16 位始终是 16 位,变的只是这 16 位内部 OP 和地址的分界线。“变长操作码”和”变长指令字”是两件事——真实的 x86 两者兼有,但统考题几乎只考”指令字定长、操作码可扩展”这一种组合。这条已在 4.1.2辨析过,这里是它第二次成为陷阱。

教材例子里”三地址指令用短操作码”的理由不是使用频率。 是因为三地址指令的地址字段吃掉 12 位,只剩 4 位给操作码——位数是被挤出来的,不是被优选的。真实 ISA 里确实也会把短码分给高频指令(这是霍夫曼编码的思想),但这是第二条独立的理由。做题时若问”为什么三地址指令操作码最短”,答地址字段占位最多,不要答”因为常用”。

“最多能有几条”和”若还要留扩展则最多几条”是两个问题。 前者用满,后者留窗口,答案差 1。教材那套 15/15/15/16 是后者的答案。

留出的窗口不必是全 1,也不必只留一个。 全 1 只是惯例。题目若说”以 1101 作为扩展标志”,一切照推,只是编码换了。若说”留 2 个编码作扩展”,则本层指令数减 2、下层容量翻倍。

零地址指令那一层不留窗口,所以是 16 条。 除非题目明说”还要预留将来扩展”。

操作码全 0 通常不作为有效指令。 现实中 0000...0 常被留作非法指令陷阱(读到全 0 的存储区能立刻报错)。统考不考这条,题目给的编码范围以题为准。

对照速查

概念一句话
扩展操作码操作码长度可变,地址字段少的指令操作码长
前缀码任何码字都不是另一码字的前缀 → 唯一可译
扩展窗口本层留出的未用编码,供下层继续扩展
Kraft 和, 表示编码空间用尽(超纲,用于验算)
递推动作公式
下层最多条数(上层总数 − 上层已用)
上层最多条数(反推)下层需求
判断对错
扩展操作码要求短码不是长码的前缀✅
扩展操作码会改变指令字长❌
三地址指令操作码最短,因为它最常用❌(因为地址字段占位最多)
16 位指令字 4 位地址,三地址 15 条时二地址最多 15 条❌(最多 16,留窗口才 15)
零地址那一层也要留窗口❌(下面没有层了)
变长指令需要一个额外字段标明长度❌(长度由操作码隐含)
变长指令的代价是存储空间❌(反了,省空间,代价是译码串行)

考点

  • 扩展操作码 = 操作码用前缀码编码;唯一约束是短码不能是长码前缀
  • 经典分配 16 位指令字 / 4 位地址:15 / 15 / 15 / 16,最后一层不留窗口
  • 通用递推:(上层总数 − 上层已用)× ,正推反推都用它
  • Kraft 不等式 可当场验算方案是否冲突或有余量
  • 变长指令的长度由操作码隐含推出,不占额外存储位
  • 变长指令真正的代价是取指与译码的串行依赖,直接指向 CISC 译码复杂与流水线障碍
  • 教材里三地址指令码短,原因是地址字段挤占,不是使用频率

链接