mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 16:23:05 +00:00
format src and tests with clang-format
This commit is contained in:
1 parent
68be13345a
commit
d8a4c83cc7
81 files changed
+19582
-21405
No files matched your search
+339
-396
@@ -1,5 +1,4 @@
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -17,484 +16,428 @@ namespace EventQueue = Libs::LibKernel::EventQueue;
|
||||
using Libs::LibKernel::KERNEL_ERROR_EBADF;
|
||||
using Libs::LibKernel::KERNEL_ERROR_ENOENT;
|
||||
|
||||
void Check(bool value, const char *text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "EventQueueLifetimeTests: failed: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
void Check(bool value, const char* text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "EventQueueLifetimeTests: failed: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckConcurrentResult(int result, const char *text) {
|
||||
Check(result == OK || result == KERNEL_ERROR_EBADF ||
|
||||
result == KERNEL_ERROR_ENOENT,
|
||||
text);
|
||||
void CheckConcurrentResult(int result, const char* text) {
|
||||
Check(result == OK || result == KERNEL_ERROR_EBADF || result == KERNEL_ERROR_ENOENT, text);
|
||||
}
|
||||
|
||||
void CountDeletedEvent(EventQueue::KernelEqueue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *count = static_cast<std::atomic_uint32_t *>(event->filter.data);
|
||||
count->fetch_add(1, std::memory_order_relaxed);
|
||||
void CountDeletedEvent(EventQueue::KernelEqueue, EventQueue::KernelEqueueEvent* event) {
|
||||
auto* count = static_cast<std::atomic_uint32_t*>(event->filter.data);
|
||||
count->fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
struct DuplicateEventOwner {
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
std::atomic_uint32_t delete_count {0};
|
||||
};
|
||||
|
||||
void QueueDuplicateEvent(EventQueue::KernelEqueueEvent *event,
|
||||
void *trigger_data) {
|
||||
auto next = event->event;
|
||||
next.data = reinterpret_cast<intptr_t>(trigger_data);
|
||||
if (event->triggered) {
|
||||
event->pending_events.push_back(next);
|
||||
} else {
|
||||
event->event = next;
|
||||
event->triggered = true;
|
||||
}
|
||||
void QueueDuplicateEvent(EventQueue::KernelEqueueEvent* event, void* trigger_data) {
|
||||
auto next = event->event;
|
||||
next.data = reinterpret_cast<intptr_t>(trigger_data);
|
||||
if (event->triggered) {
|
||||
event->pending_events.push_back(next);
|
||||
} else {
|
||||
event->event = next;
|
||||
event->triggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetDuplicateEvent(EventQueue::KernelEqueueEvent *event) {
|
||||
event->triggered = false;
|
||||
event->event.data = 0;
|
||||
void ResetDuplicateEvent(EventQueue::KernelEqueueEvent* event) {
|
||||
event->triggered = false;
|
||||
event->event.data = 0;
|
||||
}
|
||||
|
||||
void DeleteDuplicateEvent(EventQueue::KernelEqueue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *owner = static_cast<DuplicateEventOwner *>(event->filter.data);
|
||||
owner->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
void DeleteDuplicateEvent(EventQueue::KernelEqueue, EventQueue::KernelEqueueEvent* event) {
|
||||
auto* owner = static_cast<DuplicateEventOwner*>(event->filter.data);
|
||||
owner->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void PoisonDuplicateEvent(EventQueue::KernelEqueueEvent *, void *) {
|
||||
Check(false, "duplicate add replaced trigger callback");
|
||||
void PoisonDuplicateEvent(EventQueue::KernelEqueueEvent*, void*) {
|
||||
Check(false, "duplicate add replaced trigger callback");
|
||||
}
|
||||
|
||||
void TestDuplicateAddPreservesEventState() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "duplicate-add") == OK,
|
||||
"create duplicate add queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "duplicate-add") == OK,
|
||||
"create duplicate add queue");
|
||||
|
||||
auto original_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_original = original_owner;
|
||||
EventQueue::KernelEqueueEvent original{};
|
||||
original.event.ident = 17;
|
||||
original.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
original.event.udata = reinterpret_cast<void *>(0x1111);
|
||||
original.filter.data = original_owner.get();
|
||||
original.filter.owner = original_owner;
|
||||
original.filter.trigger_func = QueueDuplicateEvent;
|
||||
original.filter.reset_func = ResetDuplicateEvent;
|
||||
original.filter.delete_event_func = DeleteDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, original) == OK,
|
||||
"add original duplicate event");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void *>(0x1234)) == OK,
|
||||
"queue first trigger");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void *>(0x5678)) == OK,
|
||||
"queue pending trigger");
|
||||
auto original_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_original = original_owner;
|
||||
EventQueue::KernelEqueueEvent original {};
|
||||
original.event.ident = 17;
|
||||
original.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
original.event.udata = reinterpret_cast<void*>(0x1111);
|
||||
original.filter.data = original_owner.get();
|
||||
original.filter.owner = original_owner;
|
||||
original.filter.trigger_func = QueueDuplicateEvent;
|
||||
original.filter.reset_func = ResetDuplicateEvent;
|
||||
original.filter.delete_event_func = DeleteDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, original) == OK, "add original duplicate event");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17, EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void*>(0x1234)) == OK,
|
||||
"queue first trigger");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17, EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void*>(0x5678)) == OK,
|
||||
"queue pending trigger");
|
||||
|
||||
auto replacement_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_replacement = replacement_owner;
|
||||
EventQueue::KernelEqueueEvent duplicate{};
|
||||
duplicate.triggered = false;
|
||||
duplicate.deadline_ns = 1;
|
||||
duplicate.event.ident = 17;
|
||||
duplicate.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
duplicate.event.data = 0x7fffffff;
|
||||
duplicate.event.udata = reinterpret_cast<void *>(0x2222);
|
||||
duplicate.filter.data = replacement_owner.get();
|
||||
duplicate.filter.owner = replacement_owner;
|
||||
duplicate.filter.trigger_func = PoisonDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, duplicate) == OK,
|
||||
"update duplicate event");
|
||||
auto replacement_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_replacement = replacement_owner;
|
||||
EventQueue::KernelEqueueEvent duplicate {};
|
||||
duplicate.triggered = false;
|
||||
duplicate.deadline_ns = 1;
|
||||
duplicate.event.ident = 17;
|
||||
duplicate.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
duplicate.event.data = 0x7fffffff;
|
||||
duplicate.event.udata = reinterpret_cast<void*>(0x2222);
|
||||
duplicate.filter.data = replacement_owner.get();
|
||||
duplicate.filter.owner = replacement_owner;
|
||||
duplicate.filter.trigger_func = PoisonDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, duplicate) == OK, "update duplicate event");
|
||||
|
||||
duplicate.filter.owner.reset();
|
||||
replacement_owner.reset();
|
||||
Check(weak_replacement.expired(), "duplicate owner is not retained");
|
||||
original.filter.owner.reset();
|
||||
original_owner.reset();
|
||||
Check(!weak_original.expired(), "original event owner remains retained");
|
||||
duplicate.filter.owner.reset();
|
||||
replacement_owner.reset();
|
||||
Check(weak_replacement.expired(), "duplicate owner is not retained");
|
||||
original.filter.owner.reset();
|
||||
original_owner.reset();
|
||||
Check(!weak_original.expired(), "original event owner remains retained");
|
||||
|
||||
EventQueue::KernelEvent events[2]{};
|
||||
int out = 0;
|
||||
Libs::LibKernel::KernelUseconds timeout = 0;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK,
|
||||
"read queued duplicate triggers");
|
||||
Check(out == 2, "duplicate add preserves pending event count");
|
||||
Check(events[0].data == 0x1234 && events[1].data == 0x5678,
|
||||
"duplicate add preserves current and pending event data");
|
||||
Check(events[0].udata == reinterpret_cast<void *>(0x2222),
|
||||
"duplicate add updates current user data");
|
||||
Check(events[1].udata == reinterpret_cast<void *>(0x2222),
|
||||
"duplicate add updates pending user data");
|
||||
EventQueue::KernelEvent events[2] {};
|
||||
int out = 0;
|
||||
Libs::LibKernel::KernelUseconds timeout = 0;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK,
|
||||
"read queued duplicate triggers");
|
||||
Check(out == 2, "duplicate add preserves pending event count");
|
||||
Check(events[0].data == 0x1234 && events[1].data == 0x5678,
|
||||
"duplicate add preserves current and pending event data");
|
||||
Check(events[0].udata == reinterpret_cast<void*>(0x2222),
|
||||
"duplicate add updates current user data");
|
||||
Check(events[1].udata == reinterpret_cast<void*>(0x2222),
|
||||
"duplicate add updates pending user data");
|
||||
|
||||
EventQueue::KernelEvent timer_event{};
|
||||
Check(EventQueue::KernelWaitEqueue(queue, &timer_event, 1, &out, &timeout) ==
|
||||
OK &&
|
||||
out == 1,
|
||||
"duplicate add updates deadline metadata");
|
||||
Check(timer_event.data == 0 &&
|
||||
timer_event.udata == reinterpret_cast<void *>(0x2222),
|
||||
"deadline trigger retains updated duplicate metadata");
|
||||
EventQueue::KernelEvent timer_event {};
|
||||
Check(EventQueue::KernelWaitEqueue(queue, &timer_event, 1, &out, &timeout) == OK && out == 1,
|
||||
"duplicate add updates deadline metadata");
|
||||
Check(timer_event.data == 0 && timer_event.udata == reinterpret_cast<void*>(0x2222),
|
||||
"deadline trigger retains updated duplicate metadata");
|
||||
|
||||
auto retained_owner = weak_original.lock();
|
||||
Check(retained_owner != nullptr, "original owner alive before delete");
|
||||
Check(EventQueue::KernelDeleteEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT) ==
|
||||
OK,
|
||||
"delete duplicate event");
|
||||
Check(retained_owner->delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"duplicate add preserves delete callback");
|
||||
retained_owner.reset();
|
||||
Check(weak_original.expired(), "original owner released on delete");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete duplicate add queue");
|
||||
auto retained_owner = weak_original.lock();
|
||||
Check(retained_owner != nullptr, "original owner alive before delete");
|
||||
Check(EventQueue::KernelDeleteEvent(queue, 17, EventQueue::KERNEL_EVFILT_VIDEO_OUT) == OK,
|
||||
"delete duplicate event");
|
||||
Check(retained_owner->delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"duplicate add preserves delete callback");
|
||||
retained_owner.reset();
|
||||
Check(weak_original.expired(), "original owner released on delete");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete duplicate add queue");
|
||||
}
|
||||
|
||||
struct SimulatedVideoOutEventState;
|
||||
|
||||
struct SimulatedVideoOutRegistration {
|
||||
EventQueue::KernelEqueue handle = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
std::shared_ptr<SimulatedVideoOutEventState> state;
|
||||
uint64_t marker = 0x123456789abcdef0ull;
|
||||
EventQueue::KernelEqueue handle = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
std::shared_ptr<SimulatedVideoOutEventState> state;
|
||||
uint64_t marker = 0x123456789abcdef0ull;
|
||||
};
|
||||
|
||||
struct SimulatedVideoOutEventState {
|
||||
SimulatedVideoOutEventState(std::atomic_uint32_t &stage,
|
||||
std::atomic_uint32_t &destroy_count)
|
||||
: stage(stage), destroy_count(destroy_count) {}
|
||||
SimulatedVideoOutEventState(std::atomic_uint32_t& stage, std::atomic_uint32_t& destroy_count)
|
||||
: stage(stage), destroy_count(destroy_count) {}
|
||||
|
||||
~SimulatedVideoOutEventState() {
|
||||
destroy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
~SimulatedVideoOutEventState() { destroy_count.fetch_add(1, std::memory_order_relaxed); }
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> queues;
|
||||
std::atomic_uint32_t &stage;
|
||||
std::atomic_uint32_t &destroy_count;
|
||||
uint64_t marker = 0xfedcba9876543210ull;
|
||||
std::mutex mutex;
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> queues;
|
||||
std::atomic_uint32_t& stage;
|
||||
std::atomic_uint32_t& destroy_count;
|
||||
uint64_t marker = 0xfedcba9876543210ull;
|
||||
};
|
||||
|
||||
void DetachSimulatedVideoOutEvent(EventQueue::KernelEqueue queue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *registration =
|
||||
static_cast<SimulatedVideoOutRegistration *>(event->filter.data);
|
||||
Check(registration != nullptr && registration->handle == queue,
|
||||
"simulated registration identity");
|
||||
auto state = registration->state;
|
||||
Check(state != nullptr, "simulated event owns shared state");
|
||||
state->stage.store(1, std::memory_order_release);
|
||||
while (state->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
void DetachSimulatedVideoOutEvent(EventQueue::KernelEqueue queue,
|
||||
EventQueue::KernelEqueueEvent* event) {
|
||||
auto* registration = static_cast<SimulatedVideoOutRegistration*>(event->filter.data);
|
||||
Check(registration != nullptr && registration->handle == queue,
|
||||
"simulated registration identity");
|
||||
auto state = registration->state;
|
||||
Check(state != nullptr, "simulated event owns shared state");
|
||||
state->stage.store(1, std::memory_order_release);
|
||||
while (state->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(state->mutex);
|
||||
const auto entry =
|
||||
std::find_if(state->queues.begin(), state->queues.end(),
|
||||
[registration](const auto &candidate) {
|
||||
return candidate.get() == registration;
|
||||
});
|
||||
if (entry != state->queues.end()) {
|
||||
state->queues.erase(entry);
|
||||
}
|
||||
}
|
||||
event->filter.owner.reset();
|
||||
Check(state->marker == 0xfedcba9876543210ull &&
|
||||
registration->marker == 0x123456789abcdef0ull,
|
||||
"callback state survives simulated port destruction");
|
||||
{
|
||||
std::lock_guard lock(state->mutex);
|
||||
const auto entry = std::find_if(
|
||||
state->queues.begin(), state->queues.end(),
|
||||
[registration](const auto& candidate) { return candidate.get() == registration; });
|
||||
if (entry != state->queues.end()) {
|
||||
state->queues.erase(entry);
|
||||
}
|
||||
}
|
||||
event->filter.owner.reset();
|
||||
Check(state->marker == 0xfedcba9876543210ull && registration->marker == 0x123456789abcdef0ull,
|
||||
"callback state survives simulated port destruction");
|
||||
}
|
||||
|
||||
void TestCallbackStateOutlivesPort() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "shared-port-state") == OK,
|
||||
"create shared port state queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "shared-port-state") == OK,
|
||||
"create shared port state queue");
|
||||
|
||||
std::atomic_uint32_t stage{0};
|
||||
std::atomic_uint32_t destroy_count{0};
|
||||
auto port_state =
|
||||
std::make_shared<SimulatedVideoOutEventState>(stage, destroy_count);
|
||||
std::weak_ptr<SimulatedVideoOutEventState> weak_state = port_state;
|
||||
auto registration = std::make_shared<SimulatedVideoOutRegistration>();
|
||||
std::weak_ptr<SimulatedVideoOutRegistration> weak_registration =
|
||||
registration;
|
||||
registration->handle = queue;
|
||||
registration->state = port_state;
|
||||
port_state->queues.push_back(registration);
|
||||
std::atomic_uint32_t stage {0};
|
||||
std::atomic_uint32_t destroy_count {0};
|
||||
auto port_state = std::make_shared<SimulatedVideoOutEventState>(stage, destroy_count);
|
||||
std::weak_ptr<SimulatedVideoOutEventState> weak_state = port_state;
|
||||
auto registration = std::make_shared<SimulatedVideoOutRegistration>();
|
||||
std::weak_ptr<SimulatedVideoOutRegistration> weak_registration = registration;
|
||||
registration->handle = queue;
|
||||
registration->state = port_state;
|
||||
port_state->queues.push_back(registration);
|
||||
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 8;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DetachSimulatedVideoOutEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add shared port state event");
|
||||
event.filter.owner.reset();
|
||||
EventQueue::KernelEqueueEvent event {};
|
||||
event.event.ident = 8;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DetachSimulatedVideoOutEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add shared port state event");
|
||||
event.filter.owner.reset();
|
||||
|
||||
std::jthread close([&] {
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete shared port state queue");
|
||||
});
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
std::jthread close([&] {
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete shared port state queue");
|
||||
});
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> detached;
|
||||
{
|
||||
std::lock_guard lock(port_state->mutex);
|
||||
detached = std::move(port_state->queues);
|
||||
}
|
||||
registration.reset();
|
||||
detached.clear();
|
||||
port_state.reset();
|
||||
Check(!weak_state.expired(),
|
||||
"callback state outlives simulated port object");
|
||||
Check(!weak_registration.expired(),
|
||||
"registration outlives simulated port object");
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> detached;
|
||||
{
|
||||
std::lock_guard lock(port_state->mutex);
|
||||
detached = std::move(port_state->queues);
|
||||
}
|
||||
registration.reset();
|
||||
detached.clear();
|
||||
port_state.reset();
|
||||
Check(!weak_state.expired(), "callback state outlives simulated port object");
|
||||
Check(!weak_registration.expired(), "registration outlives simulated port object");
|
||||
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(weak_registration.expired(), "detached registration is released");
|
||||
Check(weak_state.expired(), "detached event state is released");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"shared event state is destroyed exactly once");
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(weak_registration.expired(), "detached registration is released");
|
||||
Check(weak_state.expired(), "detached event state is released");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"shared event state is destroyed exactly once");
|
||||
}
|
||||
|
||||
struct OwnedCallbackPayload {
|
||||
OwnedCallbackPayload(std::atomic_uint32_t &stage,
|
||||
std::atomic_uint32_t &delete_count,
|
||||
std::atomic_uint32_t &destroy_count)
|
||||
: stage(stage), delete_count(delete_count), destroy_count(destroy_count) {}
|
||||
OwnedCallbackPayload(std::atomic_uint32_t& stage, std::atomic_uint32_t& delete_count,
|
||||
std::atomic_uint32_t& destroy_count)
|
||||
: stage(stage), delete_count(delete_count), destroy_count(destroy_count) {}
|
||||
|
||||
std::atomic_uint32_t &stage;
|
||||
std::atomic_uint32_t &delete_count;
|
||||
std::atomic_uint32_t &destroy_count;
|
||||
uint64_t marker = 0xc0dec0dec0dec0deull;
|
||||
std::atomic_uint32_t& stage;
|
||||
std::atomic_uint32_t& delete_count;
|
||||
std::atomic_uint32_t& destroy_count;
|
||||
uint64_t marker = 0xc0dec0dec0dec0deull;
|
||||
|
||||
~OwnedCallbackPayload() {
|
||||
destroy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
~OwnedCallbackPayload() { destroy_count.fetch_add(1, std::memory_order_relaxed); }
|
||||
};
|
||||
|
||||
void DeleteOwnedEvent(EventQueue::KernelEqueue queue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *payload = static_cast<OwnedCallbackPayload *>(event->filter.data);
|
||||
Check(payload != nullptr, "owned callback payload");
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"owned callback runs after registry removal");
|
||||
payload->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
event->filter.owner.reset();
|
||||
payload->stage.store(1, std::memory_order_release);
|
||||
while (payload->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
Check(payload->marker == 0xc0dec0dec0dec0deull,
|
||||
"owned callback payload remains valid");
|
||||
void DeleteOwnedEvent(EventQueue::KernelEqueue queue, EventQueue::KernelEqueueEvent* event) {
|
||||
auto* payload = static_cast<OwnedCallbackPayload*>(event->filter.data);
|
||||
Check(payload != nullptr, "owned callback payload");
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "owned callback runs after registry removal");
|
||||
payload->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
event->filter.owner.reset();
|
||||
payload->stage.store(1, std::memory_order_release);
|
||||
while (payload->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
Check(payload->marker == 0xc0dec0dec0dec0deull, "owned callback payload remains valid");
|
||||
}
|
||||
|
||||
void TestCallbackOwnsPayload() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "owned-callback") == OK,
|
||||
"create owned callback queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "owned-callback") == OK,
|
||||
"create owned callback queue");
|
||||
|
||||
std::atomic_uint32_t stage{0};
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
std::atomic_uint32_t destroy_count{0};
|
||||
auto registration =
|
||||
std::make_shared<OwnedCallbackPayload>(stage, delete_count, destroy_count);
|
||||
std::weak_ptr<OwnedCallbackPayload> weak_registration = registration;
|
||||
std::vector<std::shared_ptr<OwnedCallbackPayload>> port_registrations{
|
||||
registration};
|
||||
{
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 2;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DeleteOwnedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add owned callback event");
|
||||
}
|
||||
std::atomic_uint32_t stage {0};
|
||||
std::atomic_uint32_t delete_count {0};
|
||||
std::atomic_uint32_t destroy_count {0};
|
||||
auto registration = std::make_shared<OwnedCallbackPayload>(stage, delete_count, destroy_count);
|
||||
std::weak_ptr<OwnedCallbackPayload> weak_registration = registration;
|
||||
std::vector<std::shared_ptr<OwnedCallbackPayload>> port_registrations {registration};
|
||||
{
|
||||
EventQueue::KernelEqueueEvent event {};
|
||||
event.event.ident = 2;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DeleteOwnedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add owned callback event");
|
||||
}
|
||||
|
||||
std::jthread close([&] {
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete owned callback queue");
|
||||
});
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
std::jthread close(
|
||||
[&] { Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete owned callback queue"); });
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"owned callback queue removed while callback blocked");
|
||||
port_registrations.clear();
|
||||
registration.reset();
|
||||
Check(!weak_registration.expired(),
|
||||
"delete callback retains detached payload");
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"owned callback queue removed while callback blocked");
|
||||
port_registrations.clear();
|
||||
registration.reset();
|
||||
Check(!weak_registration.expired(), "delete callback retains detached payload");
|
||||
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"owned callback runs exactly once");
|
||||
Check(weak_registration.expired(),
|
||||
"owned callback payload released with event");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"owned callback payload destroyed exactly once");
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1, "owned callback runs exactly once");
|
||||
Check(weak_registration.expired(), "owned callback payload released with event");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"owned callback payload destroyed exactly once");
|
||||
}
|
||||
|
||||
void TestPinnedClose() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "pinned-close") == OK,
|
||||
"create pinned queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "pinned-close") == OK, "create pinned queue");
|
||||
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 1;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = &delete_count;
|
||||
event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add callback event");
|
||||
std::atomic_uint32_t delete_count {0};
|
||||
EventQueue::KernelEqueueEvent event {};
|
||||
event.event.ident = 1;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = &delete_count;
|
||||
event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add callback event");
|
||||
|
||||
auto owner = EventQueue::KernelPinEqueue(queue);
|
||||
Check(owner != nullptr, "pin live queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete pinned queue");
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"close invokes callback once");
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "deleted queue leaves registry");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 1,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
nullptr) == KERNEL_ERROR_EBADF,
|
||||
"stale trigger rejected");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == KERNEL_ERROR_EBADF,
|
||||
"second queue delete rejected");
|
||||
auto owner = EventQueue::KernelPinEqueue(queue);
|
||||
Check(owner != nullptr, "pin live queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete pinned queue");
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1, "close invokes callback once");
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "deleted queue leaves registry");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 1, EventQueue::KERNEL_EVFILT_VIDEO_OUT, nullptr) ==
|
||||
KERNEL_ERROR_EBADF,
|
||||
"stale trigger rejected");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == KERNEL_ERROR_EBADF,
|
||||
"second queue delete rejected");
|
||||
|
||||
owner.reset();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"deferred destruction does not repeat callback");
|
||||
owner.reset();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"deferred destruction does not repeat callback");
|
||||
}
|
||||
|
||||
void TestStaleHandleNeverAliasesNewQueue() {
|
||||
EventQueue::KernelEqueue stale = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&stale, "stale-handle") == OK,
|
||||
"create stale queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(stale) == OK, "delete stale queue");
|
||||
EventQueue::KernelEqueue stale = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&stale, "stale-handle") == OK, "create stale queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(stale) == OK, "delete stale queue");
|
||||
|
||||
EventQueue::KernelEqueue replacement = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&replacement, "replacement") == OK,
|
||||
"create replacement queue");
|
||||
Check(stale != replacement, "queue handles are never recycled");
|
||||
Check(!EventQueue::KernelPinEqueue(stale), "stale handle does not pin");
|
||||
Check(EventQueue::KernelAddUserEvent(stale, 11) == KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot mutate replacement");
|
||||
Check(EventQueue::KernelAddUserEvent(replacement, 11) == OK,
|
||||
"replacement handle remains valid");
|
||||
Check(EventQueue::KernelTriggerUserEvent(stale, 11, nullptr) ==
|
||||
KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot trigger replacement");
|
||||
Check(EventQueue::KernelTriggerUserEvent(replacement, 11, nullptr) == OK,
|
||||
"replacement event triggers");
|
||||
Check(EventQueue::KernelDeleteEqueue(replacement) == OK,
|
||||
"delete replacement queue");
|
||||
EventQueue::KernelEqueue replacement = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&replacement, "replacement") == OK,
|
||||
"create replacement queue");
|
||||
Check(stale != replacement, "queue handles are never recycled");
|
||||
Check(!EventQueue::KernelPinEqueue(stale), "stale handle does not pin");
|
||||
Check(EventQueue::KernelAddUserEvent(stale, 11) == KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot mutate replacement");
|
||||
Check(EventQueue::KernelAddUserEvent(replacement, 11) == OK,
|
||||
"replacement handle remains valid");
|
||||
Check(EventQueue::KernelTriggerUserEvent(stale, 11, nullptr) == KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot trigger replacement");
|
||||
Check(EventQueue::KernelTriggerUserEvent(replacement, 11, nullptr) == OK,
|
||||
"replacement event triggers");
|
||||
Check(EventQueue::KernelDeleteEqueue(replacement) == OK, "delete replacement queue");
|
||||
}
|
||||
|
||||
void TestConcurrentCloseCallback() {
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "callback-race") == OK,
|
||||
"create callback race queue");
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "callback-race") == OK,
|
||||
"create callback race queue");
|
||||
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
EventQueue::KernelEqueueEvent callback_event{};
|
||||
callback_event.event.ident = 9;
|
||||
callback_event.event.filter = EventQueue::KERNEL_EVFILT_GRAPHICS;
|
||||
callback_event.filter.data = &delete_count;
|
||||
callback_event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, callback_event) == OK,
|
||||
"add callback race event");
|
||||
std::atomic_uint32_t delete_count {0};
|
||||
EventQueue::KernelEqueueEvent callback_event {};
|
||||
callback_event.event.ident = 9;
|
||||
callback_event.event.filter = EventQueue::KERNEL_EVFILT_GRAPHICS;
|
||||
callback_event.filter.data = &delete_count;
|
||||
callback_event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, callback_event) == OK, "add callback race event");
|
||||
|
||||
std::atomic_bool start{false};
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 9, EventQueue::KERNEL_EVFILT_GRAPHICS, nullptr),
|
||||
"callback race trigger result");
|
||||
}
|
||||
});
|
||||
std::atomic_bool start {false};
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelTriggerEvent(
|
||||
queue, 9, EventQueue::KERNEL_EVFILT_GRAPHICS, nullptr),
|
||||
"callback race trigger result");
|
||||
}
|
||||
});
|
||||
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"callback race queue delete");
|
||||
trigger.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"concurrent close invokes callback exactly once");
|
||||
}
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "callback race queue delete");
|
||||
trigger.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"concurrent close invokes callback exactly once");
|
||||
}
|
||||
}
|
||||
|
||||
void TestConcurrentDelete() {
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "concurrent-delete") == OK,
|
||||
"create concurrent queue");
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 7;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_USER;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add concurrent event");
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "concurrent-delete") == OK,
|
||||
"create concurrent queue");
|
||||
EventQueue::KernelEqueueEvent event {};
|
||||
event.event.ident = 7;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_USER;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add concurrent event");
|
||||
|
||||
std::atomic_bool start{false};
|
||||
std::jthread mutate([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelAddEvent(queue, event),
|
||||
"concurrent add result");
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"concurrent trigger result");
|
||||
CheckConcurrentResult(EventQueue::KernelDeleteEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER),
|
||||
"concurrent event delete result");
|
||||
}
|
||||
});
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 128; i++) {
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"parallel trigger result");
|
||||
}
|
||||
});
|
||||
std::atomic_bool start {false};
|
||||
std::jthread mutate([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelAddEvent(queue, event),
|
||||
"concurrent add result");
|
||||
CheckConcurrentResult(EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"concurrent trigger result");
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelDeleteEvent(queue, 7, EventQueue::KERNEL_EVFILT_USER),
|
||||
"concurrent event delete result");
|
||||
}
|
||||
});
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 128; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"parallel trigger result");
|
||||
}
|
||||
});
|
||||
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"concurrent queue delete");
|
||||
mutate.join();
|
||||
trigger.join();
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"concurrent queue removed from registry");
|
||||
}
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "concurrent queue delete");
|
||||
mutate.join();
|
||||
trigger.join();
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "concurrent queue removed from registry");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestDuplicateAddPreservesEventState();
|
||||
TestCallbackStateOutlivesPort();
|
||||
TestCallbackOwnsPayload();
|
||||
TestPinnedClose();
|
||||
TestStaleHandleNeverAliasesNewQueue();
|
||||
TestConcurrentCloseCallback();
|
||||
TestConcurrentDelete();
|
||||
std::printf("EventQueueLifetimeTests: all cases passed\n");
|
||||
return 0;
|
||||
TestDuplicateAddPreservesEventState();
|
||||
TestCallbackStateOutlivesPort();
|
||||
TestCallbackOwnsPayload();
|
||||
TestPinnedClose();
|
||||
TestStaleHandleNeverAliasesNewQueue();
|
||||
TestConcurrentCloseCallback();
|
||||
TestConcurrentDelete();
|
||||
std::printf("EventQueueLifetimeTests: all cases passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
using Owners = std::vector<uint32_t>;
|
||||
using Table = Libs::Graphics::MultiLevelPageTable<Owners>;
|
||||
using Owners = std::vector<uint32_t>;
|
||||
using Table = Libs::Graphics::MultiLevelPageTable<Owners>;
|
||||
using OwnerIndex = Libs::Graphics::MultiRangePageOwnerIndex<uint32_t>;
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
@@ -24,23 +24,26 @@ void TestMultiOwnerAndExactErase() {
|
||||
owners.push_back(11);
|
||||
owners.push_back(22);
|
||||
|
||||
Check(table.Find(17) != nullptr && table.Find(17)->size() == 2, "both page owners are retained");
|
||||
Check(table.Find(17) != nullptr && table.Find(17)->size() == 2,
|
||||
"both page owners are retained");
|
||||
Check(Libs::Graphics::EraseExact(owners, 11U), "registered owner is erased");
|
||||
Check(owners.size() == 1 && owners.front() == 22, "erasing one owner preserves its neighbor");
|
||||
Check(!Libs::Graphics::EraseExact(owners, 33U), "missing owner is reported without mutation");
|
||||
}
|
||||
|
||||
void TestCrossBucketRange() {
|
||||
Table::PageRange range{};
|
||||
constexpr uint64_t bucket_boundary = uint64_t{Table::kBucketEntries} << Table::kPageBits;
|
||||
Table::PageRange range {};
|
||||
constexpr uint64_t bucket_boundary = uint64_t {Table::kBucketEntries} << Table::kPageBits;
|
||||
Check(Table::TryGetPageRange(bucket_boundary - 1, 2, range), "cross-bucket range is valid");
|
||||
Check(range.first == Table::kBucketEntries - 1 && range.last_exclusive == Table::kBucketEntries + 1,
|
||||
Check(range.first == Table::kBucketEntries - 1 &&
|
||||
range.last_exclusive == Table::kBucketEntries + 1,
|
||||
"cross-bucket range covers both pages");
|
||||
|
||||
Table table;
|
||||
table[range.first].push_back(1);
|
||||
table[range.last_exclusive - 1].push_back(2);
|
||||
Check(table.AllocatedBucketCount() == 2, "pages across the L1 boundary use distinct sparse buckets");
|
||||
Check(table.AllocatedBucketCount() == 2,
|
||||
"pages across the L1 boundary use distinct sparse buckets");
|
||||
}
|
||||
|
||||
void TestQueriesDoNotAllocate() {
|
||||
@@ -55,27 +58,34 @@ void TestQueriesDoNotAllocate() {
|
||||
}
|
||||
|
||||
void TestAddressSpaceBoundaries() {
|
||||
Table::PageRange range{};
|
||||
Check(Table::TryGetPageRange(Table::kAddressSpaceSize - 1, 1, range), "last guest byte is valid");
|
||||
Table::PageRange range {};
|
||||
Check(Table::TryGetPageRange(Table::kAddressSpaceSize - 1, 1, range),
|
||||
"last guest byte is valid");
|
||||
Check(range.first == Table::kPageCount - 1 && range.last_exclusive == Table::kPageCount,
|
||||
"last guest byte maps to the final page");
|
||||
Check(!Table::TryGetPageRange(0, 0, range), "empty ranges are rejected");
|
||||
Check(!Table::TryGetPageRange(Table::kAddressSpaceSize, 1, range), "first out-of-range byte is rejected");
|
||||
Check(!Table::TryGetPageRange(Table::kAddressSpaceSize - 1, 2, range), "crossing the address-space end is rejected");
|
||||
Check(!Table::TryGetPageRange(Table::kAddressSpaceSize, 1, range),
|
||||
"first out-of-range byte is rejected");
|
||||
Check(!Table::TryGetPageRange(Table::kAddressSpaceSize - 1, 2, range),
|
||||
"crossing the address-space end is rejected");
|
||||
Check(!Table::TryGetPageRange(UINT64_MAX - 1, 4, range), "wrapping input is rejected");
|
||||
|
||||
Table table;
|
||||
table.GetOrCreate(Table::kPageCount - 1).push_back(99);
|
||||
Check(table.Find(Table::kPageCount - 1) != nullptr && table.Find(Table::kPageCount - 1)->front() == 99,
|
||||
Check(table.Find(Table::kPageCount - 1) != nullptr &&
|
||||
table.Find(Table::kPageCount - 1)->front() == 99,
|
||||
"final page supports allocating and nonallocating access");
|
||||
}
|
||||
|
||||
void TestMultiRangeRegistrationDeduplicatesPages() {
|
||||
OwnerIndex index;
|
||||
// Depth and stencil-like planes overlap tracking pages and share one 1 MiB bucket.
|
||||
Check(index.Register(7, {{0x101000, 0x2800}, {0x102000, 0x3000}}), "multi-range owner registers");
|
||||
Check(index.CoarseMembershipCount(1) == 1, "one owner is inserted once in a shared 1 MiB bucket");
|
||||
Check(index.TrackingMembershipCount(0x102) == 1, "overlapping planes insert one 4 KiB membership");
|
||||
Check(index.Register(7, {{0x101000, 0x2800}, {0x102000, 0x3000}}),
|
||||
"multi-range owner registers");
|
||||
Check(index.CoarseMembershipCount(1) == 1,
|
||||
"one owner is inserted once in a shared 1 MiB bucket");
|
||||
Check(index.TrackingMembershipCount(0x102) == 1,
|
||||
"overlapping planes insert one 4 KiB membership");
|
||||
Check(!index.Register(7, {{0x101000, 0x1000}}), "duplicate owner registration hard-fails");
|
||||
|
||||
const auto owners = index.Query(0x100000, 0x10000);
|
||||
@@ -83,9 +93,10 @@ void TestMultiRangeRegistrationDeduplicatesPages() {
|
||||
}
|
||||
|
||||
void TestSharedPageUnregisterLifecycle() {
|
||||
OwnerIndex index;
|
||||
const std::vector<OwnerIndex::ByteRange> ranges{{0x202000, 0x2000}};
|
||||
Check(index.Register(11, ranges) && index.Register(22, ranges), "two owners register on identical pages");
|
||||
OwnerIndex index;
|
||||
const std::vector<OwnerIndex::ByteRange> ranges {{0x202000, 0x2000}};
|
||||
Check(index.Register(11, ranges) && index.Register(22, ranges),
|
||||
"two owners register on identical pages");
|
||||
Check(index.CoarseMembershipCount(2) == 2 && index.TrackingMembershipCount(0x202) == 2,
|
||||
"coarse and tracking pages retain both owners");
|
||||
|
||||
@@ -97,7 +108,8 @@ void TestSharedPageUnregisterLifecycle() {
|
||||
Check(!index.Unregister(11, releases), "missing membership hard-fails without mutation");
|
||||
|
||||
Check(index.Unregister(22, releases), "final owner unregisters");
|
||||
Check(releases.size() == 1 && releases.front().address == 0x202000 && releases.front().size == 0x2000,
|
||||
Check(releases.size() == 1 && releases.front().address == 0x202000 &&
|
||||
releases.front().size == 0x2000,
|
||||
"adjacent final-owner tracking pages return one contiguous release");
|
||||
}
|
||||
|
||||
@@ -105,14 +117,19 @@ void TestStrictByteFilteringAndPredicate() {
|
||||
OwnerIndex index;
|
||||
Check(index.Register(31, {{0x300100, 0x100}}), "first byte-disjoint owner registers");
|
||||
Check(index.Register(32, {{0x300800, 0x100}}), "second byte-disjoint owner registers");
|
||||
Check(index.TrackingMembershipCount(0x300) == 2, "byte-disjoint owners share one tracking page");
|
||||
Check(index.TrackingMembershipCount(0x300) == 2,
|
||||
"byte-disjoint owners share one tracking page");
|
||||
Check(index.Query(0x300400, 0x40).empty(), "page hit without byte overlap is filtered out");
|
||||
const auto page_candidates = index.QueryCandidates(0x300400, 0x40);
|
||||
Check(page_candidates.size() == 2, "fault candidate query retains byte-disjoint owners on the touched page");
|
||||
Check(page_candidates.size() == 2,
|
||||
"fault candidate query retains byte-disjoint owners on the touched page");
|
||||
const auto first = index.Query(0x300180, 0x10);
|
||||
Check(first.size() == 1 && first.front() == 31, "strict byte overlap selects only the matching owner");
|
||||
const auto predicate_filtered = index.Query(0x300000, 0x1000, [](uint32_t owner) { return owner == 32; });
|
||||
Check(predicate_filtered.size() == 1 && predicate_filtered.front() == 32, "supplied predicate filters query owners");
|
||||
Check(first.size() == 1 && first.front() == 31,
|
||||
"strict byte overlap selects only the matching owner");
|
||||
const auto predicate_filtered =
|
||||
index.Query(0x300000, 0x1000, [](uint32_t owner) { return owner == 32; });
|
||||
Check(predicate_filtered.size() == 1 && predicate_filtered.front() == 32,
|
||||
"supplied predicate filters query owners");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
+967
-1053
File diff suppressed because it is too large.
Load diff
+578
-626
File diff suppressed because it is too large.
Load diff
@@ -1,9 +1,9 @@
|
||||
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@@ -36,7 +36,7 @@ void YieldMany() {
|
||||
}
|
||||
|
||||
void TestFaultBlocksPublisher() {
|
||||
ResourceMutex mutex;
|
||||
ResourceMutex mutex;
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread publisher;
|
||||
@@ -67,7 +67,7 @@ void TestFaultDrainsExistingOwner() {
|
||||
std::atomic_bool release_fault {false};
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread fault([&] {
|
||||
std::thread fault([&] {
|
||||
fault_started.store(true, std::memory_order_release);
|
||||
ResourceMutex::FaultScope scope(mutex);
|
||||
fault_entered.store(true, std::memory_order_release);
|
||||
@@ -111,7 +111,7 @@ void TestFaultScopesSerialize() {
|
||||
std::atomic_bool release_first {false};
|
||||
std::atomic_bool release_second {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread first([&] {
|
||||
std::thread first([&] {
|
||||
ResourceMutex::FaultScope scope(mutex);
|
||||
first_entered.store(true, std::memory_order_release);
|
||||
while (!release_first.load(std::memory_order_acquire)) {
|
||||
@@ -155,7 +155,7 @@ void TestFaultScopesSerialize() {
|
||||
}
|
||||
|
||||
void TestPreownedFaultKeepsResourceTransaction() {
|
||||
ResourceMutex mutex;
|
||||
ResourceMutex mutex;
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread publisher;
|
||||
@@ -199,10 +199,10 @@ void TestPreownedFaultKeepsResourceTransaction() {
|
||||
void CheckDeathCase(const char* name) {
|
||||
char path[MAX_PATH] {};
|
||||
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed");
|
||||
std::string command = std::string("\"") + path + "\" --death " + name;
|
||||
std::string command = std::string("\"") + path + "\" --death " + name;
|
||||
std::vector<char> mutable_command(command.begin(), command.end());
|
||||
mutable_command.push_back('\0');
|
||||
STARTUPINFOA startup {sizeof(startup)};
|
||||
STARTUPINFOA startup {sizeof(startup)};
|
||||
PROCESS_INFORMATION process {};
|
||||
Check(CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
|
||||
nullptr, nullptr, &startup, &process) != 0,
|
||||
|
||||
+1686
-1858
File diff suppressed because it is too large.
Load diff
@@ -199,13 +199,13 @@ void TestNestedLoopPhiConvergence() {
|
||||
program.blocks[3].predecessors = {2};
|
||||
program.blocks[3].successors = {1};
|
||||
Instruction increment;
|
||||
increment.op = Opcode::IAddU32;
|
||||
increment.dst = Sgpr(0);
|
||||
increment.src[0] = Sgpr(0);
|
||||
increment.src[1] = Imm(1);
|
||||
increment.src_count = 2;
|
||||
program.blocks[0].instructions = {increment};
|
||||
program.blocks[2].instructions = {BufferUse(4, 0)};
|
||||
increment.op = Opcode::IAddU32;
|
||||
increment.dst = Sgpr(0);
|
||||
increment.src[0] = Sgpr(0);
|
||||
increment.src[1] = Imm(1);
|
||||
increment.src_count = 2;
|
||||
program.blocks[0].instructions = {increment};
|
||||
program.blocks[2].instructions = {BufferUse(4, 0)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -443,19 +443,19 @@ void TestReadLaneDescriptorSpill() {
|
||||
program.blocks[0].instructions.push_back(MoveImmediate(0xb0, 84, 0));
|
||||
program.blocks[0].instructions.push_back(ReadLane(0x510, 0, 11, 0));
|
||||
Instruction sample;
|
||||
sample.pc = 0x868;
|
||||
sample.op = Opcode::ImageSample;
|
||||
sample.memory.kind = ResourceKind::Image;
|
||||
sample.memory.resource = 0;
|
||||
sample.memory.sampler = 4;
|
||||
sample.pc = 0x868;
|
||||
sample.op = Opcode::ImageSample;
|
||||
sample.memory.kind = ResourceKind::Image;
|
||||
sample.memory.resource = 0;
|
||||
sample.memory.sampler = 4;
|
||||
sample.memory.image_dimension =
|
||||
Libs::Graphics::ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
program.blocks[0].instructions.push_back(sample);
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error), error.c_str());
|
||||
const auto source_id = program.blocks[0].instructions.back().memory.resource_source;
|
||||
const auto* source = GetDescriptorSource(program, source_id);
|
||||
const auto source_id = program.blocks[0].instructions.back().memory.resource_source;
|
||||
const auto* source = GetDescriptorSource(program, source_id);
|
||||
Check(source != nullptr && DescriptorSourceResolved(program, source_id),
|
||||
"readlane descriptor spill was not resolved");
|
||||
Check(Value(program, source->dwords[0]).op == ScalarValueOp::ReadConstBuffer,
|
||||
@@ -463,7 +463,7 @@ void TestReadLaneDescriptorSpill() {
|
||||
std::vector<uint32_t> user_data(28);
|
||||
SetPointer(&user_data, 24, table.data());
|
||||
user_data[26] = sizeof(table);
|
||||
DescriptorValue descriptor;
|
||||
DescriptorValue descriptor;
|
||||
const SrtRuntime runtime {user_data, 0, ReadHostMemory, nullptr};
|
||||
Check(EvaluateDescriptorSource(program, source_id, sample.pc, runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
@@ -477,13 +477,13 @@ void TestReadLaneVectorOverwriteInvalidatesSpill() {
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
Instruction overwrite;
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = Opcode::MoveU32;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 11, 4, 0), overwrite,
|
||||
ReadLane(8, 0, 11, 0), BufferUse(12, 0)};
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = Opcode::MoveU32;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 11, 4, 0), overwrite, ReadLane(8, 0, 11, 0),
|
||||
BufferUse(12, 0)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -545,13 +545,13 @@ void TestReadLaneWideAndRelativeWritesInvalidateSpill() {
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
Instruction overwrite;
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = op;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 12, 4, 0), overwrite,
|
||||
ReadLane(8, 0, 12, 0), BufferUse(12, 0)};
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = op;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 12, 4, 0), overwrite, ReadLane(8, 0, 12, 0),
|
||||
BufferUse(12, 0)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -585,12 +585,12 @@ void TestReadLaneModuloAndDynamicLane() {
|
||||
dynamic.wave_size = 64;
|
||||
dynamic.user_data_count = 8;
|
||||
dynamic.blocks.resize(1);
|
||||
auto write = WriteLane(0, 11, 4, 0);
|
||||
write.src[1] = Sgpr(7);
|
||||
auto write = WriteLane(0, 11, 4, 0);
|
||||
write.src[1] = Sgpr(7);
|
||||
dynamic.blocks[0].instructions = {write, ReadLane(4, 0, 11, 0), BufferUse(8, 0)};
|
||||
Check(BuildScalarProvenance(dynamic, &error), error.c_str());
|
||||
Check(!DescriptorSourceResolved(
|
||||
dynamic, dynamic.blocks[0].instructions.back().memory.resource_source),
|
||||
Check(!DescriptorSourceResolved(dynamic,
|
||||
dynamic.blocks[0].instructions.back().memory.resource_source),
|
||||
"dynamic writelane selector retained unsafe lane provenance");
|
||||
}
|
||||
|
||||
@@ -599,9 +599,8 @@ void TestReadLaneEliminationSnapshotsWriteValue() {
|
||||
program.wave_size = 64;
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
program.blocks[0].instructions = {
|
||||
MoveImmediate(0, 4, 0x12345678u), WriteLane(4, 11, 4, 4),
|
||||
MoveImmediate(8, 4, 0xdeadbeefu), ReadLane(12, 0, 11, 4)};
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 4, 0x12345678u), WriteLane(4, 11, 4, 4),
|
||||
MoveImmediate(8, 4, 0xdeadbeefu), ReadLane(12, 0, 11, 4)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -675,13 +674,12 @@ void TestReadLaneEliminationHonorsVectorInvalidation() {
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
Instruction overwrite;
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = Opcode::MoveU32;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 11, 4, 4), overwrite,
|
||||
ReadLane(8, 0, 11, 4)};
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = Opcode::MoveU32;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 11, 4, 4), overwrite, ReadLane(8, 0, 11, 4)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -696,10 +694,10 @@ void TestReadLaneEliminationFoldsScalarLaneSelector() {
|
||||
program.wave_size = 32;
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
auto write = WriteLane(4, 11, 4, 0);
|
||||
write.src[1] = Sgpr(7);
|
||||
auto read = ReadLane(8, 0, 11, 0);
|
||||
read.src[1] = Sgpr(7);
|
||||
auto write = WriteLane(4, 11, 4, 0);
|
||||
write.src[1] = Sgpr(7);
|
||||
auto read = ReadLane(8, 0, 11, 0);
|
||||
read.src[1] = Sgpr(7);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 7, 33), write, read};
|
||||
|
||||
std::string error;
|
||||
@@ -840,8 +838,7 @@ void TestDynamicReadIsNotFlattened() {
|
||||
DescriptorValue descriptor;
|
||||
const auto source = program.blocks[0].instructions[1].memory.resource_source;
|
||||
const SrtRuntime runtime {user_data, 0, ReadHostMemory, nullptr};
|
||||
Check(EvaluateDescriptorSource(program, source, 4, runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
Check(EvaluateDescriptorSource(program, source, 4, runtime, descriptor, &error), error.c_str());
|
||||
Check(descriptor.dwords[0] == table[1], "dynamic ReadConst evaluated the wrong dword");
|
||||
}
|
||||
|
||||
@@ -1144,46 +1141,43 @@ void TestCommonScalarPointerOps() {
|
||||
void TestBitFieldMaskDescriptor() {
|
||||
Program program;
|
||||
program.blocks.resize(1);
|
||||
auto mask = MoveImmediate(4, 29, 0);
|
||||
mask.op = Opcode::BitFieldMaskU32;
|
||||
mask.src[0] = Imm(12);
|
||||
mask.src[1] = Imm(12);
|
||||
mask.src_count = 2;
|
||||
auto high = MoveImmediate(8, 30, 0x05500000u);
|
||||
high.op = Opcode::MoveU64;
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high,
|
||||
BufferUse(12, 28)};
|
||||
auto mask = MoveImmediate(4, 29, 0);
|
||||
mask.op = Opcode::BitFieldMaskU32;
|
||||
mask.src[0] = Imm(12);
|
||||
mask.src[1] = Imm(12);
|
||||
mask.src_count = 2;
|
||||
auto high = MoveImmediate(8, 30, 0x05500000u);
|
||||
high.op = Opcode::MoveU64;
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high, BufferUse(12, 28)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error), error.c_str());
|
||||
DescriptorValue descriptor;
|
||||
DescriptorValue descriptor;
|
||||
const SrtRuntime runtime {{}, 0, nullptr, nullptr};
|
||||
Check(EvaluateDescriptorSource(program,
|
||||
program.blocks[0].instructions.back().memory.resource_source,
|
||||
16, runtime, descriptor, &error),
|
||||
program.blocks[0].instructions.back().memory.resource_source, 16,
|
||||
runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
Check(descriptor.dwords[0] == 0x92u && descriptor.dwords[1] == 0x00fff000u &&
|
||||
descriptor.dwords[2] == 0x05500000u && descriptor.dwords[3] == 0,
|
||||
"production sampler bit-field mask evaluated incorrectly");
|
||||
|
||||
mask.src[0] = Imm(0);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high,
|
||||
BufferUse(12, 28)};
|
||||
mask.src[0] = Imm(0);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high, BufferUse(12, 28)};
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error), error.c_str());
|
||||
Check(EvaluateDescriptorSource(program,
|
||||
program.blocks[0].instructions.back().memory.resource_source,
|
||||
12, runtime, descriptor, &error),
|
||||
program.blocks[0].instructions.back().memory.resource_source, 12,
|
||||
runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
Check(descriptor.dwords[1] == 0, "zero-width bit-field mask was not zero");
|
||||
|
||||
mask.src[0] = Imm(31);
|
||||
mask.src[1] = Imm(31);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high,
|
||||
BufferUse(12, 28)};
|
||||
mask.src[0] = Imm(31);
|
||||
mask.src[1] = Imm(31);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high, BufferUse(12, 28)};
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error), error.c_str());
|
||||
Check(EvaluateDescriptorSource(program,
|
||||
program.blocks[0].instructions.back().memory.resource_source,
|
||||
12, runtime, descriptor, &error),
|
||||
program.blocks[0].instructions.back().memory.resource_source, 12,
|
||||
runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
Check(descriptor.dwords[1] == 0x80000000u,
|
||||
"maximum bit-field mask count/offset evaluated incorrectly");
|
||||
|
||||
+15146
-16525
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,6 @@
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -24,12 +23,12 @@ std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program> SrtProgram(
|
||||
program.srt_patching_complete = true;
|
||||
program.resource_tracking_complete = true;
|
||||
program.provenance.values.resize(6);
|
||||
program.provenance.values[0].op = ScalarValueOp::Undefined;
|
||||
program.provenance.values[1].op = ScalarValueOp::Unknown;
|
||||
program.provenance.values[2].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[2].imm = static_cast<uint32_t>(address);
|
||||
program.provenance.values[3].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[3].imm = static_cast<uint32_t>(address >> 32u);
|
||||
program.provenance.values[0].op = ScalarValueOp::Undefined;
|
||||
program.provenance.values[1].op = ScalarValueOp::Unknown;
|
||||
program.provenance.values[2].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[2].imm = static_cast<uint32_t>(address);
|
||||
program.provenance.values[3].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[3].imm = static_cast<uint32_t>(address >> 32u);
|
||||
program.provenance.values[4].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[5].op = ScalarValueOp::ReadConst;
|
||||
program.provenance.values[5].args = {2, 3, 4};
|
||||
@@ -54,15 +53,14 @@ std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program> UnbasedFlat
|
||||
|
||||
void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
using namespace Libs::Graphics;
|
||||
const uint32_t dword = 0x12345678;
|
||||
auto cached_program = SrtProgram(reinterpret_cast<uint64_t>(&dword));
|
||||
const uint32_t dword = 0x12345678;
|
||||
auto cached_program = SrtProgram(reinterpret_cast<uint64_t>(&dword));
|
||||
ShaderStageRuntime stage;
|
||||
std::string error;
|
||||
std::string error;
|
||||
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error), error.c_str());
|
||||
Check(stage.program == cached_program && stage.resources != nullptr,
|
||||
"cache rematerialization did not publish the mapped stage");
|
||||
Check(stage.resources->flattened_srt.size() == 1 &&
|
||||
stage.resources->flattened_srt[0] == dword,
|
||||
Check(stage.resources->flattened_srt.size() == 1 && stage.resources->flattened_srt[0] == dword,
|
||||
"cache rematerialization did not use the direct reader by default");
|
||||
}
|
||||
|
||||
@@ -72,7 +70,7 @@ void TestUnbasedFlatCacheHitFailsClosed() {
|
||||
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
|
||||
auto prior_resources = std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
|
||||
ShaderStageRuntime stage {prior_program, prior_resources};
|
||||
std::string error;
|
||||
std::string error;
|
||||
Check(!ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error) &&
|
||||
error.find("requires runtime guest-address translation") != std::string::npos,
|
||||
"unbased FLAT cache hit did not fail without a runtime translator");
|
||||
|
||||
@@ -18,19 +18,19 @@ void Check(bool value, const char* text) {
|
||||
|
||||
struct Fixture {
|
||||
std::array<uint16_t, static_cast<size_t>(AgcDirectResourceType::Last) + 1> offsets {};
|
||||
ShaderUserData user_data {};
|
||||
ShaderSemantic semantic {};
|
||||
ShaderMappedData mapped {};
|
||||
ShaderUserData user_data {};
|
||||
ShaderSemantic semantic {};
|
||||
ShaderMappedData mapped {};
|
||||
|
||||
Fixture() {
|
||||
offsets.fill(AGC_ILLEGAL_DIRECT_OFFSET);
|
||||
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 2;
|
||||
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 2;
|
||||
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexAttribDescTable)] = 4;
|
||||
user_data.direct_resource_offset = offsets.data();
|
||||
user_data.direct_resource_count = static_cast<uint16_t>(offsets.size());
|
||||
mapped.user_data = &user_data;
|
||||
mapped.input_semantics = &semantic;
|
||||
mapped.num_input_semantics = 1;
|
||||
mapped.user_data = &user_data;
|
||||
mapped.input_semantics = &semantic;
|
||||
mapped.num_input_semantics = 1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,9 +48,9 @@ void CheckRejected(const ShaderMappedData& data, const char* text) {
|
||||
}
|
||||
|
||||
void TestValidAndInvalidMetadata() {
|
||||
Fixture fixture;
|
||||
Fixture fixture;
|
||||
ShaderVertexMetadata output;
|
||||
std::string error;
|
||||
std::string error;
|
||||
Check(ShaderReadVertexMetadata(fixture.mapped, 64, output, &error),
|
||||
"valid AGC vertex metadata was rejected");
|
||||
Check(output.vertex_buffer_reg == 2 && output.vertex_attrib_reg == 4 &&
|
||||
@@ -75,8 +75,8 @@ void TestValidAndInvalidMetadata() {
|
||||
CheckRejected(excessive_semantics.mapped, "excessive vertex semantic count was accepted");
|
||||
|
||||
Fixture excessive_register;
|
||||
excessive_register.offsets[
|
||||
static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 63;
|
||||
excessive_register.offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] =
|
||||
63;
|
||||
CheckRejected(excessive_register.mapped, "out-of-domain vertex table SGPR was accepted");
|
||||
|
||||
Fixture missing_semantics;
|
||||
|
||||
@@ -107,12 +107,11 @@ void InitSubsystems() {
|
||||
slist->Add(log, {core, config});
|
||||
Check("InitSubsystems", slist->InitAll(false), "failed to initialize logging subsystem");
|
||||
|
||||
const auto param_json =
|
||||
std::filesystem::temp_directory_path() /
|
||||
("kyty_virtual_memory_" +
|
||||
std::to_string(reinterpret_cast<uintptr_t>(&initialized)) + ".json");
|
||||
const auto param_json = std::filesystem::temp_directory_path() /
|
||||
("kyty_virtual_memory_" +
|
||||
std::to_string(reinterpret_cast<uintptr_t>(&initialized)) + ".json");
|
||||
constexpr char json[] = R"({"kernel":{"flexibleMemorySize":3221225472}})";
|
||||
Common::File param_file;
|
||||
Common::File param_file;
|
||||
Check("InitSubsystems", param_file.Create(param_json), "failed to create temporary param.json");
|
||||
uint32_t bytes_written = 0;
|
||||
param_file.Write(json, sizeof(json) - 1, &bytes_written);
|
||||
@@ -247,8 +246,8 @@ void TestGuestAddressSpaceOwnsReservationsBeforeBacking() {
|
||||
Check(test, Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, SceKernelPageSize),
|
||||
"semantic reservation replaced the owner's placeholder");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize, Common::VirtualMemory::Mode::NoAccess),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::NoAccess),
|
||||
"owner rejected a sparse placeholder protection no-op");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap");
|
||||
Check(test, Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, SceKernelPageSize),
|
||||
@@ -402,8 +401,7 @@ void TestFlexibleDmemCompatAndAlignmentFlags() {
|
||||
Check(test, stack_start == nullptr && stack_end == nullptr,
|
||||
"DMEM_COMPAT flexible mapping was reported as a stack");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize),
|
||||
"KernelMunmap");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"DMEM_COMPAT cleanup did not restore flexible capacity");
|
||||
|
||||
@@ -443,8 +441,8 @@ void TestFlexibleNoCoalescePreservesBoundaries() {
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
void* reserve = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelReserveVirtualRange(
|
||||
&reserve, SceKernelPageSize * 2, 0, SceKernelPageSize),
|
||||
Libs::LibKernel::Memory::KernelReserveVirtualRange(&reserve, SceKernelPageSize * 2, 0,
|
||||
SceKernelPageSize),
|
||||
"KernelReserveVirtualRange");
|
||||
const auto base = reinterpret_cast<uint64_t>(reserve);
|
||||
|
||||
@@ -601,13 +599,12 @@ void TestRuntimeMemoryOwnerLifecycle() {
|
||||
Common::VirtualMemory::Mode::ReadWrite, "runtime_adjacent_second", true);
|
||||
Check(test, adjacent_second == adjacent_first + SceKernelPageSize,
|
||||
"second adjacent runtime allocation failed");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::FreeGuestMemory(adjacent_first, SceKernelPageSize * 2),
|
||||
Check(test, Libs::LibKernel::Memory::FreeGuestMemory(adjacent_first, SceKernelPageSize * 2),
|
||||
"combined adjacent runtime free failed");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(adjacent_first,
|
||||
SceKernelPageSize * 2),
|
||||
"combined adjacent runtime free did not restore one owner placeholder");
|
||||
Check(
|
||||
test,
|
||||
Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(adjacent_first, SceKernelPageSize * 2),
|
||||
"combined adjacent runtime free did not restore one owner placeholder");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
@@ -877,12 +874,12 @@ void TestDirectPartialProtectUnmapPreservesNeighbors() {
|
||||
SceKernelPageSize, SceKernelProtCpuRead),
|
||||
"KernelMprotect(middle)");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, size, Common::VirtualMemory::Mode::Read),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, size,
|
||||
Common::VirtualMemory::Mode::Read),
|
||||
"owner could not protect fragmented backing views");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, size, Common::VirtualMemory::Mode::ReadWrite),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, size,
|
||||
Common::VirtualMemory::Mode::ReadWrite),
|
||||
"owner could not restore fragmented backing views");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize),
|
||||
@@ -1091,12 +1088,12 @@ void TestMunmapAcrossAdjacentFlexibleMappings() {
|
||||
Libs::LibKernel::Memory::ClampRangeSize(base + SceKernelPageSize - 0x100, 0x200) == 0x200,
|
||||
"ClampRangeSize did not cross adjacent committed mappings");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize * 2, Common::VirtualMemory::Mode::Read),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, SceKernelPageSize * 2,
|
||||
Common::VirtualMemory::Mode::Read),
|
||||
"owner could not protect adjacent backing mappings");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize * 2, Common::VirtualMemory::Mode::ReadWrite),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, SceKernelPageSize * 2,
|
||||
Common::VirtualMemory::Mode::ReadWrite),
|
||||
"owner could not restore adjacent backing mappings");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2),
|
||||
|
||||
+63
-73
@@ -4,22 +4,22 @@
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/shader/recompiler/ExecMask.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderInfoCollection.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/ir/SrtPatcher.h"
|
||||
#include "graphics/shader/recompiler/ir/SrtWalker.h"
|
||||
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "libs/agc.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
@@ -411,9 +411,9 @@ constexpr uint32_t EncodeSopp(uint32_t opcode, uint32_t simm = 0) {
|
||||
}
|
||||
|
||||
void TestNativeShaderResourceDependencies() {
|
||||
const auto stages = ShaderPipelineStages(
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment |
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
const auto stages =
|
||||
ShaderPipelineStages(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment |
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
Check(stages == (vk::PipelineStageFlagBits::eVertexShader |
|
||||
vk::PipelineStageFlagBits::eFragmentShader |
|
||||
vk::PipelineStageFlagBits::eComputeShader),
|
||||
@@ -463,8 +463,7 @@ void TestNormalizedImageContracts() {
|
||||
ImageInfo container {};
|
||||
container.data = {0x10000, 0x15000};
|
||||
container.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
||||
container.guest_format =
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm);
|
||||
container.guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm);
|
||||
container.type = Prospero::ImageType::kColor2D;
|
||||
container.extent = {64, 64, 1};
|
||||
container.resources = {3, 4};
|
||||
@@ -476,38 +475,34 @@ void TestNormalizedImageContracts() {
|
||||
container.mip_layout[1] = {0x10000, 0x4000, 32, 32};
|
||||
container.mip_layout[2] = {0x14000, 0x1000, 16, 16};
|
||||
|
||||
ImageInfo subresource = container;
|
||||
subresource.data = {0x22000, 0x1000};
|
||||
subresource.extent = {32, 32, 1};
|
||||
subresource.resources = {1, 1};
|
||||
subresource.pitch = 32;
|
||||
subresource.mip_layout = {};
|
||||
ImageInfo subresource = container;
|
||||
subresource.data = {0x22000, 0x1000};
|
||||
subresource.extent = {32, 32, 1};
|
||||
subresource.resources = {1, 1};
|
||||
subresource.pitch = 32;
|
||||
subresource.mip_layout = {};
|
||||
subresource.mip_layout[0] = {0, 0x1000, 32, 32};
|
||||
|
||||
Check(container.BlockExtent() == vk::Extent2D {64, 64},
|
||||
"normalized image block extent changed");
|
||||
Check(subresource.IsCompatible(container), "normalized compatible image was rejected");
|
||||
Check(subresource.MipOf(container) == 1, "normalized mip lookup missed a subresource");
|
||||
Check(subresource.SliceOf(container, 1) == 2,
|
||||
"normalized slice lookup missed a subresource");
|
||||
Check(subresource.SliceOf(container, 1) == 2, "normalized slice lookup missed a subresource");
|
||||
|
||||
auto incompatible = subresource;
|
||||
auto incompatible = subresource;
|
||||
incompatible.samples = 2;
|
||||
Check(!incompatible.IsCompatible(container) && incompatible.MipOf(container) == -1,
|
||||
"sample-count mismatch was accepted as a compatible image");
|
||||
|
||||
auto compressed = container;
|
||||
compressed.guest_format =
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::kBc3UNorm);
|
||||
compressed.pitch = 128;
|
||||
compressed.extent.height = 64;
|
||||
auto compressed = container;
|
||||
compressed.guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::kBc3UNorm);
|
||||
compressed.pitch = 128;
|
||||
compressed.extent.height = 64;
|
||||
Check(compressed.BlockExtent() == vk::Extent2D {32, 16},
|
||||
"block-compressed extent was not expressed in blocks");
|
||||
|
||||
Check(ImageViewOps::FormatsCompatible(vk::Format::eR8G8B8A8Unorm,
|
||||
vk::Format::eR8G8B8A8Uint) &&
|
||||
!ImageViewOps::FormatsCompatible(vk::Format::eD32Sfloat,
|
||||
vk::Format::eR32Sfloat) &&
|
||||
Check(ImageViewOps::FormatsCompatible(vk::Format::eR8G8B8A8Unorm, vk::Format::eR8G8B8A8Uint) &&
|
||||
!ImageViewOps::FormatsCompatible(vk::Format::eD32Sfloat, vk::Format::eR32Sfloat) &&
|
||||
ImageViewOps::FormatsCompatible(vk::Format::eBc3UnormBlock,
|
||||
vk::Format::eR32G32B32A32Uint),
|
||||
"Vulkan image-view compatibility classes diverged from production");
|
||||
@@ -524,20 +519,19 @@ void TestNativeSubgroupPolicy() {
|
||||
safe.wave_size = 32;
|
||||
safe.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eVertex, safe).mode ==
|
||||
ShaderSubgroupMode::Natural,
|
||||
vk::ShaderStageFlagBits::eVertex, safe)
|
||||
.mode == ShaderSubgroupMode::Natural,
|
||||
"native wave32 policy changed");
|
||||
safe.wave_size = 64;
|
||||
Check(SelectGraphicsLaneMaskMode(safe.wave_size) ==
|
||||
ShaderLaneMaskMode::PerInvocation &&
|
||||
Check(SelectGraphicsLaneMaskMode(safe.wave_size) == ShaderLaneMaskMode::PerInvocation &&
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eVertex, safe).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eVertex, safe)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"wave64 graphics mismatch accepted native-wave mask lowering");
|
||||
safe.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eVertex, safe).mode ==
|
||||
ShaderSubgroupMode::PerInvocationGraphics,
|
||||
vk::ShaderStageFlagBits::eVertex, safe)
|
||||
.mode == ShaderSubgroupMode::PerInvocationGraphics,
|
||||
"wave64 graphics mismatch did not select per-invocation masks");
|
||||
|
||||
ShaderRecompiler::IR::Program cross_lane = safe;
|
||||
@@ -545,14 +539,14 @@ void TestNativeSubgroupPolicy() {
|
||||
ShaderRecompiler::IR::Opcode::ReadLaneU32;
|
||||
Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(cross_lane) &&
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eVertex, cross_lane).mode ==
|
||||
ShaderSubgroupMode::PerInvocationGraphics,
|
||||
vk::ShaderStageFlagBits::eVertex, cross_lane)
|
||||
.mode == ShaderSubgroupMode::PerInvocationGraphics,
|
||||
"graphics mismatch did not select per-invocation masks");
|
||||
auto cross_lane_compute = cross_lane;
|
||||
cross_lane_compute.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eCompute, cross_lane_compute).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eCompute, cross_lane_compute)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"cross-lane compute mismatch bypassed the exact subgroup requirement");
|
||||
|
||||
ShaderRecompiler::IR::Program zero_exec = safe;
|
||||
@@ -568,8 +562,8 @@ void TestNativeSubgroupPolicy() {
|
||||
zero_bfm.scalar_sources[0] = 2;
|
||||
Check(!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(zero_exec) &&
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eCompute, zero_exec).mode ==
|
||||
ShaderSubgroupMode::FlattenedMasks,
|
||||
vk::ShaderStageFlagBits::eCompute, zero_exec)
|
||||
.mode == ShaderSubgroupMode::FlattenedMasks,
|
||||
"compile-time uniform-zero EXEC write did not stay on the mask-free path");
|
||||
|
||||
ShaderRecompiler::IR::Program selective_exec = safe;
|
||||
@@ -600,8 +594,8 @@ void TestNativeSubgroupPolicy() {
|
||||
ds_partial.blocks.emplace_back().instructions.emplace_back().op =
|
||||
ShaderRecompiler::IR::Opcode::DsAppend;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eCompute, ds_partial).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eCompute, ds_partial)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"partial wave64 DS append bypassed the exact subgroup requirement");
|
||||
context.max_subgroup_size = 64;
|
||||
const auto controlled =
|
||||
@@ -614,17 +608,18 @@ void TestNativeSubgroupPolicy() {
|
||||
cross_lane.wave_size = 32;
|
||||
cross_lane.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eFragment, cross_lane).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eFragment, cross_lane)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"inverse graphics mismatch was accepted as one guest wave");
|
||||
cross_lane_compute.wave_size = 32;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eCompute, cross_lane_compute).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eCompute, cross_lane_compute)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"inverse cross-lane compute mismatch was accepted");
|
||||
}
|
||||
|
||||
std::array<uint32_t, 64> ImageTestUserData(Prospero::ImageType type = Prospero::ImageType::kColor2D) {
|
||||
std::array<uint32_t, 64>
|
||||
ImageTestUserData(Prospero::ImageType type = Prospero::ImageType::kColor2D) {
|
||||
std::array<uint32_t, 64> data {};
|
||||
for (uint32_t start = 0; start + 3u < data.size(); start += 4u) {
|
||||
data[start] = 0x1000u + start * 0x100u;
|
||||
@@ -3021,7 +3016,7 @@ void TestNewShaderRecompilerImageQueryLowering() {
|
||||
|
||||
void TestNewShaderRecompilerCubeSampleCoordinates() {
|
||||
constexpr uint32_t MimgDimCube = 3;
|
||||
const uint32_t shader[] = {
|
||||
const uint32_t shader[] = {
|
||||
EncodeMimg0(0x20, 0xf, false, MimgDimCube),
|
||||
EncodeMimg1(0, 0, 1, 0), // image_sample cube
|
||||
EncodeMimg0(0x60, 0x3, false, MimgDimCube),
|
||||
@@ -3029,7 +3024,7 @@ void TestNewShaderRecompilerCubeSampleCoordinates() {
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
auto user_data = ImageTestUserData(Prospero::ImageType::kCube);
|
||||
auto user_data = ImageTestUserData(Prospero::ImageType::kCube);
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.user_data = user_data.data();
|
||||
@@ -3525,10 +3520,8 @@ void TestNewShaderRecompilerImageViewDimensions() {
|
||||
"SPIR-V binary does not contain sampled 2D-array image type");
|
||||
Check(SpirvContainsTypeImage(result.spirv, SpirvDim3D, 0, 1),
|
||||
"SPIR-V binary does not contain sampled 3D image type");
|
||||
Check(SpirvContainsCapability(result.spirv, 43),
|
||||
"SPIR-V binary does not request Sampled1D");
|
||||
Check(SpirvContainsCapability(result.spirv, 44),
|
||||
"SPIR-V binary does not request Image1D");
|
||||
Check(SpirvContainsCapability(result.spirv, 43), "SPIR-V binary does not request Sampled1D");
|
||||
Check(SpirvContainsCapability(result.spirv, 44), "SPIR-V binary does not request Image1D");
|
||||
Check(SpirvContainsOpcode(result.spirv, 95),
|
||||
"SPIR-V binary does not contain array image fetch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
@@ -3617,7 +3610,7 @@ void TestNewShaderRecompilerRejectsOneDimensionalGather() {
|
||||
EncodeMimg1(0, 0, 1, 0),
|
||||
0xbf810000u,
|
||||
};
|
||||
auto user_data = ImageTestUserData(type);
|
||||
auto user_data = ImageTestUserData(type);
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.user_data = user_data.data();
|
||||
@@ -4116,8 +4109,7 @@ void TestNewShaderRecompilerVintrpLowering() {
|
||||
options.pixel_input_info = &flat_ps_info;
|
||||
|
||||
ShaderRecompiler::CompileResult flat_result;
|
||||
Check(ShaderRecompiler::TryRecompile(flat_shader, options, flat_result, &error),
|
||||
error.c_str());
|
||||
Check(ShaderRecompiler::TryRecompile(flat_shader, options, flat_result, &error), error.c_str());
|
||||
Check(SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u),
|
||||
"flat VINTRP input did not emit a Flat decoration");
|
||||
Check(!SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 13u),
|
||||
@@ -5996,7 +5988,7 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
||||
|
||||
ShaderPixelInputInfo uint16_info;
|
||||
uint16_info.target_output_mode[0] = 7;
|
||||
options.pixel_input_info = &uint16_info;
|
||||
options.pixel_input_info = &uint16_info;
|
||||
ShaderRecompiler::CompileResult uint16_result;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, uint16_result, &error), error.c_str());
|
||||
const auto uint16_source = DisassembleSpirvBinary(uint16_result.spirv);
|
||||
@@ -6027,10 +6019,10 @@ void TestRenderTargetReverseFloat16ExportMapping() {
|
||||
format.export_mapping.ApplyMask(0xfu) == 0xfu,
|
||||
"reverse RGBA16F render-target export or write-mask mapping is "
|
||||
"incorrect");
|
||||
const auto legacy_alt = TextureGetRenderTargetFormat(
|
||||
Prospero::GpuEnumValue(Prospero::ChannelLayout::k8_8_8_8),
|
||||
Prospero::GpuEnumValue(Prospero::ChannelType::kUNorm),
|
||||
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt));
|
||||
const auto legacy_alt =
|
||||
TextureGetRenderTargetFormat(Prospero::GpuEnumValue(Prospero::ChannelLayout::k8_8_8_8),
|
||||
Prospero::GpuEnumValue(Prospero::ChannelType::kUNorm),
|
||||
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt));
|
||||
Check(legacy_alt.format == vk::Format::eB8G8R8A8Unorm && legacy_alt.export_mapping.IsIdentity(),
|
||||
"legacy BGRA render target acquired a duplicate shader export mapping");
|
||||
|
||||
@@ -6060,8 +6052,7 @@ void TestRenderTargetReverseFloat16ExportMapping() {
|
||||
CheckSpirvBinaryValidates(reversed_result.spirv);
|
||||
|
||||
HW::PixelShaderInfo regs {};
|
||||
Check(ShaderGetIdPS(regs, identity_info, false) !=
|
||||
ShaderGetIdPS(regs, reversed_info, false),
|
||||
Check(ShaderGetIdPS(regs, identity_info, false) != ShaderGetIdPS(regs, reversed_info, false),
|
||||
"pixel shader cache identity omitted the render-target export mapping");
|
||||
|
||||
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
|
||||
@@ -6069,7 +6060,7 @@ void TestRenderTargetReverseFloat16ExportMapping() {
|
||||
ShaderMappedData mapped {};
|
||||
mapped.code_size_bytes = sizeof(shader);
|
||||
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
||||
HW::ShaderRegisters sh {};
|
||||
HW::ShaderRegisters sh {};
|
||||
ShaderVertexInputInfo vs_info {};
|
||||
vs_info.stage.program = std::make_shared<ShaderRecompiler::IR::Program>();
|
||||
std::array<Prospero::ColorComponentMapping, 8> mappings {};
|
||||
@@ -6901,8 +6892,8 @@ void TestPixelProgramCacheDescriptorSetIdentity() {
|
||||
ShaderVertexInputInfo vs_info {};
|
||||
vs_info.stage.program = std::move(vs_program);
|
||||
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings {};
|
||||
ShaderPixelInputInfo ps_info {};
|
||||
std::span<const uint32_t> spirv;
|
||||
ShaderPixelInputInfo ps_info {};
|
||||
std::span<const uint32_t> spirv;
|
||||
Check(ShaderCompileInfoPS(regs, sh, ShaderLaneMaskMode::NativeWave, vs_info,
|
||||
identity_mappings, ps_info, spirv),
|
||||
"pixel program-cache transition failed to compile");
|
||||
@@ -6929,8 +6920,8 @@ void TestPixelProgramCacheDescriptorSetIdentity() {
|
||||
ShaderVertexInputInfo vs_info {};
|
||||
vs_info.stage.program = std::make_shared<ShaderRecompiler::IR::Program>();
|
||||
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings {};
|
||||
ShaderPixelInputInfo ps_info {};
|
||||
std::span<const uint32_t> spirv;
|
||||
ShaderPixelInputInfo ps_info {};
|
||||
std::span<const uint32_t> spirv;
|
||||
Check(ShaderCompileInfoPS(mask_regs, sh, mode, vs_info, identity_mappings, ps_info, spirv),
|
||||
"pixel lane-mask cache transition failed to compile");
|
||||
Check(ps_info.stage.program != nullptr && ps_info.stage.program->lane_mask_mode == mode,
|
||||
@@ -7006,8 +6997,7 @@ void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
|
||||
options.flat_memory_base = 0;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
const bool compiled =
|
||||
ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error);
|
||||
const bool compiled = ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error);
|
||||
Check(compiled, error.c_str());
|
||||
Check(result.program.info.addresses.size() == 2,
|
||||
"segmented address resources were not tracked independently");
|
||||
|
||||
Reference in new issue
Block a user