• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2025 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_JS_REFCONVERT_H_
17 #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_REFCONVERT_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/interop_common.h"
22 #include "plugins/ets/runtime/ets_platform_types.h"
23 #include "libpandabase/macros.h"
24 #include <node_api.h>
25 #include <unordered_map>
26 
27 namespace ark::ets::interop::js {
28 
29 class InteropCtx;
30 class JSRefConvertCache;
31 
32 // Forward declarations to avoid cyclic deps.
33 inline JSRefConvertCache *RefConvertCacheFromInteropCtx(InteropCtx *ctx);
34 inline napi_env JSEnvFromInteropCtx(InteropCtx *ctx);
35 
36 // Conversion interface for some ark::Class objects
37 class JSRefConvert {
38 public:
39     // Convert ets->js, returns nullptr if failed, throws JS exceptions
Wrap(InteropCtx * ctx,EtsObject * obj)40     napi_value Wrap(InteropCtx *ctx, EtsObject *obj)
41     {
42         ASSERT(obj != nullptr);
43         return (this->*(this->wrap_))(ctx, obj);
44     }
45 
46     // Convert js->ets, returns nullopt if failed, throws ETS/JS exceptions
Unwrap(InteropCtx * ctx,napi_value jsValue)47     EtsObject *Unwrap(InteropCtx *ctx, napi_value jsValue)
48     {
49         ASSERT(!IsUndefined(JSEnvFromInteropCtx(ctx), jsValue));
50         return (this->*(this->unwrap_))(ctx, jsValue);
51     }
52 
53     JSRefConvert() = delete;
54     NO_COPY_SEMANTIC(JSRefConvert);
55     NO_MOVE_SEMANTIC(JSRefConvert);
56     virtual ~JSRefConvert() = default;
57 
58     template <typename D, typename = std::enable_if_t<std::is_base_of_v<JSRefConvert, D>>>
Cast(JSRefConvert * base)59     static D *Cast(JSRefConvert *base)
60     {
61         ASSERT(base->wrap_ == &D::WrapImpl && base->unwrap_ == &D::UnwrapImpl);
62         return static_cast<D *>(base);
63     }
64 
65 protected:
66     template <typename D>
JSRefConvert(D *)67     explicit JSRefConvert(D * /*unused*/)
68         : wrap_(static_cast<WrapT>(&D::WrapImpl)), unwrap_(static_cast<UnwrapT>(&D::UnwrapImpl))
69     {
70     }
71 
72 private:
73     using WrapT = decltype(&JSRefConvert::Wrap);
74     using UnwrapT = decltype(&JSRefConvert::Unwrap);
75 
76     const WrapT wrap_;
77     const UnwrapT unwrap_;
78 };
79 
80 // Fast cache to find convertor for some ark::Class
81 class JSRefConvertCache {
82 public:
Lookup(Class * klass)83     JSRefConvert *Lookup(Class *klass)
84     {
85         auto *entry = GetDirCacheEntry(klass);
86         if (LIKELY(entry->klass == klass)) {
87             return entry->value;
88         }
89         auto value = LookupFull(klass);
90         *entry = {klass, value};  // Update dircache_
91         return value;
92     }
93 
Insert(Class * klass,std::unique_ptr<JSRefConvert> value)94     JSRefConvert *Insert(Class *klass, std::unique_ptr<JSRefConvert> value)
95     {
96         ASSERT(value != nullptr);
97         ASSERT(klass->IsInitialized());
98         auto ownedValue = value.get();
99         auto [it, inserted] = cache_.insert_or_assign(klass, std::move(value));
100         ASSERT(inserted);
101         (void)inserted;
102         *GetDirCacheEntry(klass) = {klass, ownedValue};
103         return ownedValue;
104     }
105 
JSRefConvertCache()106     JSRefConvertCache() : dircache_(new DirCachePair[DIRCACHE_SZ]) {}
107     ~JSRefConvertCache() = default;
108     NO_COPY_SEMANTIC(JSRefConvertCache);
109     NO_MOVE_SEMANTIC(JSRefConvertCache);
110 
111 private:
LookupFull(Class * klass)112     __attribute__((noinline)) JSRefConvert *LookupFull(Class *klass)
113     {
114         auto it = cache_.find(klass);
115         if (UNLIKELY(it == cache_.end())) {
116             return nullptr;
117         }
118         return it->second.get();
119     }
120 
121     struct DirCachePair {
122         Class *klass {};
123         JSRefConvert *value {};
124     };
125 
126     static constexpr uint32_t DIRCACHE_SZ = 1024;
127 
GetDirCacheEntry(Class * klass)128     DirCachePair *GetDirCacheEntry(Class *klass)
129     {
130         static_assert(helpers::math::IsPowerOfTwo(DIRCACHE_SZ));
131         auto hash = helpers::math::PowerOfTwoTableSlot<uint32_t>(ToUintPtr(klass), DIRCACHE_SZ,
132                                                                  GetLogAlignment(alignof(Class)));
133         return &dircache_[hash];
134     }
135 
136     std::unique_ptr<DirCachePair[]> dircache_;  // NOLINT(modernize-avoid-c-arrays)
137     std::unordered_map<Class *, std::unique_ptr<JSRefConvert>> cache_;
138 };
139 
140 void RegisterBuiltinJSRefConvertors(InteropCtx *ctx);
141 
142 // Try to create JSRefConvert for nonexisting cache entry
143 template <bool ALLOW_INIT = false>
144 extern JSRefConvert *JSRefConvertCreate(InteropCtx *ctx, Class *klass);
145 
146 // Find or create JSRefConvert for some Class
147 // NOTE(vpukhov): <ALLOW_INIT = false> should never throw?
148 template <bool ALLOW_INIT = false>
JSRefConvertResolve(InteropCtx * ctx,Class * klass)149 inline JSRefConvert *JSRefConvertResolve(InteropCtx *ctx, Class *klass)
150 {
151     JSRefConvertCache *cache = RefConvertCacheFromInteropCtx(ctx);
152     auto conv = cache->Lookup(klass);
153     if (LIKELY(conv != nullptr)) {
154         return conv;
155     }
156     return JSRefConvertCreate<ALLOW_INIT>(ctx, klass);
157 }
158 
159 template <bool ALLOW_INIT = false>
160 // CC-OFFNXT(G.FUD.06) perf critical
CheckClassInitialized(Class * klass)161 inline bool CheckClassInitialized(Class *klass)
162 {
163     ASSERT(klass != nullptr);
164     if constexpr (ALLOW_INIT) {
165         if (UNLIKELY(!klass->IsInitialized())) {
166             auto coro = EtsCoroutine::GetCurrent();
167             ASSERT(coro != nullptr);
168             auto classLinker = coro->GetPandaVM()->GetClassLinker();
169             if (!classLinker->InitializeClass(coro, EtsClass::FromRuntimeClass(klass))) {
170                 INTEROP_LOG(ERROR) << "Class " << klass->GetDescriptor() << " cannot be initialized";
171                 return false;
172             }
173         }
174     } else {
175         INTEROP_FATAL_IF(!klass->IsInitialized());
176     }
177     return true;
178 }
179 
IsStdClass(Class * klass)180 inline bool IsStdClass(Class *klass)
181 {
182     const char *desc = utf::Mutf8AsCString(klass->GetDescriptor());
183     return strstr(desc, "Lstd/") == desc || strstr(desc, "Lescompat/") == desc;
184 }
185 
IsStdClass(EtsClass * klass)186 inline bool IsStdClass(EtsClass *klass)
187 {
188     return IsStdClass(klass->GetRuntimeClass());
189 }
190 
IsSubClassOfError(EtsClass * klass)191 inline bool IsSubClassOfError(EtsClass *klass)
192 {
193     return klass->IsSubClass(PlatformTypes()->escompatError);
194 }
195 
196 }  // namespace ark::ets::interop::js
197 
198 #endif  // !PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_JS_REFCONVERT_H_
199