第63部分- Linux x86 64位汇编 FPU之概要
X87浮点单元FPU,提供高性能浮点处理。从80486就开始支持了。
Intel x87 FPU专门用于执行标量浮点计算,对单精度浮点(32位)、双精度浮点(64位)以及扩展双精度浮点(80位)进行计算,并顺从IEEE754标准。
FPU的数据寄存器的个数都一样,只有8个。对x87 FPU的数据寄存器的访问方式与一般的寄存器有所不同,是栈式访问。通过FLD指令把外部数据搬到x87 FPU的数据寄存器中时,那么x87 FPU会根据所搬数据的长度(32位、64位、80位)将输入数据分别对待为单精度浮点、双精度浮点和扩展双精度浮点方式,然后统一转为双精度扩展模式放到数据栈顶。因此x87 FPU的数据寄存器长度为80位,并且后续的浮点计算都是基于扩展双精度进行的。
在栈顶的数据寄存器索引为0,那么它下面一个就是1, 然后是2, 以此类推,到栈底则为7。
数据输出到存储器时使用FST或FSTP指令,前者仅仅是根据目标存储器的长度(32位、64位、80位)将扩展双精度类型分别转为单精度、双精度、扩展双精度类型,然后输出到指定的存储器位置中。FSTP指令,除了将数据搬到外部外,还会执行推出堆栈的操作。
FLD和FST以及FSTP还能搬移FPU内部寄存器的数据,FLD的作用是将指定的FPU数据寄存器位置的数据搬移到栈顶寄存器中,而FST则是将栈顶寄存器的数据搬移到指定的FPU数据寄存器的位置。
由于80位(10个字节)并不是32位(4个字节)的整数倍,因此数据加载或存储双精度扩展浮点往往是用96位(12个字节)为单位进行,所以“tbyte”所指的就是“twelve bytes”
Intel汇编格式的字节、字宽指定修饰词:byte(字节,8位),word(字,16位),dword(双字,32位),qword(四字,64位),tbyte(十二字节,96位),mmword(64位,但只能用于MMX指令集中),xmmword(128位,用于SSE指令集),ymmword(256位,用于最新的AVX指令集)。
FPU有以环形堆栈组织的八个10字节寄存器。 堆栈顶部-寄存器ST(0),其他寄存器为ST(1),ST(2)…ST(7)。 在处理浮点数据时,通常会使用它。
数据传输指令
FDL-加载浮点。
FST-存储浮点(在ST(0)寄存器中)。
FSTP-存储浮点数(在ST(0)寄存器中)并弹出。
FLD类似于PUSH指令
FSTP类似于POP指令
FADD类似于ADD指令
算术指令:
FADD-浮点加法;// ST(0)与 ST(1)相加,结果暂存在 ST(1)。然后 ST(0) 弹出堆栈,把加法结果保留在栈顶。
FIADD-将整数添加到浮点
FSUB-减去浮点
FISUB-从浮点数减去整数
FABS-获得绝对值
FIMUL-整数和浮点数相乘
FIDIV-设备整数和浮点
示例
定义printResult.c文件如下:
#include <stdio.h>
extern int printResult(double result);
int printResult(double result) {
printf("Circle radius is - %f\n", result);
return 0;
}
-
-
-
Nasm语法
-
-
定义实现的汇编文件printResult.asm如下:
extern printResult
section .data
radius dq 1.7
result dq 0
SYS_EXIT equ 60
EXIT_CODE equ 0
global _start
section .text
_start:
fld qword [radius];//保存到st(0)
fld qword [radius] ;//保存到st(1)
fmul;//将 ST(O) 与 ST(1) 相乘,乘积暂存于ST(1)。然后 ST(0) 弹出堆栈,将乘积则继续留在栈顶。
fldpi;//装入浮点的pi,就是装入st(1)
fmul;//将ST(O) 与 ST(1) 相乘,保存到st(0)
fstp qword [result];//将结果保存到[result]
mov rax, 0;// 表示xmmN寄存器的数量放入rax寄存器
movq xmm0, [result];//将参数也就是结果放到xmm0.
call printResult
mov rax, SYS_EXIT;退出系统调用的号
mov rdi, EXIT_CODE
syscall
其中DQ作为汇编语言中的伪操作命令,用来定义操作数占用的字节数,占有4个字,即8个字节(64位),可用来存放双精度浮点数。
浮点参数的传参有特殊的寄存器:sse提供的xmm0-xmm15(而不是通常的rdi,rsi……)。需要将xmmN寄存器的数量放入rax寄存器(本例中为0),然后将结果放入xmm0寄存器。
编译如下:
gcc -g -c printResult.c -o c.o
nasm -f elf64 printResult.asm -o printResult.o
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc printResult.o c.o -o printResult
gas语法
定义实现的汇编文件如下:
printResult.s
.extern printResult
.section .data
radius: .double 1.7
result: .double 0
.equ SYS_EXIT , 60
.equ EXIT_CODE, 0
.global _start
.section .text
_start:
leaq radius,%rbx
fldl (%rbx)
fldl (%rbx)
fmulp
fldpi
fmulp
fstl result
movq $0,%rax
movq result, %xmm0
call printResult
movq $SYS_EXIT,%rax
movq $EXIT_CODE,%rdi
syscall
编译如下:
gcc -g -c printResult.c -o c.o
as -g -o printResult.o printResult.s
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc printResult.o c.o -o printResult
tips:在gdb中可以时候用print $st0来打印寄存器中 值。