• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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 PANDA_RUNTIME_ETS_FFI_CLASSES_ETS_PROMISE_H_
17 #define PANDA_RUNTIME_ETS_FFI_CLASSES_ETS_PROMISE_H_
18 
19 #include "libpandabase/macros.h"
20 #include "plugins/ets/runtime/ets_panda_file_items.h"
21 #include "plugins/ets/runtime/types/ets_object.h"
22 #include "plugins/ets/runtime/types/ets_array.h"
23 #include "plugins/ets/runtime/types/ets_primitives.h"
24 #include "runtime/coroutines/coroutine_events.h"
25 
26 namespace ark::ets {
27 
28 class EtsCoroutine;
29 
30 namespace test {
31 class EtsPromiseTest;
32 }  // namespace test
33 
34 class EtsPromise : public ObjectHeader {
35 public:
36     // temp
37     static constexpr EtsInt STATE_PENDING = 0;
38     static constexpr EtsInt STATE_RESOLVED = 1;
39     static constexpr EtsInt STATE_REJECTED = 2;
40 
41     EtsPromise() = delete;
42     ~EtsPromise() = delete;
43 
44     NO_COPY_SEMANTIC(EtsPromise);
45     NO_MOVE_SEMANTIC(EtsPromise);
46 
47     PANDA_PUBLIC_API static EtsPromise *Create(EtsCoroutine *etsCoroutine = EtsCoroutine::GetCurrent());
48 
FromCoreType(ObjectHeader * promise)49     static EtsPromise *FromCoreType(ObjectHeader *promise)
50     {
51         return reinterpret_cast<EtsPromise *>(promise);
52     }
53 
GetCoreType()54     ObjectHeader *GetCoreType() const
55     {
56         return static_cast<ObjectHeader *>(const_cast<EtsPromise *>(this));
57     }
58 
AsObject()59     EtsObject *AsObject()
60     {
61         return EtsObject::FromCoreType(this);
62     }
63 
AsObject()64     const EtsObject *AsObject() const
65     {
66         return EtsObject::FromCoreType(this);
67     }
68 
FromEtsObject(EtsObject * promise)69     static EtsPromise *FromEtsObject(EtsObject *promise)
70     {
71         return reinterpret_cast<EtsPromise *>(promise);
72     }
73 
GetCallbackQueue(EtsCoroutine * coro)74     EtsObjectArray *GetCallbackQueue(EtsCoroutine *coro)
75     {
76         return EtsObjectArray::FromCoreType(
77             ObjectAccessor::GetObject(coro, this, MEMBER_OFFSET(EtsPromise, callbackQueue_)));
78     }
79 
GetCoroPtrQueue(EtsCoroutine * coro)80     EtsLongArray *GetCoroPtrQueue(EtsCoroutine *coro)
81     {
82         return reinterpret_cast<EtsLongArray *>(
83             ObjectAccessor::GetObject(coro, this, MEMBER_OFFSET(EtsPromise, coroPtrQueue_)));
84     }
85 
GetQueueSize()86     EtsInt GetQueueSize()
87     {
88         return queueSize_;
89     }
90 
GetEventPtr()91     CoroutineEvent *GetEventPtr()
92     {
93         return reinterpret_cast<CoroutineEvent *>(event_);
94     }
95 
SetEventPtr(CoroutineEvent * e)96     void SetEventPtr(CoroutineEvent *e)
97     {
98         event_ = reinterpret_cast<EtsLong>(e);
99     }
100 
IsResolved()101     bool IsResolved() const
102     {
103         return (state_ == STATE_RESOLVED);
104     }
105 
IsRejected()106     bool IsRejected() const
107     {
108         return (state_ == STATE_REJECTED);
109     }
110 
IsPending()111     bool IsPending() const
112     {
113         return (state_ == STATE_PENDING);
114     }
115 
IsProxy()116     bool IsProxy()
117     {
118         return GetLinkedPromise(EtsCoroutine::GetCurrent()) != nullptr;
119     }
120 
GetInteropObject(EtsCoroutine * coro)121     EtsObject *GetInteropObject(EtsCoroutine *coro)
122     {
123         auto *obj = ObjectAccessor::GetObject(coro, this, MEMBER_OFFSET(EtsPromise, interopObject_));
124         return EtsObject::FromCoreType(obj);
125     }
126 
SetInteropObject(EtsCoroutine * coro,EtsObject * o)127     void SetInteropObject(EtsCoroutine *coro, EtsObject *o)
128     {
129         ObjectAccessor::SetObject(coro, this, MEMBER_OFFSET(EtsPromise, interopObject_), o->GetCoreType());
130     }
131 
GetLinkedPromise(EtsCoroutine * coro)132     EtsObject *GetLinkedPromise(EtsCoroutine *coro)
133     {
134         auto *obj = ObjectAccessor::GetObject(coro, this, MEMBER_OFFSET(EtsPromise, linkedPromise_));
135         return EtsObject::FromCoreType(obj);
136     }
137 
SetLinkedPromise(EtsCoroutine * coro,EtsObject * p)138     void SetLinkedPromise(EtsCoroutine *coro, EtsObject *p)
139     {
140         ObjectAccessor::SetObject(coro, this, MEMBER_OFFSET(EtsPromise, linkedPromise_), p->GetCoreType());
141     }
142 
Resolve(EtsCoroutine * coro,EtsObject * value)143     void Resolve(EtsCoroutine *coro, EtsObject *value)
144     {
145         os::memory::LockHolder lk(*this);
146         ASSERT(state_ == STATE_PENDING);
147         auto coreValue = (value == nullptr) ? nullptr : value->GetCoreType();
148         ObjectAccessor::SetObject(coro, this, MEMBER_OFFSET(EtsPromise, value_), coreValue);
149         state_ = STATE_RESOLVED;
150         OnPromiseCompletion(coro);
151     }
152 
Reject(EtsCoroutine * coro,EtsObject * error)153     void Reject(EtsCoroutine *coro, EtsObject *error)
154     {
155         os::memory::LockHolder lk(*this);
156         ASSERT(state_ == STATE_PENDING);
157         ObjectAccessor::SetObject(coro, this, MEMBER_OFFSET(EtsPromise, value_), error->GetCoreType());
158         state_ = STATE_REJECTED;
159         OnPromiseCompletion(coro);
160     }
161 
SubmitCallback(EtsCoroutine * coro,const EtsHandle<EtsObject> & hcallback)162     void SubmitCallback(EtsCoroutine *coro, const EtsHandle<EtsObject> &hcallback)
163     {
164         ASSERT(IsLocked());
165         // We need to promote weak reference to promise to prevent GC from collecting promise
166         PromotePromiseRef(coro);
167         EnsureCapacity(coro);
168         auto *cbQueue = GetCallbackQueue(coro);
169         auto *coroQueue = GetCoroPtrQueue(coro);
170         coroQueue->Set(queueSize_, ToUintPtr(coro));
171         cbQueue->Set(queueSize_, hcallback.GetPtr());
172         queueSize_++;
173     }
174 
GetValue(EtsCoroutine * coro)175     EtsObject *GetValue(EtsCoroutine *coro)
176     {
177         return EtsObject::FromCoreType(ObjectAccessor::GetObject(coro, this, MEMBER_OFFSET(EtsPromise, value_)));
178     }
179 
GetState()180     uint32_t GetState() const
181     {
182         return state_;
183     }
184 
ValueOffset()185     static size_t ValueOffset()
186     {
187         return MEMBER_OFFSET(EtsPromise, value_);
188     }
189 
Lock()190     void Lock()
191     {
192         auto tid = EtsCoroutine::GetCurrent()->GetCoroutineId();
193         ASSERT((tid & MarkWord::LIGHT_LOCK_THREADID_MASK) != 0);
194         auto mwExpected = AtomicGetMark();
195         switch (mwExpected.GetState()) {
196             case MarkWord::STATE_GC:
197                 LOG(FATAL, COROUTINES) << "Cannot lock a GC-collected Promise object!";
198                 break;
199             case MarkWord::STATE_LIGHT_LOCKED:
200             case MarkWord::STATE_HEAVY_LOCKED:
201                 // should wait until unlock...
202                 // NOTE(konstanting, #I67QXC): check tid and decide what to do in case when locked by current tid
203                 mwExpected = mwExpected.DecodeFromUnlocked();
204                 break;
205             case MarkWord::STATE_HASHED:
206                 // NOTE(konstanting, #I67QXC): should save the hash
207                 LOG(FATAL, COROUTINES)
208                     << "Cannot lock a hashed Promise object! This functionality is not implemented yet.";
209                 break;
210             case MarkWord::STATE_UNLOCKED:
211             default:
212                 break;
213         }
214         auto newMw = mwExpected.DecodeFromLightLock(tid, 0);
215 #ifndef NDEBUG
216         size_t cycles = 0;
217 #endif /* NDEBUG */
218         while (!AtomicSetMark<false>(mwExpected, newMw)) {
219             mwExpected = mwExpected.DecodeFromUnlocked();
220             newMw = mwExpected.DecodeFromLightLock(tid, 0);
221 #ifndef NDEBUG
222             ++cycles;
223 #endif /* NDEBUG */
224         }
225 #ifndef NDEBUG
226         LOG(DEBUG, COROUTINES) << "Promise::Lock(): took " << cycles << " cycles";
227 #endif /* NDEBUG */
228     }
229 
Unlock()230     void Unlock()
231     {
232         // precondition: is locked AND if the event pointer is set then the event is locked
233         // NOTE(konstanting, #I67QXC): find a way to add an ASSERT for (the event is locked)
234         // NOTE(konstanting, #I67QXC): should load the hash once we support the hashed state in Lock()
235         auto mw = AtomicGetMark();
236         ASSERT(mw.GetState() == MarkWord::STATE_LIGHT_LOCKED);
237         auto newMw = mw.DecodeFromUnlocked();
238         while (!AtomicSetMark<false>(mw, newMw)) {
239             ASSERT(mw.GetState() == MarkWord::STATE_LIGHT_LOCKED);
240             newMw = mw.DecodeFromUnlocked();
241         }
242     }
243 
IsLocked()244     bool IsLocked()
245     {
246         auto mw = AtomicGetMark();
247         return mw.GetState() == MarkWord::STATE_LIGHT_LOCKED;
248     }
249 
250     /**
251      * Precondition: promise is locked.
252      * In case of COMPLETION event just return it.
253      * In case of GENERIC event create it or increment the reference counter and return.
254      */
255     CoroutineEvent *GetOrCreateEventPtr();
256 
257     /**
258      * Precondition: event is GENERIC and linked to a promise (obtained using the GetOrCreateEventPtr method).
259      * Decrement the reference counter and in case it reaches zero delete the event.
260      */
261     void RetireEventPtr(CoroutineEvent *event);
262 
263 private:
264     void EnsureCapacity(EtsCoroutine *coro);
265     void OnPromiseCompletion(EtsCoroutine *coro);
266     void PromotePromiseRef(EtsCoroutine *coro);
267 
ClearQueues(EtsCoroutine * coro)268     void ClearQueues(EtsCoroutine *coro)
269     {
270         ObjectAccessor::SetObject(coro, this, MEMBER_OFFSET(EtsPromise, callbackQueue_), nullptr);
271         ObjectAccessor::SetObject(coro, this, MEMBER_OFFSET(EtsPromise, coroPtrQueue_), nullptr);
272         queueSize_ = 0;
273     }
274 
275     ObjectPointer<EtsObject> value_;  // the completion value of the Promise
276     ObjectPointer<EtsObjectArray>
277         callbackQueue_;  // the queue of 'then and catch' calbacks which will be called when the Promise gets fulfilled
278     ObjectPointer<EtsLongArray> coroPtrQueue_;  // the queue of Coroutine pointers associated with callbacks
279     ObjectPointer<EtsObject> interopObject_;    // internal object used in js interop
280     ObjectPointer<EtsObject> linkedPromise_;    // linked JS promise as JSValue (if exists)
281     EtsInt queueSize_;
282     EtsLong event_;
283     EtsInt eventRefCounter_;
284     uint32_t state_;  // the Promise's state
285 
286     friend class test::EtsPromiseTest;
287 };
288 
289 }  // namespace ark::ets
290 
291 #endif  // PANDA_RUNTIME_ETS_FFI_CLASSES_ETS_PROMISE_H_
292