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
Refstor()151 mem::GlobalObjectStorage *Refstor() const
152 {
153 return refstor_;
154 }
155
GetClassLinker()156 ClassLinker *GetClassLinker() const
157 {
158 return classLinker_;
159 }
160
LinkerCtx()161 ClassLinkerContext *LinkerCtx() const
162 {
163 return linkerCtx_;
164 }
165
GetStringStor()166 JSValueStringStorage *GetStringStor()
167 {
168 return &jsValueStringStor_;
169 }
170
GetLocalScopesStorage()171 LocalScopesStorage *GetLocalScopesStorage()
172 {
173 return &localScopesStorage_;
174 }
175
DestroyLocalScopeForTopFrame(Frame * frame)176 void DestroyLocalScopeForTopFrame(Frame *frame)
177 {
178 GetLocalScopesStorage()->DestroyLocalScopeForTopFrame(jsEnv_, frame);
179 }
180
GetJSValueFinalizationRegistry()181 mem::Reference *GetJSValueFinalizationRegistry() const
182 {
183 return jsvalueFregistryRef_;
184 }
185
GetRegisterFinalizerMethod()186 Method *GetRegisterFinalizerMethod() const
187 {
188 return jsvalueFregistryRegister_;
189 }
190
191 // NOTE(vpukhov): implement in native code
PushOntoFinalizationRegistry(EtsCoroutine * coro,EtsObject * obj,EtsObject * cbarg)192 [[nodiscard]] bool PushOntoFinalizationRegistry(EtsCoroutine *coro, EtsObject *obj, EtsObject *cbarg)
193 {
194 auto queue = Refstor()->Get(jsvalueFregistryRef_);
195 std::array<Value, 4U> args = {Value(queue), Value(obj->GetCoreType()), Value(cbarg->GetCoreType()),
196 Value(static_cast<ObjectHeader *>(nullptr))};
197 jsvalueFregistryRegister_->Invoke(coro, args.data());
198 return !coro->HasPendingException();
199 }
200
201 // NOTE(vpukhov): replace with stack-like allocator
202 template <typename T, size_t OPT_SZ>
203 struct TempArgs {
204 public:
TempArgsTempArgs205 explicit TempArgs(size_t sz)
206 {
207 if (LIKELY(sz <= OPT_SZ)) {
208 sp_ = {new (inlArr_.data()) T[sz], sz};
209 } else {
210 sp_ = {new T[sz], sz};
211 }
212 }
213
~TempArgsTempArgs214 ~TempArgs()
215 {
216 if (UNLIKELY(static_cast<void *>(sp_.data()) != inlArr_.data())) {
217 delete[] sp_.data();
218 } else {
219 static_assert(std::is_trivially_destructible_v<T>);
220 }
221 }
222
223 NO_COPY_SEMANTIC(TempArgs);
224 #if defined(__clang__)
225 NO_MOVE_SEMANTIC(TempArgs);
226 #elif defined(__GNUC__) // RVO bug
227 NO_MOVE_OPERATOR(TempArgs);
TempArgsTempArgs228 TempArgs(TempArgs &&other) : sp_(other.sp_), inlArr_(other.inlArr_)
229 {
230 if (LIKELY(sp_.size() <= OPT_SZ)) {
231 sp_ = Span<T>(reinterpret_cast<T *>(inlArr_.data()), sp_.size());
232 }
233 }
234 #endif
235
236 Span<T> &operator*()
237 {
238 return sp_;
239 }
240 Span<T> *operator->()
241 {
242 return &sp_;
243 }
244 T &operator[](size_t idx)
245 {
246 return sp_[idx];
247 }
248
249 private:
250 Span<T> sp_;
251 alignas(T) std::array<uint8_t, sizeof(T) * OPT_SZ> inlArr_;
252 };
253
254 template <typename T, size_t OPT_SZ = 8U>
GetTempArgs(size_t sz)255 ALWAYS_INLINE static auto GetTempArgs(size_t sz)
256 {
257 return TempArgs<T, OPT_SZ>(sz);
258 }
259
CallStack()260 InteropCallStack &CallStack()
261 {
262 return interopStk_;
263 }
264
GetRefConvertCache()265 JSRefConvertCache *GetRefConvertCache()
266 {
267 return &refconvertCache_;
268 }
269
GetJSValueClass()270 Class *GetJSValueClass() const
271 {
272 return jsValueClass_;
273 }
274
GetJSErrorClass()275 Class *GetJSErrorClass() const
276 {
277 return jsErrorClass_;
278 }
279
GetObjectClass()280 Class *GetObjectClass() const
281 {
282 return objectClass_;
283 }
284
GetStringClass()285 Class *GetStringClass() const
286 {
287 return stringClass_;
288 }
289
GetUndefinedClass()290 Class *GetUndefinedClass() const
291 {
292 return undefinedClass_;
293 }
294
GetPromiseClass()295 Class *GetPromiseClass() const
296 {
297 return promiseClass_;
298 }
299
GetPromiseInteropConnectMethod()300 Method *GetPromiseInteropConnectMethod() const
301 {
302 return promiseInteropConnectMethod_;
303 }
304
GetErrorClass()305 Class *GetErrorClass() const
306 {
307 return errorClass_;
308 }
309
GetExceptionClass()310 Class *GetExceptionClass() const
311 {
312 return exceptionClass_;
313 }
314
GetTypeClass()315 Class *GetTypeClass() const
316 {
317 return typeClass_;
318 }
319
GetBoxIntClass()320 Class *GetBoxIntClass() const
321 {
322 return boxIntClass_;
323 }
324
GetBoxLongClass()325 Class *GetBoxLongClass() const
326 {
327 return boxLongClass_;
328 }
329
GetArrayClass()330 Class *GetArrayClass() const
331 {
332 return arrayClass_;
333 }
334
GetArrayBufferClass()335 Class *GetArrayBufferClass() const
336 {
337 return arraybufClass_;
338 }
339
IsFunctionalInterface(Class * klass)340 bool IsFunctionalInterface(Class *klass) const
341 {
342 return functionalInterfaces_.count(klass) > 0;
343 }
344
345 EtsObject *CreateETSCoreJSError(EtsCoroutine *coro, JSValue *jsvalue);
346
347 static void ThrowETSError(EtsCoroutine *coro, napi_value val);
348 static void ThrowETSError(EtsCoroutine *coro, const char *msg);
ThrowETSError(EtsCoroutine * coro,const std::string & msg)349 static void ThrowETSError(EtsCoroutine *coro, const std::string &msg)
350 {
351 ThrowETSError(coro, msg.c_str());
352 }
353
354 PANDA_PUBLIC_API static void ThrowJSError(napi_env env, const std::string &msg);
355 static void ThrowJSTypeError(napi_env env, const std::string &msg);
356 static void ThrowJSValue(napi_env env, napi_value val);
357
358 void ForwardEtsException(EtsCoroutine *coro);
359 void ForwardJSException(EtsCoroutine *coro);
360
SanityETSExceptionPending()361 [[nodiscard]] static bool SanityETSExceptionPending()
362 {
363 auto coro = EtsCoroutine::GetCurrent();
364 auto env = InteropCtx::Current(coro)->GetJSEnv();
365 return coro->HasPendingException() && !NapiIsExceptionPending(env);
366 }
367
SanityJSExceptionPending()368 [[nodiscard]] static bool SanityJSExceptionPending()
369 {
370 auto coro = EtsCoroutine::GetCurrent();
371 auto env = InteropCtx::Current(coro)->GetJSEnv();
372 return !coro->HasPendingException() && NapiIsExceptionPending(env);
373 }
374
375 // Die and print execution stacks
376 [[noreturn]] PANDA_PUBLIC_API static void Fatal(const char *msg);
Fatal(const std::string & msg)377 [[noreturn]] static void Fatal(const std::string &msg)
378 {
379 Fatal(msg.c_str());
380 }
381
SetPendingNewInstance(EtsObject * newInstance)382 void SetPendingNewInstance(EtsObject *newInstance)
383 {
384 pendingNewInstance_ = newInstance;
385 }
386
AcquirePendingNewInstance()387 EtsObject *AcquirePendingNewInstance()
388 {
389 auto res = pendingNewInstance_;
390 pendingNewInstance_ = nullptr;
391 return res;
392 }
393
GetEtsMethodWrappersCache()394 ets_proxy::EtsMethodWrappersCache *GetEtsMethodWrappersCache()
395 {
396 return &etsMethodWrappersCache_;
397 }
398
GetEtsClassWrappersCache()399 ets_proxy::EtsClassWrappersCache *GetEtsClassWrappersCache()
400 {
401 return &etsClassWrappersCache_;
402 }
403
GetSharedRefStorage()404 ets_proxy::SharedReferenceStorage *GetSharedRefStorage()
405 {
406 return etsProxyRefStorage_.get();
407 }
408
GetUndefinedObject()409 EtsObject *GetUndefinedObject()
410 {
411 return EtsObject::FromCoreType(GetPandaEtsVM()->GetUndefinedObject());
412 }
413
GetConstStringStorage()414 ConstStringStorage *GetConstStringStorage()
415 {
416 return &constStringStorage_;
417 }
418
AllocateSlotsInStringBuffer(uint32_t count)419 uint32_t AllocateSlotsInStringBuffer(uint32_t count)
420 {
421 // Atomic with relaxed order reason: ordering constraints are not required
422 return qnameBufferSize_.fetch_add(count, std::memory_order_relaxed);
423 }
424
GetStringBufferSize()425 uint32_t GetStringBufferSize()
426 {
427 // Atomic with relaxed order reason: ordering constraints are not required
428 return qnameBufferSize_.load(std::memory_order_relaxed);
429 }
430
FromConstStringStorage(ConstStringStorage * constStringStorage)431 static InteropCtx *FromConstStringStorage(ConstStringStorage *constStringStorage)
432 {
433 return reinterpret_cast<InteropCtx *>(reinterpret_cast<uintptr_t>(constStringStorage) -
434 MEMBER_OFFSET(InteropCtx, constStringStorage_));
435 }
436
437 private:
438 explicit InteropCtx(EtsCoroutine *coro, napi_env env);
439 void CacheClasses(EtsClassLinker *etsClassLinker);
440
441 napi_env jsEnv_ {};
442
443 mem::GlobalObjectStorage *refstor_ {};
444 ClassLinker *classLinker_ {};
445 ClassLinkerContext *linkerCtx_ {};
446 JSValueStringStorage jsValueStringStor_ {};
447 ConstStringStorage constStringStorage_ {};
448
449 LocalScopesStorage localScopesStorage_ {};
450 mem::Reference *jsvalueFregistryRef_ {};
451
452 InteropCallStack interopStk_ {};
453
454 JSRefConvertCache refconvertCache_;
455
456 Class *jsRuntimeClass_ {};
457 Class *jsValueClass_ {};
458 Class *jsErrorClass_ {};
459 Class *objectClass_ {};
460 Class *stringClass_ {};
461 Class *undefinedClass_ {};
462 Class *promiseClass_ {};
463 Class *errorClass_ {};
464 Class *exceptionClass_ {};
465 Class *typeClass_ {};
466 Class *arrayClass_ {};
467 Class *arraybufClass_ {};
468
469 Class *boxIntClass_ {};
470 Class *boxLongClass_ {};
471
472 std::set<Class *> functionalInterfaces_ {};
473
474 Method *jsvalueFregistryRegister_ {};
475 Method *promiseInteropConnectMethod_ = nullptr;
476
477 // ets_proxy data
478 EtsObject *pendingNewInstance_ {};
479 ets_proxy::EtsMethodWrappersCache etsMethodWrappersCache_ {};
480 ets_proxy::EtsClassWrappersCache etsClassWrappersCache_ {};
481 std::unique_ptr<ets_proxy::SharedReferenceStorage> etsProxyRefStorage_ {};
482
483 // should be one per VM when we will have multiple InteropContexts
484 std::atomic_uint32_t qnameBufferSize_ {};
485
486 friend class JSNapiEnvScope;
487 };
488
RefConvertCacheFromInteropCtx(InteropCtx * ctx)489 inline JSRefConvertCache *RefConvertCacheFromInteropCtx(InteropCtx *ctx)
490 {
491 return ctx->GetRefConvertCache();
492 }
JSEnvFromInteropCtx(InteropCtx * ctx)493 inline napi_env JSEnvFromInteropCtx(InteropCtx *ctx)
494 {
495 return ctx->GetJSEnv();
496 }
RefstorFromInteropCtx(InteropCtx * ctx)497 inline mem::GlobalObjectStorage *RefstorFromInteropCtx(InteropCtx *ctx)
498 {
499 return ctx->Refstor();
500 }
501
502 template <typename Callback>
EnumerateStrings(size_t startFrom,size_t count,Callback cb)503 bool ConstStringStorage::EnumerateStrings(size_t startFrom, size_t count, Callback cb)
504 {
505 auto jsArr = GetReferenceValue(Ctx()->GetJSEnv(), jsStringBufferRef_);
506 for (size_t index = startFrom; index < startFrom + count; index++) {
507 napi_value jsStr;
508 napi_get_element(Ctx()->GetJSEnv(), jsArr, index, &jsStr);
509 ASSERT(GetValueType(Ctx()->GetJSEnv(), jsStr) == napi_string);
510 if (!cb(jsStr)) {
511 return false;
512 }
513 }
514 return true;
515 }
516
517 } // namespace ark::ets::interop::js
518
519 #endif // !PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_INTEROP_CONTEXT_H_
520