/*
 * mono-hwcap-x86.c: x86 hardware feature detection
 *
 * Authors:
 *    Alex Rønne Petersen (alexrp@xamarin.com)
 *    Elijah Taylor (elijahtaylor@google.com)
 *    Miguel de Icaza (miguel@xamarin.com)
 *    Neale Ferguson (Neale.Ferguson@SoftwareAG-usa.com)
 *    Paolo Molaro (lupus@xamarin.com)
 *    Rodrigo Kumpera (kumpera@gmail.com)
 *    Sebastien Pouliot (sebastien@xamarin.com)
 *    Zoltan Varga (vargaz@xamarin.com)
 *
 * Copyright 2003 Ximian, Inc.
 * Copyright 2003-2011 Novell, Inc
 * Copyright 2006 Broadcom
 * Copyright 2007-2008 Andreas Faerber
 * Copyright 2011-2013 Xamarin Inc
 */

#include "mono/utils/mono-hwcap-x86.h"

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

#if defined(_MSC_VER)
#include <intrin.h>
#endif

gboolean mono_hwcap_x86_is_xen = FALSE;
gboolean mono_hwcap_x86_has_cmov = FALSE;
gboolean mono_hwcap_x86_has_fcmov = FALSE;
gboolean mono_hwcap_x86_has_sse1 = FALSE;
gboolean mono_hwcap_x86_has_sse2 = FALSE;
gboolean mono_hwcap_x86_has_sse3 = FALSE;
gboolean mono_hwcap_x86_has_ssse3 = FALSE;
gboolean mono_hwcap_x86_has_sse41 = FALSE;
gboolean mono_hwcap_x86_has_sse42 = FALSE;
gboolean mono_hwcap_x86_has_sse4a = FALSE;

#if defined(MONO_CROSS_COMPILE)
void
mono_hwcap_arch_init (void)
{
}
#else
static gboolean
cpuid (int id, int *p_eax, int *p_ebx, int *p_ecx, int *p_edx)
{
#if defined(_MSC_VER)
	int info [4];
#endif

	/* First, make sure we can use cpuid if we're on 32-bit. */
#if defined(TARGET_X86)
	gboolean have_cpuid = FALSE;

#if defined(_MSC_VER)
	__asm {
		pushfd
		pop eax
		mov edx, eax
		xor eax, 0x200000
		push eax
		popfd
		pushfd
		pop eax
		xor eax, edx
		and eax, 0x200000
		mov have_cpuid, eax
	}
#else
	__asm__ __volatile__ (
		"pushfl\n\t"
		"popl\t%%eax\n\t"
		"movl\t%%eax, %%edx\n\t"
		"xorl\t$0x200000, %%eax\n\t"
		"pushl\t%%eax\n\t"
		"popfl\n\t"
		"pushfl\n\t"
		"popl\t%%eax\n\t"
		"xorl\t%%edx, %%eax\n\t"
		"andl\t$0x200000, %%eax\n\t"
		"movl\t%%eax, %0\n\t"
		: "=r" (have_cpuid)
		:
		: "%eax", "%edx"
	);
#endif

	if (!have_cpuid)
		return FALSE;
#endif

	/* Now issue the actual cpuid instruction. We can use
	   MSVC's __cpuid on both 32-bit and 64-bit. */
#if defined(_MSC_VER)
	__cpuid (info, id);
	*p_eax = info [0];
	*p_ebx = info [1];
	*p_ecx = info [2];
	*p_edx = info [3];
#elif defined(TARGET_X86)
	/* This complicated stuff is necessary because EBX
	   may be used by the compiler in PIC mode. */
	__asm__ __volatile__ (
		"xchgl\t%%ebx, %k1\n\t"
		"cpuid\n\t"
		"xchgl\t%%ebx, %k1\n\t"
		: "=a" (*p_eax), "=&r" (*p_ebx), "=c" (*p_ecx), "=d" (*p_edx)
		: "0" (id)
	);
#else
	__asm__ __volatile__ (
		"cpuid\n\t"
		: "=a" (*p_eax), "=b" (*p_ebx), "=c" (*p_ecx), "=d" (*p_edx)
		: "a" (id)
	);
#endif

	return TRUE;
}

void
mono_hwcap_arch_init (void)
{
	int eax, ebx, ecx, edx;

	if (cpuid (1, &eax, &ebx, &ecx, &edx)) {
		if (edx & (1 << 15)) {
			mono_hwcap_x86_has_cmov = TRUE;

			if (edx & 1)
				mono_hwcap_x86_has_fcmov = TRUE;
		}

		if (edx & (1 << 25))
			mono_hwcap_x86_has_sse1 = TRUE;

		if (edx & (1 << 26))
			mono_hwcap_x86_has_sse2 = TRUE;

		if (ecx & (1 << 0))
			mono_hwcap_x86_has_sse3 = TRUE;

		if (ecx & (1 << 9))
			mono_hwcap_x86_has_ssse3 = TRUE;

		if (ecx & (1 << 19))
			mono_hwcap_x86_has_sse41 = TRUE;

		if (ecx & (1 << 20))
			mono_hwcap_x86_has_sse42 = TRUE;
	}

	if (cpuid (0x80000000, &eax, &ebx, &ecx, &edx)) {
		if ((unsigned int) eax >= 0x80000001 && ebx == 0x68747541 && ecx == 0x444D4163 && edx == 0x69746E65) {
			if (cpuid (0x80000001, &eax, &ebx, &ecx, &edx)) {
				if (ecx & (1 << 6))
					mono_hwcap_x86_has_sse4a = TRUE;
			}
		}
	}

#if defined(HAVE_UNISTD_H)
	mono_hwcap_x86_is_xen = !access ("/proc/xen", F_OK);
#endif
}
#endif

void
mono_hwcap_print (FILE *f)
{
	g_fprintf (f, "mono_hwcap_x86_is_xen = %i\n", mono_hwcap_x86_is_xen);
	g_fprintf (f, "mono_hwcap_x86_has_cmov = %i\n", mono_hwcap_x86_has_cmov);
	g_fprintf (f, "mono_hwcap_x86_has_fcmov = %i\n", mono_hwcap_x86_has_fcmov);
	g_fprintf (f, "mono_hwcap_x86_has_sse1 = %i\n", mono_hwcap_x86_has_sse1);
	g_fprintf (f, "mono_hwcap_x86_has_sse2 = %i\n", mono_hwcap_x86_has_sse2);
	g_fprintf (f, "mono_hwcap_x86_has_sse3 = %i\n", mono_hwcap_x86_has_sse3);
	g_fprintf (f, "mono_hwcap_x86_has_ssse3 = %i\n", mono_hwcap_x86_has_ssse3);
	g_fprintf (f, "mono_hwcap_x86_has_sse41 = %i\n", mono_hwcap_x86_has_sse41);
	g_fprintf (f, "mono_hwcap_x86_has_sse42 = %i\n", mono_hwcap_x86_has_sse42);
	g_fprintf (f, "mono_hwcap_x86_has_sse4a = %i\n", mono_hwcap_x86_has_sse4a);
}
