/******************************************************************************
 *
 * Project:  CPL - Common Portability Library
 * Purpose:  CPU features detection
 * Author:   Even Rouault, <even dot rouault at spatialys dot com>
 *
 ******************************************************************************
 * Copyright (c) 2016, Even Rouault <even dot rouault at spatialys dot com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_port.h"
#include "cpl_string.h"
#include "cpl_cpu_features.h"

//! @cond Doxygen_Suppress

#define CPUID_SSSE3_ECX_BIT 9
#define CPUID_OSXSAVE_ECX_BIT 27
#define CPUID_AVX_ECX_BIT 28

#define CPUID_SSE_EDX_BIT 25

#define BIT_XMM_STATE (1 << 1)
#define BIT_YMM_STATE (2 << 1)

#define REG_EAX 0
#define REG_EBX 1
#define REG_ECX 2
#define REG_EDX 3

#if defined(__GNUC__)
#if defined(__x86_64)
#define GCC_CPUID(level, a, b, c, d)                                           \
    __asm__("xchgq %%rbx, %q1\n"                                               \
            "cpuid\n"                                                          \
            "xchgq %%rbx, %q1"                                                 \
            : "=a"(a), "=r"(b), "=c"(c), "=d"(d)                               \
            : "0"(level))
#else
#define GCC_CPUID(level, a, b, c, d)                                           \
    __asm__("xchgl %%ebx, %1\n"                                                \
            "cpuid\n"                                                          \
            "xchgl %%ebx, %1"                                                  \
            : "=a"(a), "=r"(b), "=c"(c), "=d"(d)                               \
            : "0"(level))
#endif

#define CPL_CPUID(level, array)                                                \
    GCC_CPUID(level, array[0], array[1], array[2], array[3])

#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))

#include <intrin.h>
#define CPL_CPUID(level, array) __cpuid(array, level)

#endif

#if defined(HAVE_SSE_AT_COMPILE_TIME) && !defined(HAVE_INLINE_SSE)

/************************************************************************/
/*                         CPLHaveRuntimeSSE()                          */
/************************************************************************/

bool CPLHaveRuntimeSSE()
{
    int cpuinfo[4] = {0, 0, 0, 0};
    CPL_CPUID(1, cpuinfo);
    return (cpuinfo[REG_EDX] & (1 << CPUID_SSE_EDX_BIT)) != 0;
}

#endif

#if defined(HAVE_SSSE3_AT_COMPILE_TIME) && !defined(HAVE_INLINE_SSSE3)

/************************************************************************/
/*                        CPLHaveRuntimeSSSE3()                         */
/************************************************************************/

static inline bool CPLDetectSSSE3()
{
    int cpuinfo[4] = {0, 0, 0, 0};
    CPL_CPUID(1, cpuinfo);
    return (cpuinfo[REG_ECX] & (1 << CPUID_SSSE3_ECX_BIT)) != 0;
}

#if defined(__GNUC__) && !defined(DEBUG)
bool bCPLHasSSSE3 = false;
static void CPLHaveRuntimeSSSE3Initialize() __attribute__((constructor));

static void CPLHaveRuntimeSSSE3Initialize()
{
    bCPLHasSSSE3 = CPLDetectSSSE3();
}
#else
bool CPLHaveRuntimeSSSE3()
{
#ifdef DEBUG
    if (!CPLTestBool(CPLGetConfigOption("GDAL_USE_SSSE3", "YES")))
        return false;
#endif
    return CPLDetectSSSE3();
}
#endif

#endif  // defined(HAVE_SSSE3_AT_COMPILE_TIME) && !defined(HAVE_INLINE_SSSE3)

#if defined(HAVE_AVX_AT_COMPILE_TIME) && !defined(HAVE_INLINE_AVX)

/************************************************************************/
/*                         CPLHaveRuntimeAVX()                          */
/************************************************************************/

#if defined(__GNUC__)

static bool CPLDetectRuntimeAVX()
{
    int cpuinfo[4] = {0, 0, 0, 0};
    CPL_CPUID(1, cpuinfo);

    // Check OSXSAVE feature.
    if ((cpuinfo[REG_ECX] & (1 << CPUID_OSXSAVE_ECX_BIT)) == 0)
    {
        return false;
    }

    // Check AVX feature.
    if ((cpuinfo[REG_ECX] & (1 << CPUID_AVX_ECX_BIT)) == 0)
    {
        return false;
    }

    // Issue XGETBV and check the XMM and YMM state bit.
    unsigned int nXCRLow;
    unsigned int nXCRHigh;
    __asm__("xgetbv" : "=a"(nXCRLow), "=d"(nXCRHigh) : "c"(0));
    if ((nXCRLow & (BIT_XMM_STATE | BIT_YMM_STATE)) !=
        (BIT_XMM_STATE | BIT_YMM_STATE))
    {
        return false;
    }
    CPL_IGNORE_RET_VAL(nXCRHigh);  // unused

    return true;
}

bool bCPLHasAVX = false;
static void CPLHaveRuntimeAVXInitialize() __attribute__((constructor(101)));

static void CPLHaveRuntimeAVXInitialize()
{
    bCPLHasAVX = CPLDetectRuntimeAVX();
}

#elif defined(_MSC_VER)
bool CPLHaveRuntimeAVX()
{
    static const bool bHasAVX = []() -> bool
    {
        int cpuinfo[4] = {0, 0, 0, 0};
        CPL_CPUID(1, cpuinfo);

        // Check OSXSAVE feature.
        if ((cpuinfo[REG_ECX] & (1 << CPUID_OSXSAVE_ECX_BIT)) == 0)
        {
            return false;
        }

        // Check AVX feature.
        if ((cpuinfo[REG_ECX] & (1 << CPUID_AVX_ECX_BIT)) == 0)
        {
            return false;
        }

        // Issue XGETBV and check the XMM and YMM state bit.
        unsigned __int64 xcrFeatureMask = _xgetbv(_XCR_XFEATURE_ENABLED_MASK);
        if ((xcrFeatureMask & (BIT_XMM_STATE | BIT_YMM_STATE)) !=
            (BIT_XMM_STATE | BIT_YMM_STATE))
        {
            return false;
        }

        return true;
    }();

    return bHasAVX;
}

#else

bool CPLHaveRuntimeAVX()
{
    return false;
}

#endif

#endif  // defined(HAVE_AVX_AT_COMPILE_TIME) && !defined(HAVE_INLINE_AVX)

#if defined(HAVE_AVX2_AT_COMPILE_TIME) && !defined(HAVE_INLINE_AVX2)

#if defined(__GNUC__)

bool bCPLHasAVX2 = false;
// Use 102 because  CPLHaveRuntimeAVXInitialize() uses 101, so we are run
// afterwards
static void CPLHaveRuntimeAVX2Initialize() __attribute__((constructor(102)));

static void CPLHaveRuntimeAVX2Initialize()
{
    bCPLHasAVX2 = CPLHaveRuntimeAVX() && __builtin_cpu_supports("avx2");
}

#else

/************************************************************************/
/*                         CPLHaveRuntimeAVX2()                         */
/************************************************************************/

bool CPLHaveRuntimeAVX2()
{
    static const bool bHasAVX2 = []() -> bool
    {
#if defined(_MSC_VER)
        if (!CPLHaveRuntimeAVX())
            return false;
        int cpuInfo[4] = {};
        __cpuidex(cpuInfo, 7, 0);
        return (cpuInfo[REG_EBX] & (1 << 5)) != 0;  // EBX bit 5 = AVX2
#else
        return false;
#endif
    }();
    return bHasAVX2;
}

#endif

#endif  // defined(HAVE_AVX2_AT_COMPILE_TIME) && !defined(HAVE_INLINE_AVX2)

//! @endcond
