blob: 8ae42f21cad05a781e536e09719c81d9fa14ac00 [file] [edit]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <gtest/gtest.h>
#include "src/graphics/drivers/msd-vsi-vip/src/command_buffer.h"
#include "test_command_buffer.h"
class TestExec : public TestCommandBuffer {};
// Tests submitting a simple batch that also provides a non-zero batch offset.
TEST_F(TestExec, SubmitBatchWithOffset) {
device_->StartDeviceThread();
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 4,
// The user data will start at a non-zero offset.
.batch_offset = 80,
.gpu_addr = 0x10000,
};
ASSERT_NO_FATAL_FAILURE(CreateAndSubmitBufferWaitCompletion(default_context(), buffer_desc));
}
// Verifies reset while GPU is busy.
TEST_F(TestExec, ResetAfterSubmit) {
for (uint32_t i = 0; i < 100; i++) {
device_->StartDeviceThread();
ASSERT_FALSE(default_context()->killed());
ASSERT_TRUE(device_->IsIdle());
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 4,
// The user data will start at a non-zero offset.
.batch_offset = 80,
.gpu_addr = 0x10000,
};
std::optional<uint32_t> fault_addr = {};
std::shared_ptr<MsdVsiBuffer> buffer;
ASSERT_NO_FATAL_FAILURE(CreateAndMapBuffer(default_context(), buffer_desc.buffer_size,
buffer_desc.map_page_count, buffer_desc.gpu_addr,
&buffer));
if (fault_addr.has_value()) {
constexpr uint32_t prefetch = 16; // arbitrary
WriteLinkCommand(buffer, buffer_desc.batch_offset, prefetch, fault_addr.value());
} else {
// Write a WAIT command at offset |kBatchOffset|.
WriteWaitCommand(buffer, buffer_desc.batch_offset);
}
for (uint32_t j = 0; j < 100; j++) {
auto semaphore = magma::PlatformSemaphore::Create();
ASSERT_NE(semaphore, nullptr);
std::unique_ptr<CommandBuffer> batch;
ASSERT_NO_FATAL_FAILURE(CreateAndPrepareBatch(default_context(), buffer,
buffer_desc.data_size, buffer_desc.batch_offset,
semaphore->Clone(), std::nullopt, &batch));
ASSERT_TRUE(default_context()->SubmitBatch(std::move(batch)).ok());
constexpr uint64_t kTimeoutMs = 1000;
EXPECT_EQ(MAGMA_STATUS_OK, semaphore->Wait(kTimeoutMs).get());
if (HasFailure()) {
printf("Failed TestExec on iterations %d %d\n", i, j);
device_->DumpStatusToLog();
}
ASSERT_NO_FATAL_FAILURE();
ASSERT_FALSE(default_context()->killed());
}
device_->EnqueueDeviceRequest(std::make_unique<MsdVsiDevice::ResetRequest>());
Release();
ASSERT_NO_FATAL_FAILURE(SetUp());
}
}
// Tests submitting buffers from different contexts from the same connection.
TEST_F(TestExec, SubmitBatchesMultipleContexts) {
// Create an additional context on the default connection.
auto context2 = MsdVsiContext::Create(default_connection(), default_address_space(),
device_->GetRingbuffer());
ASSERT_NE(context2, nullptr);
device_->StartDeviceThread();
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 4,
.batch_offset = 0,
.gpu_addr = 0x10000,
};
ASSERT_NO_FATAL_FAILURE(CreateAndSubmitBufferWaitCompletion(default_context(), buffer_desc));
ASSERT_EQ(device_->configured_address_space_.get(), default_address_space().get());
BufferDesc buffer_desc2 = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 4,
.batch_offset = 0,
.gpu_addr = 0x20000,
};
ASSERT_NO_FATAL_FAILURE(CreateAndSubmitBufferWaitCompletion(context2, buffer_desc2));
ASSERT_EQ(device_->configured_address_space_.get(), default_address_space().get());
}
// Tests reusing a gpu address after unmapping it.
//
// Creates two buffers, submits one and releases its GPU mapping.
// Maps the second buffer at the same GPU address and verifies that the
// GPU accesses the correct buffer.
TEST_F(TestExec, ReuseGpuAddress) {
device_->StartDeviceThread();
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
.gpu_addr = 0x10000,
};
constexpr uint32_t unmapped_gpu_addr = 0x50000;
// Create a buffer without mapping it.
std::shared_ptr<MsdVsiBuffer> msd_buffer;
ASSERT_NO_FATAL_FAILURE(CreateMsdBuffer(buffer_desc.buffer_size, &msd_buffer));
// Create, map and submit another buffer.
// This will wait for execution to complete.
std::shared_ptr<MsdVsiBuffer> submitted_buffer;
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(default_context(), buffer_desc, std::nullopt /* csb */,
true /* validate_batch */, &submitted_buffer));
// Write a bad instruction into the mapped buffer.
// If the GPU attempts to run this instruction, it will cause a MMU exception and
// the next |SubmitBatch| will fail.
uint32_t* cmd_ptr;
ASSERT_TRUE(submitted_buffer->platform_buffer()->MapCpu(reinterpret_cast<void**>(&cmd_ptr)));
BufferWriter buf_writer(cmd_ptr, buffer_desc.buffer_size, 0);
// Link to somewhere unmapped.
MiLink::write(&buf_writer, 1, unmapped_gpu_addr);
ASSERT_TRUE(submitted_buffer->platform_buffer()->UnmapCpu());
// Free the GPU address.
ASSERT_TRUE(default_connection()->ReleaseMapping(submitted_buffer->platform_buffer(),
buffer_desc.gpu_addr));
// Map the second buffer at the same GPU address and try submitting it.
magma::Status status = default_connection()->MapBufferGpu(
msd_buffer, buffer_desc.gpu_addr, 0 /* page_offset */, buffer_desc.map_page_count);
ASSERT_TRUE(status.ok());
// Submit the batch and verify we get a completion event.
auto semaphore = magma::PlatformSemaphore::Create();
EXPECT_NE(semaphore, nullptr);
std::unique_ptr<CommandBuffer> batch;
ASSERT_NO_FATAL_FAILURE(CreateAndPrepareBatch(
default_context(), msd_buffer, buffer_desc.data_size, buffer_desc.batch_offset,
semaphore->Clone(), std::nullopt /* csb */, &batch));
ASSERT_TRUE(batch->IsValidBatch());
// The context should determine that TLB flushing is required.
ASSERT_TRUE(default_context()->SubmitBatch(std::move(batch)).ok());
constexpr uint64_t kTimeoutMs = 1000;
EXPECT_EQ(MAGMA_STATUS_OK, semaphore->Wait(kTimeoutMs).get());
}
TEST_F(TestExec, Backlog) {
uint32_t num_batches = MsdVsiDevice::kNumEvents * 3;
auto semaphores = std::make_unique<std::unique_ptr<magma::PlatformSemaphore>[]>(num_batches);
for (unsigned int i = 0; i < num_batches; i++) {
semaphores[i] = magma::PlatformSemaphore::Create();
ASSERT_NE(semaphores[i], nullptr);
std::vector<std::shared_ptr<magma::PlatformSemaphore>> wait_semaphores;
std::vector<std::shared_ptr<magma::PlatformSemaphore>> signal_semaphores;
signal_semaphores.emplace_back(semaphores[i]->Clone());
auto batch =
std::make_unique<EventBatch>(default_context(), wait_semaphores, signal_semaphores);
ASSERT_EQ(MAGMA_STATUS_OK, device_->SubmitBatch(std::move(batch), false /* do_flush */).get());
}
// This will start processing all queued batches. Some of the batches will be added
// to the backlog and will be processed once earlier batches complete.
device_->StartDeviceThread();
device_->device_request_semaphore_->Signal();
// Wait for all the batches to complete.
for (unsigned int i = 0; i < num_batches; i++) {
constexpr uint64_t kTimeoutMs = 1000;
ASSERT_EQ(MAGMA_STATUS_OK, semaphores[i]->Wait(kTimeoutMs).get());
}
}
// Tests that the driver and client do not get stuck when an invalid batch is submitted.
TEST_F(TestExec, BacklogWithInvalidBatch) {
// Try to submit 2 more events than available.
uint32_t num_batches = MsdVsiDevice::kNumEvents + 2;
auto semaphores = std::make_unique<std::unique_ptr<magma::PlatformSemaphore>[]>(num_batches);
constexpr uint32_t default_data_size = 0x4;
// Make the second last batch submit a larger data size than supported.
// |SubmitCommandBuffer| will fail on this batch.
uint32_t invalid_batch_idx = num_batches - 2;
constexpr uint32_t invalid_data_size = 0xF0000;
uint32_t next_gpu_addr = 0x10000;
for (unsigned int i = 0; i < num_batches; i++) {
semaphores[i] = magma::PlatformSemaphore::Create();
ASSERT_NE(semaphores[i], nullptr);
uint32_t data_size = i == invalid_batch_idx ? invalid_data_size : default_data_size;
uint32_t buffer_size = magma::round_up(data_size + 8, magma::page_size());
std::shared_ptr<MsdVsiBuffer> buffer;
CreateAndMapBuffer(default_context(), buffer_size, buffer_size / magma::page_size(),
next_gpu_addr, &buffer);
ASSERT_NE(buffer, nullptr);
next_gpu_addr += buffer_size;
// Write a basic command into the buffer.
WriteWaitCommand(buffer, 0 /* offset */);
std::unique_ptr<CommandBuffer> batch;
CreateAndPrepareBatch(default_context(), buffer, data_size, 0, semaphores[i]->Clone(),
std::nullopt /* csb */, &batch);
ASSERT_NE(batch, nullptr);
ASSERT_EQ(MAGMA_STATUS_OK, device_->SubmitBatch(std::move(batch), false /* do_flush */).get());
}
device_->StartDeviceThread();
device_->device_request_semaphore_->Signal();
// The driver should drop any invalid batches, so we expect all semaphores to complete.
for (unsigned int i = 0; i < num_batches; i++) {
constexpr uint64_t kTimeoutMs = 1000;
ASSERT_EQ(MAGMA_STATUS_OK, semaphores[i]->Wait(kTimeoutMs).get());
}
// Ensure the driver has completed processing all requests before checking the events array.
// Otherwise for invalid batches there can be a race condition where the driver has just
// signalled the semaphore, but not yet reset the internal event state.
device_->Shutdown();
for (unsigned int i = 0; i < MsdVsiDevice::kNumEvents; i++) {
ASSERT_TRUE(!device_->events_[i].allocated);
}
}
TEST_F(TestExec, SwitchAddressSpace) {
device_->StartDeviceThread();
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
.gpu_addr = 0x10000,
};
// Create, map and submit another buffer.
// This will wait for execution to complete.
ASSERT_NO_FATAL_FAILURE(CreateAndSubmitBufferWaitCompletion(default_context(), buffer_desc));
// Drop the client before creating a new one.
DropDefaultClient();
std::unique_ptr<Client> client;
constexpr uint32_t kNewClientAddressSpaceIndex = 10;
// Replace the existing address space, connection and context.
ASSERT_NO_FATAL_FAILURE(CreateClient(kNewClientAddressSpaceIndex, &client));
BufferDesc buffer_desc2 = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
.gpu_addr = 0x20000,
};
ASSERT_NO_FATAL_FAILURE(CreateAndSubmitBufferWaitCompletion(client->context, buffer_desc2));
}
// Tests submitting buffers from many clients, each with different address spaces.
TEST_F(TestExec, SwitchMultipleAddressSpaces) {
device_->StartDeviceThread();
constexpr uint32_t kNumClients = 10;
std::vector<std::unique_ptr<Client>> clients;
for (unsigned int i = 0; i < kNumClients; i++) {
std::unique_ptr<Client> client;
ASSERT_NO_FATAL_FAILURE(CreateClient(i + 10 /* address_space_index */, &client));
clients.emplace_back(std::move(client));
}
constexpr uint32_t kBaseGpuAddr = 0x10000;
for (unsigned int i = 0; i < 2; i++) {
for (unsigned int j = 0; j < kNumClients; j++) {
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
// Use different gpu addresses to make sure the GPU is not just using the first mapping.
.gpu_addr = static_cast<uint32_t>(kBaseGpuAddr + (magma::page_size() * (i + j))),
};
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(clients[j]->context, buffer_desc));
ASSERT_EQ(device_->configured_address_space_.get(), clients[j]->address_space.get());
}
}
}
TEST_F(TestExec, SubmitContextStateBufferSameContext) {
// Allocate the context state buffers before starting the device thread,
// as this needs to allocate interrupt events.
std::unique_ptr<FakeContextStateBuffer> csb1;
std::unique_ptr<FakeContextStateBuffer> csb2;
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, default_context(), 0x10000, &csb1));
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, default_context(), 0x20000, &csb2));
device_->StartDeviceThread();
// Submit 2 batches with context state buffers in the same address space.
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
.gpu_addr = 0x30000,
};
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(default_context(), buffer_desc, csb1->ExecResource()));
buffer_desc.gpu_addr = 0x40000;
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(default_context(), buffer_desc, csb2->ExecResource()));
// Only the first context state buffer should be executed.
ASSERT_NO_FATAL_FAILURE(csb1->WaitForCompletion());
ASSERT_EQ(device_->num_events_completed_, 3u); // 1 context state buffer and 2 command buffers
}
TEST_F(TestExec, SubmitEventBeforeContextStateBuffer) {
// Allocate the context state buffer before starting the device thread,
// as this needs to allocate an interrupt event.
std::unique_ptr<FakeContextStateBuffer> csb1;
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, default_context(), 0x10000, &csb1));
device_->StartDeviceThread();
// Submit an event batch.
auto semaphore = magma::PlatformSemaphore::Create();
ASSERT_NE(semaphore, nullptr);
std::vector<std::shared_ptr<magma::PlatformSemaphore>> wait_semaphores;
std::vector<std::shared_ptr<magma::PlatformSemaphore>> signal_semaphores;
signal_semaphores.emplace_back(semaphore->Clone());
auto batch = std::make_unique<EventBatch>(default_context(), wait_semaphores, signal_semaphores);
ASSERT_EQ(MAGMA_STATUS_OK, device_->SubmitBatch(std::move(batch), false /* do_flush */).get());
constexpr uint64_t kTimeoutMs = 1000;
ASSERT_EQ(MAGMA_STATUS_OK, semaphore->Wait(kTimeoutMs).get());
// Submit a context state buffer in the same address space.
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
.gpu_addr = 0x30000,
};
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(default_context(), buffer_desc, csb1->ExecResource()));
// The context state buffer should be executed.
ASSERT_NO_FATAL_FAILURE(csb1->WaitForCompletion());
ASSERT_EQ(device_->num_events_completed_, 3u); // 1 event, context state buffer, command buffer
}
TEST_F(TestExec, SubmitContextStateBufferMultipleContexts) {
// Create contexts with the same default address space.
auto context_a = MsdVsiContext::Create(default_connection(), default_address_space(),
device_->GetRingbuffer());
ASSERT_NE(context_a, nullptr);
auto client_a =
std::make_unique<Client>(default_connection(), context_a, default_address_space());
auto context_b = MsdVsiContext::Create(default_connection(), default_address_space(),
device_->GetRingbuffer());
ASSERT_NE(context_b, nullptr);
auto client_b =
std::make_unique<Client>(default_connection(), context_b, default_address_space());
auto context_c = MsdVsiContext::Create(default_connection(), default_address_space(),
device_->GetRingbuffer());
ASSERT_NE(context_c, nullptr);
auto client_c =
std::make_unique<Client>(default_connection(), context_c, default_address_space());
// Allocate the context state buffers before starting the device thread,
// as this needs to allocate interrupt events.
std::unique_ptr<FakeContextStateBuffer> csb_a1;
std::unique_ptr<FakeContextStateBuffer> csb_b1;
std::unique_ptr<FakeContextStateBuffer> csb_b2;
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, context_a, 0x10000, &csb_a1));
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, context_b, 0x30000, &csb_b1));
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, context_b, 0x20000, &csb_b2));
device_->StartDeviceThread();
// Submit from context_a with csb_a1.
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
.gpu_addr = 0x40000,
};
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(context_a, buffer_desc, csb_a1->ExecResource()));
// Submit from context_b with csb_b1.
buffer_desc.gpu_addr = 0x50000;
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(context_b, buffer_desc, csb_b1->ExecResource()));
// Submit from context_c with no CSB.
buffer_desc.gpu_addr = 0x60000;
ASSERT_NO_FATAL_FAILURE(CreateAndSubmitBufferWaitCompletion(context_c, buffer_desc));
// Switch back to context_b and submit with csb_b2.
buffer_desc.gpu_addr = 0x70000;
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(context_b, buffer_desc, csb_b2->ExecResource()));
// All context state buffers should be executed.
ASSERT_NO_FATAL_FAILURE(csb_a1->WaitForCompletion());
ASSERT_NO_FATAL_FAILURE(csb_b1->WaitForCompletion());
ASSERT_NO_FATAL_FAILURE(csb_b2->WaitForCompletion());
ASSERT_EQ(device_->num_events_completed_, 7u); // 3 context state buffers and 4 command buffers
}
TEST_F(TestExec, SubmitContextStateBufferMultipleAddressSpaces) {
std::unique_ptr<Client> client_a;
constexpr uint32_t kClientAIndex = 2;
ASSERT_NO_FATAL_FAILURE(CreateClient(kClientAIndex, &client_a));
std::unique_ptr<Client> client_b;
constexpr uint32_t kClientBIndex = 3;
ASSERT_NO_FATAL_FAILURE(CreateClient(kClientBIndex, &client_b));
// Allocate the context state buffers before starting the device thread,
// as this needs to allocate interrupt events.
std::unique_ptr<FakeContextStateBuffer> csb_a1;
std::unique_ptr<FakeContextStateBuffer> csb_a2;
std::unique_ptr<FakeContextStateBuffer> csb_b1;
std::unique_ptr<FakeContextStateBuffer> csb_b2;
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, client_a->context, 0x10000, &csb_a1));
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, client_a->context, 0x20000, &csb_a2));
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, client_b->context, 0x30000, &csb_b1));
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, client_b->context, 0x40000, &csb_b2));
device_->StartDeviceThread();
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
.gpu_addr = 0x50000,
};
// Submit from client A.
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(client_a->context, buffer_desc, csb_a1->ExecResource()));
// Switch to client B.
buffer_desc.gpu_addr = 0x60000;
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(client_b->context, buffer_desc, csb_b1->ExecResource()));
buffer_desc.gpu_addr = 0x70000;
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(client_b->context, buffer_desc, csb_b2->ExecResource()));
// Switch back to client A.
buffer_desc.gpu_addr = 0x80000;
ASSERT_NO_FATAL_FAILURE(
CreateAndSubmitBufferWaitCompletion(client_a->context, buffer_desc, csb_a2->ExecResource()));
// We expect all context state buffers except |csb_b2| to be executed.
ASSERT_NO_FATAL_FAILURE(csb_a1->WaitForCompletion());
ASSERT_NO_FATAL_FAILURE(csb_a2->WaitForCompletion());
ASSERT_NO_FATAL_FAILURE(csb_b1->WaitForCompletion());
ASSERT_EQ(device_->num_events_completed_, 7u); // 3 context state buffers and 4 command buffers
}
TEST_F(TestExec, BatchHasTooManyResources) {
// Allocate the context state buffers before starting the device thread,
// as this needs to allocate interrupt events.
std::unique_ptr<FakeContextStateBuffer> csb1;
std::unique_ptr<FakeContextStateBuffer> csb2;
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, default_context(), 0x10000, &csb1));
ASSERT_NO_FATAL_FAILURE(
FakeContextStateBuffer::CreateWithEvent(device_, default_context(), 0x20000, &csb2));
BufferDesc buffer_desc = {
.buffer_size = 4096,
.map_page_count = 1,
.data_size = 8,
.batch_offset = 0,
.gpu_addr = 0x30000,
};
std::shared_ptr<MsdVsiBuffer> buffer;
ASSERT_NO_FATAL_FAILURE(CreateAndMapBuffer(default_context(), buffer_desc.buffer_size,
buffer_desc.map_page_count, buffer_desc.gpu_addr,
&buffer));
auto command_buffer = std::make_unique<msd::magma_command_buffer>(msd::magma_command_buffer{
.resource_count = 3,
.batch_buffer_resource_index = 0,
.batch_start_offset = buffer_desc.batch_offset,
.wait_semaphore_count = 0,
.signal_semaphore_count = 0,
.flags = 0,
});
std::vector<CommandBuffer::ExecResource> resources;
resources.emplace_back(
CommandBuffer::ExecResource{.buffer = buffer, .offset = 0, .length = buffer_desc.data_size});
resources.emplace_back(csb1->ExecResource());
resources.emplace_back(csb2->ExecResource());
std::vector<std::shared_ptr<magma::PlatformSemaphore>> signal_semaphores;
auto batch = CommandBuffer::Create(default_context(), 0, std::move(command_buffer),
std::move(resources), std::move(signal_semaphores));
ASSERT_EQ(batch, nullptr);
}
TEST_F(TestExec, KillContextOnSubmitFail) {
device_->StartDeviceThread();
constexpr uint32_t kBufferSize = 0x1000;
// This will fail in |SubmitCommandBuffer| if there is not enough space for the extra LINK
// instruction.
BufferDesc buffer_desc = {
.buffer_size = kBufferSize,
.map_page_count = 1,
.data_size = kBufferSize,
.batch_offset = 0,
.gpu_addr = kBufferSize,
};
ASSERT_NO_FATAL_FAILURE(CreateAndSubmitBufferWaitCompletion(
default_context(), buffer_desc, std::nullopt /* context_state_buffer */,
false /* validate_buffer */));
ASSERT_TRUE(default_context()->killed());
}