// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #ifndef RUNTIME_VM_HEAP_POINTER_BLOCK_H_ #define RUNTIME_VM_HEAP_POINTER_BLOCK_H_ #include "platform/assert.h" #include "platform/memory_sanitizer.h" #include "vm/globals.h" #include "vm/os_thread.h" #include "vm/tagged_pointer.h" namespace dart { // Forward declarations. class Isolate; class ObjectPointerVisitor; // A set of ObjectPtr. Must be emptied before destruction (using Pop/Reset). template class PointerBlock : public MallocAllocated { public: enum { kSize = Size }; void Reset() { top_ = 0; next_ = nullptr; } PointerBlock* next() const { return next_; } void set_next(PointerBlock* next) { next_ = next; } intptr_t Count() const { return top_; } bool IsFull() const { return Count() == kSize; } bool IsEmpty() const { return Count() == 0; } void Push(ObjectPtr obj) { ASSERT(!IsFull()); pointers_[top_++] = obj; } ObjectPtr Pop() { ASSERT(!IsEmpty()); return pointers_[--top_]; } #if defined(TESTING) bool Contains(ObjectPtr obj) const { // Generated code appends to store buffers; tell MemorySanitizer. MSAN_UNPOISON(this, sizeof(*this)); for (intptr_t i = 0; i < Count(); i++) { if (pointers_[i] == obj) { return true; } } return false; } #endif // TESTING static intptr_t top_offset() { return OFFSET_OF(PointerBlock, top_); } static intptr_t pointers_offset() { return OFFSET_OF(PointerBlock, pointers_); } void VisitObjectPointers(ObjectPointerVisitor* visitor); private: PointerBlock() : next_(nullptr), top_(0) {} ~PointerBlock() { ASSERT(IsEmpty()); // Guard against unintentionally discarding pointers. } PointerBlock* next_; int32_t top_; ObjectPtr pointers_[kSize]; template friend class BlockStack; template friend class LocalBlockWorkList; DISALLOW_COPY_AND_ASSIGN(PointerBlock); }; // A synchronized collection of pointer blocks of a particular size. // This class is meant to be used as a base (note PushBlockImpl is protected). // The global list of cached empty blocks is currently per-size. template class BlockStack { public: typedef PointerBlock Block; BlockStack(); ~BlockStack(); static void Init(); static void Cleanup(); // Partially filled blocks can be reused, and there is an "infinite" supply // of empty blocks (reused or newly allocated). In any case, the caller // takes ownership of the returned block. Block* PopNonFullBlock(); Block* PopEmptyBlock(); Block* PopNonEmptyBlock(); // Pops and returns all non-empty blocks as a linked list (owned by caller). Block* PopAll(); void PushAll(Block* blocks); // Discards the contents of all non-empty blocks. void Reset(); bool IsEmpty(); Block* WaitForWork(RelaxedAtomic* num_busy, bool abort); void VisitObjectPointers(ObjectPointerVisitor* visitor); protected: class List { public: List() : head_(nullptr), length_(0) {} ~List(); void Push(Block* block); Block* Pop(); intptr_t length() const { return length_; } bool IsEmpty() const { return head_ == nullptr; } Block* PopAll(); Block* Peek() { return head_; } private: Block* head_; RelaxedAtomic length_; DISALLOW_COPY_AND_ASSIGN(List); }; bool IsEmptyLocked(); // Adds and transfers ownership of the block to the buffer. void PushBlockImpl(Block* block); // If needed, trims the global cache of empty blocks. static void TrimGlobalEmpty(); List full_; List partial_; Monitor monitor_; // Note: This is shared on the basis of block size. static constexpr intptr_t kMaxGlobalEmpty = 100; static List* global_empty_; static Mutex* global_mutex_; private: DISALLOW_COPY_AND_ASSIGN(BlockStack); }; template class BlockWorkList : public ValueObject { public: typedef typename Stack::Block Block; explicit BlockWorkList(Stack* stack) : stack_(stack) { local_output_ = stack_->PopEmptyBlock(); local_input_ = stack_->PopEmptyBlock(); } ~BlockWorkList() { ASSERT(local_output_ == nullptr); ASSERT(local_input_ == nullptr); ASSERT(stack_ == nullptr); } // Returns false if no more work was found. DART_FORCE_INLINE bool Pop(ObjectPtr* object) { ASSERT(local_input_ != nullptr); if (local_input_->IsEmpty()) [[unlikely]] { if (!local_output_->IsEmpty()) { auto temp = local_output_; local_output_ = local_input_; local_input_ = temp; } else { Block* new_work = stack_->PopNonEmptyBlock(); if (new_work == nullptr) { return false; } stack_->PushBlock(local_input_); local_input_ = new_work; // Generated code appends to marking stacks; tell MemorySanitizer. MSAN_UNPOISON(local_input_, sizeof(*local_input_)); } } *object = local_input_->Pop(); return true; } // Returns false if no more work was found. DART_FORCE_INLINE static bool Pop(BlockWorkList* first_choice, BlockWorkList* second_choice, ObjectPtr* object) { if (!first_choice->local_input_->IsEmpty()) { *object = first_choice->local_input_->Pop(); return true; } if (!second_choice->local_input_->IsEmpty()) { *object = second_choice->local_input_->Pop(); return true; } if (first_choice->Pop(object)) { return true; } if (second_choice->Pop(object)) { return true; } return false; } void Push(ObjectPtr raw_obj) { if (local_output_->IsFull()) [[unlikely]] { stack_->PushBlock(local_output_); local_output_ = stack_->PopEmptyBlock(); } local_output_->Push(raw_obj); } void Flush() { if (!local_output_->IsEmpty()) { stack_->PushBlock(local_output_); local_output_ = stack_->PopEmptyBlock(); } if (!local_input_->IsEmpty()) { stack_->PushBlock(local_input_); local_input_ = stack_->PopEmptyBlock(); } } bool WaitForWork(RelaxedAtomic* num_busy, bool abort = false) { ASSERT(local_input_->IsEmpty() || abort); Block* new_work = stack_->WaitForWork(num_busy, abort); if (new_work == nullptr) { return false; } stack_->PushBlock(local_input_); local_input_ = new_work; return true; } void Finalize() { ASSERT(local_output_->IsEmpty()); stack_->PushBlock(local_output_); local_output_ = nullptr; ASSERT(local_input_->IsEmpty()); stack_->PushBlock(local_input_); local_input_ = nullptr; // Fail fast on attempts to mark after finalizing. stack_ = nullptr; } void AbandonWork() { stack_->PushBlock(local_output_); local_output_ = nullptr; stack_->PushBlock(local_input_); local_input_ = nullptr; stack_ = nullptr; } bool IsLocalEmpty() { if (!local_input_->IsEmpty()) { return false; } if (!local_output_->IsEmpty()) { return false; } return true; } bool IsEmpty() { return IsLocalEmpty() && stack_->IsEmpty(); } private: Block* local_output_; Block* local_input_; Stack* stack_; }; static constexpr int kStoreBufferBlockSize = 1024; class StoreBuffer : public BlockStack { public: // Interrupt when crossing this threshold of non-empty blocks in the buffer. static constexpr intptr_t kMaxNonEmpty = 100; enum ThresholdPolicy { kCheckThreshold, kIgnoreThreshold }; // Adds and transfers ownership of the block to the buffer. Optionally // checks the number of non-empty blocks for overflow, and schedules an // interrupt on the current isolate if so. void PushBlock(Block* block, ThresholdPolicy policy); // Check whether non-empty blocks have exceeded kMaxNonEmpty (but takes no // action). bool Overflowed(); intptr_t Size(); }; typedef StoreBuffer::Block StoreBufferBlock; static constexpr int kMarkingStackBlockSize = 64; class MarkingStack : public BlockStack { public: // Adds and transfers ownership of the block to the buffer. void PushBlock(Block* block) { BlockStack::PushBlockImpl(block); } }; typedef MarkingStack::Block MarkingStackBlock; typedef BlockWorkList MarkerWorkList; static constexpr int kPromotionStackBlockSize = 64; class PromotionStack : public BlockStack { public: // Adds and transfers ownership of the block to the buffer. void PushBlock(Block* block) { BlockStack::PushBlockImpl(block); } }; typedef PromotionStack::Block PromotionStackBlock; typedef BlockWorkList PromotionWorkList; template class LocalBlockWorkList : public ValueObject { public: LocalBlockWorkList() { head_ = new PointerBlock(); } ~LocalBlockWorkList() { ASSERT(head_ == nullptr); } template DART_FORCE_INLINE void Process(Lambda action) { auto* block = head_; head_ = new PointerBlock(); while (block != nullptr) { while (!block->IsEmpty()) { action(static_cast(block->Pop())); } auto* next = block->next(); delete block; block = next; } } void Push(T obj) { if (head_->IsFull()) [[unlikely]] { PointerBlock* next = new PointerBlock(); next->next_ = head_; head_ = next; } head_->Push(obj); } void Finalize() { ASSERT(head_ != nullptr); ASSERT(head_->IsEmpty()); delete head_; head_ = nullptr; } void AbandonWork() { ASSERT(head_ != nullptr); auto* block = head_; head_ = nullptr; while (block != nullptr) { auto* next = block->next_; block->Reset(); delete block; block = next; } } private: PointerBlock* head_; }; } // namespace dart #endif // RUNTIME_VM_HEAP_POINTER_BLOCK_H_