/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkCpu_DEFINED
#define SkCpu_DEFINED

#include "include/core/SkTypes.h"

struct SkCpu {
    enum {
        SSE1       = 1 << 0,
        SSE2       = 1 << 1,
        SSE3       = 1 << 2,
        SSSE3      = 1 << 3,
        SSE41      = 1 << 4,
        SSE42      = 1 << 5,
        AVX        = 1 << 6,
        F16C       = 1 << 7,
        FMA        = 1 << 8,
        AVX2       = 1 << 9,
        BMI1       = 1 << 10,
        BMI2       = 1 << 11,
        // Handy alias for all the cool Haswell+ instructions.
        HSW = AVX2 | BMI1 | BMI2 | F16C | FMA,

        AVX512F    = 1 << 12,
        AVX512DQ   = 1 << 13,
        AVX512IFMA = 1 << 14,
        AVX512PF   = 1 << 15,
        AVX512ER   = 1 << 16,
        AVX512CD   = 1 << 17,
        AVX512BW   = 1 << 18,
        AVX512VL   = 1 << 19,

        // Handy alias for all the cool Skylake Xeon+ instructions.
        SKX = AVX512F  | AVX512DQ | AVX512CD | AVX512BW | AVX512VL,
    };
    enum {
        NEON     = 1 << 0,
        NEON_FMA = 1 << 1,
        VFP_FP16 = 1 << 2,
        CRC32    = 1 << 3,
        ASIMDHP  = 1 << 4,
    };

    static void CacheRuntimeFeatures();
    static bool Supports(uint32_t);
private:
    static uint32_t gCachedFeatures;
};

inline bool SkCpu::Supports(uint32_t mask) {
    uint32_t features = gCachedFeatures;

    // If we mask in compile-time known lower limits, the compiler can
    // often compile away this entire function.
#if SK_CPU_X86
    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1
    features |= SSE1;
    #endif
    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
    features |= SSE2;
    #endif
    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE3
    features |= SSE3;
    #endif
    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSSE3
    features |= SSSE3;
    #endif
    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41
    features |= SSE41;
    #endif
    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE42
    features |= SSE42;
    #endif
    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX
    features |= AVX;
    #endif
    // F16C goes here if we add SK_CPU_SSE_LEVEL_F16C
    #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2
    features |= AVX2;
    #endif
    // FMA doesn't fit neatly into this total ordering.
    // It's available on Haswell+ just like AVX2, but it's technically a different bit.
    // TODO: circle back on this if we find ourselves limited by lack of compile-time FMA

    #if defined(SK_CPU_LIMIT_SSE41)
    features &= (SkCpu::SSE1 | SkCpu::SSE2 | SkCpu::SSE3 | SkCpu::SSSE3 | SkCpu::SSE41);
    #elif defined(SK_CPU_LIMIT_SSE2)
    features &= (SkCpu::SSE1 | SkCpu::SSE2);
    #endif

#else
    #if defined(SK_ARM_HAS_NEON)
    features |= NEON;
    #endif

    #if defined(SK_CPU_ARM64)
    features |= NEON|NEON_FMA|VFP_FP16;
    #endif

    #if defined(SK_ARM_HAS_CRC32)
    features |= CRC32;
    #endif

#endif
    return (features & mask) == mask;
}

#endif//SkCpu_DEFINED
