我已经对程序集进行了一些尝试(具体来说是:AT&T,x86-64)。
我的数据节如下所示:
.section .data
num: .short 0b1111111111111111 #16 ones, so the maximum unsigned short value
junk: .quad 0x5555555555555555
在%rax中,我有一个扩展为零的16位值,它与num的值的乘积可以保证最多为32位。我想做一些沿着的事情:imulw (num), %eax
换句话说就是:抓住机会(短)在num中,将其乘以**%ax中的值,然后得到结果-这可能需要多达32位,大小为%eax**-然后,实际上,将其存储在%eax中......我希望使用单个命令完成此操作,而不需要在中间寄存器中存储任何内容。为了将%eax作为我的目标寄存器,我必须使用imull
,但是n
只是一个短整型,这意味着如果我这样做,我将从内存中获取太多字节(特别是,我将在适当命名的“junk”标签后面获取一些0x 55字节)。
使用mul
或imul
的单操作数版本也是不可能的,因为我需要%rax的最终结果。
有没有什么方法可以实现上述目标?
**TL;DR:**是否有一条指令可以将存储在内存中的2字节乘以2字节寄存器,并将结果存储在4字节寄存器中,而不是将其截断?
1条答案
按热度按时间5fjcxozz1#
几乎所有x86指令都要求操作数大小相同,
movzx
/movsx
(AT&Tmovsbl
/movswl
等)、SIMD广播以及某些其他特殊情况除外。x86-64的操作码位已经很拥挤了,没有足够的操作码位来给予每个指令零扩展或符号扩展一个更窄的8位或16位源代码的能力。加上另一个比特来表示源很窄。(或者是一些指令的第二个操作码,以允许窄源格式。)8086使用了大部分的编码空间(可能是1字节操作码),只留下了一小部分空间供将来以简单/合理的方式扩展。
乘法的特殊之处仅在于它提供了一种扩展形式,但当乘积可以放入一个寄存器时,通常并不需要这种扩展形式。正如你所说,
mulw num(%rip)
会执行DX:AX = AX*num
,它具有一个假依赖,写入RDX的低16位,而保留高位不做修改。这也忽略了EAX源代码的高16位,因此它是不等价的。你需要
shl $16, %edx
/mov %ax, %dx
这样的代码来得到EDX中的一个32位值,通过SHL写32位EDX来零扩展到RDX(RAX的高位字节仍然保存着原始的垃圾,所以or %eax, %edx
或or %edx, %eax
是不正确的)。如果你真的想避免零扩展加载,你需要为
num
预留更多的内存,在你想使用的值的部分之后保留2个零字节。注意,紧接着16位存储的32位加载将有额外的延迟a store-forwarding stall。但是如果窄存储有时间提交到缓存,就没有损失。@fuz指出了一个有趣的事实,即传统的x87浮点可以使用窄整数源操作数,如
fimuls num(%rip)
来执行%st(0) *= int16_to_longdouble(num)
(s
用于short
不同于通常的w
用于word
。fimull
使用32位整数源,即使使用x86-64,也没有采用64位整数源的形式。https://www.felixcloutier.com/x86/fmul:fmulp:fimul因此,您必须使用fildq
/fmulp
)