blob: afd8b852864c0db17da1bd22231a7f083b18d695 [file] [edit]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <vector>
#include "dawn/wire/WireClient.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/dawn/common/Ref.h"
#include "src/dawn/wire/InlineSharedMemoryManager.h"
#include "src/dawn/wire/client/ClientInlineMemoryTransferService.h"
#include "src/dawn/wire/server/ServerInlineMemoryTransferService.h"
#include "src/utils/platform.h"
namespace dawn::wire {
namespace {
class InlineSharedMemoryManagerTest : public testing::Test {
protected:
void SetUp() override {
#if !DAWN_PLATFORM_IS(WINDOWS)
GTEST_SKIP() << "InlineSharedMemoryManager is only implemented on Windows";
#endif
mManager = CreateInlineSharedMemoryManager();
}
static constexpr std::array<std::byte, 8> kExpectedData = {
std::byte(0), std::byte(1), std::byte(2), std::byte(3),
std::byte(4), std::byte(5), std::byte(6), std::byte(7)};
static constexpr size_t kSize = kExpectedData.size();
std::shared_ptr<InlineSharedMemoryManager> mManager;
};
TEST_F(InlineSharedMemoryManagerTest, CreateSharedMemory_ReturnsNonNull) {
Ref<SharedMemory> memory = mManager->CreateSharedMemory(1024);
EXPECT_NE(nullptr, memory.Get());
}
TEST_F(InlineSharedMemoryManagerTest, CreateSharedMemory_MultipleBuffersAreDistinct) {
Ref<SharedMemory> memory1 = mManager->CreateSharedMemory(256);
Ref<SharedMemory> memory2 = mManager->CreateSharedMemory(256);
EXPECT_NE(nullptr, memory1.Get());
EXPECT_NE(nullptr, memory2.Get());
EXPECT_NE(memory1.Get(), memory2.Get());
}
TEST_F(InlineSharedMemoryManagerTest, GetMappedSpan_ReturnsNonEmptySpanWithCorrectSize) {
constexpr size_t kSize = 1024;
Ref<SharedMemory> memory = mManager->CreateSharedMemory(kSize);
std::span<std::byte> span = memory->GetMappedSpan();
EXPECT_FALSE(span.empty());
EXPECT_EQ(kSize, span.size());
EXPECT_NE(nullptr, span.data());
}
TEST_F(InlineSharedMemoryManagerTest, CreateSharedMemory_MemoryIsZeroInitialized) {
constexpr size_t kSize = 1024;
Ref<SharedMemory> memory = mManager->CreateSharedMemory(kSize);
ASSERT_NE(nullptr, memory.Get());
for (std::byte b : memory->GetMappedSpan()) {
EXPECT_EQ(std::byte(0), b);
}
}
TEST_F(InlineSharedMemoryManagerTest, GetHandle_ReturnsNonNullHandle) {
Ref<SharedMemory> memory = mManager->CreateSharedMemory(256);
EXPECT_TRUE(memory->GetSystemHandle().IsValid());
}
TEST_F(InlineSharedMemoryManagerTest, PutOnWire_ReturnsUniqueIds) {
Ref<SharedMemory> memory1 = mManager->CreateSharedMemory(256);
Ref<SharedMemory> memory2 = mManager->CreateSharedMemory(256);
SharedMemoryID id1 = mManager->PutOnWireAndGetID(memory1.Get());
SharedMemoryID id2 = mManager->PutOnWireAndGetID(memory2.Get());
EXPECT_NE(id1, id2);
}
TEST_F(InlineSharedMemoryManagerTest, AcquireFromWire_ReturnsSameMemory) {
Ref<SharedMemory> memory = mManager->CreateSharedMemory(256);
SharedMemoryID id = mManager->PutOnWireAndGetID(memory.Get());
Ref<SharedMemory> acquired = mManager->AcquireFromWire(id);
EXPECT_EQ(memory.Get(), acquired.Get());
}
TEST_F(InlineSharedMemoryManagerTest, AcquireFromWire_ReturnsNullForUnknownId) {
EXPECT_EQ(nullptr, mManager->AcquireFromWire(SharedMemoryID(0u)).Get());
EXPECT_EQ(nullptr, mManager->AcquireFromWire(SharedMemoryID(99999u)).Get());
}
TEST_F(InlineSharedMemoryManagerTest, AcquireFromWire_RemovesFromWire) {
Ref<SharedMemory> memory = mManager->CreateSharedMemory(256);
SharedMemoryID id = mManager->PutOnWireAndGetID(memory.Get());
EXPECT_NE(nullptr, mManager->AcquireFromWire(id).Get());
// A second acquire finds nothing since the reference was already transferred off the wire.
EXPECT_EQ(nullptr, mManager->AcquireFromWire(id).Get());
}
TEST_F(InlineSharedMemoryManagerTest, PutOnWire_KeepsMemoryAliveAfterLocalRefDropped) {
Ref<SharedMemory> memory = mManager->CreateSharedMemory(256);
SharedMemory* rawSharedMemoryPtr = memory.Get();
SharedMemoryID id = mManager->PutOnWireAndGetID(rawSharedMemoryPtr);
// Drop the local reference; the wire still holds one so the memory stays alive.
memory = nullptr;
Ref<SharedMemory> acquired = mManager->AcquireFromWire(id);
EXPECT_EQ(rawSharedMemoryPtr, acquired.Get());
EXPECT_FALSE(acquired->GetMappedSpan().empty());
}
TEST_F(InlineSharedMemoryManagerTest, DataRoundtrip_WriteAndReadBack) {
constexpr size_t kSize = 256;
Ref<SharedMemory> memory = mManager->CreateSharedMemory(kSize);
std::span<std::byte> span = memory->GetMappedSpan();
ASSERT_EQ(kSize, span.size());
// Write a known pattern.
for (size_t i = 0; i < kSize; ++i) {
span[i] = static_cast<std::byte>(i);
}
// Put the memory on the wire and retrieve it back.
SharedMemoryID id = mManager->PutOnWireAndGetID(memory.Get());
Ref<SharedMemory> acquired = mManager->AcquireFromWire(id);
ASSERT_NE(nullptr, acquired.Get());
// Read back through the acquired reference and compare the data with the expected values.
std::span<std::byte> readSpan = acquired->GetMappedSpan();
ASSERT_EQ(kSize, readSpan.size());
for (size_t i = 0; i < kSize; ++i) {
EXPECT_EQ(static_cast<std::byte>(i), readSpan[i]) << " at index " << i;
}
}
TEST_F(InlineSharedMemoryManagerTest, MultipleBuffers_DataIsIsolated) {
constexpr size_t kSize = 128;
Ref<SharedMemory> memory1 = mManager->CreateSharedMemory(kSize);
Ref<SharedMemory> memory2 = mManager->CreateSharedMemory(kSize);
std::span<std::byte> span1 = memory1->GetMappedSpan();
std::span<std::byte> span2 = memory2->GetMappedSpan();
constexpr std::byte kData1 = std::byte{0xAA};
constexpr std::byte kData2 = std::byte{0xBB};
std::fill(span1.begin(), span1.end(), kData1);
std::fill(span2.begin(), span2.end(), kData2);
for (std::byte b : memory1->GetMappedSpan()) {
EXPECT_EQ(kData1, b);
}
for (std::byte b : memory2->GetMappedSpan()) {
EXPECT_EQ(kData2, b);
}
}
// Tests for the client-side `InlineMemoryTransferService` created with a non-null
// `InlineSharedMemoryManager`.
class ClientInlineMemoryTransferServiceTest : public InlineSharedMemoryManagerTest {
protected:
void SetUp() override {
InlineSharedMemoryManagerTest::SetUp();
mService = client::CreateInlineMemoryTransferService(mManager);
ASSERT_NE(nullptr, mService);
}
using MemoryHandle = client::MemoryTransferService::MemoryHandle;
using MemoryHandleUse = client::MemoryTransferService::MemoryHandleUse;
// Reads back a `SharedMemoryHandle` serialized by `MemoryHandle::SerializeCreate`.
static SharedMemoryHandle ReadSerializedHandle(Span<const std::byte> serialized) {
EXPECT_EQ(sizeof(SharedMemoryHandle), serialized.size());
return ReinterpretSpan<const SharedMemoryHandle>(serialized)[0];
}
void TestSerializedAsStagingHandle(MemoryHandleUse stagingMemoryHandleUse) {
DAWN_ASSERT(stagingMemoryHandleUse == MemoryHandleUse::BulkData ||
stagingMemoryHandleUse == MemoryHandleUse::MappedAtCreationData);
std::unique_ptr<MemoryHandle> handle =
mService->CreateMemoryHandle(kSize, stagingMemoryHandleUse);
ASSERT_EQ(sizeof(SharedMemoryHandle), handle->GetSerializeCreateSize());
std::vector<std::byte> serialized(handle->GetSerializeCreateSize());
handle->SerializeCreate(
std::span<volatile std::byte>(serialized.data(), serialized.size()));
// Serialize the handle and read back the serialized `SharedMemoryHandle`. `wireHandle.id`
// won't be `kInvalidSharedMemoryID` for a shared-memory-backed handle.
SharedMemoryHandle wireHandle = ReadSerializedHandle(serialized);
EXPECT_EQ(kInvalidSharedMemoryID, wireHandle.id);
}
std::unique_ptr<client::MemoryTransferService> mService;
};
// A handle backed by shared memory serializes a reference to the same underlying memory.
TEST_F(ClientInlineMemoryTransferServiceTest, SharedMemoryHandle_SerializeCreate) {
std::unique_ptr<MemoryHandle> handle =
mService->CreateMemoryHandle(kSize, MemoryHandleUse::MappedBuffer);
ASSERT_EQ(sizeof(SharedMemoryHandle), handle->GetSerializeCreateSize());
// Record the handle's mapped data before serializing.
std::span<std::byte> handleData = handle->GetData();
ASSERT_EQ(kSize, handleData.size());
std::vector<std::byte> serialized(handle->GetSerializeCreateSize());
handle->SerializeCreate(std::span<volatile std::byte>(serialized.data(), serialized.size()));
// Deserializing should yield the exact same memory: same pointer and size.
SharedMemoryHandle wireHandle = ReadSerializedHandle(serialized);
Ref<SharedMemory> acquired = mManager->AcquireFromWire(SharedMemoryID(wireHandle.id));
ASSERT_NE(nullptr, acquired.Get());
EXPECT_EQ(handleData.data(), acquired->GetMappedSpan().data());
EXPECT_EQ(handleData.size(), acquired->GetMappedSpan().size());
}
// A shared-memory-backed handle exposes the shared memory directly, so writes through GetData are
// visible in the acquired memory and data updates serialize nothing.
TEST_F(ClientInlineMemoryTransferServiceTest, SharedMemoryHandle_DataIsSharedDirectly) {
std::unique_ptr<MemoryHandle> handle =
mService->CreateMemoryHandle(kSize, MemoryHandleUse::MappedBuffer);
std::span<std::byte> data = handle->GetData();
ASSERT_EQ(kSize, data.size());
// With shared memory we don't need to serialize anything.
EXPECT_EQ(0u, handle->GetSerializeDataUpdateSize(0u, kSize));
// Write a known pattern through the handle.
Span<std::byte>(data).CopyFrom(kExpectedData);
// Acquire the same shared memory off the wire and verify the pattern is visible.
std::vector<std::byte> serialized(handle->GetSerializeCreateSize());
handle->SerializeCreate(std::span<volatile std::byte>(serialized.data(), serialized.size()));
SharedMemoryHandle wireHandle = ReadSerializedHandle(serialized);
Ref<SharedMemory> acquired = mManager->AcquireFromWire(SharedMemoryID(wireHandle.id));
ASSERT_NE(nullptr, acquired.Get());
std::span<std::byte> acquiredData = acquired->GetMappedSpan();
ASSERT_EQ(kSize, acquiredData.size());
EXPECT_THAT(acquiredData, testing::ElementsAreArray(kExpectedData));
}
// With `MemoryHandleUse::BulkData` the service falls back to a staging-buffer-backed handle whose
// SerializeCreate writes an empty (zeroed) SharedMemoryHandle.
TEST_F(ClientInlineMemoryTransferServiceTest, StagingHandle_SerializeCreate_BulkData) {
TestSerializedAsStagingHandle(MemoryHandleUse::BulkData);
}
// With `MemoryHandleUse::MappedAtCreationData` the service falls back to a staging-buffer-backed
// handle whose SerializeCreate writes an empty (zeroed) SharedMemoryHandle.
TEST_F(ClientInlineMemoryTransferServiceTest, StagingHandle_SerializeCreate_MappedAtCreationData) {
TestSerializedAsStagingHandle(MemoryHandleUse::MappedAtCreationData);
}
// A staging-backed handle serializes its data updates into the provided array so the exact bytes
// written through GetData can be recovered from the serialized array.
TEST_F(ClientInlineMemoryTransferServiceTest, StagingHandle_SerializeDataUpdate) {
std::unique_ptr<MemoryHandle> handle =
mService->CreateMemoryHandle(kSize, MemoryHandleUse::BulkData);
std::span<std::byte> data = handle->GetData();
ASSERT_EQ(kSize, data.size());
// Write a known pattern through the handle.
Span<std::byte>(data).CopyFrom(kExpectedData);
// Serialize the full range into an array and verify the array holds the written pattern.
ASSERT_EQ(kSize, handle->GetSerializeDataUpdateSize(0u, kSize));
std::vector<std::byte> serialized(handle->GetSerializeDataUpdateSize(0u, kSize));
handle->SerializeDataUpdate(std::span<volatile std::byte>(serialized.data(), serialized.size()),
0u, kSize);
EXPECT_THAT(serialized, testing::ElementsAreArray(kExpectedData));
}
// Tests for the server-side `InlineMemoryTransferService` created with a non-null
// `InlineSharedMemoryManager`.
class ServerInlineMemoryTransferServiceTest : public InlineSharedMemoryManagerTest {
protected:
void SetUp() override {
InlineSharedMemoryManagerTest::SetUp();
mClientService = client::CreateInlineMemoryTransferService(mManager);
mServerService = server::CreateInlineMemoryTransferService(mManager);
ASSERT_NE(nullptr, mClientService);
ASSERT_NE(nullptr, mServerService);
}
using ClientMemoryHandle = client::MemoryTransferService::MemoryHandle;
using ClientMemoryHandleUse = client::MemoryTransferService::MemoryHandleUse;
using ServerMemoryHandle = server::MemoryTransferService::MemoryHandle;
struct HandlePair {
std::unique_ptr<ClientMemoryHandle> clientHandle;
std::unique_ptr<ServerMemoryHandle> serverHandle;
};
// Creates a shared-memory-backed client handle, serializes its `SharedMemoryHandle` into a byte
// vector, and deserializes the `SharedMemoryHandle` into a server handle through
// `DeserializeMemoryHandle`.
HandlePair CreateSharedMemoryHandlePair() {
HandlePair pair;
pair.clientHandle =
mClientService->CreateMemoryHandle(kSize, ClientMemoryHandleUse::MappedBuffer);
EXPECT_NE(nullptr, pair.clientHandle);
std::vector<std::byte> createData(pair.clientHandle->GetSerializeCreateSize());
pair.clientHandle->SerializeCreate(
std::span<volatile std::byte>(createData.data(), createData.size()));
pair.serverHandle = mServerService->DeserializeMemoryHandle(
std::span<const std::byte>(createData.data(), createData.size()));
EXPECT_NE(nullptr, pair.serverHandle);
return pair;
}
std::unique_ptr<client::MemoryTransferService> mClientService;
std::unique_ptr<server::MemoryTransferService> mServerService;
};
// Test that deserializing a data update copies the shared memory into the target buffer.
TEST_F(ServerInlineMemoryTransferServiceTest, DeserializeDataUpdate_CopiesSharedMemoryIntoTarget) {
HandlePair pair = CreateSharedMemoryHandlePair();
std::span<std::byte> clientData = pair.clientHandle->GetData();
Span<std::byte>(clientData).CopyFrom(kExpectedData);
EXPECT_EQ(0u, pair.serverHandle->GetSerializeDataUpdateSize(0u, kSize));
std::vector<std::byte> target(kSize);
EXPECT_TRUE(pair.serverHandle->DeserializeDataUpdate(
std::span<const std::byte>(), 0u, kSize,
std::span<std::byte>(target.data(), target.size())));
EXPECT_THAT(target, testing::ElementsAreArray(kExpectedData));
}
// Test that serializing a data update copies the data into the shared memory.
TEST_F(ServerInlineMemoryTransferServiceTest, SerializeDataUpdate_CopiesDataIntoSharedMemory) {
HandlePair pair = CreateSharedMemoryHandlePair();
std::vector<std::byte> data(kSize);
Span<std::byte>(data).CopyFrom(kExpectedData);
pair.serverHandle->SerializeDataUpdate(std::span<volatile std::byte>(), 0u, kSize, data);
std::span<std::byte> clientData = pair.clientHandle->GetData();
EXPECT_THAT(clientData, testing::ElementsAreArray(kExpectedData));
}
} // namespace
} // namespace dawn::wire