| /* |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_IMPL_HEAP_PAGE_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_IMPL_HEAP_PAGE_H_ |
| |
| #include <stdint.h> |
| #include <array> |
| #include <atomic> |
| |
| #include "base/bits.h" |
| #include "base/compiler_specific.h" |
| #include "build/build_config.h" |
| #include "third_party/blink/renderer/platform/heap/blink_gc.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/heap/heap_buildflags.h" |
| #include "third_party/blink/renderer/platform/heap/impl/gc_info.h" |
| #include "third_party/blink/renderer/platform/heap/impl/thread_state_statistics.h" |
| #include "third_party/blink/renderer/platform/heap/impl/unsanitized_atomic.h" |
| #include "third_party/blink/renderer/platform/heap/thread_state.h" |
| #include "third_party/blink/renderer/platform/heap/visitor.h" |
| #include "third_party/blink/renderer/platform/platform_export.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/container_annotations.h" |
| #include "third_party/blink/renderer/platform/wtf/forward.h" |
| #include "third_party/blink/renderer/platform/wtf/sanitizers.h" |
| #include "third_party/blink/renderer/platform/wtf/threading_primitives.h" |
| |
| namespace blink { |
| |
| // TODO(palmer): Document the reason for 17. |
| constexpr size_t kBlinkPageSizeLog2 = 17; |
| constexpr size_t kBlinkPageSize = 1 << kBlinkPageSizeLog2; |
| constexpr size_t kBlinkPageOffsetMask = kBlinkPageSize - 1; |
| constexpr size_t kBlinkPageBaseMask = ~kBlinkPageOffsetMask; |
| |
| // We allocate pages at random addresses but in groups of kBlinkPagesPerRegion |
| // at a given random address. We group pages to not spread out too much over the |
| // address space which would blow away the page tables and lead to bad |
| // performance. |
| constexpr size_t kBlinkPagesPerRegion = 10; |
| |
| // TODO(nya): Replace this with something like #if ENABLE_NACL. |
| #if defined(ARCH_CPU_PPC64) |
| // NaCl's system page size is 64 KiB. This causes a problem in Oilpan's heap |
| // layout because Oilpan allocates two guard pages for each Blink page (whose |
| // size is kBlinkPageSize = 2^17 = 128 KiB). So we don't use guard pages in |
| // NaCl. |
| // The same issue holds for ppc64 systems, which use a 64k page size. |
| PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t |
| BlinkGuardPageSize() { |
| return 0; |
| } |
| #else |
| PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t |
| BlinkGuardPageSize() { |
| return base::SystemPageSize(); |
| } |
| #endif |
| |
| // Double precision floats are more efficient when 8-byte aligned, so we 8-byte |
| // align all allocations (even on 32 bit systems). |
| static_assert(8 == sizeof(double), "We expect sizeof(double) to be 8"); |
| constexpr size_t kAllocationGranularity = sizeof(double); |
| constexpr size_t kAllocationMask = kAllocationGranularity - 1; |
| constexpr size_t kLargeObjectSizeThreshold = kBlinkPageSize / 2; |
| |
| // A zap value used for freed memory that is allowed to be added to the free |
| // list in the next call to AddToFreeList. |
| constexpr uint8_t kReuseAllowedZapValue = 0x2a; |
| // A zap value used for freed memory that is forbidden to be added to the free |
| // list in the next call to AddToFreeList. |
| constexpr uint8_t kReuseForbiddenZapValue = 0x2c; |
| |
| // In non-production builds, memory is zapped when it's freed. The zapped memory |
| // is zeroed out when the memory is reused in ThreadHeap::AllocateObject. |
| // |
| // In production builds, memory is not zapped (for performance). The memory is |
| // just zeroed out when it is added to the free list. |
| #if defined(MEMORY_SANITIZER) |
| // TODO(kojii): We actually need __msan_poison/unpoison here, but it'll be |
| // added later. |
| #define SET_MEMORY_INACCESSIBLE(address, size) \ |
| FreeList::ZapFreedMemory(address, size); |
| #define SET_MEMORY_ACCESSIBLE(address, size) memset((address), 0, (size)) |
| #define CHECK_MEMORY_INACCESSIBLE(address, size) \ |
| ASAN_UNPOISON_MEMORY_REGION(address, size); \ |
| FreeList::CheckFreedMemoryIsZapped(address, size); \ |
| ASAN_POISON_MEMORY_REGION(address, size) |
| #elif DCHECK_IS_ON() || defined(LEAK_SANITIZER) || defined(ADDRESS_SANITIZER) |
| #define SET_MEMORY_INACCESSIBLE(address, size) \ |
| FreeList::ZapFreedMemory(address, size); \ |
| ASAN_POISON_MEMORY_REGION(address, size) |
| #define SET_MEMORY_ACCESSIBLE(address, size) \ |
| ASAN_UNPOISON_MEMORY_REGION(address, size); \ |
| memset((address), 0, (size)) |
| #define CHECK_MEMORY_INACCESSIBLE(address, size) \ |
| ASAN_UNPOISON_MEMORY_REGION(address, size); \ |
| FreeList::CheckFreedMemoryIsZapped(address, size); \ |
| ASAN_POISON_MEMORY_REGION(address, size) |
| #else |
| #define SET_MEMORY_INACCESSIBLE(address, size) memset((address), 0, (size)) |
| #define SET_MEMORY_ACCESSIBLE(address, size) \ |
| do { \ |
| } while (false) |
| #define CHECK_MEMORY_INACCESSIBLE(address, size) \ |
| do { \ |
| } while (false) |
| #endif |
| |
| class NormalPageArena; |
| class PageMemory; |
| class BaseArena; |
| class ThreadHeap; |
| |
| // HeapObjectHeader is a 32-bit object that has the following layout: |
| // |
| // | padding (32 bits) | Only present on 64-bit platforms. |
| // | gc_info_index (14 bits) | |
| // | unused (1 bit) | |
| // | in construction (1 bit) | true: bit not set; false bit set |
| // |
| // | size (14 bits) | Actually 17 bits because sizes are aligned. |
| // | unused (1 bit) | |
| // | mark bit (1 bit) | |
| // |
| // Notes: |
| // - 14 bits for |gc_info_index} (type information) are enough as there are |
| // fewer than 2^14 types allocated in Blink. |
| // - |size| for regular objects is encoded with 14 bits but can actually |
| // represent sizes up to |kBlinkPageSize| (2^17) because allocations are |
| // always 8 byte aligned (see kAllocationGranularity). |
| // - |size| for large objects is encoded as 0. The size of a large object is |
| // stored in |LargeObjectPage::PayloadSize()|. |
| // - |mark bit| and |in construction| bits are located in separate variables and |
| // therefore can be accessed concurrently. Since tsan works with word-size |
| // objects they still should be accessed atomically. |
| constexpr uint16_t kHeaderMarkBitMask = 1; |
| constexpr uint16_t kHeaderSizeShift = 2; |
| constexpr uint16_t kHeaderSizeMask = |
| static_cast<uint16_t>(((1 << 14) - 1) << kHeaderSizeShift); |
| |
| constexpr uint16_t kHeaderIsInConstructionMask = 1; |
| constexpr uint16_t kHeaderGCInfoIndexShift = 2; |
| constexpr uint16_t kHeaderGCInfoSize = static_cast<uint16_t>(1 << 14); |
| constexpr uint16_t kHeaderGCInfoIndexMask = |
| static_cast<uint16_t>((kHeaderGCInfoSize - 1) << kHeaderGCInfoIndexShift); |
| |
| constexpr uint16_t kLargeObjectSizeInHeader = 0; |
| constexpr uint16_t kGcInfoIndexForFreeListHeader = 0; |
| constexpr size_t kNonLargeObjectPageSizeMax = 1 << kBlinkPageSizeLog2; |
| |
| static_assert(kHeaderGCInfoSize == GCInfoTable::kMaxIndex, |
| "GCInfoTable size and and header GCInfo index size must match"); |
| |
| static_assert( |
| kNonLargeObjectPageSizeMax >= kBlinkPageSize, |
| "max size supported by HeapObjectHeader must at least be kBlinkPageSize"); |
| |
| namespace internal { |
| |
| NO_SANITIZE_ADDRESS constexpr uint16_t EncodeSize(size_t size) { |
| // Essentially, gets optimized to >> 1. |
| return static_cast<uint16_t>((size << kHeaderSizeShift) / |
| kAllocationGranularity); |
| } |
| |
| NO_SANITIZE_ADDRESS constexpr size_t DecodeSize(uint16_t encoded) { |
| // Essentially, gets optimized to << 1. |
| return ((encoded & kHeaderSizeMask) >> kHeaderSizeShift) * |
| kAllocationGranularity; |
| } |
| |
| } // namespace internal |
| |
| class PLATFORM_EXPORT HeapObjectHeader { |
| DISALLOW_NEW(); |
| |
| public: |
| enum class AccessMode : uint8_t { kNonAtomic, kAtomic }; |
| |
| static HeapObjectHeader* FromPayload(const void*); |
| template <AccessMode = AccessMode::kNonAtomic> |
| static HeapObjectHeader* FromInnerAddress(const void*); |
| |
| // Checks sanity of the header given a payload pointer. |
| static void CheckFromPayload(const void*); |
| |
| // If |gc_info_index| is 0, this header is interpreted as a free list header. |
| HeapObjectHeader(size_t, size_t); |
| |
| template <AccessMode mode = AccessMode::kNonAtomic> |
| NO_SANITIZE_ADDRESS bool IsFree() const { |
| return GcInfoIndex<mode>() == kGcInfoIndexForFreeListHeader; |
| } |
| |
| template <AccessMode mode = AccessMode::kNonAtomic> |
| NO_SANITIZE_ADDRESS uint32_t GcInfoIndex() const { |
| const uint16_t encoded = |
| LoadEncoded<mode, EncodedHalf::kHigh, std::memory_order_acquire>(); |
| return (encoded & kHeaderGCInfoIndexMask) >> kHeaderGCInfoIndexShift; |
| } |
| |
| template <AccessMode = AccessMode::kNonAtomic> |
| size_t size() const; |
| void SetSize(size_t size); |
| |
| template <AccessMode = AccessMode::kNonAtomic> |
| bool IsLargeObject() const; |
| |
| template <AccessMode = AccessMode::kNonAtomic> |
| bool IsMarked() const; |
| template <AccessMode = AccessMode::kNonAtomic> |
| void Unmark(); |
| template <AccessMode = AccessMode::kNonAtomic> |
| bool TryMark(); |
| |
| template <AccessMode = AccessMode::kNonAtomic> |
| bool IsOld() const; |
| |
| template <AccessMode = AccessMode::kNonAtomic> |
| bool IsInConstruction() const; |
| template <AccessMode = AccessMode::kNonAtomic> |
| void MarkFullyConstructed(); |
| |
| // The payload starts directly after the HeapObjectHeader, and the payload |
| // size does not include the sizeof(HeapObjectHeader). |
| Address Payload() const; |
| size_t PayloadSize() const; |
| template <AccessMode = AccessMode::kNonAtomic> |
| Address PayloadEnd() const; |
| |
| void Finalize(Address, size_t); |
| |
| // Returns true if object has finalizer. |
| bool HasNonTrivialFinalizer() const; |
| |
| // Returns a human-readable name of this object. |
| const char* Name() const; |
| |
| private: |
| enum class EncodedHalf : uint8_t { kLow, kHigh }; |
| |
| template <AccessMode, |
| EncodedHalf part, |
| std::memory_order = std::memory_order_seq_cst> |
| uint16_t LoadEncoded() const; |
| template <AccessMode mode, |
| EncodedHalf part, |
| std::memory_order = std::memory_order_seq_cst> |
| void StoreEncoded(uint16_t bits, uint16_t mask); |
| |
| #if defined(ARCH_CPU_64_BITS) |
| uint32_t padding_ = 0; |
| #endif // defined(ARCH_CPU_64_BITS) |
| uint16_t encoded_high_; |
| uint16_t encoded_low_; |
| }; |
| |
| class FreeListEntry final : public HeapObjectHeader { |
| public: |
| NO_SANITIZE_ADDRESS |
| explicit FreeListEntry(size_t size) |
| : HeapObjectHeader(size, kGcInfoIndexForFreeListHeader), next_(nullptr) {} |
| |
| Address GetAddress() { return reinterpret_cast<Address>(this); } |
| |
| NO_SANITIZE_ADDRESS |
| void Unlink(FreeListEntry** previous_next) { |
| *previous_next = next_; |
| next_ = nullptr; |
| } |
| |
| NO_SANITIZE_ADDRESS |
| void Link(FreeListEntry** previous_next) { |
| next_ = *previous_next; |
| *previous_next = this; |
| } |
| |
| NO_SANITIZE_ADDRESS |
| FreeListEntry* Next() const { return next_; } |
| |
| NO_SANITIZE_ADDRESS |
| void Append(FreeListEntry* next) { |
| DCHECK(!next_); |
| next_ = next; |
| } |
| |
| private: |
| FreeListEntry* next_; |
| |
| friend class FreeList; |
| }; |
| |
| class FreeList { |
| DISALLOW_NEW(); |
| |
| public: |
| // Returns a bucket number for inserting a |FreeListEntry| of a given size. |
| // All entries in the given bucket, n, have size >= 2^n. |
| static int BucketIndexForSize(size_t); |
| |
| #if DCHECK_IS_ON() || defined(LEAK_SANITIZER) || defined(ADDRESS_SANITIZER) || \ |
| defined(MEMORY_SANITIZER) |
| static void GetAllowedAndForbiddenCounts(Address, size_t, size_t&, size_t&); |
| static void ZapFreedMemory(Address, size_t); |
| static void CheckFreedMemoryIsZapped(Address, size_t); |
| #endif |
| |
| FreeList(); |
| |
| FreeListEntry* Allocate(size_t); |
| void Add(Address, size_t); |
| void MoveFrom(FreeList*); |
| void Clear(); |
| |
| bool IsEmpty() const; |
| size_t FreeListSize() const; |
| |
| void CollectStatistics(ThreadState::Statistics::FreeListStatistics*); |
| |
| template <typename Predicate> |
| FreeListEntry* FindEntry(Predicate pred) { |
| for (size_t i = 0; i < kBlinkPageSizeLog2; ++i) { |
| for (FreeListEntry* entry = free_list_heads_[i]; entry; |
| entry = entry->Next()) { |
| if (pred(entry)) { |
| return entry; |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| private: |
| bool IsConsistent(size_t index) const { |
| return (!free_list_heads_[index] && !free_list_tails_[index]) || |
| (free_list_heads_[index] && free_list_tails_[index] && |
| !free_list_tails_[index]->Next()); |
| } |
| |
| // All |FreeListEntry|s in the nth list have size >= 2^n. |
| FreeListEntry* free_list_heads_[kBlinkPageSizeLog2]; |
| FreeListEntry* free_list_tails_[kBlinkPageSizeLog2]; |
| int biggest_free_list_index_; |
| }; |
| |
| // Blink heap pages are set up with a guard page before and after the payload. |
| PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t |
| BlinkPagePayloadSize() { |
| return kBlinkPageSize - 2 * BlinkGuardPageSize(); |
| } |
| |
| // Blink heap pages are aligned to the Blink heap page size. Therefore, the |
| // start of a Blink page can be obtained by rounding down to the Blink page |
| // size. |
| inline Address RoundToBlinkPageStart(Address address) { |
| return reinterpret_cast<Address>(reinterpret_cast<uintptr_t>(address) & |
| kBlinkPageBaseMask); |
| } |
| |
| inline Address RoundToBlinkPageEnd(Address address) { |
| return reinterpret_cast<Address>(reinterpret_cast<uintptr_t>(address - 1) & |
| kBlinkPageBaseMask) + |
| kBlinkPageSize; |
| } |
| |
| // Masks an address down to the enclosing Blink page base address. |
| inline Address BlinkPageAddress(Address address) { |
| return reinterpret_cast<Address>(reinterpret_cast<uintptr_t>(address) & |
| kBlinkPageBaseMask); |
| } |
| |
| inline bool VTableInitialized(const void* object_pointer) { |
| return !!(*reinterpret_cast<const ConstAddress*>(object_pointer)); |
| } |
| |
| #if DCHECK_IS_ON() |
| |
| // Sanity check for a page header address: the address of the page header should |
| // be 1 OS page size away from being Blink page size-aligned. |
| inline bool IsPageHeaderAddress(Address address) { |
| return !((reinterpret_cast<uintptr_t>(address) & kBlinkPageOffsetMask) - |
| BlinkGuardPageSize()); |
| } |
| |
| #endif |
| |
| // |FinalizeType| specifies when finalization should take place. |
| // In case of concurrent sweeper we defer finalization to be done |
| // on the main thread. |
| enum class FinalizeType : uint8_t { kInlined, kDeferred }; |
| |
| // |SweepResult| indicates if page turned out to be empty after sweeping. |
| enum class SweepResult : uint8_t { kPageEmpty, kPageNotEmpty }; |
| |
| // |PageType| indicates whether a page is used for normal objects or whether it |
| // holds a large object. |
| enum class PageType : uint8_t { kNormalPage, kLargeObjectPage }; |
| |
| // |BasePage| is a base class for |NormalPage| and |LargeObjectPage|. |
| // |
| // - |NormalPage| is a page whose size is |kBlinkPageSize|. A |NormalPage| can |
| // contain multiple objects. An object whose size is smaller than |
| // |kLargeObjectSizeThreshold| is stored in a |NormalPage|. |
| // |
| // - |LargeObjectPage| is a page that contains only one object. The object size |
| // is arbitrary. An object whose size is larger than |kBlinkPageSize| is |
| // stored as a single project in |LargeObjectPage|. |
| // |
| // Note: An object whose size is between |kLargeObjectSizeThreshold| and |
| // |kBlinkPageSize| can go to either of |NormalPage| or |LargeObjectPage|. |
| class BasePage { |
| DISALLOW_NEW(); |
| |
| public: |
| BasePage(PageMemory*, BaseArena*, PageType); |
| virtual ~BasePage() = default; |
| |
| // Virtual methods are slow. So performance-sensitive methods should be |
| // defined as non-virtual methods on |NormalPage| and |LargeObjectPage|. The |
| // following methods are not performance-sensitive. |
| virtual size_t ObjectPayloadSizeForTesting() = 0; |
| virtual void RemoveFromHeap() = 0; |
| // Sweeps a page. Returns true when that page is empty and false otherwise. |
| // Does not create free list entries for empty pages. |
| virtual bool Sweep(FinalizeType) = 0; |
| virtual void MakeConsistentForMutator() = 0; |
| virtual void Unmark() = 0; |
| |
| // Calls finalizers after sweeping is done. |
| virtual void FinalizeSweep(SweepResult) = 0; |
| |
| #if defined(ADDRESS_SANITIZER) |
| virtual void PoisonUnmarkedObjects() = 0; |
| #endif |
| |
| virtual void CollectStatistics( |
| ThreadState::Statistics::ArenaStatistics* arena_stats) = 0; |
| |
| #if DCHECK_IS_ON() |
| virtual bool Contains(ConstAddress) const = 0; |
| #endif |
| virtual size_t size() const = 0; |
| |
| Address GetAddress() const { |
| return reinterpret_cast<Address>(const_cast<BasePage*>(this)); |
| } |
| PageMemory* Storage() const { return storage_; } |
| BaseArena* Arena() const { return arena_; } |
| ThreadState* thread_state() const { return thread_state_; } |
| |
| // Returns true if this page has been swept by the ongoing sweep; false |
| // otherwise. |
| bool HasBeenSwept() const { return swept_; } |
| |
| void MarkAsSwept() { |
| DCHECK(!swept_); |
| swept_ = true; |
| } |
| |
| void MarkAsUnswept() { |
| DCHECK(swept_); |
| swept_ = false; |
| } |
| |
| // Returns true if this page is a large object page; false otherwise. |
| bool IsLargeObjectPage() const { |
| return page_type_ == PageType::kLargeObjectPage; |
| } |
| |
| // Young pages are pages that contain at least a single young object. |
| bool IsYoung() const { return is_young_; } |
| |
| void SetAsYoung(bool young) { is_young_ = young; } |
| |
| // The number of bytes that allocated on the page and are not freed yet. |
| size_t AllocatedBytes() { return allocated_bytes_; } |
| |
| void SetAllocatedBytes(size_t bytes) { allocated_bytes_ = bytes; } |
| |
| void IncreaseAllocatedBytes(size_t bytes) { allocated_bytes_ += bytes; } |
| |
| void DecreaseAllocatedBytes(size_t bytes) { |
| DCHECK_GE(allocated_bytes_, bytes); |
| allocated_bytes_ -= bytes; |
| } |
| |
| virtual void VerifyMarking() = 0; |
| |
| private: |
| void SynchronizedLoad() { |
| #if defined(THREAD_SANITIZER) |
| WTF::AsAtomicPtr(&page_type_)->load(std::memory_order_acquire); |
| #endif |
| } |
| void SynchronizedStore() { |
| std::atomic_thread_fence(std::memory_order_seq_cst); |
| #if defined(THREAD_SANITIZER) |
| WTF::AsAtomicPtr(&page_type_)->store(page_type_, std::memory_order_release); |
| #endif |
| } |
| |
| PageMemory* const storage_; |
| BaseArena* const arena_; |
| ThreadState* const thread_state_; |
| |
| // The counter is updated by the sweeper and the allocator. |
| // It is the sum of objects marked at the last GC and objects allocated on |
| // the page since the last GC. |
| size_t allocated_bytes_ = 0; |
| |
| // Track the sweeping state of a page. Set to false at the start of a sweep, |
| // true upon completion of sweeping that page. |
| bool swept_ = true; |
| bool is_young_ = false; |
| |
| PageType page_type_; |
| |
| friend class BaseArena; |
| friend class ThreadHeap; |
| }; |
| |
| class PageStack : Vector<BasePage*> { |
| using Base = Vector<BasePage*>; |
| |
| public: |
| PageStack() = default; |
| |
| void Push(BasePage* page) { push_back(page); } |
| |
| BasePage* Pop() { |
| if (IsEmpty()) |
| return nullptr; |
| BasePage* top = back(); |
| pop_back(); |
| return top; |
| } |
| |
| BasePage* Top() const { |
| if (IsEmpty()) |
| return nullptr; |
| return back(); |
| } |
| |
| using Base::begin; |
| using Base::end; |
| |
| using Base::clear; |
| using Base::erase; |
| |
| using Base::IsEmpty; |
| using Base::size; |
| }; |
| |
| class PageStackThreadSafe : public PageStack { |
| public: |
| void PushLocked(BasePage* page) { |
| WTF::MutexLocker locker(mutex_); |
| Push(page); |
| } |
| |
| BasePage* PopLocked() { |
| WTF::MutexLocker locker(mutex_); |
| return Pop(); |
| } |
| |
| bool IsEmptyLocked() const { |
| WTF::MutexLocker locker(mutex_); |
| return IsEmpty(); |
| } |
| |
| // Explicit unsafe move assignment. |
| void MoveFrom(PageStack&& other) { PageStack::operator=(std::move(other)); } |
| |
| private: |
| mutable WTF::Mutex mutex_; |
| }; |
| |
| // A bitmap for recording object starts. Objects have to be allocated at |
| // minimum granularity of kGranularity. |
| // |
| // Depends on internals such as: |
| // - kBlinkPageSize |
| // - kAllocationGranularity |
| class PLATFORM_EXPORT ObjectStartBitmap { |
| USING_FAST_MALLOC(ObjectStartBitmap); |
| |
| public: |
| // Granularity of addresses added to the bitmap. |
| static constexpr size_t Granularity() { return kAllocationGranularity; } |
| |
| // Maximum number of entries in the bitmap. |
| static constexpr size_t MaxEntries() { |
| return kReservedForBitmap * kCellSize; |
| } |
| |
| explicit ObjectStartBitmap(Address offset); |
| |
| // Finds an object header based on a |
| // address_maybe_pointing_to_the_middle_of_object. Will search for an object |
| // start in decreasing address order. |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| Address FindHeader( |
| ConstAddress address_maybe_pointing_to_the_middle_of_object) const; |
| |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| inline void SetBit(Address); |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| inline void ClearBit(Address); |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| inline bool CheckBit(Address) const; |
| |
| // Iterates all object starts recorded in the bitmap. |
| // |
| // The callback is of type |
| // void(Address) |
| // and is passed the object start address as parameter. |
| template <typename Callback> |
| inline void Iterate(Callback) const; |
| |
| // Clear the object start bitmap. |
| void Clear(); |
| |
| private: |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| void store(size_t cell_index, uint8_t value); |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| uint8_t load(size_t cell_index) const; |
| |
| static const size_t kCellSize = sizeof(uint8_t) * 8; |
| static const size_t kCellMask = sizeof(uint8_t) * 8 - 1; |
| static const size_t kBitmapSize = |
| (kBlinkPageSize + ((kCellSize * kAllocationGranularity) - 1)) / |
| (kCellSize * kAllocationGranularity); |
| static const size_t kReservedForBitmap = |
| ((kBitmapSize + kAllocationMask) & ~kAllocationMask); |
| |
| inline void ObjectStartIndexAndBit(Address, size_t*, size_t*) const; |
| |
| Address offset_; |
| // The bitmap contains a bit for every kGranularity aligned address on a |
| // a NormalPage, i.e., for a page of size kBlinkPageSize. |
| uint8_t object_start_bit_map_[kReservedForBitmap]; |
| }; |
| |
| // A platform aware version of ObjectStartBitmap to provide platform specific |
| // optimizations (e.g. Use non-atomic stores on ARMv7 when not marking). |
| class PLATFORM_EXPORT PlatformAwareObjectStartBitmap |
| : public ObjectStartBitmap { |
| USING_FAST_MALLOC(PlatformAwareObjectStartBitmap); |
| |
| public: |
| explicit PlatformAwareObjectStartBitmap(Address offset); |
| |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| inline void SetBit(Address); |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| inline void ClearBit(Address); |
| |
| private: |
| template <HeapObjectHeader::AccessMode> |
| static bool ShouldForceNonAtomic(); |
| }; |
| |
| class PLATFORM_EXPORT NormalPage final : public BasePage { |
| public: |
| NormalPage(PageMemory*, BaseArena*); |
| ~NormalPage() override; |
| |
| Address Payload() const { return GetAddress() + PageHeaderSize(); } |
| static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t |
| PayloadSize() { |
| return (BlinkPagePayloadSize() - PageHeaderSize()) & ~kAllocationMask; |
| } |
| Address PayloadEnd() const { return Payload() + PayloadSize(); } |
| bool ContainedInObjectPayload(ConstAddress address) const { |
| return Payload() <= address && address < PayloadEnd(); |
| } |
| |
| size_t ObjectPayloadSizeForTesting() override; |
| void RemoveFromHeap() override; |
| bool Sweep(FinalizeType) override; |
| void MakeConsistentForMutator() override; |
| void Unmark() override; |
| void FinalizeSweep(SweepResult) override; |
| #if defined(ADDRESS_SANITIZER) |
| void PoisonUnmarkedObjects() override; |
| #endif |
| |
| void CollectStatistics( |
| ThreadState::Statistics::ArenaStatistics* arena_stats) override; |
| |
| #if DCHECK_IS_ON() |
| // Returns true for the whole |kBlinkPageSize| page that the page is on, even |
| // for the header, and the unmapped guard page at the start. That ensures the |
| // result can be used to populate the negative page cache. |
| bool Contains(ConstAddress) const override; |
| #endif |
| size_t size() const override { return kBlinkPageSize; } |
| static constexpr size_t PageHeaderSize() { |
| // Compute the amount of padding we have to add to a header to make the size |
| // of the header plus the padding a multiple of 8 bytes. |
| constexpr size_t kPaddingSize = |
| (sizeof(NormalPage) + kAllocationGranularity - |
| (sizeof(HeapObjectHeader) % kAllocationGranularity)) % |
| kAllocationGranularity; |
| return sizeof(NormalPage) + kPaddingSize; |
| } |
| |
| inline NormalPageArena* ArenaForNormalPage() const; |
| |
| // Context object holding the state of the arena page compaction pass, passed |
| // in when compacting individual pages. |
| class CompactionContext { |
| STACK_ALLOCATED(); |
| |
| public: |
| // Page compacting into. |
| NormalPage* current_page_ = nullptr; |
| // Offset into |current_page_| to the next free address. |
| size_t allocation_point_ = 0; |
| // Vector of available pages to use for compaction. Page compaction picks |
| // the next one when the current one is exhausted. |
| PageStack available_pages_; |
| // Vector of pages that have been compacted. Page compaction will add |
| // compacted pages once the current one becomes exhausted. |
| PageStack* compacted_pages_; |
| }; |
| |
| void SweepAndCompact(CompactionContext&); |
| |
| // Object start bitmap of this page. |
| PlatformAwareObjectStartBitmap* object_start_bit_map() { |
| return &object_start_bit_map_; |
| } |
| const PlatformAwareObjectStartBitmap* object_start_bit_map() const { |
| return &object_start_bit_map_; |
| } |
| |
| // Verifies that the object start bitmap only contains a bit iff the object |
| // is also reachable through iteration on the page. |
| void VerifyObjectStartBitmapIsConsistentWithPayload(); |
| |
| // Uses the object_start_bit_map_ to find an object for a given address. The |
| // returned header is either nullptr, indicating that no object could be |
| // found, or it is pointing to valid object or free list entry. |
| // This method is called only during stack scanning when there are no |
| // concurrent markers, thus no atomics required. |
| HeapObjectHeader* ConservativelyFindHeaderFromAddress(ConstAddress) const; |
| |
| // Uses the object_start_bit_map_ to find an object for a given address. It is |
| // assumed that the address points into a valid heap object. Use the |
| // conservative version if that assumption does not hold. |
| template < |
| HeapObjectHeader::AccessMode = HeapObjectHeader::AccessMode::kNonAtomic> |
| HeapObjectHeader* FindHeaderFromAddress(ConstAddress) const; |
| |
| void VerifyMarking() override; |
| |
| // Marks a card corresponding to address. |
| void MarkCard(Address address); |
| |
| // Iterates over all objects in marked cards. |
| template <typename Function> |
| void IterateCardTable(Function function) const; |
| |
| // Clears all bits in the card table. |
| void ClearCardTable() { card_table_.Clear(); } |
| |
| private: |
| // Data structure that divides a page in a number of cards each of 512 bytes |
| // size. Marked cards are stored in bytes, not bits, to make write barrier |
| // faster and reduce chances of false sharing. This gives only ~0.1% of memory |
| // overhead. Also, since there are guard pages before and after a Blink page, |
| // some of card bits are wasted and unneeded. |
| class CardTable final { |
| public: |
| struct value_type { |
| uint8_t bit; |
| size_t index; |
| }; |
| |
| struct iterator { |
| iterator& operator++() { |
| ++index; |
| return *this; |
| } |
| value_type operator*() const { return {table->table_[index], index}; } |
| bool operator!=(iterator other) const { |
| return table != other.table || index != other.index; |
| } |
| |
| size_t index = 0; |
| const CardTable* table = nullptr; |
| }; |
| |
| using const_iterator = iterator; |
| |
| static constexpr size_t kBitsPerCard = 9; |
| static constexpr size_t kCardSize = 1 << kBitsPerCard; |
| |
| const_iterator begin() const { return {FirstPayloadCard(), this}; } |
| const_iterator end() const { return {LastPayloadCard(), this}; } |
| |
| void Mark(size_t card) { |
| DCHECK_LE(FirstPayloadCard(), card); |
| DCHECK_GT(LastPayloadCard(), card); |
| table_[card] = 1; |
| } |
| |
| bool IsMarked(size_t card) const { |
| DCHECK_LE(FirstPayloadCard(), card); |
| DCHECK_GT(LastPayloadCard(), card); |
| return table_[card]; |
| } |
| |
| void Clear() { std::fill(table_.begin(), table_.end(), 0); } |
| |
| private: |
| static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t |
| FirstPayloadCard() { |
| return (BlinkGuardPageSize() + NormalPage::PageHeaderSize()) / kCardSize; |
| } |
| static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t |
| LastPayloadCard() { |
| return (BlinkGuardPageSize() + BlinkPagePayloadSize()) / kCardSize; |
| } |
| |
| std::array<uint8_t, kBlinkPageSize / kCardSize> table_{}; |
| }; |
| |
| struct ToBeFinalizedObject { |
| HeapObjectHeader* header; |
| void Finalize(); |
| }; |
| struct FutureFreelistEntry { |
| Address start; |
| size_t size; |
| }; |
| |
| template <typename Function> |
| void IterateOnCard(Function function, size_t card_number) const; |
| |
| void MergeFreeLists(); |
| void AddToFreeList(Address start, |
| size_t size, |
| FinalizeType finalize_type, |
| bool found_finalizer); |
| |
| CardTable card_table_; |
| PlatformAwareObjectStartBitmap object_start_bit_map_; |
| #if BUILDFLAG(BLINK_HEAP_YOUNG_GENERATION) |
| std::unique_ptr<PlatformAwareObjectStartBitmap> cached_object_start_bit_map_; |
| #endif |
| Vector<ToBeFinalizedObject> to_be_finalized_objects_; |
| FreeList cached_freelist_; |
| Vector<FutureFreelistEntry> unfinalized_freelist_; |
| |
| friend class CardTableTest; |
| }; |
| |
| // Large allocations are allocated as separate objects and linked in a list. |
| // |
| // In order to use the same memory allocation routines for everything allocated |
| // in the heap, large objects are considered heap pages containing only one |
| // object. |
| class PLATFORM_EXPORT LargeObjectPage final : public BasePage { |
| public: |
| static size_t PageHeaderSize() { |
| // Compute the amount of padding we have to add to a header to make the size |
| // of the header plus the padding a multiple of 8 bytes. |
| size_t padding_size = |
| (sizeof(LargeObjectPage) + kAllocationGranularity - |
| (sizeof(HeapObjectHeader) % kAllocationGranularity)) % |
| kAllocationGranularity; |
| return sizeof(LargeObjectPage) + padding_size; |
| } |
| |
| LargeObjectPage(PageMemory*, BaseArena*, size_t); |
| |
| // LargeObjectPage has the following memory layout: |
| // this -> +------------------+ |
| // | Header | PageHeaderSize() |
| // ObjectHeader() -> +------------------+ |
| // | HeapObjectHeader | sizeof(HeapObjectHeader) |
| // Payload() -> +------------------+ |
| // | Object payload | PayloadSize() |
| // | | |
| // PayloadEnd() -> +------------------+ |
| // |
| // ObjectSize(): PayloadSize() + sizeof(HeapObjectHeader) |
| // size(): ObjectSize() + PageHeaderSize() |
| |
| HeapObjectHeader* ObjectHeader() const { |
| Address header_address = GetAddress() + PageHeaderSize(); |
| return reinterpret_cast<HeapObjectHeader*>(header_address); |
| } |
| |
| // Returns the size of the page that is allocatable for objects. This differs |
| // from PayloadSize() as it also includes the HeapObjectHeader. |
| size_t ObjectSize() const { return object_size_; } |
| |
| // Returns the size of the page including the header. |
| size_t size() const override { return PageHeaderSize() + object_size_; } |
| |
| // Returns the payload start of the underlying object. |
| Address Payload() const { return ObjectHeader()->Payload(); } |
| |
| // Returns the payload size of the underlying object. |
| size_t PayloadSize() const { return object_size_ - sizeof(HeapObjectHeader); } |
| |
| // Points to the payload end of the underlying object. |
| Address PayloadEnd() const { return Payload() + PayloadSize(); } |
| |
| bool ContainedInObjectPayload(ConstAddress address) const { |
| return Payload() <= address && address < PayloadEnd(); |
| } |
| |
| size_t ObjectPayloadSizeForTesting() override; |
| void RemoveFromHeap() override; |
| bool Sweep(FinalizeType) override; |
| void MakeConsistentForMutator() override; |
| void Unmark() override; |
| void FinalizeSweep(SweepResult) override; |
| |
| void CollectStatistics( |
| ThreadState::Statistics::ArenaStatistics* arena_stats) override; |
| |
| void VerifyMarking() override; |
| |
| #if defined(ADDRESS_SANITIZER) |
| void PoisonUnmarkedObjects() override; |
| #endif |
| |
| #if DCHECK_IS_ON() |
| // Returns true for any address that is on one of the pages that this large |
| // object uses. That ensures that we can use a negative result to populate the |
| // negative page cache. |
| bool Contains(ConstAddress) const override; |
| #endif |
| |
| #ifdef ANNOTATE_CONTIGUOUS_CONTAINER |
| void SetIsVectorBackingPage() { is_vector_backing_page_ = true; } |
| bool IsVectorBackingPage() const { return is_vector_backing_page_; } |
| #endif |
| |
| // Remembers the page as containing inter-generational pointers. |
| void SetRemembered(bool remembered) { is_remembered_ = remembered; } |
| bool IsRemembered() const { return is_remembered_; } |
| |
| private: |
| // The size of the underlying object including HeapObjectHeader. |
| size_t object_size_; |
| #ifdef ANNOTATE_CONTIGUOUS_CONTAINER |
| bool is_vector_backing_page_; |
| #endif |
| bool is_remembered_ = false; |
| }; |
| |
| // Each thread has a number of thread arenas (e.g., Generic arenas, typed arenas |
| // for |Node|, arenas for collection backings, etc.) and |BaseArena| represents |
| // each thread arena. |
| // |
| // |BaseArena| is a parent class of |NormalPageArena| and |LargeObjectArena|. |
| // |NormalPageArena| represents a part of a heap that contains |NormalPage|s, |
| // and |LargeObjectArena| represents a part of a heap that contains |
| // |LargeObjectPage|s. |
| class PLATFORM_EXPORT BaseArena { |
| USING_FAST_MALLOC(BaseArena); |
| |
| public: |
| BaseArena(ThreadState*, int); |
| virtual ~BaseArena(); |
| void RemoveAllPages(); |
| |
| void CollectStatistics(std::string, ThreadState::Statistics*); |
| virtual void CollectFreeListStatistics( |
| ThreadState::Statistics::FreeListStatistics*) {} |
| size_t AllocatedBytes(); |
| |
| #if DCHECK_IS_ON() |
| BasePage* FindPageFromAddress(ConstAddress) const; |
| #endif |
| virtual void ClearFreeLists() {} |
| virtual void MakeIterable() {} |
| virtual void MakeConsistentForGC(); |
| void MakeConsistentForMutator(); |
| void Unmark(); |
| #if DCHECK_IS_ON() |
| virtual bool IsConsistentForGC() = 0; |
| #endif |
| size_t ObjectPayloadSizeForTesting(); |
| void PrepareForSweep(BlinkGC::CollectionType); |
| #if defined(ADDRESS_SANITIZER) |
| void PoisonUnmarkedObjects(); |
| #endif |
| Address LazySweep(size_t, size_t gc_info_index); |
| bool SweepUnsweptPage(BasePage*); |
| bool SweepUnsweptPageOnConcurrentThread(BasePage*); |
| // Returns true if we have swept all pages within the deadline. Returns false |
| // otherwise. |
| bool LazySweepWithDeadline(base::TimeTicks deadline); |
| // Returns true if the arena has been fully swept. |
| bool ConcurrentSweepOnePage(); |
| void CompleteSweep(); |
| void InvokeFinalizersOnSweptPages(); |
| |
| ThreadState* GetThreadState() { return thread_state_; } |
| int ArenaIndex() const { return index_; } |
| |
| Address AllocateLargeObject(size_t allocation_size, size_t gc_info_index); |
| |
| // Resets the allocation point if it exists for an arena. |
| virtual void ResetAllocationPoint() {} |
| |
| void VerifyMarking(); |
| virtual void VerifyObjectStartBitmap() {} |
| |
| protected: |
| bool SweepingCompleted() const { return unswept_pages_.IsEmptyLocked(); } |
| bool SweepingAndFinalizationCompleted() const { |
| return unswept_pages_.IsEmptyLocked() && |
| swept_unfinalized_pages_.IsEmptyLocked() && |
| swept_unfinalized_empty_pages_.IsEmptyLocked(); |
| } |
| |
| // Pages for allocation. |
| PageStackThreadSafe swept_pages_; |
| // Pages that are being swept. |
| PageStackThreadSafe unswept_pages_; |
| // Pages that have been swept but contain unfinalized objects. |
| PageStackThreadSafe swept_unfinalized_pages_; |
| // Pages that have been swept and need to be removed from the heap. |
| PageStackThreadSafe swept_unfinalized_empty_pages_; |
| |
| protected: |
| void SynchronizedStore(BasePage* page) { page->SynchronizedStore(); } |
| |
| private: |
| virtual Address LazySweepPages(size_t, size_t gc_info_index) = 0; |
| |
| ThreadState* thread_state_; |
| |
| // Index into the page pools. This is used to ensure that the pages of the |
| // same type go into the correct page pool and thus avoid type confusion. |
| // |
| // TODO(palmer): Should this be size_t? |
| int index_; |
| }; |
| |
| class PLATFORM_EXPORT NormalPageArena final : public BaseArena { |
| public: |
| NormalPageArena(ThreadState*, int index); |
| void AddToFreeList(Address address, size_t size); |
| void AddToFreeList(FreeList* other) { free_list_.MoveFrom(other); } |
| void ClearFreeLists() override; |
| void CollectFreeListStatistics( |
| ThreadState::Statistics::FreeListStatistics*) override; |
| void MakeIterable() override; |
| |
| #if DCHECK_IS_ON() |
| bool IsConsistentForGC() override; |
| bool PagesToBeSweptContains(ConstAddress) const; |
| #endif |
| |
| Address AllocateObject(size_t allocation_size, size_t gc_info_index); |
| |
| void FreePage(NormalPage*); |
| |
| void PromptlyFreeObject(HeapObjectHeader*); |
| void PromptlyFreeObjectInFreeList(HeapObjectHeader*, size_t); |
| bool ExpandObject(HeapObjectHeader*, size_t); |
| bool ShrinkObject(HeapObjectHeader*, size_t); |
| size_t promptly_freed_size() const { return promptly_freed_size_; } |
| |
| bool IsObjectAllocatedAtAllocationPoint(HeapObjectHeader* header) { |
| return header->PayloadEnd() == current_allocation_point_; |
| } |
| |
| size_t ArenaSize(); |
| size_t FreeListSize(); |
| |
| void SweepAndCompact(); |
| |
| void ResetAllocationPoint() override { SetAllocationPoint(nullptr, 0); } |
| |
| void VerifyObjectStartBitmap() override; |
| |
| Address CurrentAllocationPoint() const { return current_allocation_point_; } |
| |
| bool IsInCurrentAllocationPointRegion(ConstAddress address) const { |
| return HasCurrentAllocationArea() && |
| (CurrentAllocationPoint() <= address) && |
| (address < (CurrentAllocationPoint() + RemainingAllocationSize())); |
| } |
| |
| size_t RemainingAllocationSize() const { return remaining_allocation_size_; } |
| |
| void MakeConsistentForGC() override; |
| |
| template <typename Function> |
| void IterateAndClearCardTables(Function function); |
| |
| private: |
| void AllocatePage(); |
| |
| // OutOfLineAllocate represent the slow-path allocation. The suffixed version |
| // contains just allocation code while the other version also invokes a |
| // safepoint where allocated bytes are reported to observers. |
| Address OutOfLineAllocate(size_t allocation_size, size_t gc_info_index); |
| Address OutOfLineAllocateImpl(size_t allocation_size, size_t gc_info_index); |
| |
| Address AllocateFromFreeList(size_t, size_t gc_info_index); |
| |
| Address LazySweepPages(size_t, size_t gc_info_index) override; |
| |
| bool HasCurrentAllocationArea() const { |
| return CurrentAllocationPoint() && RemainingAllocationSize(); |
| } |
| void SetAllocationPoint(Address, size_t); |
| |
| FreeList free_list_; |
| Address current_allocation_point_; |
| size_t remaining_allocation_size_; |
| |
| // The size of promptly freed objects in the heap. This counter is set to |
| // zero before sweeping when clearing the free list and after coalescing. |
| // It will increase for promptly freed objects on already swept pages. |
| size_t promptly_freed_size_; |
| }; |
| |
| class LargeObjectArena final : public BaseArena { |
| public: |
| LargeObjectArena(ThreadState*, int index); |
| Address AllocateLargeObjectPage(size_t, size_t gc_info_index); |
| void FreeLargeObjectPage(LargeObjectPage*); |
| |
| #if DCHECK_IS_ON() |
| bool IsConsistentForGC() override { return true; } |
| #endif |
| |
| template <typename Function> |
| void IterateAndClearRememberedPages(Function function); |
| |
| private: |
| Address DoAllocateLargeObjectPage(size_t, size_t gc_info_index); |
| Address LazySweepPages(size_t, size_t gc_info_index) override; |
| }; |
| |
| // Mask an address down to the enclosing Oilpan heap base page. All Oilpan heap |
| // pages are aligned at |kBlinkPageBase| plus the size of a guard page. This |
| // will work only for 1) a pointer pointing to a non-large object and 2) a |
| // pointer pointing to the beginning of a large object. |
| // |
| // FIXME: Remove PLATFORM_EXPORT once we get a proper public interface to our |
| // typed arenas. This is only exported to enable tests in HeapTest.cpp. |
| PLATFORM_EXPORT ALWAYS_INLINE BasePage* PageFromObject(const void* object) { |
| Address address = reinterpret_cast<Address>(const_cast<void*>(object)); |
| BasePage* page = reinterpret_cast<BasePage*>(BlinkPageAddress(address) + |
| BlinkGuardPageSize()); |
| #if DCHECK_IS_ON() |
| DCHECK(page->Contains(address)); |
| #endif |
| return page; |
| } |
| |
| inline HeapObjectHeader* HeapObjectHeader::FromPayload(const void* payload) { |
| Address addr = reinterpret_cast<Address>(const_cast<void*>(payload)); |
| HeapObjectHeader* header = |
| reinterpret_cast<HeapObjectHeader*>(addr - sizeof(HeapObjectHeader)); |
| return header; |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline HeapObjectHeader* HeapObjectHeader::FromInnerAddress( |
| const void* address) { |
| BasePage* const page = PageFromObject(address); |
| return page->IsLargeObjectPage() |
| ? static_cast<LargeObjectPage*>(page)->ObjectHeader() |
| : static_cast<NormalPage*>(page)->FindHeaderFromAddress<mode>( |
| reinterpret_cast<ConstAddress>(address)); |
| } |
| |
| inline void HeapObjectHeader::CheckFromPayload(const void* payload) { |
| (void)FromPayload(payload); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| NO_SANITIZE_ADDRESS inline size_t HeapObjectHeader::size() const { |
| // Size is immutable after construction while either marking or sweeping |
| // is running so relaxed load (if mode == kAtomic) is enough. |
| uint16_t encoded_low_value = |
| LoadEncoded<mode, EncodedHalf::kLow, std::memory_order_relaxed>(); |
| const size_t result = internal::DecodeSize(encoded_low_value); |
| // Large objects should not refer to header->size() but use |
| // LargeObjectPage::PayloadSize(). |
| DCHECK(result != kLargeObjectSizeInHeader); |
| DCHECK(!PageFromObject(this)->IsLargeObjectPage()); |
| return result; |
| } |
| |
| NO_SANITIZE_ADDRESS inline void HeapObjectHeader::SetSize(size_t size) { |
| DCHECK(!PageFromObject(Payload())->thread_state()->IsIncrementalMarking()); |
| DCHECK_LT(size, kNonLargeObjectPageSizeMax); |
| encoded_low_ = static_cast<uint16_t>(internal::EncodeSize(size) | |
| (encoded_low_ & ~kHeaderSizeMask)); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| NO_SANITIZE_ADDRESS inline bool HeapObjectHeader::IsLargeObject() const { |
| uint16_t encoded_low_value = |
| LoadEncoded<mode, EncodedHalf::kLow, std::memory_order_relaxed>(); |
| return internal::DecodeSize(encoded_low_value) == kLargeObjectSizeInHeader; |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| NO_SANITIZE_ADDRESS inline bool HeapObjectHeader::IsInConstruction() const { |
| return (LoadEncoded<mode, EncodedHalf::kHigh, std::memory_order_acquire>() & |
| kHeaderIsInConstructionMask) == 0; |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| NO_SANITIZE_ADDRESS inline void HeapObjectHeader::MarkFullyConstructed() { |
| DCHECK(IsInConstruction()); |
| StoreEncoded<mode, EncodedHalf::kHigh, std::memory_order_release>( |
| kHeaderIsInConstructionMask, kHeaderIsInConstructionMask); |
| } |
| |
| inline Address HeapObjectHeader::Payload() const { |
| return reinterpret_cast<Address>(const_cast<HeapObjectHeader*>(this)) + |
| sizeof(HeapObjectHeader); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline Address HeapObjectHeader::PayloadEnd() const { |
| return reinterpret_cast<Address>(const_cast<HeapObjectHeader*>(this)) + |
| size<mode>(); |
| } |
| |
| NO_SANITIZE_ADDRESS inline size_t HeapObjectHeader::PayloadSize() const { |
| const size_t size = internal::DecodeSize(encoded_low_); |
| if (UNLIKELY(size == kLargeObjectSizeInHeader)) { |
| DCHECK(PageFromObject(this)->IsLargeObjectPage()); |
| return static_cast<LargeObjectPage*>(PageFromObject(this))->PayloadSize(); |
| } |
| DCHECK(!PageFromObject(this)->IsLargeObjectPage()); |
| return size - sizeof(HeapObjectHeader); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| NO_SANITIZE_ADDRESS inline bool HeapObjectHeader::IsMarked() const { |
| const uint16_t encoded = |
| LoadEncoded<mode, EncodedHalf::kLow, std::memory_order_relaxed>(); |
| return encoded & kHeaderMarkBitMask; |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| NO_SANITIZE_ADDRESS inline bool HeapObjectHeader::IsOld() const { |
| // Oilpan uses the sticky-mark-bits technique to encode old objects. |
| return IsMarked<mode>(); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| NO_SANITIZE_ADDRESS inline void HeapObjectHeader::Unmark() { |
| DCHECK(IsMarked<mode>()); |
| StoreEncoded<mode, EncodedHalf::kLow, std::memory_order_relaxed>( |
| 0u, kHeaderMarkBitMask); |
| } |
| |
| // The function relies on size bits being unmodified when the function is |
| // called, i.e. SetSize() and TryMark() can't be called concurrently. |
| template <HeapObjectHeader::AccessMode mode> |
| NO_SANITIZE_ADDRESS inline bool HeapObjectHeader::TryMark() { |
| if (mode == AccessMode::kNonAtomic) { |
| if (encoded_low_ & kHeaderMarkBitMask) |
| return false; |
| encoded_low_ |= kHeaderMarkBitMask; |
| return true; |
| } |
| auto* atomic_encoded = internal::AsUnsanitizedAtomic(&encoded_low_); |
| uint16_t old_value = atomic_encoded->load(std::memory_order_relaxed); |
| if (old_value & kHeaderMarkBitMask) |
| return false; |
| const uint16_t new_value = old_value | kHeaderMarkBitMask; |
| return atomic_encoded->compare_exchange_strong(old_value, new_value, |
| std::memory_order_relaxed); |
| } |
| |
| inline Address NormalPageArena::AllocateObject(size_t allocation_size, |
| size_t gc_info_index) { |
| if (LIKELY(allocation_size <= remaining_allocation_size_)) { |
| Address header_address = current_allocation_point_; |
| current_allocation_point_ += allocation_size; |
| remaining_allocation_size_ -= allocation_size; |
| DCHECK_GT(gc_info_index, 0u); |
| new (NotNull, header_address) |
| HeapObjectHeader(allocation_size, gc_info_index); |
| DCHECK(!PageFromObject(header_address)->IsLargeObjectPage()); |
| static_cast<NormalPage*>(PageFromObject(header_address)) |
| ->object_start_bit_map() |
| ->SetBit<HeapObjectHeader::AccessMode::kAtomic>(header_address); |
| Address result = header_address + sizeof(HeapObjectHeader); |
| DCHECK(!(reinterpret_cast<uintptr_t>(result) & kAllocationMask)); |
| |
| SET_MEMORY_ACCESSIBLE(result, allocation_size - sizeof(HeapObjectHeader)); |
| #if DCHECK_IS_ON() |
| DCHECK(FindPageFromAddress(header_address + allocation_size - 1)); |
| #endif |
| return result; |
| } |
| return OutOfLineAllocate(allocation_size, gc_info_index); |
| } |
| |
| inline NormalPageArena* NormalPage::ArenaForNormalPage() const { |
| return static_cast<NormalPageArena*>(Arena()); |
| } |
| |
| // Iterates over all card tables and clears them. |
| template <typename Function> |
| inline void NormalPageArena::IterateAndClearCardTables(Function function) { |
| for (BasePage* page : swept_pages_) { |
| auto* normal_page = static_cast<NormalPage*>(page); |
| normal_page->IterateCardTable(function); |
| normal_page->ClearCardTable(); |
| } |
| } |
| |
| // Iterates over all pages that may contain inter-generational pointers. |
| template <typename Function> |
| inline void LargeObjectArena::IterateAndClearRememberedPages( |
| Function function) { |
| for (BasePage* page : swept_pages_) { |
| auto* large_page = static_cast<LargeObjectPage*>(page); |
| if (large_page->IsRemembered()) { |
| function(large_page->ObjectHeader()); |
| large_page->SetRemembered(false); |
| } |
| } |
| } |
| |
| // static |
| template <HeapObjectHeader::AccessMode mode> |
| bool PlatformAwareObjectStartBitmap::ShouldForceNonAtomic() { |
| #if defined(ARCH_CPU_ARMEL) |
| // Use non-atomic accesses on ARMv7 when marking is not active. |
| if (mode == HeapObjectHeader::AccessMode::kAtomic) { |
| if (LIKELY(!ThreadState::Current()->IsAnyIncrementalMarking())) |
| return true; |
| } |
| #endif // defined(ARCH_CPU_ARMEL) |
| return false; |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline void PlatformAwareObjectStartBitmap::SetBit(Address header_address) { |
| if (ShouldForceNonAtomic<mode>()) { |
| ObjectStartBitmap::SetBit<HeapObjectHeader::AccessMode::kNonAtomic>( |
| header_address); |
| return; |
| } |
| ObjectStartBitmap::SetBit<mode>(header_address); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline void PlatformAwareObjectStartBitmap::ClearBit(Address header_address) { |
| if (ShouldForceNonAtomic<mode>()) { |
| ObjectStartBitmap::ClearBit<HeapObjectHeader::AccessMode::kNonAtomic>( |
| header_address); |
| return; |
| } |
| ObjectStartBitmap::ClearBit<mode>(header_address); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline void ObjectStartBitmap::store(size_t cell_index, uint8_t value) { |
| if (mode == HeapObjectHeader::AccessMode::kNonAtomic) { |
| object_start_bit_map_[cell_index] = value; |
| return; |
| } |
| WTF::AsAtomicPtr(&object_start_bit_map_[cell_index]) |
| ->store(value, std::memory_order_release); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline uint8_t ObjectStartBitmap::load(size_t cell_index) const { |
| if (mode == HeapObjectHeader::AccessMode::kNonAtomic) { |
| return object_start_bit_map_[cell_index]; |
| } |
| return WTF::AsAtomicPtr(&object_start_bit_map_[cell_index]) |
| ->load(std::memory_order_acquire); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline void ObjectStartBitmap::SetBit(Address header_address) { |
| size_t cell_index, object_bit; |
| ObjectStartIndexAndBit(header_address, &cell_index, &object_bit); |
| // Only the mutator thread writes to the bitmap during concurrent marking, |
| // so no need for CAS here. |
| store<mode>(cell_index, |
| static_cast<uint8_t>(load(cell_index) | (1 << object_bit))); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline void ObjectStartBitmap::ClearBit(Address header_address) { |
| size_t cell_index, object_bit; |
| ObjectStartIndexAndBit(header_address, &cell_index, &object_bit); |
| store<mode>(cell_index, |
| static_cast<uint8_t>(load(cell_index) & ~(1 << object_bit))); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| inline bool ObjectStartBitmap::CheckBit(Address header_address) const { |
| size_t cell_index, object_bit; |
| ObjectStartIndexAndBit(header_address, &cell_index, &object_bit); |
| return load<mode>(cell_index) & (1 << object_bit); |
| } |
| |
| inline void ObjectStartBitmap::ObjectStartIndexAndBit(Address header_address, |
| size_t* cell_index, |
| size_t* bit) const { |
| const size_t object_offset = header_address - offset_; |
| DCHECK(!(object_offset & kAllocationMask)); |
| const size_t object_start_number = object_offset / kAllocationGranularity; |
| *cell_index = object_start_number / kCellSize; |
| #if DCHECK_IS_ON() |
| const size_t bitmap_size = kBitmapSize; |
| DCHECK_LT(*cell_index, bitmap_size); |
| #endif |
| *bit = object_start_number & kCellMask; |
| } |
| |
| template <typename Callback> |
| inline void ObjectStartBitmap::Iterate(Callback callback) const { |
| for (size_t cell_index = 0; cell_index < kReservedForBitmap; cell_index++) { |
| uint8_t value = load(cell_index); |
| while (value) { |
| const int trailing_zeroes = base::bits::CountTrailingZeroBits(value); |
| const size_t object_start_number = |
| (cell_index * kCellSize) + trailing_zeroes; |
| const Address object_address = |
| offset_ + (kAllocationGranularity * object_start_number); |
| callback(object_address); |
| // Clear current object bit in temporary value to advance iteration. |
| value &= ~(1 << (object_start_number & kCellMask)); |
| } |
| } |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| Address ObjectStartBitmap::FindHeader( |
| ConstAddress address_maybe_pointing_to_the_middle_of_object) const { |
| size_t object_offset = |
| address_maybe_pointing_to_the_middle_of_object - offset_; |
| size_t object_start_number = object_offset / kAllocationGranularity; |
| size_t cell_index = object_start_number / kCellSize; |
| #if DCHECK_IS_ON() |
| const size_t bitmap_size = kReservedForBitmap; |
| DCHECK_LT(cell_index, bitmap_size); |
| #endif |
| size_t bit = object_start_number & kCellMask; |
| uint8_t byte = load<mode>(cell_index) & ((1 << (bit + 1)) - 1); |
| while (!byte) { |
| DCHECK_LT(0u, cell_index); |
| byte = load<mode>(--cell_index); |
| } |
| int leading_zeroes = base::bits::CountLeadingZeroBits(byte); |
| object_start_number = |
| (cell_index * kCellSize) + (kCellSize - 1) - leading_zeroes; |
| object_offset = object_start_number * kAllocationGranularity; |
| return object_offset + offset_; |
| } |
| |
| NO_SANITIZE_ADDRESS inline HeapObjectHeader::HeapObjectHeader( |
| size_t size, |
| size_t gc_info_index) { |
| // sizeof(HeapObjectHeader) must be equal to or smaller than |
| // |kAllocationGranularity|, because |HeapObjectHeader| is used as a header |
| // for a freed entry. Given that the smallest entry size is |
| // |kAllocationGranurarity|, |HeapObjectHeader| must fit into the size. |
| static_assert( |
| sizeof(HeapObjectHeader) <= kAllocationGranularity, |
| "size of HeapObjectHeader must be smaller than kAllocationGranularity"); |
| |
| DCHECK_LT(gc_info_index, GCInfoTable::kMaxIndex); |
| DCHECK_LT(size, kNonLargeObjectPageSizeMax); |
| DCHECK_EQ(0u, size & kAllocationMask); |
| // Relaxed memory order is enough as in construction is created/synchronized |
| // as follows: |
| // - Page allocator gets zeroed page and uses page initialization fence. |
| // - Sweeper zeroes memory and synchronizes via global lock. |
| internal::AsUnsanitizedAtomic(&encoded_high_) |
| ->store(static_cast<uint16_t>(gc_info_index << kHeaderGCInfoIndexShift), |
| std::memory_order_relaxed); |
| encoded_low_ = internal::EncodeSize(size); |
| DCHECK(IsInConstruction()); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode, |
| HeapObjectHeader::EncodedHalf part, |
| std::memory_order memory_order> |
| NO_SANITIZE_ADDRESS inline uint16_t HeapObjectHeader::LoadEncoded() const { |
| const uint16_t& half = |
| part == EncodedHalf::kLow ? encoded_low_ : encoded_high_; |
| if (mode == AccessMode::kNonAtomic) |
| return half; |
| return internal::AsUnsanitizedAtomic(&half)->load(memory_order); |
| } |
| |
| // Sets bits selected by the mask to the given value. Please note that atomicity |
| // of the whole operation is not guaranteed. |
| template <HeapObjectHeader::AccessMode mode, |
| HeapObjectHeader::EncodedHalf part, |
| std::memory_order memory_order> |
| NO_SANITIZE_ADDRESS inline void HeapObjectHeader::StoreEncoded(uint16_t bits, |
| uint16_t mask) { |
| DCHECK_EQ(static_cast<uint16_t>(0u), bits & ~mask); |
| uint16_t& half = part == EncodedHalf::kLow ? encoded_low_ : encoded_high_; |
| if (mode == AccessMode::kNonAtomic) { |
| half = (half & ~mask) | bits; |
| return; |
| } |
| // We don't perform CAS loop here assuming that the data is constant and no |
| // one except for us can change this half concurrently. |
| auto* atomic_encoded = internal::AsUnsanitizedAtomic(&half); |
| uint16_t value = atomic_encoded->load(std::memory_order_relaxed); |
| value = (value & ~mask) | bits; |
| atomic_encoded->store(value, memory_order); |
| } |
| |
| template <HeapObjectHeader::AccessMode mode> |
| HeapObjectHeader* NormalPage::FindHeaderFromAddress( |
| ConstAddress address) const { |
| DCHECK(ContainedInObjectPayload(address)); |
| HeapObjectHeader* header = reinterpret_cast<HeapObjectHeader*>( |
| object_start_bit_map()->FindHeader<mode>(address)); |
| DCHECK_LT(0u, header->GcInfoIndex<mode>()); |
| DCHECK_GT(header->PayloadEnd<HeapObjectHeader::AccessMode::kAtomic>(), |
| address); |
| return header; |
| } |
| |
| template <typename Function> |
| void NormalPage::IterateCardTable(Function function) const { |
| // TODO(bikineev): Consider introducing a "dirty" per-page bit to avoid |
| // the loop (this may in turn pessimize barrier implementation). |
| for (auto card : card_table_) { |
| if (UNLIKELY(card.bit)) { |
| IterateOnCard(function, card.index); |
| } |
| } |
| } |
| |
| // Iterates over all objects in the specified marked card. Please note that |
| // since objects are not aligned by the card boundary, it starts from the |
| // object which may reside on a previous card. |
| template <typename Function> |
| void NormalPage::IterateOnCard(Function function, size_t card_number) const { |
| #if DCHECK_IS_ON() |
| DCHECK(card_table_.IsMarked(card_number)); |
| DCHECK(ArenaForNormalPage()->IsConsistentForGC()); |
| #endif |
| |
| const Address card_begin = RoundToBlinkPageStart(GetAddress()) + |
| (card_number << CardTable::kBitsPerCard); |
| const Address card_end = card_begin + CardTable::kCardSize; |
| // Generational barrier marks cards corresponding to slots (not source |
| // objects), therefore the potential source object may reside on a |
| // previous card. |
| HeapObjectHeader* header = reinterpret_cast<HeapObjectHeader*>( |
| card_number == card_table_.begin().index |
| ? Payload() |
| : object_start_bit_map_.FindHeader(card_begin)); |
| for (; header < reinterpret_cast<HeapObjectHeader*>(card_end); |
| reinterpret_cast<Address&>(header) += header->size()) { |
| if (!header->IsFree()) { |
| function(header); |
| } |
| } |
| } |
| |
| inline void NormalPage::MarkCard(Address address) { |
| #if DCHECK_IS_ON() |
| DCHECK(Contains(address)); |
| #endif |
| const size_t byte = reinterpret_cast<size_t>(address) & kBlinkPageOffsetMask; |
| const size_t card = byte / CardTable::kCardSize; |
| card_table_.Mark(card); |
| } |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_IMPL_HEAP_PAGE_H_ |