1/*
2 * Copyright (c) 2021 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 ECMASCRIPT_ECMA_GLOBAL_STORAGE_H
17#define ECMASCRIPT_ECMA_GLOBAL_STORAGE_H
18
19#include "ecmascript/js_thread.h"
20#include "ecmascript/mem/native_area_allocator.h"
21#include "ecmascript/mem/c_containers.h"
22#include "ecmascript/platform/backtrace.h"
23#ifdef HOOK_ENABLE
24#include "memory_trace.h"
25#endif
26
27namespace panda::ecmascript {
28class Node {
29public:
30    JSTaggedType GetObject() const
31    {
32        return obj_;
33    }
34
35    void SetObject(JSTaggedType obj)
36    {
37        obj_ = obj;
38    }
39
40    Node *GetNext() const
41    {
42        return next_;
43    }
44
45    void SetNext(Node *node)
46    {
47        next_ = node;
48    }
49
50    Node *GetPrev() const
51    {
52        return prev_;
53    }
54
55    void SetPrev(Node *node)
56    {
57        prev_ = node;
58    }
59
60    uint32_t GetIndex() const
61    {
62        return index_;
63    }
64
65    void SetIndex(uint32_t index)
66    {
67        index_ = index;
68    }
69
70    void SetUsing(bool isUsing)
71    {
72        isUsing_ = isUsing;
73    }
74
75    void SetWeak(bool isWeak)
76    {
77        isWeak_ = isWeak;
78    }
79
80    bool IsUsing() const
81    {
82        return isUsing_;
83    }
84
85    bool IsWeak() const
86    {
87        return isWeak_;
88    }
89
90    uintptr_t GetObjectAddress() const
91    {
92        return reinterpret_cast<uintptr_t>(&obj_);
93    }
94
95    // If isUsing is true, it means that the node is being used, otherwise it means that node is be freed
96    void Reset([[maybe_unused]] JSThread *thread, Node *next, JSTaggedType value, bool isUsing)
97    {
98        SetPrev(nullptr);
99        SetNext(next);
100        SetObject(value);
101        SetUsing(isUsing);
102#ifdef HOOK_ENABLE
103        memtrace((void *)next, sizeof(Node), "ArkJsGlobalHandle", isUsing);
104#endif
105    }
106
107private:
108    JSTaggedType obj_;
109    Node *next_ {nullptr};
110    Node *prev_ {nullptr};
111    uint32_t index_ {0};
112    bool isUsing_ {false};
113    bool isWeak_ {false};
114};
115
116class DebugNode : public Node {
117public:
118    void Reset(JSThread *thread, Node *next, JSTaggedType value, bool isUsing)
119    {
120        Node::Reset(thread, next, value, isUsing);
121        ResetDebugInfo();
122        if (isUsing && thread->IsStartGlobalLeakCheck()) {
123            if (JSTaggedValue(value).IsHeapObject()) {
124                if (thread->EnableGlobalObjectLeakCheck()) {
125                    SaveBacktrace(thread, value);
126                }
127            } else {
128                if (thread->EnableGlobalPrimitiveLeakCheck()) {
129                    SaveBacktrace(thread, value);
130                }
131            }
132        }
133    }
134
135    int32_t GetMarkCount() const
136    {
137        return markCount_;
138    }
139
140    void MarkCount()
141    {
142        markCount_++;
143    }
144
145    void ResetDebugInfo()
146    {
147        markCount_ = 0;
148        globalNumber_ = -1;
149    }
150
151    int32_t GetGlobalNumber()
152    {
153        return globalNumber_;
154    }
155
156private:
157    void SaveBacktrace(JSThread *thread, JSTaggedType value)
158    {
159        globalNumber_ = static_cast<int32_t>(thread->IncreaseGlobalNumberCount());
160        std::ostringstream stack;
161        stack << "Global Handle Number:[" << globalNumber_ << "], value:0x" <<
162            std::hex << value << std::endl;
163        Backtrace(stack, true);
164        thread->WriteToStackTraceFd(stack);
165    }
166
167    int32_t markCount_ {0};
168    // A number generated in the order of distribution.It Used to help locate global memory leaks.
169    int32_t globalNumber_ {-1};
170};
171
172class WeakNode : public Node {
173public:
174    void SetReference(void *ref)
175    {
176        reference_ = ref;
177    }
178
179    void* GetReference() const
180    {
181        return reference_;
182    }
183
184    void SetFreeGlobalCallback(WeakClearCallback callback)
185    {
186        freeGlobalCallback_ = callback;
187    }
188
189    void SetNativeFinalizeCallback(WeakClearCallback callback)
190    {
191        nativeFinalizeCallback_ = callback;
192    }
193
194    WeakClearCallback GetNativeFinalizeCallback() const
195    {
196        return nativeFinalizeCallback_;
197    }
198
199    WeakClearCallback GetFreeGlobalCallback() const
200    {
201        return freeGlobalCallback_;
202    }
203
204    void CallFreeGlobalCallback()
205    {
206        if (freeGlobalCallback_ != nullptr) {
207            freeGlobalCallback_(reference_);
208        }
209    }
210
211    void CallNativeFinalizeCallback()
212    {
213        if (nativeFinalizeCallback_ != nullptr) {
214            nativeFinalizeCallback_(reference_);
215        }
216    }
217private:
218    void *reference_ {nullptr};
219    WeakClearCallback freeGlobalCallback_ {nullptr};
220    WeakClearCallback nativeFinalizeCallback_ {nullptr};
221};
222
223template<typename T>
224class NodeList {
225public:
226    NodeList()
227    {
228        bool isWeak = std::is_same<T, WeakNode>::value;
229        for (uint32_t i = 0; i < GLOBAL_BLOCK_SIZE; i++) {
230            nodeList_[i].SetIndex(i);
231            nodeList_[i].SetWeak(isWeak);
232        }
233    }
234    ~NodeList() = default;
235
236    inline static NodeList<T> *NodeToNodeList(T *node)
237    {
238        uintptr_t ptr = ToUintPtr(node) - node->GetIndex() * sizeof(T);
239        return reinterpret_cast<NodeList<T> *>(ptr);
240    }
241
242    inline T *NewNode(JSThread *thread, JSTaggedType value)
243    {
244        if (IsFull()) {
245            return nullptr;
246        }
247        T *node = &nodeList_[index_++];
248        node->Reset(thread, usedList_, value, true);
249        if (usedList_ != nullptr) {
250            usedList_->SetPrev(node);
251        }
252
253        usedList_ = node;
254        return node;
255    }
256
257    inline T *GetFreeNode(JSThread *thread, JSTaggedType value)
258    {
259        T *node = freeList_;
260        if (node != nullptr) {
261            freeList_ = reinterpret_cast<T *>(node->GetNext());
262            node->Reset(thread, usedList_, value, true);
263            if (usedList_ != nullptr) {
264                usedList_->SetPrev(node);
265            }
266            usedList_ = node;
267        }
268        return node;
269    }
270
271    inline void FreeNode(JSThread *thread, T *node)
272    {
273        if (node->GetPrev() != nullptr) {
274            node->GetPrev()->SetNext(node->GetNext());
275        }
276        if (node->GetNext() != nullptr) {
277            node->GetNext()->SetPrev(node->GetPrev());
278        }
279        if (node == usedList_) {
280            usedList_ = reinterpret_cast<T *>(node->GetNext());
281        }
282        node->Reset(thread, freeList_, JSTaggedValue::Undefined().GetRawData(), false);
283        if (node->IsWeak()) {
284            reinterpret_cast<WeakNode *>(node)->SetReference(nullptr);
285            reinterpret_cast<WeakNode *>(node)->SetFreeGlobalCallback(nullptr);
286            reinterpret_cast<WeakNode *>(node)->SetNativeFinalizeCallback(nullptr);
287        }
288        if (freeList_ != nullptr) {
289            freeList_->SetPrev(node);
290        }
291        freeList_ = node;
292    }
293
294    inline void LinkTo(NodeList<T> *prev)
295    {
296        next_ = nullptr;
297        prev_ = prev;
298        prev_->next_ = this;
299    }
300    inline void RemoveList()
301    {
302        if (next_ != nullptr) {
303            next_->SetPrev(prev_);
304        }
305        if (prev_ != nullptr) {
306            prev_->SetNext(next_);
307        }
308        if (freeNext_ != nullptr) {
309            freeNext_->SetFreePrev(freePrev_);
310        }
311        if (freePrev_ != nullptr) {
312            freePrev_->SetFreeNext(freeNext_);
313        }
314    }
315
316    inline bool IsFull()
317    {
318        return index_ >= GLOBAL_BLOCK_SIZE;
319    }
320
321    inline bool HasFreeNode()
322    {
323        return freeList_ != nullptr;
324    }
325
326    inline bool HasUsagedNode()
327    {
328        return !IsFull() || usedList_ != nullptr;
329    }
330
331    inline void SetNext(NodeList *next)
332    {
333        next_ = next;
334    }
335    inline NodeList<T> *GetNext() const
336    {
337        return next_;
338    }
339
340    inline void SetPrev(NodeList<T> *prev)
341    {
342        prev_ = prev;
343    }
344    inline NodeList<T> *GetPrev() const
345    {
346        return prev_;
347    }
348
349    inline void SetFreeNext(NodeList<T> *next)
350    {
351        freeNext_ = next;
352    }
353    inline NodeList<T> *GetFreeNext() const
354    {
355        return freeNext_;
356    }
357
358    inline void SetFreePrev(NodeList<T> *prev)
359    {
360        freePrev_ = prev;
361    }
362    inline NodeList<T> *GetFreePrev() const
363    {
364        return freePrev_;
365    }
366
367    template<class Callback>
368    inline void IterateUsageGlobal(Callback callback)
369    {
370        T *next = usedList_;
371        T *current = nullptr;
372        while (next != nullptr) {
373            current = next;
374            next = reinterpret_cast<T *>(current->GetNext());
375            ASSERT(current != next);
376            callback(current);
377        }
378    }
379
380private:
381    static const uint32_t GLOBAL_BLOCK_SIZE = 256;
382    T nodeList_[GLOBAL_BLOCK_SIZE];  // all
383    T *freeList_ {nullptr};  // dispose node
384    T *usedList_ {nullptr};  // usage node
385    uint32_t index_ {0};
386    NodeList<T> *next_ {nullptr};
387    NodeList<T> *prev_ {nullptr};
388    NodeList<T> *freeNext_ {nullptr};
389    NodeList<T> *freePrev_ {nullptr};
390};
391
392template<typename T>
393class EcmaGlobalStorage {
394public:
395    using WeakClearCallback = void (*)(void *);
396
397    EcmaGlobalStorage(JSThread *thread, NativeAreaAllocator *allocator)
398        : thread_(thread), allocator_(allocator)
399    {
400        topGlobalNodes_ = lastGlobalNodes_ = allocator_->New<NodeList<T>>();
401        topWeakGlobalNodes_ = lastWeakGlobalNodes_ = allocator_->New<NodeList<WeakNode>>();
402    }
403
404    ~EcmaGlobalStorage()
405    {
406        auto *weakNext = topWeakGlobalNodes_;
407        NodeList<WeakNode> *weakCurrent = nullptr;
408        while (weakNext != nullptr) {
409            weakCurrent = weakNext;
410            weakNext = weakCurrent->GetNext();
411            weakCurrent->IterateUsageGlobal([] (WeakNode *node) {
412                node->SetUsing(false);
413                node->SetObject(JSTaggedValue::Undefined().GetRawData());
414                node->CallFreeGlobalCallback();
415                node->CallNativeFinalizeCallback();
416            });
417            allocator_->Delete(weakCurrent);
418        }
419
420        auto *next = topGlobalNodes_;
421        NodeList<T> *current = nullptr;
422        while (next != nullptr) {
423            current = next;
424            next = current->GetNext();
425            current->IterateUsageGlobal([] (T *node) {
426                node->SetUsing(false);
427                node->SetObject(JSTaggedValue::Undefined().GetRawData());
428            });
429            allocator_->Delete(current);
430        }
431    }
432
433    inline uintptr_t NewGlobalHandle(JSTaggedType value)
434    {
435        return NewGlobalHandleImplement(&lastGlobalNodes_, &freeListNodes_, value);
436    }
437
438    inline void DisposeGlobalHandle(uintptr_t nodeAddr)
439    {
440        T *node = reinterpret_cast<T *>(nodeAddr);
441        if (!node->IsUsing()) {
442            return;
443        }
444        if (node->IsWeak()) {
445            DisposeGlobalHandleInner(reinterpret_cast<WeakNode *>(node), &weakFreeListNodes_, &topWeakGlobalNodes_,
446                &lastWeakGlobalNodes_);
447        } else {
448            DisposeGlobalHandleInner(node, &freeListNodes_, &topGlobalNodes_, &lastGlobalNodes_);
449        }
450    }
451
452    inline uintptr_t SetWeak(uintptr_t nodeAddr, void *ref = nullptr, WeakClearCallback freeGlobalCallBack = nullptr,
453                             WeakClearCallback nativeFinalizeCallBack = nullptr)
454    {
455        auto value = reinterpret_cast<T *>(nodeAddr)->GetObject();
456        DisposeGlobalHandle(nodeAddr);
457        uintptr_t addr = NewGlobalHandleImplement(&lastWeakGlobalNodes_, &weakFreeListNodes_, value);
458        WeakNode *node = reinterpret_cast<WeakNode *>(addr);
459        node->SetReference(ref);
460        node->SetFreeGlobalCallback(freeGlobalCallBack);
461        node->SetNativeFinalizeCallback(nativeFinalizeCallBack);
462        return addr;
463    }
464
465    inline uintptr_t ClearWeak(uintptr_t nodeAddr)
466    {
467        auto value = reinterpret_cast<T *>(nodeAddr)->GetObject();
468        DisposeGlobalHandle(nodeAddr);
469        uintptr_t ret = NewGlobalHandleImplement(&lastGlobalNodes_, &freeListNodes_, value);
470        return ret;
471    }
472    inline bool IsWeak(uintptr_t addr) const
473    {
474        T *node = reinterpret_cast<T *>(addr);
475        return node->IsWeak();
476    }
477
478    template<class Callback>
479    void IterateUsageGlobal(Callback callback)
480    {
481        NodeList<T> *next = topGlobalNodes_;
482        NodeList<T> *current = nullptr;
483        while (next != nullptr) {
484            current = next;
485            next = current->GetNext();
486            ASSERT(current != next);
487            current->IterateUsageGlobal(callback);
488        }
489    }
490
491    template<class Callback>
492    void IterateWeakUsageGlobal(Callback callback)
493    {
494        NodeList<WeakNode> *next = topWeakGlobalNodes_;
495        NodeList<WeakNode> *current = nullptr;
496        while (next != nullptr) {
497            current = next;
498            next = current->GetNext();
499            ASSERT(current != next);
500            current->IterateUsageGlobal(callback);
501        }
502    }
503
504private:
505    NO_COPY_SEMANTIC(EcmaGlobalStorage);
506    NO_MOVE_SEMANTIC(EcmaGlobalStorage);
507
508    template<typename S>
509    inline void DisposeGlobalHandleInner(S *node, NodeList<S> **freeList, NodeList<S> **topNodes,
510                                    NodeList<S> **lastNodes)
511    {
512        NodeList<S> *list = NodeList<S>::NodeToNodeList(node);
513        list->FreeNode(thread_, node);
514
515        // If NodeList has no usage node, then delete NodeList
516        if (!list->HasUsagedNode() && (*topNodes != *lastNodes)) {
517            list->RemoveList();
518            if (*freeList == list) {
519                *freeList = list->GetFreeNext();
520            }
521            if (*topNodes == list) {
522                *topNodes = list->GetNext();
523            }
524            if (*lastNodes == list) {
525                *lastNodes = list->GetPrev();
526            }
527            allocator_->Delete(list);
528        } else {
529            // Add to freeList
530            if (list != *freeList && list->GetFreeNext() == nullptr && list->GetFreePrev() == nullptr) {
531                list->SetFreeNext(*freeList);
532                if (*freeList != nullptr) {
533                    (*freeList)->SetFreePrev(list);
534                }
535                *freeList = list;
536            }
537        }
538    }
539
540    template<typename S>
541    inline uintptr_t NewGlobalHandleImplement(NodeList<S> **storage, NodeList<S> **freeList, JSTaggedType value)
542    {
543#if ECMASCRIPT_ENABLE_NEW_HANDLE_CHECK
544        thread_->CheckJSTaggedType(value);
545#endif
546        if (!(*storage)->IsFull()) {
547            // alloc new block
548            S *node = (*storage)->NewNode(thread_, value);
549            ASSERT(node != nullptr);
550            return node->GetObjectAddress();
551        }
552        if (*freeList != nullptr) {
553            // use free_list node
554            S *node = (*freeList)->GetFreeNode(thread_, value);
555            ASSERT(node != nullptr);
556            if (!(*freeList)->HasFreeNode()) {
557                auto next = (*freeList)->GetFreeNext();
558                (*freeList)->SetFreeNext(nullptr);
559                (*freeList)->SetFreePrev(nullptr);
560                if (next != nullptr) {
561                    next->SetFreePrev(nullptr);
562                }
563                *freeList = next;
564            }
565            return node->GetObjectAddress();
566        }
567        auto block = allocator_->New<NodeList<S>>();
568        if (block == nullptr) {
569            LOG_ECMA(FATAL) << "NewGlobalHandleImplement:block is nullptr";
570        }
571        block->LinkTo(*storage);
572        *storage = block;
573
574        // use node in block finally
575        S *node = (*storage)->NewNode(thread_, value);
576        ASSERT(node != nullptr);
577        return node->GetObjectAddress();
578    }
579
580    [[maybe_unused]] JSThread *thread_ {nullptr};
581    NativeAreaAllocator *allocator_ {nullptr};
582    NodeList<T> *topGlobalNodes_ {nullptr};
583    NodeList<T> *lastGlobalNodes_ {nullptr};
584    NodeList<T> *freeListNodes_ {nullptr};
585
586    NodeList<WeakNode> *topWeakGlobalNodes_ {nullptr};
587    NodeList<WeakNode> *lastWeakGlobalNodes_ {nullptr};
588    NodeList<WeakNode> *weakFreeListNodes_ {nullptr};
589};
590}  // namespace panda::ecmascript
591#endif  // ECMASCRIPT_ECMA_GLOBAL_STORAGE_H
592