• 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 "libpandabase/macros.h"
20 #include "plugins/ets/runtime/ets_coroutine.h"
21 #include "plugins/ets/runtime/ets_vm.h"
22 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.h"
23 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_method_wrapper.h"
24 #include "plugins/ets/runtime/interop_js/ets_proxy/shared_reference_storage.h"
25 #include "plugins/ets/runtime/interop_js/js_job_queue.h"
26 #include "plugins/ets/runtime/interop_js/js_refconvert.h"
27 #include "plugins/ets/runtime/interop_js/intrinsics_api_impl.h"
28 #include "plugins/ets/runtime/interop_js/intrinsics/std_js_jsruntime.h"
29 #include "runtime/include/value.h"
30 
31 #include <node_api.h>
32 #include <unordered_map>
33 
34 namespace panda {
35 
36 class Class;
37 class ClassLinkerContext;
38 
39 namespace mem {
40 class GlobalObjectStorage;
41 class Reference;
42 }  // namespace mem
43 
44 }  // namespace panda
45 
46 namespace panda::ets::interop::js {
47 
48 class JSValue;
49 
50 // Work-around for String JSValue and node_api
51 class JSValueStringStorage {
52 public:
53     class CachedEntry {
54     public:
Data()55         std::string const *Data()
56         {
57             return data_;
58         }
59 
60     private:
61         friend class JSValue;
62         friend class JSValueStringStorage;
63 
CachedEntry(std::string const * data)64         explicit CachedEntry(std::string const *data) : data_(data) {}
65 
66         std::string const *data_ {};
67     };
68 
69     explicit JSValueStringStorage() = default;
70 
Get(std::string && str)71     CachedEntry Get(std::string &&str)
72     {
73         auto [it, inserted] = stringTab_.insert({str, 0});
74         it->second++;
75         return CachedEntry(&it->first);
76     }
77 
Release(CachedEntry str)78     void Release(CachedEntry str)
79     {
80         auto it = stringTab_.find(*str.Data());
81         ASSERT(it != stringTab_.end());
82         if (--(it->second) == 0) {
83             stringTab_.erase(it);
84         }
85     }
86 
87 private:
88     std::unordered_map<std::string, uint64_t> stringTab_;
89 };
90 
91 using ArgValueBox = std::variant<uint64_t, ObjectHeader **>;
92 
93 class LocalScopesStorage {
94 public:
95     LocalScopesStorage() = default;
96 
~LocalScopesStorage()97     ~LocalScopesStorage()
98     {
99         ASSERT(localScopesStorage_.empty());
100     }
101 
102     NO_COPY_SEMANTIC(LocalScopesStorage);
103     NO_MOVE_SEMANTIC(LocalScopesStorage);
104 
CreateLocalScope(napi_env env,Frame * frame)105     void CreateLocalScope(napi_env env, Frame *frame)
106     {
107         napi_handle_scope scope;
108         [[maybe_unused]] auto status = napi_open_handle_scope(env, &scope);
109         ASSERT(status == napi_ok);
110         localScopesStorage_.emplace_back(frame, scope);
111     }
112 
DestroyTopLocalScope(napi_env env,Frame * currFrame)113     void DestroyTopLocalScope(napi_env env, [[maybe_unused]] Frame *currFrame)
114     {
115         ASSERT(!localScopesStorage_.empty());
116         auto &[frame, scope] = localScopesStorage_.back();
117         ASSERT(currFrame == frame);
118         localScopesStorage_.pop_back();
119         [[maybe_unused]] auto status = napi_close_handle_scope(env, scope);
120         ASSERT(status == napi_ok);
121     }
122 
DestroyLocalScopeForTopFrame(napi_env env,Frame * currFrame)123     void DestroyLocalScopeForTopFrame(napi_env env, Frame *currFrame)
124     {
125         if (localScopesStorage_.empty()) {
126             return;
127         }
128         auto &[frame, scope] = localScopesStorage_.back();
129         while (frame == currFrame) {
130             localScopesStorage_.pop_back();
131             [[maybe_unused]] auto status = napi_close_handle_scope(env, scope);
132             ASSERT(status == napi_ok);
133             if (localScopesStorage_.empty()) {
134                 return;
135             }
136             std::tie(frame, scope) = localScopesStorage_.back();
137         }
138     }
139 
140 private:
141     std::vector<std::pair<const Frame *, napi_handle_scope>> localScopesStorage_ {};
142 };
143 
144 class InteropCtx {
145 public:
Init(EtsCoroutine * coro,napi_env env)146     static void Init(EtsCoroutine *coro, napi_env env)
147     {
148         // Initialize InteropCtx in VM ExternalData
149         new (InteropCtx::Current(coro)) InteropCtx(coro, env);
150     }
151 
Current(PandaEtsVM * etsVm)152     static InteropCtx *Current(PandaEtsVM *etsVm)
153     {
154         static_assert(sizeof(PandaEtsVM::ExternalData) >= sizeof(InteropCtx));
155         static_assert(alignof(PandaEtsVM::ExternalData) >= alignof(InteropCtx));
156         return reinterpret_cast<InteropCtx *>(etsVm->GetExternalData());
157     }
158 
Current(EtsCoroutine * coro)159     static InteropCtx *Current(EtsCoroutine *coro)
160     {
161         return Current(coro->GetPandaVM());
162     }
163 
Current()164     static InteropCtx *Current()
165     {
166         return Current(EtsCoroutine::GetCurrent());
167     }
168 
GetPandaEtsVM()169     PandaEtsVM *GetPandaEtsVM()
170     {
171         return PandaEtsVM::FromExternalData(reinterpret_cast<void *>(this));
172     }
173 
GetJSEnv()174     napi_env GetJSEnv() const
175     {
176         ASSERT(jsEnv_ != nullptr);
177         return jsEnv_;
178     }
179 
SetJSEnv(napi_env env)180     void SetJSEnv(napi_env env)
181     {
182         jsEnv_ = env;
183     }
184 
Refstor()185     mem::GlobalObjectStorage *Refstor() const
186     {
187         return refstor_;
188     }
189 
LinkerCtx()190     ClassLinkerContext *LinkerCtx() const
191     {
192         return linkerCtx_;
193     }
194 
GetStringStor()195     JSValueStringStorage *GetStringStor()
196     {
197         return &jsValueStringStor_;
198     }
199 
GetLocalScopesStorage()200     LocalScopesStorage *GetLocalScopesStorage()
201     {
202         return &localScopesStorage_;
203     }
204 
DestroyLocalScopeForTopFrame(Frame * frame)205     void DestroyLocalScopeForTopFrame(Frame *frame)
206     {
207         GetLocalScopesStorage()->DestroyLocalScopeForTopFrame(jsEnv_, frame);
208     }
209 
GetJSValueFinalizationQueue()210     mem::Reference *GetJSValueFinalizationQueue() const
211     {
212         return jsvalueFqueueRef_;
213     }
214 
GetRegisterFinalizerMethod()215     Method *GetRegisterFinalizerMethod() const
216     {
217         return jsvalueFqueueRegister_;
218     }
219 
220     // NOTE(vpukhov): implement in native code
PushOntoFinalizationQueue(EtsCoroutine * coro,EtsObject * obj,EtsObject * cbarg)221     [[nodiscard]] bool PushOntoFinalizationQueue(EtsCoroutine *coro, EtsObject *obj, EtsObject *cbarg)
222     {
223         auto queue = Refstor()->Get(jsvalueFqueueRef_);
224         std::array<Value, 4U> args = {Value(queue), Value(obj->GetCoreType()), Value(cbarg->GetCoreType()),
225                                       Value(static_cast<ObjectHeader *>(nullptr))};
226         jsvalueFqueueRegister_->Invoke(coro, args.data());
227         return !coro->HasPendingException();
228     }
229 
230     template <typename T, size_t OPT_SZ>
231     struct TempArgs {
232     public:
TempArgsTempArgs233         explicit TempArgs(size_t sz)
234         {
235             if (LIKELY(sz <= OPT_SZ)) {
236                 sp_ = {new (inlArr_.data()) T[sz], sz};
237             } else {
238                 sp_ = {new T[sz], sz};
239             }
240         }
241 
~TempArgsTempArgs242         ~TempArgs()
243         {
244             if (UNLIKELY(static_cast<void *>(sp_.data()) != inlArr_.data())) {
245                 delete[] sp_.data();
246             } else {
247                 static_assert(std::is_trivially_destructible_v<T>);
248             }
249         }
250 
251         NO_COPY_SEMANTIC(TempArgs);
252 #if defined(__clang__)
253         NO_MOVE_SEMANTIC(TempArgs);
254 #elif defined(__GNUC__)  // RVO bug
255         NO_MOVE_OPERATOR(TempArgs);
TempArgsTempArgs256         TempArgs(TempArgs &&other) : sp_(other.sp_), inlArr_(other.inlArr_)
257         {
258             if (LIKELY(sp_.size() <= OPT_SZ)) {
259                 sp_ = Span<T>(reinterpret_cast<T *>(inlArr_.data()), sp_.size());
260             }
261         }
262 #endif
263 
264         Span<T> &operator*()
265         {
266             return sp_;
267         }
268         Span<T> *operator->()
269         {
270             return &sp_;
271         }
272         T &operator[](size_t idx)
273         {
274             return sp_[idx];
275         }
276 
277     private:
278         Span<T> sp_;
279         alignas(T) std::array<uint8_t, sizeof(T) * OPT_SZ> inlArr_;
280     };
281 
282     template <typename T, size_t OPT_SZ = 8U>
GetTempArgs(size_t sz)283     ALWAYS_INLINE static auto GetTempArgs(size_t sz)
284     {
285         return TempArgs<T, OPT_SZ>(sz);
286     }
287 
288     // NOTE(vpukhov): implement as flags in IFrame
289     struct InteropFrameRecord {
290         void *etsFrame {};
291         bool toJs {};
292     };
293 
GetInteropFrames()294     std::vector<InteropFrameRecord> &GetInteropFrames()
295     {
296         return interopFrames_;
297     }
298 
GetRefConvertCache()299     JSRefConvertCache *GetRefConvertCache()
300     {
301         return &refconvertCache_;
302     }
303 
GetJSValueClass()304     Class *GetJSValueClass() const
305     {
306         return jsValueClass_;
307     }
308 
GetJSErrorClass()309     Class *GetJSErrorClass() const
310     {
311         return jsErrorClass_;
312     }
313 
GetObjectClass()314     Class *GetObjectClass() const
315     {
316         return objectClass_;
317     }
318 
GetStringClass()319     Class *GetStringClass() const
320     {
321         return stringClass_;
322     }
323 
GetVoidClass()324     Class *GetVoidClass() const
325     {
326         return voidClass_;
327     }
328 
GetUndefinedClass()329     Class *GetUndefinedClass() const
330     {
331         return undefinedClass_;
332     }
333 
GetPromiseClass()334     Class *GetPromiseClass() const
335     {
336         return promiseClass_;
337     }
338 
GetErrorClass()339     Class *GetErrorClass() const
340     {
341         return errorClass_;
342     }
343 
GetExceptionClass()344     Class *GetExceptionClass() const
345     {
346         return exceptionClass_;
347     }
348 
GetTypeClass()349     Class *GetTypeClass() const
350     {
351         return typeClass_;
352     }
353 
GetBoxIntClass()354     Class *GetBoxIntClass() const
355     {
356         return boxIntClass_;
357     }
358 
GetBoxLongClass()359     Class *GetBoxLongClass() const
360     {
361         return boxLongClass_;
362     }
363 
GetArrayClass()364     Class *GetArrayClass() const
365     {
366         return arrayClass_;
367     }
368 
GetArrayBufferClass()369     Class *GetArrayBufferClass() const
370     {
371         return arraybufClass_;
372     }
373 
374     EtsObject *CreateETSCoreJSError(EtsCoroutine *coro, JSValue *jsvalue);
375 
376     static void ThrowETSError(EtsCoroutine *coro, napi_value val);
377     static void ThrowETSError(EtsCoroutine *coro, const char *msg);
ThrowETSError(EtsCoroutine * coro,const std::string & msg)378     static void ThrowETSError(EtsCoroutine *coro, const std::string &msg)
379     {
380         ThrowETSError(coro, msg.c_str());
381     }
382 
383     static void ThrowJSError(napi_env env, const std::string &msg);
384     static void ThrowJSTypeError(napi_env env, const std::string &msg);
385     static void ThrowJSValue(napi_env env, napi_value val);
386 
387     void ForwardEtsException(EtsCoroutine *coro);
388     void ForwardJSException(EtsCoroutine *coro);
389 
SanityETSExceptionPending()390     [[nodiscard]] static bool SanityETSExceptionPending()
391     {
392         auto coro = EtsCoroutine::GetCurrent();
393         auto env = InteropCtx::Current(coro)->GetJSEnv();
394         return coro->HasPendingException() && !NapiIsExceptionPending(env);
395     }
396 
SanityJSExceptionPending()397     [[nodiscard]] static bool SanityJSExceptionPending()
398     {
399         auto coro = EtsCoroutine::GetCurrent();
400         auto env = InteropCtx::Current(coro)->GetJSEnv();
401         return !coro->HasPendingException() && NapiIsExceptionPending(env);
402     }
403 
404     // Die and print execution stacks
405     [[noreturn]] static void Fatal(const char *msg);
Fatal(const std::string & msg)406     [[noreturn]] static void Fatal(const std::string &msg)
407     {
408         Fatal(msg.c_str());
409     }
410 
SetPendingNewInstance(EtsObject * newInstance)411     void SetPendingNewInstance(EtsObject *newInstance)
412     {
413         pendingNewInstance_ = newInstance;
414     }
415 
AcquirePendingNewInstance()416     EtsObject *AcquirePendingNewInstance()
417     {
418         auto res = pendingNewInstance_;
419         pendingNewInstance_ = nullptr;
420         return res;
421     }
422 
GetEtsMethodWrappersCache()423     ets_proxy::EtsMethodWrappersCache *GetEtsMethodWrappersCache()
424     {
425         return &etsMethodWrappersCache_;
426     }
427 
GetEtsClassWrappersCache()428     ets_proxy::EtsClassWrappersCache *GetEtsClassWrappersCache()
429     {
430         return &etsClassWrappersCache_;
431     }
432 
GetSharedRefStorage()433     ets_proxy::SharedReferenceStorage *GetSharedRefStorage()
434     {
435         return etsProxyRefStorage_.get();
436     }
437 
GetUndefinedObject()438     EtsObject *GetUndefinedObject()
439     {
440         return EtsObject::FromCoreType(GetPandaEtsVM()->GetUndefinedObject());
441     }
442 
443 private:
444     explicit InteropCtx(EtsCoroutine *coro, napi_env env);
445 
446     napi_env jsEnv_ {};
447 
448     mem::GlobalObjectStorage *refstor_ {};
449     ClassLinkerContext *linkerCtx_ {};
450     JSValueStringStorage jsValueStringStor_ {};
451     LocalScopesStorage localScopesStorage_ {};
452     mem::Reference *jsvalueFqueueRef_ {};
453 
454     std::vector<InteropFrameRecord> interopFrames_ {};
455 
456     JSRefConvertCache refconvertCache_;
457 
458     Class *jsRuntimeClass_ {};
459     Class *jsValueClass_ {};
460     Class *jsErrorClass_ {};
461     Class *objectClass_ {};
462     Class *stringClass_ {};
463     Class *voidClass_ {};
464     Class *undefinedClass_ {};
465     Class *promiseClass_ {};
466     Class *errorClass_ {};
467     Class *exceptionClass_ {};
468     Class *typeClass_ {};
469 
470     Class *boxIntClass_ {};
471     Class *boxLongClass_ {};
472 
473     Class *arrayClass_ {};
474     Class *arraybufClass_ {};
475 
476     Method *jsvalueFqueueRegister_ {};
477 
478     // ets_proxy data
479     EtsObject *pendingNewInstance_ {};
480     ets_proxy::EtsMethodWrappersCache etsMethodWrappersCache_ {};
481     ets_proxy::EtsClassWrappersCache etsClassWrappersCache_ {};
482     std::unique_ptr<ets_proxy::SharedReferenceStorage> etsProxyRefStorage_ {};
483 
484     friend class EtsJSNapiEnvScope;
485 };
486 
RefConvertCacheFromInteropCtx(InteropCtx * ctx)487 inline JSRefConvertCache *RefConvertCacheFromInteropCtx(InteropCtx *ctx)
488 {
489     return ctx->GetRefConvertCache();
490 }
JSEnvFromInteropCtx(InteropCtx * ctx)491 inline napi_env JSEnvFromInteropCtx(InteropCtx *ctx)
492 {
493     return ctx->GetJSEnv();
494 }
RefstorFromInteropCtx(InteropCtx * ctx)495 inline mem::GlobalObjectStorage *RefstorFromInteropCtx(InteropCtx *ctx)
496 {
497     return ctx->Refstor();
498 }
499 
500 }  // namespace panda::ets::interop::js
501 
502 #endif  // !PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_INTEROP_CONTEXT_H
503