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