Массовая сборка 8086 переносит флаг между добавлением слова данных
Итак, у меня есть эта проблема, которую я должен решить, и я потратил часы, пытаясь найти лучший способ сделать это, Google не очень помог.
Проблема заключается в создании подпрограммы, которой присваивается список слов, который вы затем добавляете вместе с другим списком, который становится выходным. В основном это метод работы с большими числами.
Мой код прекрасно работает для переноса флагов в словах, но для переноса флага из одного полного слова в другое он не работает. Первое 16-битное слово (0005 в приведенном ниже примере) - это флаг, используемый, чтобы указать моей подпрограмме, сколько существует слов.
Например, учитывая следующий вход,
//si 0005 0000 EEEE DDDD CCCC BBBB
//di 0005 0000 1111 2222 3333 4445
когда ожидаемый результат:
0005 0001 0000 0000 0000 0000
Мой код выдает:
0005 0000 FFFF FFFF FFFF 0000
Я верю, что понимаю, почему это происходит по большей части, но я не уверен, как лучше решить эту проблему. Мне нужен недорогой метод переноса 1 между разными порциями данных.
;---------------------------------------
; ADD Subroutine
;---------------------------------------
.data
bxx dw 0000h ;
cxx dw 0000h ;
.code
;---------------------------------------
addx: ;
mov bxx, bx ;save incoming register
mov cxx, cx ;save incoming register
mov bx, si ;move n to bl - acts as a cursor
loopAdd: ;loop point
mov cx, [si+bx] ;move word at point si+bx into register cx
ADC [di+bx], cx ;add with carry
sub bx, 0002h; ;decrement cursor by a full word
cmp bx, 0000h ;bx == 0?
jg loopAdd ;no? jump to loop point
end: ;
mov bx, bxx ;return register to original state
mov cx, cxx ;return register to original state
ret ;return
;---------------------------------------
3 ответа
Вы должны сохранить флаг переноса из предыдущей итерации.
Попробуй это:
;---------------------------------------
; ADD Subroutine
;---------------------------------------
.data
bxx dw 0000h ;
cxx dw 0000h ;
.code
;---------------------------------------
addx: ;
mov bxx, bx ;save incoming register
mov cxx, cx ;save incoming register
mov bx, si ;move n to bl - acts as a cursor
clc ;clear carry flag
pushf ;save flag register
loopAdd: ;loop point
mov cx, [si+bx] ;move word at point si+bx into register cx
popf ;restore saved flag register
ADC [di+bx], cx ;add with carry
pushf ;save flag register
sub bx, 0002h; ;decrement cursor by a full word
jg loopAdd ;if the result is positive, jump to loop point
end: ;
popf ;remove saved flag register from the stack
mov bx, bxx ;return register to original state
mov cx, cxx ;return register to original state
ret ;return
;---------------------------------------
Обратите внимание, что cmp bx, 0000h
не нужно, потому что cmp
является sub
кроме cmp
изменяйте только флаги и не сохраняйте вычисленное значение, поэтому вы можете проверить результат sub
непосредственно.
Если вы хотите быстрое многоточное сложение, используйте 64-битный код, если это возможно. Выполнение в 4 раза больше ширины с каждой инструкцией дает ускорение в 4 раза. На 386-совместимых процессорах вы можете использовать 32-битные инструкции и регистры даже в 16-битном режиме, что даст вам двукратное ускорение.
Чтобы сделать предложение Иры развернуть шаг вперед
- сокращение накладных расходов цикла на один
lea
- избегая
adc
с назначением памяти, что медленно на Intel.
... set up for the unrolled loop, with Ira's setup code
; di = dst pointer
; bx = src-dst, so bx+di = src
add_8words: ; carry bit has value to propagate
;sahf ; another way to avoid a partial-flag stall while looping
mov ax, 0[bx+di]
adc ax, 0[di]
mov 0[di], ax
mov ax, -1[bx+di]
adc ax, -1[di]
mov -1[di], ax
mov ax, -2[bx+di]
adc ax, -2[di]
mov -2[di], ax
mov ax, -3[bx+di]
adc ax, -3[di]
mov -3[di], ax
mov ax, -4[bx+di]
adc ax, -4[di]
mov -4[di], ax
mov ax, -5[bx+di]
adc ax, -5[di]
mov -5[di], ax
mov ax, -6[bx+di]
adc ax, -6[di]
mov -6[di], ax
mov ax, -7[bx+di]
adc ax, -7[di]
mov -7[di], ax
lea di, -8[di]
; lahf
; put the flag-setting insn next to the branch for potential macro-fusion
dec cx ; causes a partial-flag stall, but only once per 8 adc
jne add_8word
Это должно работать почти на одном adc
за такт (минус издержки цикла) на Intel Broadwell и AMD K8 через Bulldozer. (Я забываю, может ли k8/k10 выполнять две загрузки за такт. Это будет узким местом, если не сможет). С помощью adc
в соответствии с таблицами Агнера Фога, у Intel не очень хорошая память, а у AMD - хорошо. Intel Haswell и более ранние версии будут ограничены задержкой 2c adc
, (Бродвелл сделал adc
а также cmov
инструкции с одним мопом, использующие поддержку 3-зависимых мопов, добавленную в Haswell, чтобы FMA могла быть единственной командой моп).
loop
Insn может быть победой на старых процессорах, где частичная остановка действительно плохая, но другие способы избежать остановки частичной пометки могут быть даже лучше, чем медленная loop
инструкция
Использование трюка dest-source уменьшает издержки цикла при уменьшении указателя. 2-регистровые режимы адресации в нагрузках не нуждаются в микроплавкости, потому что mov
нагрузка в любом случае одна. в магазинах действительно необходим микроплавкий предохранитель, поэтому вы должны предпочесть режимы регистрации с одним регистром для магазинов. Дополнительный блок адресации памяти Haswell на порту 7 может обрабатывать только простые режимы адресации, а режимы 2-регистровой адресации не могут микроплавиться.
См. Также Проблемы с ADC/SBB и INC/DEC в тесных циклах на некоторых процессорах для получения информации о многоточечных циклах АЦП и некоторых экспериментах на процессорах Core2 и SnB для производительности с частичным остановом.
Другой способ зацикливаться здесь будет lea si, -1[si]
/ mov cx, si
/ jcxz
, 16-битный отстой, и вы не можете использовать [cx]
в эффективный адрес.
ОП говорит, что хочет недорогое решение, чтобы сохранить перенос между итерациями. У @MikeCAT было решение; @PeterCordes предложил некоторые улучшения.
Есть еще одно действительно приятное улучшение, которое вы можете получить, выполняя арифметику с несколькими точностями, при условии, что ваше значение многозначности является "большим" (содержит много значений слов), и это разворачивает внутренний цикл N раз, избегая повреждения счетного цикла / повреждения флага переноса внутри развернутый раздел. (Если ваша мультиточная арифметика не очень много, вам не нужно много оптимизации).
Я пересмотрел ответ @ MikeCAT здесь, предполагая, что развертывание должно быть 8 итераций.
Код состоит из 3 частей: определение того, как обрабатывать фрагмент из 8 слов, обработка фрагмента развернутым способом, а затем эффективная обработка нескольких фрагментов из 8 слов в основном развернутом цикле.
Для примера OP из 5 слов эта процедура никогда не попадает в полный развернутый цикл. Для больших значений из нескольких слов это действительно так, и я ожидаю, что эта процедура, вероятно, будет довольно быстрой.
[Следующий код не проверен.]
;---------------------------------------
; ADD Subroutine
; si = pointer to count word of 1st multiprecision value
; di = pointer to count word of 2nd multiprecision value
; assert: si->count == di ->count
; preserves si, di; exits with carry from addition
;---------------------------------------
sizeofword equ 2
;---------------------------------------
add_multiple: ; destroys ax, si, di
push cx ;save incoming register
mov cx, [si]
lea si, sizeofword[si+cx] ; find least significant word
lea di, sizeofword[di+cx]
; determine entry point into unrolled loop by taking counter modulo 8
mov cx, si ;move n to bl - acts as a cursor
shr cl, 1
jc add_xx1
je done ; edge case: 0 words in value
add_xx0:
shr cl, 1
jc add_x10
add_x00:
shr cl, 1
jnc add_000 ; note carry flag is clear
; clc
; jmp add_100
mov ax, 0[si]
add 0[di], ax ; do 1st add without carry
lea si, -1[si]
lea di, -1[di]
jmp add_011
add_x10:
shr cl, 1
jnc add_010
; clc
; jmp add_110
mov ax, 0[si]
add 0[di], ax
lea si, -1[si]
lea di, -1[di]
jmp add_101
add_x01:
shr cl, 1
jnc add_001
; clc
; jmp add_101
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
jmp add_100
add_xx1:
shr cl, 1
jnc add_x01
add_x11:
shr cl, 1
jnc add_011
; clc
; jmp add_111
; the following code adds a fragment of an 8 word block
add_111: ; carry bit has value to propagate
mov ax, 0[si]
; adc 0[di], ax
add 0[di], ax ; no carry in on 1st add
lea si, -1[si]
lea di, -1[di]
add_110:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_101:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_100:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_011:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_010:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_001:
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
add_000:
mov ax, 0[si]
adc 0[di], ax
dec cx ; does not disturb carry
lea si, -1[si]
lea di, -1[di]
je done
; unrolled loop here; very low overhead
add_8words: ; carry bit has value to propagate
mov ax, 0[si]
adc 0[di], ax
mov ax, -1[si]
adc -1[di], ax
mov ax, -2[si]
adc -2[di], ax
mov ax, -3[si]
adc -3[di], ax
mov ax, -4[si]
adc -4[di], ax
mov ax, -5[si]
adc -5[di], ax
mov ax, -6[si]
adc -6[di], ax
mov ax, -7[si]
adc -7[di], ax
dec cx
lea si, -8[si]
lea di, -8[di]
jne add_8word
done: pop cx
ret
;---------------------------------------
Последовательность
mov ax, 0[si]
adc 0[di], ax
lea si, -1[si]
lea di, -1[di]
предлагает, возможно, использовать в качестве альтернативы инструкции перемещения блока из одного слова:
std ; want to step backward
...
lods
adc ax, 0[di]
stos
...
cld
ret
с соответствующими корректировками кода, оставленными читателю.
Является ли цикл, который я написал, или версия LODS/STOS более быстрой - это то, что нужно тщательно измерить.