硬件逆向组织 Opcode Collective 的研究员 Ken Shirriff 近期公布了一项硬核考古成果:通过高倍显微镜拍摄 1980 年发布的 Intel 8087 浮点协处理器芯片,他们逆向解码了中央微码 ROM 中的 1648 条微指令。其中最耐人寻味的发现,落在了一条看似极其平淡的指令上——FSCALE(机器码为 D9 FD)。

在现代程序员眼里,FSCALE 的语义极其简单:把一个浮点数乘以 2 的整数次幂,直觉上只要将其指数加上一个偏移量即可。但反汇编数据显示,这个基础操作在芯片内部牵扯出了多达 3 层子调用、29 条核心微指令入口(#0748 至 #0778)以及动态展开后多达 140 余条微指令路径

规矩未立之时,每一寸精度的坚守,都写满了在物理牢笼里的辗转腾挪。

4 万晶体管牢笼下的硬件魔术

1970 年代的浮点计算是一片混乱的泥潭。各家大型机厂商的标准互不兼容,为了硬件实现简便,计算往往欠缺数学严谨性,舍入误差与数值不稳定性频发。1980 年,Intel 在海法工程团队的操刀下,采用 3–4.5 µm HMOS 工艺推出了 8087 协处理器,并由数值分析大师 William Kahan 奠定了 80 位扩展精度、NaN(非数)与细粒度异常机制。

方寸裸片上塞入了超千条微指令与双比特存储阵列
方寸裸片上塞入了超千条微指令与双比特存储阵列

8087 后来成了 1985 年正式颁布的 IEEE 754 浮点标准的直接蓝本。在 1981 年 IBM PC 5150 主板上预留该芯片插槽后,电子表格和 CAD 等密集运算获得了 50 到 100 倍的提速

然而,彼时 8087 的晶体管预算只有 40,000 到 45,000 只(若按 ROM 计容方式算约 6 万多只),芯片面积仅为 5mm×6mm。为了在极度受限的面积里塞入 1,648 条 16 位微指令(约 26,368 比特),Intel 甚至设计了多尺寸晶体管,在微码 ROM 里实现了单单元存储 2 比特的黑科技。

Intel 8087 关键资源约束与性能基准 5×6 mm HMOS 工艺芯片物理面积 ~4.5 万 极度受限晶体管预算 1,648 条 16位微码指令全芯片容量 50-100倍 相对 8088 纯软模拟提速

在这样严苛的硬件约束下,很多常人以为该由硬件总线完成的事,全部被逼成了微码级的戏法。

偷换栈顶指针与尾数移位器复用

逆向解码呈现的第一个反常设计,是读取操作数的方式。

用于乘除法的移位器被微码跨通路借来截断浮点指数(剖面示意)
用于乘除法的移位器被微码跨通路借来截断浮点指数(剖面示意)

FSCALE 需要两个输入:底数 ST(0) 和缩放幂次 ST(1)。在正规体系中,硬件理应有一条第二操作数读取总线。但 8087 为了省下这条总线的硅片面积,微码在入口处执行了一套极具巧思的障眼法:通过微指令修改内部栈顶指针 TOP。

微码先弹栈(pop),让 ST(1) 物理上变成栈顶 ST(0),将其读入暂存器 tmpB;随后立即压栈(push)复位指针,将栈结构恢复原状。这种纯粹靠内部控制流欺骗硬件寄存器堆的做法,展现了极度匮乏下的工程狡黠。

FSCALE 内部偷换栈顶与数据流处理流程 1. 暂存底数 读栈顶 ST(0) 转入寄存器 tmpA 2. 伪弹栈读取 临时栈指针 TOP++ ST(1) 变 ST(0) 读入 3. 截断与对齐 复用 68 位移位器 将 ST(1) 截断为整数 4. 阶码相加 写入指数通路 异常检测并写回

更深层的复杂性在于参数的数学本质。在 8087 内部,所有数值均以 80 位扩展精度临时实数存储:包括 1 位符号、15 位带偏置的阶码以及 64 位尾数。这意味着传入的幂次 ST(1) 不是整数,而是一个完整的浮点数。微码必须计算 $\text{ST}(0) \times 2^{\text{trunc}(\text{ST}(1))}$。

芯片并没有专门的指数截断电路。8087 的做法是跨数据通路借力:调用原本为乘除法准备的 68 位尾数桶形移位器,把 ST(1) 的阶码进行移位、截断、对齐并提取出有效整数部分,最后再送回 16 位指数加法器运算。

这一过程需要处理极其繁琐的边界:操作数是零?是无穷大?还是无效的 NaN?如果底数为零,乘以任何数甚至非数是否应立即返回零?每一个分支,都化作了微码里一步步的条件跳转。


迷思澄清与历史遗产

在这项逆向工程被报道后,极客社区产生了一些典型误读。澄清这些偏差,有助于看清当时的真实技术面貌:

缩放指令早在浮点标准成型前便已作为公开规范写入手册(示意图)
缩放指令早在浮点标准成型前便已作为公开规范写入手册(示意图)
  • 误区一.所谓“未公开隐藏功能”。有人误传 8087 存在隐藏指令。实际上 FSCALE 的机器码 D9 FD 从第一天起就是公开文档指令,真正的隐蔽特征是硅片内部未公开的微操作架构,比如用微码篡改 TOP 指针寻址和复用尾数移位器。
  • 误区二.140 条微指令的量级争议。反汇编明确显示主干微指令地址仅为 #0748 至 #0778,共约 29 条。所谓的“超过 140 条”,是指包含多层嵌套公共子程序后的动态执行路径步数,亦有可能是文献中对其时钟开销周期的交叉混淆。
历史时序与标准奠基对比 1980 年:Intel 8087 先行投产 • 以物理芯片形式率先确立 80 位扩展精度 • 包含部分特有的未规格化数与临时处理特征 1985 年:IEEE 754 标准正式确立 • 以 8087 规范为蓝本进行数学标准化收敛 • 剔除历史妥协,奠定全行业软硬件通用规范

很多人以为 8087 实现了 IEEE 754,但时序恰恰相反:8087 诞生在标准成型的前夕,是标准的探路先锋。因为早于标准 5 年降生,它内部还残留着未规格化数与早期反常数的特殊行为。

对于开发 DOSBox、86Box 的底层模拟器作者而言,这类微架构逆向能精准修补边缘浮点异常的历史 Bug;而对现代系统设计者来说,这篇显微镜下的答卷更揭示了一个朴素的工程逻辑:今天的单周期硬件吞吐,无非是当年先驱者在方寸硅片上,用微指令一步一步死磕出来的软件式积淀。

  • 提醒.当前关于移位器作为指数转换器的推论基于微码反汇编序列,物理层控制信号走线尚待真实硅片电气测试的最终闭环验证。