浮点栈结构(转帖)
浮点栈结构(转帖)
在介绍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指针表示)。
下面介绍下这个浮点栈的工作原理:
上述这些操作由硬件来完成,对用户是透明的,用户无须关心压栈和出栈操作以及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.6FMUL value2 ;(b) value2=2.4FLD value3 ; value3=3.8FMUL value4 ;(c)value4=10.3FADD ST(1) ;(d)实际上,这些指令是完成了一个这样的操作: double Product = (5.6 x 2.4) + (3.8 x 10.3);让我们一步一步来看每条指令是怎么操作浮点栈的:
经过这样的解释,是不是会清晰很多?当然,如果还有疑问的话,建议去阅读 IntelArchitecture Software Developer's Manual, Volume 2: Instruction Set ReferenceManual中的浮点指令,多看几个浮点指令的用法,就知道该怎么用了。 另外,这样就结束了么?这种结构不会存在问题?对了,溢出怎么办?怎么防止栈顶覆盖栈底呢?对空栈退栈操作和对满栈入栈操作都是不允许的。 实际上,FPU还提供了一个标记寄存器tag,这个寄存器记录了每个浮点寄存器的状态,当装入数据的时,硬件会将寄存器中相应的tag置为有效,反 之置为空。当压栈时遇到已经标为有效的寄存器时,产生上溢;如果退栈时,遇到相应tag为空时,则产生下溢,处理器会触发相应的异常来进行处理。 另外,值得一提的是,其他大多数处理起上都没有这种运算方式,一般处理器都是内部防止几个通用的浮点寄存器,按照寄存器编号进行使用。因此,要在其 他体系结构下模拟这种浮点栈,是二进制翻译的一个难点。以后的文章我会详细介绍二进制翻译下怎么模拟这些浮点栈,包括寄存器旋转和归一法,此文在这里仅作为介绍性的引文,更多内容可以参考: Intel® 64 and IA-32Architectures Software Developer's Manuals |