• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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_TYPES_ETS_ARRAYBUFFER_H
17 #define PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_ARRAYBUFFER_H
18 
19 #include "include/mem/allocator.h"
20 #include "include/object_accessor.h"
21 #include "plugins/ets/runtime/types/ets_object.h"
22 #include "plugins/ets/runtime/types/ets_array.h"
23 #include "plugins/ets/runtime/types/ets_primitives.h"
24 #include "plugins/ets/runtime/ets_coroutine.h"
25 #include "plugins/ets/runtime/ets_exceptions.h"
26 #include "plugins/ets/runtime/ets_platform_types.h"
27 #include "runtime/include/thread_scopes.h"
28 
29 #include <cstdint>
30 
31 namespace ark::ets {
32 
33 namespace test {
34 class EtsArrayBufferTest;
35 class EtsEscompatArrayBufferMembers;
36 }  // namespace test
37 
38 class EtsEscompatArrayBuffer : public EtsObject {
39 public:
40     EtsEscompatArrayBuffer() = delete;
41     ~EtsEscompatArrayBuffer() = delete;
42 
43     NO_COPY_SEMANTIC(EtsEscompatArrayBuffer);
44     NO_MOVE_SEMANTIC(EtsEscompatArrayBuffer);
45 
FromEtsObject(EtsObject * arrayBuffer)46     static EtsEscompatArrayBuffer *FromEtsObject(EtsObject *arrayBuffer)
47     {
48         return reinterpret_cast<EtsEscompatArrayBuffer *>(arrayBuffer);
49     }
50 
GetClassSize()51     static constexpr size_t GetClassSize()
52     {
53         return sizeof(EtsEscompatArrayBuffer);
54     }
55 
56     /**
57      * Creates a byte array in non-movable space.
58      * @param length of created array.
59      * NOTE: non-movable creation ensures that native code can obtain raw pointer to buffer.
60      */
AllocateNonMovableArray(EtsInt length)61     ALWAYS_INLINE static ObjectHeader *AllocateNonMovableArray(EtsInt length)
62     {
63         return EtsByteArray::Create(length, SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT)->GetCoreType();
64     }
65 
GetAddress(const EtsByteArray * array)66     ALWAYS_INLINE static EtsLong GetAddress(const EtsByteArray *array)
67     {
68         return reinterpret_cast<EtsLong>(array->GetData<void>());
69     }
70 
71     /// Creates ArrayBuffer with managed buffer.
Create(EtsCoroutine * coro,size_t length,void ** resultData)72     static EtsEscompatArrayBuffer *Create(EtsCoroutine *coro, size_t length, void **resultData)
73     {
74         ASSERT_MANAGED_CODE();
75         ASSERT(!coro->HasPendingException());
76 
77         [[maybe_unused]] EtsHandleScope scope(coro);
78         auto *cls = PlatformTypes(coro)->escompatArrayBuffer;
79         EtsHandle<EtsEscompatArrayBuffer> handle(coro, EtsEscompatArrayBuffer::FromEtsObject(EtsObject::Create(cls)));
80         if (UNLIKELY(handle.GetPtr() == nullptr)) {
81             ASSERT(coro->HasPendingException());
82             return nullptr;
83         }
84 
85         handle->InitializeByDefault(coro, length);
86         *resultData = handle->GetData();
87         return handle.GetPtr();
88     }
89 
90     /// Creates ArrayBuffer with user-provided buffer and finalization function.
Create(EtsCoroutine * coro,void * externalData,size_t length,EtsFinalize finalizerFunction,void * finalizerHint)91     static EtsEscompatArrayBuffer *Create(EtsCoroutine *coro, void *externalData, size_t length,
92                                           EtsFinalize finalizerFunction, void *finalizerHint)
93     {
94         ASSERT_MANAGED_CODE();
95         ASSERT(!coro->HasPendingException());
96 
97         [[maybe_unused]] EtsHandleScope scope(coro);
98         auto *cls = PlatformTypes(coro)->escompatArrayBuffer;
99         EtsHandle<EtsEscompatArrayBuffer> handle(coro, EtsEscompatArrayBuffer::FromEtsObject(EtsObject::Create(cls)));
100         if (UNLIKELY(handle.GetPtr() == nullptr)) {
101             ASSERT(coro->HasPendingException());
102             return nullptr;
103         }
104 
105         handle->InitBufferByExternalData(coro, handle, externalData, finalizerFunction, finalizerHint, length);
106         return handle.GetPtr();
107     }
108 
AsObject()109     EtsObject *AsObject()
110     {
111         return this;
112     }
113 
GetByteLength()114     EtsInt GetByteLength() const
115     {
116         return byteLength_;
117     }
118 
119     /// @brief Returns non-null data for a non-detached buffer
GetData()120     void *GetData() const
121     {
122         ASSERT(!WasDetached());
123         return reinterpret_cast<void *>(nativeData_);
124     }
125 
Detach()126     void Detach()
127     {
128         ASSERT(IsDetachable());
129         byteLength_ = 0;
130         // Do not free memory, as the address was already passed into finalizer.
131         // Memory will be freed after GC execution with object destruction
132         nativeData_ = 0;
133         ASSERT(WasDetached());
134     }
135 
136     /// NOTE: behavior of this method must repeat implementation of `detached` property in ArkTS `ArrayBuffer`
WasDetached()137     bool WasDetached() const
138     {
139         return nativeData_ == 0;
140     }
141 
IsExternal()142     bool IsExternal() const
143     {
144         return ObjectAccessor::GetObject(this, GetManagedDataOffset()) == nullptr;
145     }
146 
IsDetachable()147     bool IsDetachable() const
148     {
149         return !WasDetached() && IsExternal();
150     }
151 
At(EtsInt pos)152     EtsByte At(EtsInt pos) const
153     {
154         if (!DoBoundaryCheck(pos)) {
155             return 0;
156         }
157         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
158         return reinterpret_cast<int8_t *>(GetData())[pos];
159     }
160 
Set(EtsInt pos,EtsByte val)161     void Set(EtsInt pos, EtsByte val)
162     {
163         if (!DoBoundaryCheck(pos)) {
164             return;
165         }
166         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
167         reinterpret_cast<int8_t *>(GetData())[pos] = val;
168     }
169 
SetValues(EtsEscompatArrayBuffer * other,EtsInt begin)170     void SetValues(EtsEscompatArrayBuffer *other, EtsInt begin)
171     {
172         ASSERT(!WasDetached());
173         ASSERT(other != nullptr);
174         ASSERT(!other->WasDetached());
175         ASSERT(begin >= 0);
176         auto thisByteLength = GetByteLength();
177         ASSERT(begin + thisByteLength <= other->GetByteLength());
178 
179         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
180         auto *srcData = reinterpret_cast<int8_t *>(other->GetData()) + begin;
181         auto *dstData = GetData();
182         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
183         [[maybe_unused]] errno_t res = memcpy_s(dstData, thisByteLength, srcData, thisByteLength);
184         ASSERT(res == 0);
185     }
186 
GetByteLengthOffset()187     static constexpr size_t GetByteLengthOffset()
188     {
189         return MEMBER_OFFSET(EtsEscompatArrayBuffer, byteLength_);
190     }
191 
GetNativeDataOffset()192     static constexpr size_t GetNativeDataOffset()
193     {
194         return MEMBER_OFFSET(EtsEscompatArrayBuffer, nativeData_);
195     }
196 
GetManagedDataOffset()197     static constexpr size_t GetManagedDataOffset()
198     {
199         return MEMBER_OFFSET(EtsEscompatArrayBuffer, managedData_);
200     }
201 
GetIsResizableOffset()202     static constexpr size_t GetIsResizableOffset()
203     {
204         return MEMBER_OFFSET(EtsEscompatArrayBuffer, isResizable_);
205     }
206 
207     template <typename T>
208     T GetElement(uint32_t index, uint32_t offset);
209     template <typename T>
210     void SetElement(uint32_t index, uint32_t offset, T element);
211     template <typename T>
212     T GetVolatileElement(uint32_t index, uint32_t offset);
213     template <typename T>
214     void SetVolatileElement(uint32_t index, uint32_t offset, T element);
215     template <typename T>
216     std::pair<bool, T> CompareAndExchangeElement(uint32_t index, uint32_t offset, T oldElement, T newElement,
217                                                  bool strong);
218     template <typename T>
219     T ExchangeElement(uint32_t index, uint32_t offset, T element);
220     template <typename T>
221     T GetAndAdd(uint32_t index, uint32_t offset, T element);
222     template <typename T>
223     T GetAndSub(uint32_t index, uint32_t offset, T element);
224     template <typename T>
225     T GetAndBitwiseOr(uint32_t index, uint32_t offset, T element);
226     template <typename T>
227     T GetAndBitwiseAnd(uint32_t index, uint32_t offset, T element);
228     template <typename T>
229     T GetAndBitwiseXor(uint32_t index, uint32_t offset, T element);
230 
231 private:
232     struct FinalizationInfo final {
233         // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
234         void *data;
235         EtsFinalize function;
236         void *hint;
237         // NOLINTEND(misc-non-private-member-variables-in-classes)
238 
FinalizationInfofinal239         explicit FinalizationInfo(void *d, EtsFinalize f, void *h) : data(d), function(f), hint(h) {}
240     };
241 
242 private:
243     /**
244      * Creates `FinalizableWeakRef` for created ArrayBuffer.
245      * @param coro in which the ArrayBuffer is created.
246      * @param arrayBufferHandle handle for the created object.
247      * @param finalizerFunction user-provided function to call upon finalization.
248      * @param finalizerHint an additional argument for the finalizer.
249      * NOTE: method must be called under `EtsHandleScope`.
250      */
RegisterFinalizationInfo(EtsCoroutine * coro,const EtsHandle<EtsEscompatArrayBuffer> & arrayBufferHandle,EtsFinalize finalizerFunction,void * finalizerHint)251     static void RegisterFinalizationInfo(EtsCoroutine *coro, const EtsHandle<EtsEscompatArrayBuffer> &arrayBufferHandle,
252                                          EtsFinalize finalizerFunction, void *finalizerHint)
253     {
254         if (finalizerFunction == nullptr) {
255             return;
256         }
257 
258         auto *allocator = static_cast<mem::Allocator *>(Runtime::GetCurrent()->GetInternalAllocator());
259         auto *pandaVm = coro->GetPandaVM();
260 
261         ASSERT(arrayBufferHandle.GetPtr() != nullptr);
262         auto *finalizationInfo =
263             allocator->New<FinalizationInfo>(arrayBufferHandle.GetPtr()->GetData(), finalizerFunction, finalizerHint);
264         EtsHandle<EtsObject> handle(arrayBufferHandle);
265         pandaVm->RegisterFinalizerForObject(coro, handle, DoFinalization, finalizationInfo);
266 
267         ScopedNativeCodeThread s(coro);
268         pandaVm->GetGC()->RegisterNativeAllocation(sizeof(FinalizationInfo));
269     }
270 
DoFinalization(void * arg)271     static void DoFinalization(void *arg)
272     {
273         ASSERT(arg != nullptr);
274         auto *info = reinterpret_cast<FinalizationInfo *>(arg);
275 
276         ASSERT(info->function != nullptr);
277         auto *allocator = static_cast<mem::Allocator *>(Runtime::GetCurrent()->GetInternalAllocator());
278 
279         info->function(info->data, info->hint);
280 
281         auto *vm = Runtime::GetCurrent()->GetPandaVM();
282         vm->GetGC()->RegisterNativeFree(sizeof(FinalizationInfo));
283 
284         allocator->Free(info);
285     }
286 
287     /**
288      * Initializes ArrayBuffer.
289      * NOTE: behavior of this method must repeat initialization from managed constructor.
290      */
InitializeByDefault(EtsCoroutine * coro,size_t length)291     void InitializeByDefault(EtsCoroutine *coro, size_t length)
292     {
293         ObjectAccessor::SetObject(coro, this, GetManagedDataOffset(), AllocateNonMovableArray(length));
294         ASSERT(length <= static_cast<size_t>(std::numeric_limits<EtsInt>::max()));
295         byteLength_ = static_cast<EtsInt>(length);
296         nativeData_ =
297             GetAddress(EtsByteArray::FromCoreType(ObjectAccessor::GetObject(coro, this, GetManagedDataOffset())));
298         ASSERT(nativeData_ != 0);
299         isResizable_ = ToEtsBoolean(false);
300     }
301 
302     /// Initializes ArrayBuffer with externally provided buffer.
InitBufferByExternalData(EtsCoroutine * coro,const EtsHandle<EtsEscompatArrayBuffer> & arrayBufferHandle,void * data,EtsFinalize finalizerFunction,void * finalizerHint,size_t length)303     void InitBufferByExternalData(EtsCoroutine *coro, const EtsHandle<EtsEscompatArrayBuffer> &arrayBufferHandle,
304                                   void *data, EtsFinalize finalizerFunction, void *finalizerHint, size_t length)
305     {
306         ObjectAccessor::SetObject(coro, this, GetManagedDataOffset(), nullptr);
307         ASSERT(length <= static_cast<size_t>(std::numeric_limits<EtsInt>::max()));
308         byteLength_ = static_cast<EtsInt>(length);
309         nativeData_ = reinterpret_cast<EtsLong>(data);
310         ASSERT(nativeData_ != 0);
311         isResizable_ = ToEtsBoolean(false);
312 
313         RegisterFinalizationInfo(coro, arrayBufferHandle, finalizerFunction, finalizerHint);
314     }
315 
316     /**
317      * @brief Checks position is inside array, throws ets exception if not.
318      * NOTE: behavior of this method must repeat initialization from managed `doBoundaryCheck`.
319      */
DoBoundaryCheck(EtsInt pos)320     bool DoBoundaryCheck(EtsInt pos) const
321     {
322         if (pos < 0 || pos >= byteLength_) {
323             PandaString message = "ArrayBuffer position ";
324             message.append(std::to_string(pos)).append(" is out of bounds");
325             ThrowEtsException(EtsCoroutine::GetCurrent(),
326                               panda_file_items::class_descriptors::INDEX_OUT_OF_BOUNDS_ERROR, message.c_str());
327             return false;
328         }
329         return true;
330     }
331 
332 private:
333     // ClassLinker reorders fileds based on them size. Object pointer size can be different for different configs
334 #if defined(PANDA_TARGET_64) && !defined(PANDA_USE_32_BIT_POINTER)
335     // Managed array used in this `ArrayBuffer`, null if buffer is external
336     ObjectPointer<EtsByteArray> managedData_;
337     // Contains pointer to either managed non-movable data or external data.
338     // Null if `ArrayBuffer` was detached, non-null otherwise
339     EtsLong nativeData_;
340     EtsInt byteLength_;
341     EtsBoolean isResizable_;
342 #else
343     // Managed array used in this `ArrayBuffer`, null if buffer is external
344     ObjectPointer<EtsByteArray> managedData_;
345     EtsInt byteLength_;
346     // Contains pointer to either managed non-movable data or external data.
347     // Null if `ArrayBuffer` was detached, non-null otherwise
348     EtsLong nativeData_;
349     EtsBoolean isResizable_;
350 #endif
351 
352     friend class test::EtsArrayBufferTest;
353     friend class test::EtsEscompatArrayBufferMembers;
354 };
355 
356 }  // namespace ark::ets
357 
358 #endif  // PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_ARRAYBUFFER_H
359