mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
272 lines
9.9 KiB
C++
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();
|
|
}
|