如何在IA32上将符号加总的符号加总。 32位有符号整数的64位和?

时间:2017-11-05 17:54:48

标签: assembly x86 att sign-extension

我试图在程序集32上对符号的整数列表求和,但我只需要对没有符号的整数求和。你知道某种方法吗?

我的程序尝试对整数进行求和并存储在resultado中,其大小为64位,所以为了能够这样做,我使用两个32位寄存器(EAX和EDX),并检查何时总和产生携带。

在所有这些之后,我在resultado上加入EAX和EDX。

# sum.s     Sumar los elementos de una lista.
#           llamando a función, pasando argumentos mediante registros
# retorna:  código retorno 0, comprobar suma en %eax mediante gdb/ddd.
# as --32 -g sum.s -o sum.o
# ld -m elf_i386 sum.o -o sum

# DATA SECTION
.section .data
lista:
    .int 4294967295, 4294967295, 4294967295, 4294967295

longlista:
    .int (.-lista)/4
resultado:
    .quad -1


.section .text
_start: .global _start

    mov $lista, %ebx
    mov longlista, %ecx
    call suma
    mov %eax, resultado
    mov %edx, resultado+4

    mov $1, %eax
    mov $0, %ebx
    int $0x80


suma:
    push %esi
    mov $0, %eax
    mov $0, %edx
    mov $0, %esi

bucle:
    add (%ebx,%esi,4), %eax
    jc .L1

bucle1:
    inc %esi
    cmp %esi,%ecx
    jne bucle
    pop %esi
    ret

.L1:
    inc %edx
    jmp bucle1

这给出了64位和,将输入视为无符号32位,这不是我想要的。

2 个答案:

答案 0 :(得分:2)

使用64位加法的下一个代码将为正数和负数提供正确的总和,而不会因为仅使用32位寄存器而进行任何回绕。
签名结果可能超出范围[-2GB,+ 2GB-1]。

suma:
    push %esi
    push %edi
    xor  %esi, %esi           ;Clear %edi:%esi
    xor  %edi, %edi
    sub  $1, %ecx             ;Start at last element in array
    jl   emptyArray
bucle:
    mov  (%ebx,%ecx,4), %eax  ;From signed 32-bit to signed 64-bit
    cdq
    add  %eax, %esi           ;Add signed 64-bit numbers
    adc  %edx, %edi
    dec  %ecx
    jge  bucle
emptyArray:
    mov  %esi, %eax           ;Move result from %edi:%esi to %edx:%eax
    mov  %edi, %edx
    pop  %edi
    pop  %esi
    ret

添加的顺序并不重要,因此代码从最后一个元素开始向第一个元素开始。

答案 1 :(得分:0)

您当前的代码隐式零扩展。它等同于add (%ebx,%esi,4), %eax / adc $0, %edx,但您需要添加到上半部分的是0或-1,具体取决于低半部分的符号。 (即32位符号位;见Sep的回答)。

32位x86可以直接使用SSE2 / AVX2 / AVX512 paddq进行64位整数运算。 (所有支持64位的CPU都支持SSE2,因此这些天它是合理的基线。)

(或MMX paddq如果您关心通过Pentium III / AMD Athlon-XP的Pentium-MMX。)

SSE4.1使符号扩展到64位便宜。

pmovsxdq  (%ebx),  %xmm1     # load 2x 32-bit (Dword) elements, sign-extending into Qword elements
paddq     %xmm1, %xmm0
add       $8, %ebx

cmp / jb             # loop while %ebx is below an end-pointer.
# preferably unroll by 2 so there's less loop overhead,
# and so it can run at 2 vectors per clock on SnB and Ryzen.  (Multiple shuffle units and load ports)

# horizontal sum
pshufd    $0b11101110, %xmm0, %xmm1    # xmm1 = [ hi | hi ]
paddq     %xmm1, %xmm0                 # xmm0 = [ lo + hi | hi + hi=garbage ]

# extract to integer registers or do a 64-bit store to memory.
movq      %xmm0, (result)

我避免使用索引寻址模式so the load can stay micro-fused with pmovsxdq on Sandybridge。 Nehalem,Haswell或更高版本或AMD上的索引都很好。

不幸的是,没有SSE4.1的CPU 仍在使用中。在这种情况下,您可能只想使用标量,但您可以手动进行签名扩展。

但是没有64位算术右移。 (仅64位元素大小的逻辑移位)。但您可以通过复制并使用32位移位来广播符号位来模拟cdq,然后解压缩。

# prefer running this on aligned memory
# Most CPUs without SSE4.1 have slow movdqu

.loop:
    movdqa    (%ebx, %esi, 1), %xmm1      # 4x 32-bit elements
    movdqa    %xmm1, %xmm2
    psrad     $31, %xmm1                  # xmm1 = high halves (broadcast sign bit to all bits with an arithmetic shift)

    movdqa    %xmm2, %xmm3               # copy low halves again before destroying.
    punpckldq %xmm1, %xmm2                # interleave low 2 elements -> sign-extended 64-bit
    paddq     %xmm2, %xmm0

    punpckhdq %xmm1, %xmm3                # interleave hi  2 elements -> sign-extended 64-bit
    paddq     %xmm3, %xmm0

    add       $16, %esi
    jnc   .loop            # loop upward toward zero, with %ebx pointing to the end of the array.
    #end of one loop iteration, does 16 bytes

(使用两个单独的向量累加器可能比在paddq中使用两个xmm0更好,以保持依赖链更短。)

这是更多的指令,但它每次迭代的次数是两倍。它仍然是每paddq更多的指令,但它可能仍然比标量好,特别是在Broadwell之前的英特尔CPU上adc为2 uop(因为它有3个输入:2个寄存器+ EFLAGS)。

在第一个psrad之前复制%xmm1可能会更好。在movdqa具有非零延迟的CPU上,我想复制然后使用原始文件缩短关键路径,因此乱序执行的隐藏时间更短。

但这意味着最后punpck正在读取2x movdqa个寄存器副本链的结果。 CPUs with mov-elimination that doesn't work 100% of the time (Intel)可能会更糟。它可能需要一个向量ALU来执行复制,因为mov寄存器副本链是mov-elimination无法正常工作的情况之一。