/*
   +----------------------------------------------------------------------+
   | Zend Engine                                                          |
   +----------------------------------------------------------------------+
   | Copyright © Zend Technologies Ltd., a subsidiary company of          |
   |     Perforce Software, Inc., and Contributors.                       |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Authors: Xinchen Hui <xinchen.h@zend.com>                            |
   +----------------------------------------------------------------------+
*/

#include "zend_cpuinfo.h"

typedef struct _zend_cpu_info {
	uint32_t eax;
	uint32_t ebx;
	uint32_t ecx;
	uint32_t edx;
	uint32_t initialized;
} zend_cpu_info;

static zend_cpu_info cpuinfo = {0};

#if (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
# if defined(HAVE_CPUID_H) && defined(HAVE_CPUID_COUNT) /* use cpuid.h functions */
#  include <cpuid.h>
static void __zend_cpuid(uint32_t func, uint32_t subfunc, zend_cpu_info *cpuinfo) {
	__cpuid_count(func, subfunc, cpuinfo->eax, cpuinfo->ebx, cpuinfo->ecx, cpuinfo->edx);
}
# else /* use inline asm */
static void __zend_cpuid(uint32_t func, uint32_t subfunc, zend_cpu_info *cpuinfo) {
#  if defined(__i386__) && (defined(__pic__) || defined(__PIC__))
	/* PIC on i386 uses %ebx, so preserve it. */
	__asm__ __volatile__ (
		"pushl  %%ebx\n"
		"cpuid\n"
		"mov    %%ebx,%1\n"
		"popl   %%ebx"
		: "=a"(cpuinfo->eax), "=r"(cpuinfo->ebx), "=c"(cpuinfo->ecx), "=d"(cpuinfo->edx)
		: "a"(func), "c"(subfunc)
	);
#  else
	__asm__ __volatile__ (
		"cpuid"
		: "=a"(cpuinfo->eax), "=b"(cpuinfo->ebx), "=c"(cpuinfo->ecx), "=d"(cpuinfo->edx)
		: "a"(func), "c"(subfunc)
	);
#  endif
}
# endif
#elif defined(_MSC_VER) && !defined(__clang__) && (defined(_M_X64) || defined(_M_IX86)) /* use MSVC __cpuidex intrin */
# include <intrin.h>
static void __zend_cpuid(uint32_t func, uint32_t subfunc, zend_cpu_info *cpuinfo) {
	int regs[4];

	__cpuidex(regs, func, subfunc);

	cpuinfo->eax = regs[0];
	cpuinfo->ebx = regs[1];
	cpuinfo->ecx = regs[2];
	cpuinfo->edx = regs[3];
}
#else /* fall back to zero */
static void __zend_cpuid(uint32_t func, uint32_t subfunc, zend_cpu_info *cpuinfo) {
	cpuinfo->eax = 0;
}
#endif

#if defined(__i386__) || defined(__x86_64__) || defined(_M_X64) || defined(_M_IX86)
/* Function based on compiler-rt implementation. */
static unsigned get_xcr0_eax(void) {
# if defined(__GNUC__) || defined(__clang__)
	// Check xgetbv; this uses a .byte sequence instead of the instruction
	// directly because older assemblers do not include support for xgetbv and
	// there is no easy way to conditionally compile based on the assembler used.
	unsigned eax, edx;
	__asm__(".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c"(0));
	return eax;
# elif defined(ZEND_WIN32) && defined(_XCR_XFEATURE_ENABLED_MASK)
	return _xgetbv(_XCR_XFEATURE_ENABLED_MASK);
# else
	return 0;
# endif
}

static bool is_avx_supported(void) {
	if (!(cpuinfo.ecx & ZEND_CPU_FEATURE_AVX)) {
		/* No support for AVX */
		return false;
	}
	if (!(cpuinfo.ecx & ZEND_CPU_FEATURE_OSXSAVE)) {
		/* The operating system does not support XSAVE. */
		return false;
	}
	if ((get_xcr0_eax() & 0x6) != 0x6) {
		/* XCR0 SSE and AVX bits must be set. */
		return false;
	}
	return true;
}
#else
static bool is_avx_supported(void) {
	return false;
}
#endif

void zend_cpu_startup(void)
{
	if (!cpuinfo.initialized) {
		zend_cpu_info ebx;
		int max_feature;

		cpuinfo.initialized = 1;
		__zend_cpuid(0, 0, &cpuinfo);
		max_feature = cpuinfo.eax;
		if (max_feature == 0) {
			return;
		}

		__zend_cpuid(1, 0, &cpuinfo);

		/* for avx2 */
		if (max_feature >= 7) {
			__zend_cpuid(7, 0, &ebx);
			cpuinfo.ebx = ebx.ebx;
		} else {
			cpuinfo.ebx = 0;
		}

		if (!is_avx_supported()) {
			cpuinfo.edx &= ~ZEND_CPU_FEATURE_AVX;
			cpuinfo.ebx &= ~(ZEND_CPU_FEATURE_AVX2 & ~ZEND_CPU_EBX_MASK);
		}
	}
}

ZEND_API int zend_cpu_supports(zend_cpu_feature feature) {
	ZEND_ASSERT(cpuinfo.initialized);
	if (feature & ZEND_CPU_EDX_MASK) {
		return (cpuinfo.edx & (feature & ~ZEND_CPU_EDX_MASK));
	} else if (feature & ZEND_CPU_EBX_MASK) {
		return (cpuinfo.ebx & (feature & ~ZEND_CPU_EBX_MASK));
	} else {
		return (cpuinfo.ecx & feature);
	}
}
