Assembly 测试所有相邻的真位

Assembly 测试所有相邻的真位,assembly,optimization,bit-manipulation,x86-64,masm,Assembly,Optimization,Bit Manipulation,X86 64,Masm,我通过遍历树来构建一个哈夫曼前缀代码LUT。我正在使用寄存器跟踪当前前缀 我退出解析树的算法的条件使用以下条件,这些条件必须为真: 树中的当前元素必须是叶 当前前缀代码设置了所有位,但没有假位 对于第二个条件,我还跟踪前缀字符串的当前长度(以位为单位) 我如何测试一个寄存器是否设置了超过1个位,以及所有设置位是否彼此相邻 编辑:设置位组必须从位0开始,长度与前缀长度相同(存储在另一个寄存器中)让您的数字在eax中,并且所需的前缀长度在edx中 首先,为了得到尾随的1的数量,计算补码并计算尾随的0

我通过遍历树来构建一个哈夫曼前缀代码LUT。我正在使用寄存器跟踪当前前缀

我退出解析树的算法的条件使用以下条件,这些条件必须为真:

  • 树中的当前元素必须是叶

  • 当前前缀代码设置了所有位,但没有假位

  • 对于第二个条件,我还跟踪前缀字符串的当前长度(以位为单位)

    我如何测试一个寄存器是否设置了超过1个位,以及所有设置位是否彼此相邻


    编辑:设置位组必须从位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
    ).BMI1
    blsi
    =隔离最低设置位。@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: