// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// ---------------------------------------------------------------------------
// NativeFormatReader
//
// Utilities to read native data from images
// ---------------------------------------------------------------------------

#pragma once

#ifndef DACCESS_COMPILE

#if defined(HOST_AMD64) || defined(HOST_X86)
#include "emmintrin.h"
#define USE_INTEL_INTRINSICS_FOR_CUCKOO_FILTER
#elif defined(HOST_ARM) || defined(HOST_ARM64)

#ifndef TARGET_UNIX // The Mac and Linux build environments are not setup for NEON simd.
#define USE_ARM_INTRINSICS_FOR_CUCKOO_FILTER

#if defined(HOST_ARM)
#include "arm_neon.h"
#else
#include "arm64_neon.h"
#endif
#endif // TARGET_UNIX

#endif // HOST_ARM || HOST_ARM64

#endif // DACCESS_COMPILE

// To reduce differences between C# and C++ versions
#define byte uint8_t
#define uint uint32_t

#define UInt16 uint16_t
#define UInt32 uint32_t
#define UInt64 uint64_t

namespace NativeFormat
{
    class NativeReader;
    class NativeHashtable;
    typedef DPTR(NativeReader)      PTR_NativeReader;
    typedef DPTR(NativeHashtable)   PTR_NativeHashtable;

    class NativeReader
    {
        PTR_CBYTE _base;
        uint _size;

    public:
        NativeReader()
        {
            _base = NULL;
            _size = 0;
        }

        NativeReader(PTR_CBYTE base_, uint size)
        {
            _base = base_;
            _size = size;
        }

        void ThrowBadImageFormatException()
        {
            _ASSERTE(false);

#if !defined(DACCESS_COMPILE)
            // Failfast instead of throwing, to avoid violating NOTHROW contracts of callers
            EEPOLICY_HANDLE_FATAL_ERROR(COR_E_BADIMAGEFORMAT);
#endif
        }

        uint EnsureOffsetInRange(uint offset, uint lookAhead)
        {
            if ((int)offset < 0 || offset + lookAhead >= _size)
                ThrowBadImageFormatException();
            return offset;
        }

        byte ReadUInt8(uint offset)
        {
            if (offset >= _size)
                ThrowBadImageFormatException();
            return *(_base + offset); // Assumes little endian and unaligned access
        }

        UInt16 ReadUInt16(uint offset)
        {
            if ((int)offset < 0 || offset + 1 >= _size)
                ThrowBadImageFormatException();
            return *dac_cast<PTR_USHORT>(_base + offset); // Assumes little endian and unaligned access
        }

        UInt32 ReadUInt32(uint offset)
        {
            if ((int)offset < 0 || offset + 3 >= _size)
                ThrowBadImageFormatException();
            return *dac_cast<PTR_UINT32>(_base + offset); // Assumes little endian and unaligned access
        }

        uint DecodeUnsigned(uint offset, uint * pValue)
        {
            if (offset >= _size)
                ThrowBadImageFormatException();

            uint val = *(_base + offset);
            if ((val & 1) == 0)
            {
                *pValue = (val >> 1);
                offset += 1;
            }
            else
            if ((val & 2) == 0)
            {
                if (offset + 1 >= _size)
                    ThrowBadImageFormatException();
                *pValue = (val >> 2) |
                      (((uint)*(_base + offset + 1)) << 6);
                offset += 2;
            }
            else
            if ((val & 4) == 0)
            {
                if (offset + 2 >= _size)
                    ThrowBadImageFormatException();
                *pValue = (val >> 3) |
                      (((uint)*(_base + offset + 1)) << 5) |
                      (((uint)*(_base + offset + 2)) << 13);
                offset += 3;
            }
            else
            if ((val & 8) == 0)
            {
                if (offset + 3 >= _size)
                    ThrowBadImageFormatException();
                *pValue = (val >> 4) |
                      (((uint)*(_base + offset + 1)) << 4) |
                      (((uint)*(_base + offset + 2)) << 12) |
                      (((uint)*(_base + offset + 3)) << 20);
                offset += 4;
            }
            else
            if ((val & 16) == 0)
            {
                *pValue = ReadUInt32(offset + 1);
                offset += 5;
            }
            else
            {
                ThrowBadImageFormatException();
            }

            return offset;
        }

        int DecodeSigned(uint offset, int * pValue)
        {
            if (offset >= _size)
                ThrowBadImageFormatException();

            int val = *(_base + offset);
            if ((val & 1) == 0)
            {
                *pValue = val >> 1;
                offset += 1;
            }
            else if ((val & 2) == 0)
            {
                if (offset + 1 >= _size)
                    ThrowBadImageFormatException();
                *pValue = (val >> 2) |
                    (((int)*(_base + offset + 1)) << 6);
                offset += 2;
            }
            else if ((val & 4) == 0)
            {
                if (offset + 2 >= _size)
                    ThrowBadImageFormatException();
                *pValue = (val >> 3) |
                    (((int)*(_base + offset + 1)) << 5) |
                    (((int)*(_base + offset + 2)) << 13);
                offset += 3;
            }
            else if ((val & 8) == 0)
            {
                if (offset + 3 >= _size)
                    ThrowBadImageFormatException();
                *pValue = (val >> 4) |
                    (((int)*(_base + offset + 1)) << 4) |
                    (((int)*(_base + offset + 2)) << 12) |
                    (((int)*(_base + offset + 3)) << 20);
                offset += 4;
            }
            else if ((val & 16) == 0)
            {
                *pValue = (int)ReadUInt32(offset + 1);
                offset += 5;
            }
            else
            {
                ThrowBadImageFormatException();
            }

            return offset;
        }

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4702) // Disable unreachable code warning
#endif
        uint SkipInteger(uint offset)
        {
            EnsureOffsetInRange(offset, 0);

            PTR_CBYTE data = (_base + offset);
            if ((*data & 1) == 0)
            {
                return offset + 1;
            }
            else if ((*data & 2) == 0)
            {
                return offset + 2;
            }
            else if ((*data & 4) == 0)
            {
                return offset + 3;
            }
            else if ((*data & 8) == 0)
            {
                return offset + 4;
            }
            else if ((*data & 16) == 0)
            {
                return offset + 5;
            }
            else if ((*data & 32) == 0)
            {
                return offset + 9;
            }
            else
            {
                ThrowBadImageFormatException();
                return offset;
            }
        }

#ifndef DACCESS_COMPILE
        const BYTE* GetBlob(uint offset)
        {
            EnsureOffsetInRange(offset, 0);
            return _base + offset;
        }
#endif
#ifdef _MSC_VER
#pragma warning(pop)
#endif
    };

    class NativeParser
    {
        PTR_NativeReader _pReader;
        uint _offset;

    public:
        NativeParser()
            : _pReader(PTR_NULL), _offset(0)
        {
        }

        NativeParser(PTR_NativeReader pReader, uint offset)
        {
            _pReader = pReader;
            _offset = offset;
        }

        bool IsNull() { return _pReader == NULL; }

        NativeReader * GetNativeReader() { return _pReader; }

        uint GetOffset() { return _offset; }
        void SetOffset(uint value) { _offset = value; }

        void ThrowBadImageFormatException()
        {
            _pReader->ThrowBadImageFormatException();
        }

        byte GetUInt8()
        {
            byte val = _pReader->ReadUInt8(_offset);
            _offset += 1;
            return val;
        }

        uint GetUnsigned()
        {
            uint value;
            _offset = _pReader->DecodeUnsigned(_offset, &value);
            return value;
        }

        int GetSigned()
        {
            int value;
            _offset = _pReader->DecodeSigned(_offset, &value);
            return value;
        }

        uint GetRelativeOffset()
        {
            uint pos = _offset;

            int delta;
            _offset = _pReader->DecodeSigned(_offset, &delta);

            return pos + (uint)delta;
        }

#ifndef DACCESS_COMPILE
        const BYTE * GetBlob()
        {
            return _pReader->GetBlob(_offset);
        }
#endif

        void SkipInteger()
        {
            _offset = _pReader->SkipInteger(_offset);
        }

        NativeParser GetParserFromRelativeOffset()
        {
            return NativeParser(_pReader, GetRelativeOffset());
        }
    };

    class NativeArray
    {
        PTR_NativeReader _pReader;
        uint _baseOffset;
        uint _nElements;
        byte _entryIndexSize;

        static const uint _blockSize = 16;

    public:
        NativeArray()
            : _pReader(PTR_NULL), _nElements(0)
        {
        }

        NativeArray(PTR_NativeReader pReader, uint offset)
            : _pReader(pReader)
        {
            uint val;
            _baseOffset = pReader->DecodeUnsigned(offset, &val);

            _nElements = (val >> 2);
            _entryIndexSize = (val & 3);
        }

        uint GetCount()
        {
            return _nElements;
        }

        bool TryGetAt(uint index, uint * pOffset)
        {
            if (index >= _nElements)
                return false;

            uint offset;
            if (_entryIndexSize == 0)
            {
                offset = _pReader->ReadUInt8(_baseOffset + (index / _blockSize));
            }
            else if (_entryIndexSize == 1)
            {
                offset = _pReader->ReadUInt16(_baseOffset + 2 * (index / _blockSize));
            }
            else
            {
                offset = _pReader->ReadUInt32(_baseOffset + 4 * (index / _blockSize));
            }
            offset += _baseOffset;

            for (uint bit = _blockSize >> 1; bit > 0; bit >>= 1)
            {
                uint val;
                uint offset2 = _pReader->DecodeUnsigned(offset, &val);
                if (index & bit)
                {
                    if ((val & 2) != 0)
                    {
                        offset = offset + (val >> 2);
                        continue;
                    }
                }
                else
                {
                    if ((val & 1) != 0)
                    {
                        offset = offset2;
                        continue;
                    }
                }

                // Not found
                if ((val & 3) == 0)
                {
                    // Matching special leaf node?
                    if ((val >> 2) == (index & (_blockSize - 1)))
                    {
                        offset = offset2;
                        break;
                    }
                }
                return false;
            }

            *pOffset = offset;
            return true;
        }
    };

    class NativeHashtable
    {
        PTR_NativeReader _pReader;
        uint _baseOffset;
        uint _bucketMask;
        byte _entryIndexSize;

        NativeParser GetParserForBucket(uint bucket, uint * pEndOffset)
        {
            uint start, end;

            if (_entryIndexSize == 0)
            {
                uint bucketOffset = _baseOffset + bucket;
                start = _pReader->ReadUInt8(bucketOffset);
                end = _pReader->ReadUInt8(bucketOffset + 1);
            }
            else if (_entryIndexSize == 1)
            {
                uint bucketOffset = _baseOffset + 2 * bucket;
                start = _pReader->ReadUInt16(bucketOffset);
                end = _pReader->ReadUInt16(bucketOffset + 2);
            }
            else
            {
                uint bucketOffset = _baseOffset + 4 * bucket;
                start = _pReader->ReadUInt32(bucketOffset);
                end = _pReader->ReadUInt32(bucketOffset + 4);
            }

            *pEndOffset = end + _baseOffset;
            return NativeParser(_pReader, _baseOffset + start);
        }

    public:
        NativeHashtable() : _pReader(PTR_NULL), _baseOffset(0), _bucketMask(0), _entryIndexSize(0)
        {
        }

        NativeHashtable(NativeParser& parser)
        {
            uint header = parser.GetUInt8();

            _pReader = dac_cast<PTR_NativeReader>(parser.GetNativeReader());
            _baseOffset = parser.GetOffset();

            int numberOfBucketsShift = (int)(header >> 2);
            if (numberOfBucketsShift > 31)
                _pReader->ThrowBadImageFormatException();
            _bucketMask = (uint)((1 << numberOfBucketsShift) - 1);

            byte entryIndexSize = (byte)(header & 3);
            if (entryIndexSize > 2)
                _pReader->ThrowBadImageFormatException();
            _entryIndexSize = entryIndexSize;
        }

        bool IsNull() { return _pReader == NULL; }

        class AllEntriesEnumerator
        {
            PTR_NativeHashtable _table;
            NativeParser        _parser;
            uint                _currentBucket;
            uint                _endOffset;

        public:
            AllEntriesEnumerator() :
                _table(dac_cast<PTR_NativeHashtable>(nullptr)),
                _parser(),
                _currentBucket(0),
                _endOffset(0)
            {

            }

            AllEntriesEnumerator(PTR_NativeHashtable table)
            {
                _table = table;
                _currentBucket = 0;
                if (_table != NULL)
                {
                    _parser = _table->GetParserForBucket(_currentBucket, &_endOffset);
                }
            }

            NativeParser GetNext()
            {
                if (_table == NULL)
                {
                    return NativeParser();
                }

                for (; ; )
                {
                    if (_parser.GetOffset() < _endOffset)
                    {
                        // Skip hashcode to get to the offset
                        _parser.GetUInt8();
                        return _parser.GetParserFromRelativeOffset();
                    }

                    if (_currentBucket >= _table->_bucketMask)
                    {
                        return NativeParser();
                    }

                    _currentBucket++;
                    _parser = _table->GetParserForBucket(_currentBucket, &_endOffset);
                }
            }
        };

        //
        // The enumerator does not conform to the regular C# enumerator pattern to avoid paying
        // its performance penalty (allocation, multiple calls per iteration)
        //
        class Enumerator
        {
            NativeParser _parser;
            uint _endOffset;
            byte _lowHashcode;

        public:
            Enumerator(NativeParser parser, uint endOffset, byte lowHashcode)
            {
                _parser = parser;
                _endOffset = endOffset;
                _lowHashcode = lowHashcode;
            }

            bool GetNext(NativeParser& entryParser)
            {
                while (_parser.GetOffset() < _endOffset)
                {
                    byte lowHashcode = _parser.GetUInt8();

                    if (lowHashcode == _lowHashcode)
                    {
                        entryParser = _parser.GetParserFromRelativeOffset();
                        return true;
                    }

                    // The entries are sorted by hashcode within the bucket. It allows us to terminate the lookup prematurely.
                    if (lowHashcode > _lowHashcode)
                    {
                        _endOffset = _parser.GetOffset(); // Ensure that extra call to GetNext returns null parser again
                        break;
                    }

                    _parser.SkipInteger();
                }

                return false;
            }
        };

        // The recommended code pattern to perform lookup is:
        //
        //  NativeHashtable::Enumerator lookup = hashtable.Lookup(dwHashCode);
        //  NativeParser entryParser;
        //  while (lookup.GetNext(entryParser))
        //  {
        //      ... read entry using entryParser ...
        //  }
        //
        Enumerator Lookup(int hashcode)
        {
            uint endOffset;
            uint bucket = ((uint)hashcode >> 8) & _bucketMask;
            NativeParser parser = GetParserForBucket(bucket, &endOffset);

            return Enumerator(parser, endOffset, (byte)hashcode);
        }
    };

    class NativeCuckooFilter;
    typedef DPTR(NativeCuckooFilter) PTR_NativeCuckooFilter;

    class NativeCuckooFilter
    {
        PTR_BYTE _base;
        UInt32 _size;
        LONG _disableFilter;

        bool IsPowerOfTwo(UInt32 number)
        {
            return (number & (number - 1)) == 0;
        }

    public:
        static UInt32 ComputeFingerprintHash(UInt16 fingerprint)
        {
            // As the number of buckets is not reasonably greater than 65536, just use fingerprint as its own hash
            // This implies that the hash of the entrypoint should be an independent hash function as compared
            // to the fingerprint
            return fingerprint;
        }

        NativeCuckooFilter()
        {
            _base = NULL;
            _size = 0;
            _disableFilter = 0;
        }

        NativeCuckooFilter(PTR_BYTE base_, UInt32 size, UInt32 rvaOfTable, UInt32 filterSize)
        {
            if (((rvaOfTable & 0xF) != 0) || ((filterSize & 0xF) != 0))
            {
                // Native cuckoo filters must be aligned at 16byte boundaries within the PE file
                NativeReader exceptionReader;
                exceptionReader.ThrowBadImageFormatException();
            }
            if ((filterSize != 0) && !IsPowerOfTwo(filterSize))
            {
                // Native cuckoo filters must be power of two in size
                NativeReader exceptionReader;
                exceptionReader.ThrowBadImageFormatException();
            }
            _base = base_ + rvaOfTable;
            _size = filterSize;
            _disableFilter = 0;
        }

        void DisableFilter()
        {
            // Set disable filter flag using interlocked to ensure that future
            // attempts to read the filter will capture the change.
            InterlockedExchange(&_disableFilter, 1);
        }

        bool HashComputationImmaterial()
        {
            if ((_base == NULL) || (_size == 0))
                return true;
            return false;
        }

        bool MayExist(UInt32 hashcode, UInt16 fingerprint)
        {
            if ((_base == NULL) || (_disableFilter))
                return true;

            if (_size == 0)
                return false; // Empty table means none of the attributes exist

            // Fingerprints of 0 don't actually exist. Just use 1, and lose some entropy
            if (fingerprint == 0)
                fingerprint = 1;

            UInt32 bucketCount = _size / 16;
            UInt32 bucketMask = bucketCount - 1; // filters are power of 2 in size

            UInt32 bucketAIndex = hashcode & bucketMask;
            UInt32 bucketBIndex = bucketAIndex ^ (ComputeFingerprintHash(fingerprint) & bucketMask);

#if defined(USE_INTEL_INTRINSICS_FOR_CUCKOO_FILTER)
            __m128i bucketA = _mm_loadu_si128(&((__m128i*)_base)[bucketAIndex]);
            __m128i bucketB = _mm_loadu_si128(&((__m128i*)_base)[bucketBIndex]);
            __m128i fingerprintSIMD = _mm_set1_epi16(fingerprint);
            __m128i bucketACompare = _mm_cmpeq_epi16(bucketA, fingerprintSIMD);
            __m128i bucketBCompare = _mm_cmpeq_epi16(bucketB, fingerprintSIMD);
            __m128i bothCompare = _mm_or_si128(bucketACompare, bucketBCompare);
            return !!_mm_movemask_epi8(bothCompare);
#elif defined(USE_ARM_INTRINSICS_FOR_CUCKOO_FILTER)
            uint16x8_t bucketA = vld1q_u16((uint16_t*)&((uint16x8_t*)_base)[bucketAIndex]);
            uint16x8_t bucketB = vld1q_u16((uint16_t*)&((uint16x8_t*)_base)[bucketBIndex]);
            uint16x8_t fingerprintSIMD = vdupq_n_u16(fingerprint);
            uint16x8_t bucketACompare = vceqq_u16(bucketA, fingerprintSIMD);
            uint16x8_t bucketBCompare = vceqq_u16(bucketB, fingerprintSIMD);
            uint16x8_t bothCompare = vorrq_u16(bucketACompare, bucketBCompare);
            uint64_t bits0Lane = vgetq_lane_u64(bothCompare, 0);
            uint64_t bits1Lane = vgetq_lane_u64(bothCompare, 1);
            return !!(bits0Lane | bits1Lane);
#else // Non-intrinsic implementation supporting NativeReader to cross DAC boundary
            NativeReader reader(_base, _size);

            // Check for existence in bucketA
            for (int i = 0; i < 8; i++)
            {
                if (reader.ReadUInt16(bucketAIndex * 16 + i * sizeof(UInt16)) == fingerprint)
                    return true;
            }

            // Check for existence in bucketB
            for (int i = 0; i < 8; i++)
            {
                if (reader.ReadUInt16(bucketBIndex * 16 + i * sizeof(UInt16)) == fingerprint)
                    return true;
            }

            return false;
#endif
        }
    };
}
