• 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_PLUGINS_ETS_RUNTIME_INTEROP_JS_INTEROP_CONTEXT_H_
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_INTEROP_CONTEXT_H_
18 
19 #include "plugins/ets/runtime/ets_coroutine.h"
20 #include "plugins/ets/runtime/ets_vm.h"
21 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.h"
22 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_method_wrapper.h"
23 #include "plugins/ets/runtime/interop_js/ets_proxy/shared_reference_storage.h"
24 #include "plugins/ets/runtime/interop_js/js_job_queue.h"
25 #include "plugins/ets/runtime/interop_js/js_refconvert.h"
26 #include "plugins/ets/runtime/interop_js/interop_stacks.h"
27 #include "plugins/ets/runtime/interop_js/intrinsics/std_js_jsruntime.h"
28 #include "runtime/include/value.h"
29 
30 #include <node_api.h>
31 #include <unordered_map>
32 
33 namespace ark {
34 
35 class Class;
36 class ClassLinkerContext;
37 
38 namespace mem {
39 class GlobalObjectStorage;
40 class Reference;
41 }  // namespace mem
42 
43 }  // namespace ark
44 
45 namespace ark::ets::interop::js {
46 
47 class JSValue;
48 
49 // Work-around for String JSValue and node_api
50 class JSValueStringStorage {
51 public:
52     class CachedEntry {
53     public:
Data()54         std::string const *Data()
55         {
56             return data_;
57         }
58 
59     private:
60         friend class JSValue;
61         friend class JSValueStringStorage;
62 
CachedEntry(std::string const * data)63         explicit CachedEntry(std::string const *data) : data_(data) {}
64 
65         std::string const *data_ {};
66     };
67 
68     explicit JSValueStringStorage() = default;
69 
Get(std::string && str)70     CachedEntry Get(std::string &&str)
71     {
72         auto [it, inserted] = stringTab_.try_emplace(std::move(str), 0);
73         it->second++;
74         return CachedEntry(&it->first);
75     }
76 
Release(CachedEntry str)77     void Release(CachedEntry str)
78     {
79         auto it = stringTab_.find(*str.Data());
80         ASSERT(it != stringTab_.end());
81         if (--(it->second) == 0) {
82             stringTab_.erase(it);
83         }
84     }
85 
86 private:
87     std::unordered_map<std::string, uint64_t> stringTab_;
88 };
89 
90 class ConstStringStorage {
91 public:
92     void LoadDynamicCallClass(Class *klass);
93 
94     template <typename Callback>
95     bool EnumerateStrings(size_t startFrom, size_t count, Callback cb);
96 
97     napi_value GetConstantPool();
98 
99 private:
100     napi_value InitBuffer(size_t length);
101     InteropCtx *Ctx();
102 
103 private:
104     std::vector<napi_value> stringBuffer_ {};
105     napi_ref jsStringBufferRef_ {};
106     Class *lastLoadedClass_ {};
107 };
108 
109 class InteropCtx {
110 public:
111     PANDA_PUBLIC_API static void Init(EtsCoroutine *coro, napi_env env);
112 
Destroy(void * ptr)113     static void Destroy(void *ptr)
114     {
115         reinterpret_cast<InteropCtx *>(ptr)->~InteropCtx();
116     }
117 
Current(PandaEtsVM * etsVm)118     static InteropCtx *Current(PandaEtsVM *etsVm)
119     {
120         static_assert(sizeof(PandaEtsVM::ExternalData) >= sizeof(InteropCtx));
121         static_assert(alignof(PandaEtsVM::ExternalData) >= alignof(InteropCtx));
122         return reinterpret_cast<InteropCtx *>(etsVm->GetExternalData());
123     }
124 
Current(EtsCoroutine * coro)125     static InteropCtx *Current(EtsCoroutine *coro)
126     {
127         return Current(coro->GetPandaVM());
128     }
129 
Current()130     static InteropCtx *Current()
131     {
132         return Current(EtsCoroutine::GetCurrent());
133     }
134 
GetPandaEtsVM()135     PandaEtsVM *GetPandaEtsVM()
136     {
137         return PandaEtsVM::FromExternalData(reinterpret_cast<void *>(this));
138     }
139 
GetJSEnv()140     napi_env GetJSEnv() const
141     {
142         ASSERT(jsEnv_ != nullptr);
143         return jsEnv_;
144     }
145 
SetJSEnv(napi_env env)146     void SetJSEnv(napi_env env)
147     {
148         jsEnv_ = env;
149     }
150 
GetJsEnvForEventLoopCallbacks()151     napi_env GetJsEnvForEventLoopCallbacks() const
152     {
153         ASSERT(EtsCoroutine::GetCurrent() == EtsCoroutine::GetCurrent()->GetCoroutineManager()->GetMainThread());
154         return jsEnvForEventLoopCallbacks_;
155     }
156 
Refstor()157     mem::GlobalObjectStorage *Refstor() const
158     {
159         return refstor_;
160     }
161 
GetClassLinker()162     ClassLinker *GetClassLinker() const
163     {
164         return classLinker_;
165     }
166 
LinkerCtx()167     ClassLinkerContext *LinkerCtx() const
168     {
169         return linkerCtx_;
170     }
171 
GetStringStor()172     JSValueStringStorage *GetStringStor()
173     {
174         return &jsValueStringStor_;
175     }
176 
GetLocalScopesStorage()177     LocalScopesStorage *GetLocalScopesStorage()
178     {
179         return &localScopesStorage_;
180     }
181 
DestroyLocalScopeForTopFrame(Frame * frame)182     void DestroyLocalScopeForTopFrame(Frame *frame)
183     {
184         GetLocalScopesStorage()->DestroyLocalScopeForTopFrame(jsEnv_, frame);
185     }
186 
GetJSValueFinalizationRegistry()187     mem::Reference *GetJSValueFinalizationRegistry() const
188     {
189         return jsvalueFregistryRef_;
190     }
191 
GetRegisterFinalizerMethod()192     Method *GetRegisterFinalizerMethod() const
193     {
194         return jsvalueFregistryRegister_;
195     }
196 
197     // NOTE(vpukhov): implement in native code
PushOntoFinalizationRegistry(EtsCoroutine * coro,EtsObject * obj,EtsObject * cbarg)198     [[nodiscard]] bool PushOntoFinalizationRegistry(EtsCoroutine *coro, EtsObject *obj, EtsObject *cbarg)
199     {
200         auto queue = Refstor()->Get(jsvalueFregistryRef_);
201         std::array<Value, 4U> args = {Value(queue), Value(obj->GetCoreType()), Value(cbarg->GetCoreType()),
202                                       Value(static_cast<ObjectHeader *>(nullptr))};
203         jsvalueFregistryRegister_->Invoke(coro, args.data());
204         return !coro->HasPendingException();
205     }
206 
207     // NOTE(vpukhov): replace with stack-like allocator
208     template <typename T, size_t OPT_SZ>
209     struct TempArgs {
210     public:
TempArgsTempArgs211         explicit TempArgs(size_t sz)
212         {
213             if (LIKELY(sz <= OPT_SZ)) {
214                 sp_ = {new (inlArr_.data()) T[sz], sz};
215             } else {
216                 sp_ = {new T[sz], sz};
217             }
218         }
219 
~TempArgsTempArgs220         ~TempArgs()
221         {
222             if (UNLIKELY(static_cast<void *>(sp_.data()) != inlArr_.data())) {
223                 delete[] sp_.data();
224             } else {
225                 static_assert(std::is_trivially_destructible_v<T>);
226             }
227         }
228 
229         NO_COPY_SEMANTIC(TempArgs);
230 #if defined(__clang__)
231         NO_MOVE_SEMANTIC(TempArgs);
232 #elif defined(__GNUC__)  // RVO bug
233         NO_MOVE_OPERATOR(TempArgs);
TempArgsTempArgs234         TempArgs(TempArgs &&other) : sp_(other.sp_), inlArr_(other.inlArr_)
235         {
236             if (LIKELY(sp_.size() <= OPT_SZ)) {
237                 sp_ = Span<T>(reinterpret_cast<T *>(inlArr_.data()), sp_.size());
238             }
239         }
240 #endif
241 
242         Span<T> &operator*()
243         {
244             return sp_;
245         }
246         Span<T> *operator->()
247         {
248             return &sp_;
249         }
250         T &operator[](size_t idx)
251         {
252             return sp_[idx];
253         }
254 
255     private:
256         Span<T> sp_;
257         alignas(T) std::array<uint8_t, sizeof(T) * OPT_SZ> inlArr_;
258     };
259 
260     template <typename T, size_t OPT_SZ = 8U>
GetTempArgs(size_t sz)261     ALWAYS_INLINE static auto GetTempArgs(size_t sz)
262     {
263         return TempArgs<T, OPT_SZ>(sz);
264     }
265 
CallStack()266     InteropCallStack &CallStack()
267     {
268         return interopStk_;
269     }
270 
GetRefConvertCache()271     JSRefConvertCache *GetRefConvertCache()
272     {
273         return &refconvertCache_;
274     }
275 
GetJSValueClass()276     Class *GetJSValueClass() const
277     {
278         return jsValueClass_;
279     }
280 
GetJSErrorClass()281     Class *GetJSErrorClass() const
282     {
283         return jsErrorClass_;
284     }
285 
GetObjectClass()286     Class *GetObjectClass() const
287     {
288         return objectClass_;
289     }
290 
GetStringClass()291     Class *GetStringClass() const
292     {
293         return stringClass_;
294     }
295 
GetBigIntClass()296     Class *GetBigIntClass() const
297     {
298         return bigintClass_;
299     }
300 
GetArrayAsListIntClass()301     Class *GetArrayAsListIntClass() const
302     {
303         return arrayAsListIntClass_;
304     }
305 
GetUndefinedClass()306     Class *GetUndefinedClass() const
307     {
308         return undefinedClass_;
309     }
310 
GetPromiseClass()311     Class *GetPromiseClass() const
312     {
313         return promiseClass_;
314     }
315 
GetPromiseInteropConnectMethod()316     Method *GetPromiseInteropConnectMethod() const
317     {
318         return promiseInteropConnectMethod_;
319     }
320 
GetErrorClass()321     Class *GetErrorClass() const
322     {
323         return errorClass_;
324     }
325 
GetExceptionClass()326     Class *GetExceptionClass() const
327     {
328         return exceptionClass_;
329     }
330 
GetTypeClass()331     Class *GetTypeClass() const
332     {
333         return typeClass_;
334     }
335 
GetBoxIntClass()336     Class *GetBoxIntClass() const
337     {
338         return boxIntClass_;
339     }
340 
GetBoxLongClass()341     Class *GetBoxLongClass() const
342     {
343         return boxLongClass_;
344     }
345 
GetArrayClass()346     Class *GetArrayClass() const
347     {
348         return arrayClass_;
349     }
350 
GetArrayBufferClass()351     Class *GetArrayBufferClass() const
352     {
353         return arraybufClass_;
354     }
355 
IsFunctionalInterface(Class * klass)356     bool IsFunctionalInterface(Class *klass) const
357     {
358         return functionalInterfaces_.count(klass) > 0;
359     }
360 
361     EtsObject *CreateETSCoreJSError(EtsCoroutine *coro, JSValue *jsvalue);
362 
363     static void ThrowETSError(EtsCoroutine *coro, napi_value val);
364     static void ThrowETSError(EtsCoroutine *coro, const char *msg);
ThrowETSError(EtsCoroutine * coro,const std::string & msg)365     static void ThrowETSError(EtsCoroutine *coro, const std::string &msg)
366     {
367         ThrowETSError(coro, msg.c_str());
368     }
369 
370     PANDA_PUBLIC_API static void ThrowJSError(napi_env env, const std::string &msg);
371     static void ThrowJSTypeError(napi_env env, const std::string &msg);
372     static void ThrowJSValue(napi_env env, napi_value val);
373 
374     void ForwardEtsException(EtsCoroutine *coro);
375     void ForwardJSException(EtsCoroutine *coro);
376 
SanityETSExceptionPending()377     [[nodiscard]] static bool SanityETSExceptionPending()
378     {
379         auto coro = EtsCoroutine::GetCurrent();
380         auto env = InteropCtx::Current(coro)->GetJSEnv();
381         return coro->HasPendingException() && !NapiIsExceptionPending(env);
382     }
383 
SanityJSExceptionPending()384     [[nodiscard]] static bool SanityJSExceptionPending()
385     {
386         auto coro = EtsCoroutine::GetCurrent();
387         auto env = InteropCtx::Current(coro)->GetJSEnv();
388         return !coro->HasPendingException() && NapiIsExceptionPending(env);
389     }
390 
391     // Die and print execution stacks
392     [[noreturn]] PANDA_PUBLIC_API static void Fatal(const char *msg);
Fatal(const std::string & msg)393     [[noreturn]] static void Fatal(const std::string &msg)
394     {
395         Fatal(msg.c_str());
396     }
397 
SetPendingNewInstance(EtsObject * newInstance)398     void SetPendingNewInstance(EtsObject *newInstance)
399     {
400         pendingNewInstance_ = newInstance;
401     }
402 
AcquirePendingNewInstance()403     EtsObject *AcquirePendingNewInstance()
404     {
405         auto res = pendingNewInstance_;
406         pendingNewInstance_ = nullptr;
407         return res;
408     }
409 
GetEtsMethodWrappersCache()410     ets_proxy::EtsMethodWrappersCache *GetEtsMethodWrappersCache()
411     {
412         return &etsMethodWrappersCache_;
413     }
414 
GetEtsClassWrappersCache()415     ets_proxy::EtsClassWrappersCache *GetEtsClassWrappersCache()
416     {
417         return &etsClassWrappersCache_;
418     }
419 
GetSharedRefStorage()420     ets_proxy::SharedReferenceStorage *GetSharedRefStorage()
421     {
422         return etsProxyRefStorage_.get();
423     }
424 
GetUndefinedObject()425     EtsObject *GetUndefinedObject()
426     {
427         return EtsObject::FromCoreType(GetPandaEtsVM()->GetUndefinedObject());
428     }
429 
GetConstStringStorage()430     ConstStringStorage *GetConstStringStorage()
431     {
432         return &constStringStorage_;
433     }
434 
AllocateSlotsInStringBuffer(uint32_t count)435     uint32_t AllocateSlotsInStringBuffer(uint32_t count)
436     {
437         // Atomic with relaxed order reason: ordering constraints are not required
438         return qnameBufferSize_.fetch_add(count, std::memory_order_relaxed);
439     }
440 
GetStringBufferSize()441     uint32_t GetStringBufferSize()
442     {
443         // Atomic with relaxed order reason: ordering constraints are not required
444         return qnameBufferSize_.load(std::memory_order_relaxed);
445     }
446 
FromConstStringStorage(ConstStringStorage * constStringStorage)447     static InteropCtx *FromConstStringStorage(ConstStringStorage *constStringStorage)
448     {
449         return reinterpret_cast<InteropCtx *>(reinterpret_cast<uintptr_t>(constStringStorage) -
450                                               MEMBER_OFFSET(InteropCtx, constStringStorage_));
451     }
452 
453 private:
454     explicit InteropCtx(EtsCoroutine *coro, napi_env env);
455     void CacheClasses(EtsClassLinker *etsClassLinker);
456 
457     napi_env jsEnv_ {};
458 
459     napi_env jsEnvForEventLoopCallbacks_ {};
460 
461     mem::GlobalObjectStorage *refstor_ {};
462     ClassLinker *classLinker_ {};
463     ClassLinkerContext *linkerCtx_ {};
464     JSValueStringStorage jsValueStringStor_ {};
465     ConstStringStorage constStringStorage_ {};
466 
467     LocalScopesStorage localScopesStorage_ {};
468     mem::Reference *jsvalueFregistryRef_ {};
469 
470     InteropCallStack interopStk_ {};
471 
472     JSRefConvertCache refconvertCache_;
473 
474     Class *jsRuntimeClass_ {};
475     Class *jsValueClass_ {};
476     Class *jsErrorClass_ {};
477     Class *objectClass_ {};
478     Class *stringClass_ {};
479     Class *bigintClass_ {};
480     Class *arrayAsListIntClass_ {};
481     Class *undefinedClass_ {};
482     Class *promiseClass_ {};
483     Class *errorClass_ {};
484     Class *exceptionClass_ {};
485     Class *typeClass_ {};
486     Class *arrayClass_ {};
487     Class *arraybufClass_ {};
488 
489     Class *boxIntClass_ {};
490     Class *boxLongClass_ {};
491 
492     std::set<Class *> functionalInterfaces_ {};
493 
494     Method *jsvalueFregistryRegister_ {};
495     Method *promiseInteropConnectMethod_ = nullptr;
496 
497     // ets_proxy data
498     EtsObject *pendingNewInstance_ {};
499     ets_proxy::EtsMethodWrappersCache etsMethodWrappersCache_ {};
500     ets_proxy::EtsClassWrappersCache etsClassWrappersCache_ {};
501     std::unique_ptr<ets_proxy::SharedReferenceStorage> etsProxyRefStorage_ {};
502 
503     // should be one per VM when we will have multiple InteropContexts
504     std::atomic_uint32_t qnameBufferSize_ {};
505 
506     friend class JSNapiEnvScope;
507 };
508 
RefConvertCacheFromInteropCtx(InteropCtx * ctx)509 inline JSRefConvertCache *RefConvertCacheFromInteropCtx(InteropCtx *ctx)
510 {
511     return ctx->GetRefConvertCache();
512 }
JSEnvFromInteropCtx(InteropCtx * ctx)513 inline napi_env JSEnvFromInteropCtx(InteropCtx *ctx)
514 {
515     return ctx->GetJSEnv();
516 }
RefstorFromInteropCtx(InteropCtx * ctx)517 inline mem::GlobalObjectStorage *RefstorFromInteropCtx(InteropCtx *ctx)
518 {
519     return ctx->Refstor();
520 }
521 
522 template <typename Callback>
EnumerateStrings(size_t startFrom,size_t count,Callback cb)523 bool ConstStringStorage::EnumerateStrings(size_t startFrom, size_t count, Callback cb)
524 {
525     auto jsArr = GetReferenceValue(Ctx()->GetJSEnv(), jsStringBufferRef_);
526     for (size_t index = startFrom; index < startFrom + count; index++) {
527         napi_value jsStr;
528         napi_get_element(Ctx()->GetJSEnv(), jsArr, index, &jsStr);
529         ASSERT(GetValueType(Ctx()->GetJSEnv(), jsStr) == napi_string);
530         if (!cb(jsStr)) {
531             return false;
532         }
533     }
534     return true;
535 }
536 
537 }  // namespace ark::ets::interop::js
538 
539 #endif  // !PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_INTEROP_CONTEXT_H_
540