From 89d18ea40f3508f4053824fd47f0b0f85fe1d7c2 Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Wed, 30 Aug 2023 17:51:28 +0000 Subject: [PATCH] 8312018: Improve reservation of class space and CDS 8313669: Reduced chance for zero-based nKlass encoding since JDK-8296565 Reviewed-by: iklam, adinn --- src/hotspot/os/aix/os_aix.cpp | 9 + src/hotspot/os/bsd/os_bsd.cpp | 11 + src/hotspot/os/linux/os_linux.cpp | 18 + src/hotspot/os/windows/os_windows.cpp | 5 + src/hotspot/share/cds/metaspaceShared.cpp | 8 +- src/hotspot/share/memory/metaspace.cpp | 155 +++----- src/hotspot/share/memory/metaspace.hpp | 10 +- src/hotspot/share/memory/virtualspace.cpp | 21 +- src/hotspot/share/memory/virtualspace.hpp | 4 + src/hotspot/share/oops/compressedKlass.cpp | 3 +- src/hotspot/share/runtime/globals.hpp | 3 + src/hotspot/share/runtime/os.cpp | 194 ++++++++++ src/hotspot/share/runtime/os.hpp | 12 + src/hotspot/share/utilities/fastrand.hpp | 47 +++ test/hotspot/gtest/runtime/test_os.cpp | 11 + .../gtest/runtime/test_os_reserve_between.cpp | 346 ++++++++++++++++++ test/hotspot/gtest/testutils.hpp | 3 + 17 files changed, 746 insertions(+), 114 deletions(-) create mode 100644 src/hotspot/share/utilities/fastrand.hpp create mode 100644 test/hotspot/gtest/runtime/test_os_reserve_between.cpp diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index f373ab12bd0..e0a403b26f8 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -2166,6 +2166,15 @@ char* os::pd_attempt_reserve_memory_at(char* requested_addr, size_t bytes, bool return addr; } +size_t os::vm_min_address() { + // On AIX, we need to make sure we don't block the sbrk. However, this is + // done at actual reservation time, where we honor a "no-mmap" area following + // the break. See MaxExpectedDataSegmentSize. So we can return a very low + // address here. + assert(is_aligned(_vm_min_address_default, os::vm_allocation_granularity()), "Sanity"); + return _vm_min_address_default; +} + // Used to convert frequent JVM_Yield() to nops bool os::dont_yield() { return DontYieldALot; diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index ae4bc6e42a9..054c89f5554 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -1822,6 +1822,17 @@ char* os::pd_attempt_reserve_memory_at(char* requested_addr, size_t bytes, bool return nullptr; } +size_t os::vm_min_address() { +#ifdef __APPLE__ + // On MacOS, the lowest 4G are denied to the application (see "PAGEZERO" resp. + // -pagezero_size linker option). + return 4 * G; +#else + assert(is_aligned(_vm_min_address_default, os::vm_allocation_granularity()), "Sanity"); + return _vm_min_address_default; +#endif +} + // Used to convert frequent JVM_Yield() to nops bool os::dont_yield() { return DontYieldALot; diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 2563fbf8f5c..3a1785b27b5 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -70,6 +70,7 @@ #include "services/runtimeService.hpp" #include "utilities/align.hpp" #include "utilities/checkedCast.hpp" +#include "utilities/debug.hpp" #include "utilities/decoder.hpp" #include "utilities/defaultStream.hpp" #include "utilities/events.hpp" @@ -4244,6 +4245,23 @@ char* os::pd_attempt_reserve_memory_at(char* requested_addr, size_t bytes, bool return nullptr; } +size_t os::vm_min_address() { + // Determined by sysctl vm.mmap_min_addr. It exists as a safety zone to prevent + // NULL pointer dereferences. + // Most distros set this value to 64 KB. It *can* be zero, but rarely is. Here, + // we impose a minimum value if vm.mmap_min_addr is too low, for increased protection. + static size_t value = 0; + if (value == 0) { + assert(is_aligned(_vm_min_address_default, os::vm_allocation_granularity()), "Sanity"); + FILE* f = fopen("/proc/sys/vm/mmap_min_addr", "r"); + if (fscanf(f, "%zu", &value) != 1) { + value = _vm_min_address_default; + } + value = MAX2(_vm_min_address_default, value); + } + return value; +} + // Used to convert frequent JVM_Yield() to nops bool os::dont_yield() { return DontYieldALot; diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index ad88e714bfc..5563169151b 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -3435,6 +3435,11 @@ char* os::pd_attempt_reserve_memory_at(char* addr, size_t bytes, bool exec) { return res; } +size_t os::vm_min_address() { + assert(is_aligned(_vm_min_address_default, os::vm_allocation_granularity()), "Sanity"); + return _vm_min_address_default; +} + char* os::pd_attempt_map_memory_to_file_at(char* requested_addr, size_t bytes, int file_desc) { assert(file_desc >= 0, "file_desc is not valid"); return map_memory_to_file(requested_addr, bytes, file_desc); diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index e99ef29ef4e..e5bfb742840 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -1328,8 +1328,12 @@ char* MetaspaceShared::reserve_address_space_for_archives(FileMapInfo* static_ma total_space_rs = ReservedSpace(total_range_size, archive_space_alignment, os::vm_page_size(), (char*) base_address); } else { - // Reserve at any address, but leave it up to the platform to choose a good one. - total_space_rs = Metaspace::reserve_address_space_for_compressed_classes(total_range_size); + // We did not manage to reserve at the preferred address, or were instructed to relocate. In that + // case we reserve whereever possible, but the start address needs to be encodable as narrow Klass + // encoding base since the archived heap objects contain nKlass IDs precalculated toward the start + // of the shared Metaspace. That prevents us from using zero-based encoding and therefore we won't + // try allocating in low-address regions. + total_space_rs = Metaspace::reserve_address_space_for_compressed_classes(total_range_size, false /* try_in_low_address_ranges */); } if (!total_space_rs.is_reserved()) { diff --git a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp index a149deedd23..d8d01e7fd73 100644 --- a/src/hotspot/share/memory/metaspace.cpp +++ b/src/hotspot/share/memory/metaspace.cpp @@ -59,6 +59,7 @@ #include "utilities/debug.hpp" #include "utilities/formatBuffer.hpp" #include "utilities/globalDefinitions.hpp" +#include "virtualspace.hpp" using metaspace::ChunkManager; using metaspace::CommitLimiter; @@ -581,86 +582,71 @@ bool Metaspace::class_space_is_initialized() { return MetaspaceContext::context_class() != nullptr; } -// Reserve a range of memory at an address suitable for en/decoding narrow -// Klass pointers (see: CompressedClassPointers::is_valid_base()). -// The returned address shall both be suitable as a compressed class pointers -// base, and aligned to Metaspace::reserve_alignment (which is equal to or a -// multiple of allocation granularity). -// On error, returns an unreserved space. -ReservedSpace Metaspace::reserve_address_space_for_compressed_classes(size_t size) { +// Reserve a range of memory that is to contain narrow Klass IDs. If "try_in_low_address_ranges" +// is true, we will attempt to reserve memory suitable for zero-based encoding. +ReservedSpace Metaspace::reserve_address_space_for_compressed_classes(size_t size, bool try_in_low_address_ranges) { -#if defined(AARCH64) || defined(PPC64) - const size_t alignment = Metaspace::reserve_alignment(); + char* result = nullptr; + const bool randomize = RandomizeClassSpaceLocation; - // AArch64: Try to align metaspace class space so that we can decode a - // compressed klass with a single MOVK instruction. We can do this iff the - // compressed class base is a multiple of 4G. - // Additionally, above 32G, ensure the lower LogKlassAlignmentInBytes bits - // of the upper 32-bits of the address are zero so we can handle a shift - // when decoding. - - // PPC64: smaller heaps up to 2g will be mapped just below 4g. Then the - // attempt to place the compressed class space just after the heap fails on - // Linux 4.1.42 and higher because the launcher is loaded at 4g - // (ELF_ET_DYN_BASE). In that case we reach here and search the address space - // below 32g to get a zerobased CCS. For simplicity we reuse the search - // strategy for AARCH64. - - static const struct { - address from; - address to; - size_t increment; - } search_ranges[] = { - { (address)(4*G), (address)(32*G), 4*G, }, - { (address)(32*G), (address)(1024*G), (4 << LogKlassAlignmentInBytes) * G }, - { nullptr, nullptr, 0 } - }; - - // Calculate a list of all possible values for the starting address for the - // compressed class space. - ResourceMark rm; - GrowableArray
list(36); - for (int i = 0; search_ranges[i].from != nullptr; i ++) { - address a = search_ranges[i].from; - assert(CompressedKlassPointers::is_valid_base(a), "Sanity"); - while (a < search_ranges[i].to) { - list.append(a); - a += search_ranges[i].increment; + // First try to reserve in low address ranges. + if (try_in_low_address_ranges) { + constexpr uintptr_t unscaled_max = ((uintptr_t)UINT_MAX + 1); + log_debug(metaspace, map)("Trying below " SIZE_FORMAT_X " for unscaled narrow Klass encoding", unscaled_max); + result = os::attempt_reserve_memory_between(nullptr, (char*)unscaled_max, + size, Metaspace::reserve_alignment(), randomize); + if (result == nullptr) { + constexpr uintptr_t zerobased_max = unscaled_max << LogKlassAlignmentInBytes; + log_debug(metaspace, map)("Trying below " SIZE_FORMAT_X " for zero-based narrow Klass encoding", zerobased_max); + result = os::attempt_reserve_memory_between((char*)unscaled_max, (char*)zerobased_max, + size, Metaspace::reserve_alignment(), randomize); } + } // end: low-address reservation + +#if defined(AARCH64) || defined(PPC64) || defined(S390) + if (result == nullptr) { + // Failing zero-based allocation, or in strict_base mode, try to come up with + // an optimized start address that is amenable to JITs that use 16-bit moves to + // load the encoding base as a short immediate. + // Therefore we try here for an address that when right-shifted by + // LogKlassAlignmentInBytes has only 1s in the third 16-bit quadrant. + // + // Example: for shift=3, the address space searched would be + // [0x0080_0000_0000 - 0xFFF8_0000_0000]. + + // Number of least significant bits that should be zero + constexpr int lo_zero_bits = 32 + LogKlassAlignmentInBytes; + // Number of most significant bits that should be zero + constexpr int hi_zero_bits = 16; + + constexpr size_t alignment = nth_bit(lo_zero_bits); + assert(alignment >= Metaspace::reserve_alignment(), "Sanity"); + constexpr uint64_t min = alignment; + constexpr uint64_t max = nth_bit(64 - hi_zero_bits); + + log_debug(metaspace, map)("Trying between " UINT64_FORMAT_X " and " UINT64_FORMAT_X + " with " SIZE_FORMAT_X " alignment", min, max, alignment); + result = os::attempt_reserve_memory_between((char*)min, (char*)max, size, alignment, randomize); + } +#endif // defined(AARCH64) || defined(PPC64) || defined(S390) + + if (result == nullptr) { + // Fallback: reserve anywhere and hope the resulting block is usable. + log_debug(metaspace, map)("Trying anywhere..."); + result = os::reserve_memory_aligned(size, Metaspace::reserve_alignment(), false); } - int len = list.length(); - int r = 0; - if (!DumpSharedSpaces) { - // Starting from a random position in the list. If the address cannot be reserved - // (the OS already assigned it for something else), go to the next position, wrapping - // around if necessary, until we exhaust all the items. - os::init_random((int)os::javaTimeNanos()); - r = os::random(); - log_info(metaspace)("Randomizing compressed class space: start from %d out of %d locations", - r % len, len); + // Wrap resulting range in ReservedSpace + ReservedSpace rs; + if (result != nullptr) { + assert(is_aligned(result, Metaspace::reserve_alignment()), "Alignment too small for metaspace"); + rs = ReservedSpace::space_for_range(result, size, Metaspace::reserve_alignment(), + os::vm_page_size(), false, false); + } else { + rs = ReservedSpace(); } - for (int i = 0; i < len; i++) { - address a = list.at((i + r) % len); - ReservedSpace rs(size, Metaspace::reserve_alignment(), - os::vm_page_size(), (char*)a); - if (rs.is_reserved()) { - assert(a == (address)rs.base(), "Sanity"); - return rs; - } - } -#endif // defined(AARCH64) || defined(PPC64) - -#ifdef AARCH64 - // Note: on AARCH64, if the code above does not find any good placement, we - // have no recourse. We return an empty space and the VM will exit. - return ReservedSpace(); -#else - // Default implementation: Just reserve anywhere. - return ReservedSpace(size, Metaspace::reserve_alignment(), os::vm_page_size(), (char*)nullptr); -#endif // AARCH64 + return rs; } - #endif // _LP64 size_t Metaspace::reserve_alignment_words() { @@ -781,14 +767,13 @@ void Metaspace::global_initialize() { // case (b) (No CDS) ReservedSpace rs; const size_t size = align_up(CompressedClassSpaceSize, Metaspace::reserve_alignment()); - address base = nullptr; // If CompressedClassSpaceBaseAddress is set, we attempt to force-map class space to // the given address. This is a debug-only feature aiding tests. Due to the ASLR lottery // this may fail, in which case the VM will exit after printing an appropriate message. // Tests using this switch should cope with that. if (CompressedClassSpaceBaseAddress != 0) { - base = (address)CompressedClassSpaceBaseAddress; + const address base = (address)CompressedClassSpaceBaseAddress; if (!is_aligned(base, Metaspace::reserve_alignment())) { vm_exit_during_initialization( err_msg("CompressedClassSpaceBaseAddress=" PTR_FORMAT " invalid " @@ -806,27 +791,9 @@ void Metaspace::global_initialize() { } } - if (!rs.is_reserved()) { - // If UseCompressedOops=1 and the java heap has been placed in coops-friendly - // territory, i.e. its base is under 32G, then we attempt to place ccs - // right above the java heap. - // Otherwise the lower 32G are still free. We try to place ccs at the lowest - // allowed mapping address. - base = (UseCompressedOops && (uint64_t)CompressedOops::base() < OopEncodingHeapMax) ? - CompressedOops::end() : (address)HeapBaseMinAddress; - base = align_up(base, Metaspace::reserve_alignment()); - - if (base != nullptr) { - if (CompressedKlassPointers::is_valid_base(base)) { - rs = ReservedSpace(size, Metaspace::reserve_alignment(), - os::vm_page_size(), (char*)base); - } - } - } - // ...failing that, reserve anywhere, but let platform do optimized placement: if (!rs.is_reserved()) { - rs = Metaspace::reserve_address_space_for_compressed_classes(size); + rs = Metaspace::reserve_address_space_for_compressed_classes(size, true); } // ...failing that, give up. diff --git a/src/hotspot/share/memory/metaspace.hpp b/src/hotspot/share/memory/metaspace.hpp index 1efdf980ac8..652a2be35d9 100644 --- a/src/hotspot/share/memory/metaspace.hpp +++ b/src/hotspot/share/memory/metaspace.hpp @@ -74,13 +74,9 @@ public: #ifdef _LP64 - // Reserve a range of memory at an address suitable for en/decoding narrow - // Klass pointers (see: CompressedClassPointers::is_valid_base()). - // The returned address shall both be suitable as a compressed class pointers - // base, and aligned to Metaspace::reserve_alignment (which is equal to or a - // multiple of allocation granularity). - // On error, returns an unreserved space. - static ReservedSpace reserve_address_space_for_compressed_classes(size_t size); + // Reserve a range of memory that is to contain narrow Klass IDs. If "try_in_low_address_ranges" + // is true, we will attempt to reserve memory suitable for zero-based encoding. + static ReservedSpace reserve_address_space_for_compressed_classes(size_t size, bool try_in_low_address_ranges); // Given a prereserved space, use that to set up the compressed class space list. static void initialize_class_space(ReservedSpace rs); diff --git a/src/hotspot/share/memory/virtualspace.cpp b/src/hotspot/share/memory/virtualspace.cpp index dbbf5a3dd5f..1a99b967436 100644 --- a/src/hotspot/share/memory/virtualspace.cpp +++ b/src/hotspot/share/memory/virtualspace.cpp @@ -355,6 +355,17 @@ void ReservedSpace::release() { } } +// Put a ReservedSpace over an existing range +ReservedSpace ReservedSpace::space_for_range(char* base, size_t size, size_t alignment, + size_t page_size, bool special, bool executable) { + assert(is_aligned(base, os::vm_allocation_granularity()), "Unaligned base"); + assert(is_aligned(size, os::vm_page_size()), "Unaligned size"); + assert(os::page_sizes().contains(page_size), "Invalid pagesize"); + ReservedSpace space; + space.initialize_members(base, size, alignment, page_size, special, executable); + return space; +} + static size_t noaccess_prefix_size(size_t alignment) { return lcm(os::vm_page_size(), alignment); } @@ -546,17 +557,7 @@ void ReservedHeapSpace::initialize_compressed_heap(const size_t size, size_t ali } // zerobased: Attempt to allocate in the lower 32G. - // But leave room for the compressed class pointers, which is allocated above - // the heap. char *zerobased_max = (char *)OopEncodingHeapMax; - const size_t class_space = align_up(CompressedClassSpaceSize, alignment); - // For small heaps, save some space for compressed class pointer - // space so it can be decoded with no base. - if (UseCompressedClassPointers && !UseSharedSpaces && !DumpSharedSpaces && - OopEncodingHeapMax <= KlassEncodingMetaspaceMax && - (uint64_t)(aligned_heap_base_min_address + size + class_space) <= KlassEncodingMetaspaceMax) { - zerobased_max = (char *)OopEncodingHeapMax - class_space; - } // Give it several tries from top of range to bottom. if (aligned_heap_base_min_address + size <= zerobased_max && // Zerobased theoretical possible. diff --git a/src/hotspot/share/memory/virtualspace.hpp b/src/hotspot/share/memory/virtualspace.hpp index 65650db4f69..264ed0d9749 100644 --- a/src/hotspot/share/memory/virtualspace.hpp +++ b/src/hotspot/share/memory/virtualspace.hpp @@ -107,6 +107,10 @@ class ReservedSpace { bool contains(const void* p) const { return (base() <= ((char*)p)) && (((char*)p) < (base() + size())); } + + // Put a ReservedSpace over an existing range + static ReservedSpace space_for_range(char* base, size_t size, size_t alignment, + size_t page_size, bool special, bool executable); }; ReservedSpace diff --git a/src/hotspot/share/oops/compressedKlass.cpp b/src/hotspot/share/oops/compressedKlass.cpp index 3821e967af9..f9750c1b7c0 100644 --- a/src/hotspot/share/oops/compressedKlass.cpp +++ b/src/hotspot/share/oops/compressedKlass.cpp @@ -62,7 +62,6 @@ void CompressedKlassPointers::initialize_for_given_encoding(address addr, size_t // will encounter (and the implicit promise that there will be no Klass // structures outside this range). void CompressedKlassPointers::initialize(address addr, size_t len) { - assert(is_valid_base(addr), "Address must be a valid encoding base"); address const end = addr + len; address base; @@ -90,6 +89,8 @@ void CompressedKlassPointers::initialize(address addr, size_t len) { set_base(base); set_shift(shift); set_range(range); + + assert(is_valid_base(_base), "Address must be a valid encoding base"); } // Given an address p, return true if p can be used as an encoding base. diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index b61ff2bdaaa..52a4af0aee8 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1419,6 +1419,9 @@ const int ObjectAlignmentInBytes = 8; "Force the class space to be allocated at this address or " \ "fails VM initialization (requires -Xshare=off.") \ \ + develop(bool, RandomizeClassSpaceLocation, true, \ + "Randomize location of class space.") \ + \ product(bool, PrintMetaspaceStatisticsAtExit, false, DIAGNOSTIC, \ "Print metaspace statistics upon VM exit.") \ \ diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index c698a48ffb2..30573591a54 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -74,6 +74,7 @@ #include "utilities/count_trailing_zeros.hpp" #include "utilities/defaultStream.hpp" #include "utilities/events.hpp" +#include "utilities/fastrand.hpp" #include "utilities/powerOfTwo.hpp" #ifndef _WINDOWS @@ -1811,13 +1812,206 @@ char* os::attempt_reserve_memory_at(char* addr, size_t bytes, bool executable) { char* result = pd_attempt_reserve_memory_at(addr, bytes, executable); if (result != nullptr) { MemTracker::record_virtual_memory_reserve((address)result, bytes, CALLER_PC); + log_debug(os)("Reserved memory at " INTPTR_FORMAT " for " SIZE_FORMAT " bytes.", p2i(addr), bytes); } else { log_debug(os)("Attempt to reserve memory at " INTPTR_FORMAT " for " SIZE_FORMAT " bytes failed, errno %d", p2i(addr), bytes, get_last_error()); } + return result; } +#ifdef ASSERT +static void print_points(const char* s, unsigned* points, unsigned num) { + stringStream ss; + for (unsigned i = 0; i < num; i ++) { + ss.print("%u ", points[i]); + } + log_trace(os, map)("%s, %u Points: %s", s, num, ss.base()); +} +#endif + +// Helper for os::attempt_reserve_memory_between +// Given an array of things, shuffle them (Fisher-Yates) +template +static void shuffle_fisher_yates(T* arr, unsigned num, FastRandom& frand) { + for (unsigned i = num - 1; i >= 1; i--) { + unsigned j = frand.next() % i; + swap(arr[i], arr[j]); + } +} + +// Helper for os::attempt_reserve_memory_between +// Given an array of things, do a hemisphere split such that the resulting +// order is: [first, last, first + 1, last - 1, ...] +template +static void hemi_split(T* arr, unsigned num) { + T* tmp = (T*)::alloca(sizeof(T) * num); + for (unsigned i = 0; i < num; i++) { + tmp[i] = arr[i]; + } + for (unsigned i = 0; i < num; i++) { + arr[i] = is_even(i) ? tmp[i / 2] : tmp[num - (i / 2) - 1]; + } +} + +// Given an address range [min, max), attempts to reserve memory within this area, with the given alignment. +// If randomize is true, the location will be randomized. +char* os::attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomize) { + + // Please keep the following constants in sync with the companion gtests: + + // Number of mmap attemts we will undertake. + constexpr unsigned max_attempts = 32; + + // In randomization mode: We require a minimum number of possible attach points for + // randomness. Below that we refuse to reserve anything. + constexpr unsigned min_random_value_range = 16; + + // In randomization mode: If the possible value range is below this threshold, we + // use a total shuffle without regard for address space fragmentation, otherwise + // we attempt to minimize fragmentation. + constexpr unsigned total_shuffle_threshold = 1024; + +#define ARGSFMT " range [" PTR_FORMAT "-" PTR_FORMAT "), size " SIZE_FORMAT_X ", alignment " SIZE_FORMAT_X ", randomize: %d" +#define ARGSFMTARGS p2i(min), p2i(max), bytes, alignment, randomize + + log_trace(os, map) ("reserve_between (" ARGSFMT ")", ARGSFMTARGS); + + assert(is_power_of_2(alignment), "alignment invalid (" ARGSFMT ")", ARGSFMTARGS); + assert(alignment < SIZE_MAX / 2, "alignment too large (" ARGSFMT ")", ARGSFMTARGS); + assert(is_aligned(bytes, os::vm_page_size()), "size not page aligned (" ARGSFMT ")", ARGSFMTARGS); + assert(max >= min, "invalid range (" ARGSFMT ")", ARGSFMTARGS); + + char* const absolute_max = (char*)(NOT_LP64(G * 3) LP64_ONLY(G * 128 * 1024)); + char* const absolute_min = (char*) os::vm_min_address(); + + const size_t alignment_adjusted = MAX2(alignment, os::vm_allocation_granularity()); + + // Calculate first and last possible attach points: + char* const lo_att = align_up(MAX2(absolute_min, min), alignment_adjusted); + if (lo_att == nullptr) { + return nullptr; // overflow + } + + char* const hi_att = align_down(MIN2(max, absolute_max) - bytes, alignment_adjusted); + if (hi_att > max) { + return nullptr; // overflow + } + + // no possible attach points + if (hi_att < lo_att) { + return nullptr; + } + + char* result = nullptr; + + const size_t num_attach_points = (size_t)((hi_att - lo_att) / alignment_adjusted) + 1; + assert(num_attach_points > 0, "Sanity"); + + // If this fires, the input range is too large for the given alignment (we work + // with int below to keep things simple). Since alignment is bound to page size, + // and the lowest page size is 4K, this gives us a minimum of 4K*4G=8TB address + // range. + assert(num_attach_points <= UINT_MAX, + "Too many possible attach points - range too large or alignment too small (" ARGSFMT ")", ARGSFMTARGS); + + const unsigned num_attempts = MIN2((unsigned)num_attach_points, max_attempts); + unsigned points[max_attempts]; + + if (randomize) { + FastRandom frand; + + if (num_attach_points < min_random_value_range) { + return nullptr; + } + + // We pre-calc the attach points: + // 1 We divide the attach range into equidistant sections and calculate an attach + // point within each section. + // 2 We wiggle those attach points around within their section (depends on attach + // point granularity) + // 3 Should that not be enough to get effective randomization, shuffle all + // attach points + // 4 Otherwise, re-order them to get an optimized probing sequence. + const unsigned stepsize = (unsigned)num_attach_points / num_attempts; + const unsigned half = num_attempts / 2; + + // 1+2: pre-calc points + for (unsigned i = 0; i < num_attempts; i++) { + const unsigned deviation = stepsize > 1 ? (frand.next() % stepsize) : 0; + points[i] = (i * stepsize) + deviation; + } + + if (num_attach_points < total_shuffle_threshold) { + // 3: + // The numeber of possible attach points is too low for the "wiggle" from + // point 2 to be enough to provide randomization. In that case, shuffle + // all attach points at the cost of possible fragmentation (e.g. if we + // end up mapping into the middle of the range). + shuffle_fisher_yates(points, num_attempts, frand); + } else { + // 4 + // We have a large enough number of attach points to satisfy the randomness + // goal without. In that case, we optimize probing by sorting the attach + // points: We attempt outermost points first, then work ourselves up to + // the middle. That reduces address space fragmentation. We also alternate + // hemispheres, which increases the chance of successfull mappings if the + // previous mapping had been blocked by large maps. + hemi_split(points, num_attempts); + } + } // end: randomized + else + { + // Non-randomized. We just attempt to reserve by probing sequentially. We + // alternate between hemispheres, working ourselves up to the middle. + const int stepsize = (unsigned)num_attach_points / num_attempts; + for (unsigned i = 0; i < num_attempts; i++) { + points[i] = (i * stepsize); + } + hemi_split(points, num_attempts); + } + +#ifdef ASSERT + // Print + check all pre-calculated attach points + print_points("before reserve", points, num_attempts); + for (unsigned i = 0; i < num_attempts; i++) { + assert(points[i] < num_attach_points, "Candidate attach point %d out of range (%u, num_attach_points: %zu) " ARGSFMT, + i, points[i], num_attach_points, ARGSFMTARGS); + } +#endif + + // Now reserve + for (unsigned i = 0; result == nullptr && i < num_attempts; i++) { + const unsigned candidate_offset = points[i]; + char* const candidate = lo_att + candidate_offset * alignment_adjusted; + assert(candidate <= hi_att, "Invalid offset %u (" ARGSFMT ")", candidate_offset, ARGSFMTARGS); + result = os::pd_attempt_reserve_memory_at(candidate, bytes, false); + if (!result) { + log_trace(os, map)("Failed to attach at " PTR_FORMAT, p2i(candidate)); + } + } + + // Sanity checks, logging, NMT stuff: + if (result != nullptr) { +#define ERRFMT "result: " PTR_FORMAT " " ARGSFMT +#define ERRFMTARGS p2i(result), ARGSFMTARGS + assert(result >= min, "OOB min (" ERRFMT ")", ERRFMTARGS); + assert((result + bytes) <= max, "OOB max (" ERRFMT ")", ERRFMTARGS); + assert(result >= (char*)os::vm_min_address(), "OOB vm.map min (" ERRFMT ")", ERRFMTARGS); + assert((result + bytes) <= absolute_max, "OOB vm.map max (" ERRFMT ")", ERRFMTARGS); + assert(is_aligned(result, alignment), "alignment invalid (" ERRFMT ")", ERRFMTARGS); + log_trace(os, map)(ERRFMT, ERRFMTARGS); + log_debug(os, map)("successfully attached at " PTR_FORMAT, p2i(result)); + MemTracker::record_virtual_memory_reserve((address)result, bytes, CALLER_PC); + } + return result; +#undef ARGSFMT +#undef ERRFMT +#undef ARGSFMTARGS +#undef ERRFMTARGS +} + static void assert_nonempty_range(const char* addr, size_t bytes) { assert(addr != nullptr && bytes > 0, "invalid range [" PTR_FORMAT ", " PTR_FORMAT ")", p2i(addr), p2i(addr) + bytes); diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index e13ce72e886..7069a4705a9 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -190,6 +190,11 @@ class os: AllStatic { static OSThread* _starting_thread; static PageSizes _page_sizes; + // The default value for os::vm_min_address() unless the platform knows better. This value + // is chosen to give us reasonable protection against NULL pointer dereferences while being + // low enough to leave most of the valuable low-4gb address space open. + static constexpr size_t _vm_min_address_default = 16 * M; + static char* pd_reserve_memory(size_t bytes, bool executable); static char* pd_attempt_reserve_memory_at(char* addr, size_t bytes, bool executable); @@ -420,6 +425,9 @@ class os: AllStatic { static size_t vm_allocation_granularity() { return OSInfo::vm_allocation_granularity(); } + // Returns the lowest address the process is allowed to map against. + static size_t vm_min_address(); + inline static size_t cds_core_region_alignment(); // Reserves virtual memory. @@ -432,6 +440,10 @@ class os: AllStatic { // Does not overwrite existing mappings. static char* attempt_reserve_memory_at(char* addr, size_t bytes, bool executable = false); + // Given an address range [min, max), attempts to reserve memory within this area, with the given alignment. + // If randomize is true, the location will be randomized. + static char* attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomize); + static bool commit_memory(char* addr, size_t bytes, bool executable); static bool commit_memory(char* addr, size_t size, size_t alignment_hint, bool executable); diff --git a/src/hotspot/share/utilities/fastrand.hpp b/src/hotspot/share/utilities/fastrand.hpp new file mode 100644 index 00000000000..30626963fef --- /dev/null +++ b/src/hotspot/share/utilities/fastrand.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Red Hat, 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. + * + */ + +#ifndef SHARE_UTILITIES_FASTRAND_HPP +#define SHARE_UTILITIES_FASTRAND_HPP + +#include "runtime/os.hpp" +#include "utilities/globalDefinitions.hpp" + +// Simple utility class to generate random numbers for use in a single-threaded +// context. Since os::random() needs to update the global seed, this is faster +// when used on within a single thread. +// Seed initialization happens, similar to os::init_random(), via os::javaTimeNanos()); + +class FastRandom { + unsigned _seed; + public: + FastRandom () : _seed((unsigned) os::javaTimeNanos()) {} + unsigned next() { + _seed = os::next_random(_seed); + return _seed; + } +}; + +#endif // SHARE_UTILITIES_FASTRAND_HPP diff --git a/test/hotspot/gtest/runtime/test_os.cpp b/test/hotspot/gtest/runtime/test_os.cpp index bcd532d1bcf..9956956423c 100644 --- a/test/hotspot/gtest/runtime/test_os.cpp +++ b/test/hotspot/gtest/runtime/test_os.cpp @@ -947,3 +947,14 @@ TEST_VM(os, reserve_at_wish_address_shall_not_replace_mappings_largepages) { tty->print_cr("Skipped."); } } + +TEST_VM(os, vm_min_address) { + size_t s = os::vm_min_address(); + ASSERT_GE(s, M); + // Test upper limit. On Linux, its adjustable, so we just test for absurd values to prevent errors + // with high vm.mmap_min_addr settings. +#if defined(_LP64) + ASSERT_LE(s, NOT_LINUX(G * 4) LINUX_ONLY(G * 1024)); +#endif +} + diff --git a/test/hotspot/gtest/runtime/test_os_reserve_between.cpp b/test/hotspot/gtest/runtime/test_os_reserve_between.cpp new file mode 100644 index 00000000000..a6e1ed1f239 --- /dev/null +++ b/test/hotspot/gtest/runtime/test_os_reserve_between.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2023, Red Hat, Inc. All rights reserved. + * Copyright (c) 2023, 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 + * 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 "precompiled.hpp" +#include "logging/log.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/os.hpp" +#include "utilities/align.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/macros.hpp" +#include "utilities/resourceHash.hpp" + +#define LOG_PLEASE +#include "testutils.hpp" +#include "unittest.hpp" + +// On AIX, these tests make no sense as long as JDK-8315321 remains unfixed since the attach +// addresses are not predictable. +#ifndef AIX + +// Must be the same as in os::attempt_reserve_memory_between() +struct ARMB_constants { + static constexpr uintptr_t absolute_max = NOT_LP64(G * 3) LP64_ONLY(G * 128 * 1024); + static constexpr unsigned max_attempts = 32; + static constexpr unsigned min_random_value_range = 16; + static constexpr unsigned total_shuffle_threshold = 1024; +}; + +// Testing os::attempt_reserve_memory_between() + +static void release_if_needed(char* p, size_t s) { + if (p != nullptr) { + os::release_memory(p, s); + } +} + +#define ERRINFO "addr: " << ((void*)addr) << " min: " << ((void*)min) << " max: " << ((void*)max) \ + << " bytes: " << bytes << " alignment: " << alignment << " randomized: " << randomized + +static char* call_attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomized) { + char* const addr = os::attempt_reserve_memory_between(min, max, bytes, alignment, randomized); + if (addr != nullptr) { + EXPECT_TRUE(is_aligned(addr, alignment)) << ERRINFO; + EXPECT_TRUE(is_aligned(addr, os::vm_allocation_granularity())) << ERRINFO; + EXPECT_LE(addr, max - bytes) << ERRINFO; + EXPECT_LE(addr, (char*)ARMB_constants::absolute_max - bytes) << ERRINFO; + EXPECT_GE(addr, min) << ERRINFO; + EXPECT_GE(addr, (char*)os::vm_min_address()) << ERRINFO; + } + return addr; +} + +class Expect { + const bool _expect_success; + const bool _expect_failure; + const char* const _expected_result; // if _expect_success +public: + Expect(bool expect_success, bool expect_failure, char* expected_result) + : _expect_success(expect_success), _expect_failure(expect_failure), _expected_result(expected_result) + { + assert(!expect_success || !expect_failure, "make up your mind"); + } + bool check_reality(char* result) const { + if (_expect_failure) { + return result == nullptr; + } + if (_expect_success) { + return (_expected_result == nullptr) ? result != nullptr : result == _expected_result; + } + return true; + } + static Expect failure() { return Expect(false, true, nullptr); } + static Expect success_any() { return Expect(true, false, nullptr); } + static Expect success(char* addr) { return Expect(true, false, addr); } + static Expect dontcare() { return Expect(false, false, nullptr); } +}; + +static void test_attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomized, + Expect expectation, int line = -1) { + char* const addr = call_attempt_reserve_memory_between(min, max, bytes, alignment, randomized); + EXPECT_TRUE(expectation.check_reality(addr)) << ERRINFO << " L" << line; + release_if_needed(addr, bytes); +} +#undef ERRINFO + +// Helper for attempt_reserve_memory_between tests to +// reserve an area with a hole in the middle +struct SpaceWithHole { + char* _base; + const size_t _len; + const size_t _hole_offset; + const size_t _hole_size; + + + static constexpr size_t _p1_offset = 0; + const size_t _p1_size; + const size_t _p2_offset; + const size_t _p2_size; + + char* _p1; + char* _p2; + + size_t p1size() const { return hole_offset(); } + size_t p2size() const { return _len - hole_size() - hole_offset(); } + +public: + + char* base() const { return _base; } + char* end() const { return _base + _len; } + char* hole() const { return _base + hole_offset(); } + char* hole_end() const { return hole() + hole_size(); } + + size_t hole_size() const { return _hole_size; } + size_t hole_offset() const { return _hole_offset; } + + SpaceWithHole(size_t total_size, size_t hole_offset, size_t hole_size) : + _base(nullptr), _len(total_size), _hole_offset(hole_offset), _hole_size(hole_size), + _p1_size(hole_offset), _p2_offset(hole_offset + hole_size), _p2_size(total_size - hole_offset - hole_size), + _p1(nullptr), _p2(nullptr) + { + assert(_p1_size > 0 && _p2_size > 0, "Cannot have holes at the border"); + } + + bool reserve() { + // We cannot create a hole by punching, since NMT cannot cope with releases + // crossing reservation boundaries. Therefore we first reserve the total, + // release it again, reserve the parts. + for (int i = 56; _base == nullptr && i > 32; i--) { + // We reserve at weird outlier addresses, in order to minimize the chance of concurrent mmaps grabbing + // the hole. + const uintptr_t candidate = nth_bit(i); + if ((candidate + _len) <= ARMB_constants::absolute_max) { + _base = os::attempt_reserve_memory_at((char*)candidate, _len); + } + } + if (_base == nullptr) { + return false; + } + // Release total mapping, remap the individual non-holy parts + os::release_memory(_base, _len); + _p1 = os::attempt_reserve_memory_at(_base + _p1_offset, _p1_size); + _p2 = os::attempt_reserve_memory_at(_base + _p2_offset, _p2_size); + if (_p1 == nullptr || _p2 == nullptr) { + return false; + } + LOG_HERE("SpaceWithHole: [" PTR_FORMAT " ... [" PTR_FORMAT " ... " PTR_FORMAT ") ... " PTR_FORMAT ")", + p2i(base()), p2i(hole()), p2i(hole_end()), p2i(end())); + return true; + } + + ~SpaceWithHole() { + release_if_needed(_p1, _p1_size); + release_if_needed(_p2, _p2_size); + } +}; + +// Test that, when reserving in a range randomly, we get random results +static void test_attempt_reserve_memory_between_random_distribution(unsigned num_possible_attach_points) { + + const size_t ag = os::vm_allocation_granularity(); + + // Create a space that is mostly a hole bordered by two small stripes of reserved memory, with + // as many attach points as we need. + SpaceWithHole space((2 + num_possible_attach_points) * ag, ag, num_possible_attach_points * ag); + if (!space.reserve()) { + tty->print_cr("Failed to reserve holed space, skipping."); + return; + } + + const size_t bytes = ag; + const size_t alignment = ag; + + // Below this threshold the API should never return memory since the randomness is too weak. + const bool expect_failure = (num_possible_attach_points < ARMB_constants::min_random_value_range); + + // Below this threshold we expect values to be completely random, otherwise they randomized but still ordered. + const bool total_shuffled = (num_possible_attach_points < ARMB_constants::total_shuffle_threshold); + + // Allocate n times within that hole (with subsequent deletions) and remember unique addresses returned. + constexpr unsigned num_tries_per_attach_point = 100; + ResourceMark rm; + ResourceHashtable ht; + const unsigned num_tries = expect_failure ? 3 : (num_possible_attach_points * num_tries_per_attach_point); + unsigned num_uniq = 0; // Number of uniq addresses returned + + // In "total shuffle" mode, all possible attach points are randomized; outside that mode, the API + // attempts to limit fragmentation by favouring the ends of the ranges. + const unsigned expected_variance = + total_shuffled ? num_possible_attach_points : (num_possible_attach_points / ARMB_constants::max_attempts); + + // Its not easy to find a good threshold for automated tests to test randomness + // that rules out intermittent errors. We apply a generous fudge factor. + constexpr double fudge_factor = 0.25f; + const unsigned expected_variance_with_fudge = MAX2(2u, (unsigned)((double)expected_variance * fudge_factor)); + +#define ERRINFO " num_possible_attach_points: " << num_possible_attach_points << " total_shuffle? " << total_shuffled \ + << " expected variance: " << expected_variance << " with fudge: " << expected_variance_with_fudge \ + << " alignment: " << alignment << " bytes: " << bytes; + + for (unsigned i = 0; i < num_tries && + num_uniq < expected_variance_with_fudge; // Stop early if we confirmed enough variance. + i ++) { + char* p = call_attempt_reserve_memory_between(space.base(), space.end(), bytes, alignment, true); + if (p != nullptr) { + ASSERT_GE(p, space.hole()) << ERRINFO; + ASSERT_LE(p + bytes, space.hole_end()) << ERRINFO; + release_if_needed(p, bytes); + bool created = false; + unsigned* num = ht.put_if_absent(p, 0, &created); + (*num) ++; + num_uniq = (unsigned)ht.number_of_entries(); + } + } + + ASSERT_LE(num_uniq, num_possible_attach_points) << num_uniq << ERRINFO; + + if (!expect_failure) { + ASSERT_GE(num_uniq, expected_variance_with_fudge) << ERRINFO; + } +#undef ERRINFO +} + +#define RANDOMIZED_RANGE_TEST(num) \ + TEST_VM(os, attempt_reserve_memory_between_random_distribution_ ## num ## _attach_points) { \ + test_attempt_reserve_memory_between_random_distribution(num); \ +} + +RANDOMIZED_RANGE_TEST(2) +RANDOMIZED_RANGE_TEST(15) +RANDOMIZED_RANGE_TEST(16) +RANDOMIZED_RANGE_TEST(712) +RANDOMIZED_RANGE_TEST(12000) + +// Test that, given a smallish range - not many attach points - with a hole, we attach within that hole. +TEST_VM(os, attempt_reserve_memory_randomization_threshold) { + + constexpr int threshold = ARMB_constants::min_random_value_range; + const size_t ps = os::vm_page_size(); + const size_t ag = os::vm_allocation_granularity(); + + SpaceWithHole space(ag * (threshold + 2), ag, ag * threshold); + if (!space.reserve()) { + tty->print_cr("Failed to reserve holed space, skipping."); + return; + } + + // Test with a range that only allows for (threshold - 1) reservations + test_attempt_reserve_memory_between(space.hole(), space.hole_end() - ag, ps, ag, true, Expect::failure()); + + // Test with a range just above the threshold. Should succeed. + test_attempt_reserve_memory_between(space.hole(), space.hole_end(), ps, ag, true, Expect::success_any()); +} + +// Test all possible combos +TEST_VM(os, attempt_reserve_memory_between_combos) { + const size_t large_end = NOT_LP64(G) LP64_ONLY(64 * G); + for (size_t range_size = os::vm_allocation_granularity(); range_size <= large_end; range_size *= 2) { + for (size_t start_offset = 0; start_offset <= large_end; start_offset += (large_end / 2)) { + char* const min = (char*)(uintptr_t)start_offset; + char* const max = min + range_size; + for (size_t bytes = os::vm_page_size(); bytes < large_end; bytes *= 2) { + for (size_t alignment = os::vm_allocation_granularity(); alignment < large_end; alignment *= 2) { + test_attempt_reserve_memory_between(min, max, bytes, alignment, true, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between(min, max, bytes, alignment, false, Expect::dontcare(), __LINE__); + } + } + } + } +} + +TEST_VM(os, attempt_reserve_memory_randomization_cornercases) { + const size_t ps = os::vm_page_size(); + const size_t ag = os::vm_allocation_granularity(); + constexpr size_t quarter_address_space = NOT_LP64(nth_bit(30)) LP64_ONLY(nth_bit(62)); + + // Zero-sized range + test_attempt_reserve_memory_between(nullptr, nullptr, ps, ag, false, Expect::failure()); + test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, false, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between((char*)SIZE_MAX, (char*)SIZE_MAX, ps, ag, false, Expect::failure(), __LINE__); + + test_attempt_reserve_memory_between(nullptr, nullptr, ps, ag, true, Expect::failure()); + test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, true, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, true, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between((char*)SIZE_MAX, (char*)SIZE_MAX, ps, ag, true, Expect::failure(), __LINE__); + + // Full size + // Note: paradoxically, success is not guaranteed here, since a significant portion of the attach points + // could be located in un-allocatable territory. + test_attempt_reserve_memory_between(nullptr, (char*)SIZE_MAX, ps, quarter_address_space / 8, false, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between(nullptr, (char*)SIZE_MAX, ps, quarter_address_space / 8, true, Expect::dontcare(), __LINE__); + + // Very small range at start + test_attempt_reserve_memory_between(nullptr, (char*)ag, ps, ag, false, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between(nullptr, (char*)ag, ps, ag, true, Expect::dontcare(), __LINE__); + + // Very small range at end + test_attempt_reserve_memory_between((char*)(SIZE_MAX - (ag * 2)), (char*)(SIZE_MAX), ps, ag, false, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between((char*)(SIZE_MAX - (ag * 2)), (char*)(SIZE_MAX), ps, ag, true, Expect::dontcare(), __LINE__); + + // At start, high alignment, check if we run into neg. overflow problems + test_attempt_reserve_memory_between(nullptr, (char*)G, ps, G, false, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between(nullptr, (char*)G, ps, G, true, Expect::dontcare(), __LINE__); + + // At start, very high alignment, check if we run into neg. overflow problems + test_attempt_reserve_memory_between((char*)quarter_address_space, (char*)SIZE_MAX, ps, quarter_address_space, false, Expect::dontcare(), __LINE__); + test_attempt_reserve_memory_between((char*)quarter_address_space, (char*)SIZE_MAX, ps, quarter_address_space, true, Expect::dontcare(), __LINE__); +} + +// Test that, regardless where the hole is in the [min, max) range, if we probe nonrandomly, we will fill that hole +// as long as the range size is smaller than the number of probe attempts +TEST_VM(os, attempt_reserve_memory_between_small_range_fill_hole) { + const size_t ps = os::vm_page_size(); + const size_t ag = os::vm_allocation_granularity(); + constexpr int num = ARMB_constants::max_attempts; + for (int i = 0; i < num; i ++) { + SpaceWithHole space(ag * (num + 2), ag * (i + 1), ag); + if (!space.reserve()) { + tty->print_cr("Failed to reserve holed space, skipping."); + } else { + test_attempt_reserve_memory_between(space.base() + ag, space.end() - ag, space.hole_size(), space.hole_size(), false, Expect::success(space.hole()), __LINE__); + } + } +} + +#endif // AIX diff --git a/test/hotspot/gtest/testutils.hpp b/test/hotspot/gtest/testutils.hpp index 6b3d42f1127..c4c093fff23 100644 --- a/test/hotspot/gtest/testutils.hpp +++ b/test/hotspot/gtest/testutils.hpp @@ -68,4 +68,7 @@ public: #define LOG_HERE(s, ...) #endif +// handy for error analysis +#define PING { printf("%s:%d\n", __FILE__, __LINE__); fflush(stdout); } + #endif // TESTUTILS_HPP