• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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