From 1ae2fee007436ef8d842b3d71f228897ce6d2f67 Mon Sep 17 00:00:00 2001 From: Xue-Lei Andrew Fan Date: Mon, 23 Feb 2026 17:02:14 +0000 Subject: [PATCH] 8376125: Out of memory in the CDS archive error with lot of classes Co-authored-by: Ioi Lam Reviewed-by: iklam, jiangli --- src/hotspot/os/posix/os_posix.cpp | 17 +++++ .../share/cds/aotCompressedPointers.hpp | 18 ++--- src/hotspot/share/cds/archiveBuilder.cpp | 6 +- src/hotspot/share/cds/archiveUtils.cpp | 17 +---- src/hotspot/share/cds/cppVtables.cpp | 2 +- src/hotspot/share/cds/filemap.cpp | 4 +- .../share/classfile/compactHashtable.cpp | 37 ++++++---- .../share/classfile/compactHashtable.hpp | 9 ++- src/hotspot/share/runtime/vmStructs.cpp | 6 ++ .../sun/jvm/hotspot/memory/FileMapInfo.java | 42 ++++++----- test/hotspot/gtest/cds/test_scaledOffsets.cpp | 71 +++++++++++++++++++ 11 files changed, 167 insertions(+), 62 deletions(-) create mode 100644 test/hotspot/gtest/cds/test_scaledOffsets.cpp diff --git a/src/hotspot/os/posix/os_posix.cpp b/src/hotspot/os/posix/os_posix.cpp index 5412e2bc92d..f147ed4be93 100644 --- a/src/hotspot/os/posix/os_posix.cpp +++ b/src/hotspot/os/posix/os_posix.cpp @@ -906,8 +906,25 @@ FILE* os::fdopen(int fd, const char* mode) { ssize_t os::pd_write(int fd, const void *buf, size_t nBytes) { ssize_t res; +#ifdef __APPLE__ + // macOS fails for individual write operations > 2GB. + // See https://gitlab.haskell.org/ghc/ghc/-/issues/17414 + ssize_t total = 0; + while (nBytes > 0) { + size_t bytes_to_write = MIN2(nBytes, (size_t)INT_MAX); + RESTARTABLE(::write(fd, buf, bytes_to_write), res); + if (res == OS_ERR) { + return OS_ERR; + } + buf = (const char*)buf + res; + nBytes -= res; + total += res; + } + return total; +#else RESTARTABLE(::write(fd, buf, nBytes), res); return res; +#endif } ssize_t os::read_at(int fd, void *buf, unsigned int nBytes, jlong offset) { diff --git a/src/hotspot/share/cds/aotCompressedPointers.hpp b/src/hotspot/share/cds/aotCompressedPointers.hpp index ead48ef9948..a3919fb95a9 100644 --- a/src/hotspot/share/cds/aotCompressedPointers.hpp +++ b/src/hotspot/share/cds/aotCompressedPointers.hpp @@ -36,16 +36,17 @@ class AOTCompressedPointers: public AllStatic { public: // For space saving, we can encode the location of metadata objects in the "rw" and "ro" - // regions using a 32-bit offset from the bottom of the mapped AOT metaspace. - // Currently we allow only up to 2GB total size in the rw and ro regions (which are - // contiguous to each other). + // regions using a 32-bit offset from the bottom of the mapped AOT metaspace. Since metadata + // objects are 8-byte aligned, we store scaled offset units (offset_bytes >> 3) to address + // up to ~32GB on 64-bit platforms. We currently limit the MaxMetadataOffsetBytes to about + // 3.5 GB to be compatible with +CompactObjectHeaders. enum class narrowPtr : u4; - static constexpr size_t MaxMetadataOffsetBytes = 0x7FFFFFFF; + static constexpr size_t MetadataOffsetShift = LP64_ONLY(3) NOT_LP64(0); + static constexpr size_t MaxMetadataOffsetBytes = LP64_ONLY(3584ULL * M) NOT_LP64(0x7FFFFFFF); - // In the future, this could return a different numerical value than - // narrowp if the encoding contains shifts. + // Convert the encoded narrowPtr to a byte offset by applying the shift. inline static size_t get_byte_offset(narrowPtr narrowp) { - return checked_cast(narrowp); + return ((size_t)checked_cast(narrowp)) << MetadataOffsetShift; } inline static narrowPtr null() { @@ -122,7 +123,8 @@ private: static narrowPtr encode_byte_offset(size_t offset) { assert(offset != 0, "offset 0 is in protection zone"); precond(offset <= MaxMetadataOffsetBytes); - return checked_cast(offset); + assert(is_aligned(offset, (size_t)1 << MetadataOffsetShift), "offset not aligned"); + return checked_cast(offset >> MetadataOffsetShift); } }; diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index cb9459172b3..0ea5d6c6ecb 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -321,8 +321,10 @@ void ArchiveBuilder::sort_klasses() { } address ArchiveBuilder::reserve_buffer() { - // AOTCodeCache::max_aot_code_size() accounts for aot code region. - size_t buffer_size = LP64_ONLY(CompressedClassSpaceSize) NOT_LP64(256 * M) + AOTCodeCache::max_aot_code_size(); + // On 64-bit: reserve address space for archives up to the max encoded offset limit. + // On 32-bit: use 256MB + AOT code size due to limited virtual address space. + size_t buffer_size = LP64_ONLY(AOTCompressedPointers::MaxMetadataOffsetBytes) + NOT_LP64(256 * M + AOTCodeCache::max_aot_code_size()); ReservedSpace rs = MemoryReserver::reserve(buffer_size, AOTMetaspace::core_region_alignment(), os::vm_page_size(), diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index c13b447bb87..ea9bde8eb8d 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -312,22 +312,9 @@ void DumpRegion::pack(DumpRegion* next) { } void WriteClosure::do_ptr(void** p) { - // Write ptr into the archive; ptr can be: - // (a) null -> written as 0 - // (b) a "buffered" address -> written as is - // (c) a "source" address -> convert to "buffered" and write - // The common case is (c). E.g., when writing the vmClasses into the archive. - // We have (b) only when we don't have a corresponding source object. E.g., - // the archived c++ vtable entries. address ptr = *(address*)p; - if (ptr != nullptr && !ArchiveBuilder::current()->is_in_buffer_space(ptr)) { - ptr = ArchiveBuilder::current()->get_buffered_addr(ptr); - } - // null pointers do not need to be converted to offsets - if (ptr != nullptr) { - ptr = (address)ArchiveBuilder::current()->buffer_to_offset(ptr); - } - _dump_region->append_intptr_t((intptr_t)ptr, false); + AOTCompressedPointers::narrowPtr narrowp = AOTCompressedPointers::encode(ptr); + _dump_region->append_intptr_t(checked_cast(narrowp), false); } void ReadClosure::do_ptr(void** p) { diff --git a/src/hotspot/share/cds/cppVtables.cpp b/src/hotspot/share/cds/cppVtables.cpp index da68fa70761..dc5a777d7b1 100644 --- a/src/hotspot/share/cds/cppVtables.cpp +++ b/src/hotspot/share/cds/cppVtables.cpp @@ -140,7 +140,7 @@ void CppVtableCloner::initialize(const char* name, CppVtableInfo* info) { // We already checked (and, if necessary, adjusted n) when the vtables were allocated, so we are // safe to do memcpy. - log_debug(aot, vtables)("Copying %3d vtable entries for %s", n, name); + log_debug(aot, vtables)("Copying %3d vtable entries for %s to " INTPTR_FORMAT, n, name, p2i(dstvtable)); memcpy(dstvtable, srcvtable, sizeof(intptr_t) * n); } diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index da2d4f6dac2..91fbce701e5 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -717,8 +717,8 @@ bool FileMapInfo::init_from_file(int fd) { } void FileMapInfo::seek_to_position(size_t pos) { - if (os::lseek(_fd, (long)pos, SEEK_SET) < 0) { - aot_log_error(aot)("Unable to seek to position %zu", pos); + if (os::lseek(_fd, (jlong)pos, SEEK_SET) < 0) { + aot_log_error(aot)("Unable to seek to position %zu (errno=%d: %s)", pos, errno, os::strerror(errno)); AOTMetaspace::unrecoverable_loading_error(); } } diff --git a/src/hotspot/share/classfile/compactHashtable.cpp b/src/hotspot/share/classfile/compactHashtable.cpp index 6808ae7bb8f..de67971c403 100644 --- a/src/hotspot/share/classfile/compactHashtable.cpp +++ b/src/hotspot/share/classfile/compactHashtable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -96,14 +96,16 @@ void CompactHashtableWriter::allocate_table() { "Too many entries."); } - _compact_buckets = ArchiveBuilder::new_ro_array(_num_buckets + 1); - _compact_entries = ArchiveBuilder::new_ro_array(entries_space); + _num_compact_buckets = checked_cast(_num_buckets + 1); // extra slot for TABLEEND_BUCKET_TYPE + _num_compact_entries = checked_cast(entries_space); + _compact_buckets = (u4*)ArchiveBuilder::ro_region_alloc(_num_compact_buckets * sizeof(u4)); + _compact_entries = (u4*)ArchiveBuilder::ro_region_alloc(_num_compact_entries * sizeof(u4)); _stats->bucket_count = _num_buckets; - _stats->bucket_bytes = align_up(_compact_buckets->size() * BytesPerWord, + _stats->bucket_bytes = align_up(checked_cast(_num_compact_buckets * sizeof(u4)), SharedSpaceObjectAlignment); _stats->hashentry_count = _num_entries_written; - _stats->hashentry_bytes = align_up(_compact_entries->size() * BytesPerWord, + _stats->hashentry_bytes = align_up(checked_cast(_num_compact_entries * sizeof(u4)), SharedSpaceObjectAlignment); } @@ -114,21 +116,21 @@ void CompactHashtableWriter::dump_table(NumberSeq* summary) { GrowableArray* bucket = _buckets[index]; int bucket_size = bucket->length(); if (bucket_size == 1) { - _compact_buckets->at_put(index, BUCKET_INFO(offset, VALUE_ONLY_BUCKET_TYPE)); + compact_buckets_set(index, BUCKET_INFO(offset, VALUE_ONLY_BUCKET_TYPE)); Entry ent = bucket->at(0); // bucket with one entry is value_only and only has the encoded_value - _compact_entries->at_put(offset++, ent.encoded_value()); + compact_entries_set(offset++, ent.encoded_value()); _num_value_only_buckets++; } else { // regular bucket, it could contain zero or more than one entry, // each entry is a pair - _compact_buckets->at_put(index, BUCKET_INFO(offset, REGULAR_BUCKET_TYPE)); + compact_buckets_set(index, BUCKET_INFO(offset, REGULAR_BUCKET_TYPE)); for (int i=0; iat(i); - _compact_entries->at_put(offset++, u4(ent.hash())); // write entry hash - _compact_entries->at_put(offset++, ent.encoded_value()); // write entry encoded_value + compact_entries_set(offset++, u4(ent.hash())); // write entry hash + compact_entries_set(offset++, ent.encoded_value()); // write entry encoded_value } if (bucket_size == 0) { _num_empty_buckets++; @@ -140,10 +142,19 @@ void CompactHashtableWriter::dump_table(NumberSeq* summary) { } // Mark the end of the buckets - _compact_buckets->at_put(_num_buckets, BUCKET_INFO(offset, TABLEEND_BUCKET_TYPE)); - assert(offset == (u4)_compact_entries->length(), "sanity"); + compact_buckets_set(_num_buckets, BUCKET_INFO(offset, TABLEEND_BUCKET_TYPE)); + assert(offset == checked_cast(_num_compact_entries), "sanity"); } +void CompactHashtableWriter::compact_buckets_set(u4 index, u4 value) { + precond(index < _num_compact_buckets); + _compact_buckets[index] = value; +} + +void CompactHashtableWriter::compact_entries_set(u4 index, u4 value) { + precond(index < _num_compact_entries); + _compact_entries[index] = value; +} // Write the compact table void CompactHashtableWriter::dump(SimpleCompactHashtable *cht, const char* table_name) { @@ -154,7 +165,7 @@ void CompactHashtableWriter::dump(SimpleCompactHashtable *cht, const char* table int table_bytes = _stats->bucket_bytes + _stats->hashentry_bytes; address base_address = address(SharedBaseAddress); cht->init(base_address, _num_entries_written, _num_buckets, - _compact_buckets->data(), _compact_entries->data()); + _compact_buckets, _compact_entries); LogMessage(aot, hashtables) msg; if (msg.is_info()) { diff --git a/src/hotspot/share/classfile/compactHashtable.hpp b/src/hotspot/share/classfile/compactHashtable.hpp index 2fe92be0f6d..81f2951289d 100644 --- a/src/hotspot/share/classfile/compactHashtable.hpp +++ b/src/hotspot/share/classfile/compactHashtable.hpp @@ -115,8 +115,13 @@ private: int _num_other_buckets; GrowableArray** _buckets; CompactHashtableStats* _stats; - Array* _compact_buckets; - Array* _compact_entries; + u4* _compact_buckets; + size_t _num_compact_buckets; + u4* _compact_entries; + size_t _num_compact_entries; + + void compact_buckets_set(u4 index, u4 value); + void compact_entries_set(u4 index, u4 value); public: // This is called at dump-time only diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index 4d2ba90e326..9ac8f396f9a 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -1288,6 +1288,12 @@ VM_INT_CONSTANTS_GC(declare_constant, \ declare_constant_with_value) \ \ + /*****************/ \ + /* CDS constants */ \ + /*****************/ \ + \ + CDS_ONLY(declare_constant(AOTCompressedPointers::MetadataOffsetShift)) \ + \ /******************/ \ /* Useful globals */ \ /******************/ \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/FileMapInfo.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/FileMapInfo.java index 14d8af58e4d..12b614e7fa8 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/FileMapInfo.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/FileMapInfo.java @@ -41,6 +41,7 @@ public class FileMapInfo { private static Address rwRegionEndAddress; private static Address vtablesIndex; private static Address mapped_base_address; + private static long metadataOffsetShift; // HashMap created by mapping the vTable addresses in the rw region with // the corresponding metadata type. @@ -97,12 +98,15 @@ public class FileMapInfo { headerObj = VMObjectFactory.newObject(FileMapHeader.class, header); // char* mapped_base_address = header->_mapped_base_address - // narrowPtr cloned_vtable_narrowPtr = header->_cloned_vtable_offset + // narrowPtr cloned_vtable_narrowPtr = header->_cloned_vtables // size_t cloned_vtable_offset = AOTCompressedPointers::get_byte_offset(cloned_vtable_narrowPtr); // CppVtableInfo** vtablesIndex = mapped_base_address + cloned_vtable_offset; mapped_base_address = get_AddressField(FileMapHeader_type, header, "_mapped_base_address"); long cloned_vtable_narrowPtr = get_CIntegerField(FileMapHeader_type, header, "_cloned_vtables"); - long cloned_vtable_offset = cloned_vtable_narrowPtr; // Currently narrowPtr is the same as offset + // narrowPtr stores scaled offset units (byte_offset >> MetadataOffsetShift). + // Apply the left shift to convert back to byte offset. + metadataOffsetShift = db.lookupIntConstant("AOTCompressedPointers::MetadataOffsetShift").longValue(); + long cloned_vtable_offset = cloned_vtable_narrowPtr << metadataOffsetShift; vtablesIndex = mapped_base_address.addOffsetTo(cloned_vtable_offset); // CDSFileMapRegion* rw_region = &header->_region[rw]; @@ -176,9 +180,9 @@ public class FileMapInfo { // vtablesIndex points to to an array like this: // long info[] = { - // offset of the CppVtableInfo for ConstantPool, - // offset of the CppVtableInfo for InstanceKlass, - // offset of the CppVtableInfo for InstanceClassLoaderKlass, + // narrowPtr of the CppVtableInfo for ConstantPool, + // narrowPtr of the CppVtableInfo for InstanceKlass, + // narrowPtr of the CppVtableInfo for InstanceClassLoaderKlass, // ... // }; // @@ -189,8 +193,8 @@ public class FileMapInfo { // }; // // The loop below computes the following - // CppVtableInfo* t_ConstantPool = mapped_base_address + info[0]; - // CppVtableInfo* t_InstanceKlass = mapped_base_address + info[1]; + // CppVtableInfo* t_ConstantPool = mapped_base_address + (info[0] << metadataOffsetShift); + // CppVtableInfo* t_InstanceKlass = mapped_base_address + (info[1] << metadataOffsetShift); // ... // // If we have the following objects @@ -203,21 +207,21 @@ public class FileMapInfo { // // To get an idea what these address look like, do this: // - // $ java -Xlog:cds+vtables=debug -XX:+UnlockDiagnosticVMOptions -XX:ArchiveRelocationMode=0 --version - // [0.002s][debug][cds,vtables] Copying 14 vtable entries for ConstantPool to 0x800000018 - // [0.002s][debug][cds,vtables] Copying 41 vtable entries for InstanceKlass to 0x800000090 - // [0.002s][debug][cds,vtables] Copying 41 vtable entries for InstanceClassLoaderKlass to 0x8000001e0 - // [0.002s][debug][cds,vtables] Copying 41 vtable entries for InstanceMirrorKlass to 0x800000330 - // [0.002s][debug][cds,vtables] Copying 41 vtable entries for InstanceRefKlass to 0x800000480 - // [0.002s][debug][cds,vtables] Copying 41 vtable entries for InstanceStackChunkKlass to 0x8000005d0 - // [0.002s][debug][cds,vtables] Copying 14 vtable entries for Method to 0x800000720 - // [0.002s][debug][cds,vtables] Copying 42 vtable entries for ObjArrayKlass to 0x800000798 - // [0.002s][debug][cds,vtables] Copying 42 vtable entries for TypeArrayKlass to 0x8000008f0 - // java 23-internal 2024-09-17 + // $ java -Xlog:aot+vtables=debug -XX:+UnlockDiagnosticVMOptions -XX:ArchiveRelocationMode=0 --version + // [0.002s][debug][aot,vtables] Copying 14 vtable entries for ConstantPool to 0x800000018 + // [0.002s][debug][aot,vtables] Copying 41 vtable entries for InstanceKlass to 0x800000090 + // [0.002s][debug][aot,vtables] Copying 41 vtable entries for InstanceClassLoaderKlass to 0x8000001e0 + // [0.002s][debug][aot,vtables] Copying 41 vtable entries for InstanceMirrorKlass to 0x800000330 + // [0.002s][debug][aot,vtables] Copying 41 vtable entries for InstanceRefKlass to 0x800000480 + // [0.002s][debug][aot,vtables] Copying 41 vtable entries for InstanceStackChunkKlass to 0x8000005d0 + // [0.002s][debug][aot,vtables] Copying 14 vtable entries for Method to 0x800000720 + // [0.002s][debug][aot,vtables] Copying 42 vtable entries for ObjArrayKlass to 0x800000798 + // [0.002s][debug][aot,vtables] Copying 42 vtable entries for TypeArrayKlass to 0x8000008f0 // ... for (int i=0; i < metadataTypeArray.length; i++) { - long vtable_offset = vtablesIndex.getJLongAt(i * addressSize); // long offset = _index[i] + long narrowPtr = vtablesIndex.getJLongAt(i * addressSize); + long vtable_offset = narrowPtr << metadataOffsetShift; // CppVtableInfo* t = the address of the CppVtableInfo for the i-th table Address vtableInfoAddress = mapped_base_address.addOffsetTo(vtable_offset); diff --git a/test/hotspot/gtest/cds/test_scaledOffsets.cpp b/test/hotspot/gtest/cds/test_scaledOffsets.cpp new file mode 100644 index 00000000000..93b1b211276 --- /dev/null +++ b/test/hotspot/gtest/cds/test_scaledOffsets.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2026 salesforce.com, inc. All Rights Reserved + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "cds/aotCompressedPointers.hpp" +#include "unittest.hpp" +#include "utilities/globalDefinitions.hpp" + +#include + +TEST_VM(ScaledOffsetsTest, constants) { +#ifdef _LP64 + ASSERT_EQ((size_t)3, AOTCompressedPointers::MetadataOffsetShift); + ASSERT_TRUE(is_aligned(AOTCompressedPointers::MaxMetadataOffsetBytes, (size_t)1 << AOTCompressedPointers::MetadataOffsetShift)); + ASSERT_EQ((size_t)(3584ULL * M), AOTCompressedPointers::MaxMetadataOffsetBytes); +#else + ASSERT_EQ((size_t)0, AOTCompressedPointers::MetadataOffsetShift); + ASSERT_EQ((size_t)0x7FFFFFFF, AOTCompressedPointers::MaxMetadataOffsetBytes); +#endif +} + +TEST_VM(ScaledOffsetsTest, encode_decode_roundtrip) { + // Test that encoding and decoding via get_byte_offset produces correct results + const size_t unit = (size_t)1 << AOTCompressedPointers::MetadataOffsetShift; + + // Test that get_byte_offset correctly applies the shift + // Note: We can't directly test encode_byte_offset as it's private, but we can verify + // the shift value is applied correctly in get_byte_offset + AOTCompressedPointers::narrowPtr np1 = static_cast(1); + ASSERT_EQ(unit, AOTCompressedPointers::get_byte_offset(np1)); + + AOTCompressedPointers::narrowPtr np2 = static_cast(2); + ASSERT_EQ(2 * unit, AOTCompressedPointers::get_byte_offset(np2)); + + AOTCompressedPointers::narrowPtr np1024 = static_cast(1024); + ASSERT_EQ(1024 * unit, AOTCompressedPointers::get_byte_offset(np1024)); + +#ifdef _LP64 + const uint64_t max_units = (uint64_t)UINT32_MAX; + AOTCompressedPointers::narrowPtr np_max = static_cast(UINT32_MAX); + const uint64_t max_bytes = max_units << AOTCompressedPointers::MetadataOffsetShift; + ASSERT_EQ(max_bytes, AOTCompressedPointers::get_byte_offset(np_max)); + ASSERT_GE(max_bytes, AOTCompressedPointers::MaxMetadataOffsetBytes - unit); +#endif +} + +TEST_VM(ScaledOffsetsTest, null_handling) { + // Test that null() returns 0 + ASSERT_EQ(static_cast(0), AOTCompressedPointers::null()); + ASSERT_EQ((size_t)0, AOTCompressedPointers::get_byte_offset(AOTCompressedPointers::null())); +}