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 #include "ark_interop_internal.h"
17 #include "ark_interop_napi.h"
18 
19 #include <atomic>
20 #include <unordered_map>
21 #include <vector>
22 
23 using namespace panda;
24 using namespace panda::ecmascript;
25 
26 namespace {
27 class __attribute__((capability("mutex"))) SpinLock final {
28 public:
Acquire()29     void Acquire() __attribute__((acquire_capability()))
30     {
31         bool locked = false;
32         while (!isLocked_.compare_exchange_strong(locked, true)) {
33             locked = false;
34         }
35     }
36 
Release()37     void Release() __attribute__((release_capability()))
38     {
39         isLocked_ = false;
40     }
41 private:
42     std::atomic<bool> isLocked_ {false};
43 };
44 
45 class GlobalManager {
46 public:
47     static void Dispose(EcmaVM* vm, uintptr_t handle);
48     static void AsyncDisposer(ARKTS_Env env, void* data);
49 
50     explicit GlobalManager(EcmaVM* vm);
51 
52 private:
53     static SpinLock managersMutex_;
54     static std::unordered_map<EcmaVM*, GlobalManager> managers_ __attribute__((guarded_by(managersMutex_)));
55 
56 private:
57     EcmaVM* vm_;
58     bool onSchedule_;
59     SpinLock handleMutex_ {};
60     std::vector<uintptr_t> handlesToDispose_ __attribute__((guarded_by(handleMutex_))) {};
61 };
62 
63 std::unordered_map<EcmaVM*, GlobalManager> GlobalManager::managers_;
64 SpinLock GlobalManager::managersMutex_;
65 
GlobalManager(EcmaVM * vm)66 GlobalManager::GlobalManager(EcmaVM* vm)
67 {
68     onSchedule_ = false;
69     vm_ = vm;
70 }
71 
AsyncDisposer(ARKTS_Env env,void * data)72 void GlobalManager::AsyncDisposer(ARKTS_Env env, void* data)
73 {
74     auto manager = (GlobalManager*)data;
75     std::vector<uintptr_t> toDispose;
76     manager->handleMutex_.Acquire();
77     manager->onSchedule_ = false;
78     std::swap(toDispose, manager->handlesToDispose_);
79     manager->handleMutex_.Release();
80 
81     for (auto handle : toDispose) {
82         auto global = P_CAST(handle, Global<JSValueRef>*);
83         delete global;
84     }
85 }
86 
Dispose(EcmaVM * vm,uintptr_t handle)87 void GlobalManager::Dispose(EcmaVM* vm, uintptr_t handle)
88 {
89     GlobalManager* manager;
90     managersMutex_.Acquire();
91     manager = &managers_.try_emplace(vm, vm).first->second;
92     managersMutex_.Release();
93     bool needSchedule = false;
94     manager->handleMutex_.Acquire();
95     manager->handlesToDispose_.push_back(handle);
96     if (!manager->onSchedule_) {
97         manager->onSchedule_ = needSchedule = true;
98     }
99     manager->handleMutex_.Release();
100     if (needSchedule) {
101         ARKTSInner_CreateAsyncTask(P_CAST(vm, ARKTS_Env), AsyncDisposer, manager);
102     }
103 }
104 }
105 
106 // assume value is object
ARKTS_CreateGlobal(ARKTS_Env env,ARKTS_Value value)107 ARKTS_Global ARKTS_CreateGlobal(ARKTS_Env env, ARKTS_Value value)
108 {
109     ARKTS_ASSERT_P(env, "env is null");
110     ARKTS_ASSERT_P(ARKTS_IsHeapObject(value), "value is not heap object");
111 
112     auto vm = P_CAST(env, EcmaVM*);
113     auto handle = BIT_CAST(value, Local<JSValueRef>);
114     auto result = new Global<JSValueRef>(vm, handle);
115 
116     return P_CAST(result, ARKTS_Global);
117 }
118 
119 /**
120  * no actual action about this convert, the data is in the same form with JSHandle,
121  * it does not mean global is equal to local, the memory they are allocated are controlled by different system,
122  * have no idea the consequence of NativeScope escape a global variable
123  */
ARKTS_GetGlobalValue(ARKTS_Global global)124 ARKTS_Value ARKTS_GetGlobalValue(ARKTS_Global global)
125 {
126     ARKTS_ASSERT_P(global, "global is null");
127 
128     auto result = *P_CAST(global, Local<JSValueRef>*);
129 
130     return ARKTS_FromHandle(result);
131 }
132 
133 /**
134  * cj destructor called in isolate thread, may crash js GC.
135  * store the handle to a table, do dispose at js thread on idle
136  */
ARKTS_DisposeGlobal(ARKTS_Env env,ARKTS_Global global)137 void ARKTS_DisposeGlobal(ARKTS_Env env, ARKTS_Global global)
138 {
139     ARKTS_ASSERT_V(env, "env is null");
140     ARKTS_ASSERT_V(global, "global is null");
141     GlobalManager::Dispose(P_CAST(env, EcmaVM*), P_CAST(global, uintptr_t));
142 }