Calling the Intel CPUID instruction and reading processor information

Posted: (EET/GMT+2)

 

In my last post, I promised to show some code that you could use to detect processor features. So here goes.

The Intel x86 ("IA32") processors since Pentium I support the CPUID instruction, which can be used to read processor information. For example, you can use this instruction to detect if your processor supports MMX, SSE, SSE2, SSE3, IA64 or HyperThreading, and most importantly, EMT64, which is Intel's name for 64-bit extensions to their 32-bit Pentium processors. By the way, Microsoft calls EMT64 simply "x64", since they also support AMD's 64-bit extensions that are very similar to EMT64.

Now, I won't be going too much into the details, since you can download a document called the "Intel Processor Identification and the CPUID Instruction" from Intel's developer web site. Instead, I want to show you some example code written with Delphi 2006. You cannot do the same directly from C#, but you could easily write an unmanaged DLL from the Delphi code, and then call that DLL from C#.

Here's my sample application named ShowCPUID:

program ShowCPUID;

{$APPTYPE CONSOLE}
{$OPTIMIZATION OFF}
{$ALIGN 4}

{
Source:

"Intel Processor Identification and the CPUID Instruction"
- Application Note 485, January 2006.

http://developer.intel.com/
}

type
  ProcessorInfo = record
    MaximumBasicFunction    : cardinal;
    MaximumExtendedFunction : cardinal;
    VendorID                : array[0..12] of Char;
    Signature               : cardinal;
    SupportsMMX             : boolean;
    SupportsSSE             : boolean;
    SupportsSSE2            : boolean;
    SupportsSSE3            : boolean;
    SupportsHyperThreading  : boolean;
    SupportsIA64            : boolean;
    SupportsXDBit           : boolean;
    SupportsEMT64           : boolean;
    procedure Clear;
    procedure Print;
  end;

var
  { globals used by the ReadProcessorInfo function }
  MBF,MEF     : cardinal;
  Sig         : cardinal;
  A,B,C       : cardinal;
  FF1,FF2,FF3 : cardinal;

function ReadProcessorInfo : ProcessorInfo; register;
begin
  Result.Clear();
  {$REGION 'cpuid'}
  asm
    { save registers used by the register calling convention }
    push eax
    push ebx
    push ecx
    push edx
    { call CPUID }
    xor eax,eax
    cpuid
    mov MBF,eax
    { vendor ID }
    mov A,ebx
    mov B,edx
    mov C,ecx
    { signature & feature flags 1-2 }
    mov eax,1
    cpuid
    mov Sig,eax
    mov FF1,edx
    mov FF2,ecx
    { extended functions }
    mov eax,$8000000
    cpuid
    mov MEF,eax
    { feature flags 3 }
    mov eax,$8000001
    cpuid
    mov FF3,edx
    { restore }
    pop edx
    pop ecx
    pop ebx
    pop eax
  end;
  {$ENDREGION}
  {$REGION 'parse results'}
  Result.MaximumBasicFunction := MBF;
  Result.MaximumExtendedFunction := MEF;
  Result.Signature := Sig;
  Result.VendorID[0] := Chr((A and $000000FF) shr 0);
  Result.VendorID[1] := Chr((A and $0000FF00) shr 8);
  Result.VendorID[2] := Chr((A and $00FF0000) shr 16);
  Result.VendorID[3] := Chr((A and $FF000000) shr 24);

  Result.VendorID[4] := Chr((B and $000000FF) shr 0);
  Result.VendorID[5] := Chr((B and $0000FF00) shr 8);
  Result.VendorID[6] := Chr((B and $00FF0000) shr 16);
  Result.VendorID[7] := Chr((B and $FF000000) shr 24);

  Result.VendorID[8] := Chr((C and $000000FF) shr 0);
  Result.VendorID[9] := Chr((C and $0000FF00) shr 8);
  Result.VendorID[10] := Chr((C and $00FF0000) shr 16);
  Result.VendorID[11] := Chr((C and $FF000000) shr 24);
  Result.VendorID[12] := #0;
  {$ENDREGION}
  {$REGION 'flags'}
  Result.SupportsMMX := (FF1 and (1 shl 23)) <> 0;
  Result.SupportsSSE := (FF1 and (1 shl 25)) <> 0;
  Result.SupportsSSE2 := (FF1 and (1 shl 26)) <> 0;
  Result.SupportsSSE3 := (FF2 and (1 shl 0)) <> 0;
  Result.SupportsHyperThreading := (FF1 and (1 shl 28)) <> 0;
  Result.SupportsIA64 := (FF1 and (1 shl 30)) <> 0;
  Result.SupportsXDBit := (FF3 and (1 shl 20)) <> 0;
  Result.SupportsEMT64 := (FF3 and (1 shl 29)) <> 0;
  {$ENDREGION}
end;

{ ProcessorInfo }

procedure ProcessorInfo.Clear;
begin
  MaximumBasicFunction := 0;
  MaximumExtendedFunction := $80000000;
  VendorID := '';
  Signature := 0;
  SupportsMMX := False;
  SupportsSSE := False;
  SupportsSSE2 := False;
  SupportsSSE3 := False;
  SupportsHyperThreading := False;
  SupportsIA64 := False;
  SupportsXDBit := False;
  SupportsEMT64 := False;
end;

var
  PI : ProcessorInfo;

procedure ProcessorInfo.Print;
begin
  WriteLn('MaximumBasicFunction = ',MaximumBasicFunction);
  WriteLn('MaximumExtendedFunction = ',MaximumExtendedFunction);
  WriteLn('VendorID = "',VendorID,'"');
  WriteLn('Signature = ',Signature);
  WriteLn('SupportsMMX = ',SupportsMMX);
  WriteLn('SupportsSSE = ',SupportsSSE);
  WriteLn('SupportsSSE2 = ',SupportsSSE2);
  WriteLn('SupportsSSE3 = ',SupportsSSE3);
  WriteLn('SupportsHyperThreading = ',SupportsHyperThreading);
  WriteLn('SupportsIA64 = ',SupportsIA64);
  WriteLn('SupportsXDBit = ',SupportsXDBit);
  WriteLn('SupportsEMT64 = ',SupportsEMT64);
end;

begin
  WriteLn('ShowCPUID application version 1.00.');
  Write('Reading processor information...');
  PI := ReadProcessorInfo();
  WriteLn('');
  WriteLn('');
  WriteLn('Results');
  WriteLn('-------');
  PI.Print();
  WriteLn('');
  Write('Press Enter to exit...');
  ReadLn;
end.

If you run this program, it will display something similar to the following:

ShowCPUID application version 1.00.
Reading processor information...

Results
-------
MaximumBasicFunction = 5
MaximumExtendedFunction = 64
VendorID = "GenuineIntel"
Signature = 3892
SupportsMMX = TRUE
SupportsSSE = TRUE
SupportsSSE2 = TRUE
SupportsSSE3 = TRUE
SupportsHyperThreading = TRUE
SupportsIA64 = FALSE
SupportsXDBit = FALSE
SupportsEMT64 = FALSE

Press Enter to exit...

Note that my processor is a Pentium IV, but it unluckily doesn't support EMT64. My bad.