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(对于低阶部分)。。。如果执行了先前的乘法运算,则可能已经正确设置了Da寄存器(这是我的案例[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