Assembly YASM(x86_64体系结构)中带符号乘法后带符号除法
我正在为Assembly YASM(x86_64体系结构)中带符号乘法后带符号除法,assembly,x86-64,integer-arithmetic,yasm,Assembly,X86 64,Integer Arithmetic,Yasm,我正在为x86\u 64处理器体系结构使用yasm汇编程序。 假设我已经在.data部分定义了三个数字: section .data ;CONSTANTS: SYSTEM_EXIT equ 60 SUCCESS_EXIT equ 0 ;VARIABLES: dVar1 dd 40400 wVar2 dw -234 bVar3 db -23 dRes dd 0 ;quotient dRem dd 0 ;reminder 我想做的是将
x86\u 64
处理器体系结构使用yasm
汇编程序。
假设我已经在.data
部分定义了三个数字:
section .data
;CONSTANTS:
SYSTEM_EXIT equ 60
SUCCESS_EXIT equ 0
;VARIABLES:
dVar1 dd 40400
wVar2 dw -234
bVar3 db -23
dRes dd 0 ;quotient
dRem dd 0 ;reminder
我想做的是将带符号的双字dVar1与带符号的字dVar2相乘,然后除以带符号的字节bVar3
下面我将介绍我的“解决方案”,以及我为什么要做每一步的参考书。问题在课文的末尾
dVar1*wVar2(签名)
我看不到任何明确的规则,乘法只适用于相同大小的数字。但我们可以看到一些隐含的。这就是为什么我对wVar2
使用转换:
movsx eax, word [wVar2] ;[wVar2] now in eax
现在“它们”的大小相同,所以我只是将它们相乘:
imul dword [dVar1] ;edx:eax = eax * [dVar1]
…例如,将ax(16位)乘以一个字操作数(也是16位)的结果提供了一个双字(32位)结果。但是,结果并不是放在eax(这可能更容易),而是放在两个寄存器中,dx用于高阶结果(16位)和ax用于低阶结果(16位),通常写为dx:ax(按惯例)
正如我正确理解的,现在的结果是edx:eax
edx:eax/bVar3(签名)
…股息需要D寄存器(对于高阶部分)和A(对于低阶部分)。。。如果执行了先前的乘法运算,则可能已经正确设置了D和a寄存器(这是我的案例[OP注释])
及
…此外,A,以及可能的D寄存器,必须组合用于股息
- 字节分割:ax用于16位
- 32位的字除法:dx:ax
- 双字除:edx:eax用于64位(这是我的案例[OP注释])
- 四字除法:rdx:rax用于128位
bVar3
转换为双字,然后将其除以:
movsx ebx, byte [bVar3] ;ebx = [bVar3]
idiv ebx, ;eax = edx:eax / [bVar3]
那么整个代码呢
section .data
;CONSTANTS:
SYSTEM_EXIT equ 60
SUCCESS_EXIT equ 0
;VARIABLES:
dVar1 dd 40400
wVar2 dw -234
bVar3 db -23
dRes dd 0 ;quotient
dRem dd 0 ;reminder
section .text
global _start
_start:
movsx ebx, byte [bVar3] ;conversion to double-word
movsx eax, word [wVar2] ;conversion to double-word
imul dword [dVar1] ;edx:eax = eax * [dVar1]
idiv ebx ;eax = edx:eax / [bVar3], edx = edx:eax % [bVar3]
mov dword [dRes], eax
mov dword [dRem], edx
last:
mov rax, SYSTEM_EXIT
mov rdi, SUCCESS_EXIT
syscall
我使用调试器并看到正确答案:
(gdb) x/dw &dRes
0x600159: 411026
(gdb) x/dw &dRem
0x60015d: -2
但我不确定以下几点
另外,这个问题可能更像是Codese问题。如果你也这么认为,请告诉我 这是“最少可能的行数”解决方案吗 您的代码看起来不错,并且没有任何浪费指令或明显的效率(除了在系统调用中,
mov
到64位寄存器是浪费代码大小)
但在其他两次加载之后执行第二次movsx
。无序执行不会首先分析依赖关系链并在关键路径上加载。第二个movsx
加载不需要,直到imul
结果准备就绪,所以将其放在imul
之后,以便前两个加载(movsx
和imul
内存操作数)可以尽早执行,并让imul
启动
为最低数量的指令(源代码行)优化asm通常没有用处/不重要。选择代码大小(最少的机器代码字节)或性能(最少的UOP、最低的延迟等。请参阅和中的其他链接)。例如,
idiv
是,并且在所有CPU上都比您使用的任何其他指令慢得多
在具有固定宽度指令的体系结构上,指令数是代码大小的代理,但在具有可变长度指令的x86上就是这种情况
无论如何,除非除数是编译时常数:,并且32位操作数大小(带64位被除数)是您可以使用的最小/最快版本,否则没有好的方法可以避免idiv。(与大多数指令不同,div
操作数越窄速度越快)
对于代码大小,您可能希望使用一个RIP相对leardi[rel dVar1]
,然后访问其他变量,如[rdi+4]
,它需要2个字节(modr/m+disp8),而不是5个字节(modr/m+rel32)。i、 e.每个内存操作数多1个字节(与寄存器源相比)
将dword结果位置分配在字和字节位置之前是有意义的,这样所有dword都会自然对齐,并且您不必担心它们在缓存线中被拆分会带来性能损失。(或在db
之后、标签和dd
之前使用align 4
)
这里的一个危险是,如果
(dVar1*wVar2)/bVar3
的商不适合32位寄存器,则64/32=>32位除法可能溢出并出现故障(使用#DE
)。您可以通过使用64位乘法和除法来避免这种情况,如果这是一个问题的话,编译器会这样做。但请注意,在Haswell/Skylake上,64位idiv
比32位idiv
慢约3倍。()
这显然是更大的代码大小(更多的指令,其中更多的指令具有REX前缀以使用64位操作数大小),但不太明显的是,它要慢得多,因为正如我前面所说的,64位idiv
很慢
在具有两个显式操作数的64位imul
之前使用movsxd
在大多数CPU上效果更好,但在一些64位imul
较慢的CPU(AMD推土机系列或Intel Atom)上,您可以使用
movsx eax, byte [bVar3]
imul dword [dVar1] ; result in edx:eax
shl rdx, 32
or rax, rdx ; result in rax
不过,在现代主流CPU上,2操作数imul更快,因为它只需写入一个寄存器
除指令选择外: 将代码放入
.data
部分
movsx eax, byte [bVar3]
imul dword [dVar1] ; result in edx:eax
shl rdx, 32
or rax, rdx ; result in rax
default rel ; RIP-relative addressing is more efficient, but not the default
;; section .text ; already the default section
global _start
_start:
movsx eax, word [wVar2]
imul dword [dVar1] ;edx:eax = eax * [dVar1]
movsx ecx, byte [bVar3]
idiv ecx ;eax = edx:eax / [bVar3], edx = edx:eax % [bVar3]
; leaving the result in registers is as good as memory, IMO
; but presumably your assignment said to store to memory.
mov dword [dRes], eax
mov dword [dRem], edx
.last: ; local label, or don't use a label at all
mov eax, SYS_exit
xor edi, edi ; rdi = SUCCESS_EXIT. don't use mov reg,0
syscall ; sys_exit(0), 64-bit ABI.
section .bss
dRes: resd 1
dRem: resd 1
section .rodata
dVar1: dd 40400
wVar2: dw -234
bVar3: db -23
; doesn't matter what part of the file you put these in.
; use the same names as asm/unistd.h, SYS_xxx
SYS_exit equ 60
SUCCESS_EXIT equ 0