Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,7 @@ add_custom_command(

add_test_executable(agc_driver_recorder_tests EXCLUDE_FROM_ALL
tests/Recorder.cpp
tests/HostImportLifetime.cpp
tests/ResidentPresent.cpp
${agcTriangleHeaders}
${agcSampleLodShaderHeader}
Expand Down Expand Up @@ -1273,6 +1274,22 @@ if(BUILD_TESTING)
set_tests_properties(agc_pending_key_stores agc_shadowed_key_stores agc_sample_dumps agc_draw_descriptors PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_host_import_lifetime COMMAND agc_driver_recorder_tests --host-import-lifetime-only)
add_test(NAME agc_driver_host_import_mutation_race COMMAND agc_driver_recorder_tests --host-import-mutation-race-only)
set_tests_properties(agc_driver_host_import_lifetime agc_driver_host_import_mutation_race PROPERTIES
ENVIRONMENT "APS5_PIN_WAIT_MS=2000" SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_driver_unit_shadow COMMAND agc_driver_recorder_tests --unit-shadow-only)
add_test(NAME agc_driver_unit_shadow_budget COMMAND agc_driver_recorder_tests --unit-shadow-only)
set_tests_properties(agc_driver_unit_shadow PROPERTIES
ENVIRONMENT "APS5_PIN_WAIT_MS=2000" SKIP_RETURN_CODE 77 TIMEOUT 30)
set_tests_properties(agc_driver_unit_shadow_budget PROPERTIES
ENVIRONMENT "APS5_PIN_WAIT_MS=2000;APS5_UNIT_SHADOW_MIB=8" SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_host_import_lifetime agc_driver_host_import_mutation_race agc_driver_unit_shadow agc_driver_unit_shadow_budget PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_property(TEST agc_driver_host_import_lifetime APPEND PROPERTY ENVIRONMENT_MODIFICATION
"APS5_RELEASE_UNDER_LOCK=unset:" "APS5_RELEASE_ON_UNLOCK=unset:")
endif()

add_test_executable(agc_recorder_kept_bytes_tests EXCLUDE_FROM_ALL tests/RecorderKeptBytes.cpp)
Expand Down Expand Up @@ -1635,6 +1652,8 @@ target_link_libraries(agc_driver_interpolation_modes_tests PRIVATE shader_recomp

target_include_directories(agc_driver_prepared_unregistered_graphics_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)

target_link_libraries(agc_driver_guest_memory_lifetime_tests PRIVATE libkernel)

target_include_directories(agc_driver_prepared_shaders_tests PRIVATE
$<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>
)
Expand Down Expand Up @@ -1849,6 +1868,48 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_uint8x4_color_target_no_resident PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set(agcGuestMemoryLifetimeTests agc_driver_guest_memory_lifetime)
foreach(mode IN ITEMS copy direct locked heap heap_uncached)
set(testName agc_driver_guest_memory_lifetime_${mode})
if(mode STREQUAL "heap_uncached")
add_test(NAME ${testName} COMMAND agc_driver_guest_memory_lifetime_tests heap)
else()
add_test(NAME ${testName} COMMAND agc_driver_guest_memory_lifetime_tests ${mode})
endif()
list(APPEND agcGuestMemoryLifetimeTests ${testName})
endforeach()
set_tests_properties(${agcGuestMemoryLifetimeTests} PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIN_WAIT_MS=1000;APS5_HEAP_CACHE_MIB=4"
SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_driver_guest_memory_lifetime_heap_uncached PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIN_WAIT_MS=1000;APS5_HEAP_CACHE_MIB=0")
if(WIN32)
set_tests_properties(${agcGuestMemoryLifetimeTests} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND Python3_Interpreter_FOUND)
add_library(agc_guest_memory_lifetime_fixture SHARED tests/GuestMemoryLifetimeFixture.cpp)
target_include_directories(agc_guest_memory_lifetime_fixture PRIVATE
${LIBS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/core/shader/recompiler
${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include
${CMAKE_SOURCE_DIR}/3rdparty/SPIRV-Headers/include)
target_link_libraries(agc_guest_memory_lifetime_fixture PRIVATE libSceAgcDriver SDL2-static libc)
add_dependencies(agc_guest_memory_lifetime_fixture relinker libkernel)
set_target_properties(agc_guest_memory_lifetime_fixture PROPERTIES
OUTPUT_NAME GuestMemoryFixture SUFFIX ".prx" CXX_EXTENSIONS OFF
BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN")
add_test(NAME agc_driver_guest_memory_lifetime_converted COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_guest_memory_lifetime.py
${CMAKE_SOURCE_DIR}/core/relinker/relinker/tests
$<TARGET_FILE:relinker>
$<TARGET_FILE:agc_guest_memory_lifetime_fixture>
$<TARGET_FILE:libkernel>
$<TARGET_FILE:libc>
$<TARGET_FILE:libSceAgcDriver>)
set_tests_properties(agc_driver_guest_memory_lifetime_converted PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIN_WAIT_MS=1000" SKIP_RETURN_CODE 77 TIMEOUT 30)
endif()
set_tests_properties(agc_driver_flat_store PROPERTIES RUN_SERIAL TRUE)
set_target_properties(agc_driver_flat_apertures_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
foreach(flatAperturesPart RANGE 13)
Expand Down
183 changes: 98 additions & 85 deletions core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1036,7 +1036,30 @@ std::uint64_t evictableBytes(const HostImports& state, std::uint64_t begin, std:
return bytes;
}

struct ImportConstruction {
ImportConstruction(const Context& context, std::uint64_t base, std::uint64_t bytes)
: device(context.device), destroyBuffer(context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")), freeMemory(context.Function<PFN_vkFreeMemory>("vkFreeMemory")), entry{base, bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0} {}
ImportConstruction(const ImportConstruction&) = delete;
ImportConstruction& operator=(const ImportConstruction&) = delete;
~ImportConstruction() {
if (!armed) return;
if (entry.buffer != VK_NULL_HANDLE) destroyBuffer(device, entry.buffer, nullptr);
if (entry.memory != VK_NULL_HANDLE) freeMemory(device, entry.memory, nullptr);
#ifdef _WIN32
GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
#endif
}
VkDevice device;
PFN_vkDestroyBuffer destroyBuffer;
PFN_vkFreeMemory freeMemory;
HostImport entry;
bool armed = true;
};

void ensurePinWaiter();

const HostImport* importAllocation(const Context& context, HostImports& state, std::uint64_t address, const GuestAllocations::Lease& lease, std::uint64_t end = 0, bool evict = true) {
ensurePinWaiter();
const bool fresh = !importsStale(context, state);
if (fresh && end != 0) {
if (const auto* current = findImport(state, address, end)) return current;
Expand Down Expand Up @@ -1113,7 +1136,8 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import
it = next;
}
if (state.liveBytes + bytes > budget) retireLeastRecent(context, state, lease, state.liveBytes + bytes - budget);
HostImport entry{base, bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0};
ImportConstruction owner(context, base, bytes);
auto& entry = owner.entry;
entry.range = run.ranges.front();
entry.ranges = std::move(run.ranges);
#ifdef _WIN32
Expand Down Expand Up @@ -1208,9 +1232,6 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import
#endif
entry.unwatched = step == nullptr && !(entry.dmaBuf ? watchedDmaBuf : watchedHostPointer);
if (step != nullptr) {
#ifdef _WIN32
GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
#endif
failures.insert_or_assign(base, ImportFailure{base, bytes, GuestAllocations::Mapped(entry.ranges.begin(), entry.ranges.end())});
std::fprintf(stderr, "[gpu] host import of 0x%llx+0x%llx failed at %s (%d); falling back to copies\n", static_cast<unsigned long long>(base), static_cast<unsigned long long>(bytes), step, static_cast<int>(result));
#ifdef _WIN32
Expand Down Expand Up @@ -1244,7 +1265,9 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import
if (trace) std::fprintf(stderr, "[gpu] host import of 0x%llx+0x%llx ok (%zu live, %.1f MiB live, %.1f MiB ever)%s\n", static_cast<unsigned long long>(base), static_cast<unsigned long long>(bytes), state.imports.size() + 1, state.liveBytes / 1048576.0, importedBytes / 1048576.0, joined ? ", joined at bind" : "");
entry.joined = joined;
entry.lastUse = ++state.useCounter;
return &state.imports.emplace(base, entry).first->second;
const auto inserted = state.imports.emplace(base, entry);
owner.armed = false;
return &inserted.first->second;
}

// The readable registered range containing [begin, end), or null.
Expand Down Expand Up @@ -2078,7 +2101,7 @@ SnapshotStats& Snapshots() {
return stats;
}

bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) noexcept {
bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) {
const auto start = std::chrono::steady_clock::now();
bool synced = false;
bool drained = false;
Expand All @@ -2098,19 +2121,82 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) noexcept
state.stats.contentionMs += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
return true;
}
if (Recorder::InCompletion()) throw std::runtime_error("guest allocation mutation cannot wait for an active GPU completion");
if (GuestMemory::GpuMutex().HeldByThisThread()) {
try {
auto* recorder = Recorder::Active();
auto* recorder = Recorder::Active();
if (recorder != nullptr && !recorder->Idle()) {
Recorder::CountSync(4);
recorder->Sync();
synced = true;
drained = true;
}
if (recorder != nullptr) recorder->FlushDeferredReleases();
const auto end = static_cast<std::uint64_t>(address) + bytes;
auto& imports = Imports();
if (releaseParkedImports(imports, address, end)) retiredImport = true;
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
{
std::lock_guard importsLock(imports.mutex);
for (auto it = imports.imports.begin(); it != imports.imports.end();) {
const auto base = it->first;
if (base >= end || address >= base + it->second.bytes) {
++it;
continue;
}
const auto next = std::next(it);
retireImport(imports.device, imports, it, lease, false);
it = next;
retiredImport = true;
}
}
if (retiredImport && recorder != nullptr && !recorder->Idle()) {
Recorder::CountSync(4);
recorder->Sync();
synced = true;
drained = true;
}
if (retiredImport && recorder != nullptr) recorder->FlushDeferredReleases();
} else {
std::lock_guard lock(GuestMemory::GpuMutex());
auto& state = Leases();
auto* recorder = Recorder::Active();
const auto target = state.newestLeaseSerial;
if (recorder != nullptr && recorder == state.leaseRecorder && target > state.finishedLeaseSerial) {
Recorder::CountSync(4);
recorder->Submit();
recorder->FinishUpTo(target);
state.finishedLeaseSerial = target;
synced = true;
} else if (recorder != nullptr && !recorder->Idle()) {
Recorder::CountSync(4);
recorder->Sync();
synced = true;
drained = true;
} else {
std::this_thread::yield();
}
if (recorder != nullptr) recorder->FlushDeferredReleases();

const auto end = static_cast<std::uint64_t>(address) + bytes;
auto& imports = Imports();
if (releaseParkedImports(imports, address, end)) retiredImport = true;
bool overlapsImport = false;
{
std::lock_guard importsLock(imports.mutex);
for (const auto& [base, entry] : imports.imports) {
if (base < end && address < base + entry.bytes) {
overlapsImport = true;
break;
}
}
}
if (overlapsImport) {
if (recorder != nullptr && !recorder->Idle()) {
Recorder::CountSync(4);
recorder->Sync();
synced = true;
drained = true;
}
if (recorder != nullptr) recorder->FlushDeferredReleases();
const auto end = static_cast<std::uint64_t>(address) + bytes;
auto& imports = Imports();
if (releaseParkedImports(imports, address, end)) retiredImport = true;
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
{
std::lock_guard importsLock(imports.mutex);
Expand All @@ -2132,79 +2218,6 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) noexcept
synced = true;
drained = true;
}
if (retiredImport && recorder != nullptr) recorder->FlushDeferredReleases();
} catch (const std::exception& error) {
std::fprintf(stderr, "[gpu] lease wait failed: %s\n", error.what());
}
} else {
try {
std::lock_guard lock(GuestMemory::GpuMutex());
auto& state = Leases();
auto* recorder = Recorder::Active();
const auto target = state.newestLeaseSerial;
if (recorder != nullptr && recorder == state.leaseRecorder && target > state.finishedLeaseSerial) {
// The open batch may be the target; FinishUpTo waits front to back up to it only.
Recorder::CountSync(4);
recorder->Submit();
recorder->FinishUpTo(target);
state.finishedLeaseSerial = target;
synced = true;
} else if (recorder != nullptr && !recorder->Idle()) {
Recorder::CountSync(4);
recorder->Sync();
synced = true;
drained = true;
} else {
// Nothing recorded holds the lease: the holder is a lease-holding object that has
// yet to record (a test's, or a build whose worker gave up the lock); give it time.
std::this_thread::yield();
}
if (recorder != nullptr) recorder->FlushDeferredReleases();

const auto end = static_cast<std::uint64_t>(address) + bytes;
auto& imports = Imports();
if (releaseParkedImports(imports, address, end)) retiredImport = true;
bool overlapsImport = false;
{
std::lock_guard importsLock(imports.mutex);
for (const auto& [base, entry] : imports.imports) {
if (base < end && address < base + entry.bytes) {
overlapsImport = true;
break;
}
}
}
if (overlapsImport) {
if (recorder != nullptr && !recorder->Idle()) {
Recorder::CountSync(4);
recorder->Sync();
synced = true;
drained = true;
}
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
{
std::lock_guard importsLock(imports.mutex);
for (auto it = imports.imports.begin(); it != imports.imports.end();) {
const auto base = it->first;
if (base >= end || address >= base + it->second.bytes) {
++it;
continue;
}
const auto next = std::next(it);
retireImport(imports.device, imports, it, lease, false);
it = next;
retiredImport = true;
}
}
if (retiredImport && recorder != nullptr && !recorder->Idle()) {
Recorder::CountSync(4);
recorder->Sync();
synced = true;
drained = true;
}
}
} catch (const std::exception& error) {
std::fprintf(stderr, "[gpu] lease wait failed: %s\n", error.what());
}
}
auto& state = Leases();
Expand Down
Loading
Loading