汇编语言FPU寄存器栈(register stack)

FPU 不使用通用寄存器 (EAX、EBX 等等)。反之,它有自己的一组寄存器,称为寄存器栈 (register stack)。数值从内存加载到寄存器栈,然后执行计算,再将堆栈数值保存到内存。

FPU 指令用后缀 (postfix) 形式计算算术表达式,这和惠普计算器的方法大致相同。比如,现有一个中缀表达式 (infix expression):(5*6)+4,其后缀表达式为:

5 6 * 4 +

中缀表达式 (A+B)*C 要用括号来覆盖默认的优先级规则(乘法在加法之前)。与之等效的后缀表达式则不需要括号:

A B + C *

表达式堆栈

在计算后缀表达式的过程中,用堆栈来保存中间结果。下图展示了计算后缀表达式 56*4- 所需的步骤。堆栈条目被标记为 ST(0) 和 ST(1),其中 ST(0) 表示堆栈指针通常所指位置。

计算后缀表达式56*4-

中缀表达式转换为后缀表达式的常见方法在互联网以及计算机科学入门读物中都可以查阅到,此处不再赘述。下表给岀了一些等价表达式。

中缀 后缀  中缀 后缀
A+B AB+ (A+B)*(C+D) AB+CD+*
(A-B)/D AB-D/  ((A+B)/C)*(E—F) AB+C/EF-*

FPU 数据寄存器

FPU 有 8 个独立的、可寻址的 80 位数据寄存器 R0〜R7,如下图所示,这些寄存器合称为寄存器栈。FPU 状态字中名为 TOP 的一个 3 位字段给出了当前处于栈顶的寄存器编号。例如,在下图中,TOP 等于二进制数 011,这表示栈顶为 R3。在编写浮点指令时,这个位置也称为 ST(0)(或简写为 ST)。最后一个寄存器为 ST(7)。

浮点数据寄存器栈

如同所想的一样,入栈(push)操作(也称为加载)将 TOP 减 1,并把操作数复制到标识为 ST(0) 的寄存器中。如果在入栈之前,TOP 等于 0,那么 TOP 就回绕到寄存器 R7。

出栈(pop)操作(也称为保存)把 ST(0) 的数据复制到操作数,再将TOP加1。如果在出栈之前,TOP 等于 7,则 TOP 就回绕到寄存器 R0。

如果加载到堆栈的数值覆盖了寄存器栈内原有的数据,就会产生一个浮点异常(floating-point exception)。下图展示了数据 1.0 和 2.0 入栈后的堆栈情况。

1.0 和 2.0入栈后的FPU栈

尽管理解 FPU 如何用一组有限数量的寄存器实现堆栈很有意思,但这里只需关注 ST(n),其中 ST(0) 总是表示栈顶。从这里开始,引用栈寄存器时将使用 ST(0),ST(1),以此类推。指令操作数不能直接引用寄存器编号。

寄存器中浮点数使用的是 IEEE 10 字节扩展实数格式(也被称为临时实数(temporary real))。当 FPU 把算术运算结果存入内存时,它会把结果转换成如下格式之一:整数、长整
数、单精度(短实数)、双精度(长实数),或者压缩二进制编码的十进制数(BCD)。

专用寄存器

FPU 有 6 个专用(special-purpose)寄存器,如下图所示:

FPU专用寄存器
  • 操作码寄存器:保存最后执行的非控制指令的操作码。
  • 控制寄存器:执行运算时,控制精度以及 FPU 使用的舍入方法。还可以用这个寄存器来屏蔽(隐藏)单个浮点异常。
  • 状态寄存器:包含栈顶指针、条件码和异常警告。
  • 标识寄存器:指明 FPU 数据寄存器栈内每个寄存器的内容。其中,每个寄存器都用两位来表示该寄存器包含的是一个有效数、零、特殊数值 (NaN、无穷、非规格化,或不支持的格式 ),还是为空。
  • 最后指令指针寄存器:保存指向最后执行的非控制指令的指针。
  • 最后数据(操作数)指针寄存器:保存指向数据操作数的指针,如果存在,那么该数被最后执行的指令所使用。

操作系统使用这些专用寄存器在任务切换时保存状态信息。