为什么TZCNT可以用于我的Sandy Bridge处理器?

Doo*_*ins 5 x86 assembly x86-64

我正在运行Core i7 3930k,它是Sandy Bridge微体系结构的。当执行以下代码(在MSVC19,VS2015下编译)时,结果使我感到惊讶(请参阅注释):

int wmain(int argc, wchar_t* argv[])
{
    uint64_t r = 0b1110'0000'0000'0000ULL;
    uint64_t tzcnt = _tzcnt_u64(r);
    cout << tzcnt << endl; // prints 13

    int info[4]{};
    __cpuidex(info, 7, 0);
    int ebx = info[1];
    cout << bitset<32>(ebx) << endl; // prints 32 zeros (including the bmi1 bit)

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

反汇编表明该tzcnt指令确实是从内在函数发出的:

    uint64_t r = 0b1110'0000'0000'0000ULL;
00007FF64B44877F 48 C7 45 08 00 E0 00 00 mov         qword ptr [r],0E000h  
    uint64_t tzcnt = _tzcnt_u64(r);
00007FF64B448787 F3 48 0F BC 45 08    tzcnt       rax,qword ptr [r]  
00007FF64B44878D 48 89 45 28          mov         qword ptr [tzcnt],rax  
Run Code Online (Sandbox Code Playgroud)

为什么我没有收到一个#UD无效的操作码异常,指令功能正常,并且CPU的报告,它并不能支持上述指令?

这可能是某种奇怪的微代码修订版,其中包含该指令的实现,但未报告对此指令的支持(以及)中的其他支持bmi1吗?

我没有检查其余的bmi1说明,但是我想知道这种现象有多普遍。

Joh*_*ica 6

是的Sandy Bridge(或更早)的处理器似乎支持的原因lzcnt,并tzcnt为这两个指令有一个向后兼容的编码。

lzcnt eax,eax  = rep bsr eax,eax
tzcnt eax,eax  = rep bsf eax,eax
Run Code Online (Sandbox Code Playgroud)

在较旧的处理器上,rep前缀被简单地忽略。

好消息就这么多。
坏消息是两个版本的语义不同。

lzcnt eax,zero => eax = 32, CF=1, ZF=0  
bsr eax,zero   => eax = undefined, ZF=1
lzcnt eax,0xFFFFFFFF => eax=0, CF=0, ZF=1   //dest=number of msb leading zeros
bsr eax,0xFFFFFFFF => eax=31, ZF=0        //dest = bit index of highest set bit


tzcnt eax,zero => eax = 32, CF=1, ZF=0
bsf eax,zero   => eax = undefined, ZF=1
tzcnt eax,0xFFFFFFFF => eax=0, CF=0, ZF=1   //dest=number of lsb trailing zeros
bsf eax,0xFFFFFFFF => eax=0, ZF=0        //dest = bit index of lowest set bit
Run Code Online (Sandbox Code Playgroud)

至少bsftzcnt产生相同的输出,当源<> 0bsrlzcnt不同意这一点。
lzcnttzcnt执行速度远远超过bsr/ bsf
它完全吸的是bsftzcnt对标志使用不能同意。这种不必要的不​​一致意味着我不能将其tzcnt用作替代品,bsf除非我可以确定其来源非零。