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 #include "runtime/mem/refstorage/reference_storage.h"
17
18 #include "libpandabase/mem/mem.h"
19 #include "libpandabase/utils/dfx.h"
20 #include "runtime/include/thread.h"
21 #include "runtime/mem/object_helpers.h"
22 #include "runtime/mem/refstorage/global_object_storage.h"
23 #include "runtime/mem/gc/gc_root.h"
24 #include "runtime/include/stack_walker-inl.h"
25 #include "libpandabase/utils/bit_utils.h"
26
27 namespace ark::mem {
28
29 // NOTE(alovkov): remove check for null, create managed thread in test instead of std::thread
30 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
31 #define ASSERT_THREAD_STATE() \
32 ASSERT(MTManagedThread::GetCurrent() == nullptr || !MTManagedThread::GetCurrent()->IsInNativeCode())
33
ReferenceStorage(GlobalObjectStorage * globalStorage,mem::InternalAllocatorPtr allocator,bool refCheckValidate)34 ReferenceStorage::ReferenceStorage(GlobalObjectStorage *globalStorage, mem::InternalAllocatorPtr allocator,
35 bool refCheckValidate)
36 : globalStorage_(globalStorage), internalAllocator_(allocator), refCheckValidate_(refCheckValidate)
37 {
38 }
39
~ReferenceStorage()40 ReferenceStorage::~ReferenceStorage()
41 {
42 if (frameAllocator_ != nullptr) {
43 internalAllocator_->Delete(frameAllocator_);
44 }
45 if (localStorage_ != nullptr) {
46 internalAllocator_->Delete(localStorage_);
47 }
48 }
49
Init()50 bool ReferenceStorage::Init()
51 {
52 if (localStorage_ != nullptr || frameAllocator_ != nullptr || blocksCount_ != 0) {
53 return false;
54 }
55 localStorage_ = internalAllocator_->New<PandaVector<RefBlock *>>(internalAllocator_->Adapter());
56 if (localStorage_ == nullptr) {
57 return false;
58 }
59 frameAllocator_ = internalAllocator_->New<StorageFrameAllocator>();
60 if (frameAllocator_ == nullptr) {
61 return false;
62 }
63 // main frame should always be created
64 auto *firstBlock = CreateBlock();
65 if (firstBlock == nullptr) {
66 return false;
67 }
68 firstBlock->Reset();
69 blocksCount_ = 1;
70 localStorage_->push_back(firstBlock);
71 return true;
72 }
73
IsValidRef(const Reference * ref)74 bool ReferenceStorage::IsValidRef(const Reference *ref)
75 {
76 ASSERT(ref != nullptr);
77 auto type = Reference::GetType(ref);
78
79 bool res = false;
80 if (type == mem::Reference::ObjectType::STACK) {
81 res = StackReferenceCheck(ref);
82 } else if (type == mem::Reference::ObjectType::GLOBAL || type == mem::Reference::ObjectType::WEAK) {
83 // global-storage should accept ref with type
84 res = globalStorage_->IsValidGlobalRef(ref);
85 } else {
86 auto refWithoutType = Reference::GetRefWithoutType(ref);
87 // NOTE(alovkov): can be optimized with mmap + make additional checks that we really have ref in slots,
88 // Issue 3645
89 res = frameAllocator_->Contains(reinterpret_cast<void *>(refWithoutType));
90 }
91 return res;
92 }
93
GetObjectType(const Reference * ref)94 Reference::ObjectType ReferenceStorage::GetObjectType(const Reference *ref)
95 {
96 return ref->GetType();
97 }
98
99 // NOLINTNEXTLINE(readability-function-size)
NewRef(const ObjectHeader * object,Reference::ObjectType type)100 Reference *ReferenceStorage::NewRef(const ObjectHeader *object, Reference::ObjectType type)
101 {
102 ASSERT(type != Reference::ObjectType::STACK);
103 ASSERT_THREAD_STATE();
104 if (object == nullptr) {
105 return nullptr;
106 }
107 ValidateObject(nullptr, object);
108 Reference *ref = nullptr;
109 if (type == Reference::ObjectType::GLOBAL || type == Reference::ObjectType::WEAK) {
110 ref = static_cast<Reference *>(globalStorage_->Add(object, type));
111 } else {
112 auto *lastBlock = localStorage_->back();
113 ASSERT(lastBlock != nullptr);
114
115 RefBlock *curBlock = nullptr;
116 if (lastBlock->IsFull()) {
117 curBlock = CreateBlock();
118 if (curBlock == nullptr) {
119 // NOTE(alovkov): make free-list of holes in all blocks. O(n) operations, but it will be done only once
120 LOG(ERROR, GC) << "Can't allocate local ref for object: " << object
121 << ", cls: " << object->ClassAddr<ark::Class>()->GetName()
122 << " with type: " << static_cast<int>(type);
123 DumpLocalRef();
124 return nullptr;
125 }
126 curBlock->Reset(lastBlock);
127 (*localStorage_)[localStorage_->size() - 1] = curBlock;
128 } else {
129 curBlock = lastBlock;
130 }
131 ref = curBlock->AddRef(object, type);
132 }
133 LOG(DEBUG, GC) << "Add reference to object: " << object << " type: " << static_cast<int>(type) << " ref: " << ref;
134 return ref;
135 }
136
RemoveRef(const Reference * ref)137 void ReferenceStorage::RemoveRef(const Reference *ref)
138 {
139 if (ref == nullptr) {
140 return;
141 }
142
143 if (refCheckValidate_) {
144 if (UNLIKELY(!IsValidRef(ref))) {
145 // Undefined behavior, we just print warning here.
146 LOG(WARNING, GC) << "Try to remove not existed ref: " << ref;
147 return;
148 }
149 }
150 Reference::ObjectType objectType = ref->GetType();
151 if (objectType == Reference::ObjectType::GLOBAL || objectType == Reference::ObjectType::WEAK) {
152 // When the global or weak global ref is created by another thread, we can't suppose current thread is in
153 // MANAGED_CODE state.
154 LOG(DEBUG, GC) << "Remove global reference: " << ref << " obj: " << globalStorage_->Get(ref);
155 globalStorage_->Remove(ref);
156 } else if (objectType == Reference::ObjectType::LOCAL) {
157 ASSERT_THREAD_STATE();
158 auto addr = ToUintPtr(ref);
159 auto blockAddr = (addr >> BLOCK_ALIGNMENT) << BLOCK_ALIGNMENT;
160 auto *block = reinterpret_cast<RefBlock *>(blockAddr);
161
162 LOG(DEBUG, GC) << "Remove local reference: " << ref << " obj: " << FindLocalObject(ref);
163 block->Remove(ref);
164 } else if (objectType == Reference::ObjectType::STACK) {
165 LOG(ERROR, GC) << "Cannot remove stack type: " << ref;
166 } else {
167 LOG(FATAL, GC) << "Unknown reference type: " << ref;
168 }
169 }
170
GetObject(const Reference * ref)171 ObjectHeader *ReferenceStorage::GetObject(const Reference *ref)
172 {
173 if (UNLIKELY(ref == nullptr)) {
174 return nullptr;
175 }
176
177 if (refCheckValidate_) {
178 if (UNLIKELY(!IsValidRef(ref))) {
179 // Undefined behavior, we just print warning here.
180 LOG(WARNING, GC) << "Try to GetObject from a not existed ref: " << ref;
181 return nullptr;
182 }
183 }
184 Reference::ObjectType objectType = ref->GetType();
185 switch (objectType) {
186 case Reference::ObjectType::GLOBAL:
187 case Reference::ObjectType::WEAK: {
188 ObjectHeader *obj = globalStorage_->Get(ref);
189 #ifndef NDEBUG
190 // only weakly reachable objects can be null in storage
191 if (objectType == mem::Reference::ObjectType::GLOBAL) {
192 ASSERT(obj != nullptr);
193 }
194 #endif
195 return obj;
196 }
197 case Reference::ObjectType::STACK: {
198 // In current scheme object passed in 64-bit argument
199 // But compiler may store 32-bit and trash in hi-part
200 // That's why need cut object pointer
201 return reinterpret_cast<ObjectHeader *>(
202 (*reinterpret_cast<ObjectPointerType *>(Reference::GetRefWithoutType(ref))));
203 }
204 case Reference::ObjectType::LOCAL: {
205 ObjectHeader *obj = FindLocalObject(ref);
206 ASSERT(obj != nullptr);
207 #ifndef NDEBUG
208 /*
209 * classes are not movable objects, so they can be read from storage in native code, but general objects are
210 * not
211 */
212 auto baseCls = obj->ClassAddr<BaseClass>();
213 if (!baseCls->IsDynamicClass()) {
214 auto cls = static_cast<Class *>(baseCls);
215 if (!cls->IsClassClass()) {
216 ASSERT_THREAD_STATE();
217 }
218 }
219 #endif
220 return obj;
221 }
222 default: {
223 LOG(FATAL, RUNTIME) << "Unknown reference: " << ref << " type: " << static_cast<int>(objectType);
224 }
225 }
226 return nullptr;
227 }
228
PushLocalFrame(uint32_t capacity)229 bool ReferenceStorage::PushLocalFrame(uint32_t capacity)
230 {
231 ASSERT_THREAD_STATE();
232 size_t needBlocks = (static_cast<uint64_t>(capacity) + RefBlock::REFS_IN_BLOCK - 1) / RefBlock::REFS_IN_BLOCK;
233
234 size_t blocksFree = MAX_STORAGE_BLOCK_COUNT - blocksCount_;
235 if (needBlocks > blocksFree) {
236 LOG(ERROR, GC) << "Free size of local reference storage is less than capacity: " << capacity
237 << " blocks_count_: " << blocksCount_ << " need_blocks: " << needBlocks
238 << " blocks_free: " << blocksFree;
239 return false;
240 }
241 auto *newBlock = CreateBlock();
242 if (newBlock == nullptr) {
243 LOG(FATAL, GC) << "Can't allocate new frame";
244 UNREACHABLE();
245 }
246 newBlock->Reset();
247 localStorage_->push_back(newBlock);
248 return true;
249 }
250
PopLocalFrame(const Reference * result)251 Reference *ReferenceStorage::PopLocalFrame(const Reference *result)
252 {
253 ASSERT_THREAD_STATE();
254
255 ObjectHeader *obj;
256 if (result != nullptr) {
257 obj = GetObject(result);
258 } else {
259 obj = nullptr;
260 }
261
262 if (cachedBlock_ != nullptr) {
263 RemoveBlock(cachedBlock_);
264 cachedBlock_ = nullptr;
265 }
266
267 // We should add a log which refs are deleted under debug
268 auto *lastBlock = localStorage_->back();
269 auto isFirst = localStorage_->size() == 1;
270 while (lastBlock != nullptr) {
271 auto *prev = lastBlock->GetPrev();
272 if (prev == nullptr && isFirst) {
273 // it's the first block, which we don't delete
274 break;
275 }
276 // cache the last block for ping-pong effect
277 if (prev == nullptr) {
278 if (cachedBlock_ == nullptr) {
279 cachedBlock_ = lastBlock;
280 break;
281 }
282 }
283 RemoveBlock(lastBlock);
284 lastBlock = prev;
285 }
286
287 if (obj == nullptr) {
288 localStorage_->pop_back();
289 return nullptr;
290 }
291
292 Reference::ObjectType type = result->GetType();
293 localStorage_->pop_back();
294 return NewRef(obj, type);
295 }
296
EnsureLocalCapacity(uint32_t capacity)297 bool ReferenceStorage::EnsureLocalCapacity(uint32_t capacity)
298 {
299 size_t needBlocks = (static_cast<uint64_t>(capacity) + RefBlock::REFS_IN_BLOCK - 1) / RefBlock::REFS_IN_BLOCK;
300 size_t blocksFreed = MAX_STORAGE_BLOCK_COUNT - blocksCount_;
301 if (needBlocks > blocksFreed) {
302 LOG(ERROR, GC) << "Can't store size: " << capacity << " in local references";
303 return false;
304 }
305 return true;
306 }
307
FindLocalObject(const Reference * ref)308 ObjectHeader *ReferenceStorage::FindLocalObject(const Reference *ref)
309 {
310 ref = Reference::GetRefWithoutType(ref);
311 ObjectPointer<ObjectHeader> objPointer = *(reinterpret_cast<const ObjectPointer<ObjectHeader> *>(ref));
312 return objPointer;
313 }
314
GetAllObjects()315 PandaVector<ObjectHeader *> ReferenceStorage::GetAllObjects()
316 {
317 auto objects = globalStorage_->GetAllObjects();
318 for (const auto ¤tFrame : *localStorage_) {
319 auto lastBlock = currentFrame;
320 const PandaVector<mem::Reference *> &refs = lastBlock->GetAllReferencesInFrame();
321 for (const auto &ref : refs) {
322 ObjectHeader *obj = FindLocalObject(ref);
323 objects.push_back(reinterpret_cast<ObjectHeader *>(obj));
324 }
325 }
326 return objects;
327 }
328
VisitObjects(const GCRootVisitor & gcRootVisitor,mem::RootType rootType)329 void ReferenceStorage::VisitObjects(const GCRootVisitor &gcRootVisitor, mem::RootType rootType)
330 {
331 for (const auto &frame : *localStorage_) {
332 frame->VisitObjects(gcRootVisitor, rootType);
333 }
334 }
335
UpdateMovedRefs(const GCRootUpdater & gcRootUpdater)336 void ReferenceStorage::UpdateMovedRefs(const GCRootUpdater &gcRootUpdater)
337 {
338 for (const auto &frame : *localStorage_) {
339 frame->UpdateMovedRefs(gcRootUpdater);
340 }
341 }
342
DumpLocalRefClasses()343 void ReferenceStorage::DumpLocalRefClasses()
344 {
345 PandaMap<PandaString, int> classesInfo;
346
347 for (const auto &frame : *localStorage_) {
348 auto lastBlock = frame;
349 auto refs = lastBlock->GetAllReferencesInFrame();
350 for (const auto &ref : refs) {
351 ObjectHeader *obj = FindLocalObject(ref);
352 PandaString clsName = ConvertToString(obj->ClassAddr<ark::Class>()->GetName());
353 classesInfo[clsName]++;
354 }
355 }
356 using InfoPair = std::pair<PandaString, int>;
357 PandaVector<InfoPair> infoVec(classesInfo.begin(), classesInfo.end());
358 size_t size = std::min(MAX_DUMP_LOCAL_NUMS, infoVec.size());
359 std::partial_sort(infoVec.begin(), infoVec.begin() + size, infoVec.end(),
360 [](const InfoPair &lhs, const InfoPair &rhs) { return lhs.second < rhs.second; });
361 LOG(ERROR, GC) << "The top " << size << " classes of local references are:";
362 for (size_t i = 0; i < size; i++) {
363 LOG(ERROR, GC) << "\t" << infoVec[i].first << ": " << infoVec[i].second;
364 }
365 }
366
DumpLocalRef()367 void ReferenceStorage::DumpLocalRef()
368 {
369 if (DfxController::IsInitialized() && DfxController::GetOptionValue(DfxOptionHandler::REFERENCE_DUMP) != 1) {
370 return;
371 }
372 LOG(ERROR, GC) << "--- local reference storage dump ---";
373 LOG(ERROR, GC) << "Local reference storage addr: " << &localStorage_;
374 LOG(ERROR, GC) << "Dump the last several local references info(max " << MAX_DUMP_LOCAL_NUMS << "):";
375 size_t nDump = 0;
376
377 for (auto it = localStorage_->rbegin(); it != localStorage_->rend(); ++it) {
378 auto *frame = *it;
379 auto refs = frame->GetAllReferencesInFrame();
380 for (const auto &ref : refs) {
381 ObjectHeader *res = FindLocalObject(ref);
382 PandaString clsName = ConvertToString(res->ClassAddr<ark::Class>()->GetName());
383 LOG(ERROR, GC) << "\t local reference: " << ref << ", object: " << res << ", cls: " << clsName;
384 nDump++;
385 if (nDump == MAX_DUMP_LOCAL_NUMS) {
386 DumpLocalRefClasses();
387 LOG(ERROR, GC) << "---";
388 LOG(ERROR, GC) << "Storage dumped maximum number of references";
389 return;
390 }
391 }
392 }
393 }
394
CreateBlock()395 RefBlock *ReferenceStorage::CreateBlock()
396 {
397 if (blocksCount_ == MAX_STORAGE_BLOCK_COUNT) {
398 return nullptr;
399 }
400
401 if (cachedBlock_ != nullptr) {
402 RefBlock *newBlock = cachedBlock_;
403 cachedBlock_ = nullptr;
404 return newBlock;
405 }
406
407 blocksCount_++;
408 return static_cast<RefBlock *>(frameAllocator_->Alloc(BLOCK_SIZE));
409 }
410
RemoveBlock(RefBlock * block)411 void ReferenceStorage::RemoveBlock(RefBlock *block)
412 {
413 frameAllocator_->Free(block);
414 blocksCount_--;
415 }
416
RemoveAllLocalRefs()417 void ReferenceStorage::RemoveAllLocalRefs()
418 {
419 ASSERT_THREAD_STATE();
420
421 for (const auto &frame : *localStorage_) {
422 auto lastBlock = frame;
423 auto refs = lastBlock->GetAllReferencesInFrame();
424 for (const auto &ref : refs) {
425 lastBlock->Remove(ref);
426 }
427 }
428 }
429
GetGlobalObjectStorageSize()430 size_t ReferenceStorage::GetGlobalObjectStorageSize()
431 {
432 return globalStorage_->GetSize();
433 }
434
GetLocalObjectStorageSize()435 size_t ReferenceStorage::GetLocalObjectStorageSize()
436 {
437 size_t size = 0;
438 for (const auto &block : *localStorage_) {
439 auto *currentBlock = block;
440 size += currentBlock->GetAllReferencesInFrame().size();
441 }
442 return size;
443 }
444
SetRefCheckValidate(bool refCheckValidate)445 void ReferenceStorage::SetRefCheckValidate(bool refCheckValidate)
446 {
447 refCheckValidate_ = refCheckValidate;
448 }
449
StackReferenceCheck(const Reference * stackRefInput)450 bool ReferenceStorage::StackReferenceCheck(const Reference *stackRefInput)
451 {
452 ASSERT(stackRefInput->IsStack());
453 ManagedThread *thread = ManagedThread::GetCurrent();
454 ASSERT(thread != nullptr);
455
456 for (auto pframe = StackWalker::Create(thread); pframe.HasFrame(); pframe.NextFrame()) {
457 if (!pframe.IsCFrame()) {
458 return false;
459 }
460
461 auto cframe = pframe.GetCFrame();
462 if (!cframe.IsNative()) {
463 return false;
464 }
465
466 bool res = false;
467 pframe.IterateObjectsWithInfo([&cframe, &stackRefInput, &res](auto ®Info, [[maybe_unused]] auto &vreg) {
468 auto slotTypeRef = cframe.GetValuePtrFromSlot(regInfo.GetValue());
469 auto objectHeader = bit_cast<ObjectHeader **, const ark::CFrame::SlotType *>(slotTypeRef);
470 auto stackRef = NewStackRef(objectHeader);
471 if (stackRef == stackRefInput) {
472 res = true;
473 return false;
474 }
475 return true;
476 });
477
478 if (res) {
479 return true;
480 }
481 }
482 return false;
483 }
484
485 } // namespace ark::mem
486