/*
    libmaus2
    Copyright (C) 2009-2020 German Tischler
    Copyright (C) 2011-2013 Genome Research Limited

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <libmaus2/arch/I386Features.hpp>
#include <cstring>
#include <new>

#if defined(__linux)
#include <unistd.h>
#endif

#if defined(LIBMAUS2_HAVE_IMMINTRIN_H)
#include <immintrin.h>
#endif

#include <libmaus2/LibMausWindows.hpp>

/**
 * @return true if CPU supports SSE
 **/
bool libmaus2::arch::I386Features::hasSSE()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("sse");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_SSE);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports SSE2
 **/
bool libmaus2::arch::I386Features::hasSSE2()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("sse2");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_SSE2);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports SSE3
 **/
bool libmaus2::arch::I386Features::hasSSE3()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("sse3");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_SSE3);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports SSSE3
 **/
bool libmaus2::arch::I386Features::hasSSSE3()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("ssse3");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_SSSE3);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports SSE4.1
 **/
bool libmaus2::arch::I386Features::hasSSE41()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("sse4.1");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_SSE4_1);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports SSE4.2
 **/
bool libmaus2::arch::I386Features::hasSSE42()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("sse4.2");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_SSE4_2);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports popcnt
 **/
bool libmaus2::arch::I386Features::hasPopCnt()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("popcnt");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_POPCNT);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports avx
 **/
bool libmaus2::arch::I386Features::hasAVX()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("avx");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_AVX);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports avx2
 **/
bool libmaus2::arch::I386Features::hasAVX2()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("avx2");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_AVX2);
	#else
	return false;
	#endif
}
/**
 * @return true if CPU supports PCLMULDQ
 **/
bool libmaus2::arch::I386Features::hasPCLMULDQ()
{
	#if defined(LIBMAUS2_HAVE_BUILTIN_CPU_SUPPORTS)
	return __builtin_cpu_supports("pclmul");
	#elif defined(LIBMAUS2_HAVE_MAY_I_USE_CPU_FEATURE)
	return _may_i_use_cpu_feature(_FEATURE_PCLMULQDQ);
	#else
	return false;
	#endif
}

bool libmaus2::arch::I386Features::hasFeature(char const * name)
{
	typedef bool (*func_type)();
	typedef std::pair<char const *, func_type> feature_func;
	static char const * cnull = 0;
	func_type nullfun = 0;
	static feature_func sup[] = {
		feature_func("sse", hasSSE),
		feature_func("sse2", hasSSE2),
		feature_func("sse3", hasSSE3),
		feature_func("ssse3", hasSSSE3),
		feature_func("sse4_1", hasSSE41),
		feature_func("sse4_2", hasSSE42),
		feature_func("avx", hasAVX),
		feature_func("avx2", hasAVX2),
		feature_func("pclmulqdq", hasPCLMULDQ),
		feature_func(cnull,nullfun)
	};

	for ( feature_func * p = &sup[0]; p->first; ++p )
		if ( strcmp(name,p->first) == 0 )
			return p->second();

	return false;
}
