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
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ if(BUILD_TESTING)
add_test(NAME shader_ir_use_rewrite COMMAND shader_ir_use_rewrite_tests)
set_tests_properties(shader_ir_use_rewrite PROPERTIES TIMEOUT 20)
if(Python3_Interpreter_FOUND)
add_test(NAME shader_cache_native_paths COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/test_shader_cache_native_paths.py
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/ShaderCacheVersion.cmake ${CMAKE_COMMAND})
set_tests_properties(shader_cache_native_paths PROPERTIES TIMEOUT 20)
add_test(NAME pr_overlap_encoding COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_encoding.py)
add_test(NAME pr_overlap_dependencies COMMAND ${Python3_EXECUTABLE}
Expand Down
7 changes: 6 additions & 1 deletion core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,11 @@ target_link_libraries(agc_formatted_buffer_once_tests PRIVATE shader_recompiler_
set_target_properties(agc_formatted_buffer_once_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_formatted_buffer_once_tests)

add_test_executable(agc_fma_mix_once_tests EXCLUDE_FROM_ALL tests/FmaMixOnce.cpp)
target_link_libraries(agc_fma_mix_once_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_fma_mix_once_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_fma_mix_once_tests)

add_test_executable(agc_rect_list_position_invariant_tests EXCLUDE_FROM_ALL tests/RectListPositionInvariant.cpp)
target_link_libraries(agc_rect_list_position_invariant_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_rect_list_position_invariant_tests PROPERTIES CXX_EXTENSIONS OFF)
Expand Down Expand Up @@ -1158,7 +1163,7 @@ if(BUILD_TESTING)
if(WIN32)
set_tests_properties(agc_shader_bindless agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_spirv_bvh_intersect_function_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests})
foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_spirv_bvh_intersect_function_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests} agc_fma_mix_once_tests)
if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests")
continue()
endif()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
#include <functional>
#include <list>
#include <pthread.h>
#include <set>
#include <sstream>
#include <stdexcept>
#include <thread>
#include "Optimization/ShaderStageInputInfo.hpp"
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"

namespace AgcDriver::DriverDetail {
Expand Down Expand Up @@ -405,14 +407,48 @@ std::vector<std::uint32_t> ReadUserDataCallee(std::uint64_t address, std::uint32
throw std::runtime_error("its callee at " + hexText(address) + " does not return within " + std::to_string(limitWords * 4u) + " bytes");
}

std::vector<std::uint32_t> LinkUserDataCallees(const ShaderRecompiler::RecompileRequest& request) {
std::vector<std::uint8_t> ReadGuestBytes(std::uint64_t address, std::uint64_t bytes, const char* what) {
if (address > UINTPTR_MAX - bytes || !GuestMemory::Accessible(reinterpret_cast<const void*>(static_cast<std::uintptr_t>(address)), static_cast<std::size_t>(bytes))) {
throw std::runtime_error(std::string("its ") + what + " at " + hexText(address) + " + " + hexText(bytes) + " is not readable");
}
std::vector<std::uint8_t> data(static_cast<std::size_t>(bytes));
GuestMemory::Read(address, std::as_writable_bytes(std::span(data)));
return data;
}

std::vector<ShaderRecompiler::TableCallee> ReadTableCallees(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::TableCall& call) {
constexpr std::uint64_t limitBytes = 64u << 20u;
constexpr std::size_t limitCallees = 64u;
const auto slot = call.userDataRegister - request.context.userDataBaseRegister;
const auto pointer = static_cast<std::uint64_t>(request.context.userData[slot]) | static_cast<std::uint64_t>(request.context.userData[slot + 1u]) << 32u;
const auto words = ReadGuestBytes(pointer + call.descriptorOffset, 16u, "call table descriptor");
ShaderRecompiler::ShaderBufferResource descriptor{};
std::memcpy(descriptor.fields.data(), words.data(), words.size());
const auto bytes = descriptor.GetSize();
if (bytes > limitBytes) throw std::runtime_error("its call table of " + std::to_string(bytes) + " bytes is larger than the " + std::to_string(limitBytes) + " bytes that are searched for callees");
const auto table = bytes == 0u ? std::vector<std::uint8_t>{} : ReadGuestBytes(descriptor.Base48(), bytes, "call table");
std::set<std::uint64_t> targets;
for (std::uint64_t offset = call.entryOffset; offset + 8u <= bytes; offset += call.entryStride) {
std::uint64_t target = 0;
std::memcpy(&target, table.data() + offset, sizeof(target));
if (target != 0u) targets.insert(target);
if (call.entryStride == 0u) break;
}
if (targets.size() > limitCallees) throw std::runtime_error("its call table holds " + std::to_string(targets.size()) + " distinct targets; at most " + std::to_string(limitCallees) + " are linked");
std::vector<ShaderRecompiler::TableCallee> callees;
for (const auto target : targets) callees.push_back({target, ReadUserDataCallee(target, call.linkRegister)});
return callees;
}

ShaderRecompiler::LinkedCalls LinkUserDataCallees(const ShaderRecompiler::RecompileRequest& request) {
if (request.shader.stage != ShaderRecompiler::ShaderStage::Compute) return {};
const auto code = request.shader.code;
if (std::none_of(code.begin(), code.end(), [](std::uint32_t word) { return (word & 0xff80ff00u) == 0xbe802100u; })) return {};
const auto program = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code);
const ShaderRecompiler::SwappcInfo swappc{false, request.context.userDataBaseRegister, static_cast<std::uint32_t>(request.context.userData.size())};
const ShaderRecompiler::SwappcInfo swappc{false, request.context.userDataBaseRegister, static_cast<std::uint32_t>(request.context.userData.size()), request.context.waveSize};
const auto calls = ShaderRecompiler::FindUserDataCalls(program, swappc);
if (calls.empty()) return {};
const auto tableCalls = ShaderRecompiler::FindTableCalls(program, swappc);
if (calls.empty() && tableCalls.empty()) return {};
std::vector<std::vector<std::uint32_t>> callees;
for (const auto& call : calls) {
const auto slot = call.userDataRegister - swappc.userDataBaseRegister;
Expand All @@ -424,7 +460,15 @@ std::vector<std::uint32_t> LinkUserDataCallees(const ShaderRecompiler::Recompile
std::to_string(call.userDataRegister + 1u) + "] at program counter " + hexText(call.programCounter) + ", but " + error.what());
}
}
return ShaderRecompiler::LinkUserDataCalls(program, calls, callees);
std::vector<std::vector<ShaderRecompiler::TableCallee>> tableCallees;
for (const auto& call : tableCalls) {
try {
tableCallees.push_back(ReadTableCallees(request, call));
} catch (const std::exception& error) {
throw std::runtime_error("AGC driver: compute shader " + hexText(request.shader.codeAddress) + " calls a target loaded from a table at program counter " + hexText(call.programCounter) + ", but " + error.what());
}
}
return ShaderRecompiler::LinkCalls(program, calls, callees, tableCalls, tableCallees);
}

}
Expand All @@ -433,9 +477,12 @@ ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& s
require(codeOffset < snapshot.code.size(), "prepared shader code offset is outside the snapshot");
const auto code = std::span(snapshot.code).subspan(codeOffset);
require(request.shader.code.data() == code.data() && request.shader.code.size() == code.size(), "prepared invocation does not refer to registered code");
const auto linked = LinkUserDataCallees(request);
auto linked = LinkUserDataCallees(request);
auto source = request;
if (!linked.empty()) source.shader.code = linked;
if (!linked.code.empty()) {
source.shader.code = linked.code;
source.context.linkedCallFallbacks = linked.fallbacks;
}
auto invocationRequest = source;
struct PreparedKeyStorage {};
auto& key = HostThreadLocal<std::vector<std::uint64_t>, PreparedKeyStorage>();
Expand All @@ -445,7 +492,7 @@ ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& s
for (const auto& entry : snapshot.prepared->entries) {
if (entry.codeOffset != codeOffset) continue;
invocationRequest.shader.code = ShaderRecompiler::GetPreparedCode(*entry.handle);
if (!linked.empty() && !std::ranges::equal(invocationRequest.shader.code, linked)) continue;
if (!linked.code.empty() && !std::ranges::equal(invocationRequest.shader.code, linked.code)) continue;
if (auto invocation = ShaderRecompiler::PreparedShaderInvocation::TryCreate(invocationRequest, entry.handle, key)) return std::move(*invocation);
}
ReportPreparedAtUse(snapshot, source);
Expand Down
4 changes: 2 additions & 2 deletions core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,10 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
require((packet[8] & 3u) == 0 && packet[8] >= (opcode == 0x2c ? 16u : 20u), "invalid indirect draw stride");
require((packet[9] & ~0x20u) == (opcode == 0x2c ? 2u : 0u), "unsupported indirect draw initiator");
break;
case 0x15: size(5); if ((packet[4] & ~0xa066u) != 0x1u || (packet[4] & 0x22u) == 0x22u) throw std::runtime_error("dispatch modifiers 0x" + ToHex(packet[4]) + " are not implemented"); break;
case 0x15: size(5); if ((packet[4] & ~0xa06eu) != 0x1u || (packet[4] & 0x22u) == 0x22u) throw std::runtime_error("dispatch modifiers 0x" + ToHex(packet[4]) + " are not implemented"); break;
case 0x16:
require(packet.size() == 3 || packet.size() == 4, "invalid indirect dispatch size");
require((packet.back() & ~0xa064u) == 0x1u, "indirect dispatch modifiers are not implemented");
require((packet.back() & ~0xa06cu) == 0x1u, "indirect dispatch modifiers are not implemented");
break;
case 0x22:
size(5);
Expand Down
4 changes: 4 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ void BdaResources::CheckFault() const {
return;
}
std::ostringstream message;
if (report.reason == ShaderRecompiler::BdaAbi::FaultReason::UnlinkedCall) {
message << "shader stage " << report.stage << " called address 0x" << std::hex << report.address << " at program counter 0x" << report.instruction << ", which no linked callee of that call matches";
throw std::runtime_error(message.str());
}
message << "BDA access failed: address=0x" << std::hex << report.address << " instruction=0x" << report.instruction << std::dec << " bytes=" << report.bytes << " stage=" << report.stage << " reason=" << static_cast<std::uint32_t>(report.reason);
if (report.reason == ShaderRecompiler::BdaAbi::FaultReason::Permission && table != nullptr) {
const auto bytes = table->Bytes();
Expand Down
93 changes: 93 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/FmaMixOnce.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "BdaAbi.hpp"
#include "Recompiler.hpp"
#include <array>
#include <cstdio>
#include <exception>
#include <span>
#include <spirv/unified1/spirv.hpp>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>

namespace {

using namespace ShaderRecompiler;

void Check(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}

SpirvTarget Target() {
static constexpr std::array<std::uint32_t, 5> capabilities{
spv::CapabilityInt64, spv::CapabilityFloat64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance,
};
static constexpr std::array<std::string_view, 4> extensions{
"SPV_EXT_descriptor_indexing", "SPV_KHR_8bit_storage", "SPV_KHR_float_controls", "SPV_KHR_physical_storage_buffer",
};
return {0x00401000u, 0x00010300u, 32, BdaAbi::Version, capabilities, extensions, false,
{1024, 1024, 64}, 1024, 32768, {}, {}};
}

struct Counts {
std::size_t words = 0;
std::size_t functions = 0;
};

Counts Count(std::span<const std::uint32_t> words) {
Check(words.size() >= 5 && words[0] == spv::MagicNumber, "fma mix once: missing SPIR-V module");
Counts counts{words.size()};
for (std::size_t offset = 5; offset < words.size();) {
const auto count = words[offset] >> spv::WordCountShift;
Check(count != 0 && count <= words.size() - offset, "fma mix once: malformed SPIR-V instruction");
if ((words[offset] & spv::OpCodeMask) == spv::OpFunction) ++counts.functions;
offset += count;
}
return counts;
}

Counts Compile(std::uint32_t uses) {
std::vector<std::uint32_t> code;
for (std::uint32_t load = 0; load < 3u; ++load) code.insert(code.end(), {0xe0300000u | (load * 4u), 0x80010000u | ((1u + load) << 8u)});
code.push_back(0xbf8c3f70u);
std::uint32_t result = 1u;
for (std::uint32_t use = 0; use < uses; ++use) {
const std::uint32_t opcode = use % 2u == 0u ? 0x21u : 0x22u;
code.insert(code.end(), {0xcc000000u | (opcode << 16u) | 10u, (256u + result) | ((256u + 2u) << 9u) | ((256u + 3u) << 18u)});
result = 10u;
}
code.insert(code.end(), {0xe0700000u, 0x80010000u | (result << 8u)});
code.push_back(0xbf810000u);
const std::span<const std::uint32_t> words(code);
const std::array<MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(words)}}};
const std::array<std::uint32_t, 8> userData{0u, 0u, 0u, 0u, 0x10000000u, 0u, 0x1000u, 0x31016facu};
RecompileRequest request{};
request.shader = {ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), words, 0, {}};
request.target = Target();
request.layout = {0, 0, 0, 128};
request.context.waveSize = 32;
request.context.userDataBaseRegister = 0;
request.context.userData = userData;
request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
request.context.memory = memory;
request.useCache = false;
return Count(Recompile(request).spirv.Words());
}

}

int main() {
try {
const auto none = Compile(0);
const auto one = Compile(1);
const auto three = Compile(3);
Check(three.words - one.words < one.words - none.words, "fma mix once: 2 more v_fma_mixlo/mixhi_f16 added " + std::to_string(three.words - one.words) + " SPIR-V words, as many as the first one with its shared emulation (" + std::to_string(one.words - none.words) + ")");
Check(one.functions == none.functions + 1u, "fma mix once: one v_fma_mixlo_f16 emitted " + std::to_string(one.functions - none.functions) + " functions more than none, expected one shared emulation function");
Check(three.functions == one.functions, "fma mix once: two more v_fma_mixlo/mixhi_f16 in the same float mode emitted " + std::to_string(three.functions - one.functions) + " more functions, expected calls to the shared one");
std::printf("fma mix once tests passed (%zu words without v_fma_mix, %zu for 1, %zu for 3)\n", none.words, one.words, three.words);
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}
3 changes: 2 additions & 1 deletion core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ void testContextAndBases() {
expectFailure([] { static_cast<void>(AgcDriver::Pm4::PartialGroupThreads({0x10002, 1, 1}, {0x0001ffff, 0x00010001, 0x00010001})); }, "exceeds 32 bits");
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x2063}, 2), 0); }, "dispatch modifiers");
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x41}, 4), 0); }, "header flags");
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x49}), 0); }, "dispatch modifiers");
AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x49}), 0);
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x51}), 0); }, "dispatch modifiers");
execute(state, makePacket(0x13, {32}));
execute(state, makePacket(0x26, {0x1000, 1}));
execute(state, makePacket(0x2a, {1}));
Expand Down
10 changes: 8 additions & 2 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2185,13 +2185,19 @@ void verifyPixelRequestSerialization() {
minimal.context.pixel = ShaderPixelStageInfo{};
minimal.context.pixel->rasterPrimitive = RasterPrimitive::Rectangle;
const auto encoded = serializer.Serialize(minimal);
require(requestPrefix(encoded, 8u) == "NVNQQRgAAAA=", "new requests did not use serialization version 24");
require(requestPrefix(encoded, 8u) == "NVNQQRkAAAA=", "new requests did not use serialization version 25");
const std::array<std::uint32_t, 2> fallbacks{0x40u, 0x1ac4u};
auto withFallbacks = minimal;
withFallbacks.context.linkedCallFallbacks = fallbacks;
const auto fallbackRequest = serializer.Deserialize(serializer.Serialize(withFallbacks));
require(std::ranges::equal(fallbackRequest.request.context.linkedCallFallbacks, fallbacks), "the linked call fallbacks did not round-trip");
require(serializer.Deserialize(encoded).request.context.linkedCallFallbacks.empty(), "a request without linked call fallbacks gained some");
require(serializer.Deserialize(encoded).request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle, "the pixel primitive class did not round-trip");
constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u;
for (std::size_t bytes = 0; bytes < 20u; ++bytes) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag or primitive class was accepted");
}
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRkAAAA="}) {
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRoAAAA="}) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
}
static constexpr std::string_view version18Request{
Expand Down
Loading