diff --git a/src/hotspot/share/utilities/bitMap.cpp b/src/hotspot/share/utilities/bitMap.cpp index 3cef6122557..9bd5c8e0006 100644 --- a/src/hotspot/share/utilities/bitMap.cpp +++ b/src/hotspot/share/utilities/bitMap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, 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 @@ -99,6 +99,54 @@ void GrowableBitMap::resize(idx_t new_size_in_bits, bool cl update(map, new_size_in_bits); } +template +bm_word_t* GrowableBitMap::copy_of_range(idx_t start_bit, idx_t end_bit) { + assert(start_bit < end_bit, "End bit must come after start bit"); + assert(end_bit <= size(), "End bit not in bitmap"); + + // We might have extra bits at the end that we don't want to lose + const idx_t cutoff = bit_in_word(end_bit); + const idx_t start_word = to_words_align_down(start_bit); + const idx_t end_word = to_words_align_up(end_bit); + const bm_word_t* const old_map = map(); + + const BitMapWithAllocator* const derived = static_cast(this); + + bm_word_t* const new_map = derived->allocate(end_word - start_word); + + // All words need to be shifted by this amount + const idx_t shift = bit_in_word(start_bit); + // Bits shifted out by a word need to be passed into the next + bm_word_t carry = 0; + + // Iterate the map backwards as the shift will result in carry-out bits + for (idx_t i = end_word; i-- > start_word;) { + new_map[i-start_word] = old_map[i] >> shift; + + if (shift != 0) { + new_map[i-start_word] |= carry; + carry = old_map[i] << (BitsPerWord - shift); + } + } + + return new_map; +} + +template +void GrowableBitMap::truncate(idx_t start_bit, idx_t end_bit) { + const size_t old_size_in_words = calc_size_in_words(size()); + const idx_t new_size_in_bits = end_bit - start_bit; + bm_word_t* const old_map = map(); + + bm_word_t* const new_map = copy_of_range(start_bit, end_bit); + + const BitMapWithAllocator* const derived = static_cast(this); + // Free and clear old map to avoid left over bits + derived->free(old_map, old_size_in_words); + update(nullptr, 0); + update(new_map, new_size_in_bits); +} + ArenaBitMap::ArenaBitMap(Arena* arena, idx_t size_in_bits, bool clear) : GrowableBitMap(), _arena(arena) { initialize(size_in_bits, clear); diff --git a/src/hotspot/share/utilities/bitMap.hpp b/src/hotspot/share/utilities/bitMap.hpp index fd696bc7502..6d83c5cdad9 100644 --- a/src/hotspot/share/utilities/bitMap.hpp +++ b/src/hotspot/share/utilities/bitMap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, 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 @@ -566,6 +566,11 @@ class GrowableBitMap : public BitMap { GrowableBitMap() : GrowableBitMap(nullptr, 0) {} GrowableBitMap(bm_word_t* map, idx_t size_in_bits) : BitMap(map, size_in_bits) {} + private: + // Copy the region [start, end) of the bitmap + // Bits in the selected range are copied to a newly allocated map + bm_word_t* copy_of_range(idx_t start_bit, idx_t end_bit); + public: // Set up and optionally clear the bitmap memory. // @@ -585,6 +590,9 @@ class GrowableBitMap : public BitMap { // Old bits are transferred to the new memory // and the extended memory is optionally cleared. void resize(idx_t new_size_in_bits, bool clear = true); + // Reduce bitmap to the region [start, end) + // Previous map is deallocated and replaced with the newly allocated map from copy_of_range + void truncate(idx_t start_bit, idx_t end_bit); }; // A concrete implementation of the "abstract" BitMap class. diff --git a/test/hotspot/gtest/utilities/test_bitMap.cpp b/test/hotspot/gtest/utilities/test_bitMap.cpp index 7d18d66f45d..aeb41693286 100644 --- a/test/hotspot/gtest/utilities/test_bitMap.cpp +++ b/test/hotspot/gtest/utilities/test_bitMap.cpp @@ -114,6 +114,165 @@ class BitMapTest { #endif }; +class BitMapTruncateTest { + + public: + const static BitMap::idx_t BITMAP_SIZE = 128; + + template + static void fillBitMap(ResizableBitMapClass& map, BitMap::idx_t size) { + BitMap::idx_t set_bits[] = {0, 31, 63, 64, 95, 127}; + for (BitMap::idx_t bit : set_bits) { + if (bit < size) { + map.set_bit(bit); + } + } + } + + template + static void testTruncateOneWord() { + ResourceMark rm; + + ResizableBitMapClass map(64); + map.set_bit(0); + map.set_bit(1); + map.set_bit(2); + map.set_bit(3); + + ResizableBitMapClass result(2); + result.set_bit(0); + result.set_bit(1); + + map.truncate(1, 3); + + EXPECT_TRUE(map.is_same(result)); + } + + template + static void testTruncateSame() { + // Resulting map should be the same as the original + ResourceMark rm; + ResizableBitMapClass expected(BITMAP_SIZE); + fillBitMap(expected, BITMAP_SIZE); + + ResizableBitMapClass map(BITMAP_SIZE); + fillBitMap(map, BITMAP_SIZE); + map.truncate(0, BITMAP_SIZE); + + EXPECT_TRUE(map.is_same(expected)); + } + + template + static void testTruncateStart() { + // Resulting map should start at the beginning of the original + ResourceMark rm; + ResizableBitMapClass expected(64); + fillBitMap(expected, 64); + + ResizableBitMapClass map(BITMAP_SIZE); + fillBitMap(map, BITMAP_SIZE); + map.truncate(0, 64); + + EXPECT_TRUE(map.is_same(expected)); + } + + template + static void testTruncateEnd() { + // Resulting map should end at the end of the original + ResourceMark rm; + ResizableBitMapClass expected(64); + expected.set_bit(0); + expected.set_bit(31); + expected.set_bit(63); + + ResizableBitMapClass map(BITMAP_SIZE); + fillBitMap(map, BITMAP_SIZE); + map.truncate(64, 128); + + EXPECT_TRUE(map.is_same(expected)); + } + + template + static void testTruncateMiddle() { + // Resulting map should end at the end of the original + ResourceMark rm; + ResizableBitMapClass expected(64); + expected.set_bit(31); + expected.set_bit(32); + expected.set_bit(63); + + ResizableBitMapClass map(BITMAP_SIZE); + fillBitMap(map, BITMAP_SIZE); + map.truncate(32, 96); + + EXPECT_TRUE(map.is_same(expected)); + } + + template + static void testTruncateStartUnaligned() { + // Resulting map should start at the beginning of the original + ResourceMark rm; + ResizableBitMapClass expected(96); + fillBitMap(expected, 96); + + ResizableBitMapClass map(BITMAP_SIZE); + fillBitMap(map, BITMAP_SIZE); + map.truncate(0, 96); + + EXPECT_TRUE(map.is_same(expected)); + } + + template + static void testTruncateEndUnaligned() { + // Resulting map should end at the end of the original + ResourceMark rm; + ResizableBitMapClass expected(97); + expected.set_bit(0); + expected.set_bit(32); + expected.set_bit(33); + expected.set_bit(64); + expected.set_bit(96); + + ResizableBitMapClass map(BITMAP_SIZE); + fillBitMap(map, BITMAP_SIZE); + map.truncate(31, 128); + + EXPECT_TRUE(map.is_same(expected)); + } + + template + static void testRandom() { + for (int i = 0; i < 100; i++) { + ResourceMark rm; + + const size_t max_size = 1024; + const size_t size = os::random() % max_size + 1; + const size_t truncate_size = os::random() % size + 1; + const size_t truncate_start = size == truncate_size ? 0 : os::random() % (size - truncate_size); + + ResizableBitMapClass map(size); + ResizableBitMapClass result(truncate_size); + + for (BitMap::idx_t idx = 0; idx < truncate_start; idx++) { + if (os::random() % 2 == 0) { + map.set_bit(idx); + } + } + + for (BitMap::idx_t idx = 0; idx < truncate_size; idx++) { + if (os::random() % 2 == 0) { + map.set_bit(truncate_start + idx); + result.set_bit(idx); + } + } + + map.truncate(truncate_start, truncate_start + truncate_size); + + EXPECT_TRUE(map.is_same(result)); + } + } +}; + // TestArenaBitMap is the shorthand combination of Arena and ArenaBitMap. // Multiple inheritance guarantees to construct Arena first. class TestArenaBitMap : private Arena, public ArenaBitMap { @@ -204,3 +363,75 @@ TEST_VM(BitMap, print_on) { } #endif + +TEST_VM(BitMap, truncate_same) { + BitMapTruncateTest::testTruncateSame(); + EXPECT_FALSE(HasFailure()) << "Failed on type ResourceBitMap"; + BitMapTruncateTest::testTruncateSame(); + EXPECT_FALSE(HasFailure()) << "Failed on type CHeapBitMap"; + BitMapTruncateTest::testTruncateSame(); + EXPECT_FALSE(HasFailure()) << "Failed on type ArenaBitMap"; +} + +TEST_VM(BitMap, truncate_start) { + BitMapTruncateTest::testTruncateStart(); + EXPECT_FALSE(HasFailure()) << "Failed on type ResourceBitMap"; + BitMapTruncateTest::testTruncateStart(); + EXPECT_FALSE(HasFailure()) << "Failed on type CHeapBitMap"; + BitMapTruncateTest::testTruncateStart(); + EXPECT_FALSE(HasFailure()) << "Failed on type ArenaBitMap"; +} + +TEST_VM(BitMap, truncate_end) { + BitMapTruncateTest::testTruncateEnd(); + EXPECT_FALSE(HasFailure()) << "Failed on type ResourceBitMap"; + BitMapTruncateTest::testTruncateEnd(); + EXPECT_FALSE(HasFailure()) << "Failed on type CHeapBitMap"; + BitMapTruncateTest::testTruncateEnd(); + EXPECT_FALSE(HasFailure()) << "Failed on type ArenaBitMap"; +} + +TEST_VM(BitMap, truncate_middle) { + BitMapTruncateTest::testTruncateMiddle(); + EXPECT_FALSE(HasFailure()) << "Failed on type ResourceBitMap"; + BitMapTruncateTest::testTruncateMiddle(); + EXPECT_FALSE(HasFailure()) << "Failed on type CHeapBitMap"; + BitMapTruncateTest::testTruncateMiddle(); + EXPECT_FALSE(HasFailure()) << "Failed on type ArenaBitMap"; +} + +TEST_VM(BitMap, truncate_start_unaligned) { + BitMapTruncateTest::testTruncateStartUnaligned(); + EXPECT_FALSE(HasFailure()) << "Failed on type ResourceBitMap"; + BitMapTruncateTest::testTruncateStartUnaligned(); + EXPECT_FALSE(HasFailure()) << "Failed on type CHeapBitMap"; + BitMapTruncateTest::testTruncateStartUnaligned(); + EXPECT_FALSE(HasFailure()) << "Failed on type ArenaBitMap"; +} + +TEST_VM(BitMap, truncate_end_unaligned) { + BitMapTruncateTest::testTruncateEndUnaligned(); + EXPECT_FALSE(HasFailure()) << "Failed on type ResourceBitMap"; + BitMapTruncateTest::testTruncateEndUnaligned(); + EXPECT_FALSE(HasFailure()) << "Failed on type CHeapBitMap"; + BitMapTruncateTest::testTruncateEndUnaligned(); + EXPECT_FALSE(HasFailure()) << "Failed on type ArenaBitMap"; +} + +TEST_VM(BitMap, truncate_one_word) { + BitMapTruncateTest::testTruncateOneWord(); + EXPECT_FALSE(HasFailure()) << "Failed on type ResourceBitMap"; + BitMapTruncateTest::testTruncateOneWord(); + EXPECT_FALSE(HasFailure()) << "Failed on type CHeapBitMap"; + BitMapTruncateTest::testTruncateOneWord(); + EXPECT_FALSE(HasFailure()) << "Failed on type ArenaBitMap"; +} + +TEST_VM(BitMap, truncate_random) { + BitMapTruncateTest::testRandom(); + EXPECT_FALSE(HasFailure()) << "Failed on type ResourceBitMap"; + BitMapTruncateTest::testRandom(); + EXPECT_FALSE(HasFailure()) << "Failed on type CHeapBitMap"; + BitMapTruncateTest::testRandom(); + EXPECT_FALSE(HasFailure()) << "Failed on type ArenaBitMap"; +}