| // Copyright 2017 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef V8_STRINGS_STRING_HASHER_INL_H_ |
| #define V8_STRINGS_STRING_HASHER_INL_H_ |
| |
| #include "src/strings/string-hasher.h" |
| |
| #include <type_traits> |
| |
| #include "src/objects/objects.h" |
| #include "src/objects/string-inl.h" |
| #include "src/strings/char-predicates-inl.h" |
| #include "src/utils/utils-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| uint32_t StringHasher::AddCharacterCore(uint32_t running_hash, uint16_t c) { |
| running_hash += c; |
| running_hash += (running_hash << 10); |
| running_hash ^= (running_hash >> 6); |
| return running_hash; |
| } |
| |
| uint32_t StringHasher::GetHashCore(uint32_t running_hash) { |
| running_hash += (running_hash << 3); |
| running_hash ^= (running_hash >> 11); |
| running_hash += (running_hash << 15); |
| int32_t hash = static_cast<int32_t>(running_hash & String::kHashBitMask); |
| int32_t mask = (hash - 1) >> 31; |
| return running_hash | (kZeroHash & mask); |
| } |
| |
| uint32_t StringHasher::GetTrivialHash(int length) { |
| DCHECK_GT(length, String::kMaxHashCalcLength); |
| // The hash of a large string is simply computed from the length. |
| // Ensure that the max length is small enough to be shifted without losing |
| // information. |
| STATIC_ASSERT(base::bits::CountLeadingZeros32(String::kMaxLength) >= |
| String::kHashShift); |
| uint32_t hash = static_cast<uint32_t>(length); |
| return (hash << String::kHashShift) | String::kIsNotIntegerIndexMask; |
| } |
| |
| template <typename char_t> |
| uint32_t StringHasher::HashSequentialString(const char_t* chars_raw, int length, |
| uint64_t seed) { |
| STATIC_ASSERT(std::is_integral<char_t>::value); |
| STATIC_ASSERT(sizeof(char_t) <= 2); |
| using uchar = typename std::make_unsigned<char_t>::type; |
| const uchar* chars = reinterpret_cast<const uchar*>(chars_raw); |
| DCHECK_LE(0, length); |
| DCHECK_IMPLIES(0 < length, chars != nullptr); |
| if (length >= 1) { |
| if (IsDecimalDigit(chars[0]) && (length == 1 || chars[0] != '0')) { |
| if (length <= String::kMaxArrayIndexSize) { |
| // Possible array index; try to compute the array index hash. |
| uint32_t index = chars[0] - '0'; |
| int i = 1; |
| do { |
| if (i == length) { |
| return MakeArrayIndexHash(index, length); |
| } |
| } while (TryAddArrayIndexChar(&index, chars[i++])); |
| } |
| // The following block wouldn't do anything on 32-bit platforms, |
| // because kMaxArrayIndexSize == kMaxIntegerIndexSize there, and |
| // if we wanted to compile it everywhere, then {index_big} would |
| // have to be a {size_t}, which the Mac compiler doesn't like to |
| // implicitly cast to uint64_t for the {TryAddIndexChar} call. |
| #if V8_HOST_ARCH_64_BIT |
| // No "else" here: if the block above was entered and fell through, |
| // we'll have to take this branch. |
| if (length <= String::kMaxIntegerIndexSize) { |
| // Not an array index, but it could still be an integer index. |
| // Perform a regular hash computation, and additionally check |
| // if there are non-digit characters. |
| uint32_t is_integer_index = 0; |
| uint32_t running_hash = static_cast<uint32_t>(seed); |
| uint64_t index_big = 0; |
| const uchar* end = &chars[length]; |
| while (chars != end) { |
| if (is_integer_index == 0 && |
| !TryAddIntegerIndexChar(&index_big, *chars)) { |
| is_integer_index = String::kIsNotIntegerIndexMask; |
| } |
| running_hash = AddCharacterCore(running_hash, *chars++); |
| } |
| uint32_t hash = (GetHashCore(running_hash) << String::kHashShift) | |
| is_integer_index; |
| if (Name::ContainsCachedArrayIndex(hash)) { |
| // The hash accidentally looks like a cached index. Fix that by |
| // setting a bit that looks like a longer-than-cacheable string |
| // length. |
| hash |= (String::kMaxCachedArrayIndexLength + 1) |
| << String::ArrayIndexLengthBits::kShift; |
| } |
| DCHECK(!Name::ContainsCachedArrayIndex(hash)); |
| return hash; |
| } |
| #endif |
| } |
| // No "else" here: if the first character was a decimal digit, we might |
| // still have to take this branch. |
| if (length > String::kMaxHashCalcLength) { |
| return GetTrivialHash(length); |
| } |
| } |
| |
| // Non-index hash. |
| uint32_t running_hash = static_cast<uint32_t>(seed); |
| const uchar* end = &chars[length]; |
| while (chars != end) { |
| running_hash = AddCharacterCore(running_hash, *chars++); |
| } |
| |
| return (GetHashCore(running_hash) << String::kHashShift) | |
| String::kIsNotIntegerIndexMask; |
| } |
| |
| std::size_t SeededStringHasher::operator()(const char* name) const { |
| return StringHasher::HashSequentialString( |
| name, static_cast<int>(strlen(name)), hashseed_); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_STRINGS_STRING_HASHER_INL_H_ |