1

我对组装很陌生
考虑以下功能:
在此处输入图像描述

其中“+”代表“或”逻辑门,变量串联代表“与”逻辑门。
如何在emu8086中实现这样的功能?假设输入参数可能代表寄存器 AL 的位,例如,输出会将其值更改为 0 或 1。

更新:这就是我所做的,我知道它写得不好,如果有任何建议或更简单的方法,它似乎确实有效,让我知道
感谢大家的帮助,尤其是彼得。

org 100h

mov al, 0A3h     pour le test              

mov ah, al
and ah, 01h        ;ah = x0 
shr al, 1

mov bl, al 
and bl, 01h        ;bl = x1 
shr al, 1

mov bh, al
and bh, 01h            
not bh
and bh, 01h         ;bh = !x2
shr al, 1

mov cl, al 
and cl, 01h        
not cl
and cl, 01h        ;cl = !x3
shr al, 1

mov ch, al
and ch, 01h        
not ch
and ch, 01h        ;ch = !x4
shr al, 1

mov dl, al
and dl, 01h        ;x5 = dl
shr al, 1

mov dh, al
and dh, 01h        
not dh
and dh, 01h        ;dh = !x6    

shr al, 1          ;al = x7  


and bh, dl
and bh, cl
and bh, ah         ;!x2 and x5 and !x3 and x0     

and dh, bl 
and dh, ch
and dh, al         ;!x6 and x1 and !x4 and x7 

or dh, bh   

mov ah, dh         ;resultat dans ah







ret
4

2 回答 2

2

确定具有四个x_n相邻值的表达式应该是按位与,而不是将它们连接成 4 位值吗?然后是二进制加法?因为我可能已经猜到了。如果是这样,请参阅https://codegolf.stackexchange.com/a/203610rcl reg, 1 ,了解在一对寄存器之间拆分位的移位和方法。或者在具有 BMI2 的现代 x86 上,您可以使用 2xpextadd执行此操作。

表达式在每个组中的位具有特定的顺序,而不仅仅是升序或降序这一事实可能是他们希望您将字节解压缩为两个 4 位整数并+对其进行正常处理的线索。


如果我们假设您的 asm 是正确功能的示例

这个答案的其余部分是关于优化你的 asm 中的操作,它执行两组 AND 和 OR,它们一起产生一个布尔值,在 AL 中产生一个0或。1

您可以对简单/直接的实现进行一些改进,即分别提取每个位。例如,您不需要在您之前之后都不需要。第一个 AND 将使高位全为 0,然后 NOT 使它们为 1,然后第二个 AND 使它们再次为零。

mov bh, al
; and bh, 01h            ; This is pointless
not bh
and bh, 01h         ;bh = !x2

您可以更进一步:您纯粹使用按位运算,只关心每个寄存器中的低位。 您可以and al, 1在最后隔离您想要的位,所有临时人员都在其高位携带垃圾。


要翻转一些位但不是全部,请使用带有常量掩码的 XOR。例如,要翻转 AL 中的 6、4、3、2 位并保持其他位不变,请使用xor al, 01011100b1。然后,您可以在不需要任何 NOT 指令的情况下移位和移动到单独的寄存器。

脚注 1:尾随b表示基数 2 / 二进制。如果 emu8086 支持它,或者如果你必须编写等效的十六进制,这适用于MASM 语法、IDK。

而且你可以直接进入这些寄存器而不是先提取,所以你只需要两个临时寄存器。

   xor   al, 01011100b    ; complement bits 6,4,3,2
   mov   cl, al           ; x0, first bit of the 2&5&3&0 group

   shr   al, 1
   mov   dl, al           ; x1, first bit of the 6&1&4&7 group

   shr   al, 1
   and   cl, al           ; AND X2 into the first group, X2 & x0

   shr   al, 1
   and   cl, al           ; cl = X2 & X3 & x0

   ...                    ; cl = 2&5&3&0,  dl = 6&1&4 with a few more steps

   shr   al, 1            ; AL = x7 
   and   al, dl           ; AL = x6 & x1 & x4 & x7  (reading 6,1,4 from dl)

   or    al, cl           ; logical + apparently is regular (not exclusive) OR
   and   al, 1            ; clear high garbage
   ret

(对于普通的 ASCII 注释,我忽略了“补码”部分,因为我们在一开始就用一条指令来处理它。)


据我所知,我们采用了一个简单的实现,它只是将位放到寄存器的底部,并使用单独的 asm 指令执行每个布尔运算(除了补码)。

为了做得更好,我们需要利用寄存器中的 8(或 16)位,我们可以与一条指令并行执行。我们不能轻易地打乱位以使它们彼此对齐,因为模式是不规则的。

IDK 如果有什么聪明的东西,我们可以左移 AX 以将 AL 中的位放入 AH 的底部,并将一些分组在 AL 的顶部。嗯,也许可以交替shl axrol al位发送回 AL 的底部。但这仍然需要 7 次移位来分离位。(shl ax,2并且rol al,2对于在一起的连续位(7,6 和 3,2)仅在 186 上可用,并且在 CL 中计数几乎不值得)。

更可能的攻击角度是 FLAGS:大多数 ALU 操作会根据结果更新 FLAGS,如果结果中的所有位都为 0,则 ZF 设置为 1,否则设置为 1。这为我们提供了跨一个寄存器中的位的水平 OR 运算. 由于!(a | b)= !a & !b,我们可以反转输入中的非补码位以将其用作水平 AND 而不是 OR。(我正在使用!单个位反转。在 C 中,!是一个逻辑非,它将任何非零数变为 0,这与~按位 NOT 不同。)

但不幸的是,8086 没有一种简单的方法可以直接将 ZF 变成寄存器中的 0/1。(386 增加setcc r/m8,例如setz dl根据 ZF 设置 DL = 0 或 1。)这对于 CF是可能的。我们可以通过使用 来根据寄存器非零来sub reg, 1设置 CF,如果 reg 为 0,则设置 CF(因为借位出现在顶部)。否则清除 CF。我们可以根据 CF 用sbb al, al(借用减法)在 reg 中得到 0 / -1。al-al 部分取消,离开0 - CF

要设置使用 FLAGS,我们可以使用 AND 掩码将位分成两组。

;; UNTESTED, I might have some logic inverted.

   xor   al, 10100011b         ; all bits are the inverse of their state in the original expression.

   mov   dl, al
   and   dl, 11010010b         ; ~x[7,6,4,1]
   and   al, 00101101b         ; ~x[5,3,2,0]

   cmp   dl, 1                 ; set CF if that group was all zero (i.e. if the original AND was 1), else clear
   sbb   dl, dl                ; dl = -1 or 0 for the first group

   cmp   al, 1
   sbb   al, al                ; al = -1 or 0 for the second group.  Fun fact: undocumented SALC does this

   or    al, dl                ; The + in the original expression
   and   al, 1                 ; keep only the low bit

   ret

根据 DL 中的 SBB 结果,我们可能还可以做更多事情,比如and al, dl是否清除 AL 中的位。或者也许adc al, -1不是cmp al, 1使用来自 DL 的 CF 结果来影响如何从 AL 设置 CF。

而不是减去1,您可以sub dl, 11010010b使用您使用的 AND 掩码,所以0如果它们都已设置,您会得到,否则它会换行并设置 CF。不确定这是否有用。

否定/反转的数量很快就会在您的脑海中变得棘手,但是如果代码大小的每个字节或每个性能周期都很重要,那么您应该研究一下。(如今这种情况很少发生,当您经常使用 SSE2 或 AVX 进行矢量化时,您不会有标志,只是在矢量元素中按位和打包比较将匹配变为全一和不匹配为 0。)

请注意,在使用 mov/AND 拆分后,AL 和 DL 都不能为全一,因此加法1永远不能归零。那么也许sbb al, -1可以添加 0 或 1 并设置 ZF?

如果你想分支,ZF 上的分支可以使用jzor jnz 这在 8086 上甚至可能是最好的,例如,如果第一个 AND 组给出 a 1,我们不需要隔离另一个组。因此xor al, ...,为了相应地补充位,那么//test al, mask1将是一个很好的快速路径。jnz check_other_groupmov al,1

于 2020-04-17T22:35:55.010 回答
1

Peter Cordesprl评论的启发:

int test(char x)
{
    return ((x & 0x2d) == 0x21) || ((x & 0xd2) == 0x82);
}

Godbolt (x86 msvc v19.24 /Os) 生成:

   _x$ = 8                                       ; size = 1
int test(char) PROC                                  ; test
        mov     cl, BYTE PTR _x$[esp-4]
        mov     al, cl
        and     al, 45                                    ; 0000002dH
        cmp     al, 33                                    ; 00000021H
        je      SHORT $LN3@test
        and     cl, 210                             ; 000000d2H
        cmp     cl, 130                             ; 00000082H
        je      SHORT $LN3@test
        xor     eax, eax
        ret     0
$LN3@test:
        mov     eax, 1
        ret     0
int test(char) ENDP                                  ; test
于 2020-04-19T08:41:56.133 回答