X86中的浮点栈结构

On October 13, 2010, in 系统结构, by sponge

在介绍X86浮点栈结构之前,先说明一下X87,X87是IA_32体系结构中为提高浮点数据处理能力而增加的X87芯片系列数学协处理器,使用X87指令,X86指令集和X87指令集统称为X86指令集。可以简单的理解X87是一个浮点协处理器,是X86的浮点处理单元。

X86的浮点单元(X87 FPU)用作浮点数据处理,必然要使用寄存器,而且这个寄存器必然要同整点的不同。那么,在FPU中是怎么设计这些浮点寄存器呢?

实际上,X86的FPU中包含一个浮点寄存器栈,它包含了8个80位的可以直接进行浮点运算的寄存器,浮点数以双精度格式存储在这些寄存器中,无论是单精度、双精度还是整数等,被load到这些个寄存器中后,都会转化为双精度。FPU指令在使用这些寄存器的时候,是以一种“栈”的方式来使用,叫做“浮点寄存器栈”,其结构如下图所示:

这个栈由8个可以直接进行浮点运算的寄存器组成,按照顺序编号为0-7。CPU在处理浮点运算的时候,将这些寄存器作为一个栈来使用。它是一个向下扩展的循环栈。我们知道,整点运算有个FLAG状态寄存器,同样,浮点运算部件中也有个浮点状态寄存器FFLAG,处理器利用浮点状态寄存器中的bit11-bit13三个bit来标记这个栈的栈顶位置(用top指针表示)。

下面介绍下这个浮点栈的工作原理:

  1. 当程序向寄存器栈中装入数据的时候,top指针的值减1,然后将数据压入top指针指向的浮点寄存器中。
  2. 当top值为0的时候,下一次压栈则将数据压入7号浮点寄存器中,top指针被设置成7(也就是说浮点栈是一个循环栈)。
  3. 当需要将堆栈中的数据保存到内存中的时候(FST指令),则进行退栈操作。退栈操作与压栈类似,top指针增加1,若退栈前top值为7,则退栈后top为0(对于X87寄存器来说,一个load操作相当于push,一个store操作相当于pop)。

上述这些操作由硬件来完成,对用户是透明的,用户无须关心压栈和出栈操作以及top值这些细节,FPU提供了FLD,FST等访存指令和FADD等浮点运算指令以及其他一些辅助指令来操作浮点寄存器(可参考IA_32指令集手册)。用户可以通过FLD指令将内存中的浮点数据压入浮点寄存器栈,通过FADD指令进行一个加法运算,然后通过FST指令来保存结果并退栈。

那么,按照上面的逻辑,我们是不是之能够使用栈顶的浮点寄存器呢?X87的设计者当然不会这么做。那么,用户怎么操作这些寄存器呢?这些寄存器的名字是什么呢?

在用户通过指令对浮点数据操作的时候,这些浮点寄存器所呈现给用户的名字是ST[0],ST[1],ST[2]......ST[7]。但是,要注意,这里的ST[i]中的i并不是和上图中的编号一一对应的,而是离栈顶的距离。也就是说,ST[i]表示距离栈顶的第i个单元的寄存器。按照上面的图所示的状态,ST[0]为第3号寄存器,ST[1]为第4号寄存器,以此类推。

听到这里,是不是有点糊涂了?我们在实际浮点运算中到底怎么使用浮点栈?如果需要store ST[2],还要出栈么?不要着急,让我们看几个指令的例子:

看下面这几条指令:

FLD    value1 ;(a) value1=5.6
FMUL  value2 ;(b) value2=2.4
FLD    value3 ; value3=3.8
FMUL  value4 ;(c)value4=10.3
FADD  ST(1)  ;(d)

实际上,这些指令是完成了一个这样的操作:

double Product = (5.6 x 2.4) + (3.8 x 10.3);

让我们一步一步来看每条指令是怎么操作浮点栈的:

  1. 第一条指令(FLD value1)将top指针减1(压栈),将值5.6 从内存load到ST[0]寄存器,如下面的图中(a)所示。
  2. 第二个指令将ST(0)同内存中的valua2(2.4)相乘,并将结果存在ST[0]中。如(b)所示。
  3. 第三条指令将top减1,并将3.8 load到ST[0]中。
  4. 第四条指令将ST[0]中的值同内存中的value4(10.3)相乘,并将结果存储在ST[0]中,如(c)所示。
  5. 第五条指令将ST[0]中的值同ST[1]中的值相加,把结果存放到ST[0]中,如(d)所示。

经过这样的解释,是不是会清晰很多?当然,如果还有疑问的话,建议去阅读

Intel Architecture Software Developer's Manual, Volume 2: Instruction Set Reference Manual

中的浮点指令,多看几个浮点指令的用法,就知道该怎么用了。

另外,这样就结束了么?这种结构不会存在问题?对了,溢出怎么办?怎么防止栈顶覆盖栈底呢?对空栈退栈操作和对满栈入栈操作都是不允许的。

实际上,FPU还提供了一个标记寄存器tag,这个寄存器记录了每个浮点寄存器的状态,当装入数据的时,硬件会将寄存器中相应的tag置为有效,反之置为空。当压栈时遇到已经标为有效的寄存器时,产生上溢;如果退栈时,遇到相应tag为空时,则产生下溢,处理器会触发相应的异常来进行处理。

另外,值得一提的是,其他大多数处理起上都没有这种运算方式,一般处理器都是内部防止几个通用的浮点寄存器,按照寄存器编号进行使用。因此,要在其他体系结构下模拟这种浮点栈,是二进制翻译的一个难点。以后的文章我会详细介绍二进制翻译下怎么模拟这些浮点栈,包括寄存器旋转和归一法,此文在这里仅作为介绍性的引文,更多内容可以参考:

Intel® 64 and IA-32 Architectures Software Developer's Manuals

anyShare分享到:
Tagged with:
 

2 Responses to “X86中的浮点栈结构”

  1. zero says:

    泄密了

Leave a Reply to sponge

Note: Commenter is allowed to use '@User+blank' to automatically notify your reply to other commenter. e.g, if ABC is one of commenter of this post, then write '@ABC '(exclude ') will automatically send your comment to ABC. Using '@all ' to notify all previous commenters. Be sure that the value of User should exactly match with commenter's name (case sensitive).