// Copyright (c) 2026 Advanced Micro Devices, Inc. // SPDX-License-Identifier: MIT #include "simdojo/components/sparse_memory.h" #include #include #include #include #include #include #include #include #include namespace { TEST(SparseMemoryThreadingTest, ConcurrentDifferentPageWritesArePreserved) { simdojo::SparseMemory memory("memory"); constexpr uint32_t kThreads = 8; constexpr uint32_t kPagesPerThread = 64; constexpr uint64_t kBase = 0x800000; std::barrier start(kThreads); std::vector workers; workers.reserve(kThreads); for (uint32_t tid = 0; tid < kThreads; ++tid) { workers.emplace_back([&, tid] { std::array page{}; start.arrive_and_wait(); for (uint32_t i = 0; i < kPagesPerThread; ++i) { const uint64_t addr = kBase + (static_cast(i) * kThreads + tid) * simdojo::SparseMemory::PAGE_SIZE; for (uint32_t b = 0; b < page.size(); ++b) page[b] = static_cast((tid << 4) ^ i ^ b); memory.write_block(addr, page); } }); } for (auto &worker : workers) worker.join(); EXPECT_EQ(memory.num_pages(), kThreads * kPagesPerThread); std::array expected{}; std::array actual{}; for (uint32_t tid = 0; tid < kThreads; ++tid) { for (uint32_t i = 0; i < kPagesPerThread; ++i) { const uint64_t addr = kBase + (static_cast(i) * kThreads + tid) * simdojo::SparseMemory::PAGE_SIZE; for (uint32_t b = 0; b < expected.size(); ++b) expected[b] = static_cast((tid << 4) ^ i ^ b); memory.read_block(addr, actual); EXPECT_EQ(actual, expected) << "addr=0x" << std::hex << addr; uint32_t expected_word = 0; std::memcpy(&expected_word, expected.data(), sizeof(expected_word)); EXPECT_EQ(memory.read32(addr), expected_word); } } } TEST(SparseMemoryThreadingTest, ConcurrentSamePageWritesArePreserved) { simdojo::SparseMemory memory("memory"); constexpr uint32_t kThreads = 8; constexpr size_t kBytesPerThread = simdojo::SparseMemory::PAGE_SIZE / kThreads; constexpr uint64_t kBase = 0x900000; std::barrier start(kThreads); std::vector workers; workers.reserve(kThreads); for (uint32_t tid = 0; tid < kThreads; ++tid) { workers.emplace_back([&, tid] { std::array bytes{}; std::fill(bytes.begin(), bytes.end(), static_cast(0x20 + tid)); start.arrive_and_wait(); for (uint32_t iteration = 0; iteration < 64; ++iteration) memory.write_block(kBase + tid * kBytesPerThread, bytes); }); } for (auto &worker : workers) worker.join(); std::array actual{}; memory.read_block(kBase, actual); for (uint32_t tid = 0; tid < kThreads; ++tid) { const auto begin = actual.begin() + tid * kBytesPerThread; const auto end = begin + kBytesPerThread; EXPECT_TRUE(std::all_of( begin, end, [tid](uint8_t byte) { return byte == static_cast(0x20 + tid); })); } } TEST(SparseMemoryTest, UnalignedBlockRoundTripCrossesPageBoundary) { simdojo::SparseMemory memory("memory"); constexpr uint64_t kAddr = simdojo::SparseMemory::PAGE_SIZE - 37; std::array input{}; for (size_t i = 0; i < input.size(); ++i) input[i] = static_cast((i * 17) ^ 0xA5); memory.write_block(kAddr, input); std::array output{}; memory.read_block(kAddr, output); EXPECT_EQ(output, input); EXPECT_EQ(memory.num_pages(), 2u); } TEST(SparseMemoryThreadingTest, ConcurrentOverlappingBlocksRemainAtomicPerPage) { simdojo::SparseMemory memory("memory"); constexpr uint64_t kBase = 0xA00000; constexpr size_t kBlockSize = 2 * simdojo::SparseMemory::PAGE_SIZE; std::array first{}; std::array second{}; first.fill(0x3C); second.fill(0xC3); std::barrier start(2); std::thread first_writer([&] { start.arrive_and_wait(); for (uint32_t iteration = 0; iteration < 128; ++iteration) memory.write_block(kBase, first); }); std::thread second_writer([&] { start.arrive_and_wait(); for (uint32_t iteration = 0; iteration < 128; ++iteration) memory.write_block(kBase, second); }); first_writer.join(); second_writer.join(); std::array actual{}; memory.read_block(kBase, actual); for (size_t page = 0; page < 2; ++page) { const auto bytes = std::span(actual).subspan( page * simdojo::SparseMemory::PAGE_SIZE, simdojo::SparseMemory::PAGE_SIZE); const bool is_first = std::all_of(bytes.begin(), bytes.end(), [](uint8_t byte) { return byte == 0x3C; }); const bool is_second = std::all_of(bytes.begin(), bytes.end(), [](uint8_t byte) { return byte == 0xC3; }); EXPECT_TRUE(is_first || is_second) << "page=" << page; } } } // namespace