Masm assembly 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
;---------------------------------------
Ответы
Ответ 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
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
is sub
, за исключением cmp
, только модифицирует флаги и не сохраняет вычисленное значение, поэтому вы можете напрямую проверить результат sub
.
Ответ 2
OP говорит, что он хочет недорогое решение для сохранения переноса между итерациями. У @MikeCAT было решение; @PeterCordes предложил некоторые улучшения.
Есть еще одно действительно хорошее улучшение, которое вы можете получить при выполнении арифметики multiprecision, в предположении, что ваше многозначное значение является "большим" (содержит много значений слов) и которое разворачивает внутренний цикл N раз, избегая флагов подсчета/переноса флага повреждение внутри развернутой секции. (Если ваша многоточечная арифметика не очень много, вам не нужна большая оптимизация).
Я пересмотрел @MikeCAT ответ здесь, исходя из предположения, что разворачивание должно состоять из 8 итераций.
Код имеет 3 части: определение того, как обрабатывать фрагмент из 8 слов,
обрабатывая фрагмент разворачиваемым способом, а затем обрабатывая несколько 8 блоков слов эффективно в основном развернутом цикле.
Для примера OPs из 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 быстрее, нужно тщательно измерить.
Ответ 3
Если вы хотите быстрое добавление с несколькими точками, используйте 64-битный код, если это вообще возможно. Выполнение 4x ширины с каждой инструкцией дает 4x ускорение. На 386-совместимых процессорах вы можете использовать 32-битные инструкции и регистры даже в 16-битном режиме, что даст вам 2x ускорение.
Чтобы сделать предложение о разворачивании Ира еще на шаг
- сокращение накладных расходов на один
lea
- избегая
adc
с местом назначения памяти, что медленнее на Intel.
... set up for the unrolled loop, with Ira 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, согласно таблицам Agner Fog. Intel Haswell и ранее будут ограничены 2c латентностью adc
. (Бродвелл сделал adc
и cmov
одноуровневые инструкции, используя 3-зависимую поддержку uop, добавленную в Haswell, поэтому FMA может быть одной командой uop).
A loop
insn может быть выигрышем на более старых процессорах, где частичный рег-стоп действительно плох, но другие способы избежать срыва с частичным флагом могут быть даже лучше, чем медленная инструкция loop
.
Использование трюка dest-source уменьшает накладные расходы цикла при уменьшении указателя. Режимы адресации 2-регистров в нагрузках не нуждаются в микроплавких предохранителях, поскольку чистые нагрузки mov
в любом случае являются одним и тем же. магазины нуждаются в микро-предохранителе, поэтому вам следует выбирать режимы адресации с одним регистром для магазинов. Дополнительный адресный блок Haswell на порту7 может обрабатывать только простые режимы адресации, а режимы адресации с двумя регистрами не могут быть микро-предохранителями.
См. также Проблемы с ADC/SBB и INC/DEC в жестких петлях на некоторых процессорах для получения информации о циклах adc для многоточечных объявлений и некоторых экспериментах по процессорам Core2 и SnB для производительность стойки с неполным флагом.
Другим способом петли здесь будет lea si, -1[si]
/mov cx, si
/jcxz
. 16bit отстой, и вы не можете использовать [cx]
в эффективном адресе.