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