/**
 * Identify the characteristics of the host CPU.
 *
 * Implemented according to:

- AP-485 Intel(C) Processor Identification and the CPUID Instruction
        $(LINK http://www.intel.com/design/xeon/applnots/241618.htm)

- Intel(R) 64 and IA-32 Architectures Software Developer's Manual, Volume 2A: Instruction Set Reference, A-M
        $(LINK http://developer.intel.com/design/pentium4/manuals/index_new.htm)

- AMD CPUID Specification Publication # 25481
        $(LINK http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/25481.pdf)

Example:
---
import std.cpuid;
import std.stdio;

void main()
{
    writefln(std.cpuid.toString());
}
---

AUTHORS:        Tomas Lindquist Olsen &lt;tomas@famolsen.dk&gt;
                (slightly altered by Walter Bright)
COPYRIGHT:      Public Domain

 * BUGS:        Only works on x86 CPUs
 *
 * Macros:
 *      WIKI = Phobos/StdCpuid
 *      COPYRIGHT = Public Domain
 */

module std.cpuid;

import std.string;

version(D_InlineAsm_X86)
    version = Asm;
version(D_InlineAsm_X86_64)
    version = Asm;

version(Asm)
{
    /// Returns everything as a printable string
    char[] toString()
    {
        char[] feats;
        if (mmx)                        feats ~= "MMX ";
        if (fxsr)                       feats ~= "FXSR ";
        if (sse)                        feats ~= "SSE ";
        if (sse2)                       feats ~= "SSE2 ";
        if (sse3)                       feats ~= "SSE3 ";
        if (ssse3)                      feats ~= "SSSE3 ";
        if (amd3dnow)                   feats ~= "3DNow! ";
        if (amd3dnowExt)                feats ~= "3DNow!+ ";
        if (amdMmx)                     feats ~= "MMX+ ";
        if (ia64)                       feats ~= "IA-64 ";
        if (amd64)                      feats ~= "AMD64 ";
        if (hyperThreading)             feats ~= "HTT";

        return format(
                "Vendor string:    %s\n", vendor,
                "Processor string: %s\n", processor,
                "Signature:        Family=%d Model=%d Stepping=%d\n", family, model, stepping,
                "Features:         %s\n", feats,
                "Multithreading:   %d threads / %d cores\n", threadsPerCPU, coresPerCPU);

    }

    /// Returns vendor string
    char[] vendor()             {return vendorStr;}
    /// Returns processor string
    char[] processor()          {return processorStr;}

    /// Is MMX supported?
    bool mmx()                  {return (flags&MMX_BIT)!=0;}
    /// Is FXSR supported?
    bool fxsr()                 {return (flags&FXSR_BIT)!=0;}
    /// Is SSE supported?
    bool sse()                  {return (flags&SSE_BIT)!=0;}
    /// Is SSE2 supported?
    bool sse2()                 {return (flags&SSE2_BIT)!=0;}
    /// Is SSE3 supported?
    bool sse3()                 {return (misc&SSE3_BIT)!=0;}
    /// Is SSSE3 supported?
    bool ssse3()                {return (misc&SSSE3_BIT)!=0;}

    /// Is AMD 3DNOW supported?
    bool amd3dnow()             {return (exflags&AMD_3DNOW_BIT)!=0;}
    /// Is AMD 3DNOW Ext supported?
    bool amd3dnowExt()          {return (exflags&AMD_3DNOW_EXT_BIT)!=0;}
    /// Is AMD MMX supported?
    bool amdMmx()               {return (exflags&AMD_MMX_BIT)!=0;}

    /// Is this an Intel Architecture IA64?
    bool ia64()                 {return (flags&IA64_BIT)!=0;}
    /// Is this an AMD 64?
    bool amd64()                {return (exflags&AMD64_BIT)!=0;}

    /// Is hyperthreading supported?
    bool hyperThreading()       {return (flags&HTT_BIT)!=0;}
    /// Returns number of threads per CPU
    uint threadsPerCPU()        {return maxThreads;}
    /// Returns number of cores in CPU
    uint coresPerCPU()          {return maxCores;}

    /// Is this an Intel processor?
    bool intel()                {return manufac==INTEL;}
    /// Is this an AMD processor?
    bool amd()                  {return manufac==AMD;}

    /// Returns stepping
    uint stepping()             {return _stepping;}
    /// Returns model
    uint model()                {return _model;}
    /// Returns family
    uint family()               {return _family;}
    //uint processorType()      {return (signature>>>12)&0x3;}

    static this()
    {
        getVendorString();
        getProcessorString();
        getFeatureFlags();

        // stepping / family / model
        _stepping = signature&0xF;
        uint fbase = (signature>>>8)&0xF;
        uint fex = (signature>>>20)&0xFF;
        uint mbase = (signature>>>4)&0xF;
        uint mex = (signature>>>16)&0xF;

        // vendor specific
        void function() threadFn;
        switch(vendorStr)
        {
            case "GenuineIntel":
                    manufac = INTEL;
                    threadFn = &getThreadingIntel;
                    if (fbase == 0xF)
                            _family = fbase+fex;
                    else
                            _family = fbase;
                    if (_family == 0x6 || _family == 0xF)
                            _model = mbase+(mex<<4);
                    else
                            _model = mbase;
                    break;

            case "AuthenticAMD":
                    manufac = AMD;
                    threadFn = &getThreadingAMD;
                    if (fbase < 0xF)
                    {
                            _family = fbase;
                            _model = mbase;
                    }
                    else
                    {
                            _family = fbase+fex;
                            _model = mbase+(mex<<4);
                    }
                    break;

            default:
                    manufac = OTHER;
        }

        // threading details
        if (hyperThreading && threadFn !is null)
        {
                threadFn();
        }
    }

private:
    // feature flags
    enum : uint
    {
            MMX_BIT = 1<<23,
            FXSR_BIT = 1<<24,
            SSE_BIT = 1<<25,
            SSE2_BIT = 1<<26,
            HTT_BIT = 1<<28,
            IA64_BIT = 1<<30
    }
    // feature flags misc
    enum : uint
    {
            SSE3_BIT = 1,
            SSSE3_BIT = 1<<9
    }
    // extended feature flags
    enum : uint
    {
            AMD_MMX_BIT = 1<<22,
            AMD64_BIT = 1<<29,
            AMD_3DNOW_EXT_BIT = 1<<30,
            AMD_3DNOW_BIT = 1<<31
    }
    // manufacturer
    enum
    {
            OTHER,
            INTEL,
            AMD
    }

    uint flags, misc, exflags, apic, signature;
    uint _stepping, _model, _family;

    char[12] vendorStr = "";
    char[] processorStr = "";

    uint maxThreads=1;
    uint maxCores=1;
    uint manufac=OTHER;

    /* **
     * fetches the cpu vendor string
     */
    private void getVendorString()
    {
        char* dst = vendorStr.ptr;
        // puts the vendor string into dst
        version (D_InlineAsm_X86)
            asm
            {
                mov EAX, 0                  ;
                cpuid                       ;
                mov EAX, dst                ;
                mov [EAX], EBX              ;
                mov [EAX+4], EDX            ;
                mov [EAX+8], ECX            ;
            }
        else version (D_InlineAsm_X86_64)
            asm
            {
                mov EAX, 0                  ;
                cpuid                       ;
                mov RAX, dst                ;
                mov [RAX], EBX              ;
                mov [RAX+4], EDX            ;
                mov [RAX+8], ECX            ;
            }
        else
            static assert(0);
    }

    private void getProcessorString()
    {
        char[48] buffer;
        char* dst = buffer.ptr;
        // puts the processor string into dst
        version (D_InlineAsm_X86)
        asm
        {
            mov EAX, 0x8000_0000        ;
            cpuid                       ;
            cmp EAX, 0x8000_0004        ;
            jb PSLabel                  ; // no support
            push EDI                    ;
            mov EDI, dst                ;
            mov EAX, 0x8000_0002        ;
            cpuid                       ;
            mov [EDI], EAX              ;
            mov [EDI+4], EBX            ;
            mov [EDI+8], ECX            ;
            mov [EDI+12], EDX           ;
            mov EAX, 0x8000_0003        ;
            cpuid                       ;
            mov [EDI+16], EAX           ;
            mov [EDI+20], EBX           ;
            mov [EDI+24], ECX           ;
            mov [EDI+28], EDX           ;
            mov EAX, 0x8000_0004        ;
            cpuid                       ;
            mov [EDI+32], EAX           ;
            mov [EDI+36], EBX           ;
            mov [EDI+40], ECX           ;
            mov [EDI+44], EDX           ;
            pop EDI                     ;
        PSLabel:                        ;
        }
        else version (D_InlineAsm_X86_64)
        asm
        {
            mov EAX, 0x8000_0000        ;
            cpuid                       ;
            cmp EAX, 0x8000_0004        ;
            jb PSLabel                  ; // no support
            mov RDI, dst                ;
            mov EAX, 0x8000_0002        ;
            cpuid                       ;
            mov [RDI], EAX              ;
            mov [RDI+4], EBX            ;
            mov [RDI+8], ECX            ;
            mov [RDI+12], EDX           ;
            mov EAX, 0x8000_0003        ;
            cpuid                       ;
            mov [RDI+16], EAX           ;
            mov [RDI+20], EBX           ;
            mov [RDI+24], ECX           ;
            mov [RDI+28], EDX           ;
            mov EAX, 0x8000_0004        ;
            cpuid                       ;
            mov [RDI+32], EAX           ;
            mov [RDI+36], EBX           ;
            mov [RDI+40], ECX           ;
            mov [RDI+44], EDX           ;
        PSLabel:                        ;
        }
        else
            static assert(0);

        if (buffer[0] == char.init) // no support
            return;

        // seems many intel processors prepend whitespace
        processorStr = std.string.strip(std.string.toString(dst)).dup;
    }

    private void getFeatureFlags()
    {
        uint f,m,e,a,s;
        version (D_InlineAsm_X86)
        asm
        {
            mov EAX, 0                  ;
            cpuid                       ;
            cmp EAX, 1                  ;
            jb FeatLabel                ; // no support
            mov EAX, 1                  ;
            cpuid                       ;
            mov f, EDX                  ;
            mov m, ECX                  ;
            mov a, EBX                  ;
            mov s, EAX                  ;

        FeatLabel:                      ;
            mov EAX, 0x8000_0000        ;
            cpuid                       ;
            cmp EAX, 0x8000_0001        ;
            jb FeatLabel2               ; // no support
            mov EAX, 0x8000_0001        ;
            cpuid                       ;
            mov e, EDX                  ;

        FeatLabel2:
            ;
        }
        else version (D_InlineAsm_X86_64)
        asm
        {
            mov EAX, 0                  ;
            cpuid                       ;
            cmp EAX, 1                  ;
            jb FeatLabel                ; // no support
            mov EAX, 1                  ;
            cpuid                       ;
            mov f, EDX                  ;
            mov m, ECX                  ;
            mov a, EBX                  ;
            mov s, EAX                  ;

        FeatLabel:                      ;
            mov EAX, 0x8000_0000        ;
            cpuid                       ;
            cmp EAX, 0x8000_0001        ;
            jb FeatLabel2               ; // no support
            mov EAX, 0x8000_0001        ;
            cpuid                       ;
            mov e, EDX                  ;

        FeatLabel2:
            ;
        }
        else
            static assert(0);
        flags = f;
        misc = m;
        exflags = e;
        apic = a;
        signature = s;
    }

    private void getThreadingIntel()
    {
        uint n;
        ubyte b = 0;
        version (D_InlineAsm_X86)
        asm
        {
            mov EAX, 0                  ;
            cpuid                       ;
            cmp EAX, 4                  ;
            jb IntelSingle              ;
            mov EAX, 4                  ;
            mov ECX, 0                  ;
            cpuid                       ;
            mov n, EAX                  ;
            mov b, 1                    ;
        IntelSingle:                    ;
        }
        else version (D_InlineAsm_X86_64)
        asm
        {
            mov EAX, 0                  ;
            cpuid                       ;
            cmp EAX, 4                  ;
            jb IntelSingle              ;
            mov EAX, 4                  ;
            mov ECX, 0                  ;
            cpuid                       ;
            mov n, EAX                  ;
            mov b, 1                    ;
        IntelSingle:                    ;
        }
        else
            static assert(0);
        if (b != 0)
        {
            maxCores = ((n>>>26)&0x3F)+1;
            maxThreads = (apic>>>16)&0xFF;
        }
        else
        {
            maxCores = maxThreads = 1;
        }
    }

    private void getThreadingAMD()
    {
        ubyte n;
        ubyte b = 0;
        version (D_InlineAsm_X86)
        asm
        {
            mov EAX, 0x8000_0000        ;
            cpuid                       ;
            cmp EAX, 0x8000_0008        ;
            jb AMDSingle                ;
            mov EAX, 0x8000_0008        ;
            cpuid                       ;
            mov n, CL                   ;
            mov b, 1                    ;
        AMDSingle:                      ;
        }
        else version (D_InlineAsm_X86_64)
        asm
        {
            mov EAX, 0x8000_0000        ;
            cpuid                       ;
            cmp EAX, 0x8000_0008        ;
            jb AMDSingle                ;
            mov EAX, 0x8000_0008        ;
            cpuid                       ;
            mov n, CL                   ;
            mov b, 1                    ;
        AMDSingle:                      ;
        }
        else
            static assert(0);
        if (b != 0)
        {
            maxCores = n+1;
            maxThreads = (apic>>>16)&0xFF;
        }
        else
        {
            maxCores = maxThreads = 1;
        }
    }
}
else
{
    char[] toString() { return "unknown CPU\n"; }

    char[] vendor()             {return "unknown vendor"; }
    char[] processor()          {return "unknown processor"; }

    bool mmx()                  {return false; }
    bool fxsr()                 {return false; }
    bool sse()                  {return false; }
    bool sse2()                 {return false; }
    bool sse3()                 {return false; }
    bool ssse3()                {return false; }

    bool amd3dnow()             {return false; }
    bool amd3dnowExt()          {return false; }
    bool amdMmx()               {return false; }

    bool ia64()                 {return false; }
    bool amd64()                {return false; }

    bool hyperThreading()       {return false; }
    uint threadsPerCPU()        {return 0; }
    uint coresPerCPU()          {return 0; }

    bool intel()                {return false; }
    bool amd()                  {return false; }

    uint stepping()             {return 0; }
    uint model()                {return 0; }
    uint family()               {return 0; }
}
