Массовая сборка 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 более быстрой - это то, что нужно тщательно измерить.

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