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