Masm程序集8086在数据字添加

时间:2016-03-03 07:08:43

标签: assembly masm word carryflag

所以我有这个问题我应该解决,我花了好几个小时试图找出最好的方法来做到这一点,谷歌没有太多的帮助。

问题是创建一个子程序,该子程序被赋予一个单词列表,然后您将该单词列表添加到另一个成为输出的列表中。它基本上是一种处理大量数据的方法。

我的代码适用于字样中的字符,但对于从一个完整字到另一个字的进位标记,它不起作用。第一个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 个答案:

答案 0 :(得分:2)

您必须保存上一次迭代中的进位标志。

试试这个:

;---------------------------------------
; 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,因为subcmp,但sub只修改了标记并且不存储计算值,所以你可以直接检查printRectangles的结果。

答案 1 :(得分:2)

OP表示他希望采用低成本解决方案来保持迭代之间的进位。 @MikeCAT有 a 解决方案; @PeterCordes提出了一些改进建议。

在进行多精度算术时,你可以得到一个非常好的改进,假设你的multiprecison值是“大”(包含许多字值),并且将内循环展开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版本是否更快是值得仔细衡量的。

答案 2 :(得分:2)

如果您想要快速多精度添加,请尽可能使用64位代码。使用每条指令直接执行4倍宽度可实现4倍的加速。在386兼容的CPU上,即使在16位模式下也可以使用32位指令和寄存器,这将为您提供2倍的加速。

将艾拉的展开建议更进一步

  • 将循环开销减少一lea
  • 避免使用内存目的地adc,这在英特尔上很慢。

    ... 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

这应该在Intel Broadwell上的每个时钟(减去环路开销)接近adc,在AMD K8上通过Bulldozer运行。 (我忘记了如果k8 / k10每个时钟可以做两次加载。如果它能够做到这一点就会成为瓶颈。)根据{{​​3}},使用具有内存目的地的adc对于英特尔来说并不好,但在AMD上则不错。英特尔Haswell及更早版本将受adc的2c延迟限制。 (Broadwell发出adccmov单uop指令,利用Haswell中添加的3依赖性uop支持,因此FMA可以是单个uop指令。

loop insn可能是旧CPU的胜利,其中部分reg失真非常糟糕,但其他避免部分标志失速的方法甚至可能比Agner Fog's tables更好。

使用dest-source技巧减少了递减指针的循环开销。负载中的2寄存器寻址模式不需要微熔丝,因为纯mov负载无论如何都是单个uop。商店确实需要微熔丝,所以你应该更喜欢商店的单寄存器寻址模式。 Has7在port7上的额外存储地址单元只能处理简单的寻址模式,the slow loop instruction

另请参阅2-register addressing modes can't micro-fuse以获取有关multiprecision adc循环的信息,以及有关部分标记停顿性能的Core2与SnB CPU的一些实验。

此处循环的另一种方式是lea si, -1[si] / mov cx, si / jcxz。 16位很糟糕,您无法在有效地址中使用[cx]