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说明,但是我想知道这种现象有多普遍。
是的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)
至少bsf和tzcnt产生相同的输出,当源<> 0bsr和lzcnt不同意这一点。
还lzcnt和tzcnt执行速度远远超过bsr/ bsf。
它完全吸的是bsf和tzcnt对标志使用不能同意。这种不必要的不一致意味着我不能将其tzcnt用作替代品,bsf除非我可以确定其来源非零。