MMX指令集

这篇来介绍intel cpu的高级特性,SIMD-单指令多数据,从名字来看,就是执行一条指令可以计算多个数据。先从最简单的mmx指令集来看,在寄存器那篇已经提 到,mmx有 mm0-mm7 共8个64位寄存器,但是寄存器并非独立寄存器,而是复用了上篇说到的fpu数据堆栈寄存器,也就是说使用mmx指令集会破坏fpu的计算,如果同时使用 着两种特性,一定要注意这点,避免出现莫名的错误。

首先mmx指令集需要cpu的支持,但不是所有cpu都支持,不然也不会称之为高级特性 了,所以在使用之前需要检测,检测指令为cpuid,获得cpu的特性,cpuid虽然只有一条指令,但是其隐含的内容太多,这里仅仅介绍检测SIMD指 令集所需要的部分,其他一些信息可参阅Intel 手册获得。

当eax为1时,cpuid指令返回cpu签名信息,放入ecx和edx寄存器中,相应位为1表示支持。检测SIMD指令集的结果如下:

寄存器特性
EDX 23 支持MMX
EDX 25 支持SSE
EDX 26 支持SSE2
ECX 0 支持SSE3

具体检测代码如下(AT&T 语法):

.section .data
    mmxstring: .asciz "支持mmx指令集
"
    ssestring: .asciz "支持sse指令集
"
    sse2string: .asciz "支持sse2指令集
"
    sse3string: .asciz "支持sse3指令集
"

.section .text
.globl _main
_main:
    movl $1, %eax
    cpuid

mmxop:
    test $0x800000, %edx
    jz sseop
    pushl $mmxstring
    call _printf

sseop:
    test $0x2000000, %edx
    jz sse2op
    pushl $ssestring
    call _printf

sse2op:
    test $0x4000000, %edx
    jz sse3op
    pushl $sse2string
    call _printf

sse3op:
    test $0x01, %ecx
    jz end
    pushl $sse3string
    call _printf
end:
    pushl $0
    call _exit

下面正式开始mmx指令集的介绍,使用mmx需要三个步骤:

  • 从整数值创建打包整数,载入mmx寄存器
  • 使用mmx指令集计算
  • 从mmx获得结果,存入内存

第一个和最后一个步骤比较简单,仅仅是数据移动而已,这里提到打包,因为这里要单指令多数据,所以需要把多数据合成一个操作数进行计算,存入64位的mmx寄存器中,打包的过程就是把 8个字节/4个字/2个双字 合成一个64位数据。

从加减法说起,对于普通数据,如果数据溢出可以置标记位,但是对于多数据的运算,由于同时计算多个加法,就不能单纯的设置标志,对mmx计算有几种情况:

环绕运算        截断其值,丢弃进位
带符号饱和   最大/最小 带符号值
无符号饱和   最大/最小 无符号值

其中饱和运算的预设值根据结果的位数决定,有符号8位最大为127,如果超过127,结果按127计算,其他情况与此类似,这里方便与一些图形处理,比如色彩黑色为0,为无符号最小值,小于其值也按黑色处理。

好 了,到此可以看一下具体的指令,这里的指令有相同的格式,instruction source, destination;其中source可以是mmx寄存器或者64位内存,destination为mmx寄存器。这是AT&T语法,对于 MASM语法源目的操作数相反。

指令说明
paddb 环绕打包字节整数加法
paddw 环绕打包字整数加法
paddd 环绕打包双字整数加法
paddsb 带符号饱和打包字节整数加法
paddsw 带符号饱和打包字整数加法
paddusb 无符号饱和打包字节整数加法
paddusw 无符号饱和打包字整数加法
psubb 环绕打包字节整数减法
psubw 环绕打包字整数减法
psubd 环绕打包双字整数减法
psubsb 带符号饱和打包字节整数减法
psubsw 带符号饱和打包字整数减法
psubusb 无符号饱和打包字节整数减法
psubusw 无符号饱和打包字整数减法

下面以AT&T加法为例进行说明,这里以饱和方式计算4个无符号字之和:

# add four word
# output : result is 18932, 7631, 65535, 510
.section .data
    value1: .short 12300, 2384, 60000, 456
    value2: .short 6632, 5247, 40000, 54

    outstring: .asciz "result is %u, %u, %u, %u
"
.section .text
.globl _main
_main:
    movq value1, %mm0
    movq value2, %mm1
    paddusw %mm1, %mm0
    movq %mm0, value1

    movl $value1, %ebx
    xorl %eax, %eax
    movw 6(%ebx), %ax
    pushl %eax
    movw 4(%ebx), %ax
    pushl %eax
    movw 2(%ebx), %ax
    pushl %eax
    movw (%ebx), %ax
    pushl %eax

    pushl $outstring
    call _printf

    pushl $0
    call _exit

movq 指令把内存中的数据传送至mmx寄存器,如果数据之前在内存中不是连续的,则需要集中存放,即进行打包,之后使用paddusw进行加法计算,输出时 word需转化成dword放入堆栈,可以看到以饱和方式第三个结果为65535,即16位无符号数的最大值。从这里例子可以看出,通过一条指令计算了四 个word整数相加,很大程度上提高了计算的效率,但是同时需要注意,整数的打包以及传送过程也需要耗时,如果打包操作很多,结果不是提高效率而是降低效 率了。

mmx指令集的加法根据需要有饱和方式和环绕方式计算,但对于乘法而言,由于结果的宽度可能是操作数的两倍,所以两种方式看上去都不合适,所以intel提供了两个指令,一个得到计算结果的低字节,另一个得到计算结果的高字节。

指令描述
pmulluw 对无符号16位整数相乘取结果低16位
pmulhuw 对无符号16位整数相乘取结果高16位
pmullw 对有符号16位整数相乘取结果低16位
pmulhw 对有符号16位整数相乘取结果高16位
pmaddwd 对4个带符号整数相乘,高位两个结果相加存入高32位,低位相同

mmx指令集还提供对四字值进行布尔逻辑操作和移位指令:

指令描述
pand 对源和目标操作数按位与操作
pandn 对目标操作数进行按位逻辑非操作,然后对源和目标操作数按位与操作
por 对源和目标操作数按位或操作
pxor 对源和目标操作数按位异或操作
psll 对目标操作数执行逻辑左移操作,使用0填充空位
psra 对目标操作数执行逻辑右移操作,使用0填充空位

其AT&T指令格式如下:

pand source, destination

其中source是mmx寄存器或者64位内存,destination必须是mmx寄存器。移位指令可以使用字,双字或者四字操作数,还有移位的位置数量。MASM格式的源目的操作数相反。

mmx构架提供了用于比较两个值的指令:

指令描述
pcmpeqb 比较打包字节整数值的相等性
pcmpeqw 比较打包字整数值的相等性
pcmpeqd 比较打包双字整数值的相等性
pcmpgtb 判断打包字节整数值是否大于另一个
pcmpgtw 判断打包字整数值是否大于另一个
pcmpgtd 判断打包双字整数值是否大于另一个

因为mmx同时比较多个数据,所以不能设置标志,替换的做法是把判断结果放到目标打包整数值中,如果打包整数值满足对比提交,就把结果设置为全1,否则设置为全0。

由于mmx指令集并非所有cpu都可以支持,所以对c语言这种编译通用性的程序而言,是不会贸然使用mmx指令集的,这也对我们手工汇编优化程序提供了很大的空间,但是需要注意打包整数的效率损耗。

另外,intel除了mmx指令集,另有SIMD指令如sse指令集,将会再下篇详细说明。

转载:http://fancymore.com/reading/assembler-mmx-instruct.html

原文地址:https://www.cnblogs.com/DeeLMind/p/7367775.html