private static uint QueueRound(uint hash, uint queuedValue) { unchecked { return(BitOperations.RotateLeft(hash + queuedValue * Prime3, 17) * Prime4); } }
private static uint Round(uint hash, uint input) { unchecked { return(BitOperations.RotateLeft(hash + input * Prime2, 13) * Prime1); } }
private static uint MixState(uint v1, uint v2, uint v3, uint v4) { unchecked { return(BitOperations.RotateLeft(v1, 1) + BitOperations.RotateLeft(v2, 7) + BitOperations.RotateLeft(v3, 12) + BitOperations.RotateLeft(v4, 18)); } }
// Use this if and only if 'Denial of Service' attacks are not a concern (i.e. never used for free-form user input), // or are otherwise mitigated internal unsafe int GetNonRandomizedHashCode() { fixed(char *src = &_firstChar) { Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'"); Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary"); uint hash1 = (5381 << 16) + 5381; uint hash2 = hash1; uint *ptr = (uint *)src; int length = this.Length; while (length > 2) { length -= 4; // Where length is 4n-1 (e.g. 3,7,11,15,19) this additionally consumes the null terminator hash1 = (BitOperations.RotateLeft(hash1, 5) + hash1) ^ ptr[0]; hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ ptr[1]; ptr += 2; } if (length > 0) { // Where length is 4n-3 (e.g. 1,5,9,13,17) this additionally consumes the null terminator hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ ptr[0]; } return((int)(hash1 + (hash2 * 1566083941))); } }
//public override void NextBytes(byte[] buffer) => NextBytes((Span<byte>)buffer); public override unsafe void NextBytes(byte[] buffer) { ulong s0 = _s0, s1 = _s1, s2 = _s2, s3 = _s3; int pos = 0; int ulongs = buffer.Length / sizeof(ulong) * sizeof(ulong); //while (buffer.Length >= sizeof(ulong)) for (; pos < ulongs; pos += sizeof(ulong)) { /*Unsafe.WriteUnaligned( * ref MemoryMarshal.GetReference(buffer), * BitOperations.RotateLeft(s1 * 5, 7) * 9);*/ Unsafe.As <byte, ulong>(ref buffer[pos]) = BitOperations.RotateLeft(s1 * 5, 7) * 9; // Update PRNG state. ulong t = s1 << 17; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; s3 = BitOperations.RotateLeft(s3, 45); //buffer = buffer.Slice(sizeof(ulong)); } //if (!buffer.IsEmpty) if (pos < buffer.Length) { ulong next = BitOperations.RotateLeft(s1 * 5, 7) * 9; byte *remainingBytes = (byte *)&next; Debug.Assert(buffer.Length < sizeof(ulong)); //for (int i = 0; i < buffer.Length; i++) for (int i = 0; pos < buffer.Length; i++, pos++) { //buffer[i] = remainingBytes[i]; buffer[pos] = remainingBytes[i]; } // Update PRNG state. ulong t = s1 << 17; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; s3 = BitOperations.RotateLeft(s3, 45); } _s0 = s0; _s1 = s1; _s2 = s2; _s3 = s3; }
internal unsafe int GetNonRandomizedHashCodeOrdinalIgnoreCase() { uint hash1 = (5381 << 16) + 5381; uint hash2 = hash1; fixed(char *src = &_firstChar) { Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'"); Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary"); uint *ptr = (uint *)src; int length = this.Length; // We "normalize to lowercase" every char by ORing with 0x0020. This casts // a very wide net because it will change, e.g., '^' to '~'. But that should // be ok because we expect this to be very rare in practice. const uint NormalizeToLowercase = 0x0020_0020u; // valid both for big-endian and for little-endian while (length > 2) { uint p0 = ptr[0]; uint p1 = ptr[1]; if (!Utf16Utility.AllCharsInUInt32AreAscii(p0 | p1)) { goto NotAscii; } length -= 4; // Where length is 4n-1 (e.g. 3,7,11,15,19) this additionally consumes the null terminator hash1 = (BitOperations.RotateLeft(hash1, 5) + hash1) ^ (p0 | NormalizeToLowercase); hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (p1 | NormalizeToLowercase); ptr += 2; } if (length > 0) { uint p0 = ptr[0]; if (!Utf16Utility.AllCharsInUInt32AreAscii(p0)) { goto NotAscii; } // Where length is 4n-3 (e.g. 1,5,9,13,17) this additionally consumes the null terminator hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (p0 | NormalizeToLowercase); } } return((int)(hash1 + (hash2 * 1566083941))); NotAscii: return(GetNonRandomizedHashCodeOrdinalIgnoreCaseSlow(this));
/// <summary> /// Called when pos reaches 64. /// </summary> private void Drain() { for (int i = 16; i != 80; i++) { _w[i] = BitOperations.RotateLeft(_w[i - 3] ^ _w[i - 8] ^ _w[i - 14] ^ _w[i - 16], 1); } uint a = _w[80]; uint b = _w[81]; uint c = _w[82]; uint d = _w[83]; uint e = _w[84]; for (int i = 0; i != 20; i++) { const uint k = 0x5A827999; uint f = (b & c) | ((~b) & d); uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + _w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp; } for (int i = 20; i != 40; i++) { uint f = b ^ c ^ d; const uint k = 0x6ED9EBA1; uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + _w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp; } for (int i = 40; i != 60; i++) { uint f = (b & c) | (b & d) | (c & d); const uint k = 0x8F1BBCDC; uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + _w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp; } for (int i = 60; i != 80; i++) { uint f = b ^ c ^ d; const uint k = 0xCA62C1D6; uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + _w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp; } _w[80] += a; _w[81] += b; _w[82] += c; _w[83] += d; _w[84] += e; _length += 512; // 64 bytes == 512 bits _pos = 0; }
[MethodImpl(MethodImplOptions.AggressiveInlining)] // small-ish hot path used by a handful of "next" methods internal uint NextUInt32() { uint result = BitOperations.RotateLeft(_s1 * 5, 7) * 9; uint t = _s1 << 9; _s2 ^= _s0; _s3 ^= _s1; _s1 ^= _s2; _s0 ^= _s3; _s2 ^= t; _s3 = BitOperations.RotateLeft(_s3, 11); return(result); }
[MethodImpl(MethodImplOptions.AggressiveInlining)] // small-ish hot path used by a handful of "next" methods internal ulong NextUInt64() { ulong result = BitOperations.RotateLeft(_s1 * 5, 7) * 9; ulong t = _s1 << 17; _s2 ^= _s0; _s3 ^= _s1; _s1 ^= _s2; _s0 ^= _s3; _s2 ^= t; _s3 = BitOperations.RotateLeft(_s3, 45); return(result); }
public override unsafe void NextBytes(Span <byte> buffer) { ulong s0 = _s0, s1 = _s1, s2 = _s2, s3 = _s3; while (buffer.Length >= sizeof(ulong)) { Unsafe.WriteUnaligned( ref MemoryMarshal.GetReference(buffer), BitOperations.RotateLeft(s1 * 5, 7) * 9); // Update PRNG state. ulong t = s1 << 17; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; s3 = BitOperations.RotateLeft(s3, 45); buffer = buffer.Slice(sizeof(ulong)); } if (!buffer.IsEmpty) { ulong next = BitOperations.RotateLeft(s1 * 5, 7) * 9; byte *remainingBytes = (byte *)&next; Debug.Assert(buffer.Length < sizeof(ulong)); for (int i = 0; i < buffer.Length; i++) { buffer[i] = remainingBytes[i]; } // Update PRNG state. ulong t = s1 << 17; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; s3 = BitOperations.RotateLeft(s3, 45); } _s0 = s0; _s1 = s1; _s2 = s2; _s3 = s3; }
static int GetNonRandomizedHashCodeOrdinalIgnoreCaseSlow(string str) { int length = str.Length; char[]? borrowedArr = null; // Important: leave an additional space for '\0' Span <char> scratch = (uint)length < 64 ? stackalloc char[64] : (borrowedArr = ArrayPool <char> .Shared.Rent(length + 1)); int charsWritten = System.Globalization.Ordinal.ToUpperOrdinal(str, scratch); Debug.Assert(charsWritten == length); scratch[length] = '\0'; const uint NormalizeToLowercase = 0x0020_0020u; uint hash1 = (5381 << 16) + 5381; uint hash2 = hash1; // Duplicate the main loop, can be removed once JIT gets "Loop Unswitching" optimization fixed(char *src = scratch) { uint *ptr = (uint *)src; while (length > 2) { length -= 4; hash1 = (BitOperations.RotateLeft(hash1, 5) + hash1) ^ (ptr[0] | NormalizeToLowercase); hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (ptr[1] | NormalizeToLowercase); ptr += 2; } if (length > 0) { hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (ptr[0] | NormalizeToLowercase); } } if (borrowedArr != null) { ArrayPool <char> .Shared.Return(borrowedArr); } return((int)(hash1 + (hash2 * 1566083941))); }
private static void Block(ref uint rp0, ref uint rp1) { uint p0 = rp0; uint p1 = rp1; p1 ^= p0; p0 = BitOperations.RotateLeft(p0, 20); p0 += p1; p1 = BitOperations.RotateLeft(p1, 9); p1 ^= p0; p0 = BitOperations.RotateLeft(p0, 27); p0 += p1; p1 = BitOperations.RotateLeft(p1, 19); rp0 = p0; rp1 = p1; }
private static void Block(ref uint rp0, ref uint rp1) { // Intrinsified in mono interpreter uint p0 = rp0; uint p1 = rp1; p1 ^= p0; p0 = BitOperations.RotateLeft(p0, 20); p0 += p1; p1 = BitOperations.RotateLeft(p1, 9); p1 ^= p0; p0 = BitOperations.RotateLeft(p0, 27); p0 += p1; p1 = BitOperations.RotateLeft(p1, 19); rp0 = p0; rp1 = p1; }
[MethodImpl(MethodImplOptions.AggressiveInlining)] // small-ish hot path used by a handful of "next" methods internal uint NextUInt32() { uint s0 = _s0, s1 = _s1, s2 = _s2, s3 = _s3; uint result = BitOperations.RotateLeft(s1 * 5, 7) * 9; uint t = s1 << 9; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; s3 = BitOperations.RotateLeft(s3, 11); _s0 = s0; _s1 = s1; _s2 = s2; _s3 = s3; return(result); }
[MethodImpl(MethodImplOptions.AggressiveInlining)] // small-ish hot path used by a handful of "next" methods internal ulong NextUInt64() { ulong s0 = _s0, s1 = _s1, s2 = _s2, s3 = _s3; ulong result = BitOperations.RotateLeft(s1 * 5, 7) * 9; ulong t = s1 << 17; s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3; s2 ^= t; s3 = BitOperations.RotateLeft(s3, 45); _s0 = s0; _s1 = s1; _s2 = s2; _s3 = s3; return(result); }
static int IBinaryInteger <int> .RotateLeft(int value, int rotateAmount) => (int)BitOperations.RotateLeft((uint)value, rotateAmount);
static ulong IBinaryInteger <ulong> .RotateLeft(ulong value, int rotateAmount) => BitOperations.RotateLeft(value, rotateAmount);
/// <inheritdoc cref="IBinaryInteger{TSelf}.RotateLeft(TSelf, int)" /> public static long RotateLeft(long value, int rotateAmount) => (long)BitOperations.RotateLeft((ulong)value, rotateAmount);
static uint IBinaryInteger <uint> .RotateLeft(uint value, int rotateAmount) => BitOperations.RotateLeft(value, rotateAmount);
private static uint QueueRound(uint hash, uint queuedValue) { return(BitOperations.RotateLeft(hash + (queuedValue * _prime3), 17) * _prime4); }
public static int ComputeHash32(ref byte data, uint count, uint p0, uint p1) { // Control flow of this method generally flows top-to-bottom, trying to // minimize the number of branches taken for large (>= 8 bytes, 4 chars) inputs. // If small inputs (< 8 bytes, 4 chars) are given, this jumps to a "small inputs" // handler at the end of the method. if (count < 8) { // We can't run the main loop, but we might still have 4 or more bytes available to us. // If so, jump to the 4 .. 7 bytes logic immediately after the main loop. if (count >= 4) { goto Between4And7BytesRemain; } else { goto InputTooSmallToEnterMainLoop; } } // Main loop - read 8 bytes at a time. // The block function is unrolled 2x in this loop. uint loopCount = count / 8; Debug.Assert(loopCount > 0, "Shouldn't reach this code path for small inputs."); do { // Most x86 processors have two dispatch ports for reads, so we can read 2x 32-bit // values in parallel. We opt for this instead of a single 64-bit read since the // typical use case for Marvin32 is computing String hash codes, and the particular // layout of String instances means the starting data is never 8-byte aligned when // running in a 64-bit process. p0 += Unsafe.ReadUnaligned <uint>(ref data); uint nextUInt32 = Unsafe.ReadUnaligned <uint>(ref Unsafe.AddByteOffset(ref data, 4)); // One block round for each of the 32-bit integers we just read, 2x rounds total. Block(ref p0, ref p1); p0 += nextUInt32; Block(ref p0, ref p1); // Bump the data reference pointer and decrement the loop count. // Decrementing by 1 every time and comparing against zero allows the JIT to produce // better codegen compared to a standard 'for' loop with an incrementing counter. // Requires https://github.com/dotnet/coreclr/issues/7566 to be addressed first // before we can realize the full benefits of this. data = ref Unsafe.AddByteOffset(ref data, 8); } while (--loopCount > 0); // n.b. We've not been updating the original 'count' parameter, so its actual value is // still the original data length. However, we can still rely on its least significant // 3 bits to tell us how much data remains (0 .. 7 bytes) after the loop above is // completed. if ((count & 0b_0100) == 0) { goto DoFinalPartialRead; } Between4And7BytesRemain: // If after finishing the main loop we still have 4 or more leftover bytes, or if we had // 4 .. 7 bytes to begin with and couldn't enter the loop in the first place, we need to // consume 4 bytes immediately and send them through one round of the block function. Debug.Assert(count >= 4, "Only should've gotten here if the original count was >= 4."); p0 += Unsafe.ReadUnaligned <uint>(ref data); Block(ref p0, ref p1); DoFinalPartialRead: // Finally, we have 0 .. 3 bytes leftover. Since we know the original data length was at // least 4 bytes (smaller lengths are handled at the end of this routine), we can safely // read the 4 bytes at the end of the buffer without reading past the beginning of the // original buffer. This necessarily means the data we're about to read will overlap with // some data we've already processed, but we can handle that below. Debug.Assert(count >= 4, "Only should've gotten here if the original count was >= 4."); // Read the last 4 bytes of the buffer. uint partialResult = Unsafe.ReadUnaligned <uint>(ref Unsafe.Add(ref Unsafe.AddByteOffset(ref data, (nuint)count & 7), -4)); // The 'partialResult' local above contains any data we have yet to read, plus some number // of bytes which we've already read from the buffer. An example of this is given below // for little-endian architectures. In this table, AA BB CC are the bytes which we still // need to consume, and ## are bytes which we want to throw away since we've already // consumed them as part of a previous read. // // (partialResult contains) (we want it to contain) // count mod 4 = 0 -> [ ## ## ## ## | ] -> 0x####_#### -> 0x0000_0080 // count mod 4 = 1 -> [ ## ## ## ## | AA ] -> 0xAA##_#### -> 0x0000_80AA // count mod 4 = 2 -> [ ## ## ## ## | AA BB ] -> 0xBBAA_#### -> 0x0080_BBAA // count mod 4 = 3 -> [ ## ## ## ## | AA BB CC ] -> 0xCCBB_AA## -> 0x80CC_BBAA count = ~count << 3; if (BitConverter.IsLittleEndian) { partialResult >>= 8; // make some room for the 0x80 byte partialResult |= 0x8000_0000u; // put the 0x80 byte at the beginning partialResult >>= (int)count & 0x1F; // shift out all previously consumed bytes } else { partialResult <<= 8; // make some room for the 0x80 byte partialResult |= 0x80u; // put the 0x80 byte at the end partialResult <<= (int)count & 0x1F; // shift out all previously consumed bytes } DoFinalRoundsAndReturn: // Now that we've computed the final partial result, merge it in and run two rounds of // the block function to finish out the Marvin algorithm. p0 += partialResult; Block(ref p0, ref p1); Block(ref p0, ref p1); return((int)(p1 ^ p0)); InputTooSmallToEnterMainLoop: // We had only 0 .. 3 bytes to begin with, so we can't perform any 32-bit reads. // This means that we're going to be building up the final result right away and // will only ever run two rounds total of the block function. Let's initialize // the partial result to "no data". if (BitConverter.IsLittleEndian) { partialResult = 0x80u; } else { partialResult = 0x80000000u; } if ((count & 0b_0001) != 0) { // If the buffer is 1 or 3 bytes in length, let's read a single byte now // and merge it into our partial result. This will result in partialResult // having one of the two values below, where AA BB CC are the buffer bytes. // // (little-endian / big-endian) // [ AA ] -> 0x0000_80AA / 0xAA80_0000 // [ AA BB CC ] -> 0x0000_80CC / 0xCC80_0000 partialResult = Unsafe.AddByteOffset(ref data, (nuint)count & 2); if (BitConverter.IsLittleEndian) { partialResult |= 0x8000; } else { partialResult <<= 24; partialResult |= 0x800000u; } } if ((count & 0b_0010) != 0) { // If the buffer is 2 or 3 bytes in length, let's read a single ushort now // and merge it into the partial result. This will result in partialResult // having one of the two values below, where AA BB CC are the buffer bytes. // // (little-endian / big-endian) // [ AA BB ] -> 0x0080_BBAA / 0xAABB_8000 // [ AA BB CC ] -> 0x80CC_BBAA / 0xAABB_CC80 (carried over from above) if (BitConverter.IsLittleEndian) { partialResult <<= 16; partialResult |= (uint)Unsafe.ReadUnaligned <ushort>(ref data); } else { partialResult |= (uint)Unsafe.ReadUnaligned <ushort>(ref data); partialResult = BitOperations.RotateLeft(partialResult, 16); } } // Everything is consumed! Go perform the final rounds and return. goto DoFinalRoundsAndReturn; }
/// <inheritdoc cref="IBinaryInteger{TSelf}.RotateLeft(TSelf, int)" /> public static uint RotateLeft(uint value, int rotateAmount) => BitOperations.RotateLeft(value, rotateAmount);
private static uint Round(uint hash, uint input) { return(BitOperations.RotateLeft(hash + (input * _prime2), 13) * _prime1); }
/// <inheritdoc cref="IBinaryInteger{TSelf}.RotateLeft(TSelf, int)" /> public static ulong RotateLeft(ulong value, int rotateAmount) => BitOperations.RotateLeft(value, rotateAmount);
/// <inheritdoc cref="IBinaryInteger{TSelf}.RotateLeft(TSelf, int)" /> public static int RotateLeft(int value, int rotateAmount) => (int)BitOperations.RotateLeft((uint)value, rotateAmount);
static long IBinaryInteger <long> .RotateLeft(long value, int rotateAmount) => (long)BitOperations.RotateLeft((ulong)value, rotateAmount);