blob: 021bb1bfe819a4f1b792126be4b0aebbd876c04d [file] [log] [blame]
// Copyright 2022 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/hal/utils/resource_set.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include "iree/base/api.h"
#include "iree/hal/api.h"
#include "iree/testing/gtest.h"
#include "iree/testing/status_matchers.h"
namespace iree {
namespace hal {
namespace {
using ::iree::testing::status::IsOkAndHolds;
using ::iree::testing::status::StatusIs;
using ::testing::Eq;
typedef struct iree_hal_test_resource_t {
iree_hal_resource_t resource;
iree_allocator_t host_allocator;
uint32_t index;
uint32_t* live_bitmap;
} iree_hal_test_resource_t;
typedef struct iree_hal_test_resource_vtable_t {
void(IREE_API_PTR* destroy)(iree_hal_test_resource_t* resource);
} iree_hal_test_resource_vtable_t;
IREE_HAL_ASSERT_VTABLE_LAYOUT(iree_hal_test_resource_vtable_t);
extern const iree_hal_test_resource_vtable_t iree_hal_test_resource_vtable;
static iree_status_t iree_hal_test_resource_create(
uint32_t index, uint32_t* live_bitmap, iree_allocator_t host_allocator,
iree_hal_resource_t** out_resource) {
iree_hal_test_resource_t* test_resource = NULL;
IREE_RETURN_IF_ERROR(iree_allocator_malloc(
host_allocator, sizeof(*test_resource), (void**)&test_resource));
iree_hal_resource_initialize(&iree_hal_test_resource_vtable,
&test_resource->resource);
test_resource->host_allocator = host_allocator;
test_resource->index = index;
test_resource->live_bitmap = live_bitmap;
*live_bitmap |= 1 << index;
*out_resource = (iree_hal_resource_t*)test_resource;
return iree_ok_status();
}
static void iree_hal_test_resource_destroy(iree_hal_test_resource_t* resource) {
iree_allocator_t host_allocator = resource->host_allocator;
*resource->live_bitmap &= ~(1 << resource->index);
iree_allocator_free(host_allocator, resource);
}
const iree_hal_test_resource_vtable_t iree_hal_test_resource_vtable = {
/*.destroy=*/iree_hal_test_resource_destroy,
};
struct ResourceSetTest : public ::testing::Test {
// We could check the allocator to ensure all memory is freed if we wanted to
// reduce the reliance on asan.
iree_allocator_t host_allocator = iree_allocator_system();
iree_arena_block_pool_t block_pool;
void SetUp() override {
memset(&block_pool, 0, sizeof(block_pool));
iree_arena_block_pool_initialize(128, host_allocator, &block_pool);
}
void TearDown() override {
// This may assert (or at least trigger asan) if there are blocks
// outstanding.
iree_arena_block_pool_deinitialize(&block_pool);
}
};
using resource_set_ptr = std::unique_ptr<iree_hal_resource_set_t,
decltype(&iree_hal_resource_set_free)>;
static resource_set_ptr make_resource_set(iree_arena_block_pool_t* block_pool) {
iree_hal_resource_set_t* set = NULL;
IREE_CHECK_OK(iree_hal_resource_set_allocate(block_pool, &set));
return resource_set_ptr(set, iree_hal_resource_set_free);
}
// Tests a set that has no resources added to it.
TEST_F(ResourceSetTest, Empty) {
iree_hal_resource_set_t* set = NULL;
IREE_ASSERT_OK(iree_hal_resource_set_allocate(&block_pool, &set));
iree_hal_resource_set_free(set);
}
// Tests insertion of a single resource.
TEST_F(ResourceSetTest, Insert1) {
auto resource_set = make_resource_set(&block_pool);
// Create test resource; it'll set its bit in the live_bitmap.
iree_hal_resource_t* resource = NULL;
uint32_t live_bitmap = 0u;
IREE_ASSERT_OK(iree_hal_test_resource_create(0, &live_bitmap, host_allocator,
&resource));
EXPECT_EQ(live_bitmap, 1u);
// Insert the resource and drop the reference; it should still be live as the
// set retains it.
IREE_ASSERT_OK(
iree_hal_resource_set_insert(resource_set.get(), 1, &resource));
iree_hal_resource_release(resource);
EXPECT_EQ(live_bitmap, 1u);
// Drop the set and expect the resource to be destroyed as it loses its last
// reference.
resource_set.reset();
EXPECT_EQ(live_bitmap, 0u);
}
// Tests inserting multiple resources at a time.
TEST_F(ResourceSetTest, Insert5) {
auto resource_set = make_resource_set(&block_pool);
// Allocate 5 resources - this lets us test for special paths that may handle
// 4 at a time (to fit in SIMD registers) as well as the leftovers.
iree_hal_resource_t* resources[5] = {NULL};
uint32_t live_bitmap = 0u;
for (iree_host_size_t i = 0; i < IREE_ARRAYSIZE(resources); ++i) {
IREE_ASSERT_OK(iree_hal_test_resource_create(
i, &live_bitmap, host_allocator, &resources[i]));
}
EXPECT_EQ(live_bitmap, 0x1Fu);
// Transfer ownership of the resources to the set.
IREE_ASSERT_OK(iree_hal_resource_set_insert(
resource_set.get(), IREE_ARRAYSIZE(resources), resources));
for (iree_host_size_t i = 0; i < IREE_ARRAYSIZE(resources); ++i) {
iree_hal_resource_release(resources[i]);
}
EXPECT_EQ(live_bitmap, 0x1Fu);
// Ensure the set releases the resources.
resource_set.reset();
EXPECT_EQ(live_bitmap, 0u);
}
// Tests inserting enough resources to force set growth. This is ensured by
// choosing a sufficiently small block size such that even 32 elements triggers
// a growth. Of course, real usage should have at least ~4KB for the block size.
TEST_F(ResourceSetTest, InsertionGrowth) {
auto resource_set = make_resource_set(&block_pool);
// Allocate 32 resources (one for each bit in our live map).
iree_hal_resource_t* resources[32] = {NULL};
uint32_t live_bitmap = 0u;
for (iree_host_size_t i = 0; i < IREE_ARRAYSIZE(resources); ++i) {
IREE_ASSERT_OK(iree_hal_test_resource_create(
i, &live_bitmap, host_allocator, &resources[i]));
}
EXPECT_EQ(live_bitmap, 0xFFFFFFFFu);
// Transfer ownership of the resources to the set.
IREE_ASSERT_OK(iree_hal_resource_set_insert(
resource_set.get(), IREE_ARRAYSIZE(resources), resources));
for (iree_host_size_t i = 0; i < IREE_ARRAYSIZE(resources); ++i) {
iree_hal_resource_release(resources[i]);
}
EXPECT_EQ(live_bitmap, 0xFFFFFFFFu);
// Ensure the set releases the resources.
resource_set.reset();
EXPECT_EQ(live_bitmap, 0u);
}
// Tests insertion of resources multiple times to verify the MRU works.
TEST_F(ResourceSetTest, RedundantInsertion) {
auto resource_set = make_resource_set(&block_pool);
// Allocate 32 resources (one for each bit in our live map).
// We want to be able to miss in the MRU.
iree_hal_resource_t* resources[32] = {NULL};
static_assert(IREE_ARRAYSIZE(resources) > IREE_HAL_RESOURCE_SET_MRU_SIZE,
"need to pick a value that lets us exceed the MRU capacity");
uint32_t live_bitmap = 0u;
for (iree_host_size_t i = 0; i < IREE_ARRAYSIZE(resources); ++i) {
IREE_ASSERT_OK(iree_hal_test_resource_create(
i, &live_bitmap, host_allocator, &resources[i]));
}
EXPECT_EQ(live_bitmap, 0xFFFFFFFFu);
// NOTE: the only requirement of the MRU is that it's _mostly_ MRU - we may
// for performance reasons make it a little fuzzy to avoid additional
// shuffling. Today it's always a proper MRU and we check the pointers here.
// NOTE: the MRU size can vary across architectures; we know it should always
// be at least ~6 though so that's what we work with here.
static_assert(IREE_HAL_RESOURCE_SET_MRU_SIZE > 6,
"need at least enough elements to test with");
// Insert in sequence, MRU should contain:
// 31 30 29 28 27 ...
IREE_ASSERT_OK(iree_hal_resource_set_insert(
resource_set.get(), IREE_ARRAYSIZE(resources), resources));
EXPECT_EQ(resource_set->mru[0], resources[31]);
EXPECT_EQ(resource_set->mru[1], resources[30]);
EXPECT_EQ(resource_set->mru[2], resources[29]);
EXPECT_EQ(resource_set->mru[3], resources[28]);
EXPECT_EQ(resource_set->mru[4], resources[27]);
// Insert 31 again, MRU should remain the same as it's at the head.
IREE_ASSERT_OK(
iree_hal_resource_set_insert(resource_set.get(), 1, &resources[31]));
EXPECT_EQ(resource_set->mru[0], resources[31]);
EXPECT_EQ(resource_set->mru[1], resources[30]);
EXPECT_EQ(resource_set->mru[2], resources[29]);
EXPECT_EQ(resource_set->mru[3], resources[28]);
EXPECT_EQ(resource_set->mru[4], resources[27]);
// Insert 28 again, MRU should be updated to move it to the front:
// 28 31 30 29 27 ...
IREE_ASSERT_OK(
iree_hal_resource_set_insert(resource_set.get(), 1, &resources[28]));
EXPECT_EQ(resource_set->mru[0], resources[28]);
EXPECT_EQ(resource_set->mru[1], resources[31]);
EXPECT_EQ(resource_set->mru[2], resources[30]);
EXPECT_EQ(resource_set->mru[3], resources[29]);
EXPECT_EQ(resource_set->mru[4], resources[27]);
// Insert 0 again, which should be a miss as it fell off the end of the MRU:
// 0 28 31 30 29 27 ...
IREE_ASSERT_OK(
iree_hal_resource_set_insert(resource_set.get(), 1, &resources[0]));
EXPECT_EQ(resource_set->mru[0], resources[0]);
EXPECT_EQ(resource_set->mru[1], resources[28]);
EXPECT_EQ(resource_set->mru[2], resources[31]);
EXPECT_EQ(resource_set->mru[3], resources[30]);
EXPECT_EQ(resource_set->mru[4], resources[29]);
EXPECT_EQ(resource_set->mru[5], resources[27]);
// Release all of the resources - they should still be owned by the set.
for (iree_host_size_t i = 0; i < IREE_ARRAYSIZE(resources); ++i) {
iree_hal_resource_release(resources[i]);
}
EXPECT_EQ(live_bitmap, 0xFFFFFFFFu);
// Ensure the set releases the resources.
resource_set.reset();
EXPECT_EQ(live_bitmap, 0u);
}
} // namespace
} // namespace hal
} // namespace iree