Assembly 测试所有相邻的真位
我通过遍历树来构建一个哈夫曼前缀代码LUT。我正在使用寄存器跟踪当前前缀 我退出解析树的算法的条件使用以下条件,这些条件必须为真:Assembly 测试所有相邻的真位,assembly,optimization,bit-manipulation,x86-64,masm,Assembly,Optimization,Bit Manipulation,X86 64,Masm,我通过遍历树来构建一个哈夫曼前缀代码LUT。我正在使用寄存器跟踪当前前缀 我退出解析树的算法的条件使用以下条件,这些条件必须为真: 树中的当前元素必须是叶 当前前缀代码设置了所有位,但没有假位 对于第二个条件,我还跟踪前缀字符串的当前长度(以位为单位) 我如何测试一个寄存器是否设置了超过1个位,以及所有设置位是否彼此相邻 编辑:设置位组必须从位0开始,长度与前缀长度相同(存储在另一个寄存器中)让您的数字在eax中,并且所需的前缀长度在edx中 首先,为了得到尾随的1的数量,计算补码并计算尾随的0
编辑:设置位组必须从位0开始,长度与前缀长度相同(存储在另一个寄存器中)让您的数字在
eax
中,并且所需的前缀长度在edx
中
首先,为了得到尾随的1的数量,计算补码并计算尾随的0的数量(如果数字不匹配,我们分支到fail
)。需要cmovz
,因为bsf
不喜欢以0作为参数调用。如果没有出现这种情况,可以删除前三条说明
mov ebx, 32 ; number of bits in an int
mov ecx, eax
not ecx
bsf ecx, ecx ; count trailing zeroes in ecx
; i.e. trailing ones in eax
cmovz ecx, ebx ; give correct result for eax == -1
cmp ecx, edx
jnz fail ; have as many ones as desired?
如果您的CPU具有tzcnt
,则可以避免此指令:
mov ecx, eax
not ecx
tzcnt ecx, ecx
cmp ecx, edx
jnz fail
请注意,在没有它的CPU上,tzcnt
被静默地解释为bsf
,从而破坏您的代码;监视未定义的指令异常不足以确保它存在
为确保未设置其他位,请清除后缀并检查结果是否为零:
lea ecx, [rax+1]
and ecx, eax ; ecx = eax & eax + 1
; i.e. clear all trailing 1 bits
jnz fail ; fail if any bits are left
尽管最快的实现可能只是创建一个包含所有后缀长度的查找表,然后检查后缀是否匹配:
lut: dd 00000000h, 00000001h, 00000003h, 00000007h
dd 0000000fh, 0000001fh, 0000003fh, 0000007fh
dd 000000ffh, 000001ffh, 000003ffh, 000007ffh
dd 00000fffh, 00001fffh, 00003fffh, 00007fffh
dd 0000ffffh, 0001ffffh, 0003ffffh, 0007ffffh
dd 000fffffh, 001fffffh, 003fffffh, 007fffffh
dd 00ffffffh, 01ffffffh, 03ffffffh, 07ffffffh
dd 0fffffffh, 1fffffffh, 3fffffffh, 7fffffffh
dd ffffffffh
...
cmp lut[edx * 4], eax
jnz fail
这方面的构造块将是:向连续组的低位添加1将使用执行清除所有这些位,在组上方保留1位。e、 g.0xff+1=0x100
;; input: prefix length in EDX
;; output: prefix mask in ESI
xor esi, esi
bts esi, edx ; set bit n; eax |= 1<<n
dec esi ; set all the bits BELOW that power of 2
; then later, inside a loop: input in EAX
cmp eax, esi
je all_bits_set_up_to_prefix
如果任何位未设置,进位将不会一直向上传播,例如0b01101+1=0b01110
,而不是设置位4。(并保留一些现有设置位未翻转,因此x&(x+1)
将为真。)
这适用于寄存器底部的位组(添加1)或任何更高位置的位组(使用(-x)&x
隔离最低设置位并添加该位,例如使用或mov/neg/and)
一个相关的bithack是对只有一个位集的整数进行的y&(y-1)
测试(通过清除最低的位集):。但由于我们使用x+1
生成y
,我们可以将其优化为x&(x+1)
,以检测寄存器底部的连续掩码
您的具体案例非常简单:
- 位范围的底部必须是位0
- 位范围正好是
位宽(前缀长度)n
这些限制意味着正好有一个整数符合这两个要求,因此您应该预先计算它,并简单地与
cmp/je
进行相等性比较。底部设置了n
位的数字是prefix_mask=(1您可以假设BMI1(和BMI2)?我的第一个想法是,将1添加到连续组的低位将清除带有执行的所有位。(0xff+1=0x100),因此您希望添加最低的设置位,并查看结果是否正好有1位设置(即,测试其是否为2的幂,且x&(x-1)=0
).BMI1blsi
=隔离最低设置位。@PeterCordes我喜欢这个想法,我在前缀上使用了inc
,然后使用bt前缀,前缀长度
,如果它都是相邻的设置位,则以CF set结束。谢谢!编辑:啊,我明白为什么现在需要2的幂。这种方法不适用于预处理的情况修复以0(高位零)开始.我的长度在另一个寄存器中,长度为3,前缀代码为001,它在检查中计算成功,应该失败。哦,您的连续位保证位于寄存器的底部?这比标题和最后问题的一般情况更简单!为您节省了blsi
或(-x)&x
来隔离最低的集合。知道当前前缀长度可以启用bt
,是的,这非常好,比我想的更容易区分1位集合和多位集合。(例如,blsi
/add
/blsr
如果输入只有1或0个连续的位组,则设置ZF,但不排除它只有1位长。)@PeterCordes是的,设置的位必须在一起,从位0开始。我不理解“blsi”指令。因此我需要测试一组分组位,位0和位(prefixlength-1)?我在问题中添加了编辑,我解释得不太清楚。@我会修改我的答案来解决这个问题吗。@PeterCordes一个更简单的方法是设置一个包含所需后缀的寄存器(即通过LUT或变量移位,然后递减)简单地比较一下。哦,好的,是的xor
-zero a reg,然后bts edx,len
/dec edx
创建(1我想出了一个诀窍,对一名候选人使用+1
和btr
/测试
,在没有LUT或预计算的情况下,仅在3个UOP中进行分支。发布了我一直在研究的答案。
;;; check the low n bits, ignoring whether higher bits are set or not
;;; inputs: prefix length in ECX, candidate in EDX
lea eax, [rdx+1]
bt eax, ecx
;;; output: CF = 1 if all the bits from 0..len-1 were set, else 0
;;; input: prefix length in ECX, candidate in EDX
lea eax, [rdx+1] ; produces a single set bit?
btr eax, ecx ; reset that bit, leaving eax=0 if no other bits were set
test eax, eax ; compare against zero
;;; output: ZF=1 (and eax=0) if EDX == (1<<ECX)-1 with no higher bits set.
jz contiguous_bitmask_of_length_ecx
; check for a contiguous bit-group at the bottom of a reg of arbitrary length, including 0
;;; input: candidate in EDX
lea eax, [rdx+1] ; carry-out clears all the set bits at the bottom
test eax, edx ; set flags from x & (x+1)
;;; output: ZF=1 if the only set bits in EDX were a contiguous group at the bottom
unsigned bits_contiguous(unsigned x) {
unsigned lowest_set = (-x) & x; // isolate lowest set bit
unsigned add = x + lowest_set; // carry flips all the contiguous set bits
return (add & x) == 0; // did add+carry leave any bits un-flipped?
}
# gcc8.3 -O3 -march=haswell (enables BMI1 and BMI2)
bits_contiguous:
blsi eax, edi
add eax, edi
test eax, edi # x & (x+lowest_set)
sete al
movzx eax, al
ret
mov eax, edi
neg eax
and eax, edi # eax = isolate_lowest(x)
add eax, edi
test eax, edi
;input: prefix len in ECX, candidate in EDX
;clobbers: EAX
popcnt eax, edx
cmp eax, ecx
jne wrong_number_of_bits_set ; skip the contiguousness test
blsi eax, edi
add eax, edi
test eax, edi # x & (x+lowest_set)
jz contiguous_bitgroup_of_length_ecx
wrong_number_of_bits_set: