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