Как сложить целые числа со знаком в более широкую сумму на IA32. 64-битная сумма 32-битных подписанных целых?

Я пытаюсь суммировать список целых чисел со знаком на Ассамблее 32, но мне нужно только суммировать целые числа без знака. Вы знаете какой-нибудь способ сделать это?

Моя программа пытается суммировать целые числа и сохранять в resultado, размер которого составляет 64 бита, поэтому для этого я использую два регистра по 32 бита (EAX и EDX) и проверяю, когда сумма производит перенос.

После всего этого я присоединяюсь к EAX и EDX в resultado.

# 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 ответа

Решение

Следующий код, который использует 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

Порядок, в котором сделаны добавления, не имеет значения, и поэтому код начинается с того, что последний элемент работает в направлении первого.

Ваш текущий код неявно расширяется нулями. Это эквивалентно add (%ebx,%esi,4), %eax / adc $0, %edx, но вам нужно добавить в верхнюю половину 0 или -1 в зависимости от знака нижней половины. (т.е. 32 копии знакового бита; см. ответ Сепа).


32-битный x86 может выполнять 64-битное целочисленное вычисление напрямую, используя SSE2/AVX2/AVX512 paddq, (Все 64-битные процессоры поддерживают SSE2, поэтому в настоящее время это разумный базовый уровень).

(Или MMX paddq если вы заботитесь о Pentium-MMX через Pentium III / AMD Athlon-XP).

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)

Я избегал режима индексированной адресации, чтобы нагрузка могла оставаться микросредой с pmovsxdq на песчаном мосту. Индексируется нормально на Nehalem, Haswell или позже, или на AMD.


К сожалению, есть процессоры без SSE4.1, все еще работающие. В этом случае вы можете просто использовать скаляр, но вы можете подписать расширение вручную.

Там нет 64-битного арифметического сдвига вправо, однако. (Только 64-битные логические сдвиги размера элемента). Но вы можете подражать cdq скопировав и используя 32-битный сдвиг для передачи знакового бита, распакуйте его.

# 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, но это, вероятно, все еще лучше, чем скаляр, особенно на процессорах Intel до Broadwell, где adc равен 2 мопам (потому что он имеет 3 входа: 2 регистра + EFLAGS).

Может быть, лучше просто скопировать%xmm1 дважды перед первым psrad, На процессорах где movdqa имеет ненулевую задержку, я хотел скопировать, а затем использовать оригинал, чтобы сократить критический путь, так что выполнение вне порядка имеет меньшую задержку, чтобы скрыть.

Но это означает, что последний punpck читает результат цепочки 2х movdqa зарегистрировать копии. Это может быть хуже на процессорах с устранением mov, которые не работают 100% времени (Intel). Для копирования может понадобиться вектор ALU, потому что цепочка mov копии реестра - это один из случаев, когда mov-elission не работает идеально.

Другие вопросы по тегам