1/*
2 * Copyright (c) 2024 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_RUNTIME_H
17#define ECMASCRIPT_RUNTIME_H
18
19#include "ecmascript/ecma_string_table.h"
20#include "ecmascript/global_env_constants.h"
21#include "ecmascript/js_runtime_options.h"
22#include "ecmascript/js_thread.h"
23#include "ecmascript/mem/heap.h"
24#include "ecmascript/module/js_shared_module_manager.h"
25#include "ecmascript/mutator_lock.h"
26#include "ecmascript/platform/mutex.h"
27#include "ecmascript/serializer/serialize_chunk.h"
28
29#include "libpandabase/macros.h"
30
31#include <list>
32#include <memory>
33
34namespace panda::ecmascript {
35class Runtime {
36public:
37    PUBLIC_API static Runtime *GetInstance();
38
39    static void CreateIfFirstVm(const JSRuntimeOptions &options);
40    static void DestroyIfLastVm();
41    void InitializeIfFirstVm(EcmaVM *vm);
42
43    void RegisterThread(JSThread* newThread);
44    void UnregisterThread(JSThread* thread);
45
46    void SuspendAll(JSThread *current);
47    void ResumeAll(JSThread *current);
48    void IterateSerializeRoot(const RootVisitor &v);
49
50    JSThread *GetMainThread() const
51    {
52        return mainThread_;
53    }
54
55    MutatorLock *GetMutatorLock()
56    {
57        return &mutatorLock_;
58    }
59
60    const MutatorLock *GetMutatorLock() const
61    {
62        return &mutatorLock_;
63    }
64
65    template<class Callback>
66    void GCIterateThreadList(const Callback &cb)
67    {
68        LockHolder lock(threadsLock_);
69        GCIterateThreadListWithoutLock(cb);
70    }
71
72    template<class Callback>
73    void GCIterateThreadListWithoutLock(const Callback &cb)
74    {
75        for (auto thread : threads_) {
76            if (thread->ReadyForGCIterating()) {
77                cb(thread);
78            }
79        }
80    }
81
82    // Result may be inaccurate, just an approximate value.
83    size_t ApproximateThreadListSize()
84    {
85        return threads_.size();
86    }
87
88    inline const GlobalEnvConstants *GetGlobalEnvConstants()
89    {
90        return &globalConst_;
91    }
92
93    inline bool SharedConstInited()
94    {
95        return sharedConstInited_;
96    }
97
98    JSTaggedValue GetGlobalEnv() const
99    {
100        return globalEnv_;
101    }
102
103    inline EcmaStringTable *GetEcmaStringTable() const
104    {
105        return stringTable_.get();
106    }
107
108    inline SerializationChunk *GetSerializeRootMapValue([[maybe_unused]] JSThread *thread,
109        uint32_t dataIndex)
110    {
111        ASSERT(thread->IsInManagedState());
112        LockHolder lock(serializeLock_);
113        auto iter = serializeRootMap_.find(dataIndex);
114        if (iter == serializeRootMap_.end()) {
115            return nullptr;
116        }
117        return iter->second.get();
118    }
119
120    uint32_t PushSerializationRoot([[maybe_unused]] JSThread *thread, std::unique_ptr<SerializationChunk> chunk)
121    {
122        ASSERT(thread->IsInManagedState());
123        LockHolder lock(serializeLock_);
124        uint32_t index = GetSerializeDataIndex();
125        ASSERT(serializeRootMap_.find(index) == serializeRootMap_.end());
126        serializeRootMap_.emplace(index, std::move(chunk));
127        return index;
128    }
129
130    void RemoveSerializationRoot([[maybe_unused]] JSThread *thread, uint32_t index)
131    {
132        ASSERT(thread->IsInManagedState());
133        LockHolder lock(serializeLock_);
134        ASSERT(serializeRootMap_.find(index) != serializeRootMap_.end());
135        serializeRootMap_.erase(index);
136        serializeDataIndexVector_.emplace_back(index);
137    }
138
139    static bool SharedGCRequest()
140    {
141        LockHolder lock(*vmCreationLock_);
142        destroyCount_++;
143        if (destroyCount_ == WORKER_DESTRUCTION_COUNT || vmCount_ < MIN_GC_TRIGGER_VM_COUNT) {
144            destroyCount_ = 0;
145            return true;
146        } else {
147            return false;
148        }
149    }
150
151    bool HasCachedConstpool(const JSPandaFile *jsPandaFile);
152    PUBLIC_API JSTaggedValue FindConstpool(const JSPandaFile *jsPandaFile, int32_t index);
153    JSTaggedValue FindConstpoolUnlocked(const JSPandaFile *jsPandaFile, int32_t index);
154    JSHandle<ConstantPool> AddOrUpdateConstpool(const JSPandaFile *jsPandaFile,
155                                                JSHandle<ConstantPool> constpool,
156                                                int32_t index = 0);
157    std::optional<std::reference_wrapper<CMap<int32_t, JSTaggedValue>>> FindConstpools(
158        const JSPandaFile *jsPandaFile);
159    void EraseUnusedConstpool(const JSPandaFile *jsPandaFile, int32_t index, int32_t constpoolIndex);
160
161    void ProcessNativeDeleteInSharedGC(const WeakRootVisitor &visitor);
162
163    void ProcessSharedNativeDelete(const WeakRootVisitor &visitor);
164    void InvokeSharedNativePointerCallbacks();
165    void PushToSharedNativePointerList(JSNativePointer *pointer);
166
167    inline bool CreateStringCacheTable(uint32_t size)
168    {
169        constexpr int32_t MAX_SIZE = 150;
170        if ((size == 0) || (size > MAX_SIZE) || (externalRegisteredStringTable_ != nullptr)) {
171            LOG_ECMA(WARN) << "invalid size of the string cache table or the table has been registered.";
172            LOG_ECMA(WARN) << "Currently, maximum size of the table is " << MAX_SIZE;
173            return false;
174        }
175
176        externalRegisteredStringTable_ = new JSTaggedValue[size];
177        if (externalRegisteredStringTable_ == nullptr) {
178            LOG_ECMA(ERROR) << "create string cache table failed";
179            return false;
180        }
181        registeredStringTableSize_ = size;
182        return true;
183    }
184
185    inline bool SetCachedString(JSHandle<EcmaString> str, uint32_t propertyIndex)
186    {
187        if (propertyIndex >= registeredStringTableSize_ || (externalRegisteredStringTable_ == nullptr)) {
188            LOG_ECMA(ERROR) << "invalid size of the string cache table or the table has never been registered.";
189            return false;
190        }
191        externalRegisteredStringTable_[propertyIndex] = str.GetTaggedValue();
192        return true;
193    }
194
195    inline JSHandle<EcmaString> GetCachedString(JSThread *thread, uint32_t propertyIndex)
196    {
197        if ((externalRegisteredStringTable_ == nullptr) || (propertyIndex >= registeredStringTableSize_)) {
198            LOG_ECMA(ERROR) << "invalid size of the string cache table or the table has never been registered.";
199            return JSHandle<EcmaString>(thread->GlobalConstants()->GetHandledEmptyString());
200        }
201        return JSHandle<EcmaString>(reinterpret_cast<uintptr_t>(&externalRegisteredStringTable_[propertyIndex]));
202    }
203
204    inline bool HasCachedString(uint32_t propertyIndex)
205    {
206        if ((externalRegisteredStringTable_ == nullptr) || propertyIndex >= registeredStringTableSize_) {
207            LOG_ECMA(ERROR) << "invalid size of the string cache table or the table has never been registered.";
208            return false;
209        }
210
211        if (externalRegisteredStringTable_[propertyIndex].GetRawData() != JSTaggedValue::NULL_POINTER) {
212            return true;
213        }
214        return false;
215    }
216
217    void IterateCachedStringRoot(const RootRangeVisitor &v)
218    {
219        if ((externalRegisteredStringTable_ == nullptr) || (registeredStringTableSize_ <= 0)) {
220            return;
221        }
222        auto begin = ObjectSlot(reinterpret_cast<uintptr_t>(externalRegisteredStringTable_));
223        auto end = ObjectSlot(reinterpret_cast<uintptr_t>(externalRegisteredStringTable_ +
224            registeredStringTableSize_));
225        v(Root::ROOT_VM, begin, end);
226    }
227
228private:
229    static constexpr int32_t WORKER_DESTRUCTION_COUNT = 3;
230    static constexpr int32_t MIN_GC_TRIGGER_VM_COUNT = 4;
231    static constexpr uint32_t MAX_SUSPEND_RETRIES = 5000;
232    Runtime() = default;
233    ~Runtime();
234    void SuspendAllThreadsImpl(JSThread *current);
235    void ResumeAllThreadsImpl(JSThread *current);
236
237    void PreInitialization(const EcmaVM *vm);
238    void PostInitialization(const EcmaVM *vm);
239
240    uint32_t GetSerializeDataIndex()
241    {
242        if (!serializeDataIndexVector_.empty()) {
243            uint32_t index = serializeDataIndexVector_.back();
244            serializeDataIndexVector_.pop_back();
245            return index;
246        }
247        return ++serializeDataIndex_;
248    }
249
250    int32_t GetAndIncreaseSharedConstpoolCount()
251    {
252        if (freeSharedConstpoolIndex_.size() > 0) {
253            auto iter = freeSharedConstpoolIndex_.begin();
254            int32_t freeCount = *iter;
255            freeSharedConstpoolIndex_.erase(iter);
256            return freeCount;
257        }
258        return sharedConstpoolCount_++;
259    }
260
261    std::vector<std::pair<NativePointerCallback, std::pair<void *, void *>>> &GetSharedNativePointerCallbacks()
262    {
263        return sharedNativePointerCallbacks_;
264    }
265
266    Mutex threadsLock_;
267    ConditionVariable threadSuspendCondVar_;
268    Mutex serializeLock_;
269    std::list<JSThread*> threads_;
270    uint32_t suspendNewCount_ {0};
271    uint32_t serializeDataIndex_ {0};
272    MutatorLock mutatorLock_;
273
274    bool sharedConstInited_ {false};
275    GlobalEnvConstants globalConst_;
276    JSTaggedValue globalEnv_ {JSTaggedValue::Hole()};
277    JSThread *mainThread_ {nullptr};
278    // for shared heap.
279    std::unique_ptr<NativeAreaAllocator> nativeAreaAllocator_;
280    std::unique_ptr<HeapRegionAllocator> heapRegionAllocator_;
281    // for stringTable.
282    std::unique_ptr<EcmaStringTable> stringTable_;
283    std::unordered_map<uint32_t, std::unique_ptr<SerializationChunk>> serializeRootMap_;
284    std::vector<uint32_t> serializeDataIndexVector_;
285
286    // Shared constantpool cache
287    Mutex constpoolLock_;
288    CMap<const JSPandaFile *, CMap<int32_t, JSTaggedValue>> globalSharedConstpools_ {};
289    int32_t sharedConstpoolCount_ = 0; // shared constpool count.
290    std::set<int32_t> freeSharedConstpoolIndex_ {}; // reuse shared constpool index.
291
292    // Runtime instance and VMs creation.
293    static int32_t vmCount_;
294    static int32_t destroyCount_;
295    static bool firstVmCreated_;
296    static Mutex *vmCreationLock_;
297    static Runtime *instance_;
298
299    // for string cache
300    JSTaggedValue *externalRegisteredStringTable_ {nullptr};
301    uint32_t registeredStringTableSize_ = 0;
302
303    // for shared native pointer
304    std::vector<std::pair<NativePointerCallback, std::pair<void *, void *>>> sharedNativePointerCallbacks_ {};
305
306    friend class EcmaVM;
307    friend class JSThread;
308    friend class SharedHeap;
309
310    NO_COPY_SEMANTIC(Runtime);
311    NO_MOVE_SEMANTIC(Runtime);
312};
313}  // namespace panda::ecmascript
314#endif // ECMASCRIPT_RUNTIME_H
315