/*
 * mptCPU.cpp
 * ----------
 * Purpose: CPU feature detection.
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "mptCPU.h"

#include "../common/mptStringBuffer.h"

#if defined(MPT_ENABLE_ARCH_INTRINSICS)
#if MPT_COMPILER_MSVC && (defined(MPT_ENABLE_ARCH_X86) || defined(MPT_ENABLE_ARCH_AMD64))
#include <intrin.h>
#endif // MPT_COMPILER_MSVC && (MPT_ENABLE_ARCH_X86 || MPT_ENABLE_ARCH_AMD64)
#endif // MPT_ENABLE_ARCH_INTRINSICS


OPENMPT_NAMESPACE_BEGIN


namespace CPU
{


#if defined(MPT_ENABLE_ARCH_INTRINSICS)


uint32 EnabledFeatures = 0;


#if MPT_COMPILER_MSVC && (defined(MPT_ENABLE_ARCH_X86) || defined(MPT_ENABLE_ARCH_AMD64))


typedef char cpuid_result_string[12];


struct cpuid_result {
	uint32 a;
	uint32 b;
	uint32 c;
	uint32 d;
	std::string as_string() const
	{
		cpuid_result_string result;
		result[0+0] = (b >> 0) & 0xff;
		result[0+1] = (b >> 8) & 0xff;
		result[0+2] = (b >>16) & 0xff;
		result[0+3] = (b >>24) & 0xff;
		result[4+0] = (d >> 0) & 0xff;
		result[4+1] = (d >> 8) & 0xff;
		result[4+2] = (d >>16) & 0xff;
		result[4+3] = (d >>24) & 0xff;
		result[8+0] = (c >> 0) & 0xff;
		result[8+1] = (c >> 8) & 0xff;
		result[8+2] = (c >>16) & 0xff;
		result[8+3] = (c >>24) & 0xff;
		return std::string(result, result + 12);
	}
	std::string as_string4() const
	{
		std::string result;
		result.push_back(static_cast<uint8>((a >>  0) & 0xff));
		result.push_back(static_cast<uint8>((a >>  8) & 0xff));
		result.push_back(static_cast<uint8>((a >> 16) & 0xff));
		result.push_back(static_cast<uint8>((a >> 24) & 0xff));
		result.push_back(static_cast<uint8>((b >>  0) & 0xff));
		result.push_back(static_cast<uint8>((b >>  8) & 0xff));
		result.push_back(static_cast<uint8>((b >> 16) & 0xff));
		result.push_back(static_cast<uint8>((b >> 24) & 0xff));
		result.push_back(static_cast<uint8>((c >>  0) & 0xff));
		result.push_back(static_cast<uint8>((c >>  8) & 0xff));
		result.push_back(static_cast<uint8>((c >> 16) & 0xff));
		result.push_back(static_cast<uint8>((c >> 24) & 0xff));
		result.push_back(static_cast<uint8>((d >>  0) & 0xff));
		result.push_back(static_cast<uint8>((d >>  8) & 0xff));
		result.push_back(static_cast<uint8>((d >> 16) & 0xff));
		result.push_back(static_cast<uint8>((d >> 24) & 0xff));
		return result;
	}
};


static cpuid_result cpuid(uint32 function)
{
	cpuid_result result;
	int CPUInfo[4];
	__cpuid(CPUInfo, function);
	result.a = CPUInfo[0];
	result.b = CPUInfo[1];
	result.c = CPUInfo[2];
	result.d = CPUInfo[3];
	return result;
}


static cpuid_result cpuidex(uint32 function_a, uint32 function_c)
{
	cpuid_result result;
	int CPUInfo[4];
	__cpuidex(CPUInfo, function_a, function_c);
	result.a = CPUInfo[0];
	result.b = CPUInfo[1];
	result.c = CPUInfo[2];
	result.d = CPUInfo[3];
	return result;
}


Info::Info()
{

	cpuid_result VendorString = cpuid(0x00000000u);
	mpt::String::WriteAutoBuf(VendorID) = VendorString.as_string();
	if(VendorString.a >= 0x00000001u)
	{
		cpuid_result StandardFeatureFlags = cpuid(0x00000001u);
		CPUID = StandardFeatureFlags.a;
		uint32 BaseStepping = (StandardFeatureFlags.a >>  0) & 0x0f;
		uint32 BaseModel    = (StandardFeatureFlags.a >>  4) & 0x0f;
		uint32 BaseFamily   = (StandardFeatureFlags.a >>  8) & 0x0f;
		uint32 ExtModel     = (StandardFeatureFlags.a >> 16) & 0x0f;
		uint32 ExtFamily    = (StandardFeatureFlags.a >> 20) & 0xff;
		if(BaseFamily == 0xf)
		{
			Family = static_cast<uint16>(ExtFamily + BaseFamily);
		} else
		{
			Family = static_cast<uint16>(BaseFamily);
		}
		if((BaseFamily == 0x6) || (BaseFamily == 0xf))
		{
			Model = static_cast<uint8>((ExtModel << 4) | (BaseModel << 0));
		} else
		{
			Model = static_cast<uint8>(BaseModel);
		}
		Stepping = static_cast<uint8>(BaseStepping);
		if(StandardFeatureFlags.d & (1<<23)) AvailableFeatures |= feature::mmx;
		if(StandardFeatureFlags.d & (1<<25)) AvailableFeatures |= feature::sse;
		if(StandardFeatureFlags.d & (1<<26)) AvailableFeatures |= feature::sse2;
		if(StandardFeatureFlags.c & (1<< 0)) AvailableFeatures |= feature::sse3;
		if(StandardFeatureFlags.c & (1<< 9)) AvailableFeatures |= feature::ssse3;
		if(StandardFeatureFlags.c & (1<<19)) AvailableFeatures |= feature::sse4_1;
		if(StandardFeatureFlags.c & (1<<20)) AvailableFeatures |= feature::sse4_2;
		if(StandardFeatureFlags.c & (1<<28)) AvailableFeatures |= feature::avx;
	}
	if(VendorString.a >= 0x00000007u)
	{
		cpuid_result ExtendedFeatures = cpuidex(0x00000007u, 0x00000000u);
		if(ExtendedFeatures.b & (1<< 5)) AvailableFeatures |= feature::avx2;
	}

	cpuid_result ExtendedVendorString = cpuid(0x80000000u);
	if(ExtendedVendorString.a >= 0x80000001u)
	{
		cpuid_result ExtendedFeatureFlags = cpuid(0x80000001u);
		if(ExtendedFeatureFlags.d & (1<<29)) AvailableFeatures |= feature::lm;
	}
	if(ExtendedVendorString.a >= 0x80000004u)
	{
		mpt::String::WriteAutoBuf(BrandID) = cpuid(0x80000002u).as_string4() + cpuid(0x80000003u).as_string4() + cpuid(0x80000004u).as_string4();
	}

}


#elif MPT_COMPILER_MSVC && (defined(MPT_ENABLE_ARCH_X86) || defined(MPT_ENABLE_ARCH_AMD64))


Info::Info()
{

	if(IsProcessorFeaturePresent(PF_MMX_INSTRUCTIONS_AVAILABLE) != 0)    AvailableFeatures |= feature::mmx;
	if(IsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE) != 0)   AvailableFeatures |= feature::sse;
	if(IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE) != 0) AvailableFeatures |= feature::sse2;
	if(IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE) != 0)   AvailableFeatures |= feature::sse3;

}


#else // !(MPT_COMPILER_MSVC && (MPT_ENABLE_ARCH_X86 || MPT_ENABLE_ARCH_AMD64))


Info::Info()
{
	return;
}


#endif // MPT_COMPILER_MSVC && (MPT_ENABLE_ARCH_X86 || MPT_ENABLE_ARCH_AMD64)


const Info & Info::Get()
{
	static Info info;
	return info;
}


struct InfoInitializer
{
	InfoInitializer()
	{
		Info::Get();
	}
};


static InfoInitializer g_InfoInitializer;


void EnableAvailableFeatures()
{
	EnabledFeatures = Info::Get().AvailableFeatures;
}


#endif // MPT_ENABLE_ARCH_INTRINSICS


uint32 GetMinimumFeatures()
{
	uint32 flags = 0;
	#ifdef MPT_ENABLE_ARCH_INTRINSICS
		#if MPT_COMPILER_MSVC
			#if defined(_M_X64)
				flags |= feature::lm | feature::sse | feature::sse2;
			#elif defined(_M_IX86)
				#if defined(_M_IX86_FP)
					#if (_M_IX86_FP >= 2)
						flags |= feature::sse | feature::sse2;
					#elif (_M_IX86_FP == 1)
						flags |= feature::sse;
					#endif
				#endif
			#endif
			#if defined(__AVX__)
				flags |= feature::avx;
			#endif
			#if defined(__AVX2__)
				flags |= feature::avx2;
			#endif
		#endif	
	#endif // MPT_ENABLE_ARCH_INTRINSICS
	return flags;
}



} // namespace CPU


OPENMPT_NAMESPACE_END