Как сложить целые числа со знаком в более широкую сумму на 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 не работает идеально.