1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #ifndef COMMON_COMPONENTS_OBJECTS_STRING_TABLE_HASHTRIEMAP_H
17 #define COMMON_COMPONENTS_OBJECTS_STRING_TABLE_HASHTRIEMAP_H
18
19 #include "common_components/heap/heap.h"
20 #include "common_components/log/log.h"
21 #include "common_interfaces/objects/readonly_handle.h"
22 #include "common_interfaces/objects/base_string.h"
23
24 namespace panda::ecmascript {
25 class TaggedObject;
26 }
27
28 namespace common {
29 class TrieMapConfig {
30 public:
31 static constexpr uint32_t ROOT_BIT = 11U;
32 static constexpr uint32_t ROOT_SIZE = (1 << ROOT_BIT);
33 static constexpr uint32_t ROOT_BIT_MASK = ROOT_SIZE - 1U;
34
35 static constexpr uint32_t N_CHILDREN_LOG2 = 3U;
36 static constexpr uint32_t TOTAL_HASH_BITS = 32U - ROOT_BIT;
37
38 static constexpr uint32_t N_CHILDREN = 1 << N_CHILDREN_LOG2;
39 static constexpr uint32_t N_CHILDREN_MASK = N_CHILDREN - 1U;
40
41 static constexpr uint32_t INDIRECT_SIZE = 8U; // 8: 2^3
42 static constexpr uint32_t INDIRECT_MASK = INDIRECT_SIZE - 1U;
43
44 static constexpr uint64_t HIGH_8_BIT_MASK = 0xFFULL << 56; // used to detect 56-63 Bits
45
46 enum SlotBarrier {
47 NeedSlotBarrier,
48 NoSlotBarrier,
49 };
50 };
51
52 class HashTrieMapEntry;
53 class HashTrieMapIndirect;
54
55 class HashTrieMapNode {
56 public:
57 // Do not use 57-64bits, HWAsan uses 57-64 bits as pointer tag
58 static constexpr uint64_t POINTER_LENGTH = 48;
59 static constexpr uint64_t ENTRY_TAG_MASK = 1ULL << POINTER_LENGTH;
60
61 using Pointer = BitField<uint64_t, 0, POINTER_LENGTH>;
62 using EntryBit = Pointer::NextFlag;
63
HashTrieMapNode()64 explicit HashTrieMapNode() {}
65
IsEntry()66 bool IsEntry() const
67 {
68 uint64_t bitField = *reinterpret_cast<const uint64_t *>(this);
69 return EntryBit::Decode(bitField);
70 }
71
72 HashTrieMapEntry* AsEntry();
73 HashTrieMapIndirect* AsIndirect();
74 };
75
76 class HashTrieMapEntry final : public HashTrieMapNode {
77 public:
HashTrieMapEntry(BaseString * v)78 HashTrieMapEntry(BaseString* v) : overflow_(nullptr)
79 {
80 // Note: CMC GC assumes string is always a non-young object and tries to optimize it out in young GC
81 ASSERT_LOGF(Heap::GetHeap().IsHeapAddress(v)
82 ? Heap::GetHeap().InRecentSpace(v) == false
83 : true,
84 "Violate CMC-GC assumption: should not be young object");
85
86 bitField_ = (ENTRY_TAG_MASK | reinterpret_cast<uint64_t>(v));
87 }
88
89 template <TrieMapConfig::SlotBarrier SlotBarrier>
Value()90 BaseString* Value() const
91 {
92 uint64_t value = Pointer::Decode(bitField_);
93 if constexpr (SlotBarrier == TrieMapConfig::NoSlotBarrier) {
94 return reinterpret_cast<BaseString *>(static_cast<uintptr_t>(value));
95 }
96 return reinterpret_cast<BaseString*>(Heap::GetBarrier().ReadStringTableStaticRef(
97 *reinterpret_cast<RefField<false>*>((void*)(&value))));
98 }
99
SetValue(BaseString * v)100 void SetValue(BaseString* v)
101 {
102 // Note: CMC GC assumes string is always a non-young object and tries to optimize it out in young GC
103 ASSERT_LOGF(Heap::GetHeap().IsHeapAddress(v)
104 ? Heap::GetHeap().InRecentSpace(v) == false
105 : true,
106 "Violate CMC-GC assumption: should not be young object");
107
108 bitField_ = ENTRY_TAG_MASK | reinterpret_cast<uint64_t>(v);
109 }
110
Overflow()111 std::atomic<HashTrieMapEntry*>& Overflow()
112 {
113 return overflow_;
114 }
115
GetBitField()116 uint64_t GetBitField() const
117 {
118 return bitField_;
119 }
120
121 private:
122 uint64_t bitField_;
123 std::atomic<HashTrieMapEntry*> overflow_;
124 };
125
126 class HashTrieMapIndirect final : public HashTrieMapNode {
127 public:
128 std::array<std::atomic<uint64_t>, TrieMapConfig::INDIRECT_SIZE> children_{};
129
HashTrieMapIndirect()130 explicit HashTrieMapIndirect() {}
131
~HashTrieMapIndirect()132 ~HashTrieMapIndirect()
133 {
134 for (std::atomic<uint64_t>& temp : children_) {
135 auto &child = reinterpret_cast<std::atomic<HashTrieMapNode*>&>(temp);
136 HashTrieMapNode* node = child.exchange(nullptr, std::memory_order_relaxed);
137 if (node == nullptr) {
138 continue;
139 }
140 if (!node->IsEntry()) {
141 delete node->AsIndirect();
142 continue;
143 }
144 HashTrieMapEntry* e = node->AsEntry();
145 // Clear overflow chain
146 for (HashTrieMapEntry* current = e->Overflow().exchange(nullptr, std::memory_order_relaxed); current !=
147 nullptr
148 ;) {
149 // atom get the next node and break the link
150 HashTrieMapEntry* next = current->Overflow().exchange(nullptr, std::memory_order_relaxed);
151 // deletion of the current node
152 delete current;
153 // move to the next node
154 current = next;
155 }
156 delete e;
157 }
158 }
159
GetChild(size_t index)160 std::atomic<HashTrieMapNode*>& GetChild(size_t index)
161 {
162 return reinterpret_cast<std::atomic<HashTrieMapNode*>&>(children_[index]);
163 }
164 };
165
166 struct HashTrieMapLoadResult {
167 BaseString* value;
168 HashTrieMapIndirect* current;
169 uint32_t hashShift;
170 std::atomic<HashTrieMapNode*>* slot;
171 };
172
AsEntry()173 inline HashTrieMapEntry* HashTrieMapNode::AsEntry()
174 {
175 ASSERT(IsEntry() && "HashTrieMap: called entry on non-entry node");
176 return static_cast<HashTrieMapEntry*>(this);
177 }
178
AsIndirect()179 inline HashTrieMapIndirect* HashTrieMapNode::AsIndirect()
180 {
181 ASSERT(!IsEntry() && "HashTrieMap: called indirect on entry node");
182 return static_cast<HashTrieMapIndirect*>(this);
183 }
184
185 template <typename Mutex, typename ThreadHolder, TrieMapConfig::SlotBarrier SlotBarrier>
186 class HashTrieMap {
187 public:
188 using WeakRefFieldVisitor = std::function<bool(RefField<>&)>;
189 using WeakRootVisitor = std::function<panda::ecmascript::TaggedObject *(panda::ecmascript::TaggedObject* p)>;
190 using Node = HashTrieMapNode;
191 using Indirect = HashTrieMapIndirect;
192 using Entry = HashTrieMapEntry;
193 using LoadResult = HashTrieMapLoadResult;
HashTrieMap()194 HashTrieMap() {}
195
~HashTrieMap()196 ~HashTrieMap()
197 {
198 Clear();
199 };
200
201 #if ECMASCRIPT_ENABLE_TRACE_STRING_TABLE
202 class StringTableTracer {
203 public:
204 static constexpr uint32_t DUMP_THRESHOLD = 40000;
GetInstance()205 static StringTableTracer& GetInstance()
206 {
207 static StringTableTracer tracer;
208 return tracer;
209 }
210
211 NO_COPY_SEMANTIC_CC(StringTableTracer);
212 NO_MOVE_SEMANTIC_CC(StringTableTracer);
213
TraceFindSuccess(uint32_t hashShift)214 void TraceFindSuccess(uint32_t hashShift)
215 {
216 totalDepth_.fetch_add(hashShift / TrieMapConfig::N_CHILDREN_LOG2 + 1, std::memory_order_relaxed);
217 uint64_t currentSuccess = totalSuccessNum_.fetch_add(1, std::memory_order_relaxed) + 1;
218 if (currentSuccess >= lastDumpPoint_.load(std::memory_order_relaxed) + DUMP_THRESHOLD) {
219 DumpWithLock(currentSuccess);
220 }
221 }
222
TraceFindFail()223 void TraceFindFail()
224 {
225 totalFailNum_.fetch_add(1, std::memory_order_relaxed);
226 }
227
228 private:
229 StringTableTracer() = default;
230
DumpWithLock(uint64_t triggerPoint)231 void DumpWithLock(uint64_t triggerPoint)
232 {
233 std::lock_guard<std::mutex> lock(mu_);
234
235 if (triggerPoint >= lastDumpPoint_.load(std::memory_order_relaxed) + DUMP_THRESHOLD) {
236 lastDumpPoint_ = triggerPoint;
237 DumpInfo();
238 }
239 }
240
DumpInfo()241 void DumpInfo() const
242 {
243 uint64_t depth = totalDepth_.load(std::memory_order_relaxed);
244 uint64_t success = totalSuccessNum_.load(std::memory_order_relaxed);
245 uint64_t fail = totalFailNum_.load(std::memory_order_relaxed);
246
247 double avgDepth = (static_cast<double>(depth) / success);
248
249 LOG_COMMON(INFO) << "------------------------------------------------------------"
250 << "---------------------------------------------------------";
251 LOG_COMMON(INFO) << "StringTableTotalSuccessFindNum: " << success;
252 LOG_COMMON(INFO) << "StringTableTotalInsertNum: " << fail;
253 LOG_COMMON(INFO) << "StringTableAverageDepth: " << avgDepth;
254 LOG_COMMON(INFO) << "------------------------------------------------------------"
255 << "---------------------------------------------------------";
256 }
257
258 std::mutex mu_;
259 std::atomic<uint64_t> totalDepth_{0};
260 std::atomic<uint64_t> totalSuccessNum_{0};
261 std::atomic<uint64_t> totalFailNum_{0};
262 std::atomic<uint64_t> lastDumpPoint_{0};
263 };
264
TraceFindSuccessDepth(uint32_t hashShift)265 void TraceFindSuccessDepth(uint32_t hashShift)
266 {
267 StringTableTracer::GetInstance().TraceFindSuccess(hashShift);
268 }
269
TraceFindFail()270 void TraceFindFail()
271 {
272 StringTableTracer::GetInstance().TraceFindFail();
273 }
274 #endif
275 template <typename ReadBarrier>
276 LoadResult Load(ReadBarrier&& readBarrier, const uint32_t key, BaseString* value);
277
278 template <bool threadState = true, typename ReadBarrier, typename HandleType>
279 BaseString* StoreOrLoad(ThreadHolder* holder, ReadBarrier&& readBarrier, const uint32_t key, LoadResult loadResult,
280 HandleType str);
281 template <typename ReadBarrier>
282 LoadResult Load(ReadBarrier&& readBarrier, const uint32_t key, const ReadOnlyHandle<BaseString>& string,
283 uint32_t offset, uint32_t utf8Len);
284 template <typename LoaderCallback, typename EqualsCallback>
285 BaseString* StoreOrLoad(ThreadHolder* holder, const uint32_t key, LoadResult loadResult,
286 LoaderCallback loaderCallback, EqualsCallback equalsCallback);
287
288 template <bool IsCheck, typename ReadBarrier>
289 BaseString* Load(ReadBarrier&& readBarrier, const uint32_t key, BaseString* value);
290 template <bool IsLock, typename LoaderCallback, typename EqualsCallback>
291 BaseString* LoadOrStore(ThreadHolder* holder, const uint32_t key, LoaderCallback loaderCallback,
292 EqualsCallback equalsCallback);
293
294 template <typename LoaderCallback, typename EqualsCallback>
295 BaseString* LoadOrStoreForJit(ThreadHolder* holder, const uint32_t key, LoaderCallback loaderCallback,
296 EqualsCallback equalsCallback);
297
ProcessHash(uint32_t & hash)298 static void ProcessHash(uint32_t &hash)
299 {
300 hash >>= TrieMapConfig::ROOT_BIT;
301 }
302
GetRootAndProcessHash(uint32_t & hash)303 Indirect* GetRootAndProcessHash(uint32_t &hash)
304 {
305 uint32_t rootID = (hash & TrieMapConfig::ROOT_BIT_MASK);
306 hash >>= TrieMapConfig::ROOT_BIT;
307 auto root = root_[rootID].load(std::memory_order_acquire);
308 if (root != nullptr) {
309 return root;
310 } else {
311 Indirect* expected = nullptr;
312 Indirect* newRoot = new Indirect();
313
314 if (root_[rootID].compare_exchange_strong(expected, newRoot,
315 std::memory_order_release, std::memory_order_acquire)) {
316 return newRoot;
317 } else {
318 delete newRoot;
319 return expected;
320 }
321 }
322 }
323
324 // All other threads have stopped due to the gc and Clear phases.
325 // Therefore, the operations related to atoms in ClearNodeFromGc and Clear use std::memory_order_relaxed,
326 // which ensures atomicity but does not guarantee memory order
327 template <TrieMapConfig::SlotBarrier Barrier = SlotBarrier,
328 std::enable_if_t<Barrier == TrieMapConfig::NeedSlotBarrier, int> = 0>
329 bool ClearNodeFromGC(Indirect* parent, int index, const WeakRefFieldVisitor& visitor,
330 std::vector<Entry*>& waitDeleteEntries);
331
332 template <TrieMapConfig::SlotBarrier Barrier = SlotBarrier,
333 std::enable_if_t<Barrier == TrieMapConfig::NoSlotBarrier, int> = 0>
334 bool ClearNodeFromGC(Indirect* parent, int index, const WeakRefFieldVisitor& visitor);
335
336 template <TrieMapConfig::SlotBarrier Barrier = SlotBarrier,
337 std::enable_if_t<Barrier == TrieMapConfig::NoSlotBarrier, int> = 0>
338 bool ClearNodeFromGC(Indirect* parent, int index, const WeakRootVisitor& visitor);
339
340 // Iterator
341 template<typename ReadBarrier>
Range(ReadBarrier && readBarrier,bool & isValid)342 void Range(ReadBarrier &&readBarrier, bool &isValid)
343 {
344 std::function<bool(Node *)> iter = [readBarrier, &isValid, this](Node *node) {
345 if (node->IsEntry()) {
346 BaseString *value = node->AsEntry()->Value<SlotBarrier>();
347 if (!IsNull(value) && !this->CheckValidity(readBarrier, value, isValid)) {
348 return false;
349 }
350 }
351 return true;
352 };
353 Range(iter);
354 }
355
Range(std::function<bool (Node *)> & iter)356 void Range(std::function<bool(Node*)> &iter)
357 {
358 for (uint32_t i = 0; i < TrieMapConfig::ROOT_SIZE; i++) {
359 if (!Iter(root_[i].load(std::memory_order_acquire), iter)) {
360 return;
361 }
362 }
363 }
364
Clear()365 void Clear()
366 {
367 for (uint32_t i = 0; i < TrieMapConfig::ROOT_SIZE; i++) {
368 // The atom replaces the root node with nullptr and obtains the old root node
369 Indirect* oldRoot = root_[i].exchange(nullptr, std::memory_order_relaxed);
370 if (oldRoot != nullptr) {
371 // Clear the entire HashTreeMap based on the Indirect destructor
372 delete oldRoot;
373 }
374 }
375 }
376
377 // ut used
GetRoot(uint32_t index)378 const std::atomic<Indirect*>& GetRoot(uint32_t index) const
379 {
380 ASSERT(index < TrieMapConfig::ROOT_SIZE);
381 return root_[index];
382 }
383
IncreaseInuseCount()384 void IncreaseInuseCount()
385 {
386 inuseCount_.fetch_add(1);
387 }
388
DecreaseInuseCount()389 void DecreaseInuseCount()
390 {
391 inuseCount_.fetch_sub(1);
392 }
393
GetMutex()394 Mutex& GetMutex()
395 {
396 return mu_;
397 }
398
399 template <TrieMapConfig::SlotBarrier Barrier = SlotBarrier,
400 std::enable_if_t<Barrier == TrieMapConfig::NeedSlotBarrier, int> = 0>
CleanUp()401 void CleanUp()
402 {
403 for (const HashTrieMapEntry* entry : waitFreeEntries_) {
404 delete entry;
405 }
406 waitFreeEntries_.clear();
407 }
408
StartSweeping()409 void StartSweeping()
410 {
411 GetMutex().Lock();
412 isSweeping = true;
413 GetMutex().Unlock();
414 }
415
FinishSweeping()416 void FinishSweeping()
417 {
418 GetMutex().Lock();
419 isSweeping = false;
420 GetMutex().Unlock();
421 }
422 std::atomic<Indirect*> root_[TrieMapConfig::ROOT_SIZE] = {};
423 private:
424 Mutex mu_;
425 std::vector<Entry*> waitFreeEntries_{};
426 std::atomic<uint32_t> inuseCount_{0};
427 bool isSweeping{false};
428 template <bool IsLock>
429 Node* Expand(Entry* oldEntry, Entry* newEntry,
430 uint32_t oldHash, uint32_t newHash, uint32_t hashShift, Indirect* parent);
431
432 bool Iter(Indirect *node, std::function<bool(Node *)> &iter);
433
434 bool CheckWeakRef(const WeakRefFieldVisitor& visitor, Entry* entry);
435 bool CheckWeakRef(const WeakRootVisitor& visitor, Entry* entry);
436
437 template <typename ReadBarrier>
438 bool CheckValidity(ReadBarrier&& readBarrier, BaseString* value, bool& isValid);
439
IsNull(BaseString * value)440 constexpr static bool IsNull(BaseString* value)
441 {
442 if constexpr (SlotBarrier == TrieMapConfig::NoSlotBarrier) {
443 return false;
444 }
445 return value == nullptr;
446 }
447
PruneHead(Entry * entry)448 constexpr Entry* PruneHead(Entry* entry)
449 {
450 if constexpr (SlotBarrier == TrieMapConfig::NoSlotBarrier) {
451 return entry;
452 }
453 if (entry == nullptr || isSweeping) {
454 // can't be pruned when sweeping.
455 return entry;
456 }
457 if (entry->Value<SlotBarrier>() == nullptr) {
458 waitFreeEntries_.push_back(entry);
459 return entry->Overflow();
460 }
461 return entry;
462 }
463 };
464
465 template <typename Mutex, typename ThreadHolder, TrieMapConfig::SlotBarrier SlotBarrier>
466 class HashTrieMapInUseScope {
467 public:
HashTrieMapInUseScope(HashTrieMap<Mutex,ThreadHolder,SlotBarrier> * hashTrieMap)468 HashTrieMapInUseScope(HashTrieMap<Mutex, ThreadHolder, SlotBarrier>* hashTrieMap) : hashTrieMap_(hashTrieMap)
469 {
470 hashTrieMap_->IncreaseInuseCount();
471 }
472
~HashTrieMapInUseScope()473 ~HashTrieMapInUseScope()
474 {
475 hashTrieMap_->DecreaseInuseCount();
476 }
477
478 NO_COPY_SEMANTIC(HashTrieMapInUseScope);
479 NO_MOVE_SEMANTIC(HashTrieMapInUseScope);
480
481 private:
482 HashTrieMap<Mutex, ThreadHolder, SlotBarrier>* hashTrieMap_;
483 };
484 }
485 #endif //COMMON_COMPONENTS_OBJECTS_STRING_TABLE_HASHTRIEMAP_H
486