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] в эффективном адресе.