————————————————————–FBLD      
        集把80位打包BCD值加载到FPU寄存器中以及从FPU寄存获取这些值FBSTP  
            同上                          
————————————————————–fbld
source————————————————————–FST  
           
用于获取FPU寄存器堆栈中顶部位置,并且把这个值放到内存位置中FSTS      

  • 浮点数如何存储
  • 浮点寄存器
  • 浮点数指令
  • 浮点计算例子
  • 浮点高级运算
  • CMOV移动指令

本篇介绍sse指令接,sse是流化SIMD扩展(Streaming SIMD Extension,
SSE),提供全新的一组寄存器,处理128位打包数据。

        同上,单精FSTL               同上,双精  

指令               描述FLD1               把+1.0压入FPU堆栈中FLDL2T  
            把10的对数(底数2)压入FPU堆栈中FLDL2E              
把e的对数(底数2)压入FPU堆栈中FLDPI              
把PI值压入FPU堆栈中FLDLG2              
把2的对数(底数10)压入FPU堆栈中FLDLN2              

浮点数如何存储

浮点数的运算完全不同于整数,从寄存器到指令,都有一套独特的处理流程,浮点单元也称作x87
FPU。

现在看浮点数的表示方式,我们所知道的,计算机使用二进制存储数据,所表示的数字都具有确定性,那是如何表示浮点这种具有近似效果的数据呢,答案是通过科学计数,科学计数由符号,尾数和指数表示,这三部分都是一个整数值,具体来看一下IEEE二进制浮点标准:

格式 说明
单精度 32位:符号占1位,指数占8位,尾数中的小数部分占23位
双精度 64位:符号占1位,指数占11位,尾数中的小数部分占52位
扩展精度 80位:符号占1位,指数占16位,尾数中的小数部分占63位

以单精度为例,在内存中的储存格式如下(左边为高位):

 | 1位符号 | 8位指数 | 23位尾数 |

其中符号位1表示负数,0表示正数,这与整数形式的符号位意义相同;
科学计数法表示形式如 m * (b ^
e),m为尾数,b为基数,e是指数,再二进制中,基数毫无疑问是2,对单精度,指数为中间8位二进制表示的数字,其中的尾数是形如1.1101
小数点后面的整数值。

关于指数,由于需要表示正负两种数据,IEEE标准规定单精度指数以127为分割线,实际存储的数据是指数加127所得结果,127为高位为零,后7位为1所得,其他双精度也以此方式计算。

为了解释内存中浮点数的存储方式,举一个浮点数的例子说明:

float test = 123.456;

int main()
{
    return 0;
}

例子再简单不过了,仅仅定义了一个全局的float类型,我们通过gcc -S test.c来生成汇编,看看123.456是如何存储的,打开反汇编后的文件,看到符号_test后定义的数字是 1123477881(这里gcc定义成了long类型,不过没有关系,因为都是四字节数字,具体的类型还得看如何使用)。可以使用计算器把十进制数字转化为二进制:0 10000101 11101101110100101111001,这里根据单精度的划分方式把32位划分成三部分,符号位为0,为正数,指数为
133,减去127得6,尾数加上1.,形式为1.11101101110100101111001,扩大2 ^
23次方为111101101110100101111001,十进制16181625,后除以2 ^ (23 – 6) = 131072,结果为123.45600128173828125,与我们所定义的浮点数正好相符。

sse提供了xmm寄存器,xmm一组8个128位的寄存器,分别名为xmm0-xmm7,sse构架提供对打包单精度浮点数的SIMD支持。

把2的对数(底数e)压入FPU堆栈中FLDZ               把+0.0压入FPU堆栈中

指令               描述MOVAPS              
把4个对位的打包单精度值传送到XMM寄存器或者内存MOVUPS              
把4个不对准的打包单精度值传到XMM寄传器或者内存MOVSS              
把1个单精度值传送到内存或者寄存器的低双字节MOVLPS              
把2个单精度值传送到内存或者寄存器的低四字节MOVHPS              
把2个单精度值传送到内存或者寄存器的高四字节MOVLHPS              
把2个单精度从低四字传到高四字MOVHLPS              
把2个单精度值从高四字传送到低四字MOVSHDUP (SSE3)          
从内存或者XMM寄存器传送128位值,复制2个和第4个32位数据元素MOVSLDUP
(SSE3)          
从内存或者XMM寄存器传送128位值,复制第1个和第3个32位数据元素MOVDDUP  
(SSE3)          

浮点寄存器

这里介绍了浮点数的二进制表示,前面说过浮点单元计算使用独立的寄存器,在寄存器那篇也稍有提及,这里详细说明一下浮点单元的寄存器设施。

FPU有 8 个独立寻址的80位寄存器,名称分别为r0, r1, …,
r7,他们以堆栈形式组织在一起,统称为寄存器栈,编写浮点指令时栈顶也写为st(0),最后一个寄存器写作st(7)。

FPU另有3个16位的寄存器,分别为控制寄存器、状态寄存器、标记寄存器,现一一详细说明此三个寄存器的作用:

状态寄存器,为用户记录浮点计算过程中的状态,其中各位的含义如下:

0 —— 非法操作异常
1 —— 非规格化操作数异常
2 —— 除数为0异常
3 —— 溢出标志异常
4 —— 下溢标志异常
5 —— 精度异常标志
6 —— 堆栈错误
7 —— 错误汇总状态
8 —— 条件代码位0(c0)
9 —— 条件代码位1(c1)
10 —— 条件代码位2 (c2)
11-13 —— 堆栈顶指针
14 —— 条件代码位3(c3)
15 —— 繁忙标志

其中读取状态寄存器内容可使用 fstsw %ax

控制寄存器的位含义如下:

0 —— 非法操作异常掩码 
1 —— 非法格式化异常掩码 
2 —— 除数为0异常掩码 
3 —— 溢出异常掩码 
4 —— 下溢异常掩码 
5 —— 精度异常亚曼 
6-7 —— 保留 
8-9 —— 精度控制(00单精度,01未使用,10双精度,11扩展精度) 
10-11 —— 舍入控制(00舍入到最近,01向下舍入,10向上舍入,11向0舍入) 
12 —— 无穷大控制 
13–15 —— 保留

其中读取控制寄存器和设置控制寄存器的指令如下:

# 加载到内存
fstcw control
# 加载到控制器
fldcw control

最后的标志寄存器最为简单,分别0-15位分别标志r0-r7共8个寄存器,每个寄存器占2位,这两位的含义如下:

11 —— 合法扩展精度 
01 —— 零 
10 —— 特殊浮点 
11 —— 无内容

另外对浮点寄存器的一些控制指令如下:

# 初始化fpu,控制、状态设为默认值,但不改变fpu的数据
finit

# 恢复保存环境
fldenv buffer
fstenv buffer

#清空浮点异常
fnclex

#fpu状态保存
fssave

fstenv
保存控制寄存器、状态寄存器、标记寄存器、FPU指令指针偏移量、FPU数据指针,FPU最后执行的操作码到内存中。

sse提供了两个版本的指令,其一以后缀ps结尾,这组指令对打包单精度浮点值执行类似mmx操作运算,而第二种后缀ss,这些指令对一个量标单精度浮点
值进行运算操作,这些指令不对打包值中的所有浮点值操作,而只对打包值中的低位双字节执行操作,源操作数中剩余的3个值直接传送给结果。

从内存或者XMM寄存器传送64位双精度浮点值,把它复制到128位XMM寄存器中

指令               转换CVTDQ2PD          
打包双字整数到打包双精度FP(XMM)CVTDQ2PS          
打包双字整数到打包单精度FP(XMM)CVTPD2DQ          
打包双精度FP到打包双字整数(XMM)CVTPD2PI          
打包双精度FP到打包双字整数(XMM)CVTPD2PS          
打包双精度FP到打包单精度FP(XMM)CVTPI2PD          
打包双字整数到打包双精度FP(XMM)CVTPI2PS          
打包双字整数到打包单精度FP(XMM)CVTPS2DQ          
打包单精度FP到打包双字速度(XMM)CVTPS2PD          
打包单精度FP到打包双精度FP(XMM)CVTPS2PI          
打包双精度FP到打包双字整数(XMM)CVTTPD2PI          
打包双精度FP到打包双字整数(XMM,截断)CVTTPD2DQ          
打包双精度FP到打包双字整数(XMM,截断)CVTTPS2DQ          
打包单精度FP到打包双字整数(XMM,截断)CVTTPS2PI          
打包单精度FP到打包双字整数(XMM,截
断)—————————————————————————————————————————-ADD  
(x)           用于把两个整数相加
————————————————————–add
source,
destination————————————————————-ADC  
(x)           带符号或者无符号整数,              
可以把值分割为多个双字数据元素并且对每个元素执行加法操作
————————————————————-adc source,
destination————————————————————–SUB  
(x)           减法的基本形式SBB   (X)          
带借位减法指令————————————————————–sub
source,
destination————————————————————–MUL  
(x)           无符号乘法IMUL   (x)          
带符号乘法————————————————————–mul
sourceimul multiplier, source,
destination————————————————————–DIV  
(x)           无符号除法IDIV   (x)          
带符号除法————————————————————–div
divisoridiv
divisor————————————————————–SAL  
            向左算述移位SHL               向左逻辑移位SAR              
移位除法 (符号操作GOOD)SHR               移位除法 (小心位清0和正负
数)—————————————————————————————————————————-
指令               描述ROL               向左循环移位ROR              
向右循环位移RCL               向左循环移位,并且包含进位标志RCR      

浮点数指令

接下来将要详细说明其计算过程,要计算数据首先得看如何从内存中加载数据到寄存器,同时把结果从寄存器取出到内存,除了加载内存中的浮点数据指令,另外还有一些常量的加载,现列举如下:

指令 说明
finit 初始化控制和状态寄存器,不改变fpu数据寄存器
fstcw control 将控制寄存器内容放到内存control处
fstsw status 将状态寄存器内容放到内存status处
flds value 加载内存中的单精浮点到fpu寄存器堆栈
fldl value 加载内存中的双精浮点到fpu寄存器堆栈
fldt value 加载内存中的扩展精度点到fpu寄存器堆栈
fld %st(i) 将%st(i)寄存器数据压入fpu寄存器堆栈
fsts value 单精度数据保存到value,不出栈
fstl value 双精度数据保存到value,不出栈
fstt value 扩展精度数据保存到value,不出栈
fstps value 单精度数据保存到value,出栈
fstpl value 双精度数据保存到value,出栈
fstpt value 扩展精度数据保存到value,出栈
fxch %st(i) 交换%st(0)和%st(i)
fld1 把 +1.0 压入 FPU 堆栈中
fldl2t 把 10 的对数(底数2)压入 FPU 堆栈中
fldl2e 把 e 的对数(底数2)压入 FPU 堆栈中
fldpi 把 pi 的值压入 FPU 堆栈中
fldlg2 把 2 的对数(底数10)压入 FPU 堆栈中
fldln2 把 2 的对数(底数e) 压入堆栈中
fldz 把 +0.0 压入压入堆栈中

以上指令虽多,但是还是很有规律,前缀f表示fpu操作,ld加载,st保存设置,p后缀弹出堆栈,s、l、t后缀表示单精度,双精度,扩展精度,c后缀表
示控制寄存器,s后缀表示状态寄存器。当然这仅仅是对AT&T语法而言,对MASM语法没有s,l,t之分,需要使用type
ptr来指明精度,即内存大小。

学会灵活的加载弹出数据堆栈后,接下来就要看一些基本的计算:

fadd    浮点加法
fdiv    浮点除法
fdivr   反向浮点除法
fmul    浮点乘法
fsub    浮点减法
fsubr   反向浮点减法

对于以上的每种指令,有几种指令格式,以fadd为例,列举如下:

# 内从中的32位或者64位值和%st(0)相加
fadd source

# 把%st(x)和%st(0)相加,结果存入%st(0)
fadd %st(x), %st(0)

# 把%st(0)和%st(x)相加,结果存入%st(x)
fadd %st(0), %st(x)

# 把%st(0)和%st(x)相加,结果存入%st(x),弹出%st(0)
faddp %st(0), %st(x)

# 把%st(0)和%st(1)相加,结果存入%st(1),弹出%st(0)
faddp

# 把16位或32位整数与%st(0)相加,结果存入%st(0)
fiadd source

这仅仅是对AT&T语法而言,对MASM源操作数与目的操作数相反!另外,对AT&T,与内存相关指令可加s、l指定内存精度。其中反向加法和反向除法是计算过程中目的与源反向计算。

指令 说明
movaps 把4个对准的单精度值传送到xmm寄存器或者内存
movups 把4个不对准的单精度值传送到xmm寄存器或者内存
movss 把1个单精度值传送到内存或者寄存器的低位双字
movlps 把2个单精度值传送到内存或者寄存器的低四字
movhps 把2个单精度值传送到内存或者寄存器的高四字
movlhps 把2个单精度值从低四字传送到高四字
movhlps 把2个单精度值从高四字传送到低四字

        向右循环移位,并且包含进拉标志

指令               描述AAA               调整加法操作的结果AAS          
    调整减法操作的结果AAM               调整乘法作的结果AAD          
    准备除法操作的被除数
—————————————————————————————————————————-AND  
            位与NOT               位非OR               位或XOR          
    异域TEST              
检查EFLAGS寄存器,在8位、16位、32位值之间执行接位逻辑AND操作,          
    并且相应地设置符号、零和奇偶校验标志,而且不修改目标值              
————————————————————–AND、OR、XOR
指令格式and source,
destination————————————————————–FSTSW  
            把状态寄存器读取到一个双字内存位置或者AX寄存器中FSTCW      
        控制寄存器的设置加载到双字内存位置中查看设置内容FLDCW          
    把双字内存值加载到控制寄存器中FILDS              
把一个双字整数值加载到FPU寄存器堆栈中FISTS              
获得寄存器堆栈顶部的值FIST               助记符指定了S字符FLDS/FLDL  
        加载内存中的单精度/双精度值FST/FSTP          
用于把ST0寄存器数据传送到另一个FPU寄存器或者数据从FPU寄存器传送到内存位置FXCH  

浮点计算例子

接下来举一个AT&T语法的例子,来计算表达式的值 ( 12.34 * 13 ) + 334.75 )
/ 17.8 :

# ( 12.34 * 13 ) + 334.75 ) / 17.8
.section .data
    values: .float 12.34, 13, 334.75, 17.8
    result: .double 0.0

    outstring: .asciz "result is %fn"
.section .text
.globl _main
_main:
    leal values, %ebx
    flds 12(%ebx)
    flds 8(%ebx)
    flds 4(%ebx)
    flds (%ebx)

    fmulp
    faddp
    fdivp %st(0), %st(1)

    fstl result

    leal result, %ebx
    pushl 4(%ebx)
    pushl (%ebx)
    pushl $outstring
    call _printf
end:
    pushl $0
    call _exit

前四个flds加载所有的数据到寄存器堆栈,可以单步运行并是用gdb的print
$st0打印堆栈寄存器的值,可以看到为什么是堆栈寄存器。需要说明的是由于printf的%f是double类型的输出,所以最后要把一个8字节浮点放
到栈中传递,最终结果为27.818541,可以看到与计算器计算的结果近似相等。

其中对准操作movaps要求数据在内存中对准16字节的边界,以提交效率,否则应使用movups传送数据。

            交换ST0寄存器和另一个FPU寄存器的值

指令               描述FADD/FADDS           浮点加法/32位FDIV          
    浮点除法FDIVR               反向浮点除法FMUL              

浮点高级运算

除了基本的浮点计算,x87还提供了一些诸如余弦运算等高级计算功能:

指令 说明
f2xm1 计算2的乘方(次数为st0中的值,减去1
fabs 计算st0中的绝对值
fchs 改变st0中的值的符号
fcos 计算st0中的值的余弦
fpatan 计算st0中的值的部分反正切
fprem 计算st0中的值除以st1的值的部分余数
fprem1 计算st0中的值除以st1的值的IEEE部分余弦
fptan 计算st0中的值的部分正切
frndint 把st0中的值舍入到最近的整数
fscale 计算st0乘以2的st1次方
fsin 计算st0中的值的正弦
fsincos 计算st0中的值的正弦和余弦
fsqrt 计算st0中的值的平方根
fyl2x 计算st1*log st0 以2为底
fyl2xp1 计算st1*log (st0 + 1) 以2为底

下面来看一下浮点条件分支,浮点数的比较不像整数,可以容易的使用cmp指令比较,判断eflags的值,关于浮点数比较,fpu提供独立的比较机制和指令,现对这组比较指令进行说明:

指令 说明
fcom 比较st0和st1寄存器的值
fcom %st(x) 比较st0和stx寄存器的值
fcom source 比较st0和32/64位内存值
fcomp 比较st0和st1寄存器的值,并弹出堆栈
fcomp %st(x) 比较st0和stx寄存器的值,并弹出堆栈
fcomp source 比较st0和32/64位内存值,并弹出堆栈
fcompp 比较st0和st1寄存器的值,并两次弹出堆栈
ftst 比较st0和0.0

浮点数比较的结果放入状态寄存器的c0,c2,c3条件代码位中,其值如下:

结果 c3 c2 c0
st0 > source 0 0 0
st0 < source 0 0 1
st0 = source 1 0 0

如此倘若直接判断c0,c2,c3的值比较繁琐,所以可以使用一些技巧,首先使用fstsw指令获得fpu状态寄存器的值并存入ax,再使用sahf指令把
ah寄存器中的值加载到eflags寄存器中,sahf指令把ah寄存器的第0、2、4、6、7分别传送至进位、奇偶、对准、零、符号位,不影响其他标
志,ah寄存器中这些位刚好包含fpu状态寄存器的条件代码值,所以通过fstsw和sahf指令组合,可以传送如下值:

把c0位传送到eflags的进位标志 
把c2位传送到eflags的奇偶校验标志 
把c3位传送到eflags的零标志

传送完毕后,可以用条件跳转使用不同的结果值,另外需要说明的是浮点数相等判断,因为浮点数本身存储结构决定了它仅仅是一个近似值,所以不能直接判断是否相
等,这样可能与自己预期的结果不同,应该判断两个浮点数之差是否在一个很小的误差范围内,来决定这两个浮点数是否相等。

根据上面的技巧,使用fstsw和fpu指令组合,可以方便的使用浮点判断结果,这对我们是一种便利,而intel的工程师又为我们设计了一个组合指令,fcomi指令执行浮点比较结果并把结果存放到eflags寄存器的进位,奇偶,和零标志。

指令 说明
fcomi 比较st0和stx寄存器的值
fcomip 比较st0和stx寄存器,并弹出堆栈
fucomi 比较之前检查无序值
fucomip 比较之前检查无序值,之后弹出堆栈

判断结束后eflags的标志设置如下:

结果 ZF PF CF
st0 > st(x) 0 0 0
st0 < st(x) 0 0 1
st0 = st(x) 1 0 0

运算指令:

浮点乘法FSUB               浮点减法FSUBR               反向浮点减法

指令               描述F2XMI              
计算2的乘方(次数为ST0中的值)减1FABS              
计算ST0中的值的绝对值FCHS               改变ST0中的值的符号FCOS      
        计算ST0中的值的余弦FPATAN              
计算ST0中的值的部分反正切FPREM              
计算ST0中的值除以ST1中的值部分余数FPREM1              
计算ST0中的值除以ST1中的值的IEEE部分余数FPTAN              
计算ST0中的值部分正切FRNDINT              
把ST0中的值舍入到最近的整数FSCALE              
把计算ST0乘以2的ST1次乘方FSIN               计算ST0中的值的正弦FSINCOS  
            计算ST0中的值的正弦和余弦FSQRT              
计算ST0中的值的平方根FYL2X/FYL2X1           计算ST1*log
ST0(以2为基数)FYL2XPI              
计算ST1*log(ST0+1)(以2为基数)FSAVE              
把所有FPU寄存器复制到一个108字节的内存位置              
,然后初始化FPU状态FRSTOR              

CMOV移动指令

最后介绍的是类似cmov的指令,根据判断结果决定是否需要移动数据,其AT&T格式为
fcmovxx source,
destination,其中source是st(x)寄存器,destination是st(0)寄存器。

指令 说明
fcmovb 如果st(0)小于st(x),则进行传送
fcmove 如果st(0)等于st(x),则进行传送
fcmovbe 如果st(0)小于或等于st(x),则进行传送
fcmovu 如果st(0)无序,则进行传送
fcmovnb 如果st(0)不小于st(x),则进行传送
fcmovne 如果st(0)不等于st(x),则进行传送
fcmovnbe 如果st(0)不小于或等于st(x),则进行传送
fcmovnu 如果st(0)非无序,则进行传送

以上可以看出,无论从寄存器的操作,还是计算过程,都比整数运算要繁琐的多,而且看似很简单的一个表达式,转化成浮点汇编需要做很多工作,由于其复杂性,同
一个表达式可以有多种运算过程,当然其中的效率相差很大,这依赖于对浮点汇编的理解程度,好在有高级语言处理相关工作,编写浮点指令的情况比较少见。

指令 说明
addps 将两个打包值相加
subps 将两个打包值相减
mulps 将两个打包值相乘
divps 将两个打包值相除
rcpps 计算打包值的倒数
sqrtps 计算打包值的平方根
rsqrtps 计算打包值的平方根倒数
maxps 计算两个打包值中的最大值
minps 计算两个打包值中的最小值
andps 计算两个打包值的按位逻辑与
andnps 计算两个打包值的按位逻辑非
orps 计算两个打包值的按位逻辑或
xorps 计算两个打包值的按位逻辑异或

恢复FPU时,所有FPU寄存器(包括数据寄存器)都被复为执行FSAVE指令时的状态

指令               描述FNCLEX               清空浮点异常标志FNSAVE      
        把FPU状态保存到内存中FNSTCW              
保存FPU控制寄存器FNSTENV               把FPU操作环境保存到内存中FSTSW  
            把FPU状态寄存器保存或者保存在AX寄存器中SAHF              
把AH寄存器的第0,2,4,6和7位分别传送到进位、奇偶校验、对准、零和符号标志,  
            不影响EFLAGS寄存器中的其它位FLDENV              
把内存块的值加载回FPU环境中
—————————————————————————————————————————-MOVSB  
            传送单一字节MOVSW               传送一个字(2字节)MOVSL      
        传送一个双字(4字
节)—————————————————————————————————————————-STD  
            用于设置DF标志CLD               将DF标志清零
—————————————————————————————————————————-REPE  
            等于时重复REPNE               不等于时重复REPNZ          
    不为零时重复REPZ               为零时重复
—————————————————————————————————————————-LODSB  
            把一个字节加载到AL寄存器中LODSW              
把一个字(2字节)加载到AX寄存器中LODSL              
把一个双字(4字节)加载到EAX寄存器中
—————————————————————————————————————————-STOSB  
            存储AL寄存器中一个字节数据STOSW              
在座AX寄存器中一个字(2字节)的数据STOSL              
在座EAX寄存器中一个双字(4个字节)的数据
—————————————————————————————————————————-FCOM  
            比较ST0寄存器和ST1寄存器FCOM ST(x)          
比较ST0寄存器和另一个FPU 寄存器FCOM source          
比较ST0寄存器和32位或者64位的内存值FCOMP              
比较ST0寄存器和ST1寄存器,并且弹出堆栈FCOMP ST(x)          
比较ST0寄存器和另一个FPU寄存器,并且弹出堆栈FCOMP source          
比较ST0寄存器和32位或者64位的内存值,并且弹出堆栈FCOMPP              
比较ST0寄存器和ST1寄存器,并且两次弹出堆栈FTST              
比较ST0寄存器和值
0.0—————————————————————————————————————————-FCOMI  
            比较ST0寄存器和ST(x)寄存器FCOMIP              
比较ST0寄存器和ST(x)寄存器,并且弹出堆栈FUCOMI              
在比较之前检查无序值FUCOMIP              
在比较之前检查无序值,并且在比较之后弹出堆栈
—————————————————————————————————————————-FCMVB  
            如果ST(0)小于ST(x),则进行传送FCMOVE              
如果ST(0)等于ST(x),则进行传送FCMOVBE              
如果ST(0)小于或者等于ST(x),则进传送FCMOVU              
如果ST(0)无序,则进行传送FCMOVNB              
如果ST(0)不小于ST(x),则进行传送FCMOVNE              
如果ST(0)不等于ST(x),则进行传送FCMOVNBE          
如果ST(0)不小于或者等于ST(x),则进行传送FCMOVNU              
如果ST(0)非无序,则进行传送
————————————————————–fcmovxx
source,
destination————————————————————–CMPSB  
            比较字节值CMPSW               比较字(2字节)值CMPSL          
    比较双字(4字节)值REP              
跨越多个字节重复地进行字符串比较REPE/REPZ          
扫描字符串的字符,查找不匹配搜索字符的字符REPNE/REPNZ          
扫描字符串的字符,查找匹配搜索字符的字符
—————————————————————————————————————————-SCASB  
            比较内存中的一个字节和AL寄存器的值SCASW              
比较内存的一个和AX寄存器的值SCAL              
比较内存中的一个双字和EAX寄存器的值
—————————————————————————————————————————-FINIT  

以上指令都是用两个操作数:源操作数可以是128位内存或者xmm寄存器,目标操作数必须是xmm寄存器。

            初始化FPUFSTP (x)           把这个双精度浮点值压入程序堆栈

这里举一个简单的例子,使用gdb查看最后结果:

.section .data
    value1: .float 12.12, 34.89, 56.23, 78.45
    value2: .float 31.12, 57.124, 234.23, 67.246
.section .text
.globl _main
_main:
    enter $0, $0

    movups value1, %xmm0
    movups value2, %xmm1
    addps %xmm0, %xmm1

    movups value2, %xmm1
    maxps %xmm0, %xmm1
    leave
    ret

编译时加-g参数加入调试信息,调用addps后查看xmm1寄存器的结果,命令如下:

(gdb) print $xmm1
$1 = {v4_float = {43.2400017, 92.0139999, 290.459991, 145.695999},
  v2_double = {26419069594869.762, 1245245520236216.2}, v16_int8 = {-61, -11,
    44, 66, 43, 7, -72, 66, -31, 58, -111, 67, 45, -78, 17, 67}, v8_int16 = {
    -2621, 16940, 1835, 17080, 15073, 17297, -19923, 17169}, v4_int32 = {
    1110242755, 1119356715, 1133591265, 1125233197}, v2_int64 = {
    4807600484593235395, 4832839782622116577},
  uint128 = 0x4311b22d43913ae142b8072b422cf5c3}
(gdb)

可以看到,调用加法指令之后,四组和都存储在xmm1寄存器中,gdb查看时由于不知道如何解析xmm1寄存器的内容,因为可能是单精度,也可能是双精度或者不同宽度的整数,所以只能按不同的解析方式全部显示,查看v4_float即四个单精度浮点数的显示。

下面介绍一下sse构架下的比较指令,sse的比较指令单独比较128位打包单精度浮点的每个元素,结果是一个掩码,满足比较条件的结果全为1值,不满足结果的全为0值(量标只对最低的双字执行)。

指令 说明
cmpps 比较打包值
cmpss 比较标量值
comiss 比较标量值并且设置eflags寄存器
ucomiss 比较标量值(包括非法值)并设置eflags寄存器

看到这里,仅仅有一个比较指令,并没有说明大小,何为满足条件全1,不满足全0呢,这样说一下指令的使用:

cmpps imp, source, destination

其中多出来的imp是一个无符号整数,这个整数表示的含义就是条件,这个条件值如下表所示:

整数 说明
0 等于
1 小于
2 小于或等于
3 无序
4 不等于
5 不小于
6 不小于或等于
7 有序

如果需要比较两个数是否相等,传imp为0即可作为条件,满足条件结果全1,这是sse的比较方式。这里说明一下条件中的无序,因为是浮点比较,寄存器或内存中的有些值并不符合规定的浮点存储格式,相互比较是没有意义的,称为无序。

除了对浮点数的支持,sse指令集也有指令对mmx提供的功能进行扩展,他们对mmx寄存器中的数据执行操作:

指令 说明
pavgb 计算打包无符号字节整数的平均值
pavgw 计算打包无符号字整数的平均值
pextrw 把一个字从mmx寄存器复制到通用寄存器
pinsrw 把一个字从通用寄存器复制到mmx寄存器
pmaxub 计算打包无符号字节整数的最大值
pmaxsw 计算打包有符号字整数的最大值
pminub 计算打包无符号字节整数的最小值
pminsw 计算打包有符号字整数的最小值
pmulhuw 将打包无符号字整数相乘并且存储高位结果
psadbw 计算无符号字节整数的绝对差的总和

SSE2 指令集又对 SSE
指令集做了很多扩充,主要对操作双精度浮点数和128位打包整数值执行数学操作,下面介绍SSE2的使用,先来看数据传送指令:

指令 说明
movapd 把2个对准的双精度值传送到xmm寄存器或者内存
movupd 把2个不对准的双精度值传送到xmm寄存器或者内存
movdqa 把2个对准的四字节整数传送到xmm寄存器或者内存
movdqu 把2个不对准的四字节整数传送到xmm寄存器或者内存
movsd 把1个双精度值传送到内存或者寄存器的低四字
movhpd 把1个双精度值传送到内存或者寄存器的高四字
movlpd 把1个双精度值传送到内存或者寄存器的低四字

SSE2指令集提供处理打包双精度浮点数,打包字整数,打包双字整数和打包四字整数值的数学指令,这里列举SSE2的加法指令来说明这一系列指令格式:

指令 说明
addpd 将打包双精度浮点值相加
addsd 将量标双精度浮点值相加
paddsb 将打包带符号字节整数相加
paddsw 将打包带符号字整数相加
paddd 将打包带符号双字整数相加
paddq 将打包带符号四字整数相加

这里虽然只列举add系列指令,这些选项也存在于乘法和除法操作中(mulpd,
mulsd, divpd, divsd等)。
另外同sse指令集,sse2指令集也提供专门的数学操作,sqrt, max, min。

最后我们来看SSE3指令集,SSE3构架并没有提供任何新的数据类型,仅仅添加了几条指令,用于更快的执行标准函数,下面是新指令的列表:

指令 说明
fisttp 把第一个fpu寄存器的值转换为整数(舍入)并且从fpu堆栈弹出
lddqu 快速从内存加载128位不对准的数据值
movshdup 传送128位值,复制第2个和第4个32位数据元素
movsldup 传送128位值,复制第1个和第3个32位数据元素
movddup 传送64位值,赋值值,使之成为128位值
addsubps 对于打包单精度浮点数,对第2个和第4个32位执行加法,第1和第3个32位执行减法
addsubpd 对于打包单精度浮点数,对第2对64位值执行加法,第1对位执行减法
haddps 对操作数的相邻的元素执行单精度浮点加法操作
haddpd 对操作数的相邻的元素执行双精度浮点加法操作
hsubps 对操作数的相邻的元素执行单精度浮点减法操作
hsubpd 对操作数的相邻的元素执行双精度浮点减法操作

SSE指令繁多,这里举得例子却很少,以后我会在此文继续附加一些说明例子,方便理解。

转载: