// Copyright 2019 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include "dawn_native/BuddyAllocator.h" using namespace dawn_native; constexpr uint64_t BuddyAllocator::kInvalidOffset; // Verify the buddy allocator with a basic test. TEST(BuddyAllocatorTests, SingleBlock) { // After one 32 byte allocation: // // Level -------------------------------- // 0 32 | A | // -------------------------------- // constexpr uint64_t maxBlockSize = 32; BuddyAllocator allocator(maxBlockSize); // Check that we cannot allocate a oversized block. ASSERT_EQ(allocator.Allocate(maxBlockSize * 2), BuddyAllocator::kInvalidOffset); // Check that we cannot allocate a zero sized block. ASSERT_EQ(allocator.Allocate(0u), BuddyAllocator::kInvalidOffset); // Allocate the block. uint64_t blockOffset = allocator.Allocate(maxBlockSize); ASSERT_EQ(blockOffset, 0u); // Check that we are full. ASSERT_EQ(allocator.Allocate(maxBlockSize), BuddyAllocator::kInvalidOffset); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 0u); // Deallocate the block. allocator.Deallocate(blockOffset); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); } // Verify multiple allocations succeeds using a buddy allocator. TEST(BuddyAllocatorTests, MultipleBlocks) { // Fill every level in the allocator (order-n = 2^n) const uint64_t maxBlockSize = (1ull << 16); for (uint64_t order = 1; (1ull << order) <= maxBlockSize; order++) { BuddyAllocator allocator(maxBlockSize); uint64_t blockSize = (1ull << order); for (uint32_t blocki = 0; blocki < (maxBlockSize / blockSize); blocki++) { ASSERT_EQ(allocator.Allocate(blockSize), blockSize * blocki); } } } // Verify that a single allocation succeeds using a buddy allocator. TEST(BuddyAllocatorTests, SingleSplitBlock) { // After one 8 byte allocation: // // Level -------------------------------- // 0 32 | S | // -------------------------------- // 1 16 | S | F | S - split // -------------------------------- F - free // 2 8 | A | F | | | A - allocated // -------------------------------- // constexpr uint64_t maxBlockSize = 32; BuddyAllocator allocator(maxBlockSize); // Allocate block (splits two blocks). uint64_t blockOffset = allocator.Allocate(8); ASSERT_EQ(blockOffset, 0u); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u); // Deallocate block (merges two blocks). allocator.Deallocate(blockOffset); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); // Check that we cannot allocate a block that is oversized. ASSERT_EQ(allocator.Allocate(maxBlockSize * 2), BuddyAllocator::kInvalidOffset); // Re-allocate the largest block allowed after merging. blockOffset = allocator.Allocate(maxBlockSize); ASSERT_EQ(blockOffset, 0u); allocator.Deallocate(blockOffset); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); } // Verify that a multiple allocated blocks can be removed in the free-list. TEST(BuddyAllocatorTests, MultipleSplitBlocks) { // After four 16 byte allocations: // // Level -------------------------------- // 0 32 | S | // -------------------------------- // 1 16 | S | S | S - split // -------------------------------- F - free // 2 8 | Aa | Ab | Ac | Ad | A - allocated // -------------------------------- // constexpr uint64_t maxBlockSize = 32; BuddyAllocator allocator(maxBlockSize); // Populates the free-list with four blocks at Level2. // Allocate "a" block (two splits). constexpr uint64_t blockSizeInBytes = 8; uint64_t blockOffsetA = allocator.Allocate(blockSizeInBytes); ASSERT_EQ(blockOffsetA, 0u); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u); // Allocate "b" block. uint64_t blockOffsetB = allocator.Allocate(blockSizeInBytes); ASSERT_EQ(blockOffsetB, blockSizeInBytes); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); // Allocate "c" block (three splits). uint64_t blockOffsetC = allocator.Allocate(blockSizeInBytes); ASSERT_EQ(blockOffsetC, blockOffsetB + blockSizeInBytes); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); // Allocate "d" block. uint64_t blockOffsetD = allocator.Allocate(blockSizeInBytes); ASSERT_EQ(blockOffsetD, blockOffsetC + blockSizeInBytes); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 0u); // Deallocate "d" block. // FreeList[Level2] = [BlockD] -> x allocator.Deallocate(blockOffsetD); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); // Deallocate "b" block. // FreeList[Level2] = [BlockB] -> [BlockD] -> x allocator.Deallocate(blockOffsetB); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u); // Deallocate "c" block (one merges). // FreeList[Level1] = [BlockCD] -> x // FreeList[Level2] = [BlockB] -> x allocator.Deallocate(blockOffsetC); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u); // Deallocate "a" block (two merges). // FreeList[Level0] = [BlockABCD] -> x allocator.Deallocate(blockOffsetA); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); } // Verify the buddy allocator can handle allocations of various sizes. TEST(BuddyAllocatorTests, MultipleSplitBlockIncreasingSize) { // After four Level4-to-Level1 byte then one L4 block allocations: // // Level ----------------------------------------------------------------- // 0 512 | S | // ----------------------------------------------------------------- // 1 256 | S | A | // ----------------------------------------------------------------- // 2 128 | S | A | | | // ----------------------------------------------------------------- // 3 64 | S | A | | | | | | | // ----------------------------------------------------------------- // 4 32 | A | F | | | | | | | | | | | | | | | // ----------------------------------------------------------------- // constexpr uint64_t maxBlockSize = 512; BuddyAllocator allocator(maxBlockSize); ASSERT_EQ(allocator.Allocate(32), 0ull); ASSERT_EQ(allocator.Allocate(64), 64ull); ASSERT_EQ(allocator.Allocate(128), 128ull); ASSERT_EQ(allocator.Allocate(256), 256ull); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); // Fill in the last free block. ASSERT_EQ(allocator.Allocate(32), 32ull); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 0u); // Check if we're full. ASSERT_EQ(allocator.Allocate(32), BuddyAllocator::kInvalidOffset); } // Verify very small allocations using a larger allocator works correctly. TEST(BuddyAllocatorTests, MultipleSplitBlocksVariableSizes) { // After allocating four pairs of one 64 byte block and one 32 byte block. // // Level ----------------------------------------------------------------- // 0 512 | S | // ----------------------------------------------------------------- // 1 256 | S | S | // ----------------------------------------------------------------- // 2 128 | S | S | S | F | // ----------------------------------------------------------------- // 3 64 | A | S | A | A | S | A | | | // ----------------------------------------------------------------- // 4 32 | | | A | A | | | | | A | A | | | | | | | // ----------------------------------------------------------------- // constexpr uint64_t maxBlockSize = 512; BuddyAllocator allocator(maxBlockSize); ASSERT_EQ(allocator.Allocate(64), 0ull); ASSERT_EQ(allocator.Allocate(32), 64ull); ASSERT_EQ(allocator.Allocate(64), 128ull); ASSERT_EQ(allocator.Allocate(32), 96ull); ASSERT_EQ(allocator.Allocate(64), 192ull); ASSERT_EQ(allocator.Allocate(32), 256ull); ASSERT_EQ(allocator.Allocate(64), 320ull); ASSERT_EQ(allocator.Allocate(32), 288ull); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); } // Verify the buddy allocator can deal with bad fragmentation. TEST(BuddyAllocatorTests, MultipleSplitBlocksInterleaved) { // Allocate every leaf then de-allocate every other of those allocations. // // Level ----------------------------------------------------------------- // 0 512 | S | // ----------------------------------------------------------------- // 1 256 | S | S | // ----------------------------------------------------------------- // 2 128 | S | S | S | S | // ----------------------------------------------------------------- // 3 64 | S | S | S | S | S | S | S | S | // ----------------------------------------------------------------- // 4 32 | A | F | A | F | A | F | A | F | A | F | A | F | A | F | A | F | // ----------------------------------------------------------------- // constexpr uint64_t maxBlockSize = 512; BuddyAllocator allocator(maxBlockSize); // Allocate leaf blocks constexpr uint64_t minBlockSizeInBytes = 32; std::vector blockOffsets; for (uint64_t i = 0; i < maxBlockSize / minBlockSizeInBytes; i++) { blockOffsets.push_back(allocator.Allocate(minBlockSizeInBytes)); } // Free every other leaf block. for (size_t count = 1; count < blockOffsets.size(); count += 2) { allocator.Deallocate(blockOffsets[count]); } ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 8u); } // Verify the buddy allocator can deal with multiple allocations with mixed alignments. TEST(BuddyAllocatorTests, SameSizeVariousAlignment) { // After two 8 byte allocations with 16 byte alignment then one 8 byte allocation with 8 byte // alignment. // // Level -------------------------------- // 0 32 | S | // -------------------------------- // 1 16 | S | S | S - split // -------------------------------- F - free // 2 8 | Aa | F | Ab | Ac | A - allocated // -------------------------------- // BuddyAllocator allocator(32); // Allocate Aa (two splits). ASSERT_EQ(allocator.Allocate(8, 16), 0u); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u); // Allocate Ab (skip Aa buddy due to alignment and perform another split). ASSERT_EQ(allocator.Allocate(8, 16), 16u); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u); // Check that we cannot fit another. ASSERT_EQ(allocator.Allocate(8, 16), BuddyAllocator::kInvalidOffset); // Allocate Ac (zero splits and Ab's buddy is now the first free block). ASSERT_EQ(allocator.Allocate(8, 8), 24u); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); } // Verify the buddy allocator can deal with multiple allocations with equal alignments. TEST(BuddyAllocatorTests, VariousSizeSameAlignment) { // After two 8 byte allocations with 4 byte alignment then one 16 byte allocation with 4 byte // alignment. // // Level -------------------------------- // 0 32 | S | // -------------------------------- // 1 16 | S | Ac | S - split // -------------------------------- F - free // 2 8 | Aa | Ab | | A - allocated // -------------------------------- // constexpr uint64_t maxBlockSize = 32; constexpr uint64_t alignment = 4; BuddyAllocator allocator(maxBlockSize); // Allocate block Aa (two splits) ASSERT_EQ(allocator.Allocate(8, alignment), 0u); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 2u); // Allocate block Ab (Aa's buddy) ASSERT_EQ(allocator.Allocate(8, alignment), 8u); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 1u); // Check that we can still allocate Ac. ASSERT_EQ(allocator.Allocate(16, alignment), 16ull); ASSERT_EQ(allocator.ComputeTotalNumOfFreeBlocksForTesting(), 0u); }