| // 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 <gtest/gtest.h> | 
 | #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<uint64_t> 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); | 
 | } |