您确定具有四个x_n
相邻值的表达式应该是按位与,而不是将它们连接成 4 位值吗?然后是二进制加法?因为我可能已经猜到了。如果是这样,请参阅https://codegolf.stackexchange.com/a/203610rcl reg, 1
,了解在一对寄存器之间拆分位的移位和方法。或者在具有 BMI2 的现代 x86 上,您可以使用 2xpext
并add
执行此操作。
表达式在每个组中的位具有特定的顺序,而不仅仅是升序或降序这一事实可能是他们希望您将字节解压缩为两个 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, 01011100b
1。然后,您可以在不需要任何 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 ax
将rol 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 上的分支可以使用jz
or jnz
。 这在 8086 上甚至可能是最好的,例如,如果第一个 AND 组给出 a 1
,我们不需要隔离另一个组。因此xor al, ...
,为了相应地补充位,那么//test al, mask1
将是一个很好的快速路径。jnz check_other_group
mov al,1