代码检测MMX / SSE / AVX但不检测AVX2

时间:2017-01-06 13:41:19

标签: delphi

我想拥有检测特定CPU功能的通用方法。为此,我创建了这个函数,它接受EAX叶号,寄存器名和位号,并返回true或false。它适用于MMX / SSEx / AVX(EAX = 1),但它不检测AVX2(EAX = 7)。

CPU:i5-4670k 操作系统:Windows 7

DetectCPUFeature('1','EDX',23) //DETECTS MMX CORRECTLY
DetectCPUFeature('1','EDX',25) //DETECTS SSE CORRECTLY
DetectCPUFeature('1','EDX',26) //DETECTS SSE2 CORRECTLY
DetectCPUFeature('1','ECX',0)  //DETECTS SSE3 CORRECTLY
DetectCPUFeature('1','ECX',9)  //DETECTS SSSE3 CORRECTLY
DetectCPUFeature('1','ECX',19) //DETECTS SSE4.1 CORRECTLY
DetectCPUFeature('1','ECX',20) //DETECTS SSE4.2 CORRECTLY
DetectCPUFeature('1','ECX',28) //DETECTS AVX CORRECTLY

DetectCPUFeature('7','EBX',5) //DOES NOT DETECT AVX2!

function DetectCPUFeature(EAX_Leaf_HEX,Register_Name:string;Bit:byte):boolean;
var _eax,_ebx,_ecx,_edx,EAX_Leaf,_Result: Longword;
    x:integer;
    Binary_mask:string;
    Decimal_mask:int64;
begin

  EAX_Leaf:=HexToInt(EAX_Leaf_HEX);
  Binary_mask:='1';
  for x:=1 to Bit do Binary_mask:=Binary_mask+'0';
  Decimal_mask:=BinToInt(Binary_mask);

  if AnsiUpperCase(Register_Name)='EDX' then
  begin
    asm
      mov eax,EAX_Leaf // https://en.wikipedia.org/wiki/CPUID
      db $0F,$A2 // db $0F,$A2 = CPUID instruction
      mov _Result,edx
    end;
  end;

  if AnsiUpperCase(Register_Name)='ECX' then
  begin
    asm
      mov eax,EAX_Leaf
      db $0F,$A2
      mov _Result,ecx
    end;
  end;

  if AnsiUpperCase(Register_Name)='EBX' then
  begin
    asm
      mov eax,EAX_Leaf
      db $0F,$A2
      mov _Result,ebx
  end;
 end;

 if (_Result and Decimal_mask) = Decimal_mask then DetectCPUFeature:=true 
 else DetectCPUFeature:=false;  

end;

3 个答案:

答案 0 :(得分:6)

这种代码非常可疑,将asm与Pascal代码混合在一起。您的代码在asm块中修改了寄存器,无法恢复它们。这可能很容易与编译器的寄存器使用冲突。我强烈建议你不要以这种方式混合asm和Pascal。始终使用纯Pascal或纯Asm。

您需要的是一个函数,它将执行CPUID指令并返回结构中的所有寄存器。然后,您可以使用Pascal代码从中选择您想要的内容。

此外,正如@J ...指出的那样,在调用ECX指令之前,需要在CPUID寄存器中指定子叶值。这是一些最近添加的CPUID参数的要求。

这是您需要的功能:

type
  TCPUID = record
    EAX: Cardinal;
    EBX: Cardinal;
    ECX: Cardinal;
    EDX: Cardinal;
  end;

function GetCPUID(Leaf, Subleaf: Cardinal): TCPUID;
asm
  push  ebx
  push  edi
  mov   edi, ecx
  mov   ecx, edx
  cpuid
  mov   [edi+$0], eax
  mov   [edi+$4], ebx
  mov   [edi+$8], ecx
  mov   [edi+$c], edx
  pop   edi
  pop   ebx
end;

我已经为32位代码写了这个,但如果你需要支持64位代码,那么支持很容易添加。

function GetCPUID(Leaf, Subleaf: Integer): TCPUID;
asm
{$IF Defined(CPUX86)}
  push  ebx
  push  edi
  mov   edi, ecx
  mov   ecx, edx
  cpuid
  mov   [edi+$0], eax
  mov   [edi+$4], ebx
  mov   [edi+$8], ecx
  mov   [edi+$c], edx
  pop   edi
  pop   ebx
{$ELSEIF Defined(CPUX64)}
  mov   r9,rcx
  mov   ecx,r8d
  mov   r8,rbx
  mov   eax,edx
  cpuid
  mov   [r9+$0], eax
  mov   [r9+$4], ebx
  mov   [r9+$8], ecx
  mov   [r9+$c], edx
  mov   rbx, r8
{$ELSE}
  {$Message Fatal 'GetCPUID has not been implemented for this architecture.'}
{$IFEND}
end;

有了这个,您可以调用CPUID传递任何值作为输入,并检索所有4个输出寄存器,然后您可以随意执行任何操作。

创建位掩码的代码效率极低,与惯用语相差甚远。使用1 shl N创建一个位置为N的单个位设置的值。

这样的代码:

if (_Result and Decimal_mask) = Decimal_mask then DetectCPUFeature:=true 
else DetectCPUFeature:=false;  

也是一种惯用语。通常会这样写:

DetectCPUFeature := value and mask <> 0;

您最终可能会得到一个如下所示的包装函数:

type
  TCPUIDRegister = (regEAX, regEBX, regECX, regEDX);

function GetCPUIDRegister(CPUID: TCPUID; Reg: TCPUIDRegister): Cardinal;
begin
  case Reg of
  regEAX:
    Result := CPUID.EAX;
  regEBX:
    Result := CPUID.EBX;
  regECX:
    Result := CPUID.ECX;
  regEDX:
    Result := CPUID.EDX;
  end;
end;

function CPUFeatureEnabled(Leaf, Subleaf: Cardinal; Reg: TCPUIDRegister; Bit: Integer): Boolean;
var
  value: Cardinal;
begin
  value := GetCPUIDRegister(GetCPUID(Leaf, Subleaf), Reg);
  Result := value and (1 shl Bit) <> 0;
end;

答案 1 :(得分:2)

虽然David的答案非常好,但是函数失败的原因是ECX寄存器未设置为零(在CPUID调用中获取扩展信息所需)。

请参阅:How to detect New Instruction support in the 4th generation Intel® Core™ processor family

在哪里找到AVX2(强调我的)

  

CPUID。(EAX = 07H, ECX = 0H ):EBX.AVX2 [bit 5] == 1

以下内容正确返回扩展信息并识别AVX2支持。

 if AnsiUpperCase(Register_Name)='EBX' then
  begin
    asm
      push ecx         { push ecx to stack}
      mov ecx, 0       { set ecx to zero}
      mov eax,EAX_Leaf
      db $0F,$A2
      mov _Result,ebx
      pop ecx          { restore ecx}
  end;

其他asm函数与ECX具有相同的错误,这些调用也需要为零。

答案 2 :(得分:-2)

摘自Synopse Informatique:

type
  /// the potential features, retrieved from an Intel CPU
  // - see https://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits
  TALIntelCpuFeature =
   ( { in EDX }
   cfFPU, cfVME, cfDE, cfPSE, cfTSC, cfMSR, cfPAE, cfMCE,
   cfCX8, cfAPIC, cf_d10, cfSEP, cfMTRR, cfPGE, cfMCA, cfCMOV,
   cfPAT, cfPSE36, cfPSN, cfCLFSH, cf_d20, cfDS, cfACPI, cfMMX,
   cfFXSR, cfSSE, cfSSE2, cfSS, cfHTT, cfTM, cfIA64, cfPBE,
   { in ECX }
   cfSSE3, cfCLMUL, cfDS64, cfMON, cfDSCPL, cfVMX, cfSMX, cfEST,
   cfTM2, cfSSSE3, cfCID, cfSDBG, cfFMA, cfCX16, cfXTPR, cfPDCM,
   cf_c16, cfPCID, cfDCA, cfSSE41, cfSSE42, cfX2A, cfMOVBE, cfPOPCNT,
   cfTSC2, cfAESNI, cfXS, cfOSXS, cfAVX, cfF16C, cfRAND, cfHYP,
   { extended features in EBX, ECX }
   cfFSGS, cf_b01, cfSGX, cfBMI1, cfHLE, cfAVX2, cf_b06, cfSMEP, cfBMI2,
   cfERMS, cfINVPCID, cfRTM, cfPQM, cf_b13, cfMPX, cfPQE, cfAVX512F,
   cfAVX512DQ, cfRDSEED, cfADX, cfSMAP, cfAVX512IFMA, cfPCOMMIT,
   cfCLFLUSH, cfCLWB, cfIPT, cfAVX512PF, cfAVX512ER, cfAVX512CD,
   cfSHA, cfAVX512BW, cfAVX512VL, cfPREFW1, cfAVX512VBMI);

  /// all features, as retrieved from an Intel CPU
  TALIntelCpuFeatures = set of TALIntelCpuFeature;

var
  /// the available CPU features, as recognized at program startup
  ALCpuFeatures: TALIntelCpuFeatures;

{**}
type
 _TRegisters = record
   eax,ebx,ecx,edx: cardinal;
 end;

{***************************************************************}
procedure _GetCPUID(Param: Cardinal; var Registers: _TRegisters);
{$IF defined(CPU64BITS)}
asm // ecx=param, rdx=Registers (Linux: edi,rsi)
  .NOFRAME
  mov     eax, ecx
  mov     r9, rdx
  mov     r10, rbx // preserve rbx
  xor     ebx, ebx
  xor     ecx, ecx
  xor     edx, edx
  cpuid
  mov     _TRegisters(r9).&eax, eax
  mov     _TRegisters(r9).&ebx, ebx
  mov     _TRegisters(r9).&ecx, ecx
  mov     _TRegisters(r9).&edx, edx
  mov     rbx, r10
end;
{$else}
asm
  push    esi
  push    edi
  mov     esi, edx
  mov     edi, eax
  pushfd
  pop     eax
  mov     edx, eax
  xor     eax, $200000
  push    eax
  popfd
  pushfd
  pop     eax
  xor     eax, edx
  jz      @nocpuid
  push    ebx
  mov     eax, edi
  xor     ecx, ecx
  cpuid
  mov     _TRegisters(esi).&eax, eax
  mov     _TRegisters(esi).&ebx, ebx
  mov     _TRegisters(esi).&ecx, ecx
  mov     _TRegisters(esi).&edx, edx
  pop     ebx
@nocpuid:
  pop     edi
  pop     esi
end;
{$ifend}

{******************************}
procedure _TestIntelCpuFeatures;
var regs: _TRegisters;
begin
  regs.edx := 0;
  regs.ecx := 0;
  _GetCPUID(1,regs);
  PIntegerArray(@ALCpuFeatures)^[0] := regs.edx;
  PIntegerArray(@ALCpuFeatures)^[1] := regs.ecx;
  _GetCPUID(7,regs);
  PIntegerArray(@ALCpuFeatures)^[2] := regs.ebx;
  PByteArray(@ALCpuFeatures)^[12] := regs.ecx;
end;

initialization
  _TestIntelCpuFeatures;