jdk/test/hotspot/gtest/gc/z/test_zVirtualMemoryManager.cpp
2026-01-22 22:21:40 -05:00

272 lines
9.9 KiB
C++

/*
* Copyright (c) 2024, 2025, 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 "gc/z/zAddress.inline.hpp"
#include "gc/z/zArguments.hpp"
#include "gc/z/zGlobals.hpp"
#include "gc/z/zInitialize.hpp"
#include "gc/z/zList.inline.hpp"
#include "gc/z/zNUMA.inline.hpp"
#include "gc/z/zValue.inline.hpp"
#include "gc/z/zVirtualMemoryManager.inline.hpp"
#include "runtime/os.hpp"
#include "zunittest.hpp"
using namespace testing;
#define ASSERT_REMOVAL_OK(range, sz) ASSERT_FALSE(range.is_null()); ASSERT_EQ(range.size(), (sz))
class ZCallbacksResetter {
private:
ZVirtualMemoryRegistry::Callbacks* _callbacks;
ZVirtualMemoryRegistry::Callbacks _saved;
public:
ZCallbacksResetter(ZVirtualMemoryRegistry::Callbacks* callbacks)
: _callbacks(callbacks),
_saved(*callbacks) {
*_callbacks = {};
}
~ZCallbacksResetter() {
*_callbacks = _saved;
}
};
class ZVirtualMemoryManagerTest : public ZTest {
private:
static constexpr size_t ReservationSize = 32 * M;
ZAddressReserver _zaddress_reserver;
ZVirtualMemoryReserver* _reserver;
ZVirtualMemoryRegistry* _registry;
public:
virtual void SetUp() {
_zaddress_reserver.SetUp(ReservationSize);
_reserver = _zaddress_reserver.reserver();
_registry = _zaddress_reserver.registry();
if (_reserver->reserved() < ReservationSize || !_registry->is_contiguous()) {
GTEST_SKIP() << "Fixture failed to reserve adequate memory, reserved "
<< (_reserver->reserved() >> ZGranuleSizeShift) << " * ZGranuleSize";
}
}
virtual void TearDown() {
_registry = nullptr;
_reserver = nullptr;
_zaddress_reserver.TearDown();
}
void test_reserve_discontiguous_and_coalesce() {
// Start by ensuring that we have 3 unreserved granules, and then let the
// fourth granule be pre-reserved and therefore blocking subsequent requests
// to reserve memory.
//
// +----+----+----+----+
// ----- pre-reserved - to block contiguous reservation
// --------------- unreserved - to allow reservation of 3 granules
//
// If we then asks for 4 granules starting at the first granule above,
// then we won't be able to reserve 4 consecutive granules and the code
// reverts into the discontiguous mode. This mode uses interval halving
// to find the limits of memory areas that have already been reserved.
// This will lead to the first 2 granules being reserved, then the third
// granule will be reserved.
//
// The problem we had with this is that this would yield two separate
// placeholder reservations, even though they are adjacent. The callbacks
// are supposed to fix that by coalescing the placeholders, *but* the
// callbacks used to be only turned on *after* the reservation call. So,
// we end up with one 3 granule large memory area in the manager, which
// unexpectedly was covered by two placeholders (instead of the expected
// one placeholder).
//
// Later when the callbacks had been installed and we tried to fetch memory
// from the manager, the callbacks would try to split off the placeholder
// to separate the fetched memory from the memory left in the manager. This
// used to fail because the memory was already split into two placeholders.
// Start at the offset we reserved.
const zoffset base_offset = _registry->peek_low_address();
// Empty the reserved memory in preparation for the rest of the test.
_reserver->unreserve_all();
const zaddress_unsafe base = ZOffset::address_unsafe(base_offset);
const zaddress_unsafe blocked = base + 3 * ZGranuleSize;
// Reserve the memory that is acting as a blocking reservation.
{
char* const result = os::attempt_reserve_memory_at((char*)untype(blocked), ZGranuleSize, mtTest);
if (uintptr_t(result) != untype(blocked)) {
GTEST_SKIP() << "Failed to reserve requested memory at " << untype(blocked);
}
}
{
// This ends up reserving 2 granules and then 1 granule adjacent to the
// first. In previous implementations this resulted in two separate
// placeholders (4MB and 2MB). This was a bug, because the manager is
// designed to have one placeholder per memory area. This in turn would
// lead to a subsequent failure when _vmr->remove* tried to split off the
// 4MB that is already covered by its own placeholder. You can't place
// a placeholder over an already existing placeholder.
// To reproduce this, the test needed to mimic the initializing memory
// reservation code which had the placeholders turned off. This was done
// with this helper:
//
// ZCallbacksResetter resetter(&_va->_callbacks);
//
// After the fix, we always have the callbacks turned on, so we don't
// need this to mimic the initializing memory reservation.
const size_t reserved = _reserver->reserve_discontiguous(base_offset, 4 * ZGranuleSize, ZGranuleSize);
ASSERT_LE(reserved, 3 * ZGranuleSize);
if (reserved < 3 * ZGranuleSize) {
GTEST_SKIP() << "Failed reserve_discontiguous"
", expected 3 * ZGranuleSize, got " << (reserved >> ZGranuleSizeShift)
<< " * ZGranuleSize";
}
}
{
// The test used to crash here because the 3 granule memory area was
// inadvertently covered by two place holders (2 granules + 1 granule).
const ZVirtualMemory vmem = _registry->remove_from_low(2 * ZGranuleSize);
ASSERT_EQ(vmem, ZVirtualMemory(base_offset, 2 * ZGranuleSize));
// Cleanup - Must happen in granule-sizes because of how Windows hands
// out memory in granule-sized placeholder reservations.
_reserver->unreserve(vmem.first_part(ZGranuleSize));
_reserver->unreserve(vmem.last_part(ZGranuleSize));
}
// Final cleanup
const ZVirtualMemory vmem = _registry->remove_from_low(ZGranuleSize);
ASSERT_EQ(vmem, ZVirtualMemory(base_offset + 2 * ZGranuleSize, ZGranuleSize));
_reserver->unreserve(vmem);
os::release_memory((char*)untype(blocked), ZGranuleSize);
}
void test_remove_from_low() {
{
// Verify that we get a placeholder for the first granule
const ZVirtualMemory removed = _registry->remove_from_low(ZGranuleSize);
ASSERT_REMOVAL_OK(removed, ZGranuleSize);
_registry->insert(removed);
}
{
// Remove something larger than a granule and then insert it
const ZVirtualMemory removed = _registry->remove_from_low(3 * ZGranuleSize);
ASSERT_REMOVAL_OK(removed, 3 * ZGranuleSize);
_registry->insert(removed);
}
{
// Insert with more memory removed
const ZVirtualMemory removed = _registry->remove_from_low(ZGranuleSize);
ASSERT_REMOVAL_OK(removed, ZGranuleSize);
ZVirtualMemory next = _registry->remove_from_low(ZGranuleSize);
ASSERT_REMOVAL_OK(next, ZGranuleSize);
_registry->insert(removed);
_registry->insert(next);
}
}
void test_remove_from_high() {
{
// Verify that we get a placeholder for the last granule
const ZVirtualMemory high = _registry->remove_from_high(ZGranuleSize);
ASSERT_REMOVAL_OK(high, ZGranuleSize);
const ZVirtualMemory prev = _registry->remove_from_high(ZGranuleSize);
ASSERT_REMOVAL_OK(prev, ZGranuleSize);
_registry->insert(high);
_registry->insert(prev);
}
{
// Remove something larger than a granule and return it
const ZVirtualMemory high = _registry->remove_from_high(2 * ZGranuleSize);
ASSERT_REMOVAL_OK(high, 2 * ZGranuleSize);
_registry->insert(high);
}
}
void test_remove_whole() {
// Need a local variable to appease gtest
const size_t reservation_size = ReservationSize;
// Remove the whole reservation
const ZVirtualMemory reserved = _registry->remove_from_low(reservation_size);
ASSERT_REMOVAL_OK(reserved, reservation_size);
const ZVirtualMemory first(reserved.start(), 4 * ZGranuleSize);
const ZVirtualMemory second(reserved.start() + 6 * ZGranuleSize, 6 * ZGranuleSize);
// Insert two chunks and then remove them again
_registry->insert(first);
_registry->insert(second);
const ZVirtualMemory removed_first = _registry->remove_from_low(first.size());
ASSERT_EQ(removed_first, first);
const ZVirtualMemory removed_second = _registry->remove_from_low(second.size());
ASSERT_EQ(removed_second, second);
// Now insert it all, and verify it can be re-removed
_registry->insert(reserved);
const ZVirtualMemory removed_reserved = _registry->remove_from_low(reservation_size);
ASSERT_EQ(removed_reserved, reserved);
_registry->insert(reserved);
}
};
TEST_VM_F(ZVirtualMemoryManagerTest, test_reserve_discontiguous_and_coalesce) {
test_reserve_discontiguous_and_coalesce();
}
TEST_VM_F(ZVirtualMemoryManagerTest, test_remove_from_low) {
test_remove_from_low();
}
TEST_VM_F(ZVirtualMemoryManagerTest, test_remove_from_high) {
test_remove_from_high();
}
TEST_VM_F(ZVirtualMemoryManagerTest, test_remove_whole) {
test_remove_whole();
}