/********************************************************************
 * 2014 -
 * open source under Apache License Version 2.0
 ********************************************************************/
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <cassert>
#include <cstdlib>

#include "HWCrc32c.h"

#if ((defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64)))
#include <cpuid.h>
#endif

#if ((defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64)))
#if !defined(__SSE4_2__)

namespace Hdfs {
namespace Internal {

#if defined(__LP64__)
static inline uint64_t _mm_crc32_u64(uint64_t crc, uint64_t value) {
    asm("crc32q %[value], %[crc]\n" : [crc] "+r"(crc) : [value] "rm"(value));
    return crc;
}
#endif

static inline uint32_t _mm_crc32_u16(uint32_t crc, uint16_t value) {
    asm("crc32w %[value], %[crc]\n" : [crc] "+r"(crc) : [value] "rm"(value));
    return crc;
}

static inline uint32_t _mm_crc32_u32(uint32_t crc, uint64_t value) {
    asm("crc32l %[value], %[crc]\n" : [crc] "+r"(crc) : [value] "rm"(value));
    return crc;
}

static inline uint32_t _mm_crc32_u8(uint32_t crc, uint8_t value) {
    asm("crc32b %[value], %[crc]\n" : [crc] "+r"(crc) : [value] "rm"(value));
    return crc;
}

}
}

#else

#include <nmmintrin.h>

#endif

namespace Hdfs {
namespace Internal {

bool HWCrc32c::available() {
#if ((defined(__X86__) || defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(__386__) || defined(__x86_64__) || defined(_M_X64)))
    uint32_t eax, ebx, ecx = 0, edx;
    /*
     * get the CPU features (level 1). ecx will have the SSE4.2 bit.
     * This gcc routine automatically handles saving ebx in the case where we are -fpic or -fPIC
     */
    __get_cpuid(1, &eax, &ebx, &ecx, &edx);
    return (ecx & (1 << 20)) != 0;
#else
    return false;
#endif
}

void HWCrc32c::update(const void * b, int len) {
    const char * p = static_cast<const char *>(b);
#if defined(__LP64__)
    const size_t bytes = sizeof(uint64_t);
#else
    const size_t bytes = sizeof(uint32_t);
#endif
    int align = bytes - reinterpret_cast<uint64_t>(p) % bytes;
    align = bytes == static_cast<size_t>(align) ? 0 : align;

    if (len < align) {
        align = len;
    }

    updateInt64(p, align);
    p = p + align;
    len -= align;

    if (len > 0) {
        assert(0 == reinterpret_cast<uint64_t>(p) % bytes);

        for (int i = len / bytes; i > 0; --i) {
#if defined(__LP64__)
            crc = _mm_crc32_u64(crc, *reinterpret_cast<const uint64_t *>(p));
#else
            crc = _mm_crc32_u32(crc, *reinterpret_cast<const uint32_t *>(p));
#endif
            p = p + bytes;
        }

        len &= bytes - 1;
        updateInt64(p, len);
    }
}

void HWCrc32c::updateInt64(const char * b, int len) {
    assert(len < 8);

    switch (len) {
    case 7:
        crc = _mm_crc32_u8(crc, *reinterpret_cast<const uint8_t *>(b++));

    case 6:
        crc = _mm_crc32_u16(crc, *reinterpret_cast<const uint16_t *>(b));
        b += 2;

        /* case 5 is below: 4 + 1 */
    case 4:
        crc = _mm_crc32_u32(crc, *reinterpret_cast<const uint32_t *>(b));
        break;

    case 3:
        crc = _mm_crc32_u8(crc, *reinterpret_cast<const uint8_t *>(b++));

    case 2:
        crc = _mm_crc32_u16(crc, *reinterpret_cast<const uint16_t *>(b));
        break;

    case 5:
        crc = _mm_crc32_u32(crc, *reinterpret_cast<const uint32_t *>(b));
        b += 4;

    case 1:
        crc = _mm_crc32_u8(crc, *reinterpret_cast<const uint8_t *>(b));
        break;

    case 0:
        break;
    }
}

}
}

#endif /* _HDFS_LIBHDFS3_COMMON_HWCHECKSUM_H_ */
