diff --git a/.gitignore b/.gitignore index 36be987bde..0941219884 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,7 @@ downloads/ !demo/web/i18n/*.json !/docs/run-report.schema.json !/docs/run-report-fixtures/*.json +!tools/titles.json !/assets/*.jpg !/assets/*.png diff --git a/CMakeLists.txt b/CMakeLists.txt index f3f2e1490e..714e62fae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -570,6 +570,9 @@ if(BUILD_TESTING) add_test(NAME performance_compare COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_performance_compare.py) set_tests_properties(performance_compare PROPERTIES TIMEOUT 20) + add_test(NAME title_run COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_title_run.py) + set_tests_properties(title_run PROPERTIES TIMEOUT 60) endif() endif() diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 7230150c09..42e5d4b2f6 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -488,6 +488,21 @@ if(BUILD_TESTING) add_test(NAME nid_elf_layout COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_elf_layout.py $) endif() + add_test_executable(guest_kernel_sys_pwrite_tests tests/GuestKernelSysPwrite.cpp + nid/src/NidCompute.cpp nid/src/Sha1.cpp) + target_include_directories(guest_kernel_sys_pwrite_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) + target_link_libraries(guest_kernel_sys_pwrite_tests PRIVATE ${CMAKE_DL_LIBS}) + add_custom_target(guest_kernel_sys_pwrite_libraries DEPENDS + ${PATCHED_DIR}/libkernel_sys.prx ${PATCHED_DIR}/libkernel.prx ${LIBC_PATCHED}) + add_dependencies(guest_kernel_sys_pwrite_tests guest_kernel_sys_pwrite_libraries) + configure_windows_unwind(guest_kernel_sys_pwrite_tests) + if(MINGW) + target_compile_options(guest_kernel_sys_pwrite_tests PRIVATE -fasynchronous-unwind-tables) + target_link_options(guest_kernel_sys_pwrite_tests PRIVATE -static -static-libgcc -static-libstdc++) + endif() + add_test(NAME guest_kernel_sys_pwrite COMMAND guest_kernel_sys_pwrite_tests ${UNPATCHED_DIR} unpatched) + add_test(NAME guest_kernel_sys_pwrite_patched COMMAND guest_kernel_sys_pwrite_tests ${PATCHED_DIR} patched) + set_tests_properties(guest_kernel_sys_pwrite guest_kernel_sys_pwrite_patched PROPERTIES TIMEOUT 20) add_test_executable(guest_kernel_sys_tests tests/GuestKernelSys.cpp) target_include_directories(guest_kernel_sys_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libSceNet libSceAmpr libkernel libc) @@ -915,6 +930,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_revoke_tests tests/GuestKernelSysRevoke.cpp) + target_include_directories(guest_kernel_sys_revoke_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_revoke_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_revoke_tests) + add_test(NAME guest_kernel_sys_revoke COMMAND guest_kernel_sys_revoke_tests) + set_tests_properties(guest_kernel_sys_revoke PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_revoke PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_netctl_ap_tests tests/GuestNetCtlAp.cpp) target_include_directories(guest_netctl_ap_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_netctl_ap_tests PRIVATE libSceNetCtlAp libc) diff --git a/core/libs/prx/libSceAgcDriver/Eq/include/Event.hpp b/core/libs/prx/libSceAgcDriver/Eq/include/Event.hpp index 3f25b907df..ffdff3df09 100644 --- a/core/libs/prx/libSceAgcDriver/Eq/include/Event.hpp +++ b/core/libs/prx/libSceAgcDriver/Eq/include/Event.hpp @@ -4,7 +4,7 @@ #include #include "SceTypes.hpp" -bool AgcDriverDeliverEopInterrupt(std::uint32_t queue); +void AgcDriverDeliverEopInterrupt(std::uint32_t queue, std::uint32_t context); extern "C" int APS5_VABI sceAgcDriverAddEqEvent(KernelEqueue eq, int id, void* udata); extern "C" int APS5_VABI sceAgcDriverDeleteEqEvent(KernelEqueue eq, int id); diff --git a/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp b/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp index 055e83ecbe..1254c09e3d 100644 --- a/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp +++ b/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp @@ -33,9 +33,11 @@ int AddEvent(KernelEqueue eq, std::uint32_t queue, std::uintptr_t ident, void* u event.event.ident = ident; event.event.filter = EvfiltGraphicsCore; event.event.flags = EV_ADD | EV_CLEAR; + event.event.data = static_cast(queue); event.event.udata = udata; - event.filter.triggerFunc = [](KernelEqueueEvent* e, void*) { - e->event.data = e->triggered ? e->event.data + 1 : 1; + event.filter.triggerFunc = [](KernelEqueueEvent* e, void* data) { + if (data == nullptr) throw std::runtime_error("graphics event triggered without its interrupt data"); + e->event.data = static_cast(*static_cast(data)); e->triggered = true; }; event.filter.resetFunc = [](KernelEqueueEvent* e) { @@ -67,20 +69,17 @@ int DeleteEvent(KernelEqueue eq, std::uintptr_t ident, const char* caller) { } -bool AgcDriverDeliverEopInterrupt(std::uint32_t queue) { +void AgcDriverDeliverEopInterrupt(std::uint32_t queue, std::uint32_t context) { std::vector registrations; { std::lock_guard lock(g_mutex); registrations = g_registrations; } - bool delivered = false; for (const auto& registration : registrations) { - if (registration.queue == queue) { - EqueueTriggerEvent_nid_postfix(registration.eq, registration.ident, EvfiltGraphicsCore, nullptr); - delivered = true; - } + if (registration.queue != queue) continue; + std::uint64_t data = static_cast(static_cast(registration.queue)) | (static_cast(context) << 16u); + EqueueTriggerEvent_nid_postfix(registration.eq, registration.ident, EvfiltGraphicsCore, &data); } - return delivered; } extern "C" { diff --git a/core/libs/prx/libSceAgcDriver/Eq/src/Query.cpp b/core/libs/prx/libSceAgcDriver/Eq/src/Query.cpp index c0987dc2b7..dc00d7ad80 100644 --- a/core/libs/prx/libSceAgcDriver/Eq/src/Query.cpp +++ b/core/libs/prx/libSceAgcDriver/Eq/src/Query.cpp @@ -17,22 +17,14 @@ uint32_t APS5_VABI sceAgcDriverGetEqContextId(const KernelEvent* ev) { if (ev->filter != -14) { throw std::runtime_error(std::string(__func__) + ": not a graphics event"); } - if (ev->ident > std::numeric_limits::max()) { - throw std::runtime_error(std::string(__func__) + ": context id overflow"); - } - return static_cast(ev->ident); + return static_cast(static_cast(ev->data) >> 16u); } int APS5_VABI sceAgcDriverGetEqEventType(const KernelEvent* ev) { if (ev == nullptr || reinterpret_cast(ev) % alignof(KernelEvent) != 0) { throw std::runtime_error(std::string(__func__) + ": null or misaligned event"); } - if (ev->filter == -14) { - if (ev->ident > static_cast(std::numeric_limits::max())) { - throw std::runtime_error(std::string(__func__) + ": graphics event type overflow"); - } - return static_cast(ev->ident); - } + if (ev->filter == -14) return static_cast(static_cast(static_cast(ev->data))); if (ev->data < std::numeric_limits::min() || ev->data > std::numeric_limits::max()) { throw std::runtime_error(std::string(__func__) + ": event type overflow"); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 45873560d1..d32168c0ed 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -145,10 +145,13 @@ private: static bool copySegment(Submission& submission, const std::uint32_t* guest, std::size_t words, std::size_t& budget); static void readRegisterLists(Submission& submission); void waitForFlipRoom(const Submission& submission); - void holdFlipBehindWorker(const Submission& submission); + void holdFlipBehindWorker(const Submission& submission, bool cpuFlip); bool awaitsTitle(std::uint64_t awaited) const; void noteAwaitingTitle(std::uint32_t queue, std::uint64_t awaited); void releaseFlipHold(); + void finishFlipWork(std::uint64_t workSerial); + std::uint64_t submitFlipWork(std::uint32_t queue); + void waitFlipWork(std::uint64_t workSerial); void reserveOutputs(Submission& submission); void executeRewindTail(const Submission& stalled); void enqueue(Submission submission); @@ -159,7 +162,7 @@ private: void deferCompletion(const Submission& submission); void submitOpenWork(const std::shared_ptr& progress = {}); void wakeQueueZeroForCompletions(); - void completeSubmission(std::uint64_t serial, std::uint64_t received, std::span labelWrites, const std::shared_ptr& progress); + void completeSubmission(std::uint64_t serial, std::uint64_t received, std::span labelWrites, const std::shared_ptr& progress, bool holdsFlip); static bool waitFree(const Submission& submission); bool queue0Before(std::uint64_t received) const; bool waitFreeBefore(std::uint64_t received) const; @@ -298,12 +301,12 @@ private: static std::vector> drawWriteRanges(const Graphics::State& graphics, std::span stages); void commitDraw(std::shared_ptr localDevice, std::uint32_t queue, const Graphics::State& graphics, const Pm4::DrawParameters& drawParameters, std::span stages, std::span snapshots, const std::shared_ptr& recipe, const std::vector>& recipeStages, std::uint64_t drawKey, VulkanDevice::DrawTraceContext trace = {}, std::uint64_t shape = 0); bool labelPacketQueues(std::span packet, std::uint32_t opcode); - bool enqueueLabelPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue); + bool enqueueLabelPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue, std::optional interrupt); bool enqueueDmaPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue, const QueueState& state); bool enqueueSampleDumpPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue); bool enqueueConstantRamDump(std::span packet, std::uint32_t opcode, std::uint32_t queue, const QueueState& state); void commitStore(std::uint32_t queue, std::uint64_t address, std::span bytes); - void commitLabel(std::uint32_t queue, std::uint64_t address, std::span bytes, bool endOfPipeInterrupt); + void commitLabel(std::uint32_t queue, std::uint64_t address, std::span bytes, std::optional interrupt); void submitDueAfterCommit(VulkanDevice* localDevice); std::optional newestWriterLocked(std::uint64_t begin, std::uint64_t end) const; std::optional newestWriter(std::uint64_t begin, std::uint64_t end); @@ -383,15 +386,15 @@ private: void recordQueuedLabelsByTry(std::uint32_t queue, std::atomic& counter); static void recordQueuedLabelsFromHook(); void flushBetweenPackets(std::uint32_t queue, std::uint32_t header, bool labelPacket); - bool preparePacketMemory(const Submission& submission, QueueState& queue, std::span packet, std::uint32_t header, std::uint32_t opcode, bool& wroteOnGpu, bool& endOfPipeInterrupt, bool& interruptDeferred, bool& drawPacket, bool& sampleDump); + bool preparePacketMemory(const Submission& submission, QueueState& queue, std::span packet, std::uint32_t header, std::uint32_t opcode, std::optional interrupt, bool& wroteOnGpu, bool& endOfPipeInterrupt, bool& interruptDeferred, bool& drawPacket, bool& sampleDump); void dumpSampleCounters(std::uint64_t address); template static void timed(double WorkerProfile::*bucket, TWork&& work); FrameTiming* frameTiming(); FrameTiming* includeTimingSubmission(const Submission& submission, bool firstSegment); void execute(const Submission& submission); - void deliverGuestEopInterrupt(std::uint32_t queue); - void deliverFlipEopInterrupt(std::uint32_t queue); + void deliverGuestEopInterrupt(std::uint32_t queue, std::uint32_t context); + static std::optional releaseInterrupt(std::span packet, std::uint32_t opcode, bool carriesFlip); void markCompleted(std::uint64_t serial); static const std::atomic*& workerQueued(); void reapCompletionLabels(); @@ -417,9 +420,6 @@ private: std::atomic flipSerial{0}; std::atomic flipBatchesUnsignaled{0}; - std::mutex eopSelfSignalMutex; - std::unordered_map eopSelfSignalFlips; - alignas(64) std::atomic packetsInFlight{0}; std::atomic packetsDone{0}; @@ -502,6 +502,8 @@ private: std::atomic queue0Awaited{0}; std::atomic flipsAhead{0}; std::atomic flipHolders{0}; + std::atomic flipWorkOpen{0}; + std::atomic flipWorkSerial{0}; std::atomic queue0AwaitsTitle{false}; std::atomic evidenceReads{0}; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index f7d2124a64..c71041fd22 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -19,13 +19,14 @@ #include #include #include +#include +#include #include namespace AgcDriver::DriverDetail { namespace { -constexpr std::uint32_t eopSelfSignalFlipLimit = 4; struct RingOrder { std::uint32_t queue = 0xffffffffu; @@ -121,22 +122,18 @@ bool& drawnSinceSeal() { } -void Driver::deliverGuestEopInterrupt(std::uint32_t queue) { - if (!AgcDriverDeliverEopInterrupt(queue)) return; - std::lock_guard lock(eopSelfSignalMutex); - eopSelfSignalFlips[queue] = 0; +void Driver::deliverGuestEopInterrupt(std::uint32_t queue, std::uint32_t context) { + AgcDriverDeliverEopInterrupt(queue, context); } -void Driver::deliverFlipEopInterrupt(std::uint32_t queue) { - { - std::lock_guard lock(eopSelfSignalMutex); - const auto latched = eopSelfSignalFlips.find(queue); - if (latched != eopSelfSignalFlips.end()) { - if (++latched->second <= eopSelfSignalFlipLimit) return; - eopSelfSignalFlips.erase(latched); - } - } - AgcDriverDeliverEopInterrupt(queue); +std::optional Driver::releaseInterrupt(std::span packet, std::uint32_t opcode, bool carriesFlip) { + if (opcode != 0x49 || carriesFlip || packet.size() < 3) return std::nullopt; + const auto interruptSelect = (packet[2] >> 24u) & 7u; + if (interruptSelect == 0 || interruptSelect == 3) return std::nullopt; + if (packet.size() < 8) throw std::runtime_error("AGC: an interrupting release has no interrupt context dword"); + const auto context = packet[7] & 0x0fffffffu; + if ((context & 0x08000000u) != 0) throw std::runtime_error("AGC: a release asks for typed interrupt context " + std::to_string(context) + ", which is not delivered as a graphics end-of-pipe event; not implemented"); + return context; } template @@ -162,14 +159,25 @@ void Driver::execute(const Submission& submission) { auto* submissionTiming = includeTimingSubmission(submission, true); struct FlipHold { Driver& driver; + std::uint32_t queue; bool held; + bool working; void Release() { if (!held) return; held = false; driver.releaseFlipHold(); } - ~FlipHold() { Release(); } - } flipHold{*this, submission.holdsFlip}; + void Finish() { + if (!working) return; + const auto workSerial = driver.submitFlipWork(queue); + working = false; + driver.finishFlipWork(workSerial); + } + ~FlipHold() { + Release(); + if (working) driver.finishFlipWork(0); + } + } flipHold{*this, submission.queue, submission.holdsFlip, submission.holdsFlip}; const bool pipelined = submission.queue == 0 && DrawPipeline::Depth() != 0; static const bool orderedWaits = std::getenv("APS5_PIPELINE_DRAIN_LABELS") == nullptr; DrawPipeline::Active() = pipelined; @@ -294,7 +302,7 @@ void Driver::execute(const Submission& submission) { if (submission.queue != 0 && Graphics::Recorder::DispatchOverlapEnabled()) noteRingPacket(submission.queue, submission.serial, opcode, header); PerformanceTimer timing("Driver.Packet"); if (!predicated) noteLabelIssue(packet, submission.queue); - if (pipelined && !predicated && header != FlipPacketHeader && header != RenderingWaitPacketHeader && !submission.flips.contains(cursor) && (enqueueLabelPacket(packet, opcode, submission.queue) || enqueueDmaPacket(packet, opcode, submission.queue, queue) || enqueueConstantRamDump(packet, opcode, submission.queue, queue) || enqueueSampleDumpPacket(packet, opcode, submission.queue))) { + if (pipelined && !predicated && header != FlipPacketHeader && header != RenderingWaitPacketHeader && !submission.flips.contains(cursor) && (enqueueLabelPacket(packet, opcode, submission.queue, releaseInterrupt(packet, opcode, false)) || enqueueDmaPacket(packet, opcode, submission.queue, queue) || enqueueConstantRamDump(packet, opcode, submission.queue, queue) || enqueueSampleDumpPacket(packet, opcode, submission.queue))) { traceLabel(packet, submission.queue); recent.Record(cursor); cursor = nextCursor; @@ -385,7 +393,7 @@ void Driver::execute(const Submission& submission) { if (profilePackets) packetProfile.flushMs += std::chrono::duration(packetTimer.start - flushStart).count(); bool wroteOnGpu = false, endOfPipeInterrupt = false, interruptDeferred = false, drawPacket = false, sampleDump = false; - const bool drains = preparePacketMemory(submission, queue, packet, header, opcode, wroteOnGpu, endOfPipeInterrupt, interruptDeferred, drawPacket, sampleDump); + const bool drains = preparePacketMemory(submission, queue, packet, header, opcode, releaseInterrupt(packet, opcode, submission.flips.contains(cursor)), wroteOnGpu, endOfPipeInterrupt, interruptDeferred, drawPacket, sampleDump); traceLabel(packet, submission.queue); timing.Mark("prepare_memory"); @@ -409,12 +417,10 @@ void Driver::execute(const Submission& submission) { Pm4::Execute(packet, queue); if (const auto label = Pm4::DecodeLabelWrite(packet)) noteLabelStore(label->address, label->Bytes(), ++eventSerial, submission.queue); } - if (endOfPipeInterrupt && !interruptDeferred) deliverGuestEopInterrupt(submission.queue); } if (deviceUse.owns_lock()) deviceUse.unlock(); CheckFailure(); std::uint64_t batchesAtFlip = 0, unsignaledAtFlip = 0; - bool flipInterruptDeferred = false; Graphics::StorageTexture::NoteFlip(); if (!drains) { @@ -424,13 +430,8 @@ void Driver::execute(const Submission& submission) { if (localDevice != nullptr) static_cast(Graphics::StorageTexture::StoreDrawnLinear("flip")); if (!recordLabelsForPacket(localDevice.get(), submission.queue) && localDevice != nullptr) localDevice->SubmitRecorded(submission.queue == 0); - if (localDevice != nullptr) { - localDevice->FlipBatches(batchesAtFlip, unsignaledAtFlip); - const auto queueId = submission.queue; - flipInterruptDeferred = localDevice->AfterRecordedWork([this, queueId] { deliverFlipEopInterrupt(queueId); }, false); - } + if (localDevice != nullptr) localDevice->FlipBatches(batchesAtFlip, unsignaledAtFlip); } - if (!flipInterruptDeferred) deliverFlipEopInterrupt(submission.queue); ++flipsCounted; Graphics::NoteHostImportFrame(); if (batchesAtFlip != 0) flipSerial = batchesAtFlip; @@ -593,7 +594,7 @@ void Driver::execute(const Submission& submission) { if (const auto label = Pm4::DecodeLabelWrite(packet)) noteLabelStore(label->address, label->Bytes(), ++eventSerial, submission.queue); } } - if (endOfPipeInterrupt && !interruptDeferred) deliverGuestEopInterrupt(submission.queue); + if (endOfPipeInterrupt && !interruptDeferred) deliverGuestEopInterrupt(submission.queue, packet[7] & 0x0fffffffu); } if (drawPacket || (sampleDump && wroteOnGpu)) Graphics::Recorder::CountRecordedWork(); timing.Mark(header == RenderingWaitPacketHeader ? "rendering_wait" : opcode == 0x3c || opcode == 0x93 ? "memory_wait" : opcode == 0x15 || opcode == 0x16 ? "dispatch" : drawPacket ? "draw" : "execute"); @@ -608,12 +609,15 @@ void Driver::execute(const Submission& submission) { static const bool submitAtEnd = std::getenv("APS5_SUBMIT_AT_END") != nullptr; if (pipelined && orderedCompletion && deferredLabels().labels.empty()) { if (submission.rewindTail == nullptr) { + flipHold.working = false; deferCompletion(submission); return; } if ((std::atomic_ref(*const_cast(submission.rewindTail - 1)).load(std::memory_order_acquire) & 0x80000000u) != 0) { - DrawPipeline::Queue0().Enqueue([this, progress = submission.progress] { - submitOpenWork(progress); + const bool holdsFlip = std::exchange(flipHold.working, false); + DrawPipeline::Queue0().Enqueue([this, holdsFlip, progress = submission.progress] { + if (holdsFlip) finishFlipWork(submitFlipWork(0)); + else submitOpenWork(progress); wakeQueueZeroForCompletions(); }, {}); endTiming.Finish(); @@ -626,6 +630,7 @@ void Driver::execute(const Submission& submission) { auto& costs = submissionCosts(submission.queue); if (!submitAtEnd && submission.rewindTail == nullptr && submission.queue == 0 && workerQueued() != nullptr && workerQueued()->load(std::memory_order_acquire) != 0) { ++costs.endSkipped; + flipHold.Finish(); return; } ++costs.endSubmits; @@ -636,6 +641,7 @@ void Driver::execute(const Submission& submission) { recordDeferredLabels(localDevice.get(), submission.queue); if (localDevice != nullptr) localDevice->SubmitRecorded(submission.queue == 0); } + flipHold.Finish(); endTiming.Finish(); if (submission.rewindTail != nullptr) { if (submission.progress) submitOpenWork(submission.progress); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp index 64a7b648bc..3618843d5b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp @@ -54,15 +54,14 @@ std::optional decodeCopy(std::span packet) } -bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue, std::span packet, std::uint32_t header, std::uint32_t opcode, bool& wroteOnGpu, bool& endOfPipeInterrupt, bool& interruptDeferred, bool& drawPacket, bool& sampleDump) { +bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue, std::span packet, std::uint32_t header, std::uint32_t opcode, std::optional interrupt, bool& wroteOnGpu, bool& endOfPipeInterrupt, bool& interruptDeferred, bool& drawPacket, bool& sampleDump) { static const bool drainAll = std::getenv("APS5_DRAIN_ALL") != nullptr; static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; wroteOnGpu = false; bool orderedAlready = false; - const auto interruptSelect = opcode == 0x49 ? (packet[2] >> 24u) & 7u : 0u; - endOfPipeInterrupt = interruptSelect != 0 && interruptSelect != 3; + endOfPipeInterrupt = interrupt.has_value(); interruptDeferred = false; if (!drainAll && endOfPipeInterrupt) { const auto label = Pm4::DecodeLabelWrite(packet); @@ -94,7 +93,8 @@ bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue } if (reason == 0 || reason == 5) { const auto queueId = submission.queue; - interruptDeferred = localDevice->AfterRecordedWork([this, queueId] { deliverGuestEopInterrupt(queueId); }, submission.queue == 0); + const auto context = *interrupt; + interruptDeferred = localDevice->AfterRecordedWork([this, queueId, context] { deliverGuestEopInterrupt(queueId, context); }, submission.queue == 0); if (interruptDeferred && workOpen) localDevice->SubmitRecorded(submission.queue == 0); wroteOnGpu = true; } else if (reason == 1) { @@ -487,10 +487,9 @@ bool Driver::labelPacketQueues(std::span packet, std::uint3 return opcode == 0x49 && ((packet[2] >> 29u) == 0 || (packet[3] | (static_cast(packet[4]) << 32u)) == 0); } -bool Driver::enqueueLabelPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue) { +bool Driver::enqueueLabelPacket(std::span packet, std::uint32_t opcode, std::uint32_t queue, std::optional interrupt) { if (!labelPacketQueues(packet, opcode)) return false; - const auto interruptSelect = opcode == 0x49 ? (packet[2] >> 24u) & 7u : 0u; - const bool endOfPipeInterrupt = interruptSelect != 0 && interruptSelect != 3; + const bool endOfPipeInterrupt = interrupt.has_value(); const auto label = Pm4::DecodeLabelWrite(packet); if (!label.has_value()) { if (opcode != 0x49) return false; @@ -513,10 +512,10 @@ bool Driver::enqueueLabelPacket(std::span packet, std::uint if (endOfPipeInterrupt) bumpEpoch(&EpochBumps::drains); auto stored = bytes; auto labelBytes = bytes; - auto enqueue =[this, queue, opcode, address, bytes = std::move(bytes), endOfPipeInterrupt, writes, stored = std::move(stored)]() mutable { - DrawPipeline::Queue0().Enqueue([this, queue, opcode, address, bytes = std::move(bytes), endOfPipeInterrupt] { + auto enqueue =[this, queue, opcode, address, bytes = std::move(bytes), interrupt, writes, stored = std::move(stored)]() mutable { + DrawPipeline::Queue0().Enqueue([this, queue, opcode, address, bytes = std::move(bytes), interrupt] { GuestMemory::SetCurrentPacket(opcode, queue); - commitLabel(queue, address, bytes, endOfPipeInterrupt); + commitLabel(queue, address, bytes, interrupt); }, std::move(writes), address, std::move(stored)); }; if (queue == 0 && DrawFinisher::Overlap() && DrawFinisher::Busy()) { @@ -532,7 +531,7 @@ bool Driver::enqueueLabelPacket(std::span packet, std::uint return true; } -void Driver::commitLabel(std::uint32_t queue, std::uint64_t address, std::span bytes, bool endOfPipeInterrupt) { +void Driver::commitLabel(std::uint32_t queue, std::uint64_t address, std::span bytes, std::optional interrupt) { GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Label); std::lock_guard gpuLock(GuestMemory::GpuMutex()); const auto localDevice = device.Load(); @@ -554,14 +553,15 @@ void Driver::commitLabel(std::uint32_t queue, std::uint64_t address, std::spanAfterRecordedWork([this, queue] { deliverGuestEopInterrupt(queue); }, queue == 0); + deferred = localDevice->AfterRecordedWork([this, queue, context] { deliverGuestEopInterrupt(queue, context); }, queue == 0); if (deferred && workOpen) localDevice->SubmitRecorded(queue == 0); } - if (!deferred) deliverGuestEopInterrupt(queue); + if (!deferred) deliverGuestEopInterrupt(queue, context); } submitDueAfterCommit(localDevice.get()); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp index e30e7dfe0f..a50fc98699 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp @@ -70,8 +70,8 @@ void Driver::deferCompletion(const Submission& submission) { queue0Uncommitted.push_back(received); } completionDeferred() = true; - DrawPipeline::Queue0().Enqueue([this, serial = submission.serial, received, labelWrites = submission.labelWrites, progress = submission.progress] { - completeSubmission(serial, received, labelWrites, progress); + DrawPipeline::Queue0().Enqueue([this, serial = submission.serial, received, labelWrites = submission.labelWrites, progress = submission.progress, holdsFlip = submission.holdsFlip] { + completeSubmission(serial, received, labelWrites, progress, holdsFlip); }, {}); } @@ -95,7 +95,15 @@ void Driver::wakeQueueZeroForCompletions() { changed.notify_all(); } -void Driver::completeSubmission(std::uint64_t serial, std::uint64_t received, std::span labelWrites, const std::shared_ptr& progress) { +std::uint64_t Driver::submitFlipWork(std::uint32_t queue) { + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::End); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + const auto localDevice = device.Load(); + recordDeferredLabels(localDevice.get(), queue); + return localDevice != nullptr ? localDevice->SubmitAndEpoch() : 0; +} + +void Driver::completeSubmission(std::uint64_t serial, std::uint64_t received, std::span labelWrites, const std::shared_ptr& progress, bool holdsFlip) { struct Complete { Driver& driver; std::uint64_t serial, received; @@ -113,7 +121,8 @@ void Driver::completeSubmission(std::uint64_t serial, std::uint64_t received, st if (notify) driver.changed.notify_all(); } } complete{*this, serial, received, labelWrites}; - submitOpenWork(progress); + if (holdsFlip) finishFlipWork(submitFlipWork(0)); + else submitOpenWork(progress); } const std::atomic*& Driver::workerQueued() { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index 76facb3cd7..0551e52ba3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -263,7 +263,7 @@ void Driver::noteAwaitingTitle(std::uint32_t queue, std::uint64_t awaited) { changed.notify_all(); } -void Driver::holdFlipBehindWorker(const Submission& submission) { +void Driver::holdFlipBehindWorker(const Submission& submission, bool cpuFlip) { if (submission.queue != 0 || onWorkerThread()) return; bool candidate = false; for (std::size_t cursor = 0; cursor < submission.commands.size() && !candidate; cursor += Pm4::PacketWords(submission.commands[cursor])) { @@ -281,6 +281,39 @@ void Driver::holdFlipBehindWorker(const Submission& submission) { flipHolders.fetch_add(1, std::memory_order_acq_rel); changed.wait(lock, [&] { return failure != nullptr || stopping.load(std::memory_order_acquire) || shutdownToken.stop_requested() || flipsAhead.load(std::memory_order_acquire) == 0 || queue0AwaitsTitle.load(std::memory_order_acquire); }); flipHolders.fetch_sub(1, std::memory_order_acq_rel); + if (cpuFlip || failure != nullptr || stopping.load(std::memory_order_acquire) || shutdownToken.stop_requested() || flipsAhead.load(std::memory_order_acquire) != 0) return; + flipHolders.fetch_add(1, std::memory_order_acq_rel); + changed.wait(lock, [&] { return failure != nullptr || stopping.load(std::memory_order_acquire) || shutdownToken.stop_requested() || flipWorkOpen.load(std::memory_order_acquire) == 0 || queue0AwaitsTitle.load(std::memory_order_acquire); }); + flipHolders.fetch_sub(1, std::memory_order_acq_rel); + if (failure != nullptr || stopping.load(std::memory_order_acquire) || shutdownToken.stop_requested() || flipWorkOpen.load(std::memory_order_acquire) != 0) return; + const auto workSerial = flipWorkSerial.load(std::memory_order_acquire); + lock.unlock(); + waitFlipWork(workSerial); +} + +void Driver::waitFlipWork(std::uint64_t workSerial) { + if (workSerial == 0) return; + auto localDevice = device.Load(); + if (localDevice == nullptr) return; + if (localDevice->CanWaitUnlocked()) { + try { + localDevice->WaitRecorded(workSerial); + } catch (...) { + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Wait); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + localDevice.reset(); + throw; + } + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Wait); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + localDevice->ReapRecorded(workSerial); + localDevice.reset(); + return; + } + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Wait); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + localDevice->WaitIdle(); + localDevice.reset(); } void Driver::releaseFlipHold() { @@ -289,6 +322,13 @@ void Driver::releaseFlipHold() { if (flipHolders.load(std::memory_order_acquire) != 0) changed.notify_all(); } +void Driver::finishFlipWork(std::uint64_t workSerial) { + std::lock_guard lock(mutex); + if (workSerial > flipWorkSerial.load(std::memory_order_relaxed)) flipWorkSerial.store(workSerial, std::memory_order_release); + flipWorkOpen.fetch_sub(1, std::memory_order_acq_rel); + if (flipHolders.load(std::memory_order_acquire) != 0) changed.notify_all(); +} + void Driver::reserveOutputs(Submission& submission) { std::set eopTokens; for (std::size_t cursor = 0; cursor < submission.commands.size();) { @@ -470,7 +510,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) { readRegisterLists(submission); if (APS5_ENABLE_TIMING_LOG) submission.validatedAt = std::chrono::steady_clock::now(); waitForFlipRoom(submission); - holdFlipBehindWorker(submission); + holdFlipBehindWorker(submission, cpuFlip); AwaitRegistrationPreparation(); if (LibraryPrebuildsWaiting()) PrioritizeLibraryPrebuilds(BoundGraphicsPrograms(submission)); if (APS5_ENABLE_TIMING_LOG) submission.roomReadyAt = std::chrono::steady_clock::now(); @@ -498,7 +538,6 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) { submission.flips.begin()->second->GpuReady(frame); lock.unlock(); Graphics::NoteHostImportFrame(); - AgcDriverDeliverEopInterrupt(0); return; } submission.shaders = shaders; @@ -518,6 +557,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) { if (queue == 0 && !submission.flips.empty()) { submission.holdsFlip = true; flipsAhead.fetch_add(1, std::memory_order_acq_rel); + flipWorkOpen.fetch_add(1, std::memory_order_acq_rel); } if (!submission.flips.empty()) ++flipSubmissionsOfThread(); enqueue(std::move(submission)); diff --git a/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp b/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp index 5e2fc66ef1..51762adbd8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ConstantBitTest.cpp @@ -126,7 +126,7 @@ bool halvesRejoin() { return rejoined == &word && fieldBack; } -bool factorFieldKept(bool multiplied) { +bool factorFieldKept(bool multiplied, bool negated) { IrProgram program; auto& block = program.CreateBlock(); program.SetEntryBlock(block); @@ -137,13 +137,14 @@ bool factorFieldKept(bool multiplied) { auto& sum = builder.Emit(IrOpcode::IAdd32, IrType::U32, {&builder.Constant(1u), &lane}); auto& masked = builder.Emit(IrOpcode::BitwiseAnd32, IrType::U32, {&sum, &builder.Constant(0xffffu)}); auto& field = builder.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&masked, &builder.Constant(0u), &builder.Constant(24u)}); - auto& used = multiplied ? builder.IMul(field, field) : field; + auto& factor = negated ? builder.Emit(IrOpcode::BitwiseXor32, IrType::U32, {&field, &builder.Constant(0x8000u)}) : field; + auto& used = multiplied ? builder.IMul(factor, factor) : factor; auto& user = builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&used}); static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); ConstantFolder().Fold(program); const IrValue* result = user.Argument(0)->Resolve(); if (!multiplied) return result == &masked; - return result->Opcode() == IrOpcode::IMul32 && result->Argument(0)->Resolve() == &field && field.Opcode() == IrOpcode::BitFieldUExtract && field.Argument(0)->Resolve() == &masked; + return result->Opcode() == IrOpcode::IMul32 && result->Argument(0)->Resolve() == &factor && field.Opcode() == IrOpcode::BitFieldUExtract && field.Argument(0)->Resolve() == &masked; } bool reversesTo(std::uint32_t word, std::uint32_t expected) { @@ -323,8 +324,9 @@ int main() { passed &= run("a mask under a merge of the halves reads through it", mergedHalfMask()); passed &= run("half extractions read the half that was merged in", halfExtractions()); passed &= run("the two halves of one word rejoin into the word", halvesRejoin()); - passed &= run("a 24-bit field read of a value masked to 16 bits folds into the value", factorFieldKept(false)); - passed &= run("a 24-bit field read that a multiply uses is kept, even for a factor masked to 16 bits", factorFieldKept(true)); + passed &= run("a 24-bit field read of a value masked to 16 bits folds into the value", factorFieldKept(false, false)); + passed &= run("a 24-bit field read that a multiply uses is kept, even for a factor masked to 16 bits", factorFieldKept(true, false)); + passed &= run("a 24-bit field read that a multiply uses through a negating xor is kept, even for a factor masked to 16 bits", factorFieldKept(true, true)); return passed ? 0 : 1; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp index 849703ad95..43a8dd8106 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp @@ -173,8 +173,11 @@ void testFlipAndBoundary() { AgcDriverUnregisterVideoOutput_nid_postfix(7, output); auto replacement = std::make_shared(); AgcDriverRegisterVideoOutput_nid_postfix(7, replacement); - submitFlip(); + auto next = std::async(std::launch::async, [] { submitFlip(); }); + check(next.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout, "a flip was accepted before the previous flip submission completed"); release.Release(); + check(next.wait_for(std::chrono::seconds(5)) == std::future_status::ready, "a flip stayed held after the previous flip submission completed"); + next.get(); AgcDriverWaitIdle_nid_postfix(); check(output->state->ready == 1 && replacement->state->ready == 1, "registration lifetime or FIFO was lost"); check(output->state->failed == 0, "successful request failed"); @@ -282,6 +285,50 @@ void testFlipHold() { AgcDriverUnregisterVideoOutput_nid_postfix(7, output); } +void testFlipHoldCoversTrailingLabel() { + auto output = std::make_shared(); + AgcDriverRegisterVideoOutput_nid_postfix(7, output); + { + std::lock_guard lock(output->state->mutex); + output->state->gate = true; + output->state->waiting = false; + } + struct GateRelease { + std::shared_ptr state; + ~GateRelease() { + { + std::lock_guard lock(state->mutex); + state->gate = false; + } + state->changed.notify_all(); + } + } gateRelease{output->state}; + alignas(8) static std::atomic label = 0; + label.store(0, std::memory_order_release); + const auto labelAddress = reinterpret_cast(&label); + std::array words{0xc004105c, 7, 0xfffffffeu, 1, 0, 0, 0xc0021018, 7, 0, 0, 0xc0033700, 0x04100200, static_cast(labelAddress), static_cast(static_cast(labelAddress) >> 32u), 1}; + Packet frame{words.data(), static_cast(words.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&frame) == 0, "flip submission with a trailing label failed"); + { + std::unique_lock lock(output->state->mutex); + check(output->state->changed.wait_for(lock, std::chrono::seconds(5), [&] { return output->state->waiting; }), "worker did not reach the rendering wait after the flip"); + } + auto next = std::async(std::launch::async, [] { submitFlip(); }); + check(next.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout, "a flip was accepted while the previous flip submission had not written its trailing label"); + check(label.load(std::memory_order_acquire) == 0, "a label behind a closed rendering wait was written"); + { + std::lock_guard lock(output->state->mutex); + output->state->gate = false; + } + output->state->changed.notify_all(); + check(next.wait_for(std::chrono::seconds(5)) == std::future_status::ready, "a flip stayed held after the previous flip submission completed"); + next.get(); + check(label.load(std::memory_order_acquire) == 1, "a flip was accepted before the previous flip submission's trailing label landed"); + AgcDriverWaitIdle_nid_postfix(); + check(output->state->ready == 2, "flips around a trailing label were lost"); + AgcDriverUnregisterVideoOutput_nid_postfix(7, output); +} + void testComputeRenderingWait() { auto output = std::make_shared(); AgcDriverRegisterVideoOutput_nid_postfix(7, output); @@ -457,7 +504,7 @@ int main(int argc, char** argv) { return 0; } if (argc == 2) testReset(std::string(argv[1]) == "compute"); - else { testFlipAndBoundary(); testFlipHold(); testComputeRenderingWait(); testComputeFlip(); testFailure(); } + else { testFlipAndBoundary(); testFlipHold(); testFlipHoldCoversTrailingLabel(); testComputeRenderingWait(); testComputeFlip(); testFailure(); } const auto shutdown = expectFailure([] { LibcRunShutdown_nid_postfix(); }); check(shutdown.find(argc == 2 ? (std::string(argv[1]) == "compute" ? "not readable" : "required shader register") : "intentional flip failure") != std::string::npos, "shutdown lost worker failure"); std::puts("AGC flip and suspend tests passed"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 5c0440e4f8..ba9d445a52 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -71,24 +71,20 @@ std::string expectFailure(TAction action) { void testEvents() { KernelEvent event{}; event.filter = -14; - event.ident = 0x40; - event.data = 123; - check(sceAgcDriverGetEqEventType(&event) == 0x40, "graphics event uses wrong field"); + event.ident = 0x20; + event.data = (0x38 << 16) | 0x20; + check(sceAgcDriverGetEqEventType(&event) == ((0x38 << 16) | 0x20), "graphics event type is not the event data"); + check(sceAgcDriverGetEqContextId(&event) == 0x38, "graphics event context id is not the data's upper bits"); + event.data = static_cast((std::uint64_t{0x0fffffff} << 16u) | 0x28); + check(sceAgcDriverGetEqContextId(&event) == 0x0fffffffu, "graphics event context id lost its upper bits"); + check(sceAgcDriverGetEqEventType(&event) == static_cast(0xffff0028u), "graphics event type is not the data's low word"); event.filter = -1; event.data = -17; check(sceAgcDriverGetEqEventType(&event) == -17, "non-graphics event uses wrong field"); event.data = std::numeric_limits::max(); expectFailure([&] { sceAgcDriverGetEqEventType(&event); }); - event.filter = -14; - event.ident = std::numeric_limits::max(); - expectFailure([&] { sceAgcDriverGetEqEventType(&event); }); expectFailure([] { sceAgcDriverGetEqEventType(nullptr); }); expectFailure([&] { sceAgcDriverGetEqEventType(reinterpret_cast(reinterpret_cast(&event) + 1)); }); - event.ident = 0x29; - check(sceAgcDriverGetEqContextId(&event) == 0x29, "graphics event context id uses wrong field"); - event.ident = std::numeric_limits::max(); - expectFailure([&] { sceAgcDriverGetEqContextId(&event); }); - event.ident = 1; event.filter = -1; expectFailure([&] { sceAgcDriverGetEqContextId(&event); }); expectFailure([] { sceAgcDriverGetEqContextId(nullptr); }); @@ -238,58 +234,47 @@ void testEndOfPipeInterrupts() { AgcDriverWaitIdle_nid_postfix(); check(output->ready.load() == 1, "flip-only queue flip was not handed to its output"); check(AgcDriver::Graphics::HostImportFramesEnded() == 1, "the first flip did not end a host import frame"); - check(owner->GetTriggeredEvents(events.data(), 2) == 1 && events[0].udata == &graphicsTag && events[0].data == 1 && sceAgcDriverGetEqEventType(events.data()) == 0, "flip on a queue that never requested a release interrupt did not raise its event"); - check(owner->GetTriggeredEvents(events.data(), 2) == 0, "flip-only queue event was not cleared"); + check(owner->GetTriggeredEvents(events.data(), 2) == 0, "a flip raised the graphics end-of-pipe event"); std::array words{0xc0064900, 0, 1u << 24u, 0, 0, 0, 0, 0}; Packet packet{words.data(), static_cast(words.size()), 0, {}}; check(sceAgcDriverSubmitDcb(&packet) == 0 && sceAgcDriverSubmitDcb(&packet) == 0, "interrupt submit failed"); AgcDriverWaitIdle_nid_postfix(); check(owner->GetTriggeredEvents(events.data(), 2) == 1, "graphics end-of-pipe interrupt was not delivered to its queue only"); - check(events[0].filter == -14 && events[0].udata == &graphicsTag && events[0].data == 2 && sceAgcDriverGetEqEventType(events.data()) == 0, "graphics end-of-pipe event encoding is wrong"); + check(events[0].filter == -14 && events[0].udata == &graphicsTag && events[0].data == 0 && sceAgcDriverGetEqEventType(events.data()) == 0, "graphics end-of-pipe event encoding is wrong"); check(sceAgcDriverGetEqContextId(events.data()) == 0, "graphics end-of-pipe event reports another context"); check(owner->GetTriggeredEvents(events.data(), 2) == 0, "delivered interrupt was not cleared"); + words[7] = 0x38; + check(sceAgcDriverSubmitDcb(&packet) == 0, "interrupt submit with a context failed"); + AgcDriverWaitIdle_nid_postfix(); + check(owner->GetTriggeredEvents(events.data(), 2) == 1 && events[0].udata == &graphicsTag && events[0].data == (0x38 << 16), "graphics end-of-pipe event does not carry the release's interrupt context"); + check(sceAgcDriverGetEqContextId(events.data()) == 0x38 && sceAgcDriverGetEqEventType(events.data()) == (0x38 << 16), "graphics end-of-pipe event reports another context"); + words[7] = 0xf0000041u; + check(sceAgcDriverSubmitDcb(&packet) == 0, "interrupt submit with high context bits failed"); + AgcDriverWaitIdle_nid_postfix(); + check(owner->GetTriggeredEvents(events.data(), 2) == 1 && sceAgcDriverGetEqContextId(events.data()) == 0x41, "the interrupt context kept bits above its 28 bits"); + words[7] = 0x38; check(sceAgcDriverSubmitAcb(0x20, &packet) == 0, "compute interrupt submit failed"); AgcDriverWaitIdle_nid_postfix(); - check(owner->GetTriggeredEvents(events.data(), 2) == 1 && events[0].udata == &computeTag && sceAgcDriverGetEqEventType(events.data()) == 0x20, "compute end-of-pipe interrupt missing"); - check(sceAgcDriverGetEqContextId(events.data()) == 0x20, "compute end-of-pipe event reports another context"); + check(owner->GetTriggeredEvents(events.data(), 2) == 1 && events[0].udata == &computeTag && events[0].data == ((0x38 << 16) | 0x20) && sceAgcDriverGetEqEventType(events.data()) == ((0x38 << 16) | 0x20), "compute end-of-pipe interrupt missing"); + check(sceAgcDriverGetEqContextId(events.data()) == 0x38, "compute end-of-pipe event reports another context"); + words[7] = 0; words[2] = 0; check(sceAgcDriverSubmitDcb(&packet) == 0, "plain release submit failed"); AgcDriverWaitIdle_nid_postfix(); check(owner->GetTriggeredEvents(events.data(), 2) == 0, "release without INT_SEL raised an interrupt"); - check(sceAgcDriverSubmitDcb(&flipPacket) == 0, "flip after a release interrupt submit failed"); - AgcDriverWaitIdle_nid_postfix(); - check(output->ready.load() == 2, "flip after a release interrupt was not handed to its output"); - check(AgcDriver::Graphics::HostImportFramesEnded() == 2, "a flip packet executed on the queue did not end a host import frame"); - check(owner->GetTriggeredEvents(events.data(), 2) == 0, "flip on a queue that already raised its own release interrupt raised a second event"); - words[2] = 1u << 24u; - check(sceAgcDriverSubmitDcb(&packet) == 0, "release before sustained flips submit failed"); - AgcDriverWaitIdle_nid_postfix(); - check(owner->GetTriggeredEvents(events.data(), 2) == 1, "a release interrupt did not deliver on a queue whose flip event is suppressed"); - std::uint32_t sustainedFlips = 0; - std::uint32_t flipsUntilEvent = 0; - while (sustainedFlips < 8 && flipsUntilEvent == 0) { - check(sceAgcDriverSubmitDcb(&flipPacket) == 0, "sustained flip submit failed"); + for (int index = 0; index < 8; ++index) { + check(sceAgcDriverSubmitDcb(&flipPacket) == 0, "flip after a release submit failed"); AgcDriverWaitIdle_nid_postfix(); - ++sustainedFlips; - check(output->ready.load() == static_cast(2 + sustainedFlips), "sustained flip was not handed to its output"); - if (owner->GetTriggeredEvents(events.data(), 2) != 0) flipsUntilEvent = sustainedFlips; + check(output->ready.load() == 2 + index, "flip after a release was not handed to its output"); + check(AgcDriver::Graphics::HostImportFramesEnded() == static_cast(2 + index), "a flip packet executed on the queue did not end a host import frame"); + check(owner->GetTriggeredEvents(events.data(), 2) == 0, "a flip after a release raised the graphics end-of-pipe event"); } - check(flipsUntilEvent != 0, "a flip never raised an event again on a queue whose own release interrupts stopped"); - check(flipsUntilEvent > 2, "a flip raised an event within two flips of the queue's own release interrupt"); - check(sceAgcDriverSubmitDcb(&flipPacket) == 0, "flip after the latch cleared submit failed"); + words[2] = 2u << 24u; + words[7] = AgcDriverReserveEopFlip_nid_postfix(AgcDriver::FlipInfo{7, 0, 1, 0}); + check(sceAgcDriverSubmitDcb(&packet) == 0, "end-of-pipe flip release submit failed"); AgcDriverWaitIdle_nid_postfix(); - check(output->ready.load() == static_cast(3 + sustainedFlips), "flip after the latch cleared was not handed to its output"); - check(owner->GetTriggeredEvents(events.data(), 2) == 1, "a flip did not raise an event after the latch cleared"); - words[2] = 1u << 24u; - check(sceAgcDriverSubmitDcb(&packet) == 0, "release after a cleared latch submit failed"); - AgcDriverWaitIdle_nid_postfix(); - check(owner->GetTriggeredEvents(events.data(), 2) == 1, "a release interrupt did not deliver after the latch cleared"); - for (int index = 0; index < 2; ++index) { - check(sceAgcDriverSubmitDcb(&flipPacket) == 0, "flip paired with a release interrupt submit failed"); - AgcDriverWaitIdle_nid_postfix(); - check(output->ready.load() == static_cast(4 + sustainedFlips + index), "flip paired with a release interrupt was not handed to its output"); - check(owner->GetTriggeredEvents(events.data(), 2) == 0, "a flip raised an event within two flips of the queue's own release interrupt"); - } + check(output->ready.load() == 10, "end-of-pipe flip was not handed to its output"); + check(owner->GetTriggeredEvents(events.data(), 2) == 0, "a release that carries a flip raised the graphics end-of-pipe event"); const auto framesBefore = AgcDriver::Graphics::HostImportFramesEnded(); const auto readyBefore = output->ready.load(); std::thread([] { AgcDriverSubmitFlip_nid_postfix(AgcDriver::FlipInfo{7, -1, 1, 0}); }).join(); @@ -354,9 +339,9 @@ std::vector commands(const std::array&... packe return words; } -std::array endOfPipeLabel(volatile std::uint32_t* address, std::uint32_t value) { +std::array endOfPipeLabel(volatile std::uint32_t* address, std::uint32_t value, std::uint32_t context) { const auto target = reinterpret_cast(address); - return {0xc0064900, 0x514, (1u << 29u) | (2u << 24u), static_cast(target), static_cast(static_cast(target) >> 32u), value, 0, 0}; + return {0xc0064900, 0x514, (1u << 29u) | (2u << 24u), static_cast(target), static_cast(static_cast(target) >> 32u), value, 0, context}; } void testEndOfPipeLabelsWithoutWork() { @@ -367,12 +352,12 @@ void testEndOfPipeLabelsWithoutWork() { int tag = 0; check(sceAgcDriverAddEqEvent(eq, 0, &tag) == 0, "graphics event registration failed"); submit(0x20, commands(waitEqual(&second, 2), writeData(&done, 1))); - submit(0, commands(endOfPipeLabel(&first, 1), endOfPipeLabel(&second, 2))); + submit(0, commands(endOfPipeLabel(&first, 1, 0x11), endOfPipeLabel(&second, 2, 0x12))); waitFor(&done, 1, "an end-of-pipe label with no work before it never landed"); check(first == 1 && second == 2, "end-of-pipe labels with no work before them landed wrong"); AgcDriverWaitIdle_nid_postfix(); std::array events{}; - check(owner->GetTriggeredEvents(events.data(), 2) == 1 && events[0].udata == &tag && events[0].data == 2, "end-of-pipe labels with no work before them lost their interrupts"); + check(owner->GetTriggeredEvents(events.data(), 2) == 1 && events[0].udata == &tag && sceAgcDriverGetEqContextId(events.data()) == 0x12, "end-of-pipe labels with no work before them lost their interrupts"); check(sceAgcDriverDeleteEqEvent(eq, 0) == 0, "graphics event deletion failed"); owner.reset(); check(sceKernelDeleteEqueue(eq) == 0, "event queue deletion failed"); @@ -1121,7 +1106,11 @@ int main(int argc, char** argv) { try { if (argc == 2 && std::string_view(argv[1]) == "--eop-interrupts") { testEndOfPipeInterrupts(); - LibcRunShutdown_nid_postfix(); + std::array typed{0xc0064900, 0, 1u << 24u, 0, 0, 0, 0, 0x08000038u}; + Packet typedPacket{typed.data(), static_cast(typed.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&typedPacket) == 0, "typed interrupt context submit failed"); + check(expectFailure([] { AgcDriverWaitIdle_nid_postfix(); }).find("typed interrupt context") != std::string::npos, "a release with a typed interrupt context did not fail"); + check(expectFailure([] { LibcRunShutdown_nid_postfix(); }).find("typed interrupt context") != std::string::npos, "shutdown lost the typed interrupt context failure"); std::puts("AGC end-of-pipe interrupt queue mode tests passed"); return 0; } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MulLoU16HighHalf.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MulLoU16HighHalf.cpp index f64c0541dd..76e260802a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MulLoU16HighHalf.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MulLoU16HighHalf.cpp @@ -56,6 +56,18 @@ alignas(256) constexpr std::array PackedHighLaneCode{ 0x80010a03, 0xbf810000, }; +alignas(256) constexpr std::array PackedNegatedLowLaneCode{ + 0x34020082, 0x34060080, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xe0302008, 0x80000601, + 0xbf8c3f70, 0x360808ff, 0x0000ffff, 0x360a0aff, 0x0000ffff, 0xcc01400a, 0x38020b04, 0xe0702000, + 0x80010a03, 0xbf810000, +}; + +alignas(256) constexpr std::array PackedNegatedHighLaneCode{ + 0x34020082, 0x34060080, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xe0302008, 0x80000601, + 0xbf8c3f70, 0x360808ff, 0xffff0000, 0x360a0aff, 0xffff0000, 0xcc01410a, 0x18020b04, 0xe0702000, + 0x80010a03, 0xbf810000, +}; + constexpr std::array, 8> Edges{{ {0xffffffffu, 0xffffffffu, 0x00000000u}, {0x0000ffffu, 0x0000ffffu, 0xabcd1234u}, {0xffff0000u, 0xffff0000u, 0xffffffffu}, {0x8000ffffu, 0x7fff8000u, 0x12345678u}, {0x12345678u, 0x9abcdef0u, 0x80000000u}, {0x00010001u, 0x00010001u, 0x0000ffffu}, @@ -136,6 +148,10 @@ int main() { Check("v_pk_mul_lo_u16 of factors whose high halves are zero", [](std::uint32_t a, std::uint32_t b, std::uint32_t) { return Low16Product(a, b); }); Run(*device, PackedHighLaneCode); Check("v_pk_mul_lo_u16 of factors whose low halves are zero", [](std::uint32_t a, std::uint32_t b, std::uint32_t) { return Low16Product(a >> 16u, b >> 16u) << 16u; }); + Run(*device, PackedNegatedLowLaneCode); + Check("v_pk_mul_lo_u16 neg_lo:[1,0] of factors whose high halves are zero", [](std::uint32_t a, std::uint32_t b, std::uint32_t) { return Low16Product(a ^ 0x8000u, b); }); + Run(*device, PackedNegatedHighLaneCode); + Check("v_pk_mul_lo_u16 neg_hi:[1,0] of factors whose low halves are zero", [](std::uint32_t a, std::uint32_t b, std::uint32_t) { return Low16Product((a >> 16u) ^ 0x8000u, b >> 16u) << 16u; }); std::puts("mul lo u16 high half tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAjm.native/Export.cpp b/core/libs/prx/libSceAjm.native/Export.cpp index 7248aaec4c..427e292b5d 100644 --- a/core/libs/prx/libSceAjm.native/Export.cpp +++ b/core/libs/prx/libSceAjm.native/Export.cpp @@ -68,9 +68,36 @@ int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const } const char* APS5_VABI sceAjmStrError(int error) { - (void)error; - AjmStub(__func__); - return nullptr; + static const char* const messages[] = { + "context does not exist", + "instance does not exist", + "batch does not exist", + "invalid parameter", + "not enough memory", + "not enough resources", + "codec is not supported", + "codec is already registered in the context", + "codec is not registered in the context", + "wrong revision in the flags", + "flag is not supported", + "busy", + "priority is invalid", + "in progress", + "operation cannot be performed at the moment", + "malformed batch", + "error at job creation", + "invalid opcode", + "priority violation", + "buffer is too big", + "invalid address", + "batch was cancelled", + "specified memory area is already registered", + "specified memory area is not registered", + }; + if (error == 0) return "ok"; + const std::uint32_t index = static_cast(error) - 0x80930002u; + if (index < sizeof(messages) / sizeof(messages[0])) return messages[index]; + return "(unknown)"; } int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(WVorbisHeaderPacketParameter* param) { diff --git a/core/libs/prx/libSceSysmodule/CMakeLists.txt b/core/libs/prx/libSceSysmodule/CMakeLists.txt index f8c08ca098..6107e70196 100644 --- a/core/libs/prx/libSceSysmodule/CMakeLists.txt +++ b/core/libs/prx/libSceSysmodule/CMakeLists.txt @@ -29,4 +29,13 @@ if(BUILD_TESTING) set_tests_properties(guest_sysmodule PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_sysmodule_invalid_ids_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/InvalidIds.cpp) + target_include_directories(guest_sysmodule_invalid_ids_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sysmodule_invalid_ids_tests PRIVATE libSceSysmodule libkernel libc) + configure_windows_unwind(guest_sysmodule_invalid_ids_tests) + add_test(NAME guest_sysmodule-invalid-ids COMMAND guest_sysmodule_invalid_ids_tests) + if(WIN32) + set_tests_properties(guest_sysmodule-invalid-ids PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceSysmodule/Export.cpp b/core/libs/prx/libSceSysmodule/Export.cpp index 72f6ab0f89..c8dd98a1fb 100644 --- a/core/libs/prx/libSceSysmodule/Export.cpp +++ b/core/libs/prx/libSceSysmodule/Export.cpp @@ -36,6 +36,10 @@ const char* findModuleName(const std::uint32_t id) { return it != kModuleTable.end() ? it->second : nullptr; } +bool isMeasuredRejectedInternalId(const std::uint32_t id) { + return id == 0x8000ffffu || id == 0x80ffff00u; +} + bool isPublicModuleId(const std::uint16_t id) { return std::binary_search(std::begin(kPublicModuleIds), std::end(kPublicModuleIds), id); } @@ -280,6 +284,9 @@ int APS5_VABI sceSysmoduleLoadModuleInternal(std::uint32_t id) { return SCE_SYSMODULE_ERROR_INVALID_VALUE; } if (!findModuleName(id)) { + if (isMeasuredRejectedInternalId(id)) { + return SCE_SYSMODULE_ERROR_INVALID_VALUE; + } throw std::runtime_error(std::string("sceSysmoduleLoadModuleInternal: unknown id ") + std::to_string(id)); } std::lock_guard lock(gMutex); @@ -292,6 +299,9 @@ int APS5_VABI sceSysmoduleUnloadModuleInternal(std::uint32_t id) { return SCE_SYSMODULE_ERROR_INVALID_VALUE; } if (!findModuleName(id)) { + if (isMeasuredRejectedInternalId(id)) { + return SCE_SYSMODULE_ERROR_INVALID_VALUE; + } throw std::runtime_error(std::string("sceSysmoduleUnloadModuleInternal: unknown id ") + std::to_string(id)); } std::lock_guard lock(gMutex); @@ -330,6 +340,9 @@ int APS5_VABI sceSysmoduleIsLoadedInternal(std::uint32_t id) { return SCE_SYSMODULE_ERROR_INVALID_VALUE; } if (!findModuleName(id)) { + if (isMeasuredRejectedInternalId(id) || id == 0x00ffffffu) { + return SCE_SYSMODULE_ERROR_INVALID_VALUE; + } throw std::runtime_error(std::string("sceSysmoduleIsLoadedInternal: unknown id ") + std::to_string(id)); } std::lock_guard lock(gMutex); diff --git a/core/libs/prx/libSceSysmodule/tests/InvalidIds.cpp b/core/libs/prx/libSceSysmodule/tests/InvalidIds.cpp new file mode 100644 index 0000000000..7aa9967657 --- /dev/null +++ b/core/libs/prx/libSceSysmodule/tests/InvalidIds.cpp @@ -0,0 +1,25 @@ +#include "prx/libSceSysmodule/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +namespace { + +constexpr int kInvalidValue = static_cast(0x805A1000); + +void Require(bool value) { if (!value) std::abort(); } + +void CheckInternalIdRejected(std::uint32_t id) { + Require(sceSysmoduleIsLoadedInternal(id) == kInvalidValue); + Require(sceSysmoduleLoadModuleInternal(id) == kInvalidValue); + Require(sceSysmoduleIsLoadedInternal(id) == kInvalidValue); + Require(sceSysmoduleUnloadModuleInternal(id) == kInvalidValue); +} + +} + +int main() { + CheckInternalIdRejected(0x80ffff00); + CheckInternalIdRejected(0x8000ffff); + Require(sceSysmoduleIsLoadedInternal(0x00ffffff) == kInvalidValue); +} diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index 13547a2588..7c645f8957 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -146,7 +146,6 @@ void testLifetime(bool reopen) { } else { check(expectFailure([&] { sceVideoOutWaitVblank(handle); }).find("closed") != std::string::npos, "closed port accepted a vblank wait"); } - AgcDriverUnregisterVideoOutput_nid_postfix(7, gate); if (reopen) sceVideoOutClose(handle); LibcRunShutdown_nid_postfix(); } @@ -223,13 +222,12 @@ void testPs4Buffers() { void testDeferredBuffer() { const int handle = sceVideoOutOpen(255, 0, 0, nullptr); auto cfg = VideoOutDriver::Get().GetConfig(handle); - auto gate = std::make_shared(); - AgcDriverRegisterVideoOutput_nid_postfix(7, gate); - std::array held{0xc004105c, 7, 0xfffffffeu, 1, 0, 0}; - Packet heldPacket{held.data(), 6, 0, {}}; - check(sceAgcDriverSubmitDcb(&heldPacket) == 0, "the gated flip was not accepted"); - std::array flip{0xc004105c, static_cast(handle), 0, 1, 3, 0}; - Packet flipPacket{flip.data(), 6, 0, {}}; + alignas(8) static std::uint32_t reached = 0; + reached = 0; + const auto label = reinterpret_cast(&reached); + std::array flip{0xc0053c00u, 0x13u, static_cast(label), static_cast(label >> 32u), 1u, 0xffffffffu, 0u, + 0xc004105c, static_cast(handle), 0, 1, 3, 0}; + Packet flipPacket{flip.data(), 13, 0, {}}; check(sceAgcDriverSubmitDcb(&flipPacket) == 0, "a GPU flip of a buffer registered before the GPU reaches it was rejected at submit"); std::vector allocation(65536 + 65535); const auto storage = alignedBuffer(allocation); @@ -237,7 +235,7 @@ void testDeferredBuffer() { VideoOutBufferAttribute2 attribute{}; sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 64, 64, 0, 0, 0); check(sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &attribute, 0, nullptr) == 0, "buffer registration failed"); - gate->Release(); + std::atomic_ref(reached).store(1u, std::memory_order_release); { std::unique_lock lock(cfg->mutex); const bool done = cfg->vblankCond.wait_for(lock, std::chrono::seconds(30), [&] { return cfg->failure || cfg->flipStatus.count == 1; }); @@ -248,7 +246,6 @@ void testDeferredBuffer() { AgcDriverWaitIdle_nid_postfix(); sceVideoOutUnregisterBuffers(handle, 0); sceVideoOutClose(handle); - AgcDriverUnregisterVideoOutput_nid_postfix(7, gate); LibcRunShutdown_nid_postfix(); } diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index f4093400fc..896c0c773b 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -1554,7 +1554,7 @@ int APS5_VABI dl_iterate_phdr_nid_postfix(int (*callback)(dl_phdr_info*, std::si [[noreturn]] void APS5_VABI _Exit_nid_postfix(int code); [[noreturn]] void APS5_VABI catchReturnFromMain_nid_postfix(int status); [[noreturn]] void APS5_VABI quick_exit_nid_postfix(int status); -[[noreturn]] void APS5_VABI _ZSt14_Throw_C_errori_nid_postfix(int code); +void APS5_VABI _ZSt14_Throw_C_errori_nid_postfix(int code); [[noreturn]] char* APS5_VABI fgetln_nid_postfix(FileStream*, std::size_t*); [[noreturn]] void APS5_VABI _Assert_nid_postfix(const char* message, const char* location); [[noreturn]] void APS5_VABI _ZSt16_Throw_Cpp_errori_nid_postfix(int code); diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index 0b95021e58..eb8ad0c317 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -147,8 +147,33 @@ unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix() { return value; } -[[noreturn]] void APS5_VABI _ZSt14_Throw_C_errori_nid_postfix(int code) { - throw std::runtime_error("_Throw_C_error: C11 thread error " + std::to_string(code)); +static constexpr int ThrdNomem = 1; +static constexpr int ThrdTimedout = 2; +static constexpr int ThrdBusy = 3; +static constexpr int ThrdError = 4; +static constexpr int ThrdPermission = 5; + +static constexpr int CppDeviceOrResourceBusy = 0; +static constexpr int CppInvalidArgument = 1; +static constexpr int CppOperationNotPermitted = 4; +static constexpr int CppResourceUnavailableTryAgain = 6; + +[[noreturn]] void ThrowCppError_nid_no_patch(int code); + +void APS5_VABI _ZSt14_Throw_C_errori_nid_postfix(int code) { + switch (code) { + case ThrdNomem: + case ThrdTimedout: + ThrowCppError_nid_no_patch(CppResourceUnavailableTryAgain); + case ThrdBusy: + ThrowCppError_nid_no_patch(CppDeviceOrResourceBusy); + case ThrdError: + ThrowCppError_nid_no_patch(CppInvalidArgument); + case ThrdPermission: + ThrowCppError_nid_no_patch(CppOperationNotPermitted); + default: + return; + } } void APS5_VABI _Lockfilelock_nid_postfix(FileStream* stream) { diff --git a/core/libs/prx/libc/src/Threads.cpp b/core/libs/prx/libc/src/Threads.cpp index f2fa003b22..bc7e868a35 100644 --- a/core/libs/prx/libc/src/Threads.cpp +++ b/core/libs/prx/libc/src/Threads.cpp @@ -488,17 +488,8 @@ static_assert(offsetof(GuestPad, condition) == 0x8 && offsetof(GuestPad, mutex) [[noreturn]] void ThrowCppError_nid_no_patch(int code); static void RequireThrd(int result, const char* what) { - switch (result) { - case threadSuccess: - return; - case threadNomem: - case threadTimedout: - ThrowCppError_nid_no_patch(6); - case threadBusy: - ThrowCppError_nid_no_patch(0); - case threadError: - ThrowCppError_nid_no_patch(1); - } + if (result == threadSuccess) return; + _ZSt14_Throw_C_errori_nid_postfix(result); throw std::runtime_error(std::string(what) + " failed with " + std::to_string(result)); } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 9a6d1cd53a..e17286f1c2 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -1675,15 +1675,29 @@ if(BUILD_TESTING) target_include_directories(guest_named_shm_windows_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include) target_link_libraries(guest_named_shm_windows_tests PRIVATE libkernel libc advapi32) configure_windows_unwind(guest_named_shm_windows_tests) - add_test(NAME guest_named_shm_windows COMMAND guest_named_shm_windows_tests) - set_tests_properties(guest_named_shm_windows PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - add_test(NAME guest_named_shm_windows_short_temp COMMAND guest_named_shm_windows_tests --short-temp) - set_tests_properties(guest_named_shm_windows_short_temp PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - add_test(NAME guest_named_shm_windows_long_short_temp COMMAND guest_named_shm_windows_tests --long-short-temp) - set_tests_properties(guest_named_shm_windows_long_short_temp PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") + foreach(part IN ITEMS basic reuse resize_read resize_write) + set(named_shm_suffix "_${part}") + if(part STREQUAL "basic") + set(named_shm_suffix "") + endif() + add_test(NAME guest_named_shm_windows${named_shm_suffix} COMMAND guest_named_shm_windows_tests --case ${part}) + add_test(NAME guest_named_shm_windows_short_temp${named_shm_suffix} COMMAND guest_named_shm_windows_tests --short-temp ${part}) + add_test(NAME guest_named_shm_windows_long_short_temp${named_shm_suffix} COMMAND guest_named_shm_windows_tests --long-short-temp ${part}) + set_tests_properties(guest_named_shm_windows${named_shm_suffix} + guest_named_shm_windows_short_temp${named_shm_suffix} guest_named_shm_windows_long_short_temp${named_shm_suffix} + PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(guest_named_shm_windows_short_temp${named_shm_suffix} guest_named_shm_windows_long_short_temp${named_shm_suffix} + PROPERTIES SKIP_RETURN_CODE 77) + if(part STREQUAL "resize_write") + set_tests_properties(guest_named_shm_windows${named_shm_suffix} + guest_named_shm_windows_short_temp${named_shm_suffix} guest_named_shm_windows_long_short_temp${named_shm_suffix} + PROPERTIES PROCESSORS 3) + elseif(part STREQUAL "reuse" OR part STREQUAL "resize_read") + set_tests_properties(guest_named_shm_windows${named_shm_suffix} + guest_named_shm_windows_short_temp${named_shm_suffix} guest_named_shm_windows_long_short_temp${named_shm_suffix} + PROPERTIES PROCESSORS 2) + endif() + endforeach() set_tests_properties(guest_shm_open PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") add_test_executable(guest_named_shm_windows_mapping_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmWindowsMapping.cpp) diff --git a/core/libs/prx/libkernel/File/include/DescriptorControl.hpp b/core/libs/prx/libkernel/File/include/DescriptorControl.hpp index f2555a2dae..e86e906039 100644 --- a/core/libs/prx/libkernel/File/include/DescriptorControl.hpp +++ b/core/libs/prx/libkernel/File/include/DescriptorControl.hpp @@ -13,7 +13,8 @@ int Fcntl(int fd, int command, std::intptr_t argument); bool IoctlRequestMalformed(std::uint64_t request); int Ioctl(int fd, std::uint64_t request, void* argument); int DuplicateFrom(int fd, int start); -void RememberDescriptor(int fd, int openFlags); +void RememberDescriptor(int fd, int openFlags, bool fixedSizeWrites = false); +bool HasFixedSizeWrites(int fd); void RememberPipe(int fd); void ShareDescriptor(int from, int to); void ForgetDescriptor(int fd); diff --git a/core/libs/prx/libkernel/File/include/File.hpp b/core/libs/prx/libkernel/File/include/File.hpp index e90c7c760d..6250e823f3 100644 --- a/core/libs/prx/libkernel/File/include/File.hpp +++ b/core/libs/prx/libkernel/File/include/File.hpp @@ -24,6 +24,7 @@ std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence); int APS5_VABI sceKernelStat(const char* path, FileStat* sb); int APS5_VABI sceKernelUnlink(const char* path); int APS5_VABI sceKernelFcntl(int d, int command, ...); +std::int64_t PosixPwrite_nid_no_patch(int d, const void* buf, std::size_t nbytes, std::int64_t offset); #ifdef _WIN32 std::int64_t NativePositioned_nid_no_patch(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset, bool write); #endif diff --git a/core/libs/prx/libkernel/File/include/SharedMemory.hpp b/core/libs/prx/libkernel/File/include/SharedMemory.hpp index 8890cc4ab4..43b38712cf 100644 --- a/core/libs/prx/libkernel/File/include/SharedMemory.hpp +++ b/core/libs/prx/libkernel/File/include/SharedMemory.hpp @@ -15,6 +15,7 @@ std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, std::int64_t SeekSharedMemory_nid_no_patch(int descriptor, std::int64_t offset, int whence); int TruncateSharedMemory_nid_no_patch(int descriptor, std::int64_t length); #ifdef _WIN32 +bool LimitSharedMemoryWrite_nid_no_patch(int descriptor, std::size_t& size, std::int64_t offset); int SetSharedMemoryMode_nid_no_patch(int descriptor, int mode); int SharedMemoryDescriptorWritable_nid_no_patch(int descriptor); void TrackSharedMemoryDescriptor_nid_no_patch(int descriptor, bool writable); diff --git a/core/libs/prx/libkernel/File/src/DescriptorControl.cpp b/core/libs/prx/libkernel/File/src/DescriptorControl.cpp index 3e0bc5ff00..c77a275ab9 100644 --- a/core/libs/prx/libkernel/File/src/DescriptorControl.cpp +++ b/core/libs/prx/libkernel/File/src/DescriptorControl.cpp @@ -118,6 +118,7 @@ struct Entry { bool closeOnExec = false; std::shared_ptr status; bool pipe = false; + bool fixedSizeWrites = false; }; std::mutex g_mutex; @@ -597,9 +598,9 @@ int Ioctl(int fd, std::uint64_t request, void* argument) { Unsupported("ioctl", std::string("request ") + hex, *kind, fd); } -void RememberDescriptor(int fd, int openFlags) { +void RememberDescriptor(int fd, int openFlags, bool fixedSizeWrites) { std::lock_guard lock(g_mutex); - g_entries[fd] = Entry{(openFlags & OpenCloseOnExec) != 0, std::make_shared(openFlags & OpenStatusFlags)}; + g_entries[fd] = Entry{.closeOnExec = (openFlags & OpenCloseOnExec) != 0, .status = std::make_shared(openFlags & OpenStatusFlags), .fixedSizeWrites = fixedSizeWrites}; #ifdef _WIN32 GuestFileLockForget_nid_no_patch(fd); #endif @@ -614,8 +615,9 @@ void RememberPipe(int fd) { void ShareDescriptor(int from, int to) { File::ShareArchivedFile(from, to); std::lock_guard lock(g_mutex); - const auto& source = Materialize(from); - g_entries[to] = Entry{false, source.status, source.pipe}; + auto copy = Materialize(from); + copy.closeOnExec = false; + g_entries[to] = std::move(copy); #ifdef _WIN32 File::SharePositionedTransfer(from, to); GuestFileLockShare_nid_no_patch(from, to); @@ -628,6 +630,12 @@ void ForgetDescriptor(int fd) { g_entries.erase(fd); } +bool HasFixedSizeWrites(int fd) { + std::lock_guard lock(g_mutex); + const auto found = g_entries.find(fd); + return found != g_entries.end() && found->second.fixedSizeWrites; +} + std::uint64_t g_synchronousWrites = 0; void SyncWrites(int fd) { diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 068ef2304d..0dcf14e482 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -84,6 +84,11 @@ static int NativeWrite(int fd, const void* buf, std::size_t n) { throw std::runtime_error("sceKernelWrite: nbytes exceeds platform limit"); } const auto positioned = File::LockPositionedTransfer(fd); + if (File::HasFixedSizeWrites(fd)) { + const auto position = ::_lseeki64(fd, 0, SEEK_CUR); + if (position < 0 || !File::LimitSharedMemoryWrite_nid_no_patch(fd, n, position)) return -1; + if (n == 0) return 0; + } const auto shared = File::WriteSharedMemory_nid_no_patch(fd, buf, n); if (shared != -2) return static_cast(shared); if (File::Appends(fd)) { diff --git a/core/libs/prx/libkernel/File/src/SharedMemory.cpp b/core/libs/prx/libkernel/File/src/SharedMemory.cpp index e2c2a05e24..ce4d8c67fc 100644 --- a/core/libs/prx/libkernel/File/src/SharedMemory.cpp +++ b/core/libs/prx/libkernel/File/src/SharedMemory.cpp @@ -5,6 +5,7 @@ #include "prx/libkernel/File/include/SharedMemory.hpp" #include "prx/libc/include/CreationMask.hpp" #include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/File/include/DescriptorControl.hpp" #include #include @@ -1050,7 +1051,12 @@ extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int m } if (accessMode == SCE_KERNEL_O_RDONLY) return Fail(GuestInvalid); const int descriptor = CreateAnonymousFile(); - if (descriptor >= 0) return descriptor; + if (descriptor >= 0) { +#ifdef _WIN32 + File::RememberDescriptor(descriptor, flags, true); +#endif + return descriptor; + } const int error = errno; if (const int guest = GuestError(error)) return Fail(guest); throw std::runtime_error("shm_open: creating the host anonymous file failed, errno=" + std::to_string(error)); diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 5c682df352..e104e3553f 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -303,11 +303,23 @@ void File::SharePositionedTransfer(int from, int to) { if (!source) source = std::make_shared(); g_positionedLocks[to] = source; } +bool File::LimitSharedMemoryWrite_nid_no_patch(int descriptor, std::size_t& size, std::int64_t offset) { + if (offset < 0) { errno = EINVAL; return false; } + const auto length = WithoutParameterHandler([descriptor] { return ::_filelengthi64(descriptor); }); + if (length < 0) return false; + size = offset >= length ? 0 : static_cast(std::min(size, static_cast(length - offset))); + return true; +} + static std::int64_t NativePositionedTransfer(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset, bool write) { if (nbytes > static_cast(std::numeric_limits::max())) { throw std::runtime_error("NativePositioned: nbytes exceeds platform limit"); } if (write) { + if (File::HasFixedSizeWrites(descriptor)) { + if (!File::LimitSharedMemoryWrite_nid_no_patch(descriptor, nbytes, offset)) return -1; + if (nbytes == 0) return 0; + } const auto result = File::PwriteSharedMemory_nid_no_patch(descriptor, buf, nbytes, offset); if (result != -2) return result; } @@ -373,6 +385,11 @@ static std::int64_t NativeTransferBuffer(int descriptor, void* buf, std::size_t throw std::runtime_error("NativeTransfer: nbytes exceeds platform limit"); } if (write) { + if (File::HasFixedSizeWrites(descriptor)) { + const auto position = WithoutParameterHandler([descriptor] { return ::_lseeki64(descriptor, 0, SEEK_CUR); }); + if (position < 0 || !File::LimitSharedMemoryWrite_nid_no_patch(descriptor, nbytes, position)) return -1; + if (nbytes == 0) return 0; + } const auto result = File::WriteSharedMemory_nid_no_patch(descriptor, buf, nbytes); if (result != -2) return result; if (File::Appends(descriptor)) { @@ -1158,6 +1175,10 @@ int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t off } int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_t nbytes, int64_t offset) { + return PosixPwrite_nid_no_patch(d, buf, nbytes, offset); +} + +std::int64_t PosixPwrite_nid_no_patch(int d, const void* buf, std::size_t nbytes, std::int64_t offset) { if (File::IsVirtualDirectoryDescriptor(d)) return PosixFailure(GUEST_EISDIR); if (ExceedsTransferLimit(nbytes)) return PosixFailure(GUEST_EINVAL); if (const int error = PositionedArgumentError(d, true, buf == nullptr && nbytes != 0, offset)) return PosixFailure(error); diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index d3f44c6f4c..0cfb2a93cb 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -338,19 +338,22 @@ int APS5_VABI ptrace_nid_postfix(void) { return 0; } -int APS5_VABI pwrite_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI reboot_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI revoke_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI revoke_nid_postfix(const char* path) { + constexpr std::uint16_t typeMask = 0170000; + constexpr std::uint16_t characterDevice = 0020000; + constexpr int guestEinval = 22; + FileStat status{}; + if (stat_nid_postfix(path, &status) != 0) return -1; + if ((status.st_mode & typeMask) != characterDevice) { + *__error_nid_postfix() = guestEinval; + return -1; + } + NotImplemented_nid_no_patch("revoke of a character device"); } int APS5_VABI rfork_thread_nid_postfix(void) { diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index 2ed2882ec6..399d0f503c 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -34,6 +34,7 @@ #include "prx/libkernel/File/include/DescriptorControl.hpp" #include "prx/libc/include/CreationMask.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" +#include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/File/include/SharedMemory.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" @@ -446,6 +447,10 @@ std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t mask) { return File::ExchangeCreationMask_nid_no_patch(mask); } +std::int64_t APS5_VABI pwrite_nid_postfix(int d, const void* buf, std::size_t nbytes, std::int64_t offset) { + return PosixPwrite_nid_no_patch(d, buf, nbytes, offset); +} + int APS5_VABI sceKernelGetOpenPsIdForSystem(void* id) { if (id == nullptr) return GuestEinval; return sceKernelGetOpenPsId(id); diff --git a/core/libs/prx/libkernel_sys/include/Api.hpp b/core/libs/prx/libkernel_sys/include/Api.hpp index 0d1da8b729..215465c7f8 100644 --- a/core/libs/prx/libkernel_sys/include/Api.hpp +++ b/core/libs/prx/libkernel_sys/include/Api.hpp @@ -163,9 +163,9 @@ int APS5_VABI pthread_suspend_user_context_np_nid_postfix(void); int APS5_VABI pthread_switch_add_np_nid_postfix(void); int APS5_VABI pthread_switch_delete_np_nid_postfix(void); int APS5_VABI ptrace_nid_postfix(void); -int APS5_VABI pwrite_nid_postfix(void); +std::int64_t APS5_VABI pwrite_nid_postfix(int d, const void* buf, std::size_t nbytes, std::int64_t offset); int APS5_VABI reboot_nid_postfix(void); -int APS5_VABI revoke_nid_postfix(void); +int APS5_VABI revoke_nid_postfix(const char* path); int APS5_VABI rfork_thread_nid_postfix(void); int APS5_VABI rtld_printf_nid_postfix(void); int APS5_VABI sceCoredumpDebugForceCoredumpOnAppClose(void); diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index b3ae2b5f17..58a0561e82 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -1961,6 +1961,31 @@ void TestAt9RunDecodesWholeInput(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +void TestStrError() { + const char* const expected[] = { + "context does not exist", "instance does not exist", "batch does not exist", "invalid parameter", + "not enough memory", "not enough resources", "codec is not supported", + "codec is already registered in the context", "codec is not registered in the context", + "wrong revision in the flags", "flag is not supported", "busy", "priority is invalid", "in progress", + "operation cannot be performed at the moment", "malformed batch", "error at job creation", + "invalid opcode", "priority violation", "buffer is too big", "invalid address", "batch was cancelled", + "specified memory area is already registered", "specified memory area is not registered", + }; + static_assert(sizeof(expected) / sizeof(expected[0]) == 24); + for (std::uint32_t i = 0; i < 24; ++i) { + const int code = static_cast(0x80930002u + i); + const char* text = sceAjmStrError(code); + Require(text != nullptr && std::strcmp(text, expected[i]) == 0); + Require(sceAjmStrError(code) == text); + } + const char* ok = sceAjmStrError(0); + Require(ok != nullptr && std::strcmp(ok, "ok") == 0); + for (std::uint32_t code : {0x80930000u, 0x80930001u, 0x8093001au, 0x8093001bu, 0x8092ffffu, 1u, 0xffffffffu, 0x80020001u, 0x8a6e0001u, 0x7fffffffu}) { + const char* text = sceAjmStrError(static_cast(code)); + Require(text != nullptr && std::strcmp(text, "(unknown)") == 0); + } +} + void TestAt9FlagRules(std::uint32_t context) { constexpr std::uint64_t runMultipleFrames = 1ull << 12; constexpr std::uint64_t sidebandStream = 1ull << 47; @@ -2643,6 +2668,7 @@ int main(int argc, char** argv) { TestAt9GaplessSegments(context); TestAt9GaplessResetRestartsTotal(context); TestAt9RunDecodesWholeInput(context); + TestStrError(); TestAt9FlagRules(context); TestPcmResample(context); TestConsoleBufferJobs(context); diff --git a/core/libs/tests/GuestAmprCppError.S b/core/libs/tests/GuestAmprCppError.S index f5e0769766..19e8dc9c18 100644 --- a/core/libs/tests/GuestAmprCppError.S +++ b/core/libs/tests/GuestAmprCppError.S @@ -17,7 +17,8 @@ GuestCatch: .cfi_def_cfa_register %rbp subq $16, %rsp .Lcatch_call: - call _ZSt16_Throw_Cpp_errori_nid_postfix + movq FixtureThrow(%rip), %rax + call *%rax .Lcatch_call_end: ud2 .Lcatch_landing: diff --git a/core/libs/tests/GuestAmprCppError.cpp b/core/libs/tests/GuestAmprCppError.cpp index 62d9843363..10ca07b1e1 100644 --- a/core/libs/tests/GuestAmprCppError.cpp +++ b/core/libs/tests/GuestAmprCppError.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ extern const unsigned char _ZTISt12system_error_nid_postfix[]; void APS5_VABI GuestCatch(int code); +void (APS5_VABI *FixtureThrow)(int) = nullptr; const void* FixtureCatchType = nullptr; const void* FixtureOtherType = nullptr; const Expected* FixtureExpected = nullptr; @@ -129,6 +131,7 @@ extern "C" void APS5_VABI FixtureInspect(void* object) { } static void Catch(const void* type, int code) { + FixtureThrow = _ZSt16_Throw_Cpp_errori_nid_postfix; FixtureCatchType = type; FixtureExpected = &Errors[code]; const auto before = FixtureInspected; @@ -136,6 +139,15 @@ static void Catch(const void* type, int code) { Require(FixtureInspected == before + 1, 2); } +static void CatchCError(const void* type, int code, int cppIndex) { + FixtureThrow = _ZSt14_Throw_C_errori_nid_postfix; + FixtureCatchType = type; + FixtureExpected = &Errors[cppIndex]; + const auto before = FixtureInspected; + GuestCatch(code); + Require(FixtureInspected == before + 1, 5); +} + static void CheckOutOfRange(int code) { bool threw = false; try { @@ -147,6 +159,15 @@ static void CheckOutOfRange(int code) { Require(threw, 3); } +static void CheckCErrorReturns(int code) { + try { + _ZSt14_Throw_C_errori_nid_postfix(code); + } catch (...) { + std::fprintf(stderr, "guest _Throw_C_error unexpectedly threw for %d\n", code); + FixtureFail(6); + } +} + int main() { const std::array api {}; ApplicationHeapRegister_nid_no_patch(api.data()); @@ -156,6 +177,15 @@ int main() { Catch(_ZTISt9exception_nid_postfix, 6); CheckOutOfRange(-1); CheckOutOfRange(static_cast(std::size(Errors))); - std::puts("guest _Throw_Cpp_error system_error, generic category and range: PASS"); + CatchCError(_ZTISt12system_error_nid_postfix, 1, 6); + CatchCError(_ZTISt12system_error_nid_postfix, 2, 6); + CatchCError(_ZTISt12system_error_nid_postfix, 3, 0); + CatchCError(_ZTISt12system_error_nid_postfix, 4, 1); + CatchCError(_ZTISt13runtime_error_nid_postfix, 3, 0); + CatchCError(_ZTISt9exception_nid_postfix, 4, 1); + const std::array returnCodes {std::numeric_limits::min(), -1, 0, 6, std::numeric_limits::max()}; + for (const int code : returnCodes) CheckCErrorReturns(code); + CatchCError(_ZTISt12system_error_nid_postfix, 5, 4); + std::puts("guest _Throw_Cpp_error and _Throw_C_error system_error, generic category and range: PASS"); return 0; } diff --git a/core/libs/tests/GuestKernelSysPwrite.cpp b/core/libs/tests/GuestKernelSysPwrite.cpp new file mode 100644 index 0000000000..ade46e9e23 --- /dev/null +++ b/core/libs/tests/GuestKernelSysPwrite.cpp @@ -0,0 +1,172 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#else +#include +#endif + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "Kernel sys pwrite check failed at line %u\n", location.line()); + std::abort(); + } +} + +static void* LoadLibraryFile(const std::filesystem::path& path) { +#ifdef _WIN32 + auto* module = reinterpret_cast(LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH)); +#else + auto* module = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!module) std::fprintf(stderr, "%s\n", dlerror()); +#endif + Require(module != nullptr); +#ifdef _WIN32 + std::wstring loaded(32768, L'\0'); + const auto length = GetModuleFileNameW(static_cast(module), loaded.data(), static_cast(loaded.size())); + Require(length != 0 && length < loaded.size()); + loaded.resize(length); + Require(std::filesystem::equivalent(path, std::filesystem::path(loaded))); +#endif + return module; +} + +template +static Function LoadFunction(void* module, const char* name, bool patched, const char* unpatched = nullptr) { + const auto symbol = patched ? Nid::ComputeNid(name, "") : unpatched ? std::string(unpatched) : std::string(name) + "_nid_postfix"; +#ifdef _WIN32 + const auto function = reinterpret_cast(reinterpret_cast(GetProcAddress(static_cast(module), symbol.c_str()))); +#else + const auto function = reinterpret_cast(dlsym(module, symbol.c_str())); +#endif + if (!function) std::fprintf(stderr, "Missing symbol %s\n", symbol.c_str()); + Require(function != nullptr); + return function; +} + +int main(int argc, char** argv) { + Require(argc == 3); + const auto directory = std::filesystem::absolute(argv[1]); + const bool patched = std::strcmp(argv[2], "patched") == 0; + auto* sys = LoadLibraryFile(directory / "libkernel_sys.prx"); + auto* kernel = LoadLibraryFile(directory / "libkernel.prx"); + auto* libc = LoadLibraryFile(directory / "libc.prx"); + using Pwrite = std::int64_t (APS5_VABI*)(int, const void*, std::size_t, std::int64_t); + const auto writeAt = LoadFunction(sys, "pwrite", patched); + const auto kernelWriteAt = LoadFunction(kernel, "pwrite", patched, "pwrite_nid_disambig1_nid_postfix"); + Require(writeAt != kernelWriteAt); + const auto open = LoadFunction(kernel, "open", patched); + const auto close = LoadFunction(kernel, "close", patched); + const auto duplicate = LoadFunction(kernel, "dup", patched); + const auto duplicateTo = LoadFunction(kernel, "dup2", patched); + const auto write = LoadFunction(kernel, "write", patched); + const auto seek = LoadFunction(kernel, "lseek", patched); + const auto readAt = LoadFunction(kernel, "pread", patched); + const auto error = LoadFunction(libc, "__error", patched); + const auto shmOpen = LoadFunction(kernel, "shm_open", patched); + const auto truncate = LoadFunction(kernel, "ftruncate", patched); + const auto pipe = LoadFunction(kernel, "pipe", patched); + const auto socket = LoadFunction(kernel, "socket", patched); + const auto root = std::filesystem::path("anyps5-kernel-sys-pwrite-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + const auto path = (root / "data").string(); + { std::ofstream stream(path, std::ios::binary); stream << "abcdefgh"; Require(stream.good()); } +#ifdef _WIN32 + const auto sparseFile = CreateFileW(std::filesystem::path(path).c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, 0, nullptr); + Require(sparseFile != INVALID_HANDLE_VALUE); + DWORD returned = 0; + Require(DeviceIoControl(sparseFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &returned, nullptr) != FALSE); + Require(CloseHandle(sparseFile) != FALSE); +#endif + const int descriptor = open(path.c_str(), SCE_KERNEL_O_RDWR, 0); + Require(descriptor >= 0); + Require(seek(descriptor, 3, SEEK_SET) == 3); + *error() = 13; + Require(writeAt(descriptor, "XY", 2, 5) == 2 && *error() == 13); + Require(seek(descriptor, 0, SEEK_CUR) == 3); + char contents[8]{}; + Require(readAt(descriptor, contents, sizeof(contents), 0) == 8 && std::memcmp(contents, "abcdeXYh", 8) == 0); + constexpr std::int64_t largeOffset = (std::int64_t{1} << 32) + 17; + Require(writeAt(descriptor, "Z", 1, largeOffset) == 1); + Require(std::filesystem::file_size(path) == largeOffset + 1); + Require(readAt(descriptor, contents, 2, largeOffset - 1) == 2 && contents[0] == 0 && contents[1] == 'Z'); + Require(readAt(descriptor, contents, 8, 0) == 8 && std::memcmp(contents, "abcdeXYh", 8) == 0); + Require(writeAt(descriptor, nullptr, 0, 0) == 0); + Require(writeAt(descriptor, nullptr, 1, 0) == -1 && *error() == 14); + Require(writeAt(descriptor, "x", 1, -1) == -1 && *error() == 22); + Require(writeAt(descriptor, nullptr, 0, -5) == -1 && *error() == 22); + Require(writeAt(descriptor, nullptr, 1, -1) == -1 && *error() == 22); + Require(writeAt(-1, nullptr, 1, -1) == -1 && *error() == 9); + Require(writeAt(-1, "x", 1, 0) == -1 && *error() == 9); + Require(writeAt(-1, nullptr, 0, 0) == -1 && *error() == 9); + const auto oversized = static_cast(std::numeric_limits::max()) + 1; + Require(writeAt(descriptor, "x", oversized, 0) == -1 && *error() == 22); + Require(seek(descriptor, 0, SEEK_CUR) == 3); + Require(truncate(descriptor, 8) == 0); + Require(close(descriptor) == 0); +#ifndef _WIN32 + const int append = open(path.c_str(), SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_APPEND, 0); + Require(append >= 0 && seek(append, 2, SEEK_SET) == 2); + Require(writeAt(append, "!", 1, 0) == 1 && seek(append, 0, SEEK_CUR) == 2); + Require(std::filesystem::file_size(path) == 9); + Require(close(append) == 0); +#endif + const int readOnly = open(path.c_str(), SCE_KERNEL_O_RDONLY, 0); + Require(readOnly >= 0); + Require(writeAt(readOnly, "x", 1, 0) == -1 && *error() == 9); + Require(writeAt(readOnly, nullptr, 1, -1) == -1 && *error() == 9); +#ifndef _WIN32 + Require(readAt(readOnly, contents, 1, 8) == 1 && contents[0] == '!'); +#endif + Require(close(readOnly) == 0); + int descriptors[2]{}; + Require(pipe(descriptors) == 0); + Require(writeAt(descriptors[1], "x", 1, 0) == -1 && *error() == 29); + Require(close(descriptors[0]) == 0 && close(descriptors[1]) == 0); + const int sock = socket(2, 1, 0); + Require(sock >= 0); + Require(writeAt(sock, "x", 1, 0) == -1 && *error() == 29); + Require(close(sock) == 0); + Require(writeAt(sock, "x", 1, 0) == -1 && *error() == 9); + const int shared = shmOpen(reinterpret_cast(1), 0x2, 0600); + Require(shared >= 0 && truncate(shared, 0x4000) == 0); + Require(seek(shared, 7, SEEK_SET) == 7); + Require(writeAt(shared, "shared", 6, 0x1000) == 6); + Require(readAt(shared, contents, 6, 0x1000) == 6 && std::memcmp(contents, "shared", 6) == 0); + Require(seek(shared, 0, SEEK_CUR) == 7); + Require(writeAt(shared, "tail", 4, 0x3ffe) == 2); + Require(readAt(shared, contents, 4, 0x3ffe) == 2 && std::memcmp(contents, "ta", 2) == 0); + Require(seek(shared, 0, SEEK_CUR) == 7); + Require(seek(shared, 0, SEEK_END) == 0x4000); + const int copy = duplicate(shared); + Require(copy >= 0 && close(shared) == 0); + Require(kernelWriteAt(copy, "edge", 4, 0x3ffe) == 2); + Require(writeAt(copy, "x", 1, 0x4000) == 0 && seek(copy, 0, SEEK_END) == 0x4000); + Require(seek(copy, 0x3fff, SEEK_SET) == 0x3fff); + Require(write(copy, "ab", 2) == 1 && write(copy, "x", 1) == 0); + Require(seek(copy, 0, SEEK_END) == 0x4000); + Require(close(copy) == 0); + const int ordinary = open(path.c_str(), SCE_KERNEL_O_RDWR, 0); + Require(ordinary >= 0 && duplicateTo(ordinary, shared) == shared); + Require(writeAt(shared, "grow", 4, 16) == 4 && seek(shared, 0, SEEK_END) == 20); + Require(close(shared) == 0); + if (ordinary != shared) Require(close(ordinary) == 0); + std::filesystem::remove_all(root); + return 0; +} diff --git a/core/libs/tests/GuestKernelSysRevoke.cpp b/core/libs/tests/GuestKernelSysRevoke.cpp new file mode 100644 index 0000000000..9a40394be2 --- /dev/null +++ b/core/libs/tests/GuestKernelSysRevoke.cpp @@ -0,0 +1,53 @@ +#include "prx/libc/include/GuestErrno.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel_sys/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int Enoent = 2; +constexpr int Efault = 14; +constexpr int Enotdir = 20; +constexpr int Einval = 22; +constexpr int Enametoolong = 63; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "check failed at line %u\n", static_cast(location.line())); + std::exit(1); +} + +int Error() { + return *__error_nid_postfix(); +} + +bool Fails(const std::string& path, int error) { + return revoke_nid_postfix(path.c_str()) == -1 && Error() == error; +} + +} + +int main() { + const auto root = std::filesystem::path("anyps5-revoke-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + const auto file = (root / "file.txt").string(); + std::ofstream(file).put('x'); + + Require(Fails(file, Einval)); + Require(Fails(root.string(), Einval)); + Require(Fails((root / "missing").string(), Enoent)); + Require(Fails(file + "/below", Enotdir)); + Require(Fails("", Enoent)); + Require(Fails(std::string(1024, 'a'), Enametoolong)); + Require(revoke_nid_postfix(nullptr) == -1 && Error() == Efault); + + std::filesystem::remove_all(root); + std::puts("guest_kernel_sys_revoke passed"); + return 0; +} diff --git a/core/libs/tests/GuestNamedShmWindows.cpp b/core/libs/tests/GuestNamedShmWindows.cpp index 2719e11beb..516f6e72bb 100644 --- a/core/libs/tests/GuestNamedShmWindows.cpp +++ b/core/libs/tests/GuestNamedShmWindows.cpp @@ -240,9 +240,37 @@ void VerifyResizeReadPosition(const std::string& name) { RequireNoUnlinkedFiles(name); } -void Run() { - VerifyAnonymousDescriptorClassification(); +void Run(std::string_view selection) { + if (selection == "all") { + for (const auto part : {"basic", "reuse", "resize_read", "resize_write"}) Run(part); + return; + } const std::string base = "/anyps5-windows-named-" + std::to_string(::GetCurrentProcessId()) + "-" + std::to_string(::GetTickCount64()); + if (selection == "reuse") { + const std::string name = base + "-reuse"; + const int descriptor = shm_open_nid_postfix(name.c_str(), ReadWrite | Create | Exclusive, 0600); + Require(descriptor >= 0 && ftruncate_nid_postfix(descriptor, 64) == 0, "creating the descriptor-reuse object failed"); + VerifyConcurrentDescriptorReuse(name); + Require(shm_unlink_nid_postfix(name.c_str()) == 0 && close_nid_postfix(descriptor) == 0, "descriptor-reuse cleanup failed"); + RequireNoUnlinkedFiles(name); + return; + } + if (selection == "resize_read") { + VerifyResizeReadPosition(base + "-resize-read"); + return; + } + if (selection == "resize_write") { + const std::string raceName = base + "-cross-process-race"; + const int raceDescriptor = shm_open_nid_postfix(raceName.c_str(), ReadWrite | Create | Exclusive, 0600); + Require(raceDescriptor >= 0 && ftruncate_nid_postfix(raceDescriptor, RaceSize) == 0, + "cross-process race object creation failed"); + VerifyCrossProcessResizeWrite(raceName, raceDescriptor); + Require(close_nid_postfix(raceDescriptor) == 0, "closing cross-process race descriptor failed"); + RequireNoUnlinkedFiles(raceName); + return; + } + Require(selection == "basic", "unknown named shared-memory test selection"); + VerifyAnonymousDescriptorClassification(); const std::string name = base + "-lifecycle"; Require(shm_unlink_nid_postfix(name.c_str()) == -1 && *__error_nid_postfix() == 2, "new test name unexpectedly existed"); @@ -252,7 +280,12 @@ void Run() { Require(original >= 0 && *__error_nid_postfix() == 73, "exclusive creation failed or changed guest errno"); VerifyUntrackedDescriptor(original); Require(ftruncate_nid_postfix(original, 64) == 0, "sizing the named object failed"); - VerifyConcurrentDescriptorReuse(name); + const int reopened = shm_open_nid_postfix(name.c_str(), ReadWrite, 0); + Require(reopened >= 0 && File::IsSharedMemoryDescriptor_nid_no_patch(reopened), "reopened named descriptor is not tracked"); + Require(pwrite_nid_disambig1_nid_postfix(reopened, "x", 1, 64) == 0, "positioned write at the named-object boundary extended it"); + struct _stat64 bounded{}; + Require(::_fstat64(reopened, &bounded) == 0 && bounded.st_size == 64, "positioned write changed the named-object size"); + Require(close_nid_postfix(reopened) == 0, "closing reopened named descriptor failed"); const char originalBytes[] = "original"; Require(pwrite_nid_disambig1_nid_postfix(original, originalBytes, sizeof(originalBytes), 0) == sizeof(originalBytes), "write to named object failed"); @@ -338,7 +371,6 @@ void Run() { close_nid_postfix(duplicateReplacement) == 0 && close_nid_postfix(duplicate) == 0, "cleaning up dup2 replacement descriptors failed"); RequireNoUnlinkedFiles(replacementName); - VerifyResizeReadPosition(base + "-resize-read"); const std::string concurrentName = base + "-exclusive-race"; constexpr int contenderCount = 12; @@ -365,13 +397,6 @@ void Run() { Require(created == 1, "exclusive create race produced more or fewer than one winner"); Require(shm_unlink_nid_postfix(concurrentName.c_str()) == 0, "exclusive-race cleanup failed"); - const std::string raceName = base + "-cross-process-race"; - const int raceDescriptor = shm_open_nid_postfix(raceName.c_str(), ReadWrite | Create | Exclusive, 0600); - Require(raceDescriptor >= 0 && ftruncate_nid_postfix(raceDescriptor, RaceSize) == 0, - "cross-process race object creation failed"); - VerifyCrossProcessResizeWrite(raceName, raceDescriptor); - Require(close_nid_postfix(raceDescriptor) == 0, "closing cross-process race descriptor failed"); - const std::string permissionsName = base + "-permissions"; const int restricted = shm_open_nid_postfix(permissionsName.c_str(), ReadWrite | Create | Exclusive, 0400); Require(restricted >= 0, "read-only mode creation failed"); @@ -501,7 +526,7 @@ void RemovePrivateDirectory(const std::filesystem::path& directory) { throw std::runtime_error("removing the private temporary directory failed"); } -int RunWithShortTempPath(bool longPath) { +int RunWithShortTempPath(bool longPath, std::string_view selection) { wchar_t tempBuffer[32768]{}; const DWORD tempLength = ::GetTempPathW(static_cast(std::size(tempBuffer)), tempBuffer); if (tempLength == 0 || tempLength >= std::size(tempBuffer)) @@ -593,7 +618,7 @@ int RunWithShortTempPath(bool longPath) { const DWORD executableLength = ::GetModuleFileNameW(nullptr, executable, static_cast(std::size(executable))); if (executableLength == 0 || executableLength >= std::size(executable)) throw std::runtime_error("locating the Windows shared-memory test executable failed"); - std::wstring command = L"\"" + std::wstring(executable, executableLength) + L"\" --short-temp-child"; + std::wstring command = L"\"" + std::wstring(executable, executableLength) + L"\" --short-temp-child " + std::wstring(selection.begin(), selection.end()); auto environment = EnvironmentWithTempPath(DosPath(shortRoot).native()); STARTUPINFOW startup{}; startup.cb = sizeof(startup); @@ -626,15 +651,18 @@ int main(int argumentCount, char** arguments) { try { if (argumentCount == 3 && std::string_view(arguments[1]) == "--race") return RunResizeWriteRace(arguments[2]) ? 0 : 1; - if (argumentCount == 2 && std::string_view(arguments[1]) == "--short-temp") - return RunWithShortTempPath(false); - if (argumentCount == 2 && std::string_view(arguments[1]) == "--long-short-temp") - return RunWithShortTempPath(true); - if (argumentCount == 2 && std::string_view(arguments[1]) == "--short-temp-child") { - Run(); - return 0; + const std::string_view selection = argumentCount == 3 ? arguments[2] : "all"; + Require(selection == "all" || selection == "basic" || selection == "reuse" || selection == "resize_read" || selection == "resize_write", + "unknown named shared-memory test selection"); + if (argumentCount == 2 || argumentCount == 3) { + const std::string_view option = arguments[1]; + if (option == "--short-temp") return RunWithShortTempPath(false, selection); + if (option == "--long-short-temp") return RunWithShortTempPath(true, selection); + Require(option == "--case" || option == "--short-temp-child", "unknown named shared-memory test option"); + } else { + Require(argumentCount == 1, "invalid named shared-memory test arguments"); } - Run(); + Run(selection); return 0; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); diff --git a/core/libs/tests/GuestSysmodule.cpp b/core/libs/tests/GuestSysmodule.cpp index 20c7a7235c..0b5218ab5e 100644 --- a/core/libs/tests/GuestSysmodule.cpp +++ b/core/libs/tests/GuestSysmodule.cpp @@ -121,7 +121,7 @@ void CheckUnwindInfo() { #endif std::uint16_t UnknownModuleId() { - for (std::uint32_t id = 0xffff; id != 0; --id) { + for (std::uint32_t id = 0xff; id != 0; --id) { if (kModuleTable.find(id) == kModuleTable.end()) return static_cast(id); } std::abort(); @@ -274,7 +274,7 @@ int main() { Require(result == 0x5a5a5a5a); } - Require(ThrowsRuntimeError([] { sceSysmoduleIsLoadedInternal(0x00ffffffu); })); + Require(sceSysmoduleIsLoadedInternal(0x00ffffffu) == kInvalidValue); Require(sceSysmoduleIsLoadedInternal(kFiberModuleId) == kModuleUnloaded); Require(sceSysmoduleIsLoaded(kFiberModuleId) == kModuleUnloaded); Require(sceSysmoduleLoadModuleInternal(kFiberModuleId) == 0); diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index cacb89d684..4976bebf47 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -955,6 +955,11 @@ if(BUILD_TESTING) add_asan_test_options(input_magic max_allocation_size_mb=512) add_test(NAME input_preservation COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_input_preservation.py $) + foreach(mode linux windows controls) + add_test(NAME patch_input_preservation_${mode} COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_patch_input_preservation.py $ ${mode}) + set_tests_properties(patch_input_preservation_${mode} PROPERTIES TIMEOUT 20) + endforeach() add_test(NAME output_collisions COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_output_collisions.py $) add_test(NAME secondary_output_collisions COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 9eb2f4c633..d2b4cd113f 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -382,6 +382,8 @@ int Run(const int argc, char* argv[]) { for (const auto& artifact : guestArtifacts) if (SameOutputFile(artifact.SourcePath, path)) throw Domain::RelinkerException(std::string(kind) + " output would overwrite an input module: " + Io::Utf8Path(path)); + if (!args.noPatches && std::filesystem::is_regular_file(patchFile) && SameOutputFile(patchFile, path)) + throw Domain::RelinkerException(std::string(kind) + " output would overwrite the patch file: " + Io::Utf8Path(path)); }; for (std::size_t index = 1; index < executables.size(); ++index) checkOutputInputs(Io::NativePath(executables[index].OutputPath), "Executable"); if (desktopEntry) { @@ -411,11 +413,7 @@ int Run(const int argc, char* argv[]) { } checkOutputInputs(Io::NativePath(absPath), "Executable"); - if (!manifestPath.empty()) { - checkOutputInputs(manifestPath, "Patch manifest"); - if (std::filesystem::exists(manifestPath) && std::filesystem::exists(patchFile) && std::filesystem::equivalent(manifestPath, patchFile)) - throw Domain::RelinkerException("Patch manifest output would overwrite the patch file: " + Io::Utf8Path(manifestPath)); - } + if (!manifestPath.empty()) checkOutputInputs(manifestPath, "Patch manifest"); for (const auto& artifact : guestArtifacts) checkOutputInputs(artifact.Path, "Guest module"); std::vector outputPaths; diff --git a/core/relinker/relinker/tests/test_patch_input_preservation.py b/core/relinker/relinker/tests/test_patch_input_preservation.py new file mode 100644 index 0000000000..07631b08bd --- /dev/null +++ b/core/relinker/relinker/tests/test_patch_input_preservation.py @@ -0,0 +1,87 @@ +import os +from pathlib import Path +import subprocess +import sys +import tempfile + +from test_game_patches import patch_site, patch_text +from test_guest_intel_trampolines import main_fixture +from test_secondary_output_collisions import prepare_secondary + + +def main(): + relinker = Path(sys.argv[1]).resolve() + mode = sys.argv[2] if len(sys.argv) > 2 else "all" + formats = (False, True) if mode == "all" else (mode == "windows",) + text = patch_text(*patch_site(main_fixture()), target="input.elf").encode() + with tempfile.TemporaryDirectory(prefix="anyps5-patch-input-") as directory: + work = Path(directory) + for windows in (() if mode == "controls" else formats): + command, outputs, _ = prepare_secondary(work / f"control-{windows}", windows) + config = outputs["executable"].parent / "relinker-patches.toml" + config.write_bytes(text) + result = subprocess.run([str(relinker), *command], cwd=config.parent, + capture_output=True, text=True, timeout=20) + assert result.returncode == 0 and config.read_bytes() == text, (result.stdout, result.stderr) + outputs["manifest"] = config.parent / "output.patches.toml" + aliases = ("symlink", "direct", "hardlink") + cases = [(name, "symlink" if name == "executable" else aliases[index % len(aliases)]) + for index, name in enumerate(outputs)] + cases.extend(("executable", alias) for alias in ("direct", "hardlink")) + for name, alias in cases: + case = work / f"{windows}-{name}-{alias}" + command, paths, inputs = prepare_secondary(case, windows) + paths["manifest"] = case / "output.patches.toml" + target = paths[name] + config = target if alias == "direct" else case / "relinker-patches.toml" + config.write_bytes(text) + if alias == "hardlink": + os.link(config, target) + elif alias == "symlink": + try: + target.symlink_to(config) + except OSError as error: + if os.name != "nt" or error.winerror != 1314: + raise + print("Windows symlink privilege unavailable; checking the output through its direct path") + alias = "direct" + config = target + config.write_bytes(text) + for path in paths.values(): + if path != target: + path.write_bytes(b"previous output") + original = {path: path.read_bytes() for path in paths.values()} + options = ["--patches", str(config)] if alias == "direct" else [] + result = subprocess.run([str(relinker), *options, *command], cwd=case, + capture_output=True, text=True, timeout=20) + assert result.returncode == 2 and "would overwrite the patch file" in result.stderr, ( + windows, name, alias, result.stdout, result.stderr) + assert config.read_bytes() == text + for path, data in {**inputs, **original}.items(): + assert path.read_bytes() == data, path + assert not list(case.rglob(".anyps5-output-identity-*")) + if mode in ("all", "controls"): + case = work / "empty-config" + command, outputs, inputs = prepare_secondary(case, False) + config = outputs["registry"] + config.write_text("format = 1\n") + result = subprocess.run([str(relinker), "--patches", str(config), *command], cwd=case, + capture_output=True, text=True, timeout=20) + assert result.returncode == 2 and "would overwrite the patch file" in result.stderr, (result.stdout, result.stderr) + assert config.read_text() == "format = 1\n" + assert not outputs["executable"].exists() + for path, data in inputs.items(): + assert path.read_bytes() == data, path + + case = work / "disabled-config" + command, _, _ = prepare_secondary(case, False) + config = case / "relinker-patches.toml" + config.write_bytes(text) + result = subprocess.run([str(relinker), "--no-patches", *command[:-1], str(config)], cwd=case, + capture_output=True, text=True, timeout=20) + assert result.returncode == 0 and config.read_bytes().startswith(b"\x7fELF"), (result.stdout, result.stderr) + print("Patch input preservation tests passed") + + +if __name__ == "__main__": + main() diff --git a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp index e31e25adb8..7d908c96c0 100644 --- a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp +++ b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp @@ -226,7 +226,7 @@ bool foldRedundantMask(IrValue& value) { bool feedsMultiply(const IrValue& value) { const auto& uses = value.Uses(); - return std::any_of(uses.begin(), uses.end(), [](const IrValue* use) { return use->Opcode() == IrOpcode::IMul32; }); + return std::any_of(uses.begin(), uses.end(), [](const IrValue* use) { return use->Opcode() == IrOpcode::IMul32 || (use->Opcode() == IrOpcode::BitwiseXor32 && (isImmediate(*use->Argument(0)->Resolve(), IrType::U32) || isImmediate(*use->Argument(1)->Resolve(), IrType::U32)) && feedsMultiply(*use)); }); } bool isImmediateU32(const IrValue& value, std::uint32_t immediate) { diff --git a/docs/dev/TRACING.md b/docs/dev/TRACING.md index b9a5f8aa59..da065304b5 100644 --- a/docs/dev/TRACING.md +++ b/docs/dev/TRACING.md @@ -76,3 +76,7 @@ See [Build](BUILD.md#performance-trace) for the trace format, summary fields and ## Shader registers `APS5_TRACE_SHADER_REGS=all` prints register snapshots for every shader. Set it to a shader code address in hexadecimal to print one shader's registers. + +## Running titles repeatably + +`python tools/title_run.py --games [ID ...]` relinks and runs extracted title packages found under `--games` (a directory holding `eboot.bin` and `sce_sys/param.json`) and writes `summary.json` and a fixed-format `summary.md` under `--out` (default `build/title-runs/-