/*
								+--------------------------------+
								|                                |
								|    ***  CPU-Id wrapper  ***    |
								|                                |
								|  Copyright © -tHE SWINe- 2010  |
								|                                |
								|           CPUId.cpp            |
								|                                |
								+--------------------------------+
*/

/**
 *	@file low/CPUId.cpp
 *	@brief CPU-Id wrapper
 *	@date 2010
 *	@author -tHE SWINe-
 */

#include "../NewFix.h"
#include "../CallStack.h"
#include <string.h>
#include "../Integer.h"
#include "CPUId.h"

#if !defined(_MSC_VER) || defined(__MWERKS__)
#if defined(_M_X64) || defined(_M_AMD64) || defined(_M_IA64) || defined(__x86_64) || defined(__amd64) || defined(__ia64)
inline bool cpuid_supported()
{
	return true; // on x64 can safely assume it is supported
}
#else // _M_X64 || _M_AMD64 || _M_IA64 || __x86_64 || __amd64 || __ia64
inline bool cpuid_supported()
{
	int32_t b_supported;
	asm("pushfl					\n\t"
		"pop %%ecx				\n\t"
		"mov %%ecx, %%eax		\n\t"
		"xor $0x200000, %%eax	\n\t" // flip ID bit in EFLAGS
		"push %%eax				\n\t"
		"popfl					\n\t"
		"pushfl					\n\t"
		"pop %%eax				\n\t"
		"xor %%ecx, %%eax		\n\t"
		"shr $21, %%eax			\n\t"
		"and $1, %%eax			\n\t"
		: "=a" (b_supported));
	return b_supported == 1;
}
#endif // _M_X64 || _M_AMD64 || _M_IA64 || __x86_64 || __amd64 || __ia64

#if defined(__i386__) && defined(__PIC__)
/* %ebx may be the PIC register.  */
#define __cpuid(level,a,b,c,d)						\
	asm("xchgl\t%%ebx, %1\n\t"						\
		"cpuid\n\t"									\
		"xchgl\t%%ebx, %1\n\t"						\
		: "=a" (a), "=r" (b), "=c" (c), "=d" (d)	\
		: "0" (level))
#else // __i386__ && __PIC__
#define __cpuid(level,a,b,c,d)						\
	asm("cpuid\n\t"									\
		: "=a" (a), "=b" (b), "=c" (c), "=d" (d)	\
		: "0" (level))
#endif // __i386__ && __PIC__

inline void cpuid(uint32_t n_level, uint32_t &r_n_eax, uint32_t &r_n_ebx, uint32_t &r_n_ecx, uint32_t &r_n_edx)
{
	uint32_t n_eax, n_ebx, n_ecx, n_edx;
	__cpuid(n_level, n_eax, n_ebx, n_ecx, n_edx);
	r_n_eax = n_eax;
	r_n_ebx = n_ebx;
	r_n_ecx = n_ecx;
	r_n_edx = n_edx;
}
#else // !_MSC_VER || __MWERKS__
#if defined(_M_X64) || defined(_M_AMD64) || defined(_M_IA64) || defined(__x86_64) || defined(__amd64) || defined(__ia64)
inline bool cpuid_supported()
{
	return true; // on x64 can safely assume it is supported
}

#ifdef __HAVE_INTRINSICS
#include <intrin.h>
inline void cpuid(uint32_t n_level, uint32_t &r_n_eax, uint32_t &r_n_ebx, uint32_t &r_n_ecx, uint32_t &r_n_edx)
{
	int p_cpu_info[4];
	__cpuid(p_cpu_info, n_level);
	r_n_eax = p_cpu_info[0];
	r_n_ebx = p_cpu_info[1];
	r_n_ecx = p_cpu_info[2];
	r_n_edx = p_cpu_info[3];
}
#else // __HAVE_INTRINSICS
// use external .asm
extern "C" {
typedef struct cpuid_args_s { 
	uint32_t eax;
	uint32_t ebx;
	uint32_t ecx;
	uint32_t edx;
} CPUID_ARGS;

void cpuid64(CPUID_ARGS *p);
}

inline void cpuid(uint32_t n_level, uint32_t &r_n_eax, uint32_t &r_n_ebx, uint32_t &r_n_ecx, uint32_t &r_n_edx)
{
	CPUID_ARGS a = {n_level, 0, 0, 0};
	cpuid64(&a);
	r_n_eax = a.eax;
	r_n_ebx = a.ebx;
	r_n_ecx = a.ecx;
	r_n_edx = a.edx;
}
#endif // __HAVE_INTRINSICS
#else // _M_X64 || _M_AMD64 || _M_IA64 || __x86_64 || __amd64 || __ia64
inline bool cpuid_supported()
{
	int32_t b_supported;
	__asm {
		pushfd
		pop ecx
		mov eax, ecx
		xor eax, 0x200000 // flip ID bit in EFLAGS
		push eax
		popfd
		pushfd
		pop eax
		xor eax, ecx
		shr eax, 21
		and eax, 1
		mov b_supported, eax
	}
	return b_supported == 1;
}

inline void cpuid(uint32_t n_level, uint32_t &r_n_eax, uint32_t &r_n_ebx, uint32_t &r_n_ecx, uint32_t &r_n_edx)
{
	uint32_t n_eax, n_ebx, n_ecx, n_edx;
	__asm {
		mov eax, n_level
		cpuid
		mov n_eax, eax
		mov n_ebx, ebx
		mov n_ecx, ecx
		mov n_edx, edx
	}
	r_n_eax = n_eax;
	r_n_ebx = n_ebx;
	r_n_ecx = n_ecx;
	r_n_edx = n_edx;
}
#endif // _M_X64 || _M_AMD64 || _M_IA64 || __x86_64 || __amd64 || __ia64
#endif // !_MSC_VER || __MWERKS__

/*
 *								=== CCPU_Id ===
 */

CCPU_Id::CCPU_Id()
{
	char p_s_vendor[12];
	char p_s_brand[48];
	uint32_t n_info_bits;
	uint32_t p_feature_bits[2];
	uint32_t n_ext_fflags;

	bool b_supported = cpuid_supported();

	if(b_supported) {
		{
			uint32_t *p_vendor = (uint32_t*)p_s_vendor, n_tmp;
			cpuid(0x00, n_tmp, p_vendor[0], p_vendor[2], p_vendor[1]);
		}
		// get CPU vendor

		{
			uint32_t n_tmp;
			cpuid(0x01, n_info_bits, n_tmp, p_feature_bits[0], p_feature_bits[1]);
		}
		// get basic flags

		{
			uint32_t n_tmp0, n_tmp1, n_tmp2;
			cpuid(0x80000001, n_tmp0, n_tmp1, n_tmp2, n_ext_fflags);
		}
		// get extended flags

		{
			uint32_t *p_brand = (uint32_t*)p_s_brand;
			cpuid(0x80000002, p_brand[0], p_brand[1], p_brand[2], p_brand[3]);
			p_brand += 4;
			cpuid(0x80000003, p_brand[0], p_brand[1], p_brand[2], p_brand[3]);
			p_brand += 4;
			cpuid(0x80000004, p_brand[0], p_brand[1], p_brand[2], p_brand[3]);
		}
		// get CPU brand
	}
	// call cpuid 0, 1, 0x80000001 and 0x80000002 - 0x80000004

	if(b_supported) {
		m_b_supported = true;

		memcpy(m_p_s_brand, p_s_brand, 48 * sizeof(char));
		m_p_s_brand[48] = 0;
		memcpy(m_p_s_vendor, p_s_vendor, 12 * sizeof(char));
		m_p_s_vendor[12] = 0;
		// copy strings (possibly not null-terminated) to member variables

		m_n_ext_fflags = n_ext_fflags;
		m_n_info_bits = n_info_bits;
		m_p_feature_bits[0] = p_feature_bits[0];
		m_p_feature_bits[1] = p_feature_bits[1];
		// copy feature flags
	} else {
		m_b_supported = false;

		m_p_s_brand[0] = 0;
		m_p_s_vendor[0] = 0;
		n_ext_fflags = 0;
		m_n_info_bits = 0;
		m_p_feature_bits[0] = 0;
		m_p_feature_bits[1] = 0;
		// CPUId not supported
	}
}

bool CCPU_Id::b_Supported() const
{
	return m_b_supported;
}

const char *CCPU_Id::p_s_Vendor() const
{
	return m_p_s_vendor;
}

const char *CCPU_Id::p_s_Brand() const
{
	return m_p_s_brand;
}

int CCPU_Id::n_Feature_Bits() const
{
	return int(m_n_info_bits);
}

int CCPU_Id::n_Feature_Bits(int n_first_bit, int n_last_bit) const
{
	n_last_bit -= n_first_bit;
	++ n_last_bit;
	// get length

	unsigned int n_mask = (1 << (n_last_bit - 1)) | ((1 << (n_last_bit - 1)) - 1);
	// calculate mask (works for 32bits as well)

	return int((m_n_info_bits >> n_first_bit) & n_mask);
	// shift and mask requested bits
}

int CCPU_Id::n_Stepping() const
{
	return n_Feature_Bits(0, 3);
}

int CCPU_Id::n_Model() const
{
	return n_Feature_Bits(4, 7) + (n_Feature_Bits(16, 19) << 4); // model + ext model << 4
}

int CCPU_Id::n_Family() const
{
	int n_family = n_Feature_Bits(8, 11);
	if(n_family == 0x0 || n_family == 0xf) // 0x0, 0xf == refer to ext family
		return n_family + n_Feature_Bits(20, 27); // family + ext family
	return n_family; // family only
}

int CCPU_Id::n_Type() const
{
	return n_Feature_Bits(12, 13);
}

int CCPU_Id::n_FeatureFlags_C() const
{
	return m_p_feature_bits[0];
}

int CCPU_Id::n_FeatureFlags_D() const
{
	return m_p_feature_bits[1];
}

bool CCPU_Id::b_Have_MMX() const
{
	return ((m_p_feature_bits[1] >> 23) & 1) != 0;
}

bool CCPU_Id::b_Have_3DNow() const
{
	return ((m_n_ext_fflags >> 31) & 1) != 0;
}

bool CCPU_Id::b_Have_3DNow_Plus() const
{
	return ((m_n_ext_fflags >> 30) & 1) != 0;
}

bool CCPU_Id::b_Have_SSE() const
{
	return ((m_p_feature_bits[1] >> 25) & 1) != 0;
}

bool CCPU_Id::b_Have_SSE2() const
{
	return ((m_p_feature_bits[1] >> 26) & 1) != 0;
}

bool CCPU_Id::b_Have_SSE3() const
{
	return ((m_p_feature_bits[0] >> 0) & 1) != 0;
}

bool CCPU_Id::b_Have_SSSE3() const
{
	return ((m_p_feature_bits[0] >> 9) & 1) != 0;
}

bool CCPU_Id::b_Have_SSE4a() const
{
	return ((m_n_ext_fflags >> 6) & 1) != 0;
}

bool CCPU_Id::b_Have_SSE4_1() const
{
	return ((m_p_feature_bits[0] >> 19) & 1) != 0;
}

bool CCPU_Id::b_Have_SSE4_2() const
{
	return ((m_p_feature_bits[0] >> 20) & 1) != 0;
}

bool CCPU_Id::b_Have_AVX() const
{
	return ((m_p_feature_bits[0] >> 28) & 1) != 0;
}

void CCPU_Id::cpuid(uint32_t n_level, uint32_t &r_n_eax, uint32_t &r_n_ebx, uint32_t &r_n_ecx, uint32_t &r_n_edx)
{
	::cpuid(n_level, r_n_eax, r_n_ebx, r_n_ecx, r_n_edx);
}

/*
 *								=== ~CCPU_Id ===
 */
