Coverage Report

Created: 2025-12-12 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hermes/lib/VM/detail/IdentifierHashTable.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#include "hermes/VM/detail/IdentifierHashTable.h"
9
10
#include "hermes/VM/StringPrimitive.h"
11
12
using namespace hermes::vm::detail;
13
// In GCC/CLANG, method definitions can refer to ancestor namespaces of
14
// the namespace that the class is declared in without namespace qualifiers.
15
// This is not allowed in MSVC.
16
using hermes::vm::StringPrimitive;
17
using hermes::vm::SymbolID;
18
19
template <typename T>
20
uint32_t IdentifierHashTable::lookupString(
21
    llvh::ArrayRef<T> str,
22
    uint32_t hash,
23
663k
    bool mustBeNew) const {
24
663k
  assert(identifierTable_ && "identifier table pointer is not initialized");
25
26
663k
  auto cap = capacity();
27
663k
  assert(llvh::isPowerOf2_32(cap) && "capacity must be power of 2");
28
663k
  assert(size_ < cap && "The hash table can never be full");
29
30
663k
#ifdef HERMES_SLOW_DEBUG
31
663k
  assert(hash == hashString(str) && "invalid hash");
32
663k
#endif
33
663k
  uint32_t idx = hash & (cap - 1);
34
663k
  uint32_t base = 1;
35
  // deletedIndex tracks the index of a deleted entry found in the conflict
36
  // chain. If we could not find an entry that matches str, we would return
37
  // the deleted slot for insertion to be able to reuse deleted space.
38
663k
  OptValue<uint32_t> deletedIndex;
39
  // The loop will always terminate as long as the hash table is not full.
40
1.24M
  while (1) {
41
1.24M
    if (table_.isEmpty(idx)) {
42
      // Found an empty entry, meaning that str does not exist in the table.
43
      // If deletedIndex is available, return it, otherwise return idx.
44
322k
      return deletedIndex ? *deletedIndex : idx;
45
919k
    } else if (table_.isDeleted(idx)) {
46
1
      assert(
47
1
          !mustBeNew &&
48
1
          "mustBeNew should never be set if there are deleted entries");
49
1
      deletedIndex = idx;
50
919k
    } else if (!mustBeNew) {
51
      // If mustBeNew is set, we know this string does not exist in the table.
52
      // There is no need to compare.
53
54
879k
      auto &lookupTableEntry =
55
879k
          identifierTable_->getLookupTableEntry(table_.get(idx));
56
879k
      if (lookupTableEntry.getHash() == hash) {
57
340k
        if (lookupTableEntry.isStringPrim()) {
58
339k
          const StringPrimitive *strPrim = lookupTableEntry.getStringPrim();
59
339k
          if (strPrim->isASCII()) {
60
339k
            if (stringRefEquals(str, strPrim->castToASCIIRef())) {
61
339k
              return idx;
62
339k
            }
63
339k
          } else {
64
1
            if (stringRefEquals(str, strPrim->castToUTF16Ref())) {
65
1
              return idx;
66
1
            }
67
1
          }
68
339k
        } else if (lookupTableEntry.isLazyASCII()) {
69
          // Lazy ASCII string.
70
1.75k
          if (stringRefEquals(str, lookupTableEntry.getLazyASCIIRef())) {
71
1.75k
            return idx;
72
1.75k
          }
73
1.75k
        } else {
74
          // UTF16 string.
75
0
          if (stringRefEquals(str, lookupTableEntry.getLazyUTF16Ref())) {
76
0
            return idx;
77
0
          }
78
0
        }
79
340k
      }
80
879k
    }
81
    /// Use quadratic probing to find next probe index in the hash table.
82
    /// h(k, i) = (h(k) + 1/2 * i + 1/2 * i^2) mod m.
83
    /// This guarantees the values of h(k,i) for i in [0,m-1] are all distinct.
84
578k
    idx = (idx + base) & (cap - 1);
85
578k
    ++base;
86
578k
  }
87
663k
}
unsigned int hermes::vm::detail::IdentifierHashTable::lookupString<char>(llvh::ArrayRef<char>, unsigned int, bool) const
Line
Count
Source
23
662k
    bool mustBeNew) const {
24
662k
  assert(identifierTable_ && "identifier table pointer is not initialized");
25
26
662k
  auto cap = capacity();
27
662k
  assert(llvh::isPowerOf2_32(cap) && "capacity must be power of 2");
28
662k
  assert(size_ < cap && "The hash table can never be full");
29
30
662k
#ifdef HERMES_SLOW_DEBUG
31
662k
  assert(hash == hashString(str) && "invalid hash");
32
662k
#endif
33
662k
  uint32_t idx = hash & (cap - 1);
34
662k
  uint32_t base = 1;
35
  // deletedIndex tracks the index of a deleted entry found in the conflict
36
  // chain. If we could not find an entry that matches str, we would return
37
  // the deleted slot for insertion to be able to reuse deleted space.
38
662k
  OptValue<uint32_t> deletedIndex;
39
  // The loop will always terminate as long as the hash table is not full.
40
1.24M
  while (1) {
41
1.24M
    if (table_.isEmpty(idx)) {
42
      // Found an empty entry, meaning that str does not exist in the table.
43
      // If deletedIndex is available, return it, otherwise return idx.
44
321k
      return deletedIndex ? *deletedIndex : idx;
45
918k
    } else if (table_.isDeleted(idx)) {
46
1
      assert(
47
1
          !mustBeNew &&
48
1
          "mustBeNew should never be set if there are deleted entries");
49
1
      deletedIndex = idx;
50
918k
    } else if (!mustBeNew) {
51
      // If mustBeNew is set, we know this string does not exist in the table.
52
      // There is no need to compare.
53
54
879k
      auto &lookupTableEntry =
55
879k
          identifierTable_->getLookupTableEntry(table_.get(idx));
56
879k
      if (lookupTableEntry.getHash() == hash) {
57
340k
        if (lookupTableEntry.isStringPrim()) {
58
339k
          const StringPrimitive *strPrim = lookupTableEntry.getStringPrim();
59
339k
          if (strPrim->isASCII()) {
60
339k
            if (stringRefEquals(str, strPrim->castToASCIIRef())) {
61
339k
              return idx;
62
339k
            }
63
339k
          } else {
64
0
            if (stringRefEquals(str, strPrim->castToUTF16Ref())) {
65
0
              return idx;
66
0
            }
67
0
          }
68
339k
        } else if (lookupTableEntry.isLazyASCII()) {
69
          // Lazy ASCII string.
70
1.75k
          if (stringRefEquals(str, lookupTableEntry.getLazyASCIIRef())) {
71
1.75k
            return idx;
72
1.75k
          }
73
1.75k
        } else {
74
          // UTF16 string.
75
0
          if (stringRefEquals(str, lookupTableEntry.getLazyUTF16Ref())) {
76
0
            return idx;
77
0
          }
78
0
        }
79
340k
      }
80
879k
    }
81
    /// Use quadratic probing to find next probe index in the hash table.
82
    /// h(k, i) = (h(k) + 1/2 * i + 1/2 * i^2) mod m.
83
    /// This guarantees the values of h(k,i) for i in [0,m-1] are all distinct.
84
577k
    idx = (idx + base) & (cap - 1);
85
577k
    ++base;
86
577k
  }
87
662k
}
unsigned int hermes::vm::detail::IdentifierHashTable::lookupString<char16_t>(llvh::ArrayRef<char16_t>, unsigned int, bool) const
Line
Count
Source
23
652
    bool mustBeNew) const {
24
652
  assert(identifierTable_ && "identifier table pointer is not initialized");
25
26
652
  auto cap = capacity();
27
652
  assert(llvh::isPowerOf2_32(cap) && "capacity must be power of 2");
28
652
  assert(size_ < cap && "The hash table can never be full");
29
30
652
#ifdef HERMES_SLOW_DEBUG
31
652
  assert(hash == hashString(str) && "invalid hash");
32
652
#endif
33
652
  uint32_t idx = hash & (cap - 1);
34
652
  uint32_t base = 1;
35
  // deletedIndex tracks the index of a deleted entry found in the conflict
36
  // chain. If we could not find an entry that matches str, we would return
37
  // the deleted slot for insertion to be able to reuse deleted space.
38
652
  OptValue<uint32_t> deletedIndex;
39
  // The loop will always terminate as long as the hash table is not full.
40
1.12k
  while (1) {
41
1.12k
    if (table_.isEmpty(idx)) {
42
      // Found an empty entry, meaning that str does not exist in the table.
43
      // If deletedIndex is available, return it, otherwise return idx.
44
651
      return deletedIndex ? *deletedIndex : idx;
45
651
    } else if (table_.isDeleted(idx)) {
46
0
      assert(
47
0
          !mustBeNew &&
48
0
          "mustBeNew should never be set if there are deleted entries");
49
0
      deletedIndex = idx;
50
469
    } else if (!mustBeNew) {
51
      // If mustBeNew is set, we know this string does not exist in the table.
52
      // There is no need to compare.
53
54
469
      auto &lookupTableEntry =
55
469
          identifierTable_->getLookupTableEntry(table_.get(idx));
56
469
      if (lookupTableEntry.getHash() == hash) {
57
1
        if (lookupTableEntry.isStringPrim()) {
58
1
          const StringPrimitive *strPrim = lookupTableEntry.getStringPrim();
59
1
          if (strPrim->isASCII()) {
60
0
            if (stringRefEquals(str, strPrim->castToASCIIRef())) {
61
0
              return idx;
62
0
            }
63
1
          } else {
64
1
            if (stringRefEquals(str, strPrim->castToUTF16Ref())) {
65
1
              return idx;
66
1
            }
67
1
          }
68
1
        } else if (lookupTableEntry.isLazyASCII()) {
69
          // Lazy ASCII string.
70
0
          if (stringRefEquals(str, lookupTableEntry.getLazyASCIIRef())) {
71
0
            return idx;
72
0
          }
73
0
        } else {
74
          // UTF16 string.
75
0
          if (stringRefEquals(str, lookupTableEntry.getLazyUTF16Ref())) {
76
0
            return idx;
77
0
          }
78
0
        }
79
1
      }
80
469
    }
81
    /// Use quadratic probing to find next probe index in the hash table.
82
    /// h(k, i) = (h(k) + 1/2 * i + 1/2 * i^2) mod m.
83
    /// This guarantees the values of h(k,i) for i in [0,m-1] are all distinct.
84
468
    idx = (idx + base) & (cap - 1);
85
468
    ++base;
86
468
  }
87
652
}
88
89
// Instantiate the templated method so it can be called from other files.
90
91
template uint32_t IdentifierHashTable::lookupString(
92
    llvh::ArrayRef<char> str,
93
    uint32_t hash,
94
    bool mustBeNew) const;
95
96
template uint32_t IdentifierHashTable::lookupString(
97
    llvh::ArrayRef<char16_t> str,
98
    uint32_t hash,
99
    bool mustBeNew) const;
100
101
uint32_t IdentifierHashTable::lookupString(
102
    const StringPrimitive *str,
103
0
    bool mustBeNew) const {
104
0
  if (str->isASCII()) {
105
0
    return lookupString(str->castToASCIIRef(), mustBeNew);
106
0
  } else {
107
0
    return lookupString(str->castToUTF16Ref(), mustBeNew);
108
0
  }
109
0
}
110
111
uint32_t IdentifierHashTable::lookupString(
112
    const StringPrimitive *str,
113
    uint32_t hash,
114
132k
    bool mustBeNew) const {
115
132k
  if (str->isASCII()) {
116
132k
    return lookupString(str->castToASCIIRef(), hash, mustBeNew);
117
132k
  } else {
118
72
    return lookupString(str->castToUTF16Ref(), hash, mustBeNew);
119
72
  }
120
132k
}
121
122
183k
void IdentifierHashTable::insert(uint32_t idx, SymbolID id) {
123
183k
  table_.set(idx, id.unsafeGetIndex());
124
183k
  ++size_;
125
183k
  ++nonEmptyEntryCount_;
126
127
183k
  if (shouldGrow()) {
128
24
    growAndRehash(capacity() * 2);
129
24
  }
130
183k
}
131
132
41
void IdentifierHashTable::remove(const StringPrimitive *str) {
133
41
  if (str->isASCII()) {
134
40
    remove(str->castToASCIIRef());
135
40
  } else {
136
1
    remove(str->castToUTF16Ref());
137
1
  }
138
41
}
139
140
137
void IdentifierHashTable::growAndRehash(uint32_t newCapacity) {
141
  // Guard against potential overflow in the calculation of new capacity.
142
137
  if (LLVM_UNLIKELY(newCapacity <= capacity())) {
143
0
    hermes_fatal("too many identifiers created");
144
0
  }
145
137
  assert(llvh::isPowerOf2_32(newCapacity) && "capacity must be power of 2");
146
137
  CompactTable tmpTable(newCapacity, table_.getCurrentScale());
147
137
  tmpTable.swap(table_);
148
300k
  for (uint32_t oldIdx = 0; oldIdx < tmpTable.size(); ++oldIdx) {
149
300k
    if (!tmpTable.isValid(oldIdx)) {
150
161k
      continue;
151
161k
    }
152
    // Pass true as second argument as we know this string is not in the table.
153
138k
    uint32_t idx = 0;
154
138k
    uint32_t oldVal = tmpTable.get(oldIdx);
155
138k
    auto &lookupTableEntry = identifierTable_->getLookupTableEntry(oldVal);
156
138k
    uint32_t hash = lookupTableEntry.getHash();
157
138k
    if (lookupTableEntry.isStringPrim()) {
158
132k
      idx = lookupString(lookupTableEntry.getStringPrim(), hash, true);
159
132k
    } else if (lookupTableEntry.isLazyASCII()) {
160
5.80k
      idx = lookupString(lookupTableEntry.getLazyASCIIRef(), hash, true);
161
5.80k
    } else if (lookupTableEntry.isLazyUTF16()) {
162
0
      idx = lookupString(lookupTableEntry.getLazyUTF16Ref(), hash, true);
163
0
    }
164
138k
    table_.set(idx, oldVal);
165
138k
  }
166
137
  nonEmptyEntryCount_ = size_;
167
137
}