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