Update PartitionAlloc from Chromium at r629284. This merges in the following list of PartitionAlloc CLs: - https://crrev.com/600164 - https://crrev.com/608310 - https://crrev.com/609240 - https://crrev.com/610408 - https://crrev.com/611173 - https://crrev.com/613253 - https://crrev.com/629284 But skips these CLs: - https://crrev.com/614040 No base::bits::IsPowerOfTwo() in PDFium - https://crrev.com/620991 N/A - https://crrev.com/625332 N/A - https://crrev.com/627945 N/A Change-Id: I9c8baf8e81dd81f7c2d380efd333dab06b01b016 Reviewed-on: https://pdfium-review.googlesource.com/c/51150 Reviewed-by: Chris Palmer <palmer@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/third_party/base/allocator/partition_allocator/oom_callback.cc b/third_party/base/allocator/partition_allocator/oom_callback.cc index 36c6e97..9143438 100644 --- a/third_party/base/allocator/partition_allocator/oom_callback.cc +++ b/third_party/base/allocator/partition_allocator/oom_callback.cc
@@ -3,6 +3,7 @@ // found in the LICENSE file. #include "third_party/base/allocator/partition_allocator/oom_callback.h" + #include "third_party/base/logging.h" namespace pdfium {
diff --git a/third_party/base/allocator/partition_allocator/page_allocator.cc b/third_party/base/allocator/partition_allocator/page_allocator.cc index a65fbaa..76f584b 100644 --- a/third_party/base/allocator/partition_allocator/page_allocator.cc +++ b/third_party/base/allocator/partition_allocator/page_allocator.cc
@@ -200,11 +200,18 @@ FreePagesInternal(address, length); } -bool SetSystemPagesAccess(void* address, +bool TrySetSystemPagesAccess(void* address, + size_t length, + PageAccessibilityConfiguration accessibility) { + DCHECK(!(length & kSystemPageOffsetMask)); + return TrySetSystemPagesAccessInternal(address, length, accessibility); +} + +void SetSystemPagesAccess(void* address, size_t length, PageAccessibilityConfiguration accessibility) { DCHECK(!(length & kSystemPageOffsetMask)); - return SetSystemPagesAccessInternal(address, length, accessibility); + SetSystemPagesAccessInternal(address, length, accessibility); } void DecommitSystemPages(void* address, size_t length) {
diff --git a/third_party/base/allocator/partition_allocator/page_allocator.h b/third_party/base/allocator/partition_allocator/page_allocator.h index 64be33c..a48a841 100644 --- a/third_party/base/allocator/partition_allocator/page_allocator.h +++ b/third_party/base/allocator/partition_allocator/page_allocator.h
@@ -69,7 +69,16 @@ // // Returns true if the permission change succeeded. In most cases you must // |CHECK| the result. -BASE_EXPORT WARN_UNUSED_RESULT bool SetSystemPagesAccess( +BASE_EXPORT WARN_UNUSED_RESULT bool TrySetSystemPagesAccess( + void* address, + size_t length, + PageAccessibilityConfiguration page_accessibility); + +// Mark one or more system pages, starting at |address| with the given +// |page_accessibility|. |length| must be a multiple of |kSystemPageSize| bytes. +// +// Performs a CHECK that the operation succeeds. +BASE_EXPORT void SetSystemPagesAccess( void* address, size_t length, PageAccessibilityConfiguration page_accessibility);
diff --git a/third_party/base/allocator/partition_allocator/page_allocator_internals_posix.h b/third_party/base/allocator/partition_allocator/page_allocator_internals_posix.h index 0622222..4aba30c 100644 --- a/third_party/base/allocator/partition_allocator/page_allocator_internals_posix.h +++ b/third_party/base/allocator/partition_allocator/page_allocator_internals_posix.h
@@ -117,13 +117,20 @@ return ret; } -bool SetSystemPagesAccessInternal( +bool TrySetSystemPagesAccessInternal( void* address, size_t length, PageAccessibilityConfiguration accessibility) { return 0 == mprotect(address, length, GetAccessFlags(accessibility)); } +void SetSystemPagesAccessInternal( + void* address, + size_t length, + PageAccessibilityConfiguration accessibility) { + CHECK(0 == mprotect(address, length, GetAccessFlags(accessibility))); +} + void FreePagesInternal(void* address, size_t length) { CHECK(!munmap(address, length));
diff --git a/third_party/base/allocator/partition_allocator/page_allocator_internals_win.h b/third_party/base/allocator/partition_allocator/page_allocator_internals_win.h index 57a11c5..65e953a 100644 --- a/third_party/base/allocator/partition_allocator/page_allocator_internals_win.h +++ b/third_party/base/allocator/partition_allocator/page_allocator_internals_win.h
@@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_INTERNALS_WIN_H_ #define THIRD_PARTY_BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_INTERNALS_WIN_H_ +#include "third_party/base/allocator/partition_allocator/oom.h" #include "third_party/base/allocator/partition_allocator/page_allocator_internal.h" namespace pdfium { @@ -65,15 +66,36 @@ return ret; } -bool SetSystemPagesAccessInternal( +bool TrySetSystemPagesAccessInternal( + void* address, + size_t length, + PageAccessibilityConfiguration accessibility) { + if (accessibility == PageInaccessible) + return VirtualFree(address, length, MEM_DECOMMIT) != 0; + return nullptr != VirtualAlloc(address, length, MEM_COMMIT, + GetAccessFlags(accessibility)); +} + +void SetSystemPagesAccessInternal( void* address, size_t length, PageAccessibilityConfiguration accessibility) { if (accessibility == PageInaccessible) { - return VirtualFree(address, length, MEM_DECOMMIT) != 0; + if (!VirtualFree(address, length, MEM_DECOMMIT)) { + // We check `GetLastError` for `ERROR_SUCCESS` here so that in a crash + // report we get the error number. + CHECK(static_cast<uint32_t>(ERROR_SUCCESS) == GetLastError()); + } } else { - return nullptr != VirtualAlloc(address, length, MEM_COMMIT, - GetAccessFlags(accessibility)); + if (!VirtualAlloc(address, length, MEM_COMMIT, + GetAccessFlags(accessibility))) { + int32_t error = GetLastError(); + if (error == ERROR_COMMITMENT_LIMIT) + OOM_CRASH(); + // We check `GetLastError` for `ERROR_SUCCESS` here so that in a crash + // report we get the error number. + CHECK(ERROR_SUCCESS == error); + } } } @@ -82,13 +104,13 @@ } void DecommitSystemPagesInternal(void* address, size_t length) { - CHECK(SetSystemPagesAccess(address, length, PageInaccessible)); + SetSystemPagesAccess(address, length, PageInaccessible); } bool RecommitSystemPagesInternal(void* address, size_t length, PageAccessibilityConfiguration accessibility) { - return SetSystemPagesAccess(address, length, accessibility); + return TrySetSystemPagesAccess(address, length, accessibility); } void DiscardSystemPagesInternal(void* address, size_t length) {
diff --git a/third_party/base/allocator/partition_allocator/partition_alloc.cc b/third_party/base/allocator/partition_allocator/partition_alloc.cc index 87a51fd..df5d3f1 100644 --- a/third_party/base/allocator/partition_allocator/partition_alloc.cc +++ b/third_party/base/allocator/partition_allocator/partition_alloc.cc
@@ -224,15 +224,13 @@ // Shrink by decommitting unneeded pages and making them inaccessible. size_t decommit_size = current_size - new_size; root->DecommitSystemPages(char_ptr + new_size, decommit_size); - CHECK(SetSystemPagesAccess(char_ptr + new_size, decommit_size, - PageInaccessible)); + SetSystemPagesAccess(char_ptr + new_size, decommit_size, PageInaccessible); } else if (new_size <= internal::PartitionDirectMapExtent::FromPage(page)->map_size) { // Grow within the actually allocated memory. Just need to make the // pages accessible again. size_t recommit_size = new_size - current_size; - CHECK(SetSystemPagesAccess(char_ptr + current_size, recommit_size, - PageReadWrite)); + SetSystemPagesAccess(char_ptr + current_size, recommit_size, PageReadWrite); root->RecommitSystemPages(char_ptr + current_size, recommit_size); #if DCHECK_IS_ON() @@ -263,6 +261,10 @@ size_t new_size, const char* type_name) { #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) + // Make MEMORY_TOOL_REPLACES_ALLOCATOR behave the same for max size + // as other alloc code. + if (new_size > kGenericMaxDirectMapped) + return nullptr; void* result = realloc(ptr, new_size); CHECK(result || flags & PartitionAllocReturnNull); return result;
diff --git a/third_party/base/allocator/partition_allocator/partition_alloc.h b/third_party/base/allocator/partition_allocator/partition_alloc.h index a80755c..c3491a4 100644 --- a/third_party/base/allocator/partition_allocator/partition_alloc.h +++ b/third_party/base/allocator/partition_allocator/partition_alloc.h
@@ -272,6 +272,10 @@ size_t size, const char* type_name) { #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) + // Make MEMORY_TOOL_REPLACES_ALLOCATOR behave the same for max size + // as other alloc code. + if (size > kGenericMaxDirectMapped) + return nullptr; void* result = malloc(size); CHECK(result); return result; @@ -351,6 +355,10 @@ DCHECK(flags < PartitionAllocLastFlag << 1); #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) + // Make MEMORY_TOOL_REPLACES_ALLOCATOR behave the same for max size + // as other alloc code. + if (size > kGenericMaxDirectMapped) + return nullptr; const bool zero_fill = flags & PartitionAllocZeroFill; void* result = zero_fill ? calloc(1, size) : malloc(size); CHECK(result || flags & PartitionAllocReturnNull);
diff --git a/third_party/base/allocator/partition_allocator/partition_alloc_constants.h b/third_party/base/allocator/partition_allocator/partition_alloc_constants.h index cd9108ce..437e4df 100644 --- a/third_party/base/allocator/partition_allocator/partition_alloc_constants.h +++ b/third_party/base/allocator/partition_allocator/partition_alloc_constants.h
@@ -18,90 +18,106 @@ static const size_t kAllocationGranularityMask = kAllocationGranularity - 1; static const size_t kBucketShift = (kAllocationGranularity == 8) ? 3 : 2; -// Underlying partition storage pages are a power-of-two size. It is typical -// for a partition page to be based on multiple system pages. Most references to -// "page" refer to partition pages. -// We also have the concept of "super pages" -- these are the underlying system -// allocations we make. Super pages contain multiple partition pages inside them -// and include space for a small amount of metadata per partition page. -// Inside super pages, we store "slot spans". A slot span is a continguous range -// of one or more partition pages that stores allocations of the same size. +// Underlying partition storage pages (`PartitionPage`s) are a power-of-2 size. +// It is typical for a `PartitionPage` to be based on multiple system pages. +// Most references to "page" refer to `PartitionPage`s. +// +// *Super pages* are the underlying system allocations we make. Super pages +// contain multiple partition pages and include space for a small amount of +// metadata per partition page. +// +// Inside super pages, we store *slot spans*. A slot span is a continguous range +// of one or more `PartitionPage`s that stores allocations of the same size. // Slot span sizes are adjusted depending on the allocation size, to make sure // the packing does not lead to unused (wasted) space at the end of the last -// system page of the span. For our current max slot span size of 64k and other -// constant values, we pack _all_ PartitionRootGeneric::Alloc() sizes perfectly -// up against the end of a system page. +// system page of the span. For our current maximum slot span size of 64 KiB and +// other constant values, we pack _all_ `PartitionRootGeneric::Alloc` sizes +// perfectly up against the end of a system page. + #if defined(_MIPS_ARCH_LOONGSON) -static const size_t kPartitionPageShift = 16; // 64KB +static const size_t kPartitionPageShift = 16; // 64 KiB #else -static const size_t kPartitionPageShift = 14; // 16KB +static const size_t kPartitionPageShift = 14; // 16 KiB #endif static const size_t kPartitionPageSize = 1 << kPartitionPageShift; static const size_t kPartitionPageOffsetMask = kPartitionPageSize - 1; static const size_t kPartitionPageBaseMask = ~kPartitionPageOffsetMask; +// TODO: Should this be 1 if defined(_MIPS_ARCH_LOONGSON)? static const size_t kMaxPartitionPagesPerSlotSpan = 4; // To avoid fragmentation via never-used freelist entries, we hand out partition -// freelist sections gradually, in units of the dominant system page size. -// What we're actually doing is avoiding filling the full partition page (16 KB) +// freelist sections gradually, in units of the dominant system page size. What +// we're actually doing is avoiding filling the full `PartitionPage` (16 KiB) // with freelist pointers right away. Writing freelist pointers will fault and // dirty a private page, which is very wasteful if we never actually store // objects there. + static const size_t kNumSystemPagesPerPartitionPage = kPartitionPageSize / kSystemPageSize; static const size_t kMaxSystemPagesPerSlotSpan = kNumSystemPagesPerPartitionPage * kMaxPartitionPagesPerSlotSpan; -// We reserve virtual address space in 2MB chunks (aligned to 2MB as well). -// These chunks are called "super pages". We do this so that we can store -// metadata in the first few pages of each 2MB aligned section. This leads to -// a very fast free(). We specifically choose 2MB because this virtual address -// block represents a full but single PTE allocation on ARM, ia32 and x64. +// We reserve virtual address space in 2 MiB chunks (aligned to 2 MiB as well). +// These chunks are called *super pages*. We do this so that we can store +// metadata in the first few pages of each 2 MiB-aligned section. This makes +// freeing memory very fast. We specifically choose 2 MiB because this virtual +// address block represents a full but single PTE allocation on ARM, ia32 and +// x64. // -// The layout of the super page is as follows. The sizes below are the same -// for 32 bit and 64 bit. +// The layout of the super page is as follows. The sizes below are the same for +// 32- and 64-bit platforms. // -// | Guard page (4KB) | -// | Metadata page (4KB) | -// | Guard pages (8KB) | -// | Slot span | -// | Slot span | -// | ... | -// | Slot span | -// | Guard page (4KB) | +// +-----------------------+ +// | Guard page (4 KiB) | +// | Metadata page (4 KiB) | +// | Guard pages (8 KiB) | +// | Slot span | +// | Slot span | +// | ... | +// | Slot span | +// | Guard page (4 KiB) | +// +-----------------------+ // -// - Each slot span is a contiguous range of one or more PartitionPages. -// - The metadata page has the following format. Note that the PartitionPage -// that is not at the head of a slot span is "unused". In other words, -// the metadata for the slot span is stored only in the first PartitionPage -// of the slot span. Metadata accesses to other PartitionPages are -// redirected to the first PartitionPage. +// Each slot span is a contiguous range of one or more `PartitionPage`s. // -// | SuperPageExtentEntry (32B) | -// | PartitionPage of slot span 1 (32B, used) | -// | PartitionPage of slot span 1 (32B, unused) | -// | PartitionPage of slot span 1 (32B, unused) | -// | PartitionPage of slot span 2 (32B, used) | -// | PartitionPage of slot span 3 (32B, used) | -// | ... | -// | PartitionPage of slot span N (32B, unused) | +// The metadata page has the following format. Note that the `PartitionPage` +// that is not at the head of a slot span is "unused". In other words, the +// metadata for the slot span is stored only in the first `PartitionPage` of the +// slot span. Metadata accesses to other `PartitionPage`s are redirected to the +// first `PartitionPage`. // -// A direct mapped page has a similar layout to fake it looking like a super +// +---------------------------------------------+ +// | SuperPageExtentEntry (32 B) | +// | PartitionPage of slot span 1 (32 B, used) | +// | PartitionPage of slot span 1 (32 B, unused) | +// | PartitionPage of slot span 1 (32 B, unused) | +// | PartitionPage of slot span 2 (32 B, used) | +// | PartitionPage of slot span 3 (32 B, used) | +// | ... | +// | PartitionPage of slot span N (32 B, unused) | +// +---------------------------------------------+ +// +// A direct-mapped page has a similar layout to fake it looking like a super // page: // -// | Guard page (4KB) | -// | Metadata page (4KB) | -// | Guard pages (8KB) | -// | Direct mapped object | -// | Guard page (4KB) | +// +-----------------------+ +// | Guard page (4 KiB) | +// | Metadata page (4 KiB) | +// | Guard pages (8 KiB) | +// | Direct mapped object | +// | Guard page (4 KiB) | +// +-----------------------+ // -// - The metadata page has the following layout: +// A direct-mapped page's metadata page has the following layout: // -// | SuperPageExtentEntry (32B) | -// | PartitionPage (32B) | -// | PartitionBucket (32B) | -// | PartitionDirectMapExtent (8B) | -static const size_t kSuperPageShift = 21; // 2MB +// +--------------------------------+ +// | SuperPageExtentEntry (32 B) | +// | PartitionPage (32 B) | +// | PartitionBucket (32 B) | +// | PartitionDirectMapExtent (8 B) | +// +--------------------------------+ + +static const size_t kSuperPageShift = 21; // 2 MiB static const size_t kSuperPageSize = 1 << kSuperPageShift; static const size_t kSuperPageOffsetMask = kSuperPageSize - 1; static const size_t kSuperPageBaseMask = ~kSuperPageOffsetMask; @@ -109,15 +125,17 @@ kSuperPageSize / kPartitionPageSize; // The following kGeneric* constants apply to the generic variants of the API. -// The "order" of an allocation is closely related to the power-of-two size of -// the allocation. More precisely, the order is the bit index of the -// most-significant-bit in the allocation size, where the bit numbers starts -// at index 1 for the least-significant-bit. +// The "order" of an allocation is closely related to the power-of-1 size of the +// allocation. More precisely, the order is the bit index of the +// most-significant-bit in the allocation size, where the bit numbers starts at +// index 1 for the least-significant-bit. +// // In terms of allocation sizes, order 0 covers 0, order 1 covers 1, order 2 // covers 2->3, order 3 covers 4->7, order 4 covers 8->15. + static const size_t kGenericMinBucketedOrder = 4; // 8 bytes. -static const size_t kGenericMaxBucketedOrder = - 20; // Largest bucketed order is 1<<(20-1) (storing 512KB -> almost 1MB) +// The largest bucketed order is 1 << (20 - 1), storing [512 KiB, 1 MiB): +static const size_t kGenericMaxBucketedOrder = 20; static const size_t kGenericNumBucketedOrders = (kGenericMaxBucketedOrder - kGenericMinBucketedOrder) + 1; // Eight buckets per order (for the higher orders), e.g. order 8 is 128, 144, @@ -134,28 +152,27 @@ static const size_t kGenericMaxBucketed = (1 << (kGenericMaxBucketedOrder - 1)) + ((kGenericNumBucketsPerOrder - 1) * kGenericMaxBucketSpacing); -static const size_t kGenericMinDirectMappedDownsize = - kGenericMaxBucketed + - 1; // Limit when downsizing a direct mapping using realloc(). +// Limit when downsizing a direct mapping using `realloc`: +static const size_t kGenericMinDirectMappedDownsize = kGenericMaxBucketed + 1; static const size_t kGenericMaxDirectMapped = - (1UL << 31) + kPageAllocationGranularity; // 2 GB plus one more page. + (1UL << 31) + kPageAllocationGranularity; // 2 GiB plus 1 more page. static const size_t kBitsPerSizeT = sizeof(void*) * CHAR_BIT; // Constant for the memory reclaim logic. static const size_t kMaxFreeableSpans = 16; // If the total size in bytes of allocated but not committed pages exceeds this -// value (probably it is a "out of virtual address space" crash), -// a special crash stack trace is generated at |PartitionOutOfMemory|. -// This is to distinguish "out of virtual address space" from -// "out of physical memory" in crash reports. -static const size_t kReasonableSizeOfUnusedPages = 1024 * 1024 * 1024; // 1GB +// value (probably it is a "out of virtual address space" crash), a special +// crash stack trace is generated at +// `PartitionOutOfMemoryWithLotsOfUncommitedPages`. This is to distinguish "out +// of virtual address space" from "out of physical memory" in crash reports. +static const size_t kReasonableSizeOfUnusedPages = 1024 * 1024 * 1024; // 1 GiB -// These two byte values match tcmalloc. +// These byte values match tcmalloc. static const unsigned char kUninitializedByte = 0xAB; static const unsigned char kFreedByte = 0xCD; -// Flags for PartitionAllocGenericFlags. +// Flags for `PartitionAllocGenericFlags`. enum PartitionAllocFlags { PartitionAllocReturnNull = 1 << 0, PartitionAllocZeroFill = 1 << 1,
diff --git a/third_party/base/allocator/partition_allocator/partition_bucket.cc b/third_party/base/allocator/partition_allocator/partition_bucket.cc index b540adb..ae1cf75 100644 --- a/third_party/base/allocator/partition_allocator/partition_bucket.cc +++ b/third_party/base/allocator/partition_allocator/partition_bucket.cc
@@ -49,12 +49,12 @@ root->IncreaseCommittedPages(committed_page_size); char* slot = ptr + kPartitionPageSize; - CHECK(SetSystemPagesAccess(ptr + (kSystemPageSize * 2), - kPartitionPageSize - (kSystemPageSize * 2), - PageInaccessible)); + SetSystemPagesAccess(ptr + (kSystemPageSize * 2), + kPartitionPageSize - (kSystemPageSize * 2), + PageInaccessible); #if !defined(ARCH_CPU_64_BITS) - CHECK(SetSystemPagesAccess(ptr, kSystemPageSize, PageInaccessible)); - CHECK(SetSystemPagesAccess(slot + size, kSystemPageSize, PageInaccessible)); + SetSystemPagesAccess(ptr, kSystemPageSize, PageInaccessible); + SetSystemPagesAccess(slot + size, kSystemPageSize, PageInaccessible); #endif PartitionSuperPageExtentEntry* extent = @@ -207,7 +207,7 @@ // Fresh System Pages in the SuperPages are decommited. Commit them // before vending them back. - CHECK(SetSystemPagesAccess(ret, total_size, PageReadWrite)); + SetSystemPagesAccess(ret, total_size, PageReadWrite); root->next_partition_page += total_size; root->IncreaseCommittedPages(total_size); @@ -240,22 +240,22 @@ // hole in the middle. // This is where we put page metadata and also a tiny amount of extent // metadata. - CHECK(SetSystemPagesAccess(super_page, kSystemPageSize, PageInaccessible)); - CHECK(SetSystemPagesAccess(super_page + (kSystemPageSize * 2), - kPartitionPageSize - (kSystemPageSize * 2), - PageInaccessible)); - // CHECK(SetSystemPagesAccess(super_page + (kSuperPageSize - + SetSystemPagesAccess(super_page, kSystemPageSize, PageInaccessible); + SetSystemPagesAccess(super_page + (kSystemPageSize * 2), + kPartitionPageSize - (kSystemPageSize * 2), + PageInaccessible); + // SetSystemPagesAccess(super_page + (kSuperPageSize - // kPartitionPageSize), - // kPartitionPageSize, PageInaccessible)); + // kPartitionPageSize, PageInaccessible); // All remaining slotspans for the unallocated PartitionPages inside the // SuperPage are conceptually decommitted. Correctly set the state here // so they do not occupy resources. // // TODO(ajwong): Refactor Page Allocator API so the SuperPage comes in // decommited initially. - CHECK(SetSystemPagesAccess(super_page + kPartitionPageSize + total_size, - (kSuperPageSize - kPartitionPageSize - total_size), - PageInaccessible)); + SetSystemPagesAccess(super_page + kPartitionPageSize + total_size, + (kSuperPageSize - kPartitionPageSize - total_size), + PageInaccessible); // If we were after a specific address, but didn't get it, assume that // the system chose a lousy address. Here most OS'es have a default
diff --git a/third_party/base/allocator/partition_allocator/partition_page.h b/third_party/base/allocator/partition_allocator/partition_page.h index a40ff8e..f5ae1e4 100644 --- a/third_party/base/allocator/partition_allocator/partition_page.h +++ b/third_party/base/allocator/partition_allocator/partition_page.h
@@ -201,13 +201,13 @@ } ALWAYS_INLINE void PartitionPage::Free(void* ptr) { +#if DCHECK_IS_ON() size_t slot_size = this->bucket->slot_size; const size_t raw_size = get_raw_size(); if (raw_size) { slot_size = raw_size; } -#if DCHECK_IS_ON() // If these asserts fire, you probably corrupted memory. PartitionCookieCheckValue(ptr); PartitionCookieCheckValue(reinterpret_cast<char*>(ptr) + slot_size -