Skip to content
Open
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
6 changes: 5 additions & 1 deletion core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,10 @@ target_link_libraries(agc_fma_mix_once_tests PRIVATE shader_recompiler_agc_drive
set_target_properties(agc_fma_mix_once_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_fma_mix_once_tests)

add_test_executable(agc_bitcast_round_trip_tests EXCLUDE_FROM_ALL tests/BitcastRoundTrip.cpp)
target_link_libraries(agc_bitcast_round_trip_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_bitcast_round_trip_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_bitcast_round_trip_tests)
add_test_executable(agc_f16_single_convert_tests EXCLUDE_FROM_ALL tests/F16SingleConvert.cpp)
target_link_libraries(agc_f16_single_convert_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_f16_single_convert_tests PROPERTIES CXX_EXTENSIONS OFF)
Expand Down Expand Up @@ -2533,7 +2537,7 @@ if(BUILD_TESTING)
if(WIN32)
set_tests_properties(agc_shader_bindless agc_shader_selected_records_image_limit agc_shader_selected_records_pair_limit agc_shader_selected_records_direct_image_limit agc_shader_selected_records_direct_pair_limit agc_shader_srt_read_patching agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off agc_shader_entry_lane_proof_budget PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
foreach(agcTest IN ITEMS agc_vertex_input_tests agc_mesh_output_budget_tests agc_shared_write_tests agc_locks_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vertex_input_validation_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_barrier_batch_tests agc_shader_disk_cache_tests agc_screenshot_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 agc_formatted_buffer_byte_stores_tests ${agcExecutionTests} agc_fma_mix_once_tests agc_f16_single_convert_tests)
foreach(agcTest IN ITEMS agc_vertex_input_tests agc_mesh_output_budget_tests agc_shared_write_tests agc_locks_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vertex_input_validation_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_barrier_batch_tests agc_shader_disk_cache_tests agc_screenshot_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 agc_formatted_buffer_byte_stores_tests ${agcExecutionTests} agc_fma_mix_once_tests agc_f16_single_convert_tests agc_bitcast_round_trip_tests)
if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests" OR agcTest STREQUAL "agc_driver_pipeline_optimized_readback_tests" OR agcTest STREQUAL "agc_driver_rcp_hardware_tests")
continue()
endif()
Expand Down
105 changes: 105 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/BitcastRoundTrip.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#define SPV_ENABLE_UTILITY_CODE
#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 <unordered_map>
#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, 8> capabilities{
spv::CapabilityInt64, spv::CapabilityFloat64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance,
spv::CapabilityFloat16, spv::CapabilityRoundingModeRTE, spv::CapabilityDenormPreserve,
};
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 bitcasts = 0;
std::size_t roundTrips = 0;
};

Counts Count(std::span<const std::uint32_t> words) {
Check(words.size() >= 5 && words[0] == spv::MagicNumber, "bitcast round trip: missing SPIR-V module");
std::unordered_map<std::uint32_t, std::uint32_t> types;
std::unordered_map<std::uint32_t, std::uint32_t> sources;
Counts counts;
for (std::size_t offset = 5; offset < words.size();) {
const auto count = words[offset] >> spv::WordCountShift;
Check(count != 0 && count <= words.size() - offset, "bitcast round trip: malformed SPIR-V instruction");
const auto opcode = static_cast<spv::Op>(words[offset] & spv::OpCodeMask);
bool hasResult = false;
bool hasType = false;
spv::HasResultAndType(opcode, &hasResult, &hasType);
if (hasResult && hasType && count >= 3u) types[words[offset + 2u]] = words[offset + 1u];
if (opcode == spv::OpBitcast && count == 4u) {
++counts.bitcasts;
const auto type = words[offset + 1u];
const auto operand = words[offset + 3u];
if (const auto source = sources.find(operand); source != sources.end() && types[source->second] == type) ++counts.roundTrips;
sources[words[offset + 2u]] = operand;
}
offset += count;
}
return counts;
}

Counts Compile(std::uint32_t adds) {
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);
for (std::uint32_t add = 0; add < adds; ++add) {
code.push_back(0x7e080d00u | (256u + 1u + add % 3u));
code.push_back(0x060a0004u | (2u << 9u) | (256u + 4u));
code.push_back(0x060c0004u | (5u << 9u) | (256u + 4u));
}
code.insert(code.end(), {0xe0700000u, 0x80010000u | (6u << 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 counts = Compile(6);
Check(counts.bitcasts != 0u, "bitcast round trip: v_cvt_f32_u32 and v_add_f32 emitted no bitcast, so the test no longer covers register round trips");
Check(counts.roundTrips == 0u, "bitcast round trip: v_cvt_f32_u32 results added with v_add_f32 emitted " + std::to_string(counts.roundTrips) + " bitcasts back to the type of a bitcast's source (" + std::to_string(counts.bitcasts) + " bitcasts in all)");
std::printf("bitcast round trip tests passed (%zu bitcasts)\n", counts.bitcasts);
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ std::uint32_t AtomicFloatCompareSwap(SpirvEmitterState& state, std::uint32_t old

template<std::uint32_t TOpcode, IrType TValueType, typename... TArguments>
std::uint32_t EmitNative(SpirvEmitterState& state, TArguments... arguments) {
if constexpr (TOpcode == spv::OpBitcast && sizeof...(TArguments) == 1u) {
const std::uint32_t value = (arguments, ...);
if (const auto source = state.module.BitcastSource(value); source != 0u && state.module.ResultType(source) == TypeId(state, TValueType)) return source;
}
const auto result = state.module.AllocateId();
state.module.AddFunction(TOpcode, TypeId(state, TValueType), result, arguments...);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>

namespace ShaderRecompiler {
Expand Down Expand Up @@ -137,8 +138,12 @@ class SpirvModule {

template<typename... TOperands>
void AddFunction(std::uint32_t opcode, const TOperands&... operands) {
const auto offset = functionInstructions.size();
appendInstruction(functionInstructions, opcode, operands...);
noteFunctionResult(offset);
}
[[nodiscard]] std::uint32_t ResultType(std::uint32_t id) const;
[[nodiscard]] std::uint32_t BitcastSource(std::uint32_t id) const;

private:
static constexpr std::size_t DeclarationCacheSize = 512;
Expand Down Expand Up @@ -239,6 +244,9 @@ class SpirvModule {
std::uint32_t declareConstant(std::uint32_t opcode, std::vector<std::uint32_t> key);
static void appendString(std::vector<std::uint32_t>& words, const std::string& text);

void noteFunctionResult(std::size_t offset);
std::unordered_map<std::uint32_t, std::uint32_t> resultTypes;
std::unordered_map<std::uint32_t, std::uint32_t> bitcastSources;
std::uint32_t nextId = 1;
std::uint32_t version = 0x00010300u;
std::vector<std::uint32_t> extInstImports;
Expand Down
21 changes: 21 additions & 0 deletions core/shader/recompiler/SpirvBackend/src/SpirvModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,28 @@ void SpirvModule::AddName(std::uint32_t target, const std::string& name) {
}

void SpirvModule::AddFunction(std::span<const std::uint32_t> words) {
const auto offset = functionInstructions.size();
AppendInstructionWords(functionInstructions, words);
noteFunctionResult(offset);
}

void SpirvModule::noteFunctionResult(std::size_t offset) {
if (functionInstructions.size() < offset + 3u) return;
const auto opcode = functionInstructions[offset] & spv::OpCodeMask;
const auto type = functionInstructions[offset + 1u];
const auto result = functionInstructions[offset + 2u];
resultTypes.emplace(result, type);
if (opcode == spv::OpBitcast && functionInstructions.size() >= offset + 4u) bitcastSources.emplace(result, functionInstructions[offset + 3u]);
}

std::uint32_t SpirvModule::ResultType(std::uint32_t id) const {
const auto found = resultTypes.find(id);
return found == resultTypes.end() ? 0u : found->second;
}

std::uint32_t SpirvModule::BitcastSource(std::uint32_t id) const {
const auto found = bitcastSources.find(id);
return found == bitcastSources.end() ? 0u : found->second;
}

void SpirvModule::AddSubgroupBallot(std::uint32_t type, std::uint32_t result, std::uint32_t scope, std::uint32_t predicate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1771,6 +1771,9 @@ void EmitLabel(SpirvEmitterState& state, std::uint32_t label) {
}

std::uint32_t Unary(SpirvEmitterState& state, std::uint32_t opcode, std::uint32_t type, std::uint32_t value) {
if (opcode == spv::OpBitcast) {
if (const auto source = state.module.BitcastSource(value); source != 0u && state.module.ResultType(source) == type) return source;
}
const auto result = state.module.AllocateId();
state.module.AddFunction(opcode, type, result, value);
return result;
Expand Down
Loading