From af8aecfdde3ed36f938e18af8e3afb64a285e231 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 31 Aug 2026 05:30:18 +0400 Subject: [PATCH] shader: remove recompiler error-string propagation --- .../renderer/pipeline/descriptors.cpp | 6 +- .../shader/recompiler/ShaderRecompiler.cpp | 112 +- .../shader/recompiler/ShaderRecompiler.h | 4 +- .../recompiler/backend/spirv/SpirvEmitter.cpp | 98 +- .../recompiler/backend/spirv/SpirvEmitter.h | 7 +- .../backend/spirv/spirvEmitterAnalysis.cpp | 6 - .../backend/spirv/spirvEmitterInternal.h | 9 +- .../backend/spirv/spirvEmitterProgram.cpp | 101 +- .../recompiler/frontend/cfg/ShaderCFG.cpp | 157 +- .../recompiler/frontend/cfg/ShaderCFG.h | 4 +- .../recompiler/frontend/decode/ImageOps.cpp | 4 +- .../recompiler/frontend/decode/MemoryOps.cpp | 16 +- .../frontend/decode/ScalarAluOps.cpp | 74 +- .../recompiler/frontend/decode/ScalarAluOps.h | 16 +- .../frontend/decode/ShaderDecoder.cpp | 131 +- .../frontend/decode/ShaderDecoder.h | 9 +- .../frontend/decode/VectorAluOps.cpp | 409 ++-- .../recompiler/frontend/decode/VectorAluOps.h | 20 +- .../frontend/translate/Attribute.cpp | 24 +- .../recompiler/frontend/translate/Control.cpp | 47 +- .../frontend/translate/Dispatch.cpp | 32 +- .../recompiler/frontend/translate/Memory.cpp | 11 +- .../recompiler/frontend/translate/Scalar.cpp | 4 +- .../frontend/translate/Translate.cpp | 121 +- .../recompiler/frontend/translate/Translate.h | 4 +- .../frontend/translate/Translator.h | 64 +- .../recompiler/frontend/translate/Vector.cpp | 6 +- src/graphics/shader/recompiler/ir/Program.cpp | 184 +- src/graphics/shader/recompiler/ir/ShaderIR.h | 3 +- .../recompiler/ir/passes/BindingLayout.cpp | 78 +- .../recompiler/ir/passes/BindingLayout.h | 2 +- .../ir/passes/ResourceMaterialization.cpp | 316 +-- .../ir/passes/ResourceMaterialization.h | 12 +- .../recompiler/ir/passes/ResourceTracking.cpp | 190 +- .../recompiler/ir/passes/ResourceTracking.h | 2 +- .../ir/passes/ShaderInfoCollection.cpp | 86 +- .../ir/passes/ShaderInfoCollection.h | 2 +- .../shader/recompiler/ir/passes/SrtWalker.cpp | 257 +-- .../shader/recompiler/ir/passes/SrtWalker.h | 25 +- src/graphics/shader/shader.cpp | 29 +- src/graphics/shader/shader.h | 2 +- src/graphics/shader/shaderStageRuntime.cpp | 9 +- tests/ResourceTrackingTests.cpp | 356 ++-- tests/ScalarProvenanceTests.cpp | 113 +- tests/ShaderRecompilerComputeTests.cpp | 185 +- tests/ShaderStageRuntimeTests.cpp | 56 +- tests/shaderCfgTests.cpp | 1890 ++++++----------- 47 files changed, 2040 insertions(+), 3253 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 53d7f7e98..195892266 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -895,9 +895,9 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti EXIT_IF(!runtime); const auto& program = *runtime.program; const auto& snapshot = *runtime.resources; - std::string error; - if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) { - EXIT("invalid native shader runtime snapshot: %s\n", error.c_str()); + if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot)) { + EXIT("invalid native shader runtime snapshot: hash=0x%016" PRIx64 " stage=%u\n", + program.shader_hash, static_cast(program.stage)); } PreparedBindings prepared; diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index fff5bc0db..fc860b368 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -472,20 +472,14 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded } // namespace -bool TryRecompile(std::span code, const CompileOptions& options, - CompileResult& result, std::string* error) { +CompileResult Recompile(std::span code, const CompileOptions& options) { if (code.empty()) { - if (error != nullptr) { - *error = "invalid shader recompiler input"; - } - return false; + EXIT("shader recompiler input is empty\n"); } if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex && options.stage != ShaderType::Pixel) { - if (error != nullptr) { - *error = "shader recompiler supports compute, vertex, and pixel stages"; - } - return false; + EXIT("shader recompiler received unsupported stage %u\n", + static_cast(options.stage)); } const auto compile_begin = std::chrono::steady_clock::now(); @@ -500,9 +494,7 @@ bool TryRecompile(std::span code, const CompileOptions& options, static_cast(code.size())); Decoder::Program decoded; - if (!Decoder::DecodeProgram(code, decoded, error)) { - return false; - } + Decoder::DecodeProgram(code, decoded); LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " decode instructions=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, @@ -516,12 +508,9 @@ bool TryRecompile(std::span code, const CompileOptions& options, } } - CFG::Graph cfg; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); - if (!CFG::BuildGraph(decoded, cfg, error)) { - return false; - } + auto cfg = CFG::BuildGraph(decoded); LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64 " loops=%" PRIu64 " back_edges=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, @@ -534,13 +523,12 @@ bool TryRecompile(std::span code, const CompileOptions& options, dispatcher_reason = cfg.unsupported_reason; LogDispatcherFallback(options, cfg, "build", dispatcher_reason); } else { - std::string structure_error; - const auto unstructured_cfg = cfg; + const auto unstructured_cfg = cfg; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); - if (!CFG::Structurize(cfg, &structure_error)) { + if (!CFG::Structurize(cfg)) { dispatcher_fallback = true; - dispatcher_reason = structure_error; + dispatcher_reason = cfg.unsupported_reason; const auto failure_kind = cfg.failure_kind; const auto failure_block = cfg.failure_block; LogDispatcherFallback(options, cfg, "structurize", dispatcher_reason); @@ -548,7 +536,7 @@ bool TryRecompile(std::span code, const CompileOptions& options, cfg.unsupported = true; cfg.failure_kind = failure_kind; cfg.failure_block = failure_block; - cfg.unsupported_reason = structure_error; + cfg.unsupported_reason = dispatcher_reason; } else { LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options), static_cast(cfg.blocks.size())); @@ -560,14 +548,28 @@ bool TryRecompile(std::span code, const CompileOptions& options, static_cast(cfg.natural_loops.size()), phase_ms()); } - IR::Program ir; const ShaderVertexInputInfo* vertex = nullptr; const ShaderPixelInputInfo* pixel = nullptr; const ShaderComputeInputInfo* compute = nullptr; switch (options.stage) { - case ShaderType::Vertex: vertex = options.input_info.vertex; break; - case ShaderType::Pixel: pixel = options.input_info.pixel; break; - case ShaderType::Compute: compute = options.input_info.compute; break; + case ShaderType::Vertex: + vertex = options.input_info.vertex; + if (vertex == nullptr) { + EXIT("vertex shader recompilation has no input metadata\n"); + } + break; + case ShaderType::Pixel: + pixel = options.input_info.pixel; + if (pixel == nullptr) { + EXIT("pixel shader recompilation has no input metadata\n"); + } + break; + case ShaderType::Compute: + compute = options.input_info.compute; + if (compute == nullptr) { + EXIT("compute shader recompilation has no input metadata\n"); + } + break; default: break; } EmbeddedFetchData embedded_fetch; @@ -597,9 +599,7 @@ bool TryRecompile(std::span code, const CompileOptions& options, }; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); - if (!Frontend::TranslateProgram(decoded, cfg, translate_options, ir, error)) { - return false; - } + auto ir = Frontend::TranslateProgram(decoded, cfg, translate_options); LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram blocks=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, @@ -626,18 +626,9 @@ bool TryRecompile(std::span code, const CompileOptions& options, lds_barriers.inserted_barriers); } } - std::string srt_error; - if (!IR::BuildSrtPlan(ir, &srt_error)) { - LOGF("%s SRT planning failed: %s\n", GetDumpLabel(options), srt_error.c_str()); - if (error != nullptr) { - *error = std::move(srt_error); - } - return false; - } + IR::BuildSrtPlan(ir); IR::EliminateDeadCode(ir.blocks); - if (!IR::TrackResources(ir, error)) { - return false; - } + IR::TrackResources(ir); IR::EliminateDeadCode(ir.blocks); if (options.stage == ShaderType::Vertex) { ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr; @@ -665,27 +656,20 @@ bool TryRecompile(std::span code, const CompileOptions& options, runtime.read_memory = ReadZeroMemory; } runtime.userdata = options.read_memory_data; - if (!IR::MaterializeResources(ir, runtime, resources, error)) { - return false; + if (!IR::MaterializeResources(ir, runtime, resources)) { + EXIT("shader resource materialization failed: stage=%s hash=0x%016" PRIx64 "\n", + StageName(options.stage), options.shader_hash); } } - if (!IR::SpecializeResources(ir, resources, error)) { - return false; - } + IR::SpecializeResources(ir, resources); IR::ShaderInfoOptions info_options; info_options.vertex = vertex; info_options.pixel = pixel; info_options.compute = compute; - if (!IR::CollectShaderInfo(ir, info_options, error)) { - return false; - } - if (!IR::AllocateBindings(ir, options.push_constant_offset, error)) { - return false; - } - if (!Spirv::AnalyzeProgramRequirements(ir, error)) { - return false; - } + IR::CollectShaderInfo(ir, info_options); + IR::AllocateBindings(ir, options.push_constant_offset); + Spirv::AnalyzeProgramRequirements(ir); std::string ir_dump; if (options.dump_ir) { ir_dump = MakeIrDump(cfg, ir); @@ -694,36 +678,22 @@ bool TryRecompile(std::span code, const CompileOptions& options, } } - std::vector spirv; - std::string emit_error; LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash); - if (!Spirv::EmitProgram(ir, resources, options.input_info, spirv, &emit_error)) { - LOGF("%s typed SPIR-V emit failed: %s\n", GetDumpLabel(options), emit_error.c_str()); - if (dispatcher_fallback && error != nullptr) { - *error = fmt::format("dispatcher fallback failed after {}: {}", - dispatcher_reason.c_str(), emit_error.c_str()); - LOGF("%s dispatcher fallback emit failed: %s\n", GetDumpLabel(options), error->c_str()); - } else if (error != nullptr) { - *error = emit_error; - } - return false; - } + auto spirv = Spirv::EmitProgram(ir, resources, options.input_info); LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(options.stage), options.shader_hash, static_cast(spirv.size()), phase_ms()); + CompileResult result; result.spirv = std::move(spirv); result.program = std::move(ir); result.resources = std::move(resources); if (options.dump_ir) { result.decoded_dump = std::move(decoded_dump); result.ir_dump = std::move(ir_dump); - } else { - result.decoded_dump.clear(); - result.ir_dump.clear(); } - return true; + return result; } } // namespace Libs::Graphics::ShaderRecompiler diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.h b/src/graphics/shader/recompiler/ShaderRecompiler.h index 755c27842..d4427d84c 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.h +++ b/src/graphics/shader/recompiler/ShaderRecompiler.h @@ -39,8 +39,8 @@ struct CompileResult { IR::ResourceSnapshot resources; }; -bool TryRecompile(std::span code, const CompileOptions& options, - CompileResult& result, std::string* error); +[[nodiscard]] CompileResult Recompile(std::span code, + const CompileOptions& options); } // namespace Libs::Graphics::ShaderRecompiler diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp index 79091601b..e3d945525 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp @@ -1,19 +1,23 @@ #include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h" +#include "common/assert.h" #include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" -#include "graphics/shader/shader.h" #include #include namespace Libs::Graphics::ShaderRecompiler::Spirv { -using ShaderError::Fail; - namespace { -bool ValidateNativeProgram(const IR::Program& program, std::string* error) { +[[noreturn]] void Fail(const IR::Program& program, const char* reason) { + EXIT("SPIR-V validation failed: hash=0x%016" PRIx64 " stage=%u reason=%s\n", + program.shader_hash, static_cast(program.stage), reason); + std::abort(); +} + +void ValidateNativeProgram(const IR::Program& program) { using Kind = IR::DescriptorBindingKind; constexpr auto KindCount = static_cast(Kind::Count); std::array, KindCount> expected; @@ -36,13 +40,13 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) { for (uint32_t i = 0; i < program.info.images.size(); i++) { const auto kind = IR::DescriptorBindingForImage(program.info.images[i]); if (!kind.has_value()) { - return Fail(error, "native shader plan has an invalid image class"); + Fail(program, "native shader plan has an invalid image class"); } present[static_cast(*kind)] = true; const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::DynamicStorage; const auto count = dynamic ? program.info.images[i].mip_count : 1u; if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) { - return Fail(error, "native shader plan has an invalid image mip descriptor count"); + Fail(program, "native shader plan has an invalid image mip descriptor count"); } expected[static_cast(*kind)].insert(expected[static_cast(*kind)].end(), count, i); @@ -58,11 +62,11 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) { } const auto index = inst.Flags().index; if (index >= program.memory_info.size()) { - return Fail(error, "shared operation has invalid memory metadata"); + Fail(program, "shared operation has invalid memory metadata"); } const auto kind = program.memory_info[index].kind; if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) { - return Fail(error, "shared operation has invalid resource kind"); + Fail(program, "shared operation has invalid resource kind"); } uses_gds |= kind == IR::ResourceKind::Gds; } @@ -91,13 +95,13 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) { const auto kind = static_cast(binding.kind); if (kind >= KindCount || seen[kind] || !present[kind] || binding.resources != expected[kind]) { - return Fail(error, "native descriptor groups do not match shader topology"); + Fail(program, "native descriptor groups do not match shader topology"); } seen[kind] = true; } for (size_t i = 0; i < KindCount; i++) { if (present[i] != seen[i]) { - return Fail(error, "native shader plan is missing a required descriptor group"); + Fail(program, "native shader plan is missing a required descriptor group"); } } const auto has_user_storage = present[static_cast(Kind::UserData)]; @@ -114,7 +118,7 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) { std::adjacent_find(program.bindings.user_data_registers.begin(), program.bindings.user_data_registers.end()) != program.bindings.user_data_registers.end()) { - return Fail(error, "native user-data layout is inconsistent"); + Fail(program, "native user-data layout is inconsistent"); } const auto planning_only_handle = [&](const IR::Inst& handle) { @@ -139,7 +143,7 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) { break; } if (dense >= program.info.buffers.size()) { - return Fail(error, "typed buffer handle has an invalid dense resource"); + Fail(program, "typed buffer handle has an invalid dense resource"); } break; case IR::ValueOpcode::GetAddressResource: @@ -147,29 +151,29 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) { break; } if (inst.NumArgs() != 2 || !program.info.uses_dma) { - return Fail(error, "typed address handle has invalid DMA metadata"); + Fail(program, "typed address handle has invalid DMA metadata"); } break; case IR::ValueOpcode::GetScratchResource: if (inst.NumArgs() != 0 || program.scratch_dwords == 0) { - return Fail(error, "typed scratch handle has invalid shader metadata"); + Fail(program, "typed scratch handle has invalid shader metadata"); } break; case IR::ValueOpcode::GetImageResource: if (dense >= program.info.images.size()) { - return Fail(error, "typed image handle has an invalid dense resource"); + Fail(program, "typed image handle has an invalid dense resource"); } break; case IR::ValueOpcode::GetSamplerResource: if (dense >= program.info.samplers.size()) { - return Fail(error, "typed sampler handle has an invalid dense resource"); + Fail(program, "typed sampler handle has an invalid dense resource"); } break; case IR::ValueOpcode::ReadConst: { const auto slot = inst.Arg(1).Resolve(); if (!slot.IsImmediate() || slot.GetType() != IR::Type::U32 || slot.U32() >= program.srt_reads.size()) { - return Fail(error, "flattened SRT read has an invalid dense slot"); + Fail(program, "flattened SRT read has an invalid dense slot"); } break; } @@ -177,12 +181,11 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) { } } } - return true; } } // namespace -bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) { +void AnalyzeProgramRequirements(IR::Program& program) { program.spirv_requirements.reset(); IR::SpirvRequirements requirements {}; const auto MarkBallot = [&] { requirements.subgroup_ballot = true; }; @@ -192,30 +195,30 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) { if (address_access != IR::AddressAccess::None) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { - return Fail(error, "address operation has invalid memory metadata"); + Fail(program, "address operation has invalid memory metadata"); } if (program.memory_info[memory_index].kind == IR::ResourceKind::Scratch) { if (program.scratch_dwords == 0) { - return Fail(error, "scratch operation has no per-thread storage"); + Fail(program, "scratch operation has no per-thread storage"); } requirements.function_scratch = true; } else if (address_access == IR::AddressAccess::Write) { - return Fail(error, "writable FLAT/GLOBAL addresses require GPU ownership tracking"); + Fail(program, "writable FLAT/GLOBAL addresses require GPU ownership tracking"); } } if (IR::BufferAccessOf(inst.GetOpcode()) != IR::BufferAccess::None) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { - return Fail(error, "buffer operation has invalid memory metadata"); + Fail(program, "buffer operation has invalid memory metadata"); } const auto& memory = program.memory_info[memory_index]; if (memory.kind == IR::ResourceKind::Buffer) { if (memory.resource >= program.info.buffers.size()) { - return Fail(error, "buffer operation has invalid resource metadata"); + Fail(program, "buffer operation has invalid resource metadata"); } if ((program.info.buffers[memory.resource].packed_stride & (1u << 20u)) != 0u) { if (program.stage != ShaderType::Compute) { - return Fail(error, "buffer ADD_TID is only valid for compute shaders"); + Fail(program, "buffer ADD_TID is only valid for compute shaders"); } requirements.subgroup_local_invocation_id = true; } @@ -225,11 +228,11 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) { if (shared_access != IR::SharedAccess::None) { const auto index = inst.Flags().index; if (index >= program.memory_info.size()) { - return Fail(error, "shared operation has invalid memory metadata"); + Fail(program, "shared operation has invalid memory metadata"); } const auto kind = program.memory_info[index].kind; if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) { - return Fail(error, "shared operation has invalid resource kind"); + Fail(program, "shared operation has invalid resource kind"); } if (program.stage != ShaderType::Compute && kind == IR::ResourceKind::Lds) { requirements.function_lds = true; @@ -283,7 +286,7 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) { case IR::ValueOpcode::SetAttribute: { const auto index = inst.Flags().index; if (index >= program.export_info.size()) { - return Fail(error, "attribute export has invalid metadata"); + Fail(program, "attribute export has invalid metadata"); } if (program.stage == ShaderType::Pixel && program.export_info[index].vm) { @@ -296,37 +299,30 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) { } } program.spirv_requirements.emplace(requirements); - return true; } -bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources, - ShaderStageInputInfo input_info, std::vector& spirv, - std::string* error) { +std::vector EmitProgram(const IR::Program& program, + const IR::ResourceSnapshot& resources, + ShaderStageInputInfo input_info) { using namespace Emitter; if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex && program.stage != ShaderType::Pixel) { - SetError(error, "binary SPIR-V emitter supports compute, vertex, and pixel shaders"); - return false; + Fail(program, "binary SPIR-V emitter supports compute, vertex, and pixel shaders"); } if (!program.srt_plan_complete || !program.resource_tracking_complete || !program.shader_info_complete || !program.binding_layout_complete || !program.spirv_requirements.has_value()) { - SetError(error, "SPIR-V emitter requires a fully planned native shader program"); - return false; + Fail(program, "SPIR-V emitter requires a fully planned native shader program"); } - if (!IR::ValidateResourceSnapshot(program, resources, error)) { - return false; + if (!IR::ValidateResourceSnapshot(program, resources)) { + Fail(program, "resource snapshot is inconsistent with the native shader plan"); } - if (!IR::ValidateResourceSpecialization(program, resources, error)) { - return false; - } - if (!ValidateNativeProgram(program, error)) { - return false; - } - if (!IR::ValidateProgram(program, true, error)) { - return false; + if (!IR::ValidateResourceSpecialization(program, resources)) { + Fail(program, "resource specialization is inconsistent with the native shader plan"); } + ValidateNativeProgram(program); + IR::ValidateProgram(program, true); EmitterState state(program, resources, input_info); state.stage = program.stage; state.wave_size = program.wave_size; @@ -337,17 +333,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc AllocateInputVariables(state); AllocateOutputVariables(state); DefineModule(state); - if (!EmitProgram(state, program, error)) { - return false; - } + EmitProgram(state, program); auto binary = state.builder.Build(); if (binary.empty()) { - SetError(error, "SPIR-V builder returned an empty module"); - return false; + Fail(program, "SPIR-V builder returned an empty module"); } - spirv = std::move(binary); - return true; + return binary; } } // namespace Libs::Graphics::ShaderRecompiler::Spirv diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h index 18db206ff..819b2f2fa 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h @@ -9,10 +9,11 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv { -bool AnalyzeProgramRequirements(IR::Program& program, std::string* error); +void AnalyzeProgramRequirements(IR::Program& program); -bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources, - ShaderStageInputInfo input_info, std::vector& spirv, std::string* error); +std::vector EmitProgram(const IR::Program& program, + const IR::ResourceSnapshot& resources, + ShaderStageInputInfo input_info); } // namespace Libs::Graphics::ShaderRecompiler::Spirv diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp index b0a47064e..dfabff995 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAnalysis.cpp @@ -37,12 +37,6 @@ bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr) { ShaderPixelParameterIsCustom(*state.input_info.pixel, attr); } -void SetError(std::string* error, const char* message) { - if (error != nullptr) { - *error = message; - } -} - bool HasOutput(const std::vector& outputs, IR::StageOutputKind kind, uint32_t index) { return std::any_of(outputs.begin(), outputs.end(), [kind, index](const OutputBinding& binding) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 7a41c2042..12e6e9b7c 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -439,7 +439,8 @@ struct ValueEmitContext { const IR::MemoryInfo& Memory(const IR::Inst& inst) const; const IR::ExportInfo& Export(const IR::Inst& inst) const; uint32_t Label(const IR::Block* block) const; - void Fail(const IR::Inst& inst, const char* reason); + [[noreturn]] void Fail(const char* reason) const; + [[noreturn]] void Fail(const IR::Inst& inst, const char* reason) const; EmitterState& state; const IR::Program& program; @@ -449,8 +450,6 @@ struct ValueEmitContext { std::unordered_map> dispatcher_block_loads; const IR::Block* current_block = nullptr; uint32_t scratch_u32_variable = 0; - bool failed = false; - std::string error; }; enum class VertexInputScalarKind { Float, Sint, Uint }; @@ -613,8 +612,6 @@ uint32_t VertexParameterVectorOrScalarType(EmitterState& state, VertexInputScala uint32_t VertexParameterInputPointerType(EmitterState& state, VertexInputScalarKind kind, uint32_t components); -void SetError(std::string* error, const char* message); - bool HasOutput(const std::vector& outputs, IR::StageOutputKind kind, uint32_t index); void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program); @@ -864,7 +861,7 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst); bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst); -bool EmitProgram(EmitterState& state, const IR::Program& program, std::string* error); +void EmitProgram(EmitterState& state, const IR::Program& program); void DefineGetBdaPointer(EmitterState& state); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp index ea134abaa..48e0fb66a 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp @@ -1,8 +1,9 @@ #include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h" +#include "common/assert.h" + #include #include -#include #include #include #include @@ -196,57 +197,50 @@ uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionSta } } -bool EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) { +void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) { if (EmitValueFlow(ctx, inst) || EmitValueAlu(ctx, inst) || EmitValueMemory(ctx, inst) || EmitValueImage(ctx, inst)) { - return true; + return; } ctx.Fail(inst, "has no direct SPIR-V emitter"); - return false; } -bool EmitStructuredInstruction(ValueEmitContext& ctx, StructuredFunctionState& structured, +void EmitStructuredInstruction(ValueEmitContext& ctx, StructuredFunctionState& structured, const IR::Inst& inst) { if (inst.GetOpcode() == IR::ValueOpcode::Phi) { const auto type = ctx.TypeId(inst.GetType()); if (type == 0 || inst.NumArgs() == 0) { ctx.Fail(inst, "has no native SPIR-V representation"); - return false; } for (size_t index = 0; index < inst.NumArgs(); index++) { const auto* predecessor = inst.PhiBlock(index); if (predecessor == nullptr || !ctx.labels.contains(predecessor)) { ctx.Fail(inst, "has a predecessor outside the structured function"); - return false; } } structured.deferred_phis.push_back( {ctx.state.builder.AddDeferredPhi(type, ctx.Result(inst), inst.NumArgs()), &inst}); - return true; + return; } - return EmitDirectInstruction(ctx, inst); + EmitDirectInstruction(ctx, inst); } -bool EmitDispatcherInstruction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher, +void EmitDispatcherInstruction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher, const IR::Inst& inst) { if (inst.GetOpcode() == IR::ValueOpcode::Phi) { const auto type = ctx.TypeId(inst.GetType()); if (type == 0) { ctx.Fail(inst, "cannot be loaded by the dispatcher"); - return false; } ctx.state.builder.AddFunction( {OpLoad, type, ctx.Result(inst), dispatcher.spills.at(&inst)}); - return true; - } - if (!EmitDirectInstruction(ctx, inst)) { - return false; + return; } + EmitDirectInstruction(ctx, inst); if (const auto found = dispatcher.spills.find(&inst); found != dispatcher.spills.end()) { ctx.state.builder.AddFunction( {OpStore, found->second, ctx.Def(IR::Value(const_cast(&inst)))}); } - return true; } template @@ -258,14 +252,11 @@ void EmitBlock(ValueEmitContext& ctx, const IR::Block* block, EmitInstruction&& if (inst.GetOpcode() == IR::ValueOpcode::Phi) { if (emitted_non_phi) { ctx.Fail(inst, "appears after a non-Phi instruction"); - return; } } else { emitted_non_phi = true; } - if (!emit_instruction(inst)) { - return; - } + emit_instruction(inst); } } @@ -275,15 +266,10 @@ void PatchStructuredPhis(ValueEmitContext& ctx, StructuredFunctionState& structu const auto* predecessor = deferred.instruction->PhiBlock(index); const auto found = structured.block_exit_labels.find(predecessor); if (found == structured.block_exit_labels.end()) { - ctx.failed = true; - ctx.error = "typed Phi predecessor was not emitted"; - return; + ctx.Fail(*deferred.instruction, "has a predecessor that was not emitted"); } ctx.state.builder.PatchDeferredPhi( deferred.phi, index, ctx.Def(deferred.instruction->Arg(index)), found->second); - if (ctx.failed) { - return; - } } } } @@ -291,20 +277,15 @@ void PatchStructuredPhis(ValueEmitContext& ctx, StructuredFunctionState& structu void EmitStructuredFunction(ValueEmitContext& ctx) { StructuredFunctionState structured; ctx.state.builder.AddFunction({OpBranch, ctx.Label(ctx.program.blocks.front())}); - for (size_t index = 0; index < ctx.program.blocks.size() && !ctx.failed; index++) { + for (size_t index = 0; index < ctx.program.blocks.size(); index++) { const auto* block = ctx.program.blocks[index]; EmitBlock(ctx, block, [&](const IR::Inst& inst) { - return EmitStructuredInstruction(ctx, structured, inst); + EmitStructuredInstruction(ctx, structured, inst); }); - if (ctx.failed) { - break; - } structured.block_exit_labels.emplace(block, ctx.state.current_label); EmitStructuredTerminator(ctx, block, ctx.program.block_info[index]); } - if (!ctx.failed) { - PatchStructuredPhis(ctx, structured); - } + PatchStructuredPhis(ctx, structured); } void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState& dispatcher) { @@ -312,11 +293,8 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState const auto* entry = ctx.program.blocks.front(); state.builder.AddFunction({OpBranch, ctx.Label(entry)}); EmitBlock(ctx, entry, [&](const IR::Inst& inst) { - return EmitDispatcherInstruction(ctx, dispatcher, inst); + EmitDispatcherInstruction(ctx, dispatcher, inst); }); - if (ctx.failed) { - return; - } const auto initial_pc = EmitDispatcherNextPc(ctx, dispatcher, entry, ctx.program.block_info.front()); const auto initial_parent = state.current_label; @@ -347,22 +325,16 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState std::vector next_pc_words {OpPhi, TypeU32(state), next_pc, ConstantU32(state, UINT32_MAX), dispatcher.select_label}; - for (size_t index = 1; index < ctx.program.blocks.size() && !ctx.failed; index++) { + for (size_t index = 1; index < ctx.program.blocks.size(); index++) { EmitBlock(ctx, ctx.program.blocks[index], [&](const IR::Inst& inst) { - return EmitDispatcherInstruction(ctx, dispatcher, inst); + EmitDispatcherInstruction(ctx, dispatcher, inst); }); - if (ctx.failed) { - break; - } const auto selected = EmitDispatcherNextPc(ctx, dispatcher, ctx.program.blocks[index], ctx.program.block_info[index]); next_pc_words.push_back(selected); next_pc_words.push_back(state.current_label); state.builder.AddFunction({OpBranch, dispatcher.after_switch_label}); } - if (ctx.failed) { - return; - } EmitLabel(state, dispatcher.after_switch_label); state.builder.AddFunction(next_pc_words); state.builder.AddFunction({OpBranch, dispatcher.continue_label}); @@ -408,9 +380,7 @@ uint32_t ValueEmitContext::Def(IR::Value value) { } const auto* inst = value.ResolveInstruction(); if (inst == nullptr) { - failed = true; - error = "direct SPIR-V emitter received a non-value argument"; - return ConstantU32(state, 0); + Fail("direct SPIR-V emitter received a non-value argument"); } if (dispatcher_spills != nullptr && current_block != nullptr && inst->Parent() != current_block) { @@ -469,9 +439,7 @@ uint32_t ValueEmitContext::Define(const IR::Inst& inst, uint32_t value) { uint32_t ValueEmitContext::ResourceIndex(IR::Value value, IR::ValueOpcode opcode) { const auto* inst = value.ResolveInstruction(); if (inst == nullptr || inst->GetOpcode() != opcode) { - failed = true; - error = "typed resource handle has the wrong producer"; - return 0; + Fail("typed resource handle has the wrong producer"); } return inst->Flags(); } @@ -479,9 +447,7 @@ uint32_t ValueEmitContext::ResourceIndex(IR::Value value, IR::ValueOpcode opcode const IR::Inst* ValueEmitContext::ImageAddress(IR::Value value) { const auto* inst = value.ResolveInstruction(); if (inst == nullptr || inst->GetOpcode() != IR::ValueOpcode::MakeImageAddress) { - failed = true; - error = "typed image address was not constructed by MakeImageAddress"; - return nullptr; + Fail("typed image address was not constructed by MakeImageAddress"); } return inst; } @@ -498,12 +464,20 @@ uint32_t ValueEmitContext::Label(const IR::Block* block) const { return labels.at(block); } -void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) { - failed = true; - error = fmt::format("typed opcode {} {}", IR::ValueOpcodeName(inst.GetOpcode()), reason); +[[noreturn]] void ValueEmitContext::Fail(const char* reason) const { + EXIT("SPIR-V emission failed: hash=0x%016" PRIx64 " stage=%u reason=%s\n", + program.shader_hash, static_cast(program.stage), reason); + std::abort(); } -bool EmitProgram(EmitterState& state, const IR::Program& program, std::string* error) { +[[noreturn]] void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) const { + EXIT("SPIR-V emission failed: hash=0x%016" PRIx64 " stage=%u opcode=%s reason=%s\n", + program.shader_hash, static_cast(program.stage), + IR::ValueOpcodeName(inst.GetOpcode()), reason); + std::abort(); +} + +void EmitProgram(EmitterState& state, const IR::Program& program) { ValueEmitContext ctx(state, program); std::optional dispatcher; if (state.stage == ShaderType::Pixel && state.requirements.pixel_valid_mask) { @@ -562,10 +536,6 @@ bool EmitProgram(EmitterState& state, const IR::Program& program, std::string* e dispatch.merge_label = state.builder.AllocateId(); ctx.dispatcher_spills = &dispatch.spills; } - if (ctx.failed) { - SetError(error, ctx.error.c_str()); - return false; - } DefineGetBdaPointer(state); for (const auto* block: program.blocks) { if (std::ranges::any_of(*block, [](const IR::Inst& inst) { @@ -630,11 +600,6 @@ bool EmitProgram(EmitterState& state, const IR::Program& program, std::string* e EmitStructuredFunction(ctx); } state.builder.AddFunction({OpFunctionEnd}); - if (ctx.failed) { - SetError(error, ctx.error.c_str()); - return false; - } - return true; } } // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter diff --git a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp index 2cf5c158d..a8c97570b 100644 --- a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp +++ b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.cpp @@ -1,5 +1,7 @@ #include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h" +#include "common/assert.h" + #include #include #include @@ -24,19 +26,18 @@ struct SetpcTargetInfo { std::vector selector_target_pcs; }; -void SetError(std::string* error, const std::string& message) { - if (error != nullptr) { - *error = message; - } -} - -void SetFailure(Graph& graph, FailureKind kind, uint32_t block_id, const std::string& message, - std::string* error) { +void SetFailure(Graph& graph, FailureKind kind, uint32_t block_id, const std::string& message) { graph.unsupported = true; graph.failure_kind = kind; graph.failure_block = block_id; graph.unsupported_reason = message; - SetError(error, message); +} + +[[noreturn]] void ExitBuildFailure(Graph& graph, FailureKind kind, uint32_t block_id, + const std::string& message) { + SetFailure(graph, kind, block_id, message); + EXIT("shader CFG build failed: %s", message.c_str()); + std::abort(); } uint32_t InstructionEndPc(const Instruction& inst) { @@ -1267,7 +1268,7 @@ bool MergeLeavesContainingLoop(const Graph& graph, uint32_t header, uint32_t mer return false; } -bool CanonicalizeNaturalLoops(Graph& graph, std::string* error) { +bool CanonicalizeNaturalLoops(Graph& graph) { const auto rewrite_budget = graph.blocks.size() * 2u + 16u; for (size_t rewrite = 0; rewrite < rewrite_budget; rewrite++) { bool changed = false; @@ -1322,19 +1323,18 @@ bool CanonicalizeNaturalLoops(Graph& graph, std::string* error) { } SetFailure(graph, FailureKind::StructuredControlFlow, graph.entry_block, - "CFG loop canonicalization exceeded rewrite budget", error); + "CFG loop canonicalization exceeded rewrite budget"); return false; } -bool IsolateSemanticLoopHeaders(Graph& graph, std::string* error) { +bool IsolateSemanticLoopHeaders(Graph& graph) { const auto isolation_budget = graph.natural_loops.size() + 1u; for (size_t isolation = 0; isolation < isolation_budget; isolation++) { - const auto loop = std::find_if(graph.natural_loops.begin(), graph.natural_loops.end(), - [&](const auto& value) { - const auto* header = graph.FindBlock(value.header); - return header != nullptr && - header->inst_begin != header->inst_end; - }); + const auto loop = std::find_if( + graph.natural_loops.begin(), graph.natural_loops.end(), [&](const auto& value) { + const auto* header = graph.FindBlock(value.header); + return header != nullptr && header->inst_begin != header->inst_end; + }); if (loop == graph.natural_loops.end()) { return true; } @@ -1346,14 +1346,13 @@ bool IsolateSemanticLoopHeaders(Graph& graph, std::string* error) { // displacing OpLoopMerge into a helper-created block. if (!IsolateSemanticLoopHeader(graph, loop->header)) { SetFailure(graph, FailureKind::StructuredControlFlow, loop->header, - fmt::format("failed to isolate semantic loop header {}", loop->header), - error); + fmt::format("failed to isolate semantic loop header {}", loop->header)); return false; } } SetFailure(graph, FailureKind::StructuredControlFlow, graph.entry_block, - "CFG semantic loop-header isolation exceeded rewrite budget", error); + "CFG semantic loop-header isolation exceeded rewrite budget"); return false; } @@ -1453,7 +1452,7 @@ std::vector SelectionRegion(const Graph& graph, const BasicBlock& head return region; } -bool SplitOneSelectionMerge(Graph& graph, std::string* error) { +bool SplitOneSelectionMerge(Graph& graph) { std::vector loop_headers; loop_headers.reserve(graph.natural_loops.size()); for (const auto& loop: graph.natural_loops) { @@ -1501,8 +1500,7 @@ bool SplitOneSelectionMerge(Graph& graph, std::string* error) { graph, FailureKind::StructuredControlFlow, block_id, fmt::format("selection header block {} has externally entered region block {}; " "semantic block cloning is disabled", - block_id, *external), - error); + block_id, *external)); return false; } const auto construct_blocks = DominatedBlocks(graph, block_id, merge); @@ -1514,11 +1512,11 @@ bool SplitOneSelectionMerge(Graph& graph, std::string* error) { return false; } -bool SplitSharedMergeBlocks(Graph& graph, std::string* error) { +bool SplitSharedMergeBlocks(Graph& graph) { const auto original_block_count = static_cast(graph.blocks.size()); const auto split_budget = std::max(16u, original_block_count * 4u); for (uint32_t splits = 0; splits < split_budget; splits++) { - if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph, error)) { + if (!SplitOneLoopMerge(graph) && !SplitOneSelectionMerge(graph)) { return !graph.unsupported; } RebuildPredecessors(graph); @@ -1528,8 +1526,7 @@ bool SplitSharedMergeBlocks(Graph& graph, std::string* error) { fmt::format("CFG shared merge splitting exceeded budget: original_blocks={} " "current_blocks={} split_budget={}", original_block_count, static_cast(graph.blocks.size()), - split_budget), - error); + split_budget)); return false; } @@ -1890,12 +1887,11 @@ bool RouteSharedSelectionArm(Graph& graph, uint32_t route_variable) { } // namespace -bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* error) { - graph = {}; - +Graph BuildGraph(const Decoder::Program& program) { + Graph graph; if (program.instructions.empty()) { - SetError(error, "cannot build CFG for empty shader"); - return false; + ExitBuildFailure(graph, FailureKind::InvalidLabel, UINT32_MAX, + "cannot build CFG for empty shader"); } const auto first_pc = program.instructions.front().pc; @@ -1905,11 +1901,10 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro for (const auto& inst: program.instructions) { instruction_pcs.insert(inst.pc); if (inst.opcode == Opcode::UNSUPPORTED) { - SetFailure(graph, FailureKind::UnsupportedInstruction, UINT32_MAX, - fmt::format("unsupported decoded instruction in CFG at pc 0x{:08x}: {}", - inst.pc, Decoder::InstructionToString(inst).c_str()), - error); - return false; + ExitBuildFailure( + graph, FailureKind::UnsupportedInstruction, UINT32_MAX, + fmt::format("unsupported decoded instruction in CFG at pc 0x{:08x}: {}", inst.pc, + Decoder::InstructionToString(inst).c_str())); } } @@ -1923,11 +1918,9 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro const auto next_pc = InstructionEndPc(inst); if (IsBranch(inst.opcode)) { if (!IsValidTarget(inst.branch_target, instruction_pcs, first_pc, end_pc)) { - SetFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX, - fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}", inst.pc, - inst.branch_target), - error); - return false; + ExitBuildFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX, + fmt::format("branch at pc 0x{:08x} targets invalid pc 0x{:08x}", + inst.pc, inst.branch_target)); } labels.insert(inst.branch_target); if (next_pc <= end_pc) { @@ -1936,21 +1929,19 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro } else if (inst.opcode == Opcode::S_SETPC_B64) { SetpcTargetInfo target_info; if (!ResolveSetpcTargets(program, i, target_info)) { - SetFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX, - fmt::format("unsupported dynamic S_SETPC_B64 at pc 0x{:08x}", inst.pc), - error); - return false; + ExitBuildFailure( + graph, FailureKind::InvalidBranchTarget, UINT32_MAX, + fmt::format("unsupported dynamic S_SETPC_B64 at pc 0x{:08x}", inst.pc)); } const auto& target_pcs = target_info.indirect ? target_info.target_pcs : std::vector {target_info.target}; for (const auto target: target_pcs) { if (!IsValidTarget(target, instruction_pcs, first_pc, end_pc)) { - SetFailure(graph, FailureKind::InvalidBranchTarget, UINT32_MAX, - fmt::format("S_SETPC_B64 at pc 0x{:08x} targets invalid pc 0x{:08x}", - inst.pc, target), - error); - return false; + ExitBuildFailure( + graph, FailureKind::InvalidBranchTarget, UINT32_MAX, + fmt::format("S_SETPC_B64 at pc 0x{:08x} targets invalid pc 0x{:08x}", + inst.pc, target)); } labels.insert(target); } @@ -1971,10 +1962,9 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro continue; } if (start != end_pc && !instruction_pcs.contains(start)) { - SetFailure(graph, FailureKind::InvalidLabel, UINT32_MAX, - fmt::format("CFG label does not start on an instruction: 0x{:08x}", start), - error); - return false; + ExitBuildFailure( + graph, FailureKind::InvalidLabel, UINT32_MAX, + fmt::format("CFG label does not start on an instruction: 0x{:08x}", start)); } BasicBlock block; @@ -2046,11 +2036,10 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro block.terminator.true_block = pc_to_block.at(last.branch_target); const auto fallthrough = pc_to_block.find(next_pc); if (fallthrough == pc_to_block.end()) { - SetFailure(graph, FailureKind::MissingFallthrough, block.id, - fmt::format("conditional branch at pc 0x{:08x} has no fallthrough block", - last.pc), - error); - return false; + ExitBuildFailure( + graph, FailureKind::MissingFallthrough, block.id, + fmt::format("conditional branch at pc 0x{:08x} has no fallthrough block", + last.pc)); } block.terminator.false_block = fallthrough->second; } else { @@ -2131,27 +2120,26 @@ bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* erro graph.unsupported = false; } - return true; + return graph; } namespace { -bool StructurizeImpl(Graph& graph, std::string* error) { +bool StructurizeImpl(Graph& graph) { if (graph.unsupported || graph.irreducible) { if (graph.unsupported_reason.empty()) { graph.unsupported_reason = "unsupported CFG"; } - SetError(error, graph.unsupported_reason); return false; } - if (!CanonicalizeNaturalLoops(graph, error)) { + if (!CanonicalizeNaturalLoops(graph)) { return false; } - if (!SplitSharedMergeBlocks(graph, error)) { + if (!SplitSharedMergeBlocks(graph)) { return false; } - if (!IsolateSemanticLoopHeaders(graph, error)) { + if (!IsolateSemanticLoopHeaders(graph)) { return false; } ClearStructuredTerminators(graph); @@ -2164,8 +2152,7 @@ bool StructurizeImpl(Graph& graph, std::string* error) { graph, FailureKind::StructuredControlFlow, header, fmt::format( "duplicate structured merge block {} for header {} (already used by header {})", - merge, header, it->second), - error); + merge, header, it->second)); return false; } return true; @@ -2176,16 +2163,14 @@ bool StructurizeImpl(Graph& graph, std::string* error) { if (header == nullptr || loop.merge == UINT32_MAX || loop.continue_block == UINT32_MAX) { SetFailure( graph, FailureKind::StructuredControlFlow, loop.header, - fmt::format("loop at block {} has no structured merge/continue", loop.header), - error); + fmt::format("loop at block {} has no structured merge/continue", loop.header)); return false; } if (header->inst_begin != header->inst_end || header->terminator.kind != TerminatorKind::Branch) { - SetFailure(graph, FailureKind::StructuredControlFlow, loop.header, - fmt::format("loop header {} is not a dedicated empty control block", - loop.header), - error); + SetFailure( + graph, FailureKind::StructuredControlFlow, loop.header, + fmt::format("loop header {} is not a dedicated empty control block", loop.header)); return false; } if (!reserve_merge_block(loop.header, loop.merge)) { @@ -2208,8 +2193,7 @@ bool StructurizeImpl(Graph& graph, std::string* error) { const auto merge = FindSelectionMerge(graph, block); if (merge == UINT32_MAX) { SetFailure(graph, FailureKind::StructuredControlFlow, block.id, - fmt::format("conditional block {} has no structured merge", block.id), - error); + fmt::format("conditional block {} has no structured merge", block.id)); return false; } @@ -2224,16 +2208,15 @@ bool StructurizeImpl(Graph& graph, std::string* error) { } // namespace -bool Structurize(Graph& graph, std::string* error) { +bool Structurize(Graph& graph) { Graph original = graph; - if (StructurizeImpl(graph, error)) { + if (StructurizeImpl(graph)) { return true; } - Graph failed_graph = std::move(graph); - std::string failed_error = error != nullptr ? *error : std::string {}; - graph = std::move(original); - const auto route_budget = static_cast(graph.blocks.size()); + Graph failed_graph = std::move(graph); + graph = std::move(original); + const auto route_budget = static_cast(graph.blocks.size()); // Apply one route at a time and retry. Eagerly routing every matching diamond can // rewrite unrelated selections that were already structurally valid. for (uint32_t route_variable = 0; route_variable < route_budget; route_variable++) { @@ -2241,18 +2224,12 @@ bool Structurize(Graph& graph, std::string* error) { break; } Graph routed = graph; - if (error != nullptr) { - error->clear(); - } - if (StructurizeImpl(routed, error)) { + if (StructurizeImpl(routed)) { graph = std::move(routed); return true; } } graph = std::move(failed_graph); - if (error != nullptr) { - *error = std::move(failed_error); - } return false; } diff --git a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.h b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.h index cb47f3100..0c26a1105 100644 --- a/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.h +++ b/src/graphics/shader/recompiler/frontend/cfg/ShaderCFG.h @@ -108,8 +108,8 @@ struct Graph { uint32_t FindNearestCommonPostDominator(uint32_t block_a, uint32_t block_b) const; }; -bool BuildGraph(const Decoder::Program& program, Graph& graph, std::string* error); -bool Structurize(Graph& graph, std::string* error); +Graph BuildGraph(const Decoder::Program& program); +bool Structurize(Graph& graph); std::string BranchConditionToString(BranchCondition condition); std::string FailureKindToString(FailureKind kind); std::string GraphToString(const Graph& graph); diff --git a/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp index d81a998d5..6a8b02f58 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp @@ -357,8 +357,8 @@ void DecodeMimg(uint32_t pc, std::span code, uint32_t word_index DecodeVectorGpr(vdata, inst.dst); DecodeVectorGpr(vaddr, inst.src0); - DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr); - DecodeScalarSource(ssamp * 4u, pc, inst.src2, nullptr); + DecodeScalarSource(srsrc * 4u, pc, inst.src1); + DecodeScalarSource(ssamp * 4u, pc, inst.src2); inst.src_count = 3; } diff --git a/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp b/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp index 263d851c3..92bd8a59c 100644 --- a/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp @@ -241,11 +241,11 @@ void DecodeSmem(uint32_t pc, std::span code, uint32_t word_index MarkMemoryUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented"); } - DecodeScalarDestination(sdst, pc, inst.dst, nullptr); + DecodeScalarDestination(sdst, pc, inst.dst); // SMEM encodes SBASE in SGPR pairs. Scalar-buffer loads still use the same // pair index; their descriptor operand consumes four SGPRs from that base. - DecodeScalarSource(sbase * 2u, pc, inst.src0, nullptr); - DecodeScalarSource(soffset, pc, inst.src1, nullptr); + DecodeScalarSource(sbase * 2u, pc, inst.src0); + DecodeScalarSource(soffset, pc, inst.src1); inst.src_count = 2; } @@ -278,8 +278,8 @@ void DecodeMubuf(uint32_t pc, std::span code, uint32_t word_inde DecodeVectorGpr(vdata, inst.dst); DecodeVectorGpr(vaddr, inst.src0); - DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr); - DecodeScalarSource(soffset, pc, inst.src2, nullptr); + DecodeScalarSource(srsrc * 4u, pc, inst.src1); + DecodeScalarSource(soffset, pc, inst.src2); inst.src_count = 3; } @@ -316,8 +316,8 @@ void DecodeMtbuf(uint32_t pc, std::span code, uint32_t word_inde DecodeVectorGpr(vdata, inst.dst); DecodeVectorGpr(vaddr, inst.src0); - DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr); - DecodeScalarSource(soffset, pc, inst.src2, nullptr); + DecodeScalarSource(srsrc * 4u, pc, inst.src1); + DecodeScalarSource(soffset, pc, inst.src2); inst.src_count = 3; } @@ -364,7 +364,7 @@ void DecodeFlat(uint32_t pc, std::span code, uint32_t word_index DecodeVectorGpr(addr + 1u, inst.src1); inst.src_count = 2; } else { - DecodeScalarSource(saddr, pc, inst.src1, nullptr); + DecodeScalarSource(saddr, pc, inst.src1); inst.src_count = 2; } } diff --git a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp index 9a729d4a6..404f3bb05 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.cpp @@ -105,21 +105,18 @@ constexpr auto SOPK_OPS = Detail::MakeOpcodeTable<0x20>(SOPK_OPCODE_LIST); constexpr auto SOPC_OPS = Detail::MakeOpcodeTable<0x80>(SOPC_OPCODE_LIST); constexpr auto SOPP_OPS = Detail::MakeOpcodeTable<0x80>(SOPP_OPCODE_LIST); -bool DecodeBinarySources(uint32_t pc, std::span code, uint32_t word_index, - Instruction& inst, uint32_t ssrc0, uint32_t ssrc1, std::string* error) { - if (!DecodeScalarSource(ssrc0, pc, inst.src0, error) || - !DecodeScalarSource(ssrc1, pc, inst.src1, error)) { - return false; - } +void DecodeBinarySources(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst, uint32_t ssrc0, uint32_t ssrc1) { + DecodeScalarSource(ssrc0, pc, inst.src0); + DecodeScalarSource(ssrc1, pc, inst.src1); inst.src_count = 2; ReadLiteralOperands(code, word_index, inst); - return true; } } // namespace -bool DecodeSop1(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeSop1(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word = code[word_index]; const uint32_t opcode = (word >> 8u) & 0xffu; const uint32_t ssrc0 = word & 0xffu; @@ -134,35 +131,31 @@ bool DecodeSop1(uint32_t pc, std::span code, uint32_t word_index if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::SOP1, opcode, "SOP1 opcode is not implemented"); - return true; + return; } switch (inst.opcode) { case Opcode::S_GETPC_B64: inst.src_count = 0; - return DecodeScalarDestination(sdst, pc, inst.dst, error); + DecodeScalarDestination(sdst, pc, inst.dst); + return; case Opcode::S_SETPC_B64: inst.src_count = 1; inst.dst.kind = OperandKind::Null; - if (!DecodeScalarSource(ssrc0, pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(ssrc0, pc, inst.src0); ReadLiteralOperands(code, word_index, inst); - return true; + return; default: break; } - if (!DecodeScalarSource(ssrc0, pc, inst.src0, error) || - !DecodeScalarDestination(sdst, pc, inst.dst, error)) { - return false; - } + DecodeScalarSource(ssrc0, pc, inst.src0); + DecodeScalarDestination(sdst, pc, inst.dst); inst.src_count = 1; ReadLiteralOperands(code, word_index, inst); - return true; } -bool DecodeSop2(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeSop2(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word = code[word_index]; const uint32_t opcode = (word >> 23u) & 0x7fu; const uint32_t ssrc1 = (word >> 8u) & 0xffu; @@ -178,17 +171,15 @@ bool DecodeSop2(uint32_t pc, std::span code, uint32_t word_index if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::SOP2, opcode, "SOP2 opcode is not implemented"); - return true; + return; } - if (!DecodeScalarDestination(sdst, pc, inst.dst, error)) { - return false; - } - return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error); + DecodeScalarDestination(sdst, pc, inst.dst); + DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1); } -bool DecodeSopk(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeSopk(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word = code[word_index]; const uint32_t opcode = (word >> 23u) & 0x1fu; const uint32_t sdst = (word >> 16u) & 0x7fu; @@ -207,18 +198,18 @@ bool DecodeSopk(uint32_t pc, std::span code, uint32_t word_index if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::SOPK, opcode, "SOPK opcode is not implemented"); - return true; + return; } switch (inst.opcode) { - case Opcode::S_MOVK_I32: return DecodeScalarDestination(sdst, pc, inst.dst, error); + case Opcode::S_MOVK_I32: DecodeScalarDestination(sdst, pc, inst.dst); return; case Opcode::S_WAITCNT: { const uint32_t waitcnt = word & 0xffffu; inst.dst.kind = OperandKind::Null; inst.src0.signed_val = static_cast(waitcnt); inst.src0.value = waitcnt; inst.src_count = 1; - return true; + return; } case Opcode::S_SETREG_B32: inst.dst.kind = OperandKind::Null; @@ -226,26 +217,25 @@ bool DecodeSopk(uint32_t pc, std::span code, uint32_t word_index inst.src1.value = word & 0xffffu; inst.src1.signed_val = static_cast(imm); inst.src_count = 2; - return DecodeScalarSource(sdst, pc, inst.src0, error); + DecodeScalarSource(sdst, pc, inst.src0); + return; default: break; } inst.src1 = inst.src0; - if (!DecodeScalarSource(sdst, pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(sdst, pc, inst.src0); if (inst.opcode == Opcode::S_ADD_I32 || inst.opcode == Opcode::S_MULK_I32) { inst.src_count = 2; - return DecodeScalarDestination(sdst, pc, inst.dst, error); + DecodeScalarDestination(sdst, pc, inst.dst); + return; } inst.dst.kind = OperandKind::Scc; inst.src_count = 2; - return true; } -bool DecodeSopc(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeSopc(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word = code[word_index]; const uint32_t ssrc1 = (word >> 8u) & 0xffu; const uint32_t ssrc0 = word & 0xffu; @@ -261,10 +251,10 @@ bool DecodeSopc(uint32_t pc, std::span code, uint32_t word_index if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::SOPC, opcode, "SOPC opcode is not implemented"); - return true; + return; } - return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error); + DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1); } void DecodeSopp(uint32_t pc, std::span code, uint32_t word_index, diff --git a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.h b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.h index bf943dc9f..7a781de1b 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.h +++ b/src/graphics/shader/recompiler/frontend/decode/ScalarAluOps.h @@ -5,14 +5,14 @@ namespace Libs::Graphics::ShaderRecompiler::Decoder { -bool DecodeSop1(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error); -bool DecodeSop2(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error); -bool DecodeSopk(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error); -bool DecodeSopc(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error); +void DecodeSop1(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); +void DecodeSop2(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); +void DecodeSopk(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); +void DecodeSopc(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); void DecodeSopp(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst); diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index b3e996068..f73dec858 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -1,5 +1,6 @@ #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" +#include "common/assert.h" #include "graphics/shader/recompiler/frontend/decode/ExportOps.h" #include "graphics/shader/recompiler/frontend/decode/ImageOps.h" #include "graphics/shader/recompiler/frontend/decode/MemoryOps.h" @@ -17,12 +18,6 @@ uint32_t FloatBits(float value) { return std::bit_cast(value); } -void SetError(std::string* error, const char* message) { - if (error != nullptr) { - *error = message; - } -} - bool HasLiteral(const Instruction& inst) { return inst.src0.kind == OperandKind::LiteralConstant || inst.src1.kind == OperandKind::LiteralConstant || @@ -216,86 +211,76 @@ const char* ImageDimensionToString(ImageDimension dimension) { } } -bool DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand, std::string* error) { +void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand) { operand = {}; if (code <= 105u) { operand.kind = OperandKind::Sgpr; operand.reg = code; - return true; + return; } if (code >= 128u && code <= 192u) { operand.kind = OperandKind::IntegerInlineConstant; operand.signed_val = static_cast(code - 128u); operand.value = static_cast(operand.signed_val); - return true; + return; } if (code >= 193u && code <= 208u) { operand.kind = OperandKind::IntegerInlineConstant; operand.signed_val = 192 - static_cast(code); operand.value = static_cast(operand.signed_val); - return true; + return; } if (code >= 240u && code <= 247u) { constexpr float values[] = {0.5f, -0.5f, 1.0f, -1.0f, 2.0f, -2.0f, 4.0f, -4.0f}; operand.kind = OperandKind::FloatInlineConstant; operand.float_val = values[code - 240u]; operand.value = FloatBits(operand.float_val); - return true; + return; } if (code >= 256u && code <= 511u) { DecodeVectorGpr(code - 256u, operand); - return true; + return; } switch (code) { - case 106u: operand.kind = OperandKind::VccLo; return true; - case 107u: operand.kind = OperandKind::VccHi; return true; - case 124u: operand.kind = OperandKind::M0; return true; - case 125u: operand.kind = OperandKind::Null; return true; - case 126u: operand.kind = OperandKind::ExecLo; return true; - case 127u: operand.kind = OperandKind::ExecHi; return true; - case 239u: operand.kind = OperandKind::PopsExitingWaveId; return true; + case 106u: operand.kind = OperandKind::VccLo; return; + case 107u: operand.kind = OperandKind::VccHi; return; + case 124u: operand.kind = OperandKind::M0; return; + case 125u: operand.kind = OperandKind::Null; return; + case 126u: operand.kind = OperandKind::ExecLo; return; + case 127u: operand.kind = OperandKind::ExecHi; return; + case 239u: operand.kind = OperandKind::PopsExitingWaveId; return; case 248u: operand.kind = OperandKind::FloatInlineConstant; operand.float_val = 0.15915494309189535f; operand.value = FloatBits(operand.float_val); - return true; - case 251u: operand.kind = OperandKind::VccZ; return true; - case 252u: operand.kind = OperandKind::ExecZ; return true; - case 253u: operand.kind = OperandKind::Scc; return true; - case 255u: operand.kind = OperandKind::LiteralConstant; return true; - default: - if (error != nullptr) { - *error = fmt::format("unsupported scalar source operand 0x{:08x} at pc 0x{:08x}", - code, pc); - } - return false; + return; + case 251u: operand.kind = OperandKind::VccZ; return; + case 252u: operand.kind = OperandKind::ExecZ; return; + case 253u: operand.kind = OperandKind::Scc; return; + case 255u: operand.kind = OperandKind::LiteralConstant; return; + default: EXIT("unsupported scalar source operand 0x%08x at pc 0x%08x", code, pc); } } -bool DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand, std::string* error) { +void DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand) { operand = {}; if (code <= 105u) { operand.kind = OperandKind::Sgpr; operand.reg = code; - return true; + return; } switch (code) { - case 106u: operand.kind = OperandKind::VccLo; return true; - case 107u: operand.kind = OperandKind::VccHi; return true; - case 124u: operand.kind = OperandKind::M0; return true; - case 125u: operand.kind = OperandKind::Null; return true; - case 126u: operand.kind = OperandKind::ExecLo; return true; - case 127u: operand.kind = OperandKind::ExecHi; return true; - default: - if (error != nullptr) { - *error = fmt::format( - "unsupported scalar destination operand 0x{:08x} at pc 0x{:08x}", code, pc); - } - return false; + case 106u: operand.kind = OperandKind::VccLo; return; + case 107u: operand.kind = OperandKind::VccHi; return; + case 124u: operand.kind = OperandKind::M0; return; + case 125u: operand.kind = OperandKind::Null; return; + case 126u: operand.kind = OperandKind::ExecLo; return; + case 127u: operand.kind = OperandKind::ExecHi; return; + default: EXIT("unsupported scalar destination operand 0x%08x at pc 0x%08x", code, pc); } } @@ -368,39 +353,33 @@ Family GetInstructionFamily(uint32_t word) { } } -bool DecodeInstruction(std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeInstruction(std::span code, uint32_t word_index, Instruction& inst) { const uint32_t pc = word_index * sizeof(uint32_t); switch (GetInstructionFamily(code[word_index])) { - case Family::SOP1: return DecodeSop1(pc, code, word_index, inst, error); - case Family::SOP2: return DecodeSop2(pc, code, word_index, inst, error); - case Family::SOPK: return DecodeSopk(pc, code, word_index, inst, error); - case Family::SOPC: return DecodeSopc(pc, code, word_index, inst, error); - case Family::SOPP: DecodeSopp(pc, code, word_index, inst); return true; - case Family::VOP1: return DecodeVop1(pc, code, word_index, inst, error); - case Family::VOP2: return DecodeVop2(pc, code, word_index, inst, error); - case Family::VOP3: return DecodeVop3(pc, code, word_index, inst, error); - case Family::VOP3P: return DecodeVop3p(pc, code, word_index, inst, error); - case Family::VOPC: return DecodeVopc(pc, code, word_index, inst, error); - case Family::VINTRP: DecodeVintrp(pc, code, word_index, inst); return true; - case Family::SMEM: DecodeSmem(pc, code, word_index, inst); return true; - case Family::MUBUF: DecodeMubuf(pc, code, word_index, inst); return true; - case Family::MTBUF: DecodeMtbuf(pc, code, word_index, inst); return true; - case Family::FLAT: DecodeFlat(pc, code, word_index, inst); return true; - case Family::DS: DecodeDs(pc, code, word_index, inst); return true; - case Family::MIMG: DecodeMimg(pc, code, word_index, inst); return true; - case Family::EXP: DecodeExp(pc, code, word_index, inst); return true; + case Family::SOP1: DecodeSop1(pc, code, word_index, inst); return; + case Family::SOP2: DecodeSop2(pc, code, word_index, inst); return; + case Family::SOPK: DecodeSopk(pc, code, word_index, inst); return; + case Family::SOPC: DecodeSopc(pc, code, word_index, inst); return; + case Family::SOPP: DecodeSopp(pc, code, word_index, inst); return; + case Family::VOP1: DecodeVop1(pc, code, word_index, inst); return; + case Family::VOP2: DecodeVop2(pc, code, word_index, inst); return; + case Family::VOP3: DecodeVop3(pc, code, word_index, inst); return; + case Family::VOP3P: DecodeVop3p(pc, code, word_index, inst); return; + case Family::VOPC: DecodeVopc(pc, code, word_index, inst); return; + case Family::VINTRP: DecodeVintrp(pc, code, word_index, inst); return; + case Family::SMEM: DecodeSmem(pc, code, word_index, inst); return; + case Family::MUBUF: DecodeMubuf(pc, code, word_index, inst); return; + case Family::MTBUF: DecodeMtbuf(pc, code, word_index, inst); return; + case Family::FLAT: DecodeFlat(pc, code, word_index, inst); return; + case Family::DS: DecodeDs(pc, code, word_index, inst); return; + case Family::MIMG: DecodeMimg(pc, code, word_index, inst); return; + case Family::EXP: DecodeExp(pc, code, word_index, inst); return; default: - if (error != nullptr) { - *error = - fmt::format("unknown RDNA2 instruction family at pc 0x{:08x}, raw=0x{:08x}", pc, - code[word_index]); - } - return false; + EXIT("unknown RDNA2 instruction family at pc 0x%08x, raw=0x%08x", pc, code[word_index]); } } -bool DecodeProgram(std::span code, Program& program, std::string* error) { +void DecodeProgram(std::span code, Program& program) { program.instructions.clear(); program.instructions.reserve(code.size()); program.code = code; @@ -408,10 +387,7 @@ bool DecodeProgram(std::span code, Program& program, std::string std::vector branch_targets; for (uint32_t word_index = 0; word_index < code.size();) { program.instructions.emplace_back(); - if (!DecodeInstruction(code, word_index, program.instructions.back(), error)) { - program.instructions.pop_back(); - return false; - } + DecodeInstruction(code, word_index, program.instructions.back()); const auto& inst = program.instructions.back(); word_index += inst.word_count; @@ -425,12 +401,11 @@ bool DecodeProgram(std::span code, Program& program, std::string } if (inst.opcode == Opcode::S_ENDPGM && (word_index >= code.size() || branch_targets.empty() || !branch_targets[word_index])) { - return true; + return; } } - SetError(error, "shader decode reached the code boundary before S_ENDPGM"); - return false; + EXIT("shader decode reached the code boundary before S_ENDPGM"); } std::string OperandToString(const Operand& operand) { diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index cbdd4ba36..3c5a1bebb 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -704,12 +704,11 @@ struct Program { // Code spans are trusted to contain complete instructions, valid branch targets, and 32-bit PCs. Family GetInstructionFamily(uint32_t word); // The output object must be freshly initialized. -bool DecodeInstruction(std::span code, uint32_t word_index, Instruction& inst, - std::string* error); -bool DecodeProgram(std::span code, Program& program, std::string* error); +void DecodeInstruction(std::span code, uint32_t word_index, Instruction& inst); +void DecodeProgram(std::span code, Program& program); -bool DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand, std::string* error); -bool DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand, std::string* error); +void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand); +void DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand); void DecodeVectorGpr(uint32_t reg, Operand& operand); void ReadLiteralOperands(std::span code, uint32_t word_index, Instruction& inst); void SetRawWords(Instruction& inst, std::span code, uint32_t word_index, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 600b6a410..d2ba2cdec 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -204,55 +204,52 @@ struct VopcOpcodeInfo { }; constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = { - {0x00u, Opcode::V_CMP_F_F32}, {0x01u, Opcode::V_CMP_LT_F32}, - {0x02u, Opcode::V_CMP_EQ_F32}, {0x03u, Opcode::V_CMP_LE_F32}, - {0x04u, Opcode::V_CMP_GT_F32}, {0x05u, Opcode::V_CMP_LG_F32}, - {0x06u, Opcode::V_CMP_GE_F32}, {0x07u, Opcode::V_CMP_O_F32}, - {0x08u, Opcode::V_CMP_U_F32}, {0x09u, Opcode::V_CMP_NGE_F32}, - {0x0au, Opcode::V_CMP_NLG_F32}, {0x0bu, Opcode::V_CMP_NGT_F32}, - {0x0cu, Opcode::V_CMP_NLE_F32}, {0x0du, Opcode::V_CMP_NEQ_F32}, - {0x0eu, Opcode::V_CMP_NLT_F32}, {0x0fu, Opcode::V_CMP_TRU_F32}, - {0x11u, Opcode::V_CMPX_LT_F32}, {0x12u, Opcode::V_CMPX_EQ_F32}, - {0x13u, Opcode::V_CMPX_LE_F32}, {0x14u, Opcode::V_CMPX_GT_F32}, - {0x15u, Opcode::V_CMPX_LG_F32}, {0x16u, Opcode::V_CMPX_GE_F32}, - {0x19u, Opcode::V_CMPX_NGE_F32}, {0x1au, Opcode::V_CMPX_NLG_F32}, - {0x1bu, Opcode::V_CMPX_NGT_F32}, {0x1cu, Opcode::V_CMPX_NLE_F32}, - {0x1du, Opcode::V_CMPX_NEQ_F32}, {0x1eu, Opcode::V_CMPX_NLT_F32}, - {0x80u, Opcode::V_CMP_F_I32}, {0x81u, Opcode::V_CMP_LT_I32}, - {0x82u, Opcode::V_CMP_EQ_I32}, {0x83u, Opcode::V_CMP_LE_I32}, - {0x84u, Opcode::V_CMP_GT_I32}, {0x85u, Opcode::V_CMP_NE_I32}, - {0x86u, Opcode::V_CMP_GE_I32}, {0x87u, Opcode::V_CMP_T_I32}, - {0x88u, Opcode::V_CMP_CLASS_F32}, {0x89u, Opcode::V_CMP_LT_I16}, - {0x8au, Opcode::V_CMP_EQ_I16}, {0x8bu, Opcode::V_CMP_LE_I16}, - {0x8cu, Opcode::V_CMP_GT_I16}, {0x8du, Opcode::V_CMP_NE_I16}, - {0x8eu, Opcode::V_CMP_GE_I16}, {0x91u, Opcode::V_CMPX_LT_I32}, - {0x92u, Opcode::V_CMPX_EQ_I32}, {0x93u, Opcode::V_CMPX_LE_I32}, - {0x94u, Opcode::V_CMPX_GT_I32}, {0x95u, Opcode::V_CMPX_NE_I32}, - {0x96u, Opcode::V_CMPX_GE_I32}, {0x98u, Opcode::V_CMPX_CLASS_F32}, - {0xa9u, Opcode::V_CMP_LT_U16}, - {0xaau, Opcode::V_CMP_EQ_U16}, {0xabu, Opcode::V_CMP_LE_U16}, - {0xacu, Opcode::V_CMP_GT_U16}, {0xadu, Opcode::V_CMP_NE_U16}, - {0xaeu, Opcode::V_CMP_GE_U16}, {0xbcu, Opcode::V_CMPX_GT_U16}, - {0xc0u, Opcode::V_CMP_F_U32}, - {0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32}, - {0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32}, - {0xc5u, Opcode::V_CMP_NE_U32}, {0xc6u, Opcode::V_CMP_GE_U32}, - {0xc7u, Opcode::V_CMP_T_U32}, {0xa2u, Opcode::V_CMP_EQ_I64, false}, - {0xb5u, Opcode::V_CMPX_NE_I64, false}, - {0xd1u, Opcode::V_CMPX_LT_U32}, {0xd2u, Opcode::V_CMPX_EQ_U32}, - {0xd3u, Opcode::V_CMPX_LE_U32}, {0xd4u, Opcode::V_CMPX_GT_U32}, - {0xd5u, Opcode::V_CMPX_NE_U32}, {0xd6u, Opcode::V_CMPX_GE_U32}, - {0xe2u, Opcode::V_CMP_EQ_U64, false}, {0xe4u, Opcode::V_CMP_GT_U64, false}, - {0xe5u, Opcode::V_CMP_NE_U64, false}, - {0xf5u, Opcode::V_CMPX_NE_U64, false}, - {0xc9u, Opcode::V_CMP_LT_F16}, {0xcau, Opcode::V_CMP_EQ_F16}, - {0xcbu, Opcode::V_CMP_LE_F16}, {0xccu, Opcode::V_CMP_GT_F16}, - {0xcdu, Opcode::V_CMP_LG_F16}, {0xceu, Opcode::V_CMP_GE_F16}, - {0xedu, Opcode::V_CMP_NEQ_F16}, {0xd9u, Opcode::V_CMPX_LT_F16}, - {0xdau, Opcode::V_CMPX_EQ_F16}, {0xdbu, Opcode::V_CMPX_LE_F16}, - {0xdcu, Opcode::V_CMPX_GT_F16}, {0xdeu, Opcode::V_CMPX_GE_F16}, - {0xfbu, Opcode::V_CMPX_NGT_F16}, {0xfdu, Opcode::V_CMPX_NEQ_F16}, - {0xfeu, Opcode::V_CMPX_NLT_F16}, + {0x00u, Opcode::V_CMP_F_F32}, {0x01u, Opcode::V_CMP_LT_F32}, + {0x02u, Opcode::V_CMP_EQ_F32}, {0x03u, Opcode::V_CMP_LE_F32}, + {0x04u, Opcode::V_CMP_GT_F32}, {0x05u, Opcode::V_CMP_LG_F32}, + {0x06u, Opcode::V_CMP_GE_F32}, {0x07u, Opcode::V_CMP_O_F32}, + {0x08u, Opcode::V_CMP_U_F32}, {0x09u, Opcode::V_CMP_NGE_F32}, + {0x0au, Opcode::V_CMP_NLG_F32}, {0x0bu, Opcode::V_CMP_NGT_F32}, + {0x0cu, Opcode::V_CMP_NLE_F32}, {0x0du, Opcode::V_CMP_NEQ_F32}, + {0x0eu, Opcode::V_CMP_NLT_F32}, {0x0fu, Opcode::V_CMP_TRU_F32}, + {0x11u, Opcode::V_CMPX_LT_F32}, {0x12u, Opcode::V_CMPX_EQ_F32}, + {0x13u, Opcode::V_CMPX_LE_F32}, {0x14u, Opcode::V_CMPX_GT_F32}, + {0x15u, Opcode::V_CMPX_LG_F32}, {0x16u, Opcode::V_CMPX_GE_F32}, + {0x19u, Opcode::V_CMPX_NGE_F32}, {0x1au, Opcode::V_CMPX_NLG_F32}, + {0x1bu, Opcode::V_CMPX_NGT_F32}, {0x1cu, Opcode::V_CMPX_NLE_F32}, + {0x1du, Opcode::V_CMPX_NEQ_F32}, {0x1eu, Opcode::V_CMPX_NLT_F32}, + {0x80u, Opcode::V_CMP_F_I32}, {0x81u, Opcode::V_CMP_LT_I32}, + {0x82u, Opcode::V_CMP_EQ_I32}, {0x83u, Opcode::V_CMP_LE_I32}, + {0x84u, Opcode::V_CMP_GT_I32}, {0x85u, Opcode::V_CMP_NE_I32}, + {0x86u, Opcode::V_CMP_GE_I32}, {0x87u, Opcode::V_CMP_T_I32}, + {0x88u, Opcode::V_CMP_CLASS_F32}, {0x89u, Opcode::V_CMP_LT_I16}, + {0x8au, Opcode::V_CMP_EQ_I16}, {0x8bu, Opcode::V_CMP_LE_I16}, + {0x8cu, Opcode::V_CMP_GT_I16}, {0x8du, Opcode::V_CMP_NE_I16}, + {0x8eu, Opcode::V_CMP_GE_I16}, {0x91u, Opcode::V_CMPX_LT_I32}, + {0x92u, Opcode::V_CMPX_EQ_I32}, {0x93u, Opcode::V_CMPX_LE_I32}, + {0x94u, Opcode::V_CMPX_GT_I32}, {0x95u, Opcode::V_CMPX_NE_I32}, + {0x96u, Opcode::V_CMPX_GE_I32}, {0x98u, Opcode::V_CMPX_CLASS_F32}, + {0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16}, + {0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16}, + {0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16}, + {0xbcu, Opcode::V_CMPX_GT_U16}, {0xc0u, Opcode::V_CMP_F_U32}, + {0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32}, + {0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32}, + {0xc5u, Opcode::V_CMP_NE_U32}, {0xc6u, Opcode::V_CMP_GE_U32}, + {0xc7u, Opcode::V_CMP_T_U32}, {0xa2u, Opcode::V_CMP_EQ_I64, false}, + {0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xd1u, Opcode::V_CMPX_LT_U32}, + {0xd2u, Opcode::V_CMPX_EQ_U32}, {0xd3u, Opcode::V_CMPX_LE_U32}, + {0xd4u, Opcode::V_CMPX_GT_U32}, {0xd5u, Opcode::V_CMPX_NE_U32}, + {0xd6u, Opcode::V_CMPX_GE_U32}, {0xe2u, Opcode::V_CMP_EQ_U64, false}, + {0xe4u, Opcode::V_CMP_GT_U64, false}, {0xe5u, Opcode::V_CMP_NE_U64, false}, + {0xf5u, Opcode::V_CMPX_NE_U64, false}, {0xc9u, Opcode::V_CMP_LT_F16}, + {0xcau, Opcode::V_CMP_EQ_F16}, {0xcbu, Opcode::V_CMP_LE_F16}, + {0xccu, Opcode::V_CMP_GT_F16}, {0xcdu, Opcode::V_CMP_LG_F16}, + {0xceu, Opcode::V_CMP_GE_F16}, {0xedu, Opcode::V_CMP_NEQ_F16}, + {0xd9u, Opcode::V_CMPX_LT_F16}, {0xdau, Opcode::V_CMPX_EQ_F16}, + {0xdbu, Opcode::V_CMPX_LE_F16}, {0xdcu, Opcode::V_CMPX_GT_F16}, + {0xdeu, Opcode::V_CMPX_GE_F16}, {0xfbu, Opcode::V_CMPX_NGT_F16}, + {0xfdu, Opcode::V_CMPX_NEQ_F16}, {0xfeu, Opcode::V_CMPX_NLT_F16}, }; constexpr auto VOPC_OPS = Detail::MakeOpcodeTable<0x100>(VOPC_OPCODE_LIST); @@ -641,8 +638,8 @@ bool ValidateVop1Sdwa(Instruction& inst, uint32_t opcode, uint32_t modifier) { return true; } -bool DecodeVop1Sdwa(uint32_t pc, std::span code, uint32_t word_index, - uint32_t opcode, uint32_t vdst, Instruction& inst, std::string* error) { +void DecodeVop1Sdwa(uint32_t pc, std::span code, uint32_t word_index, + uint32_t opcode, uint32_t vdst, Instruction& inst) { const auto modifier = code[word_index + 1u]; const auto src0 = modifier & 0xffu; const auto dst_sel = (modifier >> 8u) & 0x7u; @@ -657,32 +654,27 @@ bool DecodeVop1Sdwa(uint32_t pc, std::span code, uint32_t word_i SetRawWords(inst, code, word_index, 2); if (!ValidateVop1Sdwa(inst, opcode, modifier)) { - return true; + return; } const bool scalar_dst = UsesScalarDestination(inst.opcode); if (scalar_dst) { - if (!DecodeScalarDestination(vdst, pc, inst.dst, error)) { - return false; - } + DecodeScalarDestination(vdst, pc, inst.dst); } else { DecodeVectorGpr(vdst, inst.dst); } - if (!DecodeScalarSource(src0 + (s0 == 0u ? 256u : 0u), pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(src0 + (s0 == 0u ? 256u : 0u), pc, inst.src0); inst.dst.sdwa_sel = dst_sel; inst.dst.sdwa_dst_unused = dst_u; inst.dst.explicit_sdwa_dst = true; inst.dst.clamp = clamp != 0u; inst.dst.omod = omod; - inst.src0.sdwa_sel = src0_sel; - inst.src0.sdwa_sext = src0_sext != 0u; - inst.src0.negate = src0_neg != 0u; - inst.src0.absolute = src0_abs != 0u; - inst.src_count = 1; + inst.src0.sdwa_sel = src0_sel; + inst.src0.sdwa_sext = src0_sext != 0u; + inst.src0.negate = src0_neg != 0u; + inst.src0.absolute = src0_abs != 0u; + inst.src_count = 1; ReadLiteralOperands(code, word_index, inst); - return true; } void ApplyDppModifier(Operand& operand, uint32_t modifier) { @@ -696,38 +688,33 @@ void ApplyDppModifier(Operand& operand, uint32_t modifier) { operand.dpp_row_mask = (modifier >> 28u) & 0xfu; } -bool DecodeVop1Dpp(uint32_t pc, std::span code, uint32_t word_index, - uint32_t opcode, uint32_t vdst, Instruction& inst, std::string* error) { +void DecodeVop1Dpp(uint32_t pc, std::span code, uint32_t word_index, + uint32_t opcode, uint32_t vdst, Instruction& inst) { const auto modifier = code[word_index + 1u]; const auto src0 = modifier & 0xffu; SetRawWords(inst, code, word_index, 2); const bool scalar_dst = UsesScalarDestination(inst.opcode); if (scalar_dst) { - if (!DecodeScalarDestination(vdst, pc, inst.dst, error)) { - return false; - } + DecodeScalarDestination(vdst, pc, inst.dst); } else { DecodeVectorGpr(vdst, inst.dst); } - if (!DecodeScalarSource(src0 + 256u, pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(src0 + 256u, pc, inst.src0); ApplyDppModifier(inst.src0, modifier); inst.src_count = 1; if (!IsVop1FloatSourceOpcode(inst.opcode) && (inst.src0.negate || inst.src0.absolute)) { SetUnsupported(inst, Family::VOP1, opcode, "VOP1 DPP integer source modifiers are not supported"); - return true; + return; } ReadLiteralOperands(code, word_index, inst); - return true; } -using Vop1ModifierDecodeFn = bool (*)(uint32_t pc, std::span code, +using Vop1ModifierDecodeFn = void (*)(uint32_t pc, std::span code, uint32_t word_index, uint32_t opcode, uint32_t vdst, - Instruction& inst, std::string* error); + Instruction& inst); struct Vop1ModifierDecoder { uint32_t escape = 0; @@ -740,20 +727,14 @@ constexpr Vop1ModifierDecoder VOP1_MODIFIER_DECODERS[] = { }; bool TryDecodeVop1Modifier(uint32_t pc, std::span code, uint32_t word_index, - uint32_t src0, uint32_t opcode, uint32_t vdst, Instruction& inst, - std::string* error, bool* handled) { - if (handled != nullptr) { - *handled = false; - } + uint32_t src0, uint32_t opcode, uint32_t vdst, Instruction& inst) { for (const auto& decoder: VOP1_MODIFIER_DECODERS) { if (decoder.escape == src0) { - if (handled != nullptr) { - *handled = true; - } - return decoder.decode(pc, code, word_index, opcode, vdst, inst, error); + decoder.decode(pc, code, word_index, opcode, vdst, inst); + return true; } } - return true; + return false; } bool IsVop2FloatSourceOpcode(Opcode opcode) { @@ -1034,9 +1015,8 @@ bool ValidateVop2Sdwa(Instruction& inst, uint32_t opcode, const Vop2SdwaFields& return true; } -bool FinalizeVop2Instruction(uint32_t pc, std::span code, uint32_t word_index, - Instruction& inst, std::string* error) { - (void)pc; +void FinalizeVop2Instruction(std::span code, uint32_t word_index, + Instruction& inst) { switch (inst.opcode) { case Opcode::V_MADMK_F32: case Opcode::V_FMAMK_F16: @@ -1067,55 +1047,48 @@ bool FinalizeVop2Instruction(uint32_t pc, std::span code, uint32 default: inst.src_count = 2; break; } ReadLiteralOperands(code, word_index, inst); - return true; } -bool DecodeVop2Sdwa(uint32_t pc, std::span code, uint32_t word_index, - uint32_t opcode, uint32_t vdst, uint32_t vsrc1, Instruction& inst, - std::string* error) { +void DecodeVop2Sdwa(uint32_t pc, std::span code, uint32_t word_index, + uint32_t opcode, uint32_t vdst, uint32_t vsrc1, Instruction& inst) { const auto modifier = code[word_index + 1u]; const auto fields = DecodeVop2SdwaFields(modifier); SetRawWords(inst, code, word_index, 2); if (!ValidateVop2Sdwa(inst, opcode, fields)) { - return true; + return; } DecodeVectorGpr(vdst, inst.dst); - if (!DecodeScalarSource(fields.src0 + (fields.s0 == 0u ? 256u : 0u), pc, inst.src0, error) || - !DecodeScalarSource(vsrc1 + (fields.s1 == 0u ? 256u : 0u), pc, inst.src1, error)) { - return false; - } + DecodeScalarSource(fields.src0 + (fields.s0 == 0u ? 256u : 0u), pc, inst.src0); + DecodeScalarSource(vsrc1 + (fields.s1 == 0u ? 256u : 0u), pc, inst.src1); inst.dst.sdwa_sel = fields.dst_sel; inst.dst.sdwa_dst_unused = fields.dst_u; inst.dst.explicit_sdwa_dst = true; inst.dst.clamp = fields.clamp != 0u; inst.dst.omod = fields.omod; - inst.src0.sdwa_sel = fields.src0_sel; - inst.src0.sdwa_sext = fields.src0_sext != 0u; - inst.src0.negate = fields.src0_neg != 0u; - inst.src0.absolute = fields.src0_abs != 0u; - inst.src1.sdwa_sel = fields.src1_sel; - inst.src1.sdwa_sext = fields.src1_sext != 0u; - inst.src1.negate = fields.src1_neg != 0u; - inst.src1.absolute = fields.src1_abs != 0u; - return FinalizeVop2Instruction(pc, code, word_index, inst, error); + inst.src0.sdwa_sel = fields.src0_sel; + inst.src0.sdwa_sext = fields.src0_sext != 0u; + inst.src0.negate = fields.src0_neg != 0u; + inst.src0.absolute = fields.src0_abs != 0u; + inst.src1.sdwa_sel = fields.src1_sel; + inst.src1.sdwa_sext = fields.src1_sext != 0u; + inst.src1.negate = fields.src1_neg != 0u; + inst.src1.absolute = fields.src1_abs != 0u; + FinalizeVop2Instruction(code, word_index, inst); } -bool DecodeVop2Dpp(uint32_t pc, std::span code, uint32_t word_index, - uint32_t opcode, uint32_t vdst, uint32_t vsrc1, Instruction& inst, - std::string* error) { +void DecodeVop2Dpp(uint32_t pc, std::span code, uint32_t word_index, + uint32_t opcode, uint32_t vdst, uint32_t vsrc1, Instruction& inst) { const auto modifier = code[word_index + 1u]; const auto src0 = modifier & 0xffu; SetRawWords(inst, code, word_index, 2); DecodeVectorGpr(vdst, inst.dst); DecodeVectorGpr(vsrc1, inst.src1); - if (!DecodeScalarSource(src0 + 256u, pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(src0 + 256u, pc, inst.src0); ApplyDppModifier(inst.src0, modifier); - inst.src1.negate = ((modifier >> 22u) & 0x1u) != 0u; - inst.src1.absolute = ((modifier >> 23u) & 0x1u) != 0u; + inst.src1.negate = ((modifier >> 22u) & 0x1u) != 0u; + inst.src1.absolute = ((modifier >> 23u) & 0x1u) != 0u; const bool packed_fmac = inst.opcode == Opcode::V_PK_FMAC_F16; if (packed_fmac) { inst.src0.negate_hi = inst.src0.negate; @@ -1126,14 +1099,14 @@ bool DecodeVop2Dpp(uint32_t pc, std::span code, uint32_t word_in (inst.src0.negate || inst.src0.absolute || inst.src1.negate || inst.src1.absolute)) { SetUnsupported(inst, Family::VOP2, opcode, "VOP2 DPP integer source modifiers are not supported"); - return true; + return; } - return FinalizeVop2Instruction(pc, code, word_index, inst, error); + FinalizeVop2Instruction(code, word_index, inst); } -using Vop2ModifierDecodeFn = bool (*)(uint32_t pc, std::span code, +using Vop2ModifierDecodeFn = void (*)(uint32_t pc, std::span code, uint32_t word_index, uint32_t opcode, uint32_t vdst, - uint32_t vsrc1, Instruction& inst, std::string* error); + uint32_t vsrc1, Instruction& inst); struct Vop2ModifierDecoder { uint32_t escape = 0; @@ -1147,19 +1120,14 @@ constexpr Vop2ModifierDecoder VOP2_MODIFIER_DECODERS[] = { bool TryDecodeVop2Modifier(uint32_t pc, std::span code, uint32_t word_index, uint32_t src0, uint32_t opcode, uint32_t vdst, uint32_t vsrc1, - Instruction& inst, std::string* error, bool* handled) { - if (handled != nullptr) { - *handled = false; - } + Instruction& inst) { for (const auto& decoder: VOP2_MODIFIER_DECODERS) { if (decoder.escape == src0) { - if (handled != nullptr) { - *handled = true; - } - return decoder.decode(pc, code, word_index, opcode, vdst, vsrc1, inst, error); + decoder.decode(pc, code, word_index, opcode, vdst, vsrc1, inst); + return true; } } - return true; + return false; } struct VopcSdwaFields { @@ -1200,31 +1168,29 @@ bool SupportsVopcSdwa(Opcode opcode) { return opcode != Opcode::UNSUPPORTED; } -bool DecodeVopcSdwa(uint32_t pc, std::span code, uint32_t word_index, - uint32_t opcode, uint32_t vsrc1, Instruction& inst, std::string* error) { +void DecodeVopcSdwa(uint32_t pc, std::span code, uint32_t word_index, + uint32_t opcode, uint32_t vsrc1, Instruction& inst) { const auto modifier = code[word_index + 1u]; const auto fields = DecodeVopcSdwaFields(modifier); SetRawWords(inst, code, word_index, 2); if (fields.src0_sel > 6u || fields.src1_sel > 6u) { SetUnsupported(inst, Family::VOPC, opcode, "VOPC SDWA selector is invalid"); - return true; + return; } if (!SupportsVopcSdwa(inst.opcode)) { SetUnsupported(inst, Family::VOPC, opcode, "VOPC SDWA modifier is not supported for opcode"); - return true; + return; } - if (!DecodeScalarSource(fields.src0 + (fields.s0 == 0u ? 256u : 0u), pc, inst.src0, error) || - !DecodeScalarSource(vsrc1 + (fields.s1 == 0u ? 256u : 0u), pc, inst.src1, error)) { - return false; - } + DecodeScalarSource(fields.src0 + (fields.s0 == 0u ? 256u : 0u), pc, inst.src0); + DecodeScalarSource(vsrc1 + (fields.s1 == 0u ? 256u : 0u), pc, inst.src1); if (IsVopcCompareExec(inst.opcode)) { inst.dst.kind = OperandKind::ExecLo; } else if (fields.sd == 0u) { inst.dst.kind = OperandKind::VccLo; - } else if (!DecodeScalarDestination(fields.sdst, pc, inst.dst, error)) { - return false; + } else { + DecodeScalarDestination(fields.sdst, pc, inst.dst); } inst.src0.sdwa_sel = fields.src0_sel; inst.src0.sdwa_sext = fields.src0_sext != 0u; @@ -1236,34 +1202,30 @@ bool DecodeVopcSdwa(uint32_t pc, std::span code, uint32_t word_i inst.src1.absolute = fields.src1_abs != 0u; inst.src_count = 2; ReadLiteralOperands(code, word_index, inst); - return true; } -bool DecodeVopcDpp(uint32_t pc, std::span code, uint32_t word_index, - uint32_t opcode, uint32_t vsrc1, Instruction& inst, std::string* error) { +void DecodeVopcDpp(uint32_t pc, std::span code, uint32_t word_index, + uint32_t opcode, uint32_t vsrc1, Instruction& inst) { const auto modifier = code[word_index + 1u]; const auto src0 = modifier & 0xffu; SetRawWords(inst, code, word_index, 2); if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::VOPC, opcode, "VOPC opcode is not implemented"); - return true; + return; } const auto* info = Detail::FindOpcode(VOPC_OPS, opcode); if (info == nullptr || !info->supports_dpp) { SetUnsupported(inst, Family::VOPC, opcode, "VOPC DPP modifier is not supported for opcode"); - return true; + return; } DecodeVectorGpr(vsrc1, inst.src1); - if (!DecodeScalarSource(src0 + 256u, pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(src0 + 256u, pc, inst.src0); inst.dst.kind = IsVopcCompareExec(inst.opcode) ? OperandKind::ExecLo : OperandKind::VccLo; ApplyDppModifier(inst.src0, modifier); inst.src1.negate = ((modifier >> 22u) & 0x1u) != 0u; inst.src1.absolute = ((modifier >> 23u) & 0x1u) != 0u; inst.src_count = 2; ReadLiteralOperands(code, word_index, inst); - return true; } uint32_t NativeVop3SourceCount(Opcode opcode) { @@ -1560,8 +1522,8 @@ bool IsVopcCompareExec(Opcode opcode) { } // namespace -bool DecodeVop2(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeVop2(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word = code[word_index]; const uint32_t opcode = (word >> 25u) & 0x3fu; const uint32_t vdst = (word >> 17u) & 0xffu; @@ -1577,27 +1539,20 @@ bool DecodeVop2(uint32_t pc, std::span code, uint32_t word_index if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::VOP2, opcode, "VOP2 opcode is not implemented"); - return true; + return; } - bool modifier_handled = false; - if (!TryDecodeVop2Modifier(pc, code, word_index, src0, opcode, vdst, vsrc1, inst, error, - &modifier_handled)) { - return false; - } - if (modifier_handled) { - return true; + if (TryDecodeVop2Modifier(pc, code, word_index, src0, opcode, vdst, vsrc1, inst)) { + return; } DecodeVectorGpr(vdst, inst.dst); DecodeVectorGpr(vsrc1, inst.src1); - if (!DecodeScalarSource(src0, pc, inst.src0, error)) { - return false; - } - return FinalizeVop2Instruction(pc, code, word_index, inst, error); + DecodeScalarSource(src0, pc, inst.src0); + FinalizeVop2Instruction(code, word_index, inst); } -bool DecodeVop1(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeVop1(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word = code[word_index]; const uint32_t opcode = (word >> 9u) & 0xffu; const uint32_t src0 = word & 0x1ffu; @@ -1612,39 +1567,29 @@ bool DecodeVop1(uint32_t pc, std::span code, uint32_t word_index if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::VOP1, opcode, "VOP1 opcode is not implemented"); - return true; + return; } if (inst.opcode == Opcode::V_NOP) { inst.dst.kind = OperandKind::Null; inst.src_count = 0; - return true; + return; } - bool modifier_handled = false; - if (!TryDecodeVop1Modifier(pc, code, word_index, src0, opcode, vdst, inst, error, - &modifier_handled)) { - return false; - } - if (modifier_handled) { - return true; + if (TryDecodeVop1Modifier(pc, code, word_index, src0, opcode, vdst, inst)) { + return; } const bool scalar_dst = UsesScalarDestination(inst.opcode); if (scalar_dst) { - if (!DecodeScalarDestination(vdst, pc, inst.dst, error)) { - return false; - } + DecodeScalarDestination(vdst, pc, inst.dst); } else { DecodeVectorGpr(vdst, inst.dst); } - if (!DecodeScalarSource(src0, pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(src0, pc, inst.src0); inst.src_count = 1; ReadLiteralOperands(code, word_index, inst); - return true; } -bool DecodeVopc(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeVopc(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word = code[word_index]; const uint32_t opcode = (word >> 17u) & 0xffu; const uint32_t src0 = word & 0x1ffu; @@ -1659,25 +1604,22 @@ bool DecodeVopc(uint32_t pc, std::span code, uint32_t word_index SetRawWords(inst, code, word_index, 1); switch (src0) { - case 249u: return DecodeVopcSdwa(pc, code, word_index, opcode, vsrc1, inst, error); - case 250u: return DecodeVopcDpp(pc, code, word_index, opcode, vsrc1, inst, error); + case 249u: DecodeVopcSdwa(pc, code, word_index, opcode, vsrc1, inst); return; + case 250u: DecodeVopcDpp(pc, code, word_index, opcode, vsrc1, inst); return; default: break; } if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::VOPC, opcode, "VOPC opcode is not implemented"); - return true; + return; } DecodeVectorGpr(vsrc1, inst.src1); - if (!DecodeScalarSource(src0, pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(src0, pc, inst.src0); inst.src_count = 2; ReadLiteralOperands(code, word_index, inst); - return true; } -bool DecodeVop3(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error) { +void DecodeVop3(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word0 = code[word_index]; const uint32_t word1 = code[word_index + 1u]; const uint32_t opcode = (word0 >> 16u) & 0x3ffu; @@ -1728,29 +1670,26 @@ bool DecodeVop3(uint32_t pc, std::span code, uint32_t word_index reason = "VOP3 mad-mix clamp/omod is not implemented"; } SetUnsupported(inst, Family::VOP3, opcode, reason); - return true; + return; } if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::VOP3, opcode, "VOP3 opcode is not implemented"); - return true; + return; } if (inst.opcode == Opcode::V_NOP) { inst.dst.kind = OperandKind::Null; inst.src_count = 0; - return true; + return; } - bool dst_ok = true; if (compare_exec) { inst.dst.kind = OperandKind::ExecLo; } else if (scalar_dst) { // VOP3A uses VDST for VOPC and the scalar-destination lane-read opcodes. - dst_ok = DecodeScalarDestination(vdst, pc, inst.dst, error); + DecodeScalarDestination(vdst, pc, inst.dst); } else { DecodeVectorGpr(vdst, inst.dst); } - if (!dst_ok || !DecodeScalarSource(src0, pc, inst.src0, error)) { - return false; - } + DecodeScalarSource(src0, pc, inst.src0); inst.dst.clamp = native_clamp_modifier && clamp != 0u; inst.dst.omod = native_result_modifiers ? omod : 0u; if (permlane) { @@ -1758,15 +1697,13 @@ bool DecodeVop3(uint32_t pc, std::span code, uint32_t word_index inst.dst.op_sel_hi = (op_sel & 0x2u) != 0u; } if (vop3_vopc) { - if (!DecodeScalarSource(src1, pc, inst.src1, error)) { - return false; - } + DecodeScalarSource(src1, pc, inst.src1); inst.src_count = 2; if (native_source_modifiers) { ApplyNativeVop3SourceModifiers(inst, abs, neg); } ReadLiteralOperands(code, word_index, inst); - return true; + return; } if (IsVop3EncodedVop1(opcode)) { inst.src_count = 1; @@ -1774,45 +1711,35 @@ bool DecodeVop3(uint32_t pc, std::span code, uint32_t word_index ApplyNativeVop3SourceModifiers(inst, abs, neg); } ReadLiteralOperands(code, word_index, inst); - return true; + return; } if (carry_in_out) { - if (!DecodeScalarDestination(sdst, pc, inst.dst2, error) || - !DecodeScalarSource(src1, pc, inst.src1, error) || - !DecodeScalarSource(src2, pc, inst.src2, error)) { - return false; - } + DecodeScalarDestination(sdst, pc, inst.dst2); + DecodeScalarSource(src1, pc, inst.src1); + DecodeScalarSource(src2, pc, inst.src2); inst.src_count = 3; ReadLiteralOperands(code, word_index, inst); - return true; + return; } if (vop3b_carry_out) { - if (!DecodeScalarDestination(sdst, pc, inst.dst2, error) || - !DecodeScalarSource(src1, pc, inst.src1, error)) { - return false; - } + DecodeScalarDestination(sdst, pc, inst.dst2); + DecodeScalarSource(src1, pc, inst.src1); inst.src_count = 2; ReadLiteralOperands(code, word_index, inst); - return true; + return; } if (vop3b_mad_u64) { - if (!DecodeScalarDestination(sdst, pc, inst.dst2, error) || - !DecodeScalarSource(src1, pc, inst.src1, error) || - !DecodeScalarSource(src2, pc, inst.src2, error)) { - return false; - } + DecodeScalarDestination(sdst, pc, inst.dst2); + DecodeScalarSource(src1, pc, inst.src1); + DecodeScalarSource(src2, pc, inst.src2); inst.src_count = 3; ReadLiteralOperands(code, word_index, inst); - return true; + return; } if (IsVop3EncodedVop2(opcode)) { - if (!DecodeScalarSource(src1, pc, inst.src1, error)) { - return false; - } + DecodeScalarSource(src1, pc, inst.src1); if (inst.opcode == Opcode::V_CNDMASK_B32) { - if (!DecodeScalarSource(src2, pc, inst.src2, error)) { - return false; - } + DecodeScalarSource(src2, pc, inst.src2); inst.src_count = 3; } else { inst.src_count = 2; @@ -1821,14 +1748,12 @@ bool DecodeVop3(uint32_t pc, std::span code, uint32_t word_index ApplyNativeVop3SourceModifiers(inst, abs, neg); } ReadLiteralOperands(code, word_index, inst); - return true; - } - if (!DecodeScalarSource(src1, pc, inst.src1, error)) { - return false; + return; } + DecodeScalarSource(src1, pc, inst.src1); inst.src_count = NativeVop3SourceCount(inst.opcode); - if (inst.src_count > 2u && !DecodeScalarSource(src2, pc, inst.src2, error)) { - return false; + if (inst.src_count > 2u) { + DecodeScalarSource(src2, pc, inst.src2); } if (mad_mix) { ApplyNativeVop3TernaryModifiers(inst, op_sel, abs, neg); @@ -1844,11 +1769,10 @@ bool DecodeVop3(uint32_t pc, std::span code, uint32_t word_index ApplyNativeVop3SourceModifiers(inst, abs, neg); } ReadLiteralOperands(code, word_index, inst); - return true; } -bool DecodeVop3p(uint32_t pc, std::span code, uint32_t word_index, - Instruction& inst, std::string* error) { +void DecodeVop3p(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst) { const uint32_t word0 = code[word_index]; const uint32_t word1 = code[word_index + 1u]; const uint32_t opcode = (word0 >> 16u) & 0x7fu; @@ -1872,16 +1796,14 @@ bool DecodeVop3p(uint32_t pc, std::span code, uint32_t word_inde if (inst.opcode == Opcode::UNSUPPORTED) { SetUnsupported(inst, Family::VOP3P, opcode, "VOP3P opcode is not implemented"); - return true; + return; } inst.src_count = Vop3pSourceCount(inst.opcode); DecodeVectorGpr(vdst, inst.dst); - if (!DecodeScalarSource(src0, pc, inst.src0, error) || - !DecodeScalarSource(src1, pc, inst.src1, error)) { - return false; - } - if (inst.src_count > 2u && !DecodeScalarSource(src2, pc, inst.src2, error)) { - return false; + DecodeScalarSource(src0, pc, inst.src0); + DecodeScalarSource(src1, pc, inst.src1); + if (inst.src_count > 2u) { + DecodeScalarSource(src2, pc, inst.src2); } if (inst.opcode == Opcode::V_FMA_F32) { inst.dst.clamp = clamp != 0u; @@ -1891,7 +1813,7 @@ bool DecodeVop3p(uint32_t pc, std::span code, uint32_t word_inde inst.dst.clamp = clamp != 0u; } else if (clamp != 0u) { SetUnsupported(inst, Family::VOP3P, opcode, "VOP3P integer clamp is not implemented"); - return true; + return; } ApplyVop3pSourceModifiers(inst, op_sel, op_sel_hi, neg, neg_hi); if (inst.opcode == Opcode::V_MAD_MIXLO_F16) { @@ -1904,7 +1826,6 @@ bool DecodeVop3p(uint32_t pc, std::span code, uint32_t word_inde ApplyVop3pMixAbsModifiers(inst); } ReadLiteralOperands(code, word_index, inst); - return true; } void DecodeVintrp(uint32_t pc, std::span code, uint32_t word_index, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.h b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.h index 796367fe9..c928dce40 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.h +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.h @@ -5,16 +5,16 @@ namespace Libs::Graphics::ShaderRecompiler::Decoder { -bool DecodeVop2(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error); -bool DecodeVop1(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error); -bool DecodeVopc(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error); -bool DecodeVop3(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst, - std::string* error); -bool DecodeVop3p(uint32_t pc, std::span code, uint32_t word_index, - Instruction& inst, std::string* error); +void DecodeVop2(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); +void DecodeVop1(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); +void DecodeVopc(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); +void DecodeVop3(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); +void DecodeVop3p(uint32_t pc, std::span code, uint32_t word_index, + Instruction& inst); void DecodeVintrp(uint32_t pc, std::span code, uint32_t word_index, Instruction& inst); diff --git a/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp b/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp index b02980596..ffb835c30 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp @@ -1,9 +1,8 @@ +#include "common/assert.h" #include "graphics/shader/recompiler/frontend/translate/Translator.h" -#include "graphics/shader/shader.h" #include #include -#include namespace Libs::Graphics::ShaderRecompiler::Frontend { @@ -47,14 +46,13 @@ IR::ExportFlags Translator::AddExportInfo(const Decoder::Instruction& inst) { return {.index = index, .pc = inst.pc}; } -bool Translator::TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute, +void Translator::TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute, uint32_t component_count) { for (uint32_t component = 0; component < component_count; component++) { const auto value = ir.Emit(IR::ValueOpcode::GetAttribute, {IR::Value(attribute), IR::Value(component)}); WriteOperand(OffsetOperand(inst.dst, component), value); } - return true; } void Translator::V_INTERP_P1_F32() {} @@ -65,25 +63,20 @@ void Translator::V_INTERP_P2_F32(const Decoder::Instruction& inst) { WriteOperand(inst.dst, value); } -bool Translator::V_INTERP_MOV_F32(const Decoder::Instruction& inst, std::string* error) { +void Translator::V_INTERP_MOV_F32(const Decoder::Instruction& inst) { if (inst.src0.value >= 3u) { - return ShaderError::Fail(error, - fmt::format("v_interp_mov_f32 mode {} is reserved at pc 0x{:08x}", - inst.src0.value, inst.pc)); + EXIT("v_interp_mov_f32 mode %u is reserved at pc 0x%08x", inst.src0.value, inst.pc); } const auto value = ir.Emit( IR::ValueOpcode::GetInterpolationParameter, {IR::Value(inst.src1.value), IR::Value(inst.src2.value), IR::Value(inst.src0.value)}); WriteOperand(inst.dst, value); - return true; } -bool Translator::EXP(const Decoder::Instruction& inst, std::string* error) { +void Translator::EXP(const Decoder::Instruction& inst) { uint32_t index = 0; if (ExportTargetKindFromTarget(inst.exp.target, index) == IR::ExportTargetKind::Unknown) { - return ShaderError::Fail(error, - fmt::format("unsupported EXP target 0x{:02x} at pc 0x{:08x}", - inst.exp.target, inst.pc)); + EXIT("unsupported EXP target 0x%02x at pc 0x%08x", inst.exp.target, inst.pc); } std::array components {IR::Value(0u), IR::Value(0u), IR::Value(0u), IR::Value(0u)}; @@ -93,14 +86,13 @@ bool Translator::EXP(const Decoder::Instruction& inst, std::string* error) { const auto data = ir.Emit(IR::ValueOpcode::CompositeConstructU32x4, {components[0], components[1], components[2], components[3]}); ir.Emit(IR::ValueOpcode::SetAttribute, {data, ir.GetExec()}, AddExportInfo(inst)); - return true; } -bool Translator::EmitInterpolation(const Decoder::Instruction& inst, std::string* error) { +bool Translator::EmitInterpolation(const Decoder::Instruction& inst) { switch (inst.opcode) { case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return true; case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return true; - case Decoder::Opcode::V_INTERP_MOV_F32: return V_INTERP_MOV_F32(inst, error); + case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return true; default: return false; } } diff --git a/src/graphics/shader/recompiler/frontend/translate/Control.cpp b/src/graphics/shader/recompiler/frontend/translate/Control.cpp index c718e3812..e3b89ad46 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Control.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Control.cpp @@ -1,3 +1,4 @@ +#include "common/assert.h" #include "graphics/shader/recompiler/frontend/translate/Translator.h" namespace Libs::Graphics::ShaderRecompiler::Frontend { @@ -163,12 +164,12 @@ void Translator::S_INST_PREFETCH() { ir.Emit(IR::ValueOpcode::InstPrefetch); } -bool Translator::S_GETPC_B64(const Decoder::Instruction& inst, std::string* error) { +void Translator::S_GETPC_B64(const Decoder::Instruction& inst) { const auto base = IR::U64(ir.Emit(IR::ValueOpcode::GetShaderBase)); const auto address = IR::U64( ir.Emit(IR::ValueOpcode::IAdd64, {base, IR::Value(static_cast(inst.pc) + 4u)})); if (inst.dst.kind == Decoder::OperandKind::Null) { - return true; + return; } auto high = PlainOperand(inst.dst); switch (inst.dst.kind) { @@ -179,25 +180,19 @@ bool Translator::S_GETPC_B64(const Decoder::Instruction& inst, std::string* erro high.kind = Decoder::OperandKind::VccLo; high.reg = 0u; } else { - if (error != nullptr) { - *error = "S_GETPC_B64 destination does not name a valid scalar pair"; - } - return false; + EXIT("S_GETPC_B64 destination does not name a valid scalar pair at pc 0x%08x", + inst.pc); } break; case Decoder::OperandKind::VccLo: high.kind = Decoder::OperandKind::VccHi; break; case Decoder::OperandKind::M0: high.kind = Decoder::OperandKind::Null; break; case Decoder::OperandKind::ExecLo: high.kind = Decoder::OperandKind::ExecHi; break; default: - if (error != nullptr) { - *error = "S_GETPC_B64 destination does not name a valid scalar pair"; - } - return false; + EXIT("S_GETPC_B64 destination does not name a valid scalar pair at pc 0x%08x", inst.pc); } const auto words = ExtractU64(address); WriteOperand(inst.dst, words[0]); WriteOperand(high, words[1]); - return true; } void Translator::S_CSELECT_B32(const Decoder::Instruction& inst) { @@ -213,8 +208,8 @@ void Translator::S_CSELECT_B64(const Decoder::Instruction& inst) { } void Translator::MOV_B32(const Decoder::Instruction& inst, bool apply_float_modifiers) { - if (inst.src0.kind == Decoder::OperandKind::Sgpr && inst.dst.kind == Decoder::OperandKind::Sgpr && - inst.src0.reg == inst.dst.reg) { + if (inst.src0.kind == Decoder::OperandKind::Sgpr && + inst.dst.kind == Decoder::OperandKind::Sgpr && inst.src0.reg == inst.dst.reg) { return; } if (IsExecOrVcc(inst.src0) && IsExecOrVcc(inst.dst)) { @@ -242,21 +237,15 @@ void Translator::S_WQM_B64(const Decoder::Instruction& inst) { ir.SetScc(ScalarU64NonZero(result)); } -bool Translator::V_MOVRELS_B32(const Decoder::Instruction& inst, std::string* error) { +void Translator::V_MOVRELS_B32(const Decoder::Instruction& inst) { if (inst.dst.kind != Decoder::OperandKind::Vgpr || inst.src0.kind != Decoder::OperandKind::Vgpr) { - if (error != nullptr) { - *error = "V_MOVRELS_B32 requires VGPR source and destination"; - } - return false; + EXIT("V_MOVRELS_B32 requires VGPR source and destination at pc 0x%08x", inst.pc); } if (inst.dst.sdwa_sel != 6u || inst.dst.omod != 0u || inst.dst.clamp || inst.src0.sdwa_sel != 6u || inst.src0.sdwa_sext || inst.src0.negate || inst.src0.absolute || inst.src0.dpp) { - if (error != nullptr) { - *error = "V_MOVRELS_B32 modifiers are not implemented"; - } - return false; + EXIT("V_MOVRELS_B32 modifiers are not implemented at pc 0x%08x", inst.pc); } const auto base = inst.src0.reg; const auto m0 = ir.BitwiseAnd(ReadU32(ConditionOperand(Decoder::OperandKind::M0)), @@ -267,23 +256,16 @@ bool Translator::V_MOVRELS_B32(const Decoder::Instruction& inst, std::string* er selected = ir.Select(match, ir.GetVectorReg(static_cast(index)), selected); } WriteOperand(DestinationOperand(inst), selected); - return true; } -bool Translator::V_MOVRELD_B32(const Decoder::Instruction& inst, std::string* error) { +void Translator::V_MOVRELD_B32(const Decoder::Instruction& inst) { if (inst.dst.kind != Decoder::OperandKind::Vgpr) { - if (error != nullptr) { - *error = "V_MOVRELD_B32 requires VGPR destination"; - } - return false; + EXIT("V_MOVRELD_B32 requires VGPR destination at pc 0x%08x", inst.pc); } if (inst.dst.sdwa_sel != 6u || inst.dst.omod != 0u || inst.dst.clamp || inst.src0.sdwa_sel != 6u || inst.src0.sdwa_sext || inst.src0.negate || inst.src0.absolute || inst.src0.dpp) { - if (error != nullptr) { - *error = "V_MOVRELD_B32 modifiers are not implemented"; - } - return false; + EXIT("V_MOVRELD_B32 modifiers are not implemented at pc 0x%08x", inst.pc); } const auto base = inst.dst.reg; const auto value = ReadU32(inst.src0); @@ -295,7 +277,6 @@ bool Translator::V_MOVRELD_B32(const Decoder::Instruction& inst, std::string* er const auto write = ir.LogicalAnd(ir.GetExec(), match); ir.SetVectorReg(reg, ir.Select(write, value, ir.GetVectorReg(reg))); } - return true; } void Translator::V_READFIRSTLANE_B32(const Decoder::Instruction& inst) { diff --git a/src/graphics/shader/recompiler/frontend/translate/Dispatch.cpp b/src/graphics/shader/recompiler/frontend/translate/Dispatch.cpp index f102e5d6a..a2f9aafc0 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Dispatch.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Dispatch.cpp @@ -1,23 +1,18 @@ +#include "common/assert.h" #include "graphics/shader/recompiler/frontend/translate/Translator.h" namespace Libs::Graphics::ShaderRecompiler::Frontend { -bool Translator::TranslateInstruction(const Decoder::Instruction& inst, std::string* error) { +void Translator::TranslateInstruction(const Decoder::Instruction& inst) { current_opcode = inst.opcode; current_pc = inst.pc; switch (inst.opcode) { case Decoder::Opcode::UNKNOWN: case Decoder::Opcode::COUNT: - if (error != nullptr) { - *error = "decoded opcode has no IR translation"; - } - return false; + EXIT("decoded opcode has no IR translation at pc 0x%08x", inst.pc); case Decoder::Opcode::UNSUPPORTED: - if (error != nullptr) { - *error = "unsupported decoded instruction: " + Decoder::InstructionToString(inst); - } - return false; + EXIT("unsupported decoded instruction: %s", Decoder::InstructionToString(inst).c_str()); default: break; } @@ -27,27 +22,30 @@ bool Translator::TranslateInstruction(const Decoder::Instruction& inst, std::str case Decoder::Family::SOP2: case Decoder::Family::SOPK: case Decoder::Family::SOPC: - case Decoder::Family::SOPP: translated = EmitScalar(inst, error); break; + case Decoder::Family::SOPP: translated = EmitScalar(inst); break; case Decoder::Family::VOP1: case Decoder::Family::VOP2: case Decoder::Family::VOP3: case Decoder::Family::VOP3P: - case Decoder::Family::VOPC: translated = EmitVector(inst, error); break; + case Decoder::Family::VOPC: translated = EmitVector(inst); break; case Decoder::Family::SMEM: case Decoder::Family::MUBUF: case Decoder::Family::MTBUF: case Decoder::Family::FLAT: case Decoder::Family::DS: - case Decoder::Family::MIMG: translated = EmitMemory(inst, error); break; - case Decoder::Family::VINTRP: translated = EmitInterpolation(inst, error); break; - case Decoder::Family::EXP: translated = EXP(inst, error); break; + case Decoder::Family::MIMG: translated = EmitMemory(inst); break; + case Decoder::Family::VINTRP: translated = EmitInterpolation(inst); break; + case Decoder::Family::EXP: + EXP(inst); + translated = true; + break; default: break; } - if (!translated && error != nullptr && error->empty()) { - *error = "decoded opcode has no IR translation"; + if (!translated) { + EXIT("opcode %s at pc 0x%08x has no IR translation", + Decoder::InstructionToString(inst).c_str(), inst.pc); } - return translated; } } // namespace Libs::Graphics::ShaderRecompiler::Frontend diff --git a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp index cfc2a3c69..c60526512 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp @@ -1,9 +1,7 @@ -#include "common/magicEnum.h" #include "graphics/shader/recompiler/frontend/translate/Translator.h" #include #include -#include namespace Libs::Graphics::ShaderRecompiler::Frontend { @@ -890,7 +888,7 @@ bool Translator::DS_BPERMUTE_B32(const Decoder::Instruction& inst) { return true; } -bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error) { +bool Translator::EmitMemory(const Decoder::Instruction& inst) { switch (inst.opcode) { case Decoder::Opcode::S_LOAD_DWORD: case Decoder::Opcode::S_LOAD_DWORDX2: @@ -1080,12 +1078,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst, std::string* error case Decoder::Opcode::DS_WRITE_B64: case Decoder::Opcode::DS_WRITE_B96: case Decoder::Opcode::DS_WRITE_B128: return DS_WRITE(inst); - default: - if (error != nullptr) { - *error = fmt::format("memory-family opcode has no specialized IR translation: {}", - magic_enum::enum_name(inst.opcode)); - } - return false; + default: return false; } } diff --git a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp index 9541e8e18..ae9ff28d5 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Scalar.cpp @@ -2,14 +2,14 @@ namespace Libs::Graphics::ShaderRecompiler::Frontend { -bool Translator::EmitScalar(const Decoder::Instruction& inst, std::string* error) { +bool Translator::EmitScalar(const Decoder::Instruction& inst) { using O = Decoder::Opcode; switch (inst.opcode) { case O::S_MOV_B32: case O::S_MOVK_I32: MOV_B32(inst, false); return true; case O::S_MOV_B64: S_MOV_B64(inst); return true; case O::S_WQM_B64: S_WQM_B64(inst); return true; - case O::S_GETPC_B64: return S_GETPC_B64(inst, error); + case O::S_GETPC_B64: S_GETPC_B64(inst); return true; case O::S_SETPC_B64: return true; case O::S_CSELECT_B32: S_CSELECT_B32(inst); return true; case O::S_CSELECT_B64: S_CSELECT_B64(inst); return true; diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index f0ae7b1ea..fafca91c2 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1,16 +1,14 @@ +#include "common/assert.h" #include "graphics/shader/recompiler/frontend/translate/Translator.h" #include "graphics/shader/shader.h" #include #include -#include #include #include namespace Libs::Graphics::ShaderRecompiler::Frontend { -using ShaderError::Fail; - Decoder::Operand Translator::SourceAt(const Decoder::Instruction& inst, uint32_t index) { switch (index) { case 0: return inst.src0; @@ -396,8 +394,7 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value) } } if (type == IR::Type::U16) { - Write16Bits(operand, - IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {IR::U16(value)}))); + Write16Bits(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {IR::U16(value)}))); return; } if (type == IR::Type::F16) { @@ -811,11 +808,10 @@ void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 valu WriteMask(operand, ir.LogicalAnd(ir.GetExec(), value)); } -bool Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info, - std::string* error) { +void Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info) { if (source.terminator.goto_value >= 0) { if (source.terminator.goto_variable == UINT32_MAX) { - return Fail(error, fmt::format("block {} sets an invalid goto variable", source.id)); + EXIT("block %u sets an invalid goto variable", source.id); } ir.SetGotoVariable(source.terminator.goto_variable, IR::U1(IR::Value(source.terminator.goto_value != 0))); @@ -827,13 +823,13 @@ bool Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo info.indirect_target = ir.GetScalarReg(static_cast(source.terminator.indirect_pc_sgpr)); } else { - return Fail(error, fmt::format("block {} has no indirect branch selector", source.id)); + EXIT("block %u has no indirect branch selector", source.id); } ir.Emit(IR::ValueOpcode::ReferenceU32, {info.indirect_target}); - return true; + return; } if (source.terminator.kind != CFG::TerminatorKind::ConditionalBranch) { - return true; + return; } // EXEC and VCC are invocation-local Boolean masks. Branching on that Boolean lets inactive // invocations leave the region without reconstructing a host-subgroup mask. @@ -848,17 +844,15 @@ bool Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo case CFG::BranchCondition::ExecNonZero: condition = ir.GetExec(); break; case CFG::BranchCondition::GotoVariable: if (source.terminator.goto_variable == UINT32_MAX) { - return Fail(error, - fmt::format("block {} reads an invalid goto variable", source.id)); + EXIT("block %u reads an invalid goto variable", source.id); } condition = ir.GetGotoVariable(source.terminator.goto_variable); break; case CFG::BranchCondition::Unknown: - return Fail(error, fmt::format("block {} has an unknown branch condition", source.id)); + EXIT("block %u has an unknown branch condition", source.id); } info.condition = condition; ir.Emit(IR::ValueOpcode::Reference, {condition}); - return true; } namespace { @@ -980,47 +974,42 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_ } } -bool ValidateTranslateOptions(const TranslateOptions& options, std::string* error) { +void ValidateTranslateOptions(const TranslateOptions& options) { if (options.wave_size != 32u && options.wave_size != 64u) { - return Fail(error, "shader translation requires wave32 or wave64"); + EXIT("shader translation requires wave32 or wave64, got %u", options.wave_size); } switch (options.stage) { case ShaderType::Vertex: - return options.vertex != nullptr || - Fail(error, "vertex shader translation has no vertex input metadata"); + if (options.vertex == nullptr) { + EXIT("vertex shader translation has no vertex input metadata"); + } + return; case ShaderType::Pixel: - return options.pixel != nullptr || - Fail(error, "pixel shader translation has no pixel input metadata"); + if (options.pixel == nullptr) { + EXIT("pixel shader translation has no pixel input metadata"); + } + return; case ShaderType::Compute: - return options.compute != nullptr || - Fail(error, "compute shader translation has no compute input metadata"); - default: return Fail(error, "shader translation has an unsupported stage"); + if (options.compute == nullptr) { + EXIT("compute shader translation has no compute input metadata"); + } + return; + default: + EXIT("shader translation has unsupported stage %u", + static_cast(options.stage)); } } } // namespace -bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, - const TranslateOptions& options, IR::Program& result, std::string* error) { - result = {}; - struct ResetOnFailure { - IR::Program& program; - bool complete = false; - - ~ResetOnFailure() { - if (!complete) { - program = {}; - } - } - } reset_on_failure {result}; - - if (!ValidateTranslateOptions(options, error)) { - return false; - } +IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, + const TranslateOptions& options) { + ValidateTranslateOptions(options); if (cfg.blocks.empty()) { - return Fail(error, "cannot translate an empty CFG"); + EXIT("cannot translate an empty CFG"); } + IR::Program result; result.stage = options.stage; result.wave_size = options.wave_size; result.shader_hash = options.shader_hash; @@ -1038,8 +1027,8 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, for (const auto& cfg_block: cfg.blocks) { for (uint32_t index = cfg_block.inst_begin; index < cfg_block.inst_end; index++) { if (index >= decoded.instructions.size()) { - return Fail(error, - "CFG block references an instruction outside the decoded program"); + EXIT("CFG block %u references instruction %u outside decoded program of size %zu", + cfg_block.id, index, decoded.instructions.size()); } const auto& instruction = decoded.instructions[index]; if (IsCodeTableLoad(cfg, instruction.pc)) { @@ -1056,7 +1045,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, return block.id; })->id; if (max_id == UINT32_MAX) { - return Fail(error, "cannot allocate a typed entry block id"); + EXIT("cannot allocate a typed entry block id"); } CFG::Terminator terminator; terminator.kind = CFG::TerminatorKind::Branch; @@ -1070,7 +1059,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, block_indices.reserve(cfg.blocks.size()); for (const auto& source_block: cfg.blocks) { if (!block_indices.emplace(source_block.id, result.blocks.size()).second) { - return Fail(error, fmt::format("CFG contains duplicate block id {}", source_block.id)); + EXIT("CFG contains duplicate block id %u", source_block.id); } result.block_storage.push_back(std::make_unique()); result.blocks.push_back(result.block_storage.back().get()); @@ -1082,8 +1071,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, for (const auto successor: source_block.successors) { const auto target = block_indices.find(successor); if (target == block_indices.end()) { - return Fail(error, fmt::format("CFG block {} has unknown successor {}", - source_block.id, successor)); + EXIT("CFG block %u has unknown successor %u", source_block.id, successor); } result.blocks[source_index]->AddBranch(result.blocks[target->second]); } @@ -1182,7 +1170,7 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, } } for (const auto& cfg_block: cfg.blocks) { - const auto typed_index = block_indices.at(cfg_block.id); + const auto typed_index = block_indices.at(cfg_block.id); Translator translator(result, result.blocks[typed_index], vector_limit, options.wave_size); for (uint32_t index = cfg_block.inst_begin; index < cfg_block.inst_end; index++) { const auto& instruction = decoded.instructions[index]; @@ -1198,44 +1186,27 @@ bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, instruction.data_dwords == embedded->components && instruction.dst.kind == Decoder::OperandKind::Vgpr) { if (options.vertex == nullptr) { - return Fail(error, "embedded vertex fetch plan has no vertex input metadata"); + EXIT("embedded vertex fetch plan has no vertex input metadata"); } const auto resource = ResolveEmbeddedFetchResource(*options.vertex, *embedded); if (resource < 0 || resource >= options.vertex->resources_num) { - return Fail( - error, - fmt::format( - "embedded vertex fetch at 0x{:08x} has no resource for attribute {}", - instruction.pc, embedded->attrib_id)); + EXIT("embedded vertex fetch at 0x%08x has no resource for attribute %d", + instruction.pc, embedded->attrib_id); } result.info.vertex_fetch_components[static_cast(resource)] = static_cast(std::max( result.info.vertex_fetch_components[static_cast(resource)], embedded->components)); - if (!translator.TranslateEmbeddedFetch(instruction, static_cast(resource), - embedded->components)) { - return false; - } + translator.TranslateEmbeddedFetch(instruction, static_cast(resource), + embedded->components); continue; } - if (!translator.TranslateInstruction(instruction, error)) { - if (error == nullptr || error->empty()) { - return Fail(error, fmt::format("opcode {} at 0x{:08x} has no IR translation", - magic_enum::enum_name(instruction.opcode), - instruction.pc)); - } - return false; - } - } - if (!translator.AddBranchCondition(cfg_block, result.block_info[typed_index], error)) { - return false; + translator.TranslateInstruction(instruction); } + translator.AddBranchCondition(cfg_block, result.block_info[typed_index]); } - if (!IR::ValidateProgram(result, false, error)) { - return false; - } - reset_on_failure.complete = true; - return true; + IR::ValidateProgram(result, false); + return result; } } // namespace Libs::Graphics::ShaderRecompiler::Frontend diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.h b/src/graphics/shader/recompiler/frontend/translate/Translate.h index af54d178f..fa558ecf1 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.h @@ -38,7 +38,7 @@ struct TranslateOptions { const EmbeddedFetchPlan* embedded_fetch = nullptr; }; -bool TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, - const TranslateOptions& options, IR::Program& result, std::string* error); +IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, + const TranslateOptions& options); } // namespace Libs::Graphics::ShaderRecompiler::Frontend diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index 5d452325f..8264c93d6 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -13,10 +13,10 @@ public: : program(program), ir(block), current_vector_limit(vector_limit), current_wave_size(wave_size) {} - bool TranslateInstruction(const Decoder::Instruction& inst, std::string* error); - bool TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute, + void TranslateInstruction(const Decoder::Instruction& inst); + void TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute, uint32_t component_count); - bool AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info, std::string* error); + void AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info); private: struct ScalarU64 { @@ -194,26 +194,26 @@ private: bool PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign); bool S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode, IR::ValueOpcode bit_opcode, bool negate_rhs, bool negate_result, bool unary); - bool SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type, - bool reverse, bool mask_shift_count, bool update_scc); - bool ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, - bool negate_rhs, bool negate_result, bool update_scc); - bool V_AND_OR_B32(const Decoder::Instruction& inst); - bool V_OR3_B32(const Decoder::Instruction& inst); - bool V_XOR3_B32(const Decoder::Instruction& inst); - bool S_FF1_I32_B64(const Decoder::Instruction& inst); - bool V_FFBH_32(const Decoder::Instruction& inst, bool sign); - bool S_FLBIT_I32_B64(const Decoder::Instruction& inst); - bool Integer24(const Decoder::Instruction& inst, bool sign, bool addend); - bool V_MAD_U64_U32(const Decoder::Instruction& inst); - bool V_SAD_U32(const Decoder::Instruction& inst); - bool V_ADD3_U32(const Decoder::Instruction& inst); - bool S_BITSET_B32(const Decoder::Instruction& inst, bool set); - bool V_BCNT_U32_B32(const Decoder::Instruction& inst); - bool V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low); - bool S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst); - bool S_QUADMASK_B64(const Decoder::Instruction& inst); - bool BFM_B32(const Decoder::Instruction& inst); + bool SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type, + bool reverse, bool mask_shift_count, bool update_scc); + bool ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, + bool negate_rhs, bool negate_result, bool update_scc); + bool V_AND_OR_B32(const Decoder::Instruction& inst); + bool V_OR3_B32(const Decoder::Instruction& inst); + bool V_XOR3_B32(const Decoder::Instruction& inst); + bool S_FF1_I32_B64(const Decoder::Instruction& inst); + bool V_FFBH_32(const Decoder::Instruction& inst, bool sign); + bool S_FLBIT_I32_B64(const Decoder::Instruction& inst); + bool Integer24(const Decoder::Instruction& inst, bool sign, bool addend); + bool V_MAD_U64_U32(const Decoder::Instruction& inst); + bool V_SAD_U32(const Decoder::Instruction& inst); + bool V_ADD3_U32(const Decoder::Instruction& inst); + bool S_BITSET_B32(const Decoder::Instruction& inst, bool set); + bool V_BCNT_U32_B32(const Decoder::Instruction& inst); + bool V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low); + bool S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst); + bool S_QUADMASK_B64(const Decoder::Instruction& inst); + bool BFM_B32(const Decoder::Instruction& inst); IR::U32 RightMask32(IR::U32 count); IR::U64 RightMask64(IR::U32 count); bool S_BFM_B64(const Decoder::Instruction& inst); @@ -246,27 +246,27 @@ private: void S_SENDMSG(); void S_TTRACEDATA(); void S_INST_PREFETCH(); - bool S_GETPC_B64(const Decoder::Instruction& inst, std::string* error); + void S_GETPC_B64(const Decoder::Instruction& inst); void S_CSELECT_B32(const Decoder::Instruction& inst); void S_CSELECT_B64(const Decoder::Instruction& inst); void MOV_B32(const Decoder::Instruction& inst, bool apply_float_modifiers); void S_MOV_B64(const Decoder::Instruction& inst); void S_WQM_B64(const Decoder::Instruction& inst); - bool V_MOVRELS_B32(const Decoder::Instruction& inst, std::string* error); - bool V_MOVRELD_B32(const Decoder::Instruction& inst, std::string* error); + void V_MOVRELS_B32(const Decoder::Instruction& inst); + void V_MOVRELD_B32(const Decoder::Instruction& inst); void V_READFIRSTLANE_B32(const Decoder::Instruction& inst); void V_READLANE_B32(const Decoder::Instruction& inst); void V_WRITELANE_B32(const Decoder::Instruction& inst); void V_PERMLANE16_B32(const Decoder::Instruction& inst, bool x16); void V_INTERP_P1_F32(); void V_INTERP_P2_F32(const Decoder::Instruction& inst); - bool V_INTERP_MOV_F32(const Decoder::Instruction& inst, std::string* error); - bool EXP(const Decoder::Instruction& inst, std::string* error); + void V_INTERP_MOV_F32(const Decoder::Instruction& inst); + void EXP(const Decoder::Instruction& inst); - bool EmitScalar(const Decoder::Instruction& inst, std::string* error); - bool EmitVector(const Decoder::Instruction& inst, std::string* error); - bool EmitInterpolation(const Decoder::Instruction& inst, std::string* error); - bool EmitMemory(const Decoder::Instruction& inst, std::string* error); + bool EmitScalar(const Decoder::Instruction& inst); + bool EmitVector(const Decoder::Instruction& inst); + bool EmitInterpolation(const Decoder::Instruction& inst); + bool EmitMemory(const Decoder::Instruction& inst); IR::Program& program; IR::IREmitter ir; diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 943854275..5803b1699 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -2,7 +2,7 @@ namespace Libs::Graphics::ShaderRecompiler::Frontend { -bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error) { +bool Translator::EmitVector(const Decoder::Instruction& inst) { using O = Decoder::Opcode; switch (inst.opcode) { case O::V_NOP: return true; @@ -13,8 +13,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error case O::V_SUBREV_CO_CI_U32: SUBB_U32(inst, true); return true; case O::V_MOV_B32: MOV_B32(inst, true); return true; - case O::V_MOVRELS_B32: return V_MOVRELS_B32(inst, error); - case O::V_MOVRELD_B32: return V_MOVRELD_B32(inst, error); + case O::V_MOVRELS_B32: V_MOVRELS_B32(inst); return true; + case O::V_MOVRELD_B32: V_MOVRELD_B32(inst); return true; case O::V_READFIRSTLANE_B32: V_READFIRSTLANE_B32(inst); return true; case O::V_READLANE_B32: V_READLANE_B32(inst); return true; case O::V_WRITELANE_B32: V_WRITELANE_B32(inst); return true; diff --git a/src/graphics/shader/recompiler/ir/Program.cpp b/src/graphics/shader/recompiler/ir/Program.cpp index 1944de5ea..675c84667 100644 --- a/src/graphics/shader/recompiler/ir/Program.cpp +++ b/src/graphics/shader/recompiler/ir/Program.cpp @@ -1,7 +1,6 @@ +#include "common/assert.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" -#include "graphics/shader/shader.h" - #include #include #include @@ -11,10 +10,13 @@ namespace Libs::Graphics::ShaderRecompiler::IR { -using ShaderError::Fail; - namespace { +[[noreturn]] void Fail(std::string_view message) { + EXIT("shader IR validation failed: %s", std::string(message).c_str()); + std::abort(); +} + bool IsRegisterStatePseudo(ValueOpcode opcode) { switch (opcode) { case ValueOpcode::GetThreadBitScalarRegister: @@ -153,10 +155,10 @@ Value ResolveInvariantPhi(const Program& program, Value value) { return invariant; } -bool ValidateProgram(const Program& program, bool require_ssa, std::string* error) { +void ValidateProgram(const Program& program, bool require_ssa) { if (program.blocks.size() != program.block_info.size() || program.blocks.size() != program.block_storage.size()) { - return Fail(error, "value IR block storage is inconsistent"); + return Fail("value IR block storage is inconsistent"); } std::unordered_set blocks; std::unordered_map block_indices; @@ -166,26 +168,26 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro const auto* block = program.blocks[block_index]; if (block == nullptr || program.block_storage[block_index] == nullptr || block != program.block_storage[block_index].get()) { - return Fail(error, "value IR block pointer is inconsistent"); + return Fail("value IR block pointer is inconsistent"); } if (!blocks.insert(block).second) { - return Fail(error, "value IR block pointer is duplicated"); + return Fail("value IR block pointer is duplicated"); } block_indices.emplace(block, block_index); if (program.block_info[block_index].id == UINT32_MAX) { - return Fail(error, "value IR block uses the reserved exit id"); + return Fail("value IR block uses the reserved exit id"); } if (!blocks_by_id.emplace(program.block_info[block_index].id, block).second) { - return Fail(error, "value IR block id is duplicated"); + return Fail("value IR block id is duplicated"); } for (const auto& inst: *block) { if (!instructions.insert(&inst).second) { - return Fail(error, "value IR instruction is duplicated"); + return Fail("value IR instruction is duplicated"); } } } if (!program.blocks.empty() && !program.blocks.front()->ImmPredecessors().empty()) { - return Fail(error, "value IR entry block has a predecessor"); + return Fail("value IR entry block has a predecessor"); } for (size_t block_index = 0; block_index < program.blocks.size(); block_index++) { @@ -193,28 +195,28 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro std::unordered_set predecessors; for (const auto* predecessor: block->ImmPredecessors()) { if (predecessor == nullptr || !blocks.contains(predecessor)) { - return Fail(error, "value IR block has a foreign predecessor"); + return Fail("value IR block has a foreign predecessor"); } if (!predecessors.insert(predecessor).second) { - return Fail(error, "value IR block predecessor is duplicated"); + return Fail("value IR block predecessor is duplicated"); } if (std::ranges::find(predecessor->ImmSuccessors(), block) == predecessor->ImmSuccessors().end()) { - return Fail(error, "value IR predecessor edge is not reciprocal"); + return Fail("value IR predecessor edge is not reciprocal"); } } std::unordered_set successors; for (const auto* successor: block->ImmSuccessors()) { if (successor == nullptr || !blocks.contains(successor)) { - return Fail(error, "value IR block has a foreign successor"); + return Fail("value IR block has a foreign successor"); } if (!successors.insert(successor).second) { - return Fail(error, "value IR block successor is duplicated"); + return Fail("value IR block successor is duplicated"); } if (std::ranges::find(successor->ImmPredecessors(), block) == successor->ImmPredecessors().end()) { - return Fail(error, "value IR successor edge is not reciprocal"); + return Fail("value IR successor edge is not reciprocal"); } } @@ -238,41 +240,40 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro switch (terminator.kind) { case CFG::TerminatorKind::Branch: if (!add_target(terminator.true_block)) { - return Fail(error, "value IR branch target is missing"); + return Fail("value IR branch target is missing"); } break; case CFG::TerminatorKind::ConditionalBranch: if (!add_target(terminator.true_block) || !add_target(terminator.false_block)) { - return Fail(error, "value IR conditional branch target is missing"); + return Fail("value IR conditional branch target is missing"); } if (!validate_control_value(program.block_info[block_index].condition, Type::U1)) { - return Fail(error, "value IR conditional branch condition is invalid"); + return Fail("value IR conditional branch condition is invalid"); } break; case CFG::TerminatorKind::IndirectBranch: { if (!validate_control_value(program.block_info[block_index].indirect_target, Type::U32)) { - return Fail(error, "value IR indirect branch selector is invalid"); + return Fail("value IR indirect branch selector is invalid"); } std::unordered_set indirect_targets; for (const auto target: terminator.indirect_targets) { if (!indirect_targets.insert(target).second) { - return Fail(error, "value IR indirect branch target is duplicated"); + return Fail("value IR indirect branch target is duplicated"); } if (!add_target(target)) { - return Fail(error, "value IR indirect branch target is missing"); + return Fail("value IR indirect branch target is missing"); } } if (terminator.indirect_selector_values.size() != terminator.indirect_selector_targets.size()) { - return Fail(error, "value IR indirect selector table is inconsistent"); + return Fail("value IR indirect selector table is inconsistent"); } for (const auto target: terminator.indirect_selector_targets) { const auto found = blocks_by_id.find(target); if (found == blocks_by_id.end() || !expected_successors.contains(found->second)) { - return Fail(error, - "value IR indirect selector target is not a CFG successor"); + return Fail("value IR indirect selector target is not a CFG successor"); } } break; @@ -284,73 +285,73 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro !blocks_by_id.contains(terminator.merge_block)) || (terminator.continue_block != UINT32_MAX && !blocks_by_id.contains(terminator.continue_block))) { - return Fail(error, "value IR structured control target is missing"); + return Fail("value IR structured control target is missing"); } if (successors != expected_successors) { - return Fail(error, "value IR terminator and successor graph disagree"); + return Fail("value IR terminator and successor graph disagree"); } bool saw_non_phi = false; for (const auto& inst: *block) { if (inst.Parent() != block) { - return Fail(error, "value IR instruction has the wrong parent block"); + return Fail("value IR instruction has the wrong parent block"); } if (inst.GetOpcode() == ValueOpcode::Phi) { if (saw_non_phi) { - return Fail(error, "value IR Phi appears after a non-Phi instruction"); + return Fail("value IR Phi appears after a non-Phi instruction"); } if (inst.NumPhiBlocks() != inst.NumArgs()) { - return Fail(error, "value IR Phi parent table is inconsistent"); + return Fail("value IR Phi parent table is inconsistent"); } if (inst.NumArgs() == 0) { - return Fail(error, "value IR Phi has no incoming values"); + return Fail("value IR Phi has no incoming values"); } std::unordered_set incoming_blocks; for (size_t arg_index = 0; arg_index < inst.NumArgs(); arg_index++) { const auto* predecessor = inst.PhiBlock(arg_index); if (predecessor == nullptr || !blocks.contains(predecessor) || !predecessors.contains(predecessor)) { - return Fail(error, "value IR Phi has a foreign or non-predecessor parent"); + return Fail("value IR Phi has a foreign or non-predecessor parent"); } if (!incoming_blocks.insert(predecessor).second) { - return Fail(error, "value IR Phi parent is duplicated"); + return Fail("value IR Phi parent is duplicated"); } if (inst.Arg(arg_index).GetType() != inst.GetType()) { - return Fail(error, "value IR Phi incoming type does not match its result"); + return Fail("value IR Phi incoming type does not match its result"); } } if (incoming_blocks != predecessors) { - return Fail(error, "value IR Phi does not cover every predecessor"); + return Fail("value IR Phi does not cover every predecessor"); } } else { saw_non_phi = true; } if (require_ssa && IsRegisterStatePseudo(inst.GetOpcode())) { - return Fail(error, fmt::format("register-state pseudo {} survived SSA rewrite", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("register-state pseudo {} survived SSA rewrite", + ValueOpcodeName(inst.GetOpcode()))); } if (inst.GetOpcode() != ValueOpcode::Phi && inst.GetOpcode() != ValueOpcode::Identity && inst.GetType() == Type::Opaque) { - return Fail(error, fmt::format("untyped opcode {} survived translation", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("untyped opcode {} survived translation", + ValueOpcodeName(inst.GetOpcode()))); } const bool fixed_signature = inst.GetOpcode() != ValueOpcode::Phi && inst.GetOpcode() != ValueOpcode::Identity; if (fixed_signature && inst.NumArgs() != NumArgsOf(inst.GetOpcode())) { - return Fail(error, fmt::format("{} has {} arguments, expected {}", - ValueOpcodeName(inst.GetOpcode()), inst.NumArgs(), - NumArgsOf(inst.GetOpcode()))); + return Fail(fmt::format("{} has {} arguments, expected {}", + ValueOpcodeName(inst.GetOpcode()), inst.NumArgs(), + NumArgsOf(inst.GetOpcode()))); } if (inst.GetOpcode() == ValueOpcode::ReadConstBuffer) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { - return Fail(error, fmt::format("{} has an invalid memory-info index", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has an invalid memory-info index", + ValueOpcodeName(inst.GetOpcode()))); } const auto& memory = program.memory_info[memory_index]; if (memory.kind != ResourceKind::ScalarBuffer) { - return Fail(error, fmt::format("{} has an invalid scalar-memory resource kind", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has an invalid scalar-memory resource kind", + ValueOpcodeName(inst.GetOpcode()))); } const bool valid_group_width = memory.component_count == 1u || memory.component_count == 2u || @@ -358,23 +359,23 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro memory.component_count == 16u; if (memory.data_bits != 32u || memory.data_dwords != 1u || !valid_group_width || memory.component_index >= memory.component_count) { - return Fail(error, fmt::format("{} has inconsistent scalar-memory metadata", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has inconsistent scalar-memory metadata", + ValueOpcodeName(inst.GetOpcode()))); } } const auto address_info = AddressOpcodeInfoOf(inst.GetOpcode()); if (address_info.access != AddressAccess::None) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { - return Fail(error, fmt::format("{} has an invalid memory-info index", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has an invalid memory-info index", + ValueOpcodeName(inst.GetOpcode()))); } const auto& memory = program.memory_info[memory_index]; if (!IsAddressResourceKind(memory.kind) || (memory.kind == ResourceKind::ScalarAddress && inst.GetOpcode() != ValueOpcode::LoadAddressU32)) { - return Fail(error, fmt::format("{} has an invalid address resource kind", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has an invalid address resource kind", + ValueOpcodeName(inst.GetOpcode()))); } const bool scalar_address = memory.kind == ResourceKind::ScalarAddress; const bool valid_group_width = @@ -386,50 +387,50 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro if (memory.data_bits != address_info.data_bits || memory.data_dwords != 1u || !valid_group_width || memory.component_index >= memory.component_count || memory.sampler != 0u) { - return Fail(error, fmt::format("{} has inconsistent address-memory metadata", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has inconsistent address-memory metadata", + ValueOpcodeName(inst.GetOpcode()))); } } const auto buffer_components = BufferComponentCount(inst.GetOpcode()); if (buffer_components != 0u) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { - return Fail(error, fmt::format("{} has an invalid memory-info index", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has an invalid memory-info index", + ValueOpcodeName(inst.GetOpcode()))); } const auto& memory = program.memory_info[memory_index]; if (memory.kind != ResourceKind::Buffer && memory.kind != ResourceKind::ScalarBuffer) { - return Fail(error, fmt::format("{} has a non-buffer resource kind", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has a non-buffer resource kind", + ValueOpcodeName(inst.GetOpcode()))); } if (buffer_components > 1u && (memory.kind != ResourceKind::Buffer || memory.data_bits != 32u || memory.data_dwords != buffer_components || memory.component_index != 0u)) { - return Fail(error, fmt::format("{} has inconsistent native-wide metadata", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has inconsistent native-wide metadata", + ValueOpcodeName(inst.GetOpcode()))); } if (buffer_components == 1u && (inst.GetOpcode() == ValueOpcode::LoadBufferU32 || inst.GetOpcode() == ValueOpcode::StoreBufferU32) && memory.data_dwords != 1u) { - return Fail(error, fmt::format("{} retains scalar-sibling width metadata", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} retains scalar-sibling width metadata", + ValueOpcodeName(inst.GetOpcode()))); } } const auto shared_components = SharedComponentCount(inst.GetOpcode()); if (shared_components != 0u) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { - return Fail(error, fmt::format("{} has an invalid memory-info index", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has an invalid memory-info index", + ValueOpcodeName(inst.GetOpcode()))); } const auto& memory = program.memory_info[memory_index]; if ((memory.kind != ResourceKind::Lds && memory.kind != ResourceKind::Gds) || memory.resource != 0u || memory.sampler != 0u || memory.component_count == 0u || memory.component_index >= memory.component_count) { - return Fail(error, fmt::format("{} has invalid shared-memory metadata", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has invalid shared-memory metadata", + ValueOpcodeName(inst.GetOpcode()))); } uint32_t expected_bits = 32u; if (inst.GetOpcode() == ValueOpcode::LoadSharedU8 || @@ -441,26 +442,26 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro } if (memory.data_bits != expected_bits || memory.data_dwords != shared_components || (shared_components > 1u && memory.component_index != 0u)) { - return Fail(error, fmt::format("{} has inconsistent shared-memory width", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has inconsistent shared-memory width", + ValueOpcodeName(inst.GetOpcode()))); } } const auto image_info = ImageOpcodeInfoOf(inst.GetOpcode()); if (image_info.access != ImageAccess::None) { const auto memory_index = inst.Flags().index; if (memory_index >= program.memory_info.size()) { - return Fail(error, fmt::format("{} has an invalid memory-info index", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has an invalid memory-info index", + ValueOpcodeName(inst.GetOpcode()))); } const auto& memory = program.memory_info[memory_index]; if (!ImageResourceKindMatches(memory.kind, image_info.resource_class)) { - return Fail(error, fmt::format("{} has invalid image-memory metadata", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has invalid image-memory metadata", + ValueOpcodeName(inst.GetOpcode()))); } } if (inst.GetOpcode() == ValueOpcode::SetAttribute && inst.Flags().index >= program.export_info.size()) { - return Fail(error, "SetAttribute has an invalid export-info index"); + return Fail("SetAttribute has an invalid export-info index"); } uint32_t composite_components = 0u; switch (inst.GetOpcode()) { @@ -473,25 +474,24 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro if (composite_components != 0u && (!inst.Arg(1).IsImmediate() || inst.Arg(1).GetType() != Type::U32 || inst.Arg(1).U32() >= composite_components)) { - return Fail(error, fmt::format("{} has an invalid component index", - ValueOpcodeName(inst.GetOpcode()))); + return Fail(fmt::format("{} has an invalid component index", + ValueOpcodeName(inst.GetOpcode()))); } for (size_t arg_index = 0; arg_index < inst.NumArgs(); arg_index++) { const auto arg = inst.Arg(arg_index); if (arg.IsEmpty()) { - return Fail(error, fmt::format("{} has an empty argument", - ValueOpcodeName(inst.GetOpcode()))); + return Fail( + fmt::format("{} has an empty argument", ValueOpcodeName(inst.GetOpcode()))); } if (fixed_signature && arg.GetType() != ArgTypeOf(inst.GetOpcode(), arg_index)) { - return Fail(error, - fmt::format("{} argument {} has type {}, expected {}", + return Fail(fmt::format("{} argument {} has type {}, expected {}", ValueOpcodeName(inst.GetOpcode()), arg_index, TypeName(arg.GetType()), TypeName(ArgTypeOf(inst.GetOpcode(), arg_index)))); } if (const auto* definition = arg.TryInstruction(); definition != nullptr && !instructions.contains(definition)) { - return Fail(error, "value IR argument has a foreign definition"); + return Fail("value IR argument has a foreign definition"); } if (const auto* definition = arg.TryInstruction(); definition != nullptr) { const auto& uses = definition->Uses(); @@ -499,8 +499,7 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro return candidate.user == &inst && candidate.operand == arg_index; }); if (use == uses.end()) { - return Fail(error, - fmt::format("{} argument {} is absent from {} reverse uses", + return Fail(fmt::format("{} argument {} is absent from {} reverse uses", ValueOpcodeName(inst.GetOpcode()), arg_index, ValueOpcodeName(definition->GetOpcode()))); } @@ -510,7 +509,7 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro } if (program.blocks.empty()) { - return true; + return; } std::vector reachable(program.blocks.size(), false); @@ -527,7 +526,7 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro } } if (!std::ranges::all_of(reachable, [](bool value) { return value; })) { - return Fail(error, "value IR contains an unreachable block"); + return Fail("value IR contains an unreachable block"); } std::vector> dominators(program.blocks.size(), @@ -581,28 +580,25 @@ bool ValidateProgram(const Program& program, bool require_ssa, std::string* erro const auto* predecessor = inst.PhiBlock(arg_index); if (definition->Parent() != predecessor && !dominates(definition->Parent(), predecessor)) { - return Fail(error, - "value IR Phi incoming definition does not dominate its edge"); + return Fail("value IR Phi incoming definition does not dominate its edge"); } } else if (definition->Parent() == block) { if (instruction_positions.at(definition) >= instruction_positions.at(&inst)) { - return Fail(error, - "value IR instruction uses a same-block definition before it"); + return Fail("value IR instruction uses a same-block definition before it"); } } else if (!dominates(definition->Parent(), block)) { - return Fail(error, "value IR instruction definition does not dominate its use"); + return Fail("value IR instruction definition does not dominate its use"); } } } const auto& info = program.block_info[block_index]; if (!info.condition.IsEmpty() && !control_dominates(info.condition, block)) { - return Fail(error, "value IR branch condition definition does not dominate its use"); + return Fail("value IR branch condition definition does not dominate its use"); } if (!info.indirect_target.IsEmpty() && !control_dominates(info.indirect_target, block)) { - return Fail(error, "value IR indirect selector definition does not dominate its use"); + return Fail("value IR indirect selector definition does not dominate its use"); } } - return true; } void ResolveControlFlowIdentities(Program& program) { diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index a7b0546a3..c09352a26 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -477,7 +477,6 @@ struct DescriptorSource { struct SrtRead { Value value; uint32_t flat_offset = 0; - uint32_t use_pc = 0; bool operator==(const SrtRead& other) const = default; }; @@ -523,7 +522,7 @@ struct Program { std::string ProgramToString(const Program& program); -bool ValidateProgram(const Program& program, bool require_ssa, std::string* error); +void ValidateProgram(const Program& program, bool require_ssa); void ResolveControlFlowIdentities(Program& program); bool EquivalentValue(const Program& program, Value left, Value right); Value ResolveInvariantPhi(const Program& program, Value value); diff --git a/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp b/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp index e3d8d9475..c62a6ef9b 100644 --- a/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp +++ b/src/graphics/shader/recompiler/ir/passes/BindingLayout.cpp @@ -1,5 +1,6 @@ #include "graphics/shader/recompiler/ir/passes/BindingLayout.h" +#include "common/assert.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include @@ -9,12 +10,17 @@ namespace Libs::Graphics::ShaderRecompiler::IR { namespace { -constexpr auto FirstImageBinding = DescriptorBindingKind::Sampled1D; +constexpr auto FirstImageBinding = DescriptorBindingKind::Sampled1D; constexpr uint32_t ImageBindingCount = static_cast(DescriptorBindingKind::StorageAtomic3D) - static_cast(FirstImageBinding) + 1u; -bool CollectUserData(const Program& program, std::vector& result) { +[[noreturn]] void BindingFail(const char* message) { + EXIT("shader binding layout failed: %s", message); + std::abort(); +} + +std::vector CollectUserData(const Program& program) { std::array registers {}; for (const auto* block: program.blocks) { for (const auto& inst: *block) { @@ -22,22 +28,22 @@ bool CollectUserData(const Program& program, std::vector& result) { continue; } if (inst.Arg(0).GetType() != Type::ScalarReg) { - return false; + BindingFail("typed shader contains an invalid user-data register"); } const auto index = RegIndex(inst.Arg(0).ScalarRegister()); if (index >= NumScalarRegs) { - return false; + BindingFail("typed shader contains an invalid user-data register"); } registers[index] = true; } } - result.clear(); + std::vector result; for (uint32_t index = 0; index < registers.size(); index++) { if (registers[index]) { result.push_back(index); } } - return true; + return result; } void AddBinding(BindingLayout& layout, DescriptorBindingKind kind, @@ -45,8 +51,8 @@ void AddBinding(BindingLayout& layout, DescriptorBindingKind kind, layout.descriptors.push_back({kind, std::move(resources)}); } -bool UsesGds(const Program& program, bool& uses_gds) { - uses_gds = false; +bool UsesGds(const Program& program) { + bool uses_gds = false; for (const auto* block: program.blocks) { for (const auto& inst: *block) { if (SharedAccessOf(inst.GetOpcode()) == SharedAccess::None) { @@ -54,44 +60,36 @@ bool UsesGds(const Program& program, bool& uses_gds) { } const auto index = inst.Flags().index; if (index >= program.memory_info.size()) { - return false; + BindingFail("typed shader contains invalid shared-memory metadata"); } const auto kind = program.memory_info[index].kind; if (kind != ResourceKind::Lds && kind != ResourceKind::Gds) { - return false; + BindingFail("typed shader contains invalid shared-memory metadata"); } uses_gds |= kind == ResourceKind::Gds; } } - return true; + return uses_gds; } } // namespace -bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::string* error) { +void AllocateBindings(Program& program, uint32_t push_constant_offset) { if (!program.shader_info_complete || program.binding_layout_complete) { - if (error != nullptr) { - *error = !program.shader_info_complete ? "shader info is not ready" - : "binding layout already allocated"; - } - return false; + EXIT("shader binding layout failed: %s", !program.shader_info_complete + ? "shader info is not ready" + : "binding layout already allocated"); } if (push_constant_offset > NativePushConstantSize || push_constant_offset % sizeof(uint32_t) != 0) { - if (error != nullptr) { - *error = "push-constant offset exceeds the Vulkan minimum guarantee or is unaligned"; - } - return false; + EXIT("shader binding layout failed: push-constant offset %u exceeds the Vulkan minimum " + "guarantee or is unaligned", + push_constant_offset); } BindingLayout next; next.push_constant_offset = push_constant_offset; - if (!CollectUserData(program, next.user_data_registers)) { - if (error != nullptr) { - *error = "typed shader contains an invalid user-data register"; - } - return false; - } + next.user_data_registers = CollectUserData(program); next.memory_offset_dword = static_cast(next.user_data_registers.size()); next.memory_offset_count = static_cast(program.info.buffers.size()); @@ -107,26 +105,18 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri for (uint32_t i = 0; i < program.info.images.size(); i++) { const auto kind = DescriptorBindingForImage(program.info.images[i]); if (!kind.has_value()) { - if (error != nullptr) { - *error = "shader info contains an invalid image binding class"; - } - return false; + EXIT("shader binding layout failed: image %u has an invalid binding class", i); } const auto group = static_cast(*kind) - static_cast(FirstImageBinding); if (group >= image_groups.size()) { - if (error != nullptr) { - *error = "shader info contains an unmapped image binding class"; - } - return false; + EXIT("shader binding layout failed: image %u has an unmapped binding class", i); } auto& resources = image_groups[group]; const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::DynamicStorage; const auto count = dynamic ? program.info.images[i].mip_count : 1u; if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) { - if (error != nullptr) { - *error = "image has an invalid specialized mip descriptor count"; - } - return false; + EXIT("shader binding layout failed: image %u has invalid specialized mip count %u", i, + program.info.images[i].mip_count); } resources.insert(resources.end(), count, i); } @@ -146,14 +136,7 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri } AddBinding(next, DescriptorBindingKind::Samplers, std::move(resources)); } - bool uses_gds = false; - if (!UsesGds(program, uses_gds)) { - if (error != nullptr) { - *error = "typed shader contains invalid shared-memory metadata"; - } - return false; - } - if (uses_gds) { + if (UsesGds(program)) { AddBinding(next, DescriptorBindingKind::Gds); } if (program.info.uses_dma) { @@ -178,7 +161,6 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri program.bindings = std::move(next); program.binding_layout_complete = true; - return true; } const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind) { diff --git a/src/graphics/shader/recompiler/ir/passes/BindingLayout.h b/src/graphics/shader/recompiler/ir/passes/BindingLayout.h index 6069e8a3a..8ac25cd3d 100644 --- a/src/graphics/shader/recompiler/ir/passes/BindingLayout.h +++ b/src/graphics/shader/recompiler/ir/passes/BindingLayout.h @@ -5,7 +5,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR { -bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::string* error); +void AllocateBindings(Program& program, uint32_t push_constant_offset); const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind); diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index 57358f69a..f2a25fc8c 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -1,5 +1,6 @@ #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" +#include "common/assert.h" #include "graphics/guest_gpu/gpu_format.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/shaderBindings.h" @@ -18,6 +19,11 @@ namespace { constexpr uint64_t AddressMask = 0x0000ffffffffffffull; constexpr uint64_t MaxIndirectImageProbes = 65536u; +[[noreturn]] void SpecializationFail(std::string_view message) { + EXIT("shader resource specialization failed: %s", std::string(message).c_str()); + std::abort(); +} + Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor, Decoder::ImageDimension requested) { const bool is_array = requested == Decoder::ImageDimension::Dim1DArray || @@ -79,7 +85,7 @@ uint32_t DescriptorImageSwizzle(const DescriptorValue& descriptor) { Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) { return Prospero::RemapTextureFormat(format) != format ? format - : Prospero::BufferFormat::kInvalid; + : Prospero::BufferFormat::kInvalid; } bool DescriptorIsCube(const DescriptorValue& descriptor) { @@ -153,8 +159,7 @@ bool ReadSpecializationWord(const SrtRuntime& runtime, uint64_t address, uint32_ } bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynamic_offset, - uint32_t immediate_offset, const SrtRuntime& runtime, uint32_t& word, - std::string* error, uint32_t use_pc) { + uint32_t immediate_offset, const SrtRuntime& runtime, uint32_t& word) { const auto byte_offset = static_cast(dynamic_offset) + immediate_offset; const auto aligned = byte_offset & ~uint64_t {3}; const auto size = ScalarBufferSize(descriptor); @@ -164,20 +169,10 @@ bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynam } const auto base = descriptor.Base48() & ~uint64_t {3}; if (aligned > AddressMask - base) { - if (error != nullptr) { - *error = fmt::format("indirect image scalar read at pc 0x{:08x} exceeds the " - "48-bit address space", - use_pc); - } return false; } const auto address = base + aligned; if (!ReadSpecializationWord(runtime, address, word)) { - if (error != nullptr) { - *error = fmt::format("indirect image scalar read at pc 0x{:08x} failed at " - "0x{:016x}", - use_pc, address); - } return false; } return true; @@ -186,23 +181,14 @@ bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynam bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect, const DescriptorValue& material_value, const DescriptorValue& heap_value, const SrtRuntime& runtime, - ResourceSnapshot::IndirectImage& result, std::string* error, - uint32_t use_pc) { + ResourceSnapshot::IndirectImage& result) { ShaderBufferResource material; ShaderBufferResource heap; if (!DecodeBufferDescriptor(material_value, material) || !DecodeBufferDescriptor(heap_value, heap)) { - if (error != nullptr) { - *error = fmt::format("indirect image tables at pc 0x{:08x} have invalid descriptors", - use_pc); - } return false; } if (material.Stride() != indirect.selector_stride) { - if (error != nullptr) { - *error = - fmt::format("indirect image material table at pc 0x{:08x} changed stride", use_pc); - } return false; } @@ -215,11 +201,6 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect, const auto limit = std::min(UINT32_MAX, size + 3u); const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u; if (probe_count > MaxIndirectImageProbes) { - if (error != nullptr) { - *error = fmt::format("indirect image material table at pc 0x{:08x} requires {} " - "bounded probes", - use_pc, probe_count); - } return false; } @@ -227,8 +208,7 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect, std::unordered_set seen {0u}; for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) { uint32_t key = 0; - if (!ReadScalarBufferWord(material, static_cast(offset), 0u, runtime, key, error, - use_pc)) { + if (!ReadScalarBufferWord(material, static_cast(offset), 0u, runtime, key)) { return false; } if (seen.insert(key).second) { @@ -248,7 +228,7 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect, const auto heap_offset = key << 5u; for (uint32_t dword = 0; dword < candidate.dword_count; dword++) { if (!ReadScalarBufferWord(heap, heap_offset, dword * sizeof(uint32_t), runtime, - candidate.dwords[dword], error, use_pc)) { + candidate.dwords[dword])) { return false; } } @@ -281,35 +261,22 @@ size_t FlattenedRuntimeDwords(const Program& program) { } // namespace -bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot, - std::string* error) { +bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot) { if (!program.resource_tracking_complete) { - if (error != nullptr) { - *error = "shader resources were not tracked"; - } return false; } if (snapshot.buffers.size() != program.info.buffers.size() || snapshot.images.size() != program.info.images.size() || snapshot.samplers.size() != program.info.samplers.size()) { - if (error != nullptr) { - *error = "resource snapshot does not match dense shader topology"; - } return false; } if (snapshot.flattened_srt.size() != FlattenedRuntimeDwords(program)) { - if (error != nullptr) { - *error = "flattened SRT snapshot does not match the shader plan"; - } return false; } if (program.binding_layout_complete) { for (const auto reg: program.bindings.user_data_registers) { if (reg < program.user_data_base || reg - program.user_data_base >= snapshot.user_data.size()) { - if (error != nullptr) { - *error = fmt::format("runtime snapshot is missing user SGPR {}", reg); - } return false; } } @@ -317,19 +284,12 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto alias = program.info.buffers[i].image_alias; if (alias != BufferResource::NoImageAlias && alias >= program.info.images.size()) { - if (error != nullptr) { - *error = fmt::format("buffer resource {} has invalid image alias {}", i, alias); - } return false; } } - const auto CheckWidth = [&](const auto& values, uint32_t width, const char* kind) { + const auto CheckWidth = [&](const auto& values, uint32_t width) { for (uint32_t i = 0; i < values.size(); i++) { if (values[i].dword_count != width) { - if (error != nullptr) { - *error = fmt::format("{} descriptor {} has {} dwords", kind, i, - values[i].dword_count); - } return false; } } @@ -345,56 +305,37 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn table.keys.empty() || table.keys.size() != table.candidates.size() || table.capacity < table.keys.size() || table.descriptors.empty() || !indirect_resources.insert(table.resource).second) { - if (error != nullptr) { - *error = "indirect image snapshot does not match the shader plan"; - } return false; } for (const auto candidate: table.candidates) { if (candidate >= table.descriptors.size()) { - if (error != nullptr) { - *error = "indirect image snapshot has an invalid candidate"; - } return false; } } if (snapshot.images[table.resource] != table.descriptors[table.candidates[0]]) { - if (error != nullptr) { - *error = "indirect image snapshot has an inconsistent root descriptor"; - } return false; } } - return CheckWidth(snapshot.buffers, 4, "buffer") && CheckWidth(snapshot.images, 8, "image") && - CheckWidth(snapshot.samplers, 4, "sampler"); + return CheckWidth(snapshot.buffers, 4) && CheckWidth(snapshot.images, 8) && + CheckWidth(snapshot.samplers, 4); } -bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot, - std::string* error) { - if (!ValidateResourceSnapshot(program, snapshot, error)) { +bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot) { + if (!ValidateResourceSnapshot(program, snapshot)) { return false; } if (!snapshot.indirect_images.empty()) { - if (error != nullptr) { - *error = "indirect image snapshot was not specialized"; - } return false; } for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto& buffer = program.info.buffers[i]; ShaderBufferResource descriptor; if (!DecodeBufferDescriptor(snapshot.buffers[i], descriptor)) { - if (error != nullptr) { - *error = fmt::format("buffer descriptor {} has invalid width", i); - } return false; } if (buffer.packed_stride != descriptor.PackedStride() || buffer.descriptor_format != descriptor.Format() || buffer.descriptor_swizzle != descriptor.DstSelXYZW()) { - if (error != nullptr) { - *error = fmt::format("buffer descriptor {} no longer matches specialization", i); - } return false; } } @@ -403,9 +344,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh const auto& descriptor = snapshot.images[i]; const auto mip_count = StorageMipCount(image, descriptor); if (mip_count == 0u || mip_count != image.mip_count) { - if (error != nullptr) { - *error = fmt::format("storage image descriptor {} changed mip range", i); - } return false; } if (NullImageDescriptor(descriptor)) { @@ -419,10 +357,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh program.info.images[root].conversion_format != image.conversion_format || program.info.images[root].shader_swizzle != image.shader_swizzle || program.info.images[root].cube != image.cube) { - if (error != nullptr) { - *error = fmt::format( - "null indirect image descriptor {} changed binding class", i); - } return false; } continue; @@ -434,10 +368,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh } if (image.dimension != Decoder::ImageDimension::Dim2D || image.cube || !canonical_kind) { - if (error != nullptr) { - *error = fmt::format( - "image descriptor {} no longer matches canonical null specialization", i); - } return false; } continue; @@ -445,14 +375,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh const auto dimension = DescriptorDimension(descriptor, image.dimension); if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension || DescriptorIsCube(descriptor) != image.cube) { - if (error != nullptr) { - *error = - fmt::format("image descriptor {} no longer matches specialized dimension: " - "{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x}", - i, descriptor.dwords[0], descriptor.dwords[1], descriptor.dwords[2], - descriptor.dwords[3], descriptor.dwords[4], descriptor.dwords[5], - descriptor.dwords[6], descriptor.dwords[7]); - } return false; } if (image.kind == ResourceKind::Image || image.kind == ResourceKind::ImageUint || @@ -463,49 +385,25 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh const auto format = static_cast((descriptor.dwords[1] >> 20u) & 0x1ffu); if (image.atomic && format != Prospero::BufferFormat::k32UInt) { - if (error != nullptr) { - *error = fmt::format( - "atomic image descriptor {} changed to unsupported format {}", i, - static_cast(format)); - } return false; } const auto conversion_format = ImageConversionFormat(format); if (image.conversion_format != conversion_format) { - if (error != nullptr) { - *error = fmt::format("image descriptor {} changed format conversion", i); - } return false; } if ((storage || conversion_format != Prospero::BufferFormat::kInvalid) && image.shader_swizzle != DescriptorImageSwizzle(descriptor)) { - if (error != nullptr) { - *error = fmt::format("image descriptor {} changed swizzle", i); - } return false; } - const bool raw_sint_storage = - storage && format == Prospero::BufferFormat::k32SInt && image.written && - !image.read && !image.atomic; - const bool uint_descriptor = - Prospero::IsUintTextureFormat(format) || raw_sint_storage; - const auto uint_program = image.kind == ResourceKind::ImageUint || - image.kind == ResourceKind::StorageImageUint; + const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt && + image.written && !image.read && !image.atomic; + const bool uint_descriptor = Prospero::IsUintTextureFormat(format) || raw_sint_storage; + const auto uint_program = image.kind == ResourceKind::ImageUint || + image.kind == ResourceKind::StorageImageUint; if (uint_descriptor != uint_program && !(image.atomic && uint_program)) { - if (error != nullptr) { - *error = fmt::format( - "image descriptor {} no longer matches specialized format: " - "{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x}", - i, descriptor.dwords[0], descriptor.dwords[1], descriptor.dwords[2], - descriptor.dwords[3], descriptor.dwords[4], descriptor.dwords[5], - descriptor.dwords[6], descriptor.dwords[7]); - } return false; } if (image.depth_compare && uint_program) { - if (error != nullptr) { - *error = fmt::format("integer image descriptor {} uses depth comparison", i); - } return false; } } @@ -514,11 +412,8 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh } bool MaterializeResources(const Program& program, const SrtRuntime& runtime, - ResourceSnapshot& snapshot, std::string* error) { + ResourceSnapshot& snapshot) { if (!program.resource_tracking_complete) { - if (error != nullptr) { - *error = "shader resources were not tracked"; - } return false; } @@ -527,17 +422,12 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime, requests.reserve(program.info.buffers.size() + program.info.images.size() * 2u + program.info.samplers.size()); for (const auto& buffer: program.info.buffers) { - requests.push_back({buffer.source, buffer.first_use_pc}); + requests.push_back({buffer.source}); } for (const auto& image: program.info.images) { const auto* source = Source(program, image.source); if (source != nullptr && source->indirect_image.has_value()) { if (runtime.read_specialization_memory == nullptr) { - if (error != nullptr) { - *error = fmt::format("indirect image table at pc 0x{:08x} has no clean " - "memory reader", - image.first_use_pc); - } return false; } MarkCleanFlatSlots(program, Source(program, source->indirect_image->material_source), @@ -545,16 +435,16 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime, MarkCleanFlatSlots(program, Source(program, source->indirect_image->heap_source), clean_flat_slots); } else { - requests.push_back({image.source, image.first_use_pc}); + requests.push_back({image.source}); } } for (const auto& sampler: program.info.samplers) { - requests.push_back({sampler.source, sampler.first_use_pc}); + requests.push_back({sampler.source}); } std::vector values; std::vector flattened_srt; - if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt, clean_flat_slots, - error)) { + if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt, + clean_flat_slots)) { return false; } @@ -581,20 +471,19 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime, continue; } const std::array requests { - DescriptorSourceRequest {source->indirect_image->material_source, - image.first_use_pc}, - DescriptorSourceRequest {source->indirect_image->heap_source, image.first_use_pc}}; + DescriptorSourceRequest {source->indirect_image->material_source}, + DescriptorSourceRequest {source->indirect_image->heap_source}}; SrtRuntime clean_runtime = runtime; clean_runtime.read_memory = runtime.read_specialization_memory; std::vector tables; - if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables, error)) { + if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables)) { return false; } const auto& material = tables[0]; const auto& heap = tables[1]; ResourceSnapshot::IndirectImage table; - if (!MaterializeIndirectImage(*source->indirect_image, material, heap, runtime, table, - error, image.first_use_pc)) { + if (!MaterializeIndirectImage(*source->indirect_image, material, heap, runtime, + table)) { return false; } if (image.indirect_root == ImageResource::NoIndirectImage) { @@ -608,20 +497,12 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime, } if (table.keys.size() > image.indirect_mapping_capacity || table.descriptors.size() > image.indirect_resources.size()) { - if (error != nullptr) { - *error = fmt::format( - "indirect image table at pc 0x{:08x} changed candidate topology", - image.first_use_pc); - } return false; } for (uint32_t candidate = 0; candidate < image.indirect_resources.size(); candidate++) { const auto resource = image.indirect_resources[candidate]; if (resource >= program.info.images.size() || program.info.images[resource].indirect_root != image_index) { - if (error != nullptr) { - *error = "indirect image specialization has an invalid candidate resource"; - } return false; } next.images[resource] = @@ -649,31 +530,26 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime, } } if (std::ranges::find(image_written, uint8_t {0}) != image_written.end()) { - if (error != nullptr) { - *error = "indirect image specialization left an unmaterialized candidate"; - } return false; } next.samplers.assign(cursor, cursor + program.info.samplers.size()); next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end()); - if (!ValidateResourceSnapshot(program, next, error)) { + if (!ValidateResourceSnapshot(program, next)) { return false; } snapshot = std::move(next); return true; } -bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::string* error) { +void SpecializeResources(Program& program, ResourceSnapshot& snapshot) { if (!program.resource_tracking_complete || program.shader_info_complete || program.binding_layout_complete) { - if (error != nullptr) { - *error = !program.resource_tracking_complete ? "shader resources were not tracked" - : "resource specialization is too late"; - } - return false; + return SpecializationFail(!program.resource_tracking_complete + ? "shader resources were not tracked" + : "resource specialization is too late"); } - if (!ValidateResourceSnapshot(program, snapshot, error)) { - return false; + if (!ValidateResourceSnapshot(program, snapshot)) { + return SpecializationFail("invalid resource snapshot"); } auto next = program.info; @@ -681,10 +557,8 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri for (const auto& table: snapshot.indirect_images) { if (table.resource >= next.images.size() || table.descriptors.size() < 2u || next.images.size() + table.descriptors.size() - 1u > ShaderInfo::MaxImages) { - if (error != nullptr) { - *error = "indirect image candidates exceed the dense image resource limit"; - } - return false; + return SpecializationFail( + "indirect image candidates exceed the dense image resource limit"); } const auto root_image = next.images[table.resource]; std::vector resources(table.descriptors.size()); @@ -720,10 +594,7 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri for (uint32_t i = 0; i < next.buffers.size(); i++) { ShaderBufferResource descriptor; if (!DecodeBufferDescriptor(next_snapshot.buffers[i], descriptor)) { - if (error != nullptr) { - *error = fmt::format("buffer descriptor {} has invalid width", i); - } - return false; + return SpecializationFail(fmt::format("buffer descriptor {} has invalid width", i)); } next.buffers[i].packed_stride = descriptor.PackedStride(); next.buffers[i].descriptor_format = descriptor.Format(); @@ -734,10 +605,8 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri auto& image = next.images[i]; image.mip_count = StorageMipCount(image, descriptor); if (image.mip_count == 0u) { - if (error != nullptr) { - *error = fmt::format("storage image descriptor {} has an invalid mip range", i); - } - return false; + return SpecializationFail( + fmt::format("storage image descriptor {} has an invalid mip range", i)); } if (NullImageDescriptor(descriptor)) { image.dimension = Decoder::ImageDimension::Dim2D; @@ -755,37 +624,31 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri } const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension); if (descriptor_dimension == Decoder::ImageDimension::Unknown) { - if (error != nullptr) { - *error = fmt::format( - "image descriptor {} has unsupported type {}: {:08x},{:08x},{:08x},{:08x}," - "{:08x},{:08x},{:08x},{:08x}", - i, (descriptor.dwords[3] >> 28u) & 0xfu, descriptor.dwords[0], - descriptor.dwords[1], descriptor.dwords[2], descriptor.dwords[3], - descriptor.dwords[4], descriptor.dwords[5], descriptor.dwords[6], - descriptor.dwords[7]); - } - return false; + return SpecializationFail(fmt::format( + "image descriptor {} has unsupported type {}: {:08x},{:08x},{:08x},{:08x}," + "{:08x},{:08x},{:08x},{:08x}", + i, (descriptor.dwords[3] >> 28u) & 0xfu, descriptor.dwords[0], descriptor.dwords[1], + descriptor.dwords[2], descriptor.dwords[3], descriptor.dwords[4], + descriptor.dwords[5], descriptor.dwords[6], descriptor.dwords[7])); } image.dimension = descriptor_dimension; image.cube = DescriptorIsCube(descriptor); const auto format = static_cast((descriptor.dwords[1] >> 20u) & 0x1ffu); if (image.atomic && format != Prospero::BufferFormat::k32UInt) { - if (error != nullptr) { - *error = fmt::format("atomic image descriptor {} uses unsupported format {}", i, - static_cast(format)); - } - return false; + return SpecializationFail( + fmt::format("atomic image descriptor {} uses unsupported format {}", i, + static_cast(format))); } - const bool storage = image.kind == ResourceKind::StorageImage || - image.kind == ResourceKind::StorageImageUint; + const bool storage = image.kind == ResourceKind::StorageImage || + image.kind == ResourceKind::StorageImageUint; image.conversion_format = ImageConversionFormat(format); if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) { image.shader_swizzle = DescriptorImageSwizzle(descriptor); } const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt && image.written && !image.read && !image.atomic; - const bool uint_image = Prospero::IsUintTextureFormat(format) || raw_sint_storage; + const bool uint_image = Prospero::IsUintTextureFormat(format) || raw_sint_storage; if (uint_image) { switch (image.kind) { case ResourceKind::Image: image.kind = ResourceKind::ImageUint; break; @@ -803,19 +666,13 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri static_cast(root.indirect_mapping_offset) + 1u + static_cast(root.indirect_mapping_capacity) * 2u > next_snapshot.flattened_srt.size()) { - if (error != nullptr) { - *error = "indirect image specialization has an invalid key mapping"; - } - return false; + return SpecializationFail("indirect image specialization has an invalid key mapping"); } uint32_t exemplar = ImageResource::NoIndirectImage; for (const auto resource: root.indirect_resources) { if (resource >= next.images.size() || next.images[resource].indirect_root != root_index) { - if (error != nullptr) { - *error = "indirect image specialization has an invalid candidate"; - } - return false; + return SpecializationFail("indirect image specialization has an invalid candidate"); } if (!NullImageDescriptor(next_snapshot.images[resource])) { exemplar = resource; @@ -823,10 +680,7 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri } } if (exemplar == ImageResource::NoIndirectImage) { - if (error != nullptr) { - *error = "indirect image specialization has no typed candidate"; - } - return false; + return SpecializationFail("indirect image specialization has no typed candidate"); } const auto& image_class = next.images[exemplar]; for (uint32_t candidate = 0; candidate < next.images.size(); candidate++) { @@ -849,12 +703,9 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri image.shader_swizzle != image_class.shader_swizzle || image.depth_compare != image_class.depth_compare || image.cube != image_class.cube) { - if (error != nullptr) { - *error = fmt::format( - "indirect image table at pc 0x{:08x} has incompatible candidates", - root.first_use_pc); - } - return false; + return SpecializationFail( + fmt::format("indirect image table at pc 0x{:08x} has incompatible candidates", + root.first_use_pc)); } } } @@ -867,11 +718,8 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri std::vector sampler_usage(next.samplers.size()); for (const auto& pair: next.sampled_pairs) { if (pair.image >= next.images.size() || pair.sampler >= next.samplers.size()) { - if (error != nullptr) { - *error = fmt::format("sampled resource pair at pc 0x{:08x} is invalid", - pair.first_use_pc); - } - return false; + return SpecializationFail( + fmt::format("sampled resource pair at pc 0x{:08x} is invalid", pair.first_use_pc)); } auto& usage = sampler_usage[pair.sampler]; if (next.images[pair.image].conversion_format != Prospero::BufferFormat::kInvalid) { @@ -891,10 +739,8 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri continue; } if (next.samplers.size() >= ShaderInfo::MaxSamplers) { - if (error != nullptr) { - *error = "point-filter sampler variants exceed the dense sampler resource limit"; - } - return false; + return SpecializationFail( + "point-filter sampler variants exceed the dense sampler resource limit"); } point_sampler[i] = static_cast(next.samplers.size()); auto sampler = next.samplers[i]; @@ -915,19 +761,13 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri } const auto index = inst.Flags().index; if (index >= memory_info.size()) { - if (error != nullptr) { - *error = fmt::format("typed image instruction has invalid memory metadata {}", - index); - } - return false; + return SpecializationFail( + fmt::format("typed image instruction has invalid memory metadata {}", index)); } auto& memory = memory_info[index]; if (memory.resource >= next.images.size()) { - if (error != nullptr) { - *error = fmt::format("typed image instruction has invalid resource {}", - memory.resource); - } - return false; + return SpecializationFail(fmt::format( + "typed image instruction has invalid resource {}", memory.resource)); } const auto& image = next.images[memory.resource]; if (image_opcode.needs_sampler && @@ -937,22 +777,18 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri } if (image.indirect_root == memory.resource && inst.GetOpcode() != ValueOpcode::ImageSampleRaw) { - if (error != nullptr) { - *error = fmt::format( - "indirect image table at pc 0x{:08x} is used by unsupported {}", - inst.Flags().pc, ValueOpcodeName(inst.GetOpcode())); - } - return false; + return SpecializationFail( + fmt::format("indirect image table at pc 0x{:08x} is used by unsupported {}", + inst.Flags().pc, ValueOpcodeName(inst.GetOpcode()))); } memory.kind = image.kind; memory.image_dimension = image.dimension; memory.image_cube = image.cube; } } - program.info = std::move(next); + program.info = std::move(next); program.memory_info = std::move(memory_info); - snapshot = std::move(next_snapshot); - return true; + snapshot = std::move(next_snapshot); } } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.h b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.h index a6772ab1a..9f61cf4d8 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.h +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.h @@ -22,19 +22,17 @@ struct ResourceSnapshot { std::vector indirect_images; }; -bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot, - std::string* error); -bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot, - std::string* error); +bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot); +bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot); // Resolves the immutable dense resource topology against one runtime user-data/SRT snapshot. // On failure the destination is unchanged. bool MaterializeResources(const Program& program, const SrtRuntime& runtime, - ResourceSnapshot& snapshot, std::string* error); + ResourceSnapshot& snapshot); // Applies runtime descriptor shape/format facts to a copied dense topology before layout and -// emission. On failure the program is unchanged. -bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::string* error); +// emission. Invalid specializations are fatal. +void SpecializeResources(Program& program, ResourceSnapshot& snapshot); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp index d58c9f6e4..302126569 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp @@ -1,8 +1,8 @@ #include "graphics/shader/recompiler/ir/passes/ResourceTracking.h" +#include "common/assert.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" -#include "graphics/shader/shader.h" #include #include @@ -12,7 +12,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR { namespace { -constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u); +constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u); constexpr uint32_t SamplerDword3ReservedMask = 0x3ffff000u; uint32_t PossibleU32Bits(Value value) { @@ -101,19 +101,17 @@ public: m_info.uses_dma = false; } - bool Run(std::string* error) { + void Run() { if (m_program.resource_tracking_complete) { - return Fail(0, error, "resources already tracked"); + Fail(0, "resources already tracked"); } if (!m_program.srt_plan_complete) { - return Fail(0, error, "SRT plan is not ready"); + Fail(0, "SRT plan is not ready"); } PlanIndirectImages(); for (auto* block: m_program.blocks) { for (auto& inst: *block) { - if (!Collect(inst, error)) { - return false; - } + Collect(inst); } } LinkImageAliases(); @@ -145,10 +143,9 @@ public: return std::ranges::find(plan.reads, inst) != plan.reads.end(); }); }); - m_program.descriptor_sources = std::move(m_sources); + m_program.descriptor_sources = std::move(m_sources); m_program.info = std::move(m_info); m_program.resource_tracking_complete = true; - return true; } private: @@ -173,18 +170,19 @@ private: std::array reads {}; }; - bool Fail(uint32_t pc, std::string* error, const std::string& reason) const { - return ShaderError::Fail( - error, fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}", - m_program.shader_hash, StageName(m_program.stage), pc, reason)); + [[noreturn]] void Fail(uint32_t pc, const std::string& reason) const { + const auto message = + fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}", + m_program.shader_hash, StageName(m_program.stage), pc, reason); + EXIT("%s", message.c_str()); + std::abort(); } - bool MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust, - DescriptorSource& descriptor, uint32_t pc, std::string* error) const { + void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust, + DescriptorSource& descriptor, uint32_t pc) const { if (handle.NumArgs() != width) { - return Fail(pc, error, - fmt::format("{} has {} descriptor dwords, expected {}", - ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width)); + Fail(pc, fmt::format("{} has {} descriptor dwords, expected {}", + ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width)); } descriptor.dword_count = width; for (uint32_t i = 0; i < width; i++) { @@ -199,18 +197,15 @@ private: // Border color and its table index are unused unless a clamp axis selects border mode. descriptor.dwords[3] = Value(0u); } - return true; } - bool ValidateSource(const DescriptorSource& descriptor, std::string& reason, - uint32_t& bad_dword) const { + bool ValidateSource(const DescriptorSource& descriptor, uint32_t& bad_dword) const { for (uint32_t i = 0; i < descriptor.dword_count; i++) { bad_dword = i; if (descriptor.dwords[i].Resolve().GetType() != Type::U32) { - reason = "is not U32"; return false; } - if (!ValidateRuntimeValue(m_program, descriptor.dwords[i], reason)) { + if (!ValidateRuntimeValue(m_program, descriptor.dwords[i])) { return false; } } @@ -286,13 +281,12 @@ private: bool MakeRuntimeBufferSource(const Inst& handle, uint32_t pc, uint32_t& source, DescriptorSource& descriptor) { - if (handle.GetOpcode() != ValueOpcode::GetBufferResource || - !MakeSource(handle, 4u, false, false, descriptor, pc, nullptr)) { + if (handle.GetOpcode() != ValueOpcode::GetBufferResource) { return false; } - std::string reason; - uint32_t bad_dword = 0; - if (!ValidateSource(descriptor, reason, bad_dword)) { + MakeSource(handle, 4u, false, false, descriptor, pc); + uint32_t bad_dword = 0; + if (!ValidateSource(descriptor, bad_dword)) { return false; } source = InternSource(descriptor); @@ -469,38 +463,30 @@ private: } } - bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle, - uint32_t& source, std::string* error, bool sampler = false, - bool sample_adjust = false) { + void GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle, + uint32_t& source, bool sampler = false, bool sample_adjust = false) { handle = value.Resolve().TryInstruction(); if (handle == nullptr || handle->GetOpcode() != expected) { - return Fail(pc, error, - fmt::format("memory operation requires {}", ValueOpcodeName(expected))); + Fail(pc, fmt::format("memory operation requires {}", ValueOpcodeName(expected))); } DescriptorSource descriptor; - if (!MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc, error)) { - return false; - } - std::string reason; - uint32_t bad_dword = 0; - if (!ValidateSource(descriptor, reason, bad_dword)) { - return Fail( - pc, error, - fmt::format("{} dword {} {}", ValueOpcodeName(expected), bad_dword, reason)); + MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc); + uint32_t bad_dword = 0; + if (!ValidateSource(descriptor, bad_dword)) { + Fail(pc, fmt::format("{} dword {} is not a valid runtime value", + ValueOpcodeName(expected), bad_dword)); } source = InternSource(descriptor); - return true; } - bool ValidateAddressHandle(Value value, uint32_t pc, std::string* error) const { + void ValidateAddressHandle(Value value, uint32_t pc) const { const auto* handle = value.Resolve().TryInstruction(); if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource) { - return Fail(pc, error, "address operation requires GetAddressResource"); + Fail(pc, "address operation requires GetAddressResource"); } if (handle->NumArgs() != 2) { - return Fail(pc, error, "GetAddressResource must have two address dwords"); + Fail(pc, "GetAddressResource must have two address dwords"); } - return true; } uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) { @@ -540,7 +526,7 @@ private: const auto mip = MipMode(memory); const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0; for (uint32_t i = 0; i < m_info.images.size(); i++) { - auto& image = m_info.images[i]; + auto& image = m_info.images[i]; const bool compatible_kind = image.kind == memory.kind || (IsStorageImage(image.kind) && IsStorageImage(memory.kind)); if (image.source == source && compatible_kind && @@ -593,155 +579,139 @@ private: return static_cast(m_info.samplers.size() - 1); } - bool AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc, std::string* error) { + void AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc) { for (auto& pair: m_info.sampled_pairs) { if (pair.image == image && pair.sampler == sampler) { pair.first_use_pc = std::min(pair.first_use_pc, pc); - return true; + return; } } if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) { - return Fail(pc, error, "sampled image/sampler pair limit exceeded"); + Fail(pc, "sampled image/sampler pair limit exceeded"); } m_info.sampled_pairs.push_back({image, sampler, pc}); - return true; } - bool AddHandlePatch(Inst* handle, uint32_t resource, uint32_t pc, std::string* error) { + void AddHandlePatch(Inst* handle, uint32_t resource, uint32_t pc) { for (const auto& patch: m_handle_patches) { if (patch.handle == handle) { - return patch.resource == resource || - Fail(pc, error, - fmt::format("{} is reused with incompatible resource classes", - ValueOpcodeName(handle->GetOpcode()))); + if (patch.resource != resource) { + Fail(pc, fmt::format("{} is reused with incompatible resource classes", + ValueOpcodeName(handle->GetOpcode()))); + } + return; } } m_handle_patches.push_back({handle, resource}); - return true; } - bool AddMemoryPatch(uint32_t index, uint32_t resource, uint32_t sampler, bool has_sampler, - uint32_t pc, std::string* error) { + void AddMemoryPatch(uint32_t index, uint32_t resource, uint32_t sampler, bool has_sampler, + uint32_t pc) { for (auto& patch: m_memory_patches) { if (patch.index != index) { continue; } if (patch.resource != resource || (has_sampler && patch.has_sampler && patch.sampler != sampler)) { - return Fail(pc, error, "memory metadata is reused with incompatible resources"); + Fail(pc, "memory metadata is reused with incompatible resources"); } if (has_sampler) { patch.sampler = sampler; patch.has_sampler = true; } - return true; + return; } m_memory_patches.push_back({index, resource, sampler, has_sampler}); - return true; } - bool Collect(Inst& inst, std::string* error) { + void Collect(Inst& inst) { const auto op = inst.GetOpcode(); const auto buffer = BufferAccessOf(op); const auto address_info = AddressOpcodeInfoOf(op); const auto image_info = ImageOpcodeInfoOf(op); if (buffer == BufferAccess::None && address_info.access == AddressAccess::None && image_info.access == ImageAccess::None) { - return true; + return; } const auto flags = inst.Flags(); if (flags.index >= m_program.memory_info.size()) { - return Fail(flags.pc, error, - fmt::format("memory metadata index {} is out of range", flags.index)); + Fail(flags.pc, fmt::format("memory metadata index {} is out of range", flags.index)); } if (inst.NumArgs() == 0) { - return Fail(flags.pc, error, "memory operation has no resource handle"); + Fail(flags.pc, "memory operation has no resource handle"); } const auto& memory = m_program.memory_info[flags.index]; if (memory.planning_only || IsIndirectPlanningMemory(flags.index)) { - return true; + return; } Inst* handle = nullptr; uint32_t source = 0; uint32_t resource = 0; if (buffer != BufferAccess::None) { - if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source, - error)) { - return false; - } + GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source); resource = AddBuffer(source, memory, op, flags.pc); if (resource == UINT32_MAX) { - return Fail(flags.pc, error, "buffer resource limit exceeded"); + Fail(flags.pc, "buffer resource limit exceeded"); } - return AddHandlePatch(handle, resource, flags.pc, error) && - AddMemoryPatch(flags.index, resource, 0, false, flags.pc, error); + AddHandlePatch(handle, resource, flags.pc); + AddMemoryPatch(flags.index, resource, 0, false, flags.pc); + return; } if (address_info.access != AddressAccess::None) { if (!IsAddressResourceKind(memory.kind)) { - return Fail(flags.pc, error, "address operation has invalid resource kind"); + Fail(flags.pc, "address operation has invalid resource kind"); } if (memory.kind == ResourceKind::Scratch) { handle = inst.Arg(0).Resolve().TryInstruction(); if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetScratchResource || handle->NumArgs() != 0) { - return Fail(flags.pc, error, "scratch operation requires GetScratchResource"); + Fail(flags.pc, "scratch operation requires GetScratchResource"); } if (m_program.scratch_dwords == 0) { - return Fail(flags.pc, error, - "scratch operation requires a nonzero AGC per-thread size"); + Fail(flags.pc, "scratch operation requires a nonzero AGC per-thread size"); } - return true; - } - if (!ValidateAddressHandle(inst.Arg(0), flags.pc, error)) { - return false; + return; } + ValidateAddressHandle(inst.Arg(0), flags.pc); m_info.uses_dma = true; - return true; + return; } if (!ImageResourceKindMatches(memory.kind, image_info.resource_class)) { - return Fail(flags.pc, error, "image operation has invalid resource kind"); + Fail(flags.pc, "image operation has invalid resource kind"); } handle = inst.Arg(0).Resolve().TryInstruction(); const auto* indirect = handle != nullptr ? FindIndirectImage(*handle) : nullptr; if (indirect != nullptr) { source = indirect->source; - } else if (!GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle, - source, error)) { - return false; + } else { + GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle, source); } resource = AddImage(source, memory, op, flags.pc); if (resource == UINT32_MAX) { - return Fail(flags.pc, error, "image resource limit exceeded"); - } - if (!AddHandlePatch(handle, resource, flags.pc, error)) { - return false; + Fail(flags.pc, "image resource limit exceeded"); } + AddHandlePatch(handle, resource, flags.pc); uint32_t sampler = 0; if (image_info.needs_sampler) { if (inst.NumArgs() < 2) { - return Fail(flags.pc, error, "sampled image operation has no sampler handle"); + Fail(flags.pc, "sampled image operation has no sampler handle"); } - Inst* sampler_handle = nullptr; - uint32_t sampler_source = 0; + Inst* sampler_handle = nullptr; + uint32_t sampler_source = 0; const bool sample_adjust = (memory.image_sample_flags & Decoder::ImageSampleFlagAdjust) != 0; - if (!GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc, - sampler_handle, sampler_source, error, true, sample_adjust)) { - return false; - } + GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc, sampler_handle, + sampler_source, true, sample_adjust); sampler = AddSampler(sampler_source, flags.pc); if (sampler == UINT32_MAX) { - return Fail(flags.pc, error, "sampler resource limit exceeded"); - } - if (!AddHandlePatch(sampler_handle, sampler, flags.pc, error) || - !AddSampledPair(resource, sampler, flags.pc, error)) { - return false; + Fail(flags.pc, "sampler resource limit exceeded"); } + AddHandlePatch(sampler_handle, sampler, flags.pc); + AddSampledPair(resource, sampler, flags.pc); } - return AddMemoryPatch(flags.index, resource, sampler, image_info.needs_sampler, flags.pc, - error); + AddMemoryPatch(flags.index, resource, sampler, image_info.needs_sampler, flags.pc); } const DescriptorSource* Source(uint32_t source) const { @@ -783,8 +753,8 @@ private: } // namespace -bool TrackResources(Program& program, std::string* error) { - return Tracker(program).Run(error); +void TrackResources(Program& program) { + Tracker(program).Run(); } } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h index 791424879..78b00c3c7 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.h @@ -7,7 +7,7 @@ namespace Libs::Graphics::ShaderRecompiler::IR { // Collects immutable resource topology from typed SSA handles, interns their resolved dwords in // descriptor_sources, then writes dense indices to handle flags and MemoryInfo. -bool TrackResources(Program& program, std::string* error); +void TrackResources(Program& program); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index 346cca4db..f26f309c8 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -1,5 +1,6 @@ #include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h" +#include "common/assert.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" #include @@ -8,6 +9,11 @@ namespace Libs::Graphics::ShaderRecompiler::IR { namespace { +[[noreturn]] void Fail(std::string_view message) { + EXIT("shader info collection failed: %s", std::string(message).c_str()); + std::abort(); +} + void AddInput(ShaderInfo& info, StageInputKind kind, uint32_t location, uint32_t components, std::string name, bool per_vertex = false) { const auto input = std::find_if(info.inputs.begin(), info.inputs.end(), [=](const auto& value) { @@ -34,38 +40,38 @@ void AddOutput(ShaderInfo& info, StageOutputKind kind, uint32_t index, uint32_t } } -bool ValidateOptions(const Program& program, const ShaderInfoOptions& options, std::string* error) { - auto Fail = [&](const char* message) { - if (error != nullptr) { - *error = message; - } - return false; - }; +void ValidateOptions(const Program& program, const ShaderInfoOptions& options) { switch (program.stage) { case ShaderType::Vertex: + if (options.vertex == nullptr) { + return Fail("vertex shader has no input metadata"); + } if (options.vertex->resources_num < 0 || options.vertex->resources_num > ShaderVertexInputInfo::RES_MAX) { return Fail("vertex resource count is out of range"); } - return true; + return; case ShaderType::Pixel: - return options.pixel->input_num <= std::size(options.pixel->interpolator_settings) || - Fail("pixel input count is out of range"); + if (options.pixel == nullptr) { + return Fail("pixel shader has no input metadata"); + } + if (options.pixel->input_num > std::size(options.pixel->interpolator_settings)) { + return Fail("pixel input count is out of range"); + } + return; case ShaderType::Compute: - return (options.compute->thread_ids_num >= 0 && options.compute->thread_ids_num <= 3) || - Fail("compute thread ID count is out of range"); + if (options.compute == nullptr) { + return Fail("compute shader has no input metadata"); + } + if (options.compute->thread_ids_num < 0 || options.compute->thread_ids_num > 3) { + return Fail("compute thread ID count is out of range"); + } + return; default: return Fail("unsupported shader stage for info collection"); } } -bool ValidateValueReferences(const Program& program, const ShaderInfoOptions& options, - std::string* error) { - const auto Fail = [&](const char* message) { - if (error != nullptr) { - *error = message; - } - return false; - }; +void ValidateValueReferences(const Program& program, const ShaderInfoOptions& options) { for (const auto* block: program.blocks) { for (const auto& inst: *block) { switch (inst.GetOpcode()) { @@ -148,7 +154,6 @@ bool ValidateValueReferences(const Program& program, const ShaderInfoOptions& op } } } - return true; } void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* vertex, @@ -269,14 +274,8 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { } } -bool CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex, - const ShaderPixelInputInfo* pixel, ShaderInfo& info, std::string* error) { - auto Fail = [&](std::string message) { - if (error != nullptr) { - *error = std::move(message); - } - return false; - }; +void CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex, + const ShaderPixelInputInfo* pixel, ShaderInfo& info) { for (const auto* block: program.blocks) { for (const auto& inst: *block) { if (inst.GetOpcode() != ValueOpcode::SetAttribute) { @@ -347,31 +346,23 @@ bool CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex, } } } - return true; } } // namespace -bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::string* error) { +void CollectShaderInfo(Program& program, const ShaderInfoOptions& options) { if (!program.resource_tracking_complete || program.shader_info_complete) { - if (error != nullptr) { - *error = !program.resource_tracking_complete ? "shader resources were not tracked" - : "shader info already collected"; - } - return false; - } - if (!ValidateOptions(program, options, error)) { - return false; - } - if (!ValidateValueReferences(program, options, error)) { - return false; + return Fail(!program.resource_tracking_complete ? "shader resources were not tracked" + : "shader info already collected"); } + ValidateOptions(program, options); + ValidateValueReferences(program, options); auto next = program.info; next.inputs.clear(); next.outputs.clear(); - next.has_bitwise_xor = std::any_of( - program.blocks.begin(), program.blocks.end(), [](const auto* block) { + next.has_bitwise_xor = + std::any_of(program.blocks.begin(), program.blocks.end(), [](const auto* block) { return std::any_of(block->begin(), block->end(), [](const auto& inst) { return inst.GetOpcode() == ValueOpcode::BitwiseXor32; }); @@ -380,15 +371,12 @@ bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std:: case ShaderType::Vertex: CollectVertexInputs(program, options.vertex, next); break; case ShaderType::Pixel: CollectPixelInputs(program, options.pixel, next); break; case ShaderType::Compute: CollectComputeInputs(options.compute, next); break; - default: return false; + default: return Fail("unsupported shader stage for info collection"); } CollectBuiltinInputs(program, next); - if (!CollectOutputs(program, options.vertex, options.pixel, next, error)) { - return false; - } + CollectOutputs(program, options.vertex, options.pixel, next); program.info = std::move(next); program.shader_info_complete = true; - return true; } } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h index 7e406db58..904e92f01 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h @@ -13,7 +13,7 @@ struct ShaderInfoOptions { // Completes the immutable shader interface after resource tracking. On failure Program::info and // all completion state remain unchanged. -bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::string* error); +void CollectShaderInfo(Program& program, const ShaderInfoOptions& options); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp index bda39f1ff..67c460718 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp @@ -1,7 +1,7 @@ #include "graphics/shader/recompiler/ir/passes/SrtWalker.h" +#include "common/assert.h" #include "graphics/shader/recompiler/ir/ShaderIR.h" -#include "graphics/shader/shader.h" #include #include @@ -120,10 +120,10 @@ class RuntimeValidator { public: explicit RuntimeValidator(const Program& program): m_program(program) {} - bool Run(Value value, std::string& reason) { return Validate(value, reason); } + bool Run(Value value) { return Validate(value); } private: - bool Validate(Value value, std::string& reason) { + bool Validate(Value value) { value = value.Resolve(); const auto* inst = value.TryInstruction(); if (inst == nullptr) { @@ -133,14 +133,10 @@ private: case Type::U16: case Type::U32: case Type::U64: return true; - default: - reason = fmt::format("contains a non-integer immediate of type {}", - TypeName(value.GetType())); - return false; + default: return false; } } if (!m_visiting.insert(inst).second) { - reason = fmt::format("contains a cyclic {} value", ValueOpcodeName(inst->GetOpcode())); return false; } const auto finish = [&](bool valid) { @@ -151,25 +147,21 @@ private: if (op == ValueOpcode::UndefU1 || op == ValueOpcode::UndefU8 || op == ValueOpcode::UndefU16 || op == ValueOpcode::UndefU32 || op == ValueOpcode::UndefU64 || op == ValueOpcode::Void) { - reason = fmt::format("contains {}", ValueOpcodeName(op)); return finish(false); } if (op == ValueOpcode::GetUserData) { if (inst->NumArgs() != 1 || inst->Arg(0).GetType() != Type::ScalarReg) { - reason = "contains a malformed GetUserData"; return finish(false); } const auto reg = RegIndex(inst->Arg(0).ScalarRegister()); if (reg < m_program.user_data_base || reg - m_program.user_data_base >= m_program.user_data_count) { - reason = fmt::format("references unavailable user SGPR {}", reg); return finish(false); } return finish(true); } if (op == ValueOpcode::GetShaderBase) { if (inst->NumArgs() != 0) { - reason = "contains a malformed GetShaderBase"; return finish(false); } return finish(true); @@ -177,14 +169,12 @@ private: if (op == ValueOpcode::Phi) { const auto invariant = ResolveInvariantPhi(m_program, value); if (invariant.IsEmpty()) { - reason = "contains a control-dependent phi"; return finish(false); } - return finish(Validate(invariant, reason)); + return finish(Validate(invariant)); } if (op == ValueOpcode::GetSrtResource) { if (inst->NumArgs() != 0) { - reason = "contains a malformed GetSrtResource"; return finish(false); } return finish(true); @@ -196,7 +186,6 @@ private: ValueOpcode::GetSrtResource || !slot.IsImmediate() || slot.GetType() != Type::U32 || slot.U32() >= m_program.srt_reads.size()) { - reason = "contains a malformed flattened SRT read"; return finish(false); } } else if (op == ValueOpcode::LoadAddressU32 || op == ValueOpcode::ReadConstBuffer) { @@ -206,13 +195,11 @@ private: const auto* handle = inst->NumArgs() != 0 ? inst->Arg(0).ResolveInstruction() : nullptr; if (!IsRawRead(m_program, *inst) || handle == nullptr || handle->GetOpcode() != expected) { - reason = fmt::format("contains a non-scalar {}", ValueOpcodeName(op)); return finish(false); } } else if (op == ValueOpcode::CompositeExtractU64) { const auto index = inst->NumArgs() == 2 ? inst->Arg(1).Resolve() : Value {}; if (!index.IsImmediate() || index.GetType() != Type::U32 || index.U32() >= 2u) { - reason = "contains an invalid U64 component index"; return finish(false); } } else if (op == ValueOpcode::CompositeExtractU32x2) { @@ -222,7 +209,6 @@ private: index.U32() >= 2u || (source->GetOpcode() != ValueOpcode::CompositeConstructU32x2 && source->GetOpcode() != ValueOpcode::IAddCarry32)) { - reason = "contains an unsupported composite runtime source"; return finish(false); } } @@ -233,17 +219,14 @@ private: : op == ValueOpcode::GetSamplerResource ? 4u : 2u; if (inst->NumArgs() != expected) { - reason = fmt::format("contains a malformed {}", ValueOpcodeName(op)); return finish(false); } } else if (op != ValueOpcode::ReadConst && op != ValueOpcode::ReadConstBuffer && op != ValueOpcode::LoadAddressU32 && !IsRuntimeIntegerOp(op)) { - reason = - fmt::format("contains unsupported or control-dependent {}", ValueOpcodeName(op)); return finish(false); } for (size_t index = 0; index < inst->NumArgs(); index++) { - if (!Validate(inst->Arg(index), reason)) { + if (!Validate(inst->Arg(index))) { return finish(false); } } @@ -258,7 +241,7 @@ class PlanBuilder { public: explicit PlanBuilder(Program& program): m_program(program) {} - bool Run(std::string* error) { + void Run() { m_program.srt_reads.clear(); m_program.dynamic_reads.clear(); for (auto* block: m_program.blocks) { @@ -273,34 +256,29 @@ public: (op == ValueOpcode::ReadConstBuffer && kind == ResourceKind::ScalarAddress); if (crosswired) { - return Fail(flags.pc, error, - fmt::format("{} has incompatible scalar memory metadata", - ValueOpcodeName(op))); + Fail(flags.pc, + fmt::format("{} has incompatible scalar memory metadata", + ValueOpcodeName(op))); } } } if (IsDescriptorHandle(inst.GetOpcode())) { for (size_t index = 0; index < inst.NumArgs(); index++) { - if (!Collect(inst.Arg(index), 0, error)) { - return false; - } + Collect(inst.Arg(index), 0); } } } } for (auto* block: m_program.blocks) { for (auto& inst: *block) { - std::string reason; if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 && IsRawRead(m_program, inst) && inst.Arg(1).Resolve().IsImmediate() && - ValidateRuntimeValue(m_program, Value(&inst), reason) && - !Collect(Value(&inst), inst.Flags().pc, error)) { - return false; + ValidateRuntimeValue(m_program, Value(&inst))) { + Collect(Value(&inst), inst.Flags().pc); } } } PatchReads(); - return true; } private: @@ -310,18 +288,20 @@ private: bool keep = false; }; - bool Fail(uint32_t pc, std::string* error, const std::string& message) const { - return ShaderError::Fail(error, Diagnostic(m_program, pc, message)); + [[noreturn]] void Fail(uint32_t pc, const std::string& message) const { + const auto diagnostic = Diagnostic(m_program, pc, message); + EXIT("shader SRT planning failed: %s", diagnostic.c_str()); + std::abort(); } - bool Collect(Value value, uint32_t use_pc, std::string* error) { + void Collect(Value value, uint32_t use_pc) { value = value.Resolve(); if (value.IsImmediate()) { - return true; + return; } auto* inst = value.TryInstruction(); if (inst == nullptr) { - return Fail(use_pc, error, "invalid typed planning value"); + Fail(use_pc, "invalid typed planning value"); } const auto cycle = std::ranges::find(m_visiting, inst); if (cycle != m_visiting.end()) { @@ -329,44 +309,40 @@ private: return value->GetOpcode() == ValueOpcode::Phi; }); if (contains_phi) { - return true; + return; } - return Fail(use_pc, error, - fmt::format("cyclic typed planning value {} without a phi", - ValueOpcodeName(inst->GetOpcode()))); + Fail(use_pc, fmt::format("cyclic typed planning value {} without a phi", + ValueOpcodeName(inst->GetOpcode()))); } if (std::ranges::find(m_visited, inst) != m_visited.end()) { - return true; + return; } m_visiting.push_back(inst); for (size_t index = 0; index < inst->NumArgs(); index++) { - if (!Collect(inst->Arg(index), use_pc, error)) { - return false; - } + Collect(inst->Arg(index), use_pc); } m_visiting.pop_back(); m_visited.push_back(inst); if (!IsRawRead(m_program, *inst)) { - return true; + return; } const auto offset = inst->Arg(1).Resolve(); if (!offset.IsImmediate() || offset.GetType() != Type::U32) { - if (std::ranges::find(m_program.dynamic_reads, value) == m_program.dynamic_reads.end()) { + if (std::ranges::find(m_program.dynamic_reads, value) == + m_program.dynamic_reads.end()) { m_program.dynamic_reads.push_back(value); } - return true; + return; } - use_pc = inst->Flags().pc; for (uint32_t slot = 0; slot < m_program.srt_reads.size(); slot++) { if (EquivalentValue(m_program, value, m_program.srt_reads[slot].value)) { m_patches.push_back({inst, slot, false}); - return true; + return; } } const auto slot = static_cast(m_program.srt_reads.size()); - m_program.srt_reads.push_back({value, slot, use_pc}); + m_program.srt_reads.push_back({value, slot}); m_patches.push_back({inst, slot, true}); - return true; } void PatchReads() { @@ -411,14 +387,12 @@ class Evaluator { public: Evaluator(const Program& program, const SrtRuntime& runtime, std::span clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr) - : m_program(program), m_runtime(runtime), - m_clean_flat_slots(clean_flat_slots), m_clean_evaluator(clean_evaluator) {} + : m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots), + m_clean_evaluator(clean_evaluator) {} - void SetUsePc(uint32_t pc) { m_use_pc = pc; } - - bool Evaluate(Value value, uint32_t& result, std::string* error) { + bool Evaluate(Value value, uint32_t& result) { uint64_t wide = 0; - if (!EvaluateWide(value, wide, error)) { + if (!EvaluateWide(value, wide)) { return false; } result = static_cast(wide); @@ -426,11 +400,7 @@ public: } private: - bool Fail(std::string* error, const std::string& message) const { - return ShaderError::Fail(error, Diagnostic(m_program, m_use_pc, message)); - } - - bool EvaluateWide(Value value, uint64_t& result, std::string* error) { + bool EvaluateWide(Value value, uint64_t& result) { value = value.Resolve(); if (value.IsImmediate()) { switch (value.GetType()) { @@ -439,23 +409,23 @@ private: case Type::U16: result = value.U16(); return true; case Type::U32: result = value.U32(); return true; case Type::U64: result = value.U64(); return true; - default: return Fail(error, "non-integer immediate in runtime expression"); + default: return false; } } auto* inst = value.TryInstruction(); if (inst == nullptr) { - return Fail(error, "invalid typed runtime value"); + return false; } if (const auto found = m_cache.find(inst); found != m_cache.end()) { result = found->second; return true; } if (std::ranges::find(m_visiting, inst) != m_visiting.end()) { - return Fail(error, "cyclic typed runtime value"); + return false; } m_visiting.push_back(inst); uint64_t out = 0; - if (!EvaluateInst(*inst, out, error)) { + if (!EvaluateInst(*inst, out)) { return false; } m_visiting.pop_back(); @@ -464,28 +434,27 @@ private: return true; } - bool Arg(const Inst& inst, size_t index, uint64_t& result, std::string* error) { - return EvaluateWide(inst.Arg(index), result, error); + bool Arg(const Inst& inst, size_t index, uint64_t& result) { + return EvaluateWide(inst.Arg(index), result); } - bool EvaluatePhi(const Inst& inst, uint64_t& result, std::string* error) { + bool EvaluatePhi(const Inst& inst, uint64_t& result) { const auto value = ResolveInvariantPhi(m_program, Value(const_cast(&inst))); - return !value.IsEmpty() ? EvaluateWide(value, result, error) - : Fail(error, "typed phi has runtime-dependent values"); + return !value.IsEmpty() && EvaluateWide(value, result); } - bool EvaluateExtract(const Inst& inst, uint64_t& result, std::string* error) { + bool EvaluateExtract(const Inst& inst, uint64_t& result) { const auto index = inst.Arg(1).Resolve(); if (!index.IsImmediate() || index.GetType() != Type::U32) { - return Fail(error, "dynamic composite extract in runtime expression"); + return false; } const auto component = index.U32(); if (component >= 2u) { - return Fail(error, "unsupported composite runtime source"); + return false; } if (inst.GetOpcode() == ValueOpcode::CompositeExtractU64) { uint64_t packed = 0; - if (!Arg(inst, 0, packed, error)) { + if (!Arg(inst, 0, packed)) { return false; } result = static_cast(packed >> (component * 32u)); @@ -493,15 +462,15 @@ private: } const auto* source = inst.Arg(0).ResolveInstruction(); if (source == nullptr) { - return Fail(error, "unsupported composite runtime source"); + return false; } if (source->GetOpcode() == ValueOpcode::CompositeConstructU32x2) { - return EvaluateWide(source->Arg(component), result, error); + return EvaluateWide(source->Arg(component), result); } if (source->GetOpcode() == ValueOpcode::IAddCarry32) { uint64_t lhs = 0; uint64_t rhs = 0; - if (!Arg(*source, 0, lhs, error) || !Arg(*source, 1, rhs, error)) { + if (!Arg(*source, 0, lhs) || !Arg(*source, 1, rhs)) { return false; } const auto sum = @@ -510,24 +479,23 @@ private: component == 0u ? static_cast(sum) : static_cast(sum >> 32u); return true; } - return Fail(error, "unsupported composite runtime source"); + return false; } - bool EvaluateRawRead(const Inst& inst, uint64_t& result, std::string* error) { + bool EvaluateRawRead(const Inst& inst, uint64_t& result) { const auto flags = inst.Flags(); if (flags.index >= m_program.memory_info.size()) { - return Fail(error, "raw scalar read has invalid metadata"); + return false; } const auto& mem = m_program.memory_info[flags.index]; const auto* handle = inst.Arg(0).ResolveInstruction(); if (handle == nullptr) { - return Fail(error, "raw scalar read has no descriptor handle"); + return false; } uint64_t low = 0; uint64_t high = 0; uint64_t offset = 0; - if (!Arg(*handle, 0, low, error) || !Arg(*handle, 1, high, error) || - !Arg(inst, 1, offset, error)) { + if (!Arg(*handle, 0, low) || !Arg(*handle, 1, high) || !Arg(inst, 1, offset)) { return false; } const auto base = ((high << 32u) | static_cast(low)) & AddressMask; @@ -536,12 +504,11 @@ private: if (inst.GetOpcode() == ValueOpcode::ReadConstBuffer) { uint64_t records = 0; uint64_t word3 = 0; - if (handle->NumArgs() != 4u || !Arg(*handle, 2, records, error) || - !Arg(*handle, 3, word3, error)) { - return Fail(error, "constant-buffer descriptor has invalid width"); + if (handle->NumArgs() != 4u || !Arg(*handle, 2, records) || !Arg(*handle, 3, word3)) { + return false; } if (immediate < 0) { - return Fail(error, "constant-buffer read has a negative immediate offset"); + return false; } const auto byte_offset = static_cast(immediate) + static_cast(offset); @@ -551,21 +518,20 @@ private: ? static_cast(static_cast(records)) : static_cast(stride) * static_cast(records); if (aligned > size || size - aligned < sizeof(uint32_t)) { - return Fail( - error, fmt::format("constant-buffer offset {} exceeds size {}", aligned, size)); + return false; } address = ((base & ~uint64_t {3}) + byte_offset) & ~uint64_t {3}; } else { const auto relative = (immediate & ~int64_t {3}) + static_cast(static_cast(offset) & ~3u); if (!AddSignedAddress(base & ~uint64_t {3}, relative, address)) { - return Fail(error, "raw scalar read is outside the 48-bit address space"); + return false; } } uint32_t word = 0; if (m_runtime.read_memory != nullptr) { if (!m_runtime.read_memory(m_runtime.userdata, address, &word)) { - return Fail(error, fmt::format("constant read failed at 0x{:016x}", address)); + return false; } } else { std::memcpy(&word, reinterpret_cast(address), sizeof(word)); @@ -574,28 +540,28 @@ private: return true; } - bool EvaluateInst(const Inst& inst, uint64_t& result, std::string* error) { + bool EvaluateInst(const Inst& inst, uint64_t& result) { uint64_t a = 0; uint64_t b = 0; uint64_t c = 0; - const auto binary = [&]() { return Arg(inst, 0, a, error) && Arg(inst, 1, b, error); }; + const auto binary = [&]() { return Arg(inst, 0, a) && Arg(inst, 1, b); }; const auto ternary = [&]() { - return Arg(inst, 0, a, error) && Arg(inst, 1, b, error) && Arg(inst, 2, c, error); + return Arg(inst, 0, a) && Arg(inst, 1, b) && Arg(inst, 2, c); }; switch (inst.GetOpcode()) { case ValueOpcode::GetUserData: { const auto reg = RegIndex(inst.Arg(0).ScalarRegister()); if (reg < m_program.user_data_base || reg - m_program.user_data_base >= m_runtime.user_data.size()) { - return Fail(error, fmt::format("user SGPR {} is unavailable", reg)); + return false; } result = m_runtime.user_data[reg - m_program.user_data_base]; return true; } case ValueOpcode::GetShaderBase: result = m_runtime.shader_base; return true; - case ValueOpcode::Phi: return EvaluatePhi(inst, result, error); + case ValueOpcode::Phi: return EvaluatePhi(inst, result); case ValueOpcode::CompositeExtractU64: - case ValueOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result, error); + case ValueOpcode::CompositeExtractU32x2: return EvaluateExtract(inst, result); case ValueOpcode::CompositeConstructU64: if (!binary()) { return false; @@ -607,20 +573,19 @@ private: const auto slot = inst.Arg(1).Resolve(); if (!slot.IsImmediate() || slot.GetType() != Type::U32 || slot.U32() >= m_program.srt_reads.size()) { - return Fail(error, "invalid flattened SRT index"); + return false; } if (slot.U32() < m_clean_flat_slots.size() && m_clean_flat_slots[slot.U32()] != 0u && m_clean_evaluator != nullptr) { - m_clean_evaluator->SetUsePc(m_use_pc); return m_clean_evaluator->EvaluateWide(m_program.srt_reads[slot.U32()].value, - result, error); + result); } - return EvaluateWide(m_program.srt_reads[slot.U32()].value, result, error); + return EvaluateWide(m_program.srt_reads[slot.U32()].value, result); } case ValueOpcode::LoadAddressU32: case ValueOpcode::ReadConstBuffer: if (IsRawRead(m_program, inst)) { - return EvaluateRawRead(inst, result, error); + return EvaluateRawRead(inst, result); } break; case ValueOpcode::IAdd32: @@ -690,7 +655,7 @@ private: } return false; case ValueOpcode::BitwiseNot32: - if (Arg(inst, 0, a, error)) { + if (Arg(inst, 0, a)) { result = ~static_cast(a); return true; } @@ -737,7 +702,7 @@ private: const auto offset = static_cast(b); const auto width = static_cast(c); if (offset > 32u || width > 32u - offset) { - return Fail(error, "invalid unsigned bit-field range"); + return false; } const auto mask = width == 32u ? UINT32_MAX : width == 0u ? 0u @@ -751,7 +716,7 @@ private: const auto offset = static_cast(b); const auto width = static_cast(c); if (offset > 32u || width > 32u - offset) { - return Fail(error, "invalid signed bit-field range"); + return false; } if (width == 0u) { result = 0; @@ -768,13 +733,13 @@ private: return false; case ValueOpcode::BitFieldInsert: { uint64_t d = 0; - if (!ternary() || !Arg(inst, 3, d, error)) { + if (!ternary() || !Arg(inst, 3, d)) { return false; } const auto offset = static_cast(c); const auto width = static_cast(d); if (offset > 32u || width > 32u - offset) { - return Fail(error, "invalid inserted bit-field range"); + return false; } if (width == 0u) { result = static_cast(a); @@ -836,7 +801,7 @@ private: } return false; case ValueOpcode::LogicalNot: - if (Arg(inst, 0, a, error)) { + if (Arg(inst, 0, a)) { result = a == 0u; return true; } @@ -845,18 +810,16 @@ private: case ValueOpcode::UndefU8: case ValueOpcode::UndefU16: case ValueOpcode::UndefU32: - case ValueOpcode::UndefU64: return Fail(error, "undefined typed runtime value"); + case ValueOpcode::UndefU64: return false; default: break; } - return Fail(error, fmt::format("unsupported typed runtime opcode {}", - ValueOpcodeName(inst.GetOpcode()))); + return false; } const Program& m_program; const SrtRuntime& m_runtime; std::span m_clean_flat_slots; Evaluator* m_clean_evaluator = nullptr; - uint32_t m_use_pc = 0; std::unordered_map m_cache; std::vector m_visiting; }; @@ -872,18 +835,12 @@ bool EvaluateRuntimeSourcesImpl(const Program& program std::span requests, const SrtRuntime& runtime, std::vector& results, std::vector& flat, bool evaluate_flat, - std::span clean_flat_slots, std::string* error) { + std::span clean_flat_slots) { if (!program.srt_plan_complete) { - if (error != nullptr) { - *error = Diagnostic(program, 0, "typed SRT plan is not ready"); - } return false; } if (std::ranges::any_of(clean_flat_slots, [](uint8_t clean) { return clean != 0u; }) && runtime.read_specialization_memory == nullptr) { - if (error != nullptr) { - *error = Diagnostic(program, 0, "clean flattened SRT read has no memory reader"); - } return false; } SrtRuntime clean_runtime = runtime; @@ -895,17 +852,12 @@ bool EvaluateRuntimeSourcesImpl(const Program& program for (const auto& request: requests) { const auto* source = Source(program, request.source); if (source == nullptr) { - if (error != nullptr) { - *error = Diagnostic(program, request.use_pc, - fmt::format("invalid descriptor source {}", request.source)); - } return false; } - evaluator.SetUsePc(request.use_pc); DescriptorValue value; value.dword_count = source->dword_count; for (uint32_t index = 0; index < source->dword_count; index++) { - if (!evaluator.Evaluate(source->dwords[index], value.dwords[index], error)) { + if (!evaluator.Evaluate(source->dwords[index], value.dwords[index])) { return false; } } @@ -918,9 +870,8 @@ bool EvaluateRuntimeSourcesImpl(const Program& program const bool clean = read.flat_offset < clean_flat_slots.size() && clean_flat_slots[read.flat_offset] != 0u; auto& selected = clean ? clean_evaluator : evaluator; - selected.SetUsePc(read.use_pc); if (read.flat_offset >= flattened.size() || - !selected.Evaluate(read.value, flattened[read.flat_offset], error)) { + !selected.Evaluate(read.value, flattened[read.flat_offset])) { return false; } } @@ -934,31 +885,24 @@ bool EvaluateRuntimeSourcesImpl(const Program& program } // namespace -bool ValidateRuntimeValue(const Program& program, Value value, std::string& reason) { - return RuntimeValidator(program).Run(value, reason); +bool ValidateRuntimeValue(const Program& program, Value value) { + return RuntimeValidator(program).Run(value); } -bool BuildSrtPlan(Program& program, std::string* error) { +void BuildSrtPlan(Program& program) { if (program.resource_tracking_complete) { - if (error != nullptr) { - *error = "cannot rebuild SRT after resource tracking"; - } - return false; + EXIT("shader SRT planning failed: cannot rebuild SRT after resource tracking"); } program.srt_plan_complete = false; - if (!PlanBuilder(program).Run(error)) { - return false; - } + PlanBuilder(program).Run(); program.srt_plan_complete = true; - return true; } -bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc, - const SrtRuntime& runtime, DescriptorValue& result, - std::string* error) { - const DescriptorSourceRequest request {source, use_pc}; +bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime, + DescriptorValue& result) { + const DescriptorSourceRequest request {source}; std::vector results; - if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results, error)) { + if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results)) { return false; } result = results.front(); @@ -967,26 +911,23 @@ bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t bool EvaluateDescriptorSources(const Program& program, std::span requests, - const SrtRuntime& runtime, std::vector& results, - std::string* error) { + const SrtRuntime& runtime, std::vector& results) { std::vector ignored; - return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {}, - error); + return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {}); } bool EvaluateRuntimeSources(const Program& program, std::span requests, const SrtRuntime& runtime, std::vector& results, - std::vector& flat, std::span clean_flat_slots, - std::string* error) { + std::vector& flat, + std::span clean_flat_slots) { return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true, - clean_flat_slots, error); + clean_flat_slots); } -bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector& flat, - std::string* error) { +bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector& flat) { std::vector ignored; - return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {}, error); + return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {}); } } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h index 1ecdf010c..9ef430e3c 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h @@ -13,43 +13,38 @@ using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, uint32_t* val struct SrtRuntime { std::span user_data; - uint64_t shader_base = 0; - SrtMemoryReader read_memory = nullptr; - void* userdata = nullptr; + uint64_t shader_base = 0; + SrtMemoryReader read_memory = nullptr; + void* userdata = nullptr; SrtMemoryReader read_specialization_memory = nullptr; }; struct DescriptorSourceRequest { uint32_t source = 0; - uint32_t use_pc = 0; }; // Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots; // dynamic offsets remain explicit and are never assigned a fake slot. -bool BuildSrtPlan(Program& program, std::string* error); -bool ValidateRuntimeValue(const Program& program, Value value, std::string& reason); +void BuildSrtPlan(Program& program); +bool ValidateRuntimeValue(const Program& program, Value value); -bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc, - const SrtRuntime& runtime, DescriptorValue& result, - std::string* error); +bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime, + DescriptorValue& result); // Evaluates one runtime snapshot transactionally. Scalar values and ReadConst results shared by // several descriptors are memoized once across the batch. bool EvaluateDescriptorSources(const Program& program, std::span requests, - const SrtRuntime& runtime, std::vector& results, - std::string* error); + const SrtRuntime& runtime, std::vector& results); // Evaluates descriptor sources and the flattened immediate SRT with one memoized scalar walk. // On failure neither destination is changed. bool EvaluateRuntimeSources(const Program& program, std::span requests, const SrtRuntime& runtime, std::vector& results, - std::vector& flat, std::span clean_flat_slots, - std::string* error); + std::vector& flat, std::span clean_flat_slots); -bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector& flat, - std::string* error); +bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector& flat); } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index b12a81f71..84a94ed44 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -841,23 +841,22 @@ static void ApplyPixelOutputs(ShaderPixelInputInfo& info, } } -static bool LogPermutationMismatch(const ShaderRecompiler::IR::Program& program, - const char* stage, const std::string& error) { +static bool LogPermutationMismatch(const ShaderRecompiler::IR::Program* program, + const char* stage) { static std::atomic log_count {0}; if (Config::GraphicsDebugDumpEnabled() && log_count.fetch_add(1, std::memory_order_relaxed) < 64) { - LOGF("ShaderProgramCache native runtime mismatch %s shader=0x%016" PRIx64 ": %s\n", - stage, program.shader_hash, error.c_str()); + LOGF("ShaderProgramCache native runtime mismatch %s shader=0x%016" PRIx64 "\n", + stage, program != nullptr ? program->shader_hash : 0); } return false; } bool MaterializeProgram(const std::shared_ptr& program, const ShaderParams& params, ShaderVertexInputInfo& info) { - std::string error; - if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, &error, + if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, ReadShaderGuestMemory)) { - return LogPermutationMismatch(*program, "VS", error); + return LogPermutationMismatch(program.get(), "VS"); } ApplyVertexOutputs(info, *program); return true; @@ -865,10 +864,9 @@ bool MaterializeProgram(const std::shared_ptr& program, const ShaderParams& params, ShaderPixelInputInfo& info) { - std::string error; - if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, &error, + if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, ReadShaderGuestMemory)) { - return LogPermutationMismatch(*program, "PS", error); + return LogPermutationMismatch(program.get(), "PS"); } ApplyPixelOutputs(info, *program); return true; @@ -876,10 +874,9 @@ bool MaterializeProgram(const std::shared_ptr& program, const ShaderParams& params, ShaderComputeInputInfo& info) { - std::string error; - if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, &error, + if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage, ReadShaderGuestMemory)) { - return LogPermutationMismatch(*program, "CS", error); + return LogPermutationMismatch(program.get(), "CS"); } return true; } @@ -1252,11 +1249,7 @@ static vk::ShaderModule CompileModule(vk::Device device, const ShaderParams& par ShaderStageRuntime& stage) { const auto* stage_name = ShaderStageName(options.stage); const auto* label = ShaderStageLabel(options.stage); - ShaderRecompiler::CompileResult result; - std::string error; - if (!ShaderRecompiler::TryRecompile(params.code, options, result, &error)) { - ExitShaderRecompilerFailure(label, options.shader_hash, error.c_str()); - } + auto result = ShaderRecompiler::Recompile(params.code, options); DumpShaderRecompilerOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump); if (!SpirvValidateBinary(label, options.shader_hash, result.spirv)) { diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 06c5593ee..a0c4699e9 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -57,7 +57,7 @@ using ShaderSpecializationMemoryReader = bool (*)(void* userdata, uint64_t addre bool ShaderMaterializeStageRuntime( std::shared_ptr program, std::span user_data, uint64_t shader_base, ShaderStageRuntime& stage, - std::string* error, ShaderSpecializationMemoryReader read_specialization_memory = nullptr, + ShaderSpecializationMemoryReader read_specialization_memory = nullptr, void* read_memory_data = nullptr); constexpr uint32_t DstSel(uint32_t x, uint32_t y = 0, uint32_t z = 0, uint32_t w = 0) { diff --git a/src/graphics/shader/shaderStageRuntime.cpp b/src/graphics/shader/shaderStageRuntime.cpp index dd4db4d3f..d1ff489de 100644 --- a/src/graphics/shader/shaderStageRuntime.cpp +++ b/src/graphics/shader/shaderStageRuntime.cpp @@ -7,13 +7,10 @@ namespace Libs::Graphics { bool ShaderMaterializeStageRuntime(std::shared_ptr program, std::span user_data, uint64_t shader_base, - ShaderStageRuntime& stage, std::string* error, + ShaderStageRuntime& stage, ShaderSpecializationMemoryReader read_specialization_memory, void* read_memory_data) { if (program == nullptr) { - if (error != nullptr) { - *error = "missing native shader plan"; - } return false; } ShaderRecompiler::IR::SrtRuntime runtime; @@ -22,8 +19,8 @@ bool ShaderMaterializeStageRuntime(std::shared_ptr +void CheckFatal(F &&function, std::string_view expected, const char *message) { + try { + function(); + } catch (const std::runtime_error &error) { + Check(std::string_view(error.what()).find(expected) != + std::string_view::npos, + message); + return; + } + Check(false, message); +} + struct Fixture { Program program; Block *block = nullptr; @@ -75,8 +88,7 @@ struct Fixture { } MemoryFlags AddMemory(MemoryInfo memory, uint32_t pc) { - const auto index = - static_cast(program.memory_info.size()); + const auto index = static_cast(program.memory_info.size()); program.memory_info.push_back(memory); return {index, pc}; } @@ -113,10 +125,8 @@ struct Fixture { } void PlanAndTrack() { - std::string error; - if (!BuildSrtPlan(program, &error) || !TrackResources(program, &error)) { - throw std::runtime_error(error); - } + BuildSrtPlan(program); + TrackResources(program); } }; @@ -241,10 +251,7 @@ void TestInvariantIndirectImageMaterialization() { auto fixture = MakeIndirectImageFixture(false); fixture->PlanAndTrack(); EliminateDeadCode(fixture->program.blocks); - std::string validation_error; - Check( - ValidateProgram(fixture->program, true, &validation_error), - "post-tracking dead-code elimination invalidated descriptor provenance"); + ValidateProgram(fixture->program, true); Check(fixture->program.info.buffers.size() == 1 && fixture->program.info.images.size() == 1 && @@ -291,7 +298,6 @@ void TestInvariantIndirectImageMaterialization() { .userdata = &memory, .read_specialization_memory = ReadLinearTestMemory}; ResourceSnapshot snapshot; - std::string error; const auto same_snapshot = [](const ResourceSnapshot &lhs, const ResourceSnapshot &rhs) { return lhs.buffers == rhs.buffers && lhs.images == rhs.images && @@ -300,7 +306,7 @@ void TestInvariantIndirectImageMaterialization() { lhs.user_data == rhs.user_data && lhs.indirect_images.empty() == rhs.indirect_images.empty(); }; - Check(MaterializeResources(fixture->program, runtime, snapshot, &error) && + Check(MaterializeResources(fixture->program, runtime, snapshot) && snapshot.images.size() == 1 && std::equal(image_descriptor.begin(), image_descriptor.end(), snapshot.images[0].dwords.begin()), @@ -308,8 +314,7 @@ void TestInvariantIndirectImageMaterialization() { const auto prior_snapshot = snapshot; memory.fail_address = 0x1004u; - Check(!MaterializeResources(fixture->program, runtime, snapshot, &error) && - error.find("scalar read") != std::string::npos && + Check(!MaterializeResources(fixture->program, runtime, snapshot) && same_snapshot(snapshot, prior_snapshot), "rejected planning memory read mutated the snapshot"); memory.fail_address = UINT64_MAX; @@ -325,12 +330,11 @@ void TestInvariantIndirectImageMaterialization() { memory.words[(0x2020u - memory.base) / 4u + 3u] = image_descriptor[3] ^ (1u << 28u); ResourceSnapshot null_snapshot; - Check( - MaterializeResources(fixture->program, runtime, null_snapshot, &error) && - null_snapshot.indirect_images.empty() && - std::ranges::all_of(null_snapshot.images[0].dwords, - [](uint32_t dword) { return dword == 0u; }), - "stale typed null image descriptors were not canonicalized"); + Check(MaterializeResources(fixture->program, runtime, null_snapshot) && + null_snapshot.indirect_images.empty() && + std::ranges::all_of(null_snapshot.images[0].dwords, + [](uint32_t dword) { return dword == 0u; }), + "stale typed null image descriptors were not canonicalized"); for (uint32_t dword = 0; dword < image_descriptor.size(); dword++) { memory.words[(0x2000u - memory.base) / 4u + dword] = @@ -341,13 +345,13 @@ void TestInvariantIndirectImageMaterialization() { memory.words[(0x2020u - memory.base) / 4u] ^= 1u; memory.words[(0x1000u - memory.base + 36u) / 4u] = 1u; ResourceSnapshot dynamic_snapshot; - Check(MaterializeResources(fixture->program, runtime, dynamic_snapshot, - &error) && + Check(MaterializeResources(fixture->program, runtime, dynamic_snapshot) && dynamic_snapshot.images.size() == 1 && dynamic_snapshot.indirect_images.size() == 1 && - dynamic_snapshot.indirect_images[0].descriptors.size() == 2 && - SpecializeResources(fixture->program, dynamic_snapshot, &error) && - fixture->program.info.images.size() == 2 && + dynamic_snapshot.indirect_images[0].descriptors.size() == 2, + "dynamic indirect image table did not materialize"); + SpecializeResources(fixture->program, dynamic_snapshot); + Check(fixture->program.info.images.size() == 2 && fixture->program.info.images[0].indirect_root == 0 && fixture->program.info.images[0].indirect_mapping_capacity != 0 && fixture->program.info.images[0].indirect_resources.size() == 2 && @@ -364,27 +368,21 @@ void TestInvariantIndirectImageMaterialization() { memory.words[(0x2000u - memory.base) / 4u] += 0x100u; memory.words[(0x2020u - memory.base) / 4u] += 0x101u; ResourceSnapshot rebound_snapshot; - Check(MaterializeResources(fixture->program, runtime, rebound_snapshot, - &error) && - ValidateResourceSpecialization(fixture->program, rebound_snapshot, - &error), + Check(MaterializeResources(fixture->program, runtime, rebound_snapshot) && + ValidateResourceSpecialization(fixture->program, rebound_snapshot), "stable indirect key mapping did not accept changed image addresses"); memory.words[(0x2020u - memory.base) / 4u] = memory.words[(0x2000u - memory.base) / 4u]; - Check(MaterializeResources(fixture->program, runtime, rebound_snapshot, - &error) && - ValidateResourceSpecialization(fixture->program, rebound_snapshot, - &error), + Check(MaterializeResources(fixture->program, runtime, rebound_snapshot) && + ValidateResourceSpecialization(fixture->program, rebound_snapshot), "runtime indirect key mapping did not accept collapsed candidates"); const auto collapsed_snapshot = rebound_snapshot; ResourceSnapshot capacity_snapshot; for (const uint32_t records : {1u, 3u}) { user_data[2] = records; Check( - MaterializeResources(fixture->program, runtime, capacity_snapshot, - &error) && - ValidateResourceSpecialization(fixture->program, capacity_snapshot, - &error), + MaterializeResources(fixture->program, runtime, capacity_snapshot) && + ValidateResourceSpecialization(fixture->program, capacity_snapshot), "runtime indirect key mapping rejected a fitting material-table size"); } user_data[2] = 2u; @@ -398,9 +396,7 @@ void TestInvariantIndirectImageMaterialization() { } memory.words[(0x1000u - memory.base + 68u) / 4u] = 2u; Check( - !MaterializeResources(fixture->program, runtime, rebound_snapshot, - &error) && - error.find("candidate topology") != std::string::npos && + !MaterializeResources(fixture->program, runtime, rebound_snapshot) && same_snapshot(rebound_snapshot, collapsed_snapshot), "larger indirect candidate topology reused or mutated a cached snapshot"); @@ -418,26 +414,26 @@ void TestInvariantIndirectImageMaterialization() { .read_specialization_memory = ReadLinearTestMemory}; Check(!MaterializeResources(memory_backed->program, memory_backed_runtime, - snapshot, &error) && - error.find("constant read failed") != std::string::npos && + snapshot) && same_snapshot(snapshot, prior_snapshot), "rejected indirect table descriptor read mutated the snapshot"); memory.fail_address = UINT64_MAX; auto malformed = MakeIndirectImageFixture(true); - Check(BuildSrtPlan(malformed->program, &error) && - !TrackResources(malformed->program, &error) && - error.find("ReadFirstLane") != std::string::npos && - !malformed->program.resource_tracking_complete && + BuildSrtPlan(malformed->program); + CheckFatal([&] { TrackResources(malformed->program); }, "not a valid runtime value", + "malformed indirect image pattern was accepted"); + Check(!malformed->program.resource_tracking_complete && malformed->program.info.images.empty() && malformed->program.descriptor_sources.empty(), "malformed indirect image pattern was partially accepted"); auto wrapped_immediate = MakeIndirectImageFixture(false, 4u); - Check(BuildSrtPlan(wrapped_immediate->program, &error) && - !TrackResources(wrapped_immediate->program, &error) && - error.find("ReadFirstLane") != std::string::npos && - !wrapped_immediate->program.resource_tracking_complete, + BuildSrtPlan(wrapped_immediate->program); + CheckFatal([&] { TrackResources(wrapped_immediate->program); }, + "not a valid runtime value", + "wrapped scalar immediate entered the invariant image proof"); + Check(!wrapped_immediate->program.resource_tracking_complete, "wrapped scalar immediate entered the invariant image proof"); } @@ -494,16 +490,12 @@ void TestDenseBufferTracking() { second.Instruction()->Flags() == 1, "typed handles were not assigned dense indices"); Check(fixture.program.memory_info[load_flags.index].resource == 0 && - fixture.program.memory_info[store_flags.index].resource == - 0 && - fixture.program.memory_info[other_flags.index].resource == - 1, + fixture.program.memory_info[store_flags.index].resource == 0 && + fixture.program.memory_info[other_flags.index].resource == 1, "typed memory metadata was not patched to dense indices"); - std::string error; - Check(!TrackResources(fixture.program, &error) && - error.find("already tracked") != std::string::npos, - "resource tracking allowed a second mutation pass"); + CheckFatal([&] { TrackResources(fixture.program); }, "already tracked", + "resource tracking allowed a second mutation pass"); } void TestScalarAndVectorBufferAlias() { @@ -531,8 +523,7 @@ void TestScalarAndVectorBufferAlias() { fixture.program.info.buffers[0].scalar, "typed scalar and vector uses of one descriptor were split"); Check(fixture.program.memory_info[scalar_flags.index].resource == 0 && - fixture.program.memory_info[vector_flags.index].resource == - 0, + fixture.program.memory_info[vector_flags.index].resource == 0, "scalar/vector alias did not share a dense index"); } @@ -552,16 +543,13 @@ void TestRuntimeUnsignedMinDescriptor() { std::array user_data{0xffffffffu}; SrtRuntime runtime{.user_data = user_data}; DescriptorValue value; - std::string error; const auto source = fixture.program.info.buffers[0].source; - Check(EvaluateDescriptorSource(fixture.program, source, 0x330, runtime, value, - &error) && + Check(EvaluateDescriptorSource(fixture.program, source, runtime, value) && value.dwords[3] == 0x100u, "runtime descriptor unsigned minimum did not clamp its first operand"); user_data[0] = 0x80u; Check( - EvaluateDescriptorSource(fixture.program, source, 0x330, runtime, value, - &error) && + EvaluateDescriptorSource(fixture.program, source, runtime, value) && value.dwords[3] == 0x80u, "runtime descriptor unsigned minimum did not preserve its first operand"); } @@ -624,9 +612,7 @@ void TestImagesSamplersAndAliases() { repeated.second.Instruction()->Flags() == 0, "unused sampler border colors prevented source interning"); const auto sampler_source = fixture.program.info.samplers[0].source; - Check(fixture.program.descriptor_sources[sampler_source] - .dwords[3] - .U32() == 0, + Check(fixture.program.descriptor_sources[sampler_source].dwords[3].U32() == 0, "unused sampler border color was not canonicalized"); Check(fixture.program.info.buffers[0].image_alias == 0, "buffer/image descriptor alias was not linked"); @@ -673,9 +659,7 @@ void TestSampleAdjustSamplerScratch() { std::array user_data{4u, 1u, 2u, 0x80000abcu}; SrtRuntime runtime{.user_data = user_data}; DescriptorValue descriptor; - std::string error; - Check(EvaluateDescriptorSource(fixture.program, source, 0x1ec, runtime, - descriptor, &error) && + Check(EvaluateDescriptorSource(fixture.program, source, runtime, descriptor) && descriptor.dwords[3] == 0x80000abcu, "SampleAdjust canonicalization lost sampler border fields"); @@ -704,11 +688,9 @@ void TestSampleAdjustSamplerScratch() { rejected.Emit(ValueOpcode::ImageSampleRaw, {rejected_image, rejected_sampler, rejected.ImageAddress()}, rejected.AddMemory(rejected_memory, 0x200)); - std::string rejected_error; - Check(BuildSrtPlan(rejected.program, &rejected_error) && - !TrackResources(rejected.program, &rejected_error) && - rejected_error.find("WqmMask") != std::string::npos, - message); + BuildSrtPlan(rejected.program); + CheckFatal([&] { TrackResources(rejected.program); }, + "not a valid runtime value", message); }; CheckRejected(0u, 12u, "ordinary sampling accepted SampleAdjust reserved scratch"); @@ -778,15 +760,13 @@ void TestDynamicStorageMipTracking() { descriptor.dword_count = 8; ResourceSnapshot snapshot; snapshot.images.assign(images.size(), descriptor); - std::string error; - Check(SpecializeResources(fixture.program, snapshot, &error) && - fixture.program.info.images[1].mip_count == 3 && - ValidateResourceSpecialization(fixture.program, snapshot, &error), + SpecializeResources(fixture.program, snapshot); + Check(fixture.program.info.images[1].mip_count == 3 && + ValidateResourceSpecialization(fixture.program, snapshot), "base-1 through last-3 dynamic storage range was not specialized"); ShaderComputeInputInfo compute{}; - Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) && - AllocateBindings(fixture.program, 0, &error), - "dynamic storage mip bindings were not allocated"); + CollectShaderInfo(fixture.program, {.compute = &compute}); + AllocateBindings(fixture.program, 0); const auto *storage_binding = FindBinding(fixture.program.bindings, DescriptorBindingKind::Storage2D); Check(storage_binding != nullptr && @@ -805,19 +785,19 @@ void TestDynamicStorageMipTracking() { DescriptorValue null_descriptor{}; null_descriptor.dword_count = 8; null_snapshot.images.push_back(null_descriptor); - Check(SpecializeResources(null_program, null_snapshot, &error) && - null_program.info.images[0].mip_count == 1 && - ValidateResourceSpecialization(null_program, null_snapshot, &error), + SpecializeResources(null_program, null_snapshot); + Check(null_program.info.images[0].mip_count == 1 && + ValidateResourceSpecialization(null_program, null_snapshot), "canonical null dynamic storage image did not retain one descriptor"); snapshot.images[1].dwords[3] = (snapshot.images[1].dwords[3] & ~(0xfu << 16u)) | (2u << 16u); - Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error), + Check(!ValidateResourceSpecialization(fixture.program, snapshot), "a changed dynamic storage mip count reused the specialization"); snapshot.images[1].dwords[3] = (snapshot.images[1].dwords[3] & ~((0xfu << 12u) | (0xfu << 16u))) | (4u << 12u) | (3u << 16u); - Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error), + Check(!ValidateResourceSpecialization(fixture.program, snapshot), "an inverted dynamic storage mip range was accepted"); } @@ -861,11 +841,9 @@ void TestSrtFlatteningAndRuntimeMemoization() { .userdata = &memory}; std::vector descriptors; std::vector flat; - const DescriptorSourceRequest request{fixture.program.info.buffers[0].source, - 12}; - std::string error; + const DescriptorSourceRequest request{fixture.program.info.buffers[0].source}; Check(EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, runtime, - descriptors, flat, {}, &error), + descriptors, flat, {}), "typed runtime source evaluation failed"); Check(descriptors.size() == 1 && descriptors[0].dwords[0] == 0xdeadbeefu && flat == std::vector{0xdeadbeefu} && memory.reads == 1, @@ -876,16 +854,16 @@ void TestSrtFlatteningAndRuntimeMemoization() { descriptors = {{{1u}, 1u}}; flat = {2u}; Check(!EvaluateRuntimeSources(fixture.program, std::span{&request, 1}, - runtime, descriptors, flat, {}, &error) && + runtime, descriptors, flat, {}) && descriptors == std::vector{{{1u}, 1u}} && flat == std::vector{2u}, "runtime evaluation failure was not transactional"); ShaderComputeInputInfo compute{}; - Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) && - AllocateBindings(fixture.program, 0, &error) && - FindBinding(fixture.program.bindings, - DescriptorBindingKind::FlattenedSrt) != nullptr, + CollectShaderInfo(fixture.program, {.compute = &compute}); + AllocateBindings(fixture.program, 0); + Check(FindBinding(fixture.program.bindings, + DescriptorBindingKind::FlattenedSrt) != nullptr, "flattened typed SRT reads did not receive a binding"); } @@ -919,18 +897,16 @@ void TestDynamicSrtReadRemainsExplicit() { .read_memory = ReadTestMemory, .userdata = &memory}; DescriptorValue value; - std::string error; Check(EvaluateDescriptorSource(fixture.program, - fixture.program.info.buffers[0].source, 8, - runtime, value, &error) && + fixture.program.info.buffers[0].source, runtime, value) && value.dwords[0] == 0xabcdef01u && memory.reads == 1, "dynamic typed scalar descriptor source was not evaluated"); ShaderComputeInputInfo compute{}; - Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error) && - AllocateBindings(fixture.program, 0, &error) && - FindBinding(fixture.program.bindings, - DescriptorBindingKind::FlattenedSrt) == nullptr && + CollectShaderInfo(fixture.program, {.compute = &compute}); + AllocateBindings(fixture.program, 0); + Check(FindBinding(fixture.program.bindings, + DescriptorBindingKind::FlattenedSrt) == nullptr && FindBinding(fixture.program.bindings, DescriptorBindingKind::BdaPagetable) != nullptr && FindBinding(fixture.program.bindings, @@ -966,12 +942,10 @@ void TestPhiValidation() { {handle, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(memory, 20), merge); - std::string error; - Check(BuildSrtPlan(fixture.program, &error), - "SRT planning rejected a well-formed typed phi"); - Check(!TrackResources(fixture.program, &error) && - error.find("control-dependent phi") != std::string::npos && - !fixture.program.resource_tracking_complete && + BuildSrtPlan(fixture.program); + CheckFatal([&] { TrackResources(fixture.program); }, "not a valid runtime value", + "control-dependent descriptor phi was accepted"); + Check(!fixture.program.resource_tracking_complete && fixture.program.info.buffers.empty() && fixture.program.descriptor_sources.empty(), "control-dependent descriptor phi was not rejected transactionally"); @@ -994,9 +968,7 @@ void TestLoopCycleEnteredThroughRuntimeValue() { {carried, Value(0u), Value(0u), Value(0u)}, MemoryFlags{0, 12}, loop); - std::string error; - Check(BuildSrtPlan(fixture.program, &error), - "SRT planning rejected a valid loop entered through a runtime value"); + BuildSrtPlan(fixture.program); } void TestInvariantLoopPhi() { @@ -1023,10 +995,8 @@ void TestInvariantLoopPhi() { std::array user_data{0x12345678u}; SrtRuntime runtime{.user_data = user_data}; DescriptorValue descriptor; - std::string error; Check(EvaluateDescriptorSource(fixture.program, - fixture.program.info.buffers[0].source, 4, - runtime, descriptor, &error) && + fixture.program.info.buffers[0].source, runtime, descriptor) && descriptor.dwords[0] == user_data[0], "loop-invariant descriptor phi was not evaluated through typed SSA"); } @@ -1057,11 +1027,9 @@ void TestDmaAddressMaterialization() { std::array user_data{0x2008u, 0u}; SrtRuntime runtime{.user_data = user_data}; ResourceSnapshot snapshot; - std::string error; - Check(MaterializeResources(fixture.program, runtime, snapshot, &error), + Check(MaterializeResources(fixture.program, runtime, snapshot), "DMA shader resources did not materialize"); - Check(SpecializeResources(fixture.program, snapshot, &error), - "DMA shader resources were not specialized"); + SpecializeResources(fixture.program, snapshot); } void TestDynamicFlatAddressesUseDma() { @@ -1089,8 +1057,7 @@ void TestDynamicFlatAddressesUseDma() { std::array user_data{0x23456780u, 1u, 1u}; SrtRuntime runtime{.user_data = user_data}; ResourceSnapshot snapshot; - std::string error; - Check(MaterializeResources(fixture.program, runtime, snapshot, &error), + Check(MaterializeResources(fixture.program, runtime, snapshot), "exec-masked FLAT shader resources did not materialize"); Fixture mismatch; @@ -1137,15 +1104,15 @@ void TestBufferSwizzleSpecialization() { (1u << 24u)}; SrtRuntime runtime{.user_data = user_data}; ResourceSnapshot snapshot; - std::string error; - Check(MaterializeResources(fixture.program, runtime, snapshot, &error) && - SpecializeResources(fixture.program, snapshot, &error) && - fixture.program.info.buffers[0].descriptor_swizzle == swizzle && - ValidateResourceSpecialization(fixture.program, snapshot, &error), + Check(MaterializeResources(fixture.program, runtime, snapshot), + "buffer resources did not materialize"); + SpecializeResources(fixture.program, snapshot); + Check(fixture.program.info.buffers[0].descriptor_swizzle == swizzle && + ValidateResourceSpecialization(fixture.program, snapshot), "buffer destination selectors were not specialized"); snapshot.buffers[0].dwords[3] ^= 1u << 9u; - Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error), + Check(!ValidateResourceSpecialization(fixture.program, snapshot), "buffer swizzle change did not invalidate specialization"); } @@ -1171,17 +1138,14 @@ void TestShaderInfoAndBindingLayout() { ShaderComputeInputInfo compute{}; compute.dispatch_thread_dimensions = true; - std::string error; - Check(CollectShaderInfo(fixture.program, {.compute = &compute}, &error), - "typed shader info collection failed"); + CollectShaderInfo(fixture.program, {.compute = &compute}); Check(fixture.program.info.has_bitwise_xor && !fixture.program.info.inputs.empty() && fixture.program.info.inputs[0].kind == StageInputKind::GlobalInvocationId, "typed shader values were not reflected in shader info"); - Check(AllocateBindings(fixture.program, 0, &error), - "typed binding allocation failed"); + AllocateBindings(fixture.program, 0); Check(FindBinding(fixture.program.bindings, DescriptorBindingKind::Buffers) != nullptr && FindBinding(fixture.program.bindings, DescriptorBindingKind::Gds) != @@ -1211,53 +1175,57 @@ void TestGraphicsPushConstantLayout() { } fixture.program.shader_info_complete = true; }; - - std::string error; Fixture vertex(ShaderType::Vertex); AddUserData(vertex, 9); - Check(AllocateBindings(vertex.program, 0, &error) && - vertex.program.bindings.push_constant_offset == 0 && + AllocateBindings(vertex.program, 0); + Check(vertex.program.bindings.push_constant_offset == 0 && vertex.program.bindings.push_constant_size == 9 * sizeof(uint32_t), "vertex shader was not placed at the start of the graphics push bank"); const auto pixel_offset = vertex.program.bindings.push_constant_size; Fixture pixel(ShaderType::Pixel); AddUserData(pixel, 4); - Check(AllocateBindings(pixel.program, pixel_offset, &error) && - pixel.program.bindings.push_constant_offset == pixel_offset && - pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) && - FindBinding(pixel.program.bindings, DescriptorBindingKind::UserData) == - nullptr, - "pixel shader did not follow the vertex data in the graphics push bank"); + AllocateBindings(pixel.program, pixel_offset); + Check( + pixel.program.bindings.push_constant_offset == pixel_offset && + pixel.program.bindings.push_constant_size == 4 * sizeof(uint32_t) && + FindBinding(pixel.program.bindings, + DescriptorBindingKind::UserData) == nullptr, + "pixel shader did not follow the vertex data in the graphics push bank"); Fixture edge(ShaderType::Pixel); AddUserData(edge, 1); - Check(AllocateBindings(edge.program, NativePushConstantSize - sizeof(uint32_t), - &error) && - edge.program.bindings.push_constant_size == sizeof(uint32_t), + AllocateBindings(edge.program, NativePushConstantSize - sizeof(uint32_t)); + Check(edge.program.bindings.push_constant_size == sizeof(uint32_t), "last aligned push-constant dword did not fit in the graphics bank"); Fixture spill(ShaderType::Pixel); AddUserData(spill, 32); - Check(AllocateBindings(spill.program, pixel_offset, &error) && - spill.program.bindings.push_constant_size == 0 && - FindBinding(spill.program.bindings, DescriptorBindingKind::UserData) != - nullptr, - "pixel shader overlapping the vertex push data did not spill to storage"); + AllocateBindings(spill.program, pixel_offset); + Check( + spill.program.bindings.push_constant_size == 0 && + FindBinding(spill.program.bindings, + DescriptorBindingKind::UserData) != nullptr, + "pixel shader overlapping the vertex push data did not spill to storage"); Fixture full(ShaderType::Pixel); AddUserData(full, 1); - Check(AllocateBindings(full.program, NativePushConstantSize, &error) && - full.program.bindings.push_constant_size == 0 && - FindBinding(full.program.bindings, DescriptorBindingKind::UserData) != - nullptr, + AllocateBindings(full.program, NativePushConstantSize); + Check(full.program.bindings.push_constant_size == 0 && + FindBinding(full.program.bindings, + DescriptorBindingKind::UserData) != nullptr, "full graphics push bank did not spill pixel user data to storage"); Fixture invalid(ShaderType::Pixel); AddUserData(invalid, 1); - Check(!AllocateBindings(invalid.program, - NativePushConstantSize + sizeof(uint32_t), &error) && - !invalid.program.binding_layout_complete, + CheckFatal( + [&] { + AllocateBindings(invalid.program, + NativePushConstantSize + sizeof(uint32_t)); + }, + "push-constant offset", + "push-constant placement beyond the graphics bank was accepted"); + Check(!invalid.program.binding_layout_complete, "push-constant placement beyond the graphics bank was accepted"); } @@ -1273,12 +1241,11 @@ void TestResourceLimitIsTransactional() { {handle, Value(0u), Value(0u), Value(0u), Value(true)}, fixture.AddMemory(memory, index * 4u)); } - std::string error; - Check(BuildSrtPlan(fixture.program, &error), - "SRT plan failed before resource-limit test"); - Check(!TrackResources(fixture.program, &error) && - error.find("buffer resource limit exceeded") != std::string::npos && - !fixture.program.resource_tracking_complete && + BuildSrtPlan(fixture.program); + CheckFatal([&] { TrackResources(fixture.program); }, + "buffer resource limit exceeded", + "resource-limit failure was not reported"); + Check(!fixture.program.resource_tracking_complete && fixture.program.info.buffers.empty() && fixture.program.descriptor_sources.empty(), "resource-limit failure partially mutated typed resource state"); @@ -1293,12 +1260,11 @@ void TestMalformedMemoryKindsRejected() { fixture.Emit(ValueOpcode::StoreAddressU32, {address, Value(0u), Value(0u), Value(1u), Value(true)}, fixture.AddMemory(memory, 4)); - std::string error; - Check(BuildSrtPlan(fixture.program, &error) && - !TrackResources(fixture.program, &error) && - error.find("address operation has invalid resource kind") != - std::string::npos, - "resource tracking accepted an address opcode with buffer metadata"); + BuildSrtPlan(fixture.program); + CheckFatal( + [&] { TrackResources(fixture.program); }, + "address operation has invalid resource kind", + "resource tracking accepted an address opcode with buffer metadata"); } { Fixture fixture; @@ -1311,12 +1277,11 @@ void TestMalformedMemoryKindsRejected() { fixture.Emit(ValueOpcode::ImageRead, {image, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 8)); - std::string error; - Check(BuildSrtPlan(fixture.program, &error) && - !TrackResources(fixture.program, &error) && - error.find("image operation has invalid resource kind") != - std::string::npos, - "resource tracking accepted an image opcode with address metadata"); + BuildSrtPlan(fixture.program); + CheckFatal( + [&] { TrackResources(fixture.program); }, + "image operation has invalid resource kind", + "resource tracking accepted an image opcode with address metadata"); } { Fixture fixture; @@ -1329,12 +1294,11 @@ void TestMalformedMemoryKindsRejected() { fixture.Emit(ValueOpcode::ImageRead, {image, fixture.ImageAddress(), Value(true)}, fixture.AddMemory(memory, 12)); - std::string error; - Check(BuildSrtPlan(fixture.program, &error) && - !TrackResources(fixture.program, &error) && - error.find("image operation has invalid resource kind") != - std::string::npos, - "resource tracking accepted a sampled read with storage metadata"); + BuildSrtPlan(fixture.program); + CheckFatal( + [&] { TrackResources(fixture.program); }, + "image operation has invalid resource kind", + "resource tracking accepted a sampled read with storage metadata"); } { Fixture fixture; @@ -1347,12 +1311,10 @@ void TestMalformedMemoryKindsRejected() { fixture.Emit(ValueOpcode::ImageAtomicIAdd32, {image, fixture.ImageAddress(), Value(1u), Value(true)}, fixture.AddMemory(memory, 16)); - std::string error; - Check( - BuildSrtPlan(fixture.program, &error) && - !TrackResources(fixture.program, &error) && - error.find("image operation has invalid resource kind") != - std::string::npos, + BuildSrtPlan(fixture.program); + CheckFatal( + [&] { TrackResources(fixture.program); }, + "image operation has invalid resource kind", "resource tracking accepted a uint atomic with float storage metadata"); } } @@ -1399,14 +1361,24 @@ int main() { // target links only fmt; keep assertion failures observable without widening // its focused build manifest. namespace Common { +int DbgExitHandler(const char *, int, std::string_view text) { + throw std::runtime_error(std::string(text)); +} + +int DbgExitHandler(const char *, int, fmt::text_style, std::string_view text) { + throw std::runtime_error(std::string(text)); +} + int DbgExitIfHandler(const char *expression, const char *file, int line) { throw std::runtime_error(std::string("typed IR assertion: ") + expression + " at " + file + ':' + std::to_string(line)); } -int DbgNotImplementedHandler(const char *expression, const char *file, int line) { - throw std::runtime_error(std::string("typed IR not implemented: ") + expression + - " at " + file + ':' + std::to_string(line)); +int DbgNotImplementedHandler(const char *expression, const char *file, + int line) { + throw std::runtime_error(std::string("typed IR not implemented: ") + + expression + " at " + file + ':' + + std::to_string(line)); } void DbgExit(int) { throw std::runtime_error("typed IR assertion failed"); } @@ -1415,8 +1387,8 @@ void DbgExit(int) { throw std::runtime_error("typed IR assertion failed"); } // Keep this focused standalone target self-contained by amalgamating its small // typed-IR implementation set. #include "graphics/shader/recompiler/ir/Block.cpp" +#include "graphics/shader/recompiler/ir/Program.cpp" #include "graphics/shader/recompiler/ir/Type.cpp" #include "graphics/shader/recompiler/ir/Value.cpp" -#include "graphics/shader/recompiler/ir/Program.cpp" #include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp" #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp" diff --git a/tests/ScalarProvenanceTests.cpp b/tests/ScalarProvenanceTests.cpp index 48d01a654..6372a0fb7 100644 --- a/tests/ScalarProvenanceTests.cpp +++ b/tests/ScalarProvenanceTests.cpp @@ -20,6 +20,19 @@ void Check(bool condition, const char *message) { } } +template +void CheckFatal(F &&function, std::string_view expected, const char *message) { + try { + function(); + } catch (const std::runtime_error &error) { + Check(std::string_view(error.what()).find(expected) != + std::string_view::npos, + message); + return; + } + Check(false, message); +} + struct Fixture { Program program; @@ -58,10 +71,7 @@ struct Fixture { return static_cast(program.memory_info.size() - 1u); } - void Plan() { - std::string error; - Check(BuildSrtPlan(program, &error), error.c_str()); - } + void Plan() { BuildSrtPlan(program); } }; struct TestMemory { @@ -112,8 +122,7 @@ void TestImmediateFlatteningAndGvn() { TestMemory memory_image{{{0x1020u, 0xfeedbeefu}}}; SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; std::vector flat; - std::string error; - Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str()); + Check(WalkSrt(fixture.program, runtime, flat), "flattened SRT walk failed"); Check(flat == std::vector{0xfeedbeefu} && memory_image.reads == 1, "flattened SRT did not evaluate its canonical read once"); } @@ -130,8 +139,7 @@ void TestRawScalarComponentAlignment() { TestMemory memory_image{{{0x1000u, 0x12345678u}}}; SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; std::vector flat; - std::string error; - Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str()); + Check(WalkSrt(fixture.program, runtime, flat), "raw scalar SRT walk failed"); Check( flat == std::vector{0x12345678u} && memory_image.reads == 1, "raw scalar base, immediate, and offset were not aligned independently"); @@ -141,11 +149,9 @@ void TestScalarMemoryDomainMismatchFails() { Fixture raw; const auto raw_memory = raw.AddMemory(ResourceKind::ScalarBuffer); RawRead(raw, Address(raw, Value(0x1000u), Value(0u)), Value(0u), raw_memory); - std::string error; - Check(!BuildSrtPlan(raw.program, &error) && - error.find("incompatible scalar memory metadata") != - std::string::npos, - "raw scalar load accepted descriptor-buffer metadata"); + CheckFatal([&] { BuildSrtPlan(raw.program); }, + "incompatible scalar memory metadata", + "raw scalar load accepted descriptor-buffer metadata"); Fixture buffer; const auto buffer_memory = buffer.AddMemory(ResourceKind::ScalarAddress); @@ -154,11 +160,9 @@ void TestScalarMemoryDomainMismatchFails() { {Value(0x1000u), Value(0u), Value(16u), Value(0u)}); buffer.EmitMemory(ValueOpcode::ReadConstBuffer, {resource, Value(0u)}, buffer_memory); - error.clear(); - Check(!BuildSrtPlan(buffer.program, &error) && - error.find("incompatible scalar memory metadata") != - std::string::npos, - "descriptor scalar load accepted raw-address metadata"); + CheckFatal([&] { BuildSrtPlan(buffer.program); }, + "incompatible scalar memory metadata", + "descriptor scalar load accepted raw-address metadata"); } void TestDynamicReadRemainsTyped() { @@ -191,8 +195,7 @@ void TestNestedSrtWalk() { TestMemory memory_image{{{0x1000u, 0x2000u}, {0x2000u, 0xabcdef01u}}}; SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; std::vector flat; - std::string error; - Check(WalkSrt(fixture.program, runtime, flat, &error), error.c_str()); + Check(WalkSrt(fixture.program, runtime, flat), "nested SRT walk failed"); Check(flat == std::vector({0x2000u, 0xabcdef01u}), "nested typed SRT reads were not evaluated in dependency order"); } @@ -215,10 +218,8 @@ void TestShaderBaseAndUserData() { SrtRuntime runtime{.user_data = user_data, .shader_base = 0x12345678abcdef00ull}; DescriptorValue result; - std::string error; - Check(EvaluateDescriptorSource(fixture.program, 0, 0x90, runtime, result, - &error), - error.c_str()); + Check(EvaluateDescriptorSource(fixture.program, 0, runtime, result), + "shader-relative descriptor evaluation failed"); Check(result.dword_count == 3 && result.dwords[0] == 0xabcdef00u && result.dwords[1] == 0x12345678u && result.dwords[2] == 0x24u, "shader-relative typed descriptor expression evaluated incorrectly"); @@ -242,9 +243,8 @@ void TestCarryAndBitFields() { {.dwords = {low, high, inserted, sign}, .dword_count = 4}); DescriptorValue result; - std::string error; - Check(EvaluateDescriptorSource(fixture.program, 0, 0, {}, result, &error), - error.c_str()); + Check(EvaluateDescriptorSource(fixture.program, 0, {}, result), + "carry and bit-field descriptor evaluation failed"); Check(result.dwords[0] == 1u && result.dwords[1] == 1u && result.dwords[2] == 0x89abcdefu && result.dwords[3] == 0xffffffffu, "typed carry or bit-field runtime evaluation is incorrect"); @@ -267,17 +267,13 @@ void TestInvariantAndDivergentPhi() { {.dwords = {Value(&divergent)}, .dword_count = 1}); DescriptorValue result; - std::string error; - Check( - EvaluateDescriptorSource(fixture.program, 0, 0x100, {}, result, &error) && - result.dwords[0] == 7u, - "loop-invariant typed phi was rejected"); + Check(EvaluateDescriptorSource(fixture.program, 0, {}, result) && + result.dwords[0] == 7u, + "loop-invariant typed phi was rejected"); result.dword_count = 4; result.dwords[0] = 0xdeadbeefu; - Check(!EvaluateDescriptorSource(fixture.program, 1, 0x104, {}, result, - &error) && - result.dword_count == 4 && result.dwords[0] == 0xdeadbeefu && - error.find("runtime-dependent") != std::string::npos, + Check(!EvaluateDescriptorSource(fixture.program, 1, {}, result) && + result.dword_count == 4 && result.dwords[0] == 0xdeadbeefu, "divergent phi did not fail transactionally"); } @@ -318,8 +314,7 @@ void TestRuntime64BitDescriptorOps() { {.dwords = {low, high}, .dword_count = 2}); DescriptorValue result; - std::string error; - Check(EvaluateDescriptorSource(fixture.program, 0, 0, {}, result, &error) && + Check(EvaluateDescriptorSource(fixture.program, 0, {}, result) && result.dwords[0] == 0xabcdu && result.dwords[1] == 0x1235u, "64-bit typed descriptor arithmetic evaluation is incorrect"); } @@ -339,10 +334,9 @@ void TestConstantBufferBounds() { TestMemory memory_image{{{0x300cu, 0xa5a5a5a5u}}}; SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image}; std::vector flat; - std::string error; - Check(WalkSrt(fixture.program, runtime, flat, &error) && + Check(WalkSrt(fixture.program, runtime, flat) && flat == std::vector{0xa5a5a5a5u}, - error.c_str()); + "constant-buffer SRT walk failed"); Fixture overflow; const auto overflow_memory = overflow.AddMemory(ResourceKind::ScalarBuffer); @@ -356,9 +350,8 @@ void TestConstantBufferBounds() { {overflow_read, Value(0u), Value(16u), Value(0u)}); overflow.Plan(); flat = {0x55u}; - Check(!WalkSrt(overflow.program, runtime, flat, &error) && - flat == std::vector{0x55u} && - error.find("exceeds size") != std::string::npos, + Check(!WalkSrt(overflow.program, runtime, flat) && + flat == std::vector{0x55u}, "out-of-bounds constant-buffer walk was not transactional"); } @@ -420,10 +413,9 @@ void TestControlFlowValueSurvivesReadLaneFolding() { Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind::Return; const auto undef = fixture.Emit(ValueOpcode::UndefU32); - const auto write = fixture.Emit( - ValueOpcode::WriteLane, {undef, Value(42u), Value(5u)}); - const auto read = - fixture.Emit(ValueOpcode::ReadLane, {write, Value(5u)}); + const auto write = + fixture.Emit(ValueOpcode::WriteLane, {undef, Value(42u), Value(5u)}); + const auto read = fixture.Emit(ValueOpcode::ReadLane, {write, Value(5u)}); entry_info.condition = fixture.Emit(ValueOpcode::IEqual32, {read, Value(42u)}); fixture.Emit(ValueOpcode::Reference, {entry_info.condition}); @@ -435,11 +427,9 @@ void TestControlFlowValueSurvivesReadLaneFolding() { RemoveIdentities(fixture.program.blocks); EliminateDeadCode(fixture.program.blocks); - std::string error; - Check(entry_info.condition == Value(true) && - ValidateProgram(fixture.program, true, &error), - error.empty() ? "folded branch condition did not survive identity removal" - : error.c_str()); + Check(entry_info.condition == Value(true), + "folded branch condition did not survive identity removal"); + ValidateProgram(fixture.program, true); } void TestUndefinedRuntimeValueFails() { @@ -450,11 +440,8 @@ void TestUndefinedRuntimeValueFails() { {.dwords = {undef}, .dword_count = 1}); DescriptorValue result; result.dword_count = 3; - std::string error; - Check(!EvaluateDescriptorSource(fixture.program, 0, 0x200, {}, result, - &error) && - result.dword_count == 3 && - error.find("undefined typed runtime value") != std::string::npos, + Check(!EvaluateDescriptorSource(fixture.program, 0, {}, result) && + result.dword_count == 3, "undefined typed descriptor source did not fail transactionally"); } @@ -464,6 +451,14 @@ namespace Common { int DbgExitIfHandler(const char *, const char *, int) { return 1; } +int DbgExitHandler(const char *, int, std::string_view text) { + throw std::runtime_error(std::string(text)); +} + +int DbgExitHandler(const char *, int, fmt::text_style, std::string_view text) { + throw std::runtime_error(std::string(text)); +} + void DbgExit(int) { std::abort(); } } // namespace Common @@ -496,9 +491,9 @@ int main() { // Keep this focused standalone target self-contained by amalgamating its small // typed-IR implementation set. #include "../src/graphics/shader/recompiler/ir/Block.cpp" +#include "../src/graphics/shader/recompiler/ir/Program.cpp" #include "../src/graphics/shader/recompiler/ir/Type.cpp" #include "../src/graphics/shader/recompiler/ir/Value.cpp" -#include "../src/graphics/shader/recompiler/ir/Program.cpp" #include "../src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp" #include "../src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp" #include "../src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp" diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 795c12e5c..f73e0a2cf 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -85,6 +85,12 @@ #include #include +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS +#include +#include +#include +#endif + #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #ifndef NOMINMAX #define NOMINMAX @@ -907,6 +913,31 @@ void Require(const char *shader_name, const char *stage, bool value, } } +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS +template +void ExpectFatal(const char *name, Function function) { + const pid_t pid = ::fork(); + Require(name, "fatal subprocess", pid >= 0, + "fork failed while starting fatal shader test"); + if (pid == 0) { + function(); + ::_exit(0); + } + + int status = 0; + pid_t waited = -1; + do { + waited = ::waitpid(pid, &status, 0); + } while (waited < 0 && errno == EINTR); + Require(name, "fatal subprocess", waited == pid, + "waitpid failed while collecting fatal shader test"); + Require(name, "fatal exit", + WIFEXITED(status) && WEXITSTATUS(status) == (321 & 0xff), + "fatal shader path did not terminate with status 321"); + std::printf("[host] %-32s ok\n", name); +} +#endif + void CheckLeastRecentlyUsedCacheOrdering() { Common::LeastRecentlyUsedCache cache; const auto first = cache.Insert(1, 1); @@ -979,7 +1010,6 @@ struct TestCase { std::vector> decoded_counts; std::vector> ir_counts; u32 expected_storage_mip_descriptors = 0; - std::string expected_compile_error; }; struct GraphicsCase { @@ -1187,17 +1217,7 @@ CompiledShader CompileCase(const TestCase &test) { options.wave_size = test.compute_info.wave_size; } - ShaderRecompiler::CompileResult result; - std::string error; - if (!ShaderRecompiler::TryRecompile(test.code, options, result, &error)) { - if (!test.expected_compile_error.empty() && - error.find(test.expected_compile_error) != std::string::npos) { - return {}; - } - Fail(test.name, "decode/IR", error.c_str()); - } - Require(test.name, "decode/IR", test.expected_compile_error.empty(), - "shader unexpectedly compiled despite the required rejection"); + auto result = ShaderRecompiler::Recompile(test.code, options); for (const auto &[text, expected] : test.decoded_counts) { const auto actual = CountText(result.decoded_dump, text); Require(test.name, "decoded RDNA2", actual == expected, @@ -1245,9 +1265,7 @@ CompiledShader CompileCase(const TestCase &test) { } result.program.bindings = {}; result.program.binding_layout_complete = false; - if (!ShaderRecompiler::IR::AllocateBindings(result.program, 0, &error)) { - Fail(test.name, "binding layout", error.c_str()); - } + ShaderRecompiler::IR::AllocateBindings(result.program, 0); const auto *shader_data = ShaderRecompiler::IR::FindBinding( result.program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData); @@ -1256,13 +1274,8 @@ CompiledShader CompileCase(const TestCase &test) { result.program.bindings.push_constant_size == 0 && shader_data != nullptr, "oversized shader data did not use its storage fallback"); - std::vector storage_spirv; - if (!ShaderRecompiler::Spirv::EmitProgram(result.program, result.resources, - options.input_info, storage_spirv, - &error)) { - Fail(test.name, "SPIR-V emit", error.c_str()); - } - result.spirv = std::move(storage_spirv); + result.spirv = ShaderRecompiler::Spirv::EmitProgram( + result.program, result.resources, options.input_info); } Require(test.name, "SPIR-V emit", !result.spirv.empty(), "recompiler returned empty SPIR-V"); @@ -1338,12 +1351,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) { options.input_info.pixel = &pixel_info; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - if (!ShaderRecompiler::TryRecompile(test.fragment_code, options, result, - &error)) { - Fail(test.name, "decode/IR", error.c_str()); - } + auto result = ShaderRecompiler::Recompile(test.fragment_code, options); Require(test.name, "SPIR-V emit", !result.spirv.empty(), "recompiler returned empty SPIR-V"); ValidateSpirv(test.name, result.spirv); @@ -7579,10 +7587,7 @@ public: const auto allocate_bindings = [&](ShaderRecompiler::IR::Program &program) { program.shader_info_complete = true; - std::string error; - Require(name, "binding layout", - ShaderRecompiler::IR::AllocateBindings(program, 0, &error), - error.c_str()); + ShaderRecompiler::IR::AllocateBindings(program, 0); }; constexpr auto stencil_format = Prospero::BufferFormat::k8UInt; @@ -7616,11 +7621,7 @@ public: ShaderRecompiler::IR::DescriptorValue null_descriptor{}; null_descriptor.dword_count = 8; null_snapshot.images.assign(3, null_descriptor); - std::string null_error; - Require(name, "null specialization", - ShaderRecompiler::IR::SpecializeResources( - null_program, null_snapshot, &null_error), - null_error.c_str()); + ShaderRecompiler::IR::SpecializeResources(null_program, null_snapshot); allocate_bindings(null_program); ShaderStageRuntime null_runtime{ std::make_shared( @@ -11657,10 +11658,6 @@ void CompareGraphicsWords(const GraphicsCase &test, void RunCase(VulkanHarness *vulkan, const TestCase &test) { auto compiled = CompileCase(test); - if (!test.expected_compile_error.empty()) { - std::printf("[compute] %-32s ok\n", test.name); - return; - } if (test.image_descriptor_swizzle != DstSel(4, 5, 6, 7)) { Require(test.name, "resource specialization", !compiled.program.info.images.empty() && @@ -18506,8 +18503,6 @@ TestCase FlatStoreVariants() { {O::V_MOV_B32, O::FLAT_STORE_BYTE, O::FLAT_STORE_SHORT, O::FLAT_STORE_DWORD, O::FLAT_STORE_DWORDX2, O::FLAT_STORE_DWORDX3, O::FLAT_STORE_DWORDX4, O::S_ENDPGM}}; - test.expected_compile_error = - "writable FLAT/GLOBAL addresses require GPU ownership tracking"; return test; } @@ -20314,9 +20309,7 @@ void CheckIndirectImageKeySwitch() { program.info.samplers.push_back({1u, 0x10f0u}); program.info.sampled_pairs.push_back({0u, 0u, 0x10f0u}); - std::string error; - Require(name, "binding layout", AllocateBindings(program, 0, &error), - error.c_str()); + AllocateBindings(program, 0); ResourceSnapshot snapshot{}; DescriptorValue descriptor{}; descriptor.dword_count = 8; @@ -20340,14 +20333,9 @@ void CheckIndirectImageKeySwitch() { snapshot.samplers.push_back(sampler_descriptor); ShaderComputeInputInfo compute{}; - std::vector spirv; - Require(name, "SPIR-V requirements", - ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program, &error), - error.c_str()); - Require(name, "SPIR-V emit", - ShaderRecompiler::Spirv::EmitProgram( - program, snapshot, {.compute = &compute}, spirv, &error), - error.c_str()); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); + auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, snapshot, + {.compute = &compute}); ValidateSpirv(name, spirv); spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); std::string text; @@ -21314,7 +21302,6 @@ std::vector MakeCases() { AddCase(GlobalSignedImmediateRebasesBeforeSaddr); AddCase(FlatSegmentIgnoresSaddrAndMasksOffsetMsb); AddCase(ScratchIsPrivatePerInvocation); - AddCase(FlatStoreVariants); AddCase(DsReadWriteVariants); AddCase(DsWriteB16D16HiCapturedUsesHighHalf); AddCase(DsReadU16D16CapturedPreservesHighHalf); @@ -21433,7 +21420,7 @@ void CheckPs5GameExampleImageClearRuntimeShape() { compute.thread_ids_num = 1; compute.workgroup_register = 8; - const auto Compile = [&](const char *stage, const std::vector &code) { + const auto Compile = [&](const std::vector &code) { ShaderRecompiler::CompileOptions options; options.stage = ShaderType::Compute; options.wave_size = 64; @@ -21442,17 +21429,13 @@ void CheckPs5GameExampleImageClearRuntimeShape() { options.user_data = user_data.data(); options.input_info.compute = &compute; options.dump_ir = false; - ShaderRecompiler::CompileResult result; - std::string error; - Require("Ps5GameExampleImageClear", stage, - ShaderRecompiler::TryRecompile(code, options, result, &error), - error); + auto result = ShaderRecompiler::Recompile(code, options); ValidateSpirv("Ps5GameExampleImageClear", result.spirv); return result; }; const auto code = MakeCode(); - auto positive = Compile("exact Prospero kernel", code); + auto positive = Compile(code); compute.stage.program = std::make_shared( std::move(positive.program)); @@ -21543,11 +21526,7 @@ void CheckEmbeddedFetchVertexOffset() { options.user_data = user_data.data(); options.input_info.vertex = &vertex; - ShaderRecompiler::CompileResult result; - std::string error; - Require(name, "compile", - ShaderRecompiler::TryRecompile(code, options, result, &error), - error); + auto result = ShaderRecompiler::Recompile(code, options); Require(name, "fetch rewrite", result.program.info.vertex_fetch_components[0] == 4, "encoded fetch sequence was not recognized and rewritten"); @@ -23477,12 +23456,8 @@ void CheckImageSamplerSpecialization() { ShaderRecompiler::IR::DescriptorValue sampler_descriptor{}; sampler_descriptor.dword_count = 4; mixed_sampler_snapshot.samplers.push_back(sampler_descriptor); - std::string specialization_error; - Require("ImageSamplerSpecialization", "mixed sampler specialization", - ShaderRecompiler::IR::SpecializeResources(mixed_sampler_program, - mixed_sampler_snapshot, - &specialization_error), - specialization_error); + ShaderRecompiler::IR::SpecializeResources(mixed_sampler_program, + mixed_sampler_snapshot); Require("ImageSamplerSpecialization", "mixed sampler variant", mixed_sampler_program.info.samplers.size() == 2u && !mixed_sampler_program.info.samplers[0].force_point_filtering && @@ -23493,25 +23468,45 @@ void CheckImageSamplerSpecialization() { "a shared native/bit-packed sampler was not split into point and " "native variants"); - ShaderRecompiler::IR::Program packed_atomic_program; - packed_atomic_program.resource_tracking_complete = true; - auto packed_atomic_image = sampled_image; - packed_atomic_image.kind = - ShaderRecompiler::IR::ResourceKind::StorageImageUint; - packed_atomic_image.written = true; - packed_atomic_image.atomic = true; - packed_atomic_program.info.images.push_back(packed_atomic_image); - ShaderRecompiler::IR::ResourceSnapshot packed_atomic_snapshot; - packed_atomic_snapshot.images.push_back(packed_image_descriptor); - std::string atomic_error; - Require("ImageSamplerSpecialization", "packed atomic rejection", - !ShaderRecompiler::IR::SpecializeResources( - packed_atomic_program, packed_atomic_snapshot, &atomic_error) && - atomic_error.find("unsupported format 34") != std::string::npos, - "a non-atomic PS5 packed format entered the typed atomic path"); std::printf("[host] %-32s ok\n", "ImageSpecializationPipelineId"); } +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS +void CheckShaderRecompilerFatalContracts() { + ExpectFatal("WritableFlatStoreRejection", [] { + const auto test = FlatStoreVariants(); + (void)CompileCase(test); + }); + + ExpectFatal("PackedAtomicImageRejection", [] { + ShaderRecompiler::IR::Program program; + program.resource_tracking_complete = true; + + ShaderRecompiler::IR::ImageResource image; + image.kind = ShaderRecompiler::IR::ResourceKind::StorageImageUint; + image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; + image.read = true; + image.written = true; + image.atomic = true; + program.info.images.push_back(image); + + ShaderRecompiler::IR::DescriptorValue descriptor{}; + descriptor.dword_count = 8; + descriptor.dwords[0] = 0x2000u; + descriptor.dwords[1] = + static_cast(Prospero::BufferFormat::k11_11_10UInt) << 20u; + descriptor.dwords[2] = 3u | (3u << 14u); + descriptor.dwords[3] = + DstSel(4, 5, 6, 7) | + (static_cast(Prospero::ImageType::kColor2D) << 28u); + + ShaderRecompiler::IR::ResourceSnapshot snapshot; + snapshot.images.push_back(descriptor); + ShaderRecompiler::IR::SpecializeResources(program, snapshot); + }); +} +#endif + void CheckStorageTextureVolumeUploadLayout() { constexpr auto format = Prospero::BufferFormat::k16_16_16_16Float; constexpr uint32_t width = 33; @@ -24232,10 +24227,7 @@ void CheckEmbeddedFetchLaneSpill() { options.user_data = user_data.data(); options.input_info.vertex = &vertex; - ShaderRecompiler::CompileResult result; - std::string error; - Require("EmbeddedFetchLaneSpill", "compile", - ShaderRecompiler::TryRecompile(code, options, result, &error), error); + auto result = ShaderRecompiler::Recompile(code, options); Require("EmbeddedFetchLaneSpill", "fetch rewrite", result.program.info.vertex_fetch_components[0] == 4, "lane-spilled fetch descriptor was not recognized and rewritten"); @@ -25041,6 +25033,12 @@ int main(int argc, char **argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); EnsureConfigInitialized(); CheckLeastRecentlyUsedCacheOrdering(); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + if (argc == 2 && std::strcmp(argv[1], "--shader-fatal-only") == 0) { + CheckShaderRecompilerFatalContracts(); + return 0; + } +#endif if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) { CheckClipControlDepthClipState(); return 0; @@ -25301,6 +25299,7 @@ int main(int argc, char **argv) { std::fprintf(stderr, "unknown test selector: %s\n", argv[1]); return 2; } + CheckShaderRecompilerFatalContracts(); VulkanHarness vulkan; #endif CheckClipControlDepthClipState(); diff --git a/tests/ShaderStageRuntimeTests.cpp b/tests/ShaderStageRuntimeTests.cpp index 18aebc25c..f0d8e1e58 100644 --- a/tests/ShaderStageRuntimeTests.cpp +++ b/tests/ShaderStageRuntimeTests.cpp @@ -52,7 +52,7 @@ SrtProgram(uint64_t address) { ValueOpcode::LoadAddressU32, {Value(&handle), Value(0u), Value(0u), Value(true)}); raw.SetFlags(MemoryFlags{.index = 0, .pc = 0x40}); - program.srt_reads.push_back({Value(&raw), 0, 0x40}); + program.srt_reads.push_back({Value(&raw), 0}); auto &srt = value_block.AppendNewInst(ValueOpcode::GetSrtResource); auto &flat = value_block.AppendNewInst(ValueOpcode::ReadConst, @@ -77,17 +77,38 @@ UnbasedFlatProgram() { return std::make_shared(std::move(program)); } +std::shared_ptr +UserDataBufferProgram() { + using namespace Libs::Graphics::ShaderRecompiler::IR; + Program program; + program.stage = Libs::Graphics::ShaderType::Compute; + program.srt_plan_complete = true; + program.resource_tracking_complete = true; + auto &value_block = AddValueBlock(program); + + auto &user_data = value_block.AppendNewInst( + ValueOpcode::GetUserData, {Value(static_cast(0))}); + DescriptorSource source; + source.dwords[0] = Value(&user_data); + source.dwords[1] = Value(0u); + source.dwords[2] = Value(0u); + source.dwords[3] = Value(0u); + source.dword_count = 4; + program.descriptor_sources.push_back(source); + program.info.buffers.push_back({.source = 0}); + return std::make_shared(std::move(program)); +} + void TestMappedSrtUsesDirectReaderByDefault() { using namespace Libs::Graphics; const uint32_t dword = 0x12345678; auto cached_program = SrtProgram(reinterpret_cast(&dword)); ShaderStageRuntime stage; - std::string error; uint32_t specialization_reads = 0; - Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error, + Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, RejectSpecializationRead, &specialization_reads), - error.c_str()); + "mapped SRT stage materialization failed"); Check(specialization_reads == 0, "ordinary SRT read used the specialization reader"); Check(stage.program == cached_program && stage.resources != nullptr, @@ -104,16 +125,36 @@ void TestUnbasedFlatCacheHitMaterializes() { auto prior_resources = std::make_shared(); ShaderStageRuntime stage{prior_program, prior_resources}; - std::string error; - Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error), error.c_str()); + Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage), + "unbased FLAT stage materialization failed"); Check(stage.program == cached_program && stage.resources != prior_resources, "unbased FLAT cache hit did not publish its BDA stage snapshot"); } +void TestFailedMaterializationPreservesPriorStage() { + using namespace Libs::Graphics; + auto cached_program = UserDataBufferProgram(); + auto prior_program = std::make_shared(); + auto prior_resources = + std::make_shared(); + ShaderStageRuntime stage{prior_program, prior_resources}; + + Check(!ShaderMaterializeStageRuntime(cached_program, {}, 0, stage), + "missing runtime user data did not reject the cached stage"); + Check(stage.program == prior_program && stage.resources == prior_resources, + "failed cache materialization replaced the prior stage"); +} + } // namespace namespace Common { +int DbgExitHandler(const char *, int, std::string_view) { std::abort(); } + +int DbgExitHandler(const char *, int, fmt::text_style, std::string_view) { + std::abort(); +} + int DbgExitIfHandler(const char *, const char *, int) { return 1; } void DbgExit(int) { std::abort(); } @@ -123,6 +164,7 @@ void DbgExit(int) { std::abort(); } int main() { TestMappedSrtUsesDirectReaderByDefault(); TestUnbasedFlatCacheHitMaterializes(); + TestFailedMaterializationPreservesPriorStage(); std::puts("ShaderStageRuntimeTests: all cases passed"); return 0; } @@ -130,7 +172,7 @@ int main() { // Keep this focused standalone target self-contained by amalgamating its small // typed-IR implementation set. #include "graphics/shader/recompiler/ir/Block.cpp" +#include "graphics/shader/recompiler/ir/Program.cpp" #include "graphics/shader/recompiler/ir/Type.cpp" #include "graphics/shader/recompiler/ir/Value.cpp" -#include "graphics/shader/recompiler/ir/Program.cpp" #include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.cpp" diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index fb1a06934..71485c636 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -44,6 +44,12 @@ #include #include +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS +#include +#include +#include +#endif + namespace Libs::Graphics { namespace { @@ -54,6 +60,26 @@ void Check(bool value, const char *text) { } } +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS +template +void ExpectFatal(Function function, const char *text) { + const pid_t pid = ::fork(); + Check(pid >= 0, "fork failed while starting fatal shader test"); + if (pid == 0) { + function(); + ::_exit(0); + } + + int status = 0; + pid_t waited = -1; + do { + waited = ::waitpid(pid, &status, 0); + } while (waited < 0 && errno == EINTR); + Check(waited == pid, "waitpid failed while collecting fatal shader test"); + Check(WIFEXITED(status) && WEXITSTATUS(status) == (321 & 0xff), text); +} +#endif + ShaderRecompiler::CompileOptions MakeCompileOptions(ShaderType stage) { static const ShaderVertexInputInfo vertex{}; static const ShaderPixelInputInfo pixel{}; @@ -85,9 +111,8 @@ bool ReadHostTestMemory(void *, uint64_t address, uint32_t *value) { return true; } -bool CompilePixelRuntime(const ShaderParams ¶ms, - ShaderPixelInputInfo &input_info, - std::string *error) { +void CompilePixelRuntime(const ShaderParams ¶ms, + ShaderPixelInputInfo &input_info) { auto options = MakeCompileOptions(ShaderType::Pixel); options.shader_hash = params.hash; options.shader_base = params.Base(); @@ -97,17 +122,13 @@ bool CompilePixelRuntime(const ShaderParams ¶ms, options.push_constant_offset = input_info.push_constant_offset; options.read_specialization_memory = ReadHostTestMemory; options.input_info.pixel = &input_info; - ShaderRecompiler::CompileResult result; - if (!ShaderRecompiler::TryRecompile(params.code, options, result, error)) { - return false; - } + auto result = ShaderRecompiler::Recompile(params.code, options); input_info.stage.program = std::make_shared( std::move(result.program)); input_info.stage.resources = std::make_shared( std::move(result.resources)); - return true; } template @@ -1170,10 +1191,7 @@ void TestSpirvRequirementsAnalysis() { block->AppendNewInst(ValueOpcode::SetAttribute, {Value(0u), Value(true)}); export_value.SetFlags(ExportFlags{.index = 0}); - std::string analysis_error; - Check(ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program, - &analysis_error), - "initial SPIR-V requirements analysis failed"); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); const auto requirements = *program.spirv_requirements; Check(requirements.subgroup_ballot && requirements.subgroup_shuffle && requirements.subgroup_local_invocation_id && @@ -1183,31 +1201,28 @@ void TestSpirvRequirementsAnalysis() { "consolidated SPIR-V requirements missed an IR dependency"); program.stage = ShaderType::Compute; - Check(ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program, - &analysis_error), - "compute SPIR-V requirements analysis failed"); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); const auto compute_requirements = *program.spirv_requirements; Check(!compute_requirements.function_lds && !compute_requirements.pixel_valid_mask, "stage-specific SPIR-V requirements leaked into compute"); - program.stage = ShaderType::Pixel; - shared.SetFlags(MemoryFlags{.index = 1}); - analysis_error.clear(); - Check(!ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program, - &analysis_error) && - !program.spirv_requirements.has_value() && - Common::ContainsStr(analysis_error, "invalid memory metadata"), - "invalid shared-memory requirement metadata was accepted"); - - shared.SetFlags(MemoryFlags{.index = 0}); - export_value.SetFlags(ExportFlags{.index = 1}); - analysis_error.clear(); - Check(!ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program, - &analysis_error) && - !program.spirv_requirements.has_value() && - Common::ContainsStr(analysis_error, "invalid metadata"), - "invalid export requirement metadata was accepted"); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + ExpectFatal( + [&] { + program.stage = ShaderType::Pixel; + shared.SetFlags(MemoryFlags{.index = 1}); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); + }, + "invalid shared-memory requirement metadata did not terminate analysis"); + ExpectFatal( + [&] { + program.stage = ShaderType::Pixel; + export_value.SetFlags(ExportFlags{.index = 1}); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); + }, + "invalid export requirement metadata did not terminate analysis"); +#endif Program add_tid; add_tid.stage = ShaderType::Compute; @@ -1225,35 +1240,30 @@ void TestSpirvRequirementsAnalysis() { ValueOpcode::LoadBufferU32, {Value(&buffer), Value(0u), Value(0u), Value(0u), Value(true)}); load.SetFlags(MemoryFlags{.index = 0}); - Check(ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid, - &analysis_error), - "ADD_TID SPIR-V requirements analysis failed"); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid); const auto add_tid_requirements = *add_tid.spirv_requirements; Check(add_tid_requirements.subgroup_local_invocation_id && !add_tid_requirements.subgroup_ballot && !add_tid_requirements.subgroup_shuffle, "buffer ADD_TID requested the wrong subgroup contract"); - add_tid.memory_info[0].resource = 1; - analysis_error.clear(); - Check(!ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid, - &analysis_error) && - !add_tid.spirv_requirements.has_value() && - Common::ContainsStr(analysis_error, "invalid resource metadata"), - "invalid buffer requirement metadata was accepted"); - add_tid.memory_info[0].resource = 0; - - add_tid.stage = ShaderType::Vertex; - analysis_error.clear(); - Check(!ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid, - &analysis_error) && - Common::ContainsStr(analysis_error, "only valid for compute"), - "graphics buffer ADD_TID was accepted without an exact guest lane ID"); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + ExpectFatal( + [&] { + add_tid.memory_info[0].resource = 1; + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid); + }, + "invalid buffer requirement metadata did not terminate analysis"); + ExpectFatal( + [&] { + add_tid.stage = ShaderType::Vertex; + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(add_tid); + }, + "graphics buffer ADD_TID did not terminate requirements analysis"); +#endif Program empty; - Check(ShaderRecompiler::Spirv::AnalyzeProgramRequirements(empty, - &analysis_error), - "empty SPIR-V requirements analysis failed"); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(empty); const auto empty_requirements = *empty.spirv_requirements; Check(!empty_requirements.subgroup_ballot && !empty_requirements.subgroup_shuffle && @@ -1520,10 +1530,7 @@ void TestNewShaderRecompilerSMovB32() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.spirv.empty(), "new shader recompiler produced no SPIR-V"); Check(result.spirv.front() == 0x07230203u, "new shader recompiler did not emit SPIR-V binary"); @@ -1564,10 +1571,7 @@ void TestNewShaderRecompilerSoppMarkers() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_nop 0x00000003"), "new decoder did not decode SOPP s_nop"); Check(Common::ContainsStr(result.decoded_dump, "s_waitcnt 0x00000000"), @@ -1613,10 +1617,7 @@ void TestNewShaderRecompilerSopkWaitcntMarkers() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_waitcnt 0"), "new decoder did not decode SOPK waitcnt marker"); Check(Common::ContainsStr(result.decoded_dump, "s_waitcnt 65535"), @@ -1645,10 +1646,7 @@ void TestNewShaderRecompilerRdna2ScalarOpcodes() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_bitset1_b32 vcc_lo, 0"), "new decoder did not decode RDNA2 S_BITSET1_B32"); Check(Common::ContainsStr(result.decoded_dump, @@ -1718,10 +1716,7 @@ void TestNewShaderRecompilerScalarVectorAlu() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_add_u32 s2, s0, s1"), "new decoder did not decode SOP2 add"); Check(Common::ContainsStr(result.decoded_dump, "s_addc_u32 s14, s13, 1"), @@ -1823,10 +1818,7 @@ void TestNewShaderRecompilerVop3LaneReadDestinationEncoding() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "v_readfirstlane_b32 s25, v5"), "VOP3 V_READFIRSTLANE_B32 destination was not decoded from VDST"); Check(Common::ContainsStr(result.decoded_dump, "v_readlane_b32 s26, v5, 2"), @@ -2101,10 +2093,7 @@ void TestNewShaderRecompilerMoreAluFamilies() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_movk_i32 s9"), "new decoder did not decode SOPK mov"); Check(Common::ContainsStr(result.decoded_dump, "s_cmp_gt_u32"), @@ -2949,10 +2938,7 @@ void TestNewShaderRecompilerExpandedAluBatch() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_min_u32"), "new decoder did not decode S_MIN_U32"); Check(Common::ContainsStr(result.decoded_dump, "s_mulk_i32"), @@ -3034,10 +3020,7 @@ void TestNewShaderRecompilerVop3pPackedF16() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "v_pk_add_f16 v114"), "new decoder did not decode old-backed V_PK_ADD_F16"); Check(Common::ContainsStr(result.decoded_dump, "v_pk_mul_f16 v115"), @@ -3161,10 +3144,7 @@ void TestNewShaderRecompilerStagedShaderOps() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_subb_u32 s2, s0, s1"), "new decoder did not decode RDNA2 S_SUBB_U32"); Check(Common::ContainsStr(result.decoded_dump, "s_bitset0_b32 s3, s1"), @@ -3243,10 +3223,7 @@ void TestNewShaderRecompilerBootF16UnaryOpcodes() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "v_sqrt_f16 v4.sdwa(sel=5"), "new decoder did not decode V_SQRT_F16 SDWA high-half destination"); Check(Common::ContainsStr(result.decoded_dump, "v_rndne_f16 v3"), @@ -3315,10 +3292,7 @@ void TestNewShaderRecompilerCapturedVop1SdwaByteConvert() { options.dump_ir = true; options.input_info.pixel = &ps_info; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "V_CVT_F32_UBYTE0 v4, v9.sdwa(sel=4,sext=0)"), "captured V_CVT_F32_UBYTE0 SDWA instruction was not decoded"); @@ -3392,10 +3366,7 @@ void TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "v_lshrrev_b16 v1.sdwa(sel=4"), "new decoder did not decode low-half V_LSHRREV_B16"); Check(Common::ContainsStr(result.decoded_dump, "v_lshlrev_b16 v5.sdwa(sel=5"), @@ -3552,10 +3523,7 @@ void TestNewShaderRecompilerScalarB64Alu() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_mov_b64 s2, s0"), "new decoder did not decode old-backed S_MOV_B64"); Check(Common::ContainsStr(result.decoded_dump, "s_not_b32 s41, s0"), @@ -3731,10 +3699,7 @@ void TestNewShaderRecompilerScalarB64LaneTranslation() { options.user_data_count = static_cast(std::size(user_data)); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); uint32_t componentwise_u64_values = 0; for (const auto *block : result.program.blocks) { for (const auto &value : *block) { @@ -3773,10 +3738,7 @@ void TestNewShaderRecompilerSignedCompareAlu() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_cmp_gt_i32"), "new decoder did not decode SOPC signed compare"); Check(Common::ContainsStr(result.decoded_dump, "s_cmp_lt_i32"), @@ -3832,10 +3794,7 @@ void TestNewShaderRecompilerSignedMinShiftAlu() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_min_i32"), "new decoder did not decode S_MIN_I32"); Check(Common::ContainsStr(result.decoded_dump, "s_ashr_i32"), @@ -3889,10 +3848,7 @@ void TestNewShaderRecompilerScalarBitfieldAlu() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_cselect_b32"), "new decoder did not decode S_CSELECT_B32"); Check(Common::ContainsStr(result.decoded_dump, "s_abs_i32"), @@ -3953,10 +3909,8 @@ void TestNewShaderRecompilerScalarBitfieldAlu() { void CheckNewDecoderUnsupported(const uint32_t *shader, uint32_t words, const char *family, const char *opcode_name) { ShaderRecompiler::Decoder::Program program; - std::string error; const std::span code{shader, words}; - Check(ShaderRecompiler::Decoder::DecodeProgram(code, program, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(code, program); Check(program.instructions.size() >= 2, "decoder did not return instruction plus endpgm"); const auto text = ShaderRecompiler::Decoder::ProgramToString(program); @@ -3965,14 +3919,12 @@ void CheckNewDecoderUnsupported(const uint32_t *shader, uint32_t words, Check(Common::ContainsStr(text, opcode_name), "decoder unsupported text did not include opcode name"); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(!ShaderRecompiler::TryRecompile(code, options, result, &error), - "unsupported opcode unexpectedly translated without implementation"); - Check(Common::ContainsStr(error, "no IR translation") || - Common::ContainsStr(error, "unsupported decoded"), - "unsupported translation error was not explicit"); + ExpectFatal([&] { (void)ShaderRecompiler::Recompile(code, options); }, + "unsupported opcode did not terminate shader compilation"); +#endif } void TestNewShaderDecoderArchitecture() { @@ -3987,8 +3939,7 @@ void TestNewShaderDecoderArchitecture() { const uint32_t offset_code[] = {0u, EncodeVop1(0x01, 2, 3)}; Instruction direct; - std::string error; - Check(DecodeInstruction(offset_code, 1u, direct, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(offset_code, 1u, direct); Check(direct.pc == 4u && direct.family == Family::VOP1 && direct.opcode == Opcode::V_MOV_B32 && direct.dst.reg == 2u && direct.src0.reg == 3u, @@ -3996,10 +3947,10 @@ void TestNewShaderDecoderArchitecture() { const uint32_t program_code[] = {EncodeVop1(0x01, 2, 3), EncodeSopp(0x01, 0)}; Instruction program_direct; - Check(DecodeInstruction(program_code, 0u, program_direct, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(program_code, 0u, + program_direct); Program program; - Check(DecodeProgram(program_code, program, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(program_code, program); Check(program.instructions.size() == 2u && program.instructions.front().family == program_direct.family && program.instructions.front().opcode == program_direct.opcode && @@ -4009,8 +3960,7 @@ void TestNewShaderDecoderArchitecture() { const uint32_t cmp_eq_u64_code[] = {0xd4e2006au, 0x0000d47eu}; Instruction cmp_eq_u64; - Check(DecodeInstruction(cmp_eq_u64_code, 0u, cmp_eq_u64, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(cmp_eq_u64_code, 0u, cmp_eq_u64); Check(cmp_eq_u64.family == Family::VOP3 && cmp_eq_u64.opcode == Opcode::V_CMP_EQ_U64 && cmp_eq_u64.dst.kind == OperandKind::VccLo && @@ -4021,8 +3971,7 @@ void TestNewShaderDecoderArchitecture() { const uint32_t ffbh_i32_code[] = {0x7e1c770eu}; Instruction ffbh_i32; - Check(DecodeInstruction(ffbh_i32_code, 0u, ffbh_i32, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(ffbh_i32_code, 0u, ffbh_i32); Check(ffbh_i32.family == Family::VOP1 && ffbh_i32.opcode == Opcode::V_FFBH_I32 && ffbh_i32.dst.kind == OperandKind::Vgpr && ffbh_i32.dst.reg == 14u && @@ -4033,7 +3982,7 @@ void TestNewShaderDecoderArchitecture() { const uint32_t cos_f16_code[] = {0x7e08c2f9u, 0x00051504u}; Instruction cos_f16; - Check(DecodeInstruction(cos_f16_code, 0u, cos_f16, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(cos_f16_code, 0u, cos_f16); Check(cos_f16.family == Family::VOP1 && cos_f16.opcode == Opcode::V_COS_F16 && cos_f16.word_count == 2u && cos_f16.src_count == 1u && @@ -4049,7 +3998,7 @@ void TestNewShaderDecoderArchitecture() { const uint32_t sin_f16_code[] = {0x7e08c0f9u, 0x00051504u}; Instruction sin_f16; - Check(DecodeInstruction(sin_f16_code, 0u, sin_f16, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(sin_f16_code, 0u, sin_f16); Check(sin_f16.family == Family::VOP1 && sin_f16.opcode == Opcode::V_SIN_F16 && sin_f16.word_count == 2u && sin_f16.src_count == 1u && @@ -4065,7 +4014,7 @@ void TestNewShaderDecoderArchitecture() { const uint32_t literal_code[] = {EncodeVop1(0x01, 2, 255u), 0x12345678u}; Instruction literal; - Check(DecodeInstruction(literal_code, 0u, literal, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(literal_code, 0u, literal); Check(literal.word_count == 2u && literal.src0.value == 0x12345678u, "single-instruction decoder lost a compact literal extension"); @@ -4074,8 +4023,8 @@ void TestNewShaderDecoderArchitecture() { 0x0004001cu, }; Instruction signed_bfe; - Check(DecodeInstruction(scalar_signed_bfe, 0u, signed_bfe, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(scalar_signed_bfe, 0u, + signed_bfe); Check(signed_bfe.family == Family::SOP2 && signed_bfe.opcode == Opcode::S_BFE_I32 && signed_bfe.word_count == 2u && @@ -4088,8 +4037,8 @@ void TestNewShaderDecoderArchitecture() { 0x0686156au, // v_add_nc_u32 v4.word1, vcc_lo, v97; preserve word0 }; Instruction sdwa_add; - Check(DecodeInstruction(vop2_sdwa_partial_dst, 0u, sdwa_add, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(vop2_sdwa_partial_dst, 0u, + sdwa_add); Check(sdwa_add.family == Family::VOP2 && sdwa_add.opcode == Opcode::V_ADD_NC_U32 && sdwa_add.word_count == 2u && sdwa_add.dst.reg == 4u && @@ -4104,8 +4053,8 @@ void TestNewShaderDecoderArchitecture() { 0x86000613u, // v_lshrrev_b32 v4, v19.byte0, 8 }; Instruction byte_shift; - Check(DecodeInstruction(vop2_sdwa_byte_shift, 0u, byte_shift, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(vop2_sdwa_byte_shift, 0u, + byte_shift); Check(byte_shift.family == Family::VOP2 && byte_shift.opcode == Opcode::V_LSHRREV_B32 && byte_shift.word_count == 2u && byte_shift.dst.reg == 4u && @@ -4120,9 +4069,8 @@ void TestNewShaderDecoderArchitecture() { 0x86030607u, // v_lshlrev_b32 v6, v7.byte3, 1 }; Instruction byte_left_shift; - Check(DecodeInstruction(vop2_sdwa_byte_left_shift, 0u, byte_left_shift, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(vop2_sdwa_byte_left_shift, 0u, + byte_left_shift); Check(byte_left_shift.family == Family::VOP2 && byte_left_shift.opcode == Opcode::V_LSHLREV_B32 && byte_left_shift.word_count == 2u && byte_left_shift.dst.reg == 6u && @@ -4137,7 +4085,7 @@ void TestNewShaderDecoderArchitecture() { EncodeMimg1(4, 0, 1, 8), 0x03020100u, 0x07060504u, 0x0b0a0908u}; Instruction image; - Check(DecodeInstruction(mimg_nsa, 0u, image, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(mimg_nsa, 0u, image); Check(image.family == Family::MIMG && image.word_count == 5u && image.image_nsa_dwords == 3u, "single-instruction decoder lost the MIMG NSA length"); @@ -4145,19 +4093,19 @@ void TestNewShaderDecoderArchitecture() { const uint32_t ds_code[] = {EncodeDs0(0x36) | (1u << 17u), EncodeDs1(2, 0, 1)}; Instruction ds; - Check(DecodeInstruction(ds_code, 0u, ds, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(ds_code, 0u, ds); Check(ds.opcode == Opcode::DS_READ_B32 && ds.gds, "DS decoder lost the GFX10 opcode or GDS fields"); const uint32_t boot_ds[] = {0xd8d4c480u, 0x45000045u}; Instruction boot; - Check(DecodeInstruction(boot_ds, 0u, boot, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(boot_ds, 0u, boot); Check(boot.opcode == Opcode::DS_SWIZZLE_B32 && boot.offset == 0xc480u, "DS decoder rejected a captured boot-shader instruction"); const uint32_t bpermute_ds[] = {0xdacc0000u, 0x01000311u}; Instruction bpermute; - Check(DecodeInstruction(bpermute_ds, 0u, bpermute, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(bpermute_ds, 0u, bpermute); Check(bpermute.family == Family::DS && bpermute.opcode == Opcode::DS_BPERMUTE_B32 && bpermute.word_count == 2u && bpermute.src_count == 2u && @@ -4169,7 +4117,7 @@ void TestNewShaderDecoderArchitecture() { const uint32_t d16_hi_ds[] = {0xda9c0000u, 0x05000006u}; Instruction d16_hi; - Check(DecodeInstruction(d16_hi_ds, 0u, d16_hi, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(d16_hi_ds, 0u, d16_hi); Check(d16_hi.opcode == Opcode::DS_READ_U16_D16_HI && d16_hi.src_count == 1u && d16_hi.data_bits == 16u && d16_hi.dst.reg == 5u && d16_hi.dst.sdwa_sel == 5u && @@ -4178,8 +4126,8 @@ void TestNewShaderDecoderArchitecture() { const uint32_t d16_hi_write_ds[] = {0xda840000u, 0x00000214u}; Instruction d16_hi_write; - Check(DecodeInstruction(d16_hi_write_ds, 0u, d16_hi_write, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(d16_hi_write_ds, 0u, + d16_hi_write); Check(d16_hi_write.family == Family::DS && d16_hi_write.opcode == Opcode::DS_WRITE_B16_D16_HI && d16_hi_write.word_count == 2u && d16_hi_write.src_count == 2u && @@ -4197,7 +4145,7 @@ void TestNewShaderDecoderArchitecture() { for (uint32_t source = 0; source < 3u; source++) { const auto &packed = packed_source_selectors[source]; Instruction packed_inst; - Check(DecodeInstruction(packed, 0u, packed_inst, &error), error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(packed, 0u, packed_inst); Check(packed_inst.src0.op_sel_hi == (source == 0u) && packed_inst.src1.op_sel_hi == (source == 1u) && packed_inst.src2.op_sel_hi == (source == 2u), @@ -4206,8 +4154,8 @@ void TestNewShaderDecoderArchitecture() { const uint32_t packed_fmac_dpp[] = {0x780402fau, 0xff500000u}; Instruction packed_fmac; - Check(DecodeInstruction(packed_fmac_dpp, 0u, packed_fmac, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(packed_fmac_dpp, 0u, + packed_fmac); Check(packed_fmac.opcode == Opcode::V_PK_FMAC_F16 && packed_fmac.src0.dpp && packed_fmac.src0.op_sel_hi && packed_fmac.src1.op_sel_hi && packed_fmac.dst.op_sel_hi && @@ -4228,9 +4176,7 @@ void TestNewShaderRecompilerRejectsDppOn64BitCompares() { }; ShaderRecompiler::Decoder::Program program; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(shader, program, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(shader, program); Check(program.instructions.size() == 2u, "64-bit VOPC DPP decode did not consume its modifier word"); const auto &compare = program.instructions.front(); @@ -4251,9 +4197,7 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() { }; Decoder::Instruction decoded; - std::string error; - Check(Decoder::DecodeInstruction(shader, 0u, decoded, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(shader, 0u, decoded); Check(decoded.family == Decoder::Family::VOPC && decoded.opcode == Decoder::Opcode::V_CMPX_CLASS_F32 && decoded.opcode_id == 0x98u && decoded.word_count == 2u && @@ -4274,11 +4218,10 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() { translate_options.stage = ShaderType::Pixel; translate_options.wave_size = 64u; translate_options.pixel = &pixel; - Check(Decoder::DecodeProgram(shader, program, &error) && - CFG::BuildGraph(program, graph, &error) && - Frontend::TranslateProgram(program, graph, translate_options, ir, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(shader, program); + graph = ShaderRecompiler::CFG::BuildGraph(program); + ir = ShaderRecompiler::Frontend::TranslateProgram(program, graph, + translate_options); uint32_t class_compares = 0u; uint32_t dynamic_exec_writes = 0u; for (const auto *block : ir.blocks) { @@ -4295,8 +4238,7 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() { "captured V_CMPX_CLASS_F32 did not lower to class compare plus EXEC update"); auto options = MakeCompileOptions(ShaderType::Pixel); - CompileResult result; - Check(TryRecompile(shader, options, result, &error), error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "V_CMPX_CLASS_F32 exec_lo, v13, vcc_lo"), "captured SDWA V_CMPX_CLASS_F32 was not present in the decoded dump"); @@ -4304,114 +4246,92 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() { } void TestNewShaderRecompilerIrLookupMissFailsExplicitly() { - ShaderRecompiler::Decoder::Program decoded; - ShaderRecompiler::Decoder::Instruction missing; - missing.pc = 0u; - missing.family = ShaderRecompiler::Decoder::Family::VOP1; - missing.opcode = ShaderRecompiler::Decoder::Opcode::UNKNOWN; - decoded.instructions.push_back(missing); + namespace Decoder = ShaderRecompiler::Decoder; + namespace CFG = ShaderRecompiler::CFG; + namespace Frontend = ShaderRecompiler::Frontend; + namespace IR = ShaderRecompiler::IR; - ShaderRecompiler::CFG::Graph cfg; - ShaderRecompiler::CFG::BasicBlock block; + Decoder::Program decoded; + decoded.instructions.emplace_back(); + auto &store = decoded.instructions.front(); + + CFG::Graph cfg; + CFG::BasicBlock block; block.inst_end = 1u; cfg.blocks.push_back(block); cfg.entry_block = 0u; - ShaderRecompiler::IR::Program ir; ShaderComputeInputInfo compute{}; - ShaderRecompiler::Frontend::TranslateOptions options{}; + Frontend::TranslateOptions options{}; options.stage = ShaderType::Compute; options.wave_size = 64u; options.compute = &compute; - std::string error; - Check(!ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options, ir, - &error), - "missing decoder-to-IR mapping unexpectedly translated as an instruction"); - Check(Common::ContainsStr(error, "no IR translation"), - "missing decoder-to-IR mapping did not report an explicit error"); - Check(ir.blocks.empty(), - "missing decoder-to-IR mapping emitted a fallback IR block"); - ir.block_storage.push_back(std::make_unique()); - ir.blocks.push_back(ir.block_storage.back().get()); - ir.block_info.emplace_back(); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + store.pc = 0u; + store.family = Decoder::Family::VOP1; + store.opcode = Decoder::Opcode::UNKNOWN; + ExpectFatal( + [&] { (void)Frontend::TranslateProgram(decoded, cfg, options); }, + "missing decoder-to-IR mapping did not terminate compilation"); + options.wave_size = 16u; - error.clear(); - Check(!ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options, ir, - &error), - "invalid translation options unexpectedly succeeded"); - Check(ir.blocks.empty() && ir.block_storage.empty() && ir.block_info.empty(), - "early translation failure retained a stale output program"); + ExpectFatal([&] { (void)Frontend::TranslateProgram(decoded, cfg, options); }, + "invalid translation options did not terminate compilation"); options.wave_size = 64u; - auto &store = decoded.instructions.front(); store = {}; - store.family = ShaderRecompiler::Decoder::Family::MUBUF; - store.opcode = ShaderRecompiler::Decoder::Opcode::UNSUPPORTED; + store.family = Decoder::Family::MUBUF; + store.opcode = Decoder::Opcode::UNSUPPORTED; store.unsupported_reason = "focused unsupported MUBUF diagnostic"; - error.clear(); - Check(!ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options, ir, - &error), - "unsupported memory instruction unexpectedly translated"); - Check(Common::ContainsStr(error, "focused unsupported MUBUF diagnostic"), - "unsupported memory instruction lost its decoder diagnostic"); + ExpectFatal( + [&] { (void)Frontend::TranslateProgram(decoded, cfg, options); }, + "unsupported memory instruction did not terminate compilation"); store = {}; - store.family = ShaderRecompiler::Decoder::Family::VOP1; - store.opcode = ShaderRecompiler::Decoder::Opcode::V_MOVRELS_B32; - store.dst.kind = ShaderRecompiler::Decoder::OperandKind::Sgpr; - store.src0.kind = ShaderRecompiler::Decoder::OperandKind::Vgpr; + store.family = Decoder::Family::VOP1; + store.opcode = Decoder::Opcode::V_MOVRELS_B32; + store.dst.kind = Decoder::OperandKind::Sgpr; + store.src0.kind = Decoder::OperandKind::Vgpr; store.src_count = 1u; - error.clear(); - Check(!ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options, ir, - &error), - "invalid V_MOVRELS_B32 operands unexpectedly translated"); - Check(Common::ContainsStr(error, - "requires VGPR source and destination"), - "invalid V_MOVRELS_B32 operands did not fail recoverably"); + ExpectFatal([&] { (void)Frontend::TranslateProgram(decoded, cfg, options); }, + "invalid V_MOVRELS_B32 operands did not terminate compilation"); +#endif store = {}; - store.family = ShaderRecompiler::Decoder::Family::SOP1; - store.opcode = ShaderRecompiler::Decoder::Opcode::S_GETPC_B64; - store.dst.kind = ShaderRecompiler::Decoder::OperandKind::Sgpr; + store.family = Decoder::Family::SOP1; + store.opcode = Decoder::Opcode::S_GETPC_B64; + store.dst.kind = Decoder::OperandKind::Sgpr; store.dst.reg = 105u; - error.clear(); - Check(ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options, ir, - &error), - error.c_str()); + auto ir = Frontend::TranslateProgram(decoded, cfg, options); bool getpc_wrote_vcc_lo = false; for (const auto *typed_block : ir.blocks) { for (const auto &typed_inst : *typed_block) { getpc_wrote_vcc_lo |= - typed_inst.GetOpcode() == - ShaderRecompiler::IR::ValueOpcode::SetVccLo; + typed_inst.GetOpcode() == IR::ValueOpcode::SetVccLo; } } Check(getpc_wrote_vcc_lo, "S_GETPC_B64 did not advance s105 through the VCC alias"); - const ShaderRecompiler::Decoder::OperandKind canonical_zero_masks[] = { - ShaderRecompiler::Decoder::OperandKind::Null, - ShaderRecompiler::Decoder::OperandKind::PopsExitingWaveId, - ShaderRecompiler::Decoder::OperandKind::VccZ, - ShaderRecompiler::Decoder::OperandKind::ExecZ, + const Decoder::OperandKind canonical_zero_masks[] = { + Decoder::OperandKind::Null, + Decoder::OperandKind::PopsExitingWaveId, + Decoder::OperandKind::VccZ, + Decoder::OperandKind::ExecZ, }; for (const auto kind : canonical_zero_masks) { store = {}; - store.family = ShaderRecompiler::Decoder::Family::SOP1; - store.opcode = ShaderRecompiler::Decoder::Opcode::S_NOT_B64; - store.dst.kind = ShaderRecompiler::Decoder::OperandKind::Sgpr; + store.family = Decoder::Family::SOP1; + store.opcode = Decoder::Opcode::S_NOT_B64; + store.dst.kind = Decoder::OperandKind::Sgpr; store.src0.kind = kind; store.src_count = 1u; - error.clear(); - Check(ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options, ir, - &error), - error.c_str()); + ir = Frontend::TranslateProgram(decoded, cfg, options); bool preserved_mask_tag = false; for (const auto *typed_block : ir.blocks) { for (const auto &typed_inst : *typed_block) { - if (typed_inst.GetOpcode() != - ShaderRecompiler::IR::ValueOpcode::SetScalarMaskTag) { + if (typed_inst.GetOpcode() != IR::ValueOpcode::SetScalarMaskTag) { continue; } const auto valid = typed_inst.Arg(1).Resolve(); @@ -4452,11 +4372,7 @@ void TestNewShaderRecompilerMemoryFamilyTranslation() { options.user_data = user_data.data(); options.read_memory = ReadZeroTestMemory; - ShaderRecompiler::CompileResult result; - std::string error; - const auto compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_load_dword"), "new decoder did not decode SMEM dword load"); Check(Common::ContainsStr(result.decoded_dump, "s_buffer_load_dword"), @@ -4550,10 +4466,7 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() { raw_options.user_data = raw_user_data.data(); raw_options.user_data_count = static_cast(raw_user_data.size()); - ShaderRecompiler::CompileResult raw; - std::string error; - Check(ShaderRecompiler::TryRecompile(raw_shader, raw_options, raw, &error), - error.c_str()); + auto raw = ShaderRecompiler::Recompile(raw_shader, raw_options); const auto *address_binding = ShaderRecompiler::IR::FindBinding( raw.program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable); @@ -4599,11 +4512,7 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() { buffer_options.user_data_count = static_cast(buffer_user_data.size()); - ShaderRecompiler::CompileResult buffer; - error.clear(); - Check(ShaderRecompiler::TryRecompile(buffer_shader, buffer_options, buffer, - &error), - error.c_str()); + auto buffer = ShaderRecompiler::Recompile(buffer_shader, buffer_options); const auto *buffer_binding = ShaderRecompiler::IR::FindBinding( buffer.program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Buffers); @@ -4641,10 +4550,7 @@ void TestNewShaderRecompilerImageQueryTranslation() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_get_lod"), "new decoder did not decode MIMG image get-lod query"); Check(Common::ContainsStr(result.decoded_dump, "dmask=0x3"), @@ -4688,10 +4594,7 @@ void TestNewShaderRecompilerCubeSampleCoordinates() { auto options = MakeCompileOptions(ShaderType::Compute); options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(result.program.info.images.size() == 1 && result.program.info.images[0].cube, "cube descriptor identity was not preserved through compilation"); @@ -4737,10 +4640,7 @@ void TestNewShaderRecompilerImageSampleVariants() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_sample_l"), "new decoder did not decode IMAGE_SAMPLE_L through shared MIMG path"); Check(Common::ContainsStr(result.decoded_dump, "image_sample_b"), @@ -4828,11 +4728,7 @@ void TestNewShaderRecompilerImageSampleA16SamplerCoords() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - const auto compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_dim=3d sample_flags=a16 addr_components=3"), "3D IMAGE_SAMPLE with MIMG A16 bit did not decode as three A16 sampler " @@ -4874,10 +4770,7 @@ void TestNewShaderRecompilerImageSampleOpcodeAliases() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_sample_a"), "MIMG opcode 0xa0 should decode as image_sample_a alias"); Check(Common::ContainsStr(result.decoded_dump, "image_sample_b_a"), @@ -4913,10 +4806,7 @@ void TestNewShaderRecompilerImageSampleA16ExceptionComponents() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); return result; }; @@ -4962,10 +4852,7 @@ void TestNewShaderRecompilerImageLoadA16UintCoords() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_load"), "A16 IMAGE_LOAD did not decode"); Check(Common::ContainsStr(result.ir_dump, "ImageLoad v20"), @@ -5009,10 +4896,7 @@ void TestNewShaderRecompilerPixelImageSampleLodSelection() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); return result; }; @@ -5123,11 +5007,7 @@ void TestNewShaderRecompilerImageViewDimensions() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - const auto compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_dim=2d_array"), "MIMG DIM did not decode 2D-array image view"); Check(Common::ContainsStr(result.decoded_dump, "image_dim=3d"), @@ -5204,10 +5084,7 @@ void TestNewShaderRecompilerStorageImage1DDescriptorVariants() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "image_dim=1d"), "1D storage dimension did not survive into IR metadata"); Check(Common::ContainsStr(result.ir_dump, "image_dim=1d_array"), @@ -5238,10 +5115,7 @@ void TestNewShaderRecompilerNullImageUsesCanonical2DView() { auto options = MakeCompileOptions(ShaderType::Compute); options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "sampled_2d "), @@ -5272,10 +5146,7 @@ void TestNewShaderRecompilerImageGatherVariants() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_gather4_lz"), "new decoder did not decode IMAGE_GATHER4_LZ"); Check(Common::ContainsStr(result.decoded_dump, "image_gather4_lz_o"), @@ -5361,10 +5232,7 @@ void TestNewShaderRecompilerImageLoadVariants() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_load"), "new decoder did not decode MIMG image load"); Check(Common::ContainsStr(result.decoded_dump, "image_load_mip"), @@ -5410,10 +5278,7 @@ void TestNewShaderRecompilerImageLoad2DMsaa() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_dim=2d_msaa") && Common::ContainsStr(result.ir_dump, "image_dim=2d_msaa") && Common::ContainsStr(result.ir_dump, "image_addr=3 image_mip=0"), @@ -5454,10 +5319,7 @@ void TestNewShaderRecompilerImageStoreTranslation() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_store"), "new decoder did not decode MIMG image store"); Check(Common::ContainsStr(result.decoded_dump, "image_store_mip"), @@ -5508,10 +5370,7 @@ void TestNewShaderRecompilerStorageImage3DDescriptorVariant() { options.input_info.compute = &input_info; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_dim=3d"), "MIMG store did not decode the RDNA2 3D instruction dimension"); const auto has_3d_store = std::ranges::any_of( @@ -5565,10 +5424,7 @@ void TestNewShaderRecompilerStorageImage2DDescriptorOverridesMimg3D() { options.input_info.compute = &input_info; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_dim=3d"), "test MIMG store should decode as a 3D instruction"); Check(SpirvContainsTypeImage(result.spirv, SpirvDim2D, 0, 2), @@ -5610,10 +5466,7 @@ void TestNewShaderRecompilerImageAtomicTranslation() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "image_atomic_add"), "new decoder did not decode MIMG image atomic add"); Check(Common::ContainsStr(result.decoded_dump, "image_atomic_umin"), @@ -5677,10 +5530,7 @@ void TestNewShaderRecompilerVintrpTranslation() { options.input_info.pixel = &ps_info; options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "v_interp_p1_f32"), "new decoder did not decode VINTRP P1"); Check(Common::ContainsStr(result.decoded_dump, "v_interp_p2_f32"), @@ -5719,10 +5569,7 @@ void TestNewShaderRecompilerVintrpTranslation() { options.input_info.pixel = &remapped_ps_info; - ShaderRecompiler::CompileResult remapped_result; - Check(ShaderRecompiler::TryRecompile(remapped_shader, options, - remapped_result, &error), - error.c_str()); + auto remapped_result = ShaderRecompiler::Recompile(remapped_shader, options); Check( Common::ContainsStr(remapped_result.ir_dump, "input_attr=2 input_chan=0"), "remapped VINTRP did not preserve raw pixel attribute metadata"); @@ -5745,10 +5592,8 @@ void TestNewShaderRecompilerVintrpTranslation() { options.input_info.pixel = &duplicate_ps_info; - ShaderRecompiler::CompileResult duplicate_result; - Check(ShaderRecompiler::TryRecompile(duplicate_location_shader, options, - duplicate_result, &error), - error.c_str()); + auto duplicate_result = + ShaderRecompiler::Recompile(duplicate_location_shader, options); Check(ProgramInputCount(duplicate_result.program, ShaderRecompiler::IR::StageInputKind::Parameter) == 2, "duplicate-location VINTRP shader did not reflect both raw parameter " @@ -5770,10 +5615,7 @@ void TestNewShaderRecompilerVintrpTranslation() { options.input_info.pixel = &flat_ps_info; - ShaderRecompiler::CompileResult flat_result; - Check( - ShaderRecompiler::TryRecompile(flat_shader, options, flat_result, &error), - error.c_str()); + auto flat_result = ShaderRecompiler::Recompile(flat_shader, options); Check(SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u), "flat VINTRP input did not emit a Flat decoration"); Check(!SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 13u), @@ -5789,10 +5631,8 @@ void TestNewShaderRecompilerVintrpTranslation() { options.input_info.pixel = &no_perspective_ps_info; - ShaderRecompiler::CompileResult no_perspective_result; - Check(ShaderRecompiler::TryRecompile(flat_shader, options, - no_perspective_result, &error), - error.c_str()); + auto no_perspective_result = + ShaderRecompiler::Recompile(flat_shader, options); Check(SpirvHasDecorationValueWithDecoration(no_perspective_result.spirv, 30u, 0u, 13u), "no-perspective VINTRP input did not emit a NoPerspective decoration"); @@ -5821,10 +5661,7 @@ void TestCustomVintrpMovTranslation() { options.input_info.pixel = &custom_ps_info; options.dump_ir = true; - ShaderRecompiler::CompileResult custom_result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, custom_result, &error), - error.c_str()); + auto custom_result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr( custom_result.ir_dump, "GetInterpolationParameter 0x00000000, 0x00000003, 0x00000000"), @@ -5870,10 +5707,7 @@ void TestCustomVintrpMovTranslation() { custom_ps_info.custom_interpolation_mask = 0; custom_ps_info.interpolator_settings[0] = 0; - ShaderRecompiler::CompileResult standard_result; - Check( - ShaderRecompiler::TryRecompile(shader, options, standard_result, &error), - error.c_str()); + auto standard_result = ShaderRecompiler::Recompile(shader, options); Check(SpirvInstructionOpcodeCount(standard_result.spirv, 131u) == 2u, "standard VINTRP P10/P20 did not subtract P0 exactly once each"); CheckSpirvBinaryValidates(standard_result.spirv); @@ -5888,10 +5722,7 @@ void TestCustomVintrpMovTranslation() { flat_ps_info.input_num = 1; flat_ps_info.interpolator_settings[0] = 0x00000400u; options.input_info.pixel = &flat_ps_info; - ShaderRecompiler::CompileResult flat_result; - Check( - ShaderRecompiler::TryRecompile(flat_shader, options, flat_result, &error), - error.c_str()); + auto flat_result = ShaderRecompiler::Recompile(flat_shader, options); Check(!SpirvContainsCapability(flat_result.spirv, 5284u), "flat P0 unexpectedly required fragment barycentric support"); Check(SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u), @@ -5910,20 +5741,14 @@ void TestCustomVintrpMovTranslation() { ShaderPixelInputInfo mixed_ps_info{}; mixed_ps_info.input_num = 1; options.input_info.pixel = &mixed_ps_info; - ShaderRecompiler::CompileResult mixed_result; - Check(ShaderRecompiler::TryRecompile(mixed_shader, options, mixed_result, - &error), - error.c_str()); + auto mixed_result = ShaderRecompiler::Recompile(mixed_shader, options); Check(SpirvHasDecorationValue(mixed_result.spirv, 11u, 5286u) && SpirvInstructionOpcodeCount(mixed_result.spirv, 133u) == 3u, "mixed ordinary/P0 input was not interpolated from per-vertex values"); CheckSpirvBinaryValidates(mixed_result.spirv); mixed_ps_info.ps_no_perspective = true; - ShaderRecompiler::CompileResult mixed_linear_result; - Check(ShaderRecompiler::TryRecompile(mixed_shader, options, - mixed_linear_result, &error), - error.c_str()); + auto mixed_linear_result = ShaderRecompiler::Recompile(mixed_shader, options); Check(SpirvHasDecorationValue(mixed_linear_result.spirv, 11u, 5287u), "mixed linear input did not use BaryCoordNoPerspKHR"); CheckSpirvBinaryValidates(mixed_linear_result.spirv); @@ -6052,12 +5877,10 @@ void TestNewShaderRecompilerNativeWideScalarMemoryIr() { translate_options.stage = ShaderType::Compute; translate_options.wave_size = 64u; translate_options.compute = &compute; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(shader, decoded, &error) && - ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error) && - ShaderRecompiler::Frontend::TranslateProgram( - decoded, graph, translate_options, ir, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(shader, decoded); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); + ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph, + translate_options); std::vector address_widths; std::vector buffer_widths; for (const auto &memory : ir.memory_info) { @@ -6129,10 +5952,7 @@ void TestNewShaderRecompilerNativeWideBufferIr() { }; auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); std::array counts{}; for (const auto *block : result.program.blocks) { for (const auto &inst : *block) { @@ -6179,10 +5999,7 @@ void TestNewShaderRecompilerBufferSignedLoadTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "buffer_load_sbyte"), "new decoder did not decode buffer signed byte load"); Check(Common::ContainsStr(result.decoded_dump, "buffer_load_sshort"), @@ -6219,10 +6036,7 @@ void TestNewShaderRecompilerBufferSubDwordStoreTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "buffer_store_byte"), "new decoder did not decode buffer byte store"); Check(Common::ContainsStr(result.decoded_dump, "buffer_store_short"), @@ -6274,10 +6088,7 @@ void TestNewShaderRecompilerMubufFormatTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "BUFFER_LOAD_FORMAT_X"), "new decoder did not decode MUBUF format-x load"); Check(Common::ContainsStr(result.decoded_dump, "BUFFER_LOAD_FORMAT_XYZW"), @@ -6318,10 +6129,7 @@ void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(result.resources.buffers.size() == 1 && result.resources.buffers[0].dwords[2] == 5u, "formatted store test did not preserve descriptor NumRecords"); @@ -6361,10 +6169,7 @@ void TestNewShaderRecompilerTypedBufferTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "TBUFFER_LOAD_FORMAT_X"), "new decoder did not decode typed buffer format-x load"); Check(Common::ContainsStr(result.decoded_dump, "TBUFFER_LOAD_FORMAT_XY"), @@ -6410,10 +6215,7 @@ void TestNewShaderRecompilerFlatOldBackedTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "flat_load_ubyte"), "new decoder did not decode old-backed FLAT ubyte load"); Check(Common::ContainsStr(result.ir_dump, "FlatLoadUbyte v9"), @@ -6442,10 +6244,7 @@ void TestNewShaderRecompilerUnbasedFlatUsesBda() { auto options = MakeCompileOptions(ShaderType::Pixel); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(result.program.info.uses_dma && ShaderRecompiler::IR::FindBinding( result.program.bindings, @@ -6473,10 +6272,7 @@ void TestNewShaderRecompilerFlatSignedLoadTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "flat_load_sbyte"), "new decoder did not decode flat signed byte load"); Check(Common::ContainsStr(result.decoded_dump, "flat_load_sshort"), @@ -6527,10 +6323,7 @@ void TestNewShaderRecompilerFlatStoreTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "flat_store_byte"), "new decoder did not decode flat byte store"); Check(Common::ContainsStr(result.decoded_dump, "flat_store_short"), @@ -6649,10 +6442,7 @@ void TestNewShaderRecompilerAtomicTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_swap"), "new decoder did not decode buffer atomic swap"); Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_add"), @@ -6785,13 +6575,10 @@ void TestNewShaderRecompilerDsReadWrite2Translation() { translate_options.stage = ShaderType::Compute; translate_options.wave_size = 64u; translate_options.compute = &compute; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error) && - ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error) && - ShaderRecompiler::Frontend::TranslateProgram( - decoded, graph, translate_options, typed, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); + typed = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph, + translate_options); uint32_t scalar_loads = 0; uint32_t vector_loads = 0; uint32_t scalar_stores = 0; @@ -6811,9 +6598,7 @@ void TestNewShaderRecompilerDsReadWrite2Translation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "DS_WRITE2_B32"), "new decoder did not decode old-backed DS write2"); Check(Common::ContainsStr(result.decoded_dump, "DS_READ2_B32"), @@ -6864,10 +6649,7 @@ void TestNewShaderRecompilerDsSubDwordTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "ds_write_b8"), "new decoder did not decode DS byte write"); Check(Common::ContainsStr(result.decoded_dump, "ds_write_b16"), @@ -6941,13 +6723,10 @@ void TestNewShaderRecompilerDsWideAndAtomicTranslation() { translate_options.stage = ShaderType::Compute; translate_options.wave_size = 64u; translate_options.compute = &compute; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error) && - ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error) && - ShaderRecompiler::Frontend::TranslateProgram( - decoded, graph, translate_options, typed, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); + typed = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph, + translate_options); std::array load_widths{}; std::array store_widths{}; for (const auto *block : typed.blocks) { @@ -6974,9 +6753,7 @@ void TestNewShaderRecompilerDsWideAndAtomicTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "DS_WRITE_B64"), "new decoder did not decode DS b64 write"); Check(Common::ContainsStr(result.decoded_dump, "DS_WRITE_B96"), @@ -7022,10 +6799,7 @@ void TestNewShaderRecompilerDsSwizzleTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "ds_swizzle_b32"), "new decoder did not decode DS swizzle"); Check(Common::ContainsStr(result.decoded_dump, "offset=31"), @@ -7075,10 +6849,7 @@ void TestNewShaderRecompilerDsAddtidTranslation() { options.dump_ir = true; options.input_info.compute = &input_info; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "ds_write_addtid_b32"), "new decoder did not decode DS write addtid"); Check(Common::ContainsStr(result.decoded_dump, "ds_read_addtid_b32"), @@ -7116,10 +6887,7 @@ void TestNewShaderRecompilerDsFloatMinMaxTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "ds_min_f32"), "new decoder did not decode DS float min"); Check(Common::ContainsStr(result.decoded_dump, "ds_max_f32"), @@ -7154,10 +6922,7 @@ void TestNewShaderRecompilerCfgStraightLine() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "CFG:"), "CFG dump was not emitted"); Check(Common::ContainsStr(result.ir_dump, "block_0"), @@ -7180,10 +6945,7 @@ void TestNewShaderRecompilerCfgIfElse() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "condition=scc1"), "if/else branch condition missing"); Check(SpirvContainsOpcode(result.spirv, 247), @@ -7211,11 +6973,7 @@ void TestNewShaderRecompilerCfgConsecutiveNativePhis() { auto options = MakeCompileOptions(ShaderType::Vertex); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - const bool compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(CountSourceOccurrences(result.ir_dump, " = Phi") == 2u && SpirvInstructionOpcodeCount(result.spirv, 245u) == 2u, "consecutive typed Phis were not emitted as two native OpPhi " @@ -7233,9 +6991,7 @@ void TestNewShaderRecompilerStructuredU64Phi() { EncodeSopp(0x01), }; auto options = MakeCompileOptions(ShaderType::Compute); - CompileResult result; - std::string error; - Check(TryRecompile(shader, options, result, &error), error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback, "U64 Phi fixture did not select structured mode"); @@ -7257,12 +7013,10 @@ void TestNewShaderRecompilerStructuredU64Phi() { IR::IREmitter use(join); use.Emit(IR::ValueOpcode::CompositeExtractU64, {IR::Value(&phi), IR::Value(1u)}); - Check(Spirv::AnalyzeProgramRequirements(program, &error), error.c_str()); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); - std::vector spirv; - Check(Spirv::EmitProgram(program, result.resources, options.input_info, spirv, - &error), - error.c_str()); + auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, result.resources, + options.input_info); CheckSpirvBinaryValidates(spirv); const auto before = MeasureSpirv(result.spirv); const auto after = MeasureSpirv(spirv); @@ -7281,10 +7035,7 @@ void TestNewShaderRecompilerCfgTerminalExitMergePS() { auto options = MakeCompileOptions(ShaderType::Pixel); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "terminal PS branch should stay on structured path"); Check(!Common::ContainsStr(result.ir_dump, @@ -7307,15 +7058,7 @@ void TestNewShaderRecompilerCfgPostEndTargetMergePS() { auto options = MakeCompileOptions(ShaderType::Pixel); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - const bool ok = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - if (!ok) { - std::fprintf(stderr, "PostEndTargetMergePS compile error: %s\n", - error.c_str()); - } - Check(ok, "post-end target PS shader failed to compile"); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "0x0000000c: S_MOV_B32"), "post-end branch target was not decoded"); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), @@ -7343,10 +7086,7 @@ void TestNewShaderRecompilerCfgLoopBreakContinue() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "backedge"), "loop backedge was not detected"); Check(Common::ContainsStr(result.ir_dump, "loop_header=1"), @@ -7358,6 +7098,7 @@ void TestNewShaderRecompilerCfgLoopBreakContinue() { CheckSpirvBinaryValidates(result.spirv); } +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS void TestNewShaderRecompilerCfgLoopHeaderDynamicScalarBufferLoadStructured() { const uint32_t shader[] = { EncodeSMovB32(0, 128), // preheader: s0 = 0 @@ -7374,22 +7115,16 @@ void TestNewShaderRecompilerCfgLoopHeaderDynamicScalarBufferLoadStructured() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - - ShaderRecompiler::CompileResult result; - std::string error; - const bool compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(!compiled && Common::ContainsStr(error, "control-dependent phi"), - "self-modifying scalar-buffer descriptor should fail explicitly"); + ExpectFatal([&] { (void)ShaderRecompiler::Recompile(shader, options); }, + "self-modifying scalar-buffer descriptor did not terminate " + "compilation"); } void TestNewShaderRecompilerCfgLoopHeaderBufferLoadDispatcher() { const uint32_t shader[] = { EncodeSMovB32(0, 128), // preheader: s0 = 0 EncodeMubuf0(0x0c), - EncodeMubuf1(0, 0, 1), // loop: - // buffer_load_dword - // v0 + EncodeMubuf1(0, 0, 1), // loop: buffer_load_dword v0 EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1 EncodeSopc(0x0a, 0, 130), // s_cmp_lt_u32 s0, 2 EncodeSopp(0x05, 0xfffbu), // s_cbranch_scc1 loop @@ -7400,13 +7135,11 @@ void TestNewShaderRecompilerCfgLoopHeaderBufferLoadDispatcher() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - - ShaderRecompiler::CompileResult result; - std::string error; - Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) && - Common::ContainsStr(error, "control-dependent phi"), - "self-modifying vector-buffer descriptor should fail explicitly"); + ExpectFatal([&] { (void)ShaderRecompiler::Recompile(shader, options); }, + "self-modifying vector-buffer descriptor did not terminate " + "compilation"); } +#endif void TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeStructured() { const uint32_t shader[] = { @@ -7420,19 +7153,15 @@ void TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeStructured() { EncodeSopp(0x05, 0xfff9u), // s_cbranch_scc1 loop 0xbf810000u, }; - - std::string error; ShaderRecompiler::Decoder::Program decoded; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_block_count = graph.blocks.size(); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check(graph.natural_loops.size() == 1u, "DS loop was not preserved"); Check(graph.blocks.size() == original_block_count + 1u, "DS loop structurization did not add exactly one empty header"); @@ -7447,9 +7176,7 @@ void TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeStructured() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "DS append/consume loop did not stay structured"); Check(!result.program.dispatcher_fallback, @@ -7474,11 +7201,7 @@ void TestNewShaderRecompilerCfgLoopHeaderDsReadStructured() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - const bool compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured") && !result.program.dispatcher_fallback, "DS read loop header did not stay structured"); @@ -7507,10 +7230,7 @@ void TestNewShaderRecompilerCfgLoopHeaderDsRead2B64Structured() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured") && !result.program.dispatcher_fallback, "DS read2 b64 loop header did not stay structured"); @@ -7537,10 +7257,7 @@ void TestNewShaderRecompilerCfgSharedOuterAndLoopMerge() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "shared outer/loop merge should stay on structured path"); Check( @@ -7569,10 +7286,7 @@ void TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "loop early-break CFG did not stay on structured path"); Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0, @@ -7603,10 +7317,7 @@ void TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"), "nested-loop nonlocal exit did not select dispatcher fallback"); Check(SpirvInstructionOpcodeCount(result.spirv, 251) != 0, @@ -7630,10 +7341,7 @@ void TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "nested local loop exit did not stay on structured path"); Check(SpirvInstructionOpcodeCount(result.spirv, 246) >= 2, @@ -7664,16 +7372,13 @@ void TestNewShaderRecompilerCfgNestedLoopExitTailMergeSplit() { }; ShaderRecompiler::Decoder::Program program; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, program, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, program); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(program, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(program); const auto original_block_count = graph.blocks.size(); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check(graph.blocks.size() > original_block_count, "nested loop exit tails did not create a private inner merge"); @@ -7716,10 +7421,7 @@ void TestNewShaderRecompilerCfgMixedContinueNonmergeExitDispatcher() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"), "mixed continue/nonmerge exit did not select dispatcher fallback"); Check(SpirvInstructionOpcodeCount(result.spirv, 251) != 0, @@ -7739,10 +7441,7 @@ void TestNewShaderRecompilerCfgConditionalLatchNoSelection() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "conditional latch did not stay on structured path"); Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0, @@ -7765,10 +7464,7 @@ void TestNewShaderRecompilerCfgDirectConditionalLatchNoSelection() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "direct conditional latch did not stay on structured path"); Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0, @@ -7800,10 +7496,7 @@ void TestNewShaderRecompilerCfgLoopEarlyContinuesNoSelection() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "loop early continues should stay on structured path"); Check(SpirvInstructionOpcodeCount(result.spirv, 246) != 0, @@ -7831,25 +7524,20 @@ void TestNewShaderRecompilerCfgLoopGatewaySelection() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage, "loop gateway structurization duplicated semantic instructions"); auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured") && !result.program.dispatcher_fallback, "loop-control gateway selection unexpectedly selected dispatcher"); @@ -7876,15 +7564,12 @@ void TestNewShaderRecompilerCfgConditionalLoopHeaderSelection() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_block_count = graph.blocks.size(); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check(graph.blocks.size() > original_block_count, "conditional guest loop header did not create a synthetic header"); @@ -7907,9 +7592,7 @@ void TestNewShaderRecompilerCfgConditionalLoopHeaderSelection() { "guest conditional was not separated from the loop header"); auto options = MakeCompileOptions(ShaderType::Compute); - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(SpirvInstructionOpcodeCount(result.spirv, 246) == 1u, "conditional loop-header SPIR-V has the wrong loop-merge count"); Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 1u, @@ -7932,17 +7615,14 @@ void TestNewShaderRecompilerCfgMultipleLoopLatches() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_block_count = graph.blocks.size(); Check(graph.back_edges.size() == 2u, "multiple-latch fixture lacks two native backedges"); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check(graph.blocks.size() == original_block_count + 2u, "multiple native latches did not create one synthetic continue and one " "empty header"); @@ -7956,9 +7636,7 @@ void TestNewShaderRecompilerCfgMultipleLoopLatches() { "canonical continue does not join both native latches"); auto options = MakeCompileOptions(ShaderType::Compute); - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(SpirvInstructionOpcodeCount(result.spirv, 246) == 1u, "multiple-latch SPIR-V has the wrong loop-merge count"); Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 0u, @@ -7980,10 +7658,7 @@ void TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=structured"), "duplicate merge CFG did not stay on structured path"); Check( @@ -8014,13 +7689,9 @@ void TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_block_count = graph.blocks.size(); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); @@ -8030,8 +7701,8 @@ void TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders() { }); Check(original_block_count == 8u && graph.natural_loops.size() == 1u, "nested early-exit fixture has the wrong native CFG"); - const bool structured = ShaderRecompiler::CFG::Structurize(graph, &error); - Check(structured, error.c_str()); + const bool structured = ShaderRecompiler::CFG::Structurize(graph); + Check(structured, graph.unsupported_reason.c_str()); Check(graph.blocks.size() == original_block_count + 4u && CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage && @@ -8057,9 +7728,7 @@ void TestNewShaderRecompilerCfgNestedEarlyExitLoopForwarders() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured"), "nested early-exit loop unexpectedly selected dispatcher fallback"); @@ -8083,19 +7752,16 @@ void TestNewShaderRecompilerCfgExecSccSharedArm() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); Check(std::ranges::all_of(original_coverage, [](uint32_t uses) { return uses == 1u; }), "shared-arm fixture already duplicated a semantic instruction"); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); uint32_t route_selects = 0; uint32_t route_sets = 0; for (const auto &block : graph.blocks) { @@ -8115,9 +7781,8 @@ void TestNewShaderRecompilerCfgExecSccSharedArm() { translate_options.stage = ShaderType::Compute; translate_options.wave_size = 64u; translate_options.compute = &compute_info; - Check(ShaderRecompiler::Frontend::TranslateProgram( - decoded, graph, translate_options, value_ir, &error), - error.c_str()); + value_ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph, + translate_options); uint32_t goto_sets = 0; uint32_t goto_gets = 0; for (const auto *block : value_ir.blocks) { @@ -8145,9 +7810,7 @@ void TestNewShaderRecompilerCfgExecSccSharedArm() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured"), "EXEC/SCC shared-arm epilogue did not stay structured"); @@ -8184,16 +7847,13 @@ void TestNewShaderRecompilerCfgNestedTailEarlyExit() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage, "nested-tail routing changed semantic instruction coverage"); @@ -8211,9 +7871,7 @@ void TestNewShaderRecompilerCfgNestedTailEarlyExit() { auto options = MakeCompileOptions(ShaderType::Pixel); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured") && SpirvInstructionOpcodeCount(result.spirv, 251) == 0u, @@ -8239,16 +7897,13 @@ void TestNewShaderRecompilerCfgRoutesInnerSharedExitFirst() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage, "shared-exit route ordering changed semantic instruction coverage"); @@ -8273,9 +7928,7 @@ void TestNewShaderRecompilerCfgRoutesInnerSharedExitFirst() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured") && SpirvInstructionOpcodeCount(result.spirv, 251) == 0u, @@ -8305,20 +7958,16 @@ void TestNewShaderRecompilerCfgLoopSharedRegion() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_block_count = graph.blocks.size(); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); Check(graph.natural_loops.size() == 1u, "loop shared-region fixture has the wrong native CFG"); - const bool structured = ShaderRecompiler::CFG::Structurize(graph, &error); - Check(structured, error.c_str()); + const bool structured = ShaderRecompiler::CFG::Structurize(graph); + Check(structured, graph.unsupported_reason.c_str()); const auto *loop_header = graph.FindBlockByPc(0x00u); Check(graph.natural_loops.size() == 1u && graph.back_edges.size() == 1u && loop_header != nullptr && loop_header->terminator.loop_header && @@ -8340,9 +7989,7 @@ void TestNewShaderRecompilerCfgLoopSharedRegion() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured") && SpirvInstructionOpcodeCount(result.spirv, 246) == 1u && @@ -8368,13 +8015,9 @@ void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); Check( graph.blocks.size() == 7u && graph.blocks[0].successors == std::vector({1, 2}) && @@ -8395,8 +8038,8 @@ void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() { const auto original_block_count = graph.blocks.size(); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); - const bool structured = ShaderRecompiler::CFG::Structurize(graph, &error); - Check(structured, error.c_str()); + const bool structured = ShaderRecompiler::CFG::Structurize(graph); + Check(structured, graph.unsupported_reason.c_str()); Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage, "early-exit ladder routing changed semantic instruction coverage"); @@ -8416,9 +8059,7 @@ void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() { auto options = MakeCompileOptions(ShaderType::Pixel); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured"), "overlapping early-exit ladder did not stay structured"); @@ -8442,17 +8083,13 @@ void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); - const bool structured = ShaderRecompiler::CFG::Structurize(graph, &error); - Check(structured, error.c_str()); + const bool structured = ShaderRecompiler::CFG::Structurize(graph); + Check(structured, graph.unsupported_reason.c_str()); Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage, "nested early-exit structurization changed semantic coverage"); @@ -8470,9 +8107,7 @@ void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured") && SpirvInstructionOpcodeCount(result.spirv, 251) == 0u, @@ -8497,16 +8132,13 @@ void TestNewShaderRecompilerCfgSharedTerminalEarlyExit() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check( CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage, @@ -8514,9 +8146,7 @@ void TestNewShaderRecompilerCfgSharedTerminalEarlyExit() { auto options = MakeCompileOptions(ShaderType::Pixel); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured"), "shared-terminal early exit selected dispatcher control flow"); @@ -8536,27 +8166,22 @@ void TestNewShaderRecompilerCfgPrunesUnreachableSelectionEntry() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); const auto original_coverage = CfgInstructionCoverage(graph, decoded.instructions.size()); Check(original_coverage[1] == 0u, "CFG retained an unreachable external selection entry"); - Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str()); + Check(ShaderRecompiler::CFG::Structurize(graph), + graph.unsupported_reason.c_str()); Check(CfgInstructionCoverage(graph, decoded.instructions.size()) == original_coverage, "selection structurization changed reachable semantic code"); auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!result.program.dispatcher_fallback && Common::ContainsStr(result.ir_dump, "mode=structured") && SpirvInstructionOpcodeCount(result.spirv, 251) == 0u, @@ -8576,10 +8201,7 @@ void TestNewShaderRecompilerCfgIrreducibleDispatcher() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "irreducible CFG"), "irreducible CFG reason was not retained"); Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"), @@ -8602,9 +8224,7 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() { EncodeSopp(0x01), }; auto options = MakeCompileOptions(ShaderType::Compute); - CompileResult result; - std::string error; - Check(TryRecompile(shader, options, result, &error), error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(result.program.dispatcher_fallback && result.program.blocks.size() >= 3u, "U32x3 spill fixture did not select dispatcher mode"); @@ -8617,12 +8237,9 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() { {IR::Value(1u), IR::Value(2u), IR::Value(3u)}); IR::IREmitter use(prologue_program.blocks[1]); use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)}); - Check(Spirv::AnalyzeProgramRequirements(prologue_program, &error), - error.c_str()); - std::vector spirv; - Check(Spirv::EmitProgram(prologue_program, result.resources, - options.input_info, spirv, &error), - error.c_str()); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(prologue_program); + auto spirv = ShaderRecompiler::Spirv::EmitProgram( + prologue_program, result.resources, options.input_info); CheckSpirvBinaryValidates(spirv); const auto after = MeasureSpirv(spirv); Check(after.function_variables == before.function_variables && @@ -8630,8 +8247,7 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() { "dispatcher spilled a value defined by its dominating prologue"); } - CompileResult spill_result; - Check(TryRecompile(shader, options, spill_result, &error), error.c_str()); + auto spill_result = ShaderRecompiler::Recompile(shader, options); auto program = std::move(spill_result.program); IR::IREmitter definition(program.blocks[1]); const auto vector = @@ -8640,12 +8256,10 @@ void TestNewShaderRecompilerDispatcherSpillsU32x3() { IR::IREmitter use(program.blocks[2]); use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(2u)}); use.Emit(IR::ValueOpcode::CompositeExtractU32x3, {vector, IR::Value(1u)}); - Check(Spirv::AnalyzeProgramRequirements(program, &error), error.c_str()); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); - std::vector spirv; - Check(Spirv::EmitProgram(program, spill_result.resources, - options.input_info, spirv, &error), - error.c_str()); + auto spirv = ShaderRecompiler::Spirv::EmitProgram( + program, spill_result.resources, options.input_info); CheckSpirvBinaryValidates(spirv); const auto after = MeasureSpirv(spirv); Check(after.function_variables == before.function_variables + 1u && @@ -8660,16 +8274,14 @@ void TestNewShaderRecompilerU64PairTranslation() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; CompileResult result; - std::string error; const uint32_t lshrrev_b64_shader[] = { 0xd7000001u, 0x0000d50cu, // v_lshrrev_b64 v[1:2], v12, vcc EncodeSopp(0x01), }; Decoder::Instruction decoded_lshrrev_b64; - Check(Decoder::DecodeInstruction(lshrrev_b64_shader, 0u, - decoded_lshrrev_b64, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(lshrrev_b64_shader, 0u, + decoded_lshrrev_b64); Check(decoded_lshrrev_b64.opcode == Decoder::Opcode::V_LSHRREV_B64 && decoded_lshrrev_b64.dst.kind == Decoder::OperandKind::Vgpr && decoded_lshrrev_b64.dst.reg == 1u && @@ -8678,8 +8290,7 @@ void TestNewShaderRecompilerU64PairTranslation() { decoded_lshrrev_b64.src0.reg == 12u && decoded_lshrrev_b64.src1.kind == Decoder::OperandKind::VccLo, "decoder rejected captured VOP3 V_LSHRREV_B64 fields"); - Check(TryRecompile(lshrrev_b64_shader, options, result, &error), - error.c_str()); + result = ShaderRecompiler::Recompile(lshrrev_b64_shader, options); Check(Common::ContainsStr(result.decoded_dump, "V_LSHRREV_B64 v1, v12, vcc_lo"), "captured VOP3 V_LSHRREV_B64 was not present in the decoded dump"); @@ -8690,9 +8301,8 @@ void TestNewShaderRecompilerU64PairTranslation() { EncodeSopp(0x01), }; Decoder::Instruction decoded_lshlrev_b64; - Check(Decoder::DecodeInstruction(lshlrev_b64_shader, 0u, - decoded_lshlrev_b64, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(lshlrev_b64_shader, 0u, + decoded_lshlrev_b64); Check(decoded_lshlrev_b64.opcode == Decoder::Opcode::V_LSHLREV_B64 && decoded_lshlrev_b64.dst.kind == Decoder::OperandKind::Vgpr && decoded_lshlrev_b64.dst.reg == 33u && @@ -8703,8 +8313,7 @@ void TestNewShaderRecompilerU64PairTranslation() { Decoder::OperandKind::IntegerInlineConstant && decoded_lshlrev_b64.src1.value == 1u, "decoder rejected captured VOP3 V_LSHLREV_B64 fields"); - Check(TryRecompile(lshlrev_b64_shader, options, result, &error), - error.c_str()); + result = ShaderRecompiler::Recompile(lshlrev_b64_shader, options); Check(Common::ContainsStr(result.decoded_dump, "V_LSHLREV_B64 v33, v3, 1"), "captured VOP3 V_LSHLREV_B64 was not present in the decoded dump"); @@ -8715,16 +8324,15 @@ void TestNewShaderRecompilerU64PairTranslation() { EncodeSopp(0x01), }; Decoder::Instruction decoded_cmpx_i64; - Check(Decoder::DecodeInstruction(cmpx_i64_shader, 0u, decoded_cmpx_i64, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(cmpx_i64_shader, 0u, + decoded_cmpx_i64); Check(decoded_cmpx_i64.opcode == Decoder::Opcode::V_CMPX_NE_I64 && decoded_cmpx_i64.dst.kind == Decoder::OperandKind::ExecLo && decoded_cmpx_i64.src0.value == 0u && decoded_cmpx_i64.src1.kind == Decoder::OperandKind::Vgpr && decoded_cmpx_i64.src1.reg == 7u, "decoder rejected captured VOP3 V_CMPX_NE_I64 fields"); - Check(TryRecompile(cmpx_i64_shader, options, result, &error), error.c_str()); + result = ShaderRecompiler::Recompile(cmpx_i64_shader, options); Check(Common::ContainsStr(result.decoded_dump, "V_CMPX_NE_I64 exec_lo, 0, v7"), "captured VOP3 V_CMPX_NE_I64 was not present in the decoded dump"); @@ -8735,15 +8343,14 @@ void TestNewShaderRecompilerU64PairTranslation() { EncodeSopp(0x01), }; Decoder::Instruction decoded_cmpx; - Check(Decoder::DecodeInstruction(cmpx_shader, 0u, decoded_cmpx, &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(cmpx_shader, 0u, decoded_cmpx); Check(decoded_cmpx.opcode == Decoder::Opcode::V_CMPX_NE_U64 && decoded_cmpx.dst.kind == Decoder::OperandKind::ExecLo && decoded_cmpx.src0.value == 0u && decoded_cmpx.src1.kind == Decoder::OperandKind::Vgpr && decoded_cmpx.src1.reg == 7u, "decoder rejected captured V_CMPX_NE_U64 fields"); - Check(TryRecompile(cmpx_shader, options, result, &error), error.c_str()); + result = ShaderRecompiler::Recompile(cmpx_shader, options); CheckSpirvBinaryValidates(result.spirv); const uint32_t cmpx_v2_shader[] = { @@ -8751,23 +8358,22 @@ void TestNewShaderRecompilerU64PairTranslation() { EncodeSopp(0x01), }; Decoder::Instruction decoded_cmpx_v2; - Check(Decoder::DecodeInstruction(cmpx_v2_shader, 0u, decoded_cmpx_v2, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeInstruction(cmpx_v2_shader, 0u, + decoded_cmpx_v2); Check(decoded_cmpx_v2.opcode == Decoder::Opcode::V_CMPX_NE_U64 && decoded_cmpx_v2.dst.kind == Decoder::OperandKind::ExecLo && decoded_cmpx_v2.src0.value == 0u && decoded_cmpx_v2.src1.kind == Decoder::OperandKind::Vgpr && decoded_cmpx_v2.src1.reg == 2u, "decoder rejected reported VOPC V_CMPX_NE_U64 fields"); - Check(TryRecompile(cmpx_v2_shader, options, result, &error), error.c_str()); + result = ShaderRecompiler::Recompile(cmpx_v2_shader, options); Check(Common::ContainsStr(result.decoded_dump, "V_CMPX_NE_U64 exec_lo, 0, v2"), "reported VOPC V_CMPX_NE_U64 was not present in the decoded dump"); CheckSpirvBinaryValidates(result.spirv); const uint32_t empty_shader[] = {EncodeSopp(0x01)}; - Check(TryRecompile(empty_shader, options, result, &error), error.c_str()); + result = ShaderRecompiler::Recompile(empty_shader, options); auto program = std::move(result.program); IR::IREmitter ir(program.blocks.front()); @@ -8792,12 +8398,10 @@ void TestNewShaderRecompilerU64PairTranslation() { ir.Emit(IR::ValueOpcode::ShiftRightLogical64, {base, IR::Value(count)}); ir.Emit(IR::ValueOpcode::ShiftRightArithmetic64, {base, IR::Value(count)}); } - Check(Spirv::AnalyzeProgramRequirements(program, &error), error.c_str()); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); - std::vector spirv; - Check(Spirv::EmitProgram(program, result.resources, options.input_info, spirv, - &error), - error.c_str()); + auto spirv = ShaderRecompiler::Spirv::EmitProgram(program, result.resources, + options.input_info); CheckSpirvBinaryValidates(spirv); const auto source = DisassembleSpirvBinary(spirv); Check(!Common::ContainsStr(source, "OpCapability Int64") && @@ -8819,8 +8423,7 @@ void TestNewShaderRecompilerU64PairTranslation() { EncodeSopp(0x05, 2), EncodeSopp(0x02, 0), EncodeSopp(0x05, 0xfffeu), EncodeSopp(0x02, 0xfffeu), EncodeSopp(0x01), }; - Check(TryRecompile(dispatcher_shader, options, result, &error), - error.c_str()); + result = ShaderRecompiler::Recompile(dispatcher_shader, options); Check(result.program.dispatcher_fallback && result.program.blocks.size() >= 3u, "U64 spill fixture did not select dispatcher mode"); @@ -8830,11 +8433,10 @@ void TestNewShaderRecompilerU64PairTranslation() { {IR::Value(1u), IR::Value(2u)}); IR::IREmitter use(program.blocks[2]); use.Emit(IR::ValueOpcode::CompositeExtractU64, {vector, IR::Value(1u)}); - Check(Spirv::AnalyzeProgramRequirements(program, &error), error.c_str()); + ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program); spirv.clear(); - Check(Spirv::EmitProgram(program, result.resources, options.input_info, spirv, - &error), - error.c_str()); + spirv = ShaderRecompiler::Spirv::EmitProgram(program, result.resources, + options.input_info); CheckSpirvBinaryValidates(spirv); const auto before = MeasureSpirv(result.spirv); const auto after = MeasureSpirv(spirv); @@ -8864,10 +8466,7 @@ void TestNewShaderRecompilerBufferLoadsGuardedByExec() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "BUFFER_LOAD_DWORD"), "buffer load guard regression did not decode MUBUF load"); CheckSpirvBinaryValidates(result.spirv); @@ -8905,10 +8504,7 @@ void TestNewShaderRecompilerBufferAtomicsGuardedByBounds() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "BUFFER_ATOMIC_ADD"), "buffer atomic bounds regression did not decode MUBUF atomic"); CheckSpirvBinaryValidates(result.spirv); @@ -8956,10 +8552,7 @@ void TestNewShaderRecompilerBranchConditionForms() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, c.condition), "branch condition was not preserved"); Check(SpirvContainsOpcode(result.spirv, 250), @@ -8984,10 +8577,7 @@ void TestNewShaderRecompilerSetpcBranch() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "s_setpc_b64"), "S_SETPC_B64 was not decoded"); Check(Common::ContainsStr(result.ir_dump, "successors=["), @@ -9023,10 +8613,7 @@ void TestNewShaderRecompilerSetpcJumpTable() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"), "S_SETPC_B64 jump table did not select dispatcher fallback"); const auto jump = std::find_if( @@ -9074,13 +8661,9 @@ void TestNewShaderRecompilerPrunesUnreachableSetpcMetadata() { }; ShaderRecompiler::Decoder::Program decoded; - std::string error; - Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded, - &error), - error.c_str()); + ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded); ShaderRecompiler::CFG::Graph graph; - Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error), - error.c_str()); + graph = ShaderRecompiler::CFG::BuildGraph(decoded); Check(!graph.irreducible && graph.code_table_load_pcs.empty(), "unreachable S_SETPC retained jump-table metadata or dispatcher mode"); @@ -9090,9 +8673,8 @@ void TestNewShaderRecompilerPrunesUnreachableSetpcMetadata() { translate_options.stage = ShaderType::Compute; translate_options.wave_size = 64u; translate_options.compute = &compute; - Check(ShaderRecompiler::Frontend::TranslateProgram( - decoded, graph, translate_options, ir, &error), - error.c_str()); + ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph, + translate_options); size_t scalar_loads = 0; for (const auto *block : ir.blocks) { scalar_loads += std::ranges::count_if(*block, [](const auto &inst) { @@ -9135,10 +8717,7 @@ void TestNewShaderRecompilerSetpcDwordJumpTable() { auto options = MakeCompileOptions(ShaderType::Compute); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"), "subtractive S_SETPC_B64 table did not select dispatcher fallback"); const auto jump = std::find_if( @@ -9186,10 +8765,7 @@ void TestNewShaderRecompilerExpVertexOutputs() { auto options = MakeCompileOptions(ShaderType::Vertex); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(Common::ContainsStr(result.decoded_dump, "target=0x0c"), "POS export was not decoded"); Check(Common::ContainsStr(result.decoded_dump, "target=0x20"), @@ -9214,10 +8790,7 @@ void TestNewShaderRecompilerAuxPositionExports() { auto options = MakeCompileOptions(ShaderType::Vertex); options.input_info.vertex = &vertex; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); return result; }; @@ -9295,7 +8868,9 @@ void TestNewShaderRecompilerAuxPositionExports() { std::vector({0u}), "CCDIST1 did not shift across a disabled CCDIST0 vector"); - const auto rejects = [](uint32_t control, uint32_t en, const char *message) { +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + const auto rejects = [](uint32_t control, uint32_t en, + const char *message) { const uint32_t shader[] = { EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3), EncodeExp0(0x0d, en), EncodeExp1(4, 5, 6, 7), @@ -9305,14 +8880,16 @@ void TestNewShaderRecompilerAuxPositionExports() { vertex.pa_cl_vs_out_cntl = control; auto options = MakeCompileOptions(ShaderType::Vertex); options.input_info.vertex = &vertex; - ShaderRecompiler::CompileResult result; - std::string error; - Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) && - Common::ContainsStr(error, message), - "unsupported auxiliary position export did not fail explicitly"); + ExpectFatal([&] { (void)ShaderRecompiler::Recompile(shader, options); }, + message); }; - rejects(0x00280000u, 0x4u, "viewport-index"); - rejects(0u, 0x1u, "auxiliary/stereo"); + rejects(0x00280000u, 0x4u, + "viewport-index auxiliary position export did not terminate " + "compilation"); + rejects(0u, 0x1u, + "unsupported auxiliary/stereo position export did not terminate " + "compilation"); +#endif HW::VertexShaderInfo regs{}; regs.es_regs.data_addr = 1; @@ -9370,10 +8947,7 @@ void TestDemandDrivenSpirvDeclarations() { SetImageTestFormat(&user_data, 0, format); auto options = MakeCompileOptions(ShaderType::Compute); options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); return result.spirv; }; @@ -9386,11 +8960,7 @@ void TestDemandDrivenSpirvDeclarations() { auto query_options = MakeCompileOptions(ShaderType::Pixel); query_options.input_info.pixel = &query_pixel_info; query_options.user_data = query_user_data.data(); - ShaderRecompiler::CompileResult query_result; - std::string query_error; - Check(ShaderRecompiler::TryRecompile(query, query_options, query_result, - &query_error), - query_error.c_str()); + auto query_result = ShaderRecompiler::Recompile(query, query_options); CheckSpirvBinaryValidates(query_result.spirv); const auto &query_spirv = query_result.spirv; Check(SpirvInstructionOpcodeCount(query_spirv, 105u) == 1u, @@ -9436,11 +9006,7 @@ void TestDemandDrivenSpirvDeclarations() { auto glsl_pixel_info = RegressionPixelInputInfo(); auto glsl_options = MakeCompileOptions(ShaderType::Pixel); glsl_options.input_info.pixel = &glsl_pixel_info; - ShaderRecompiler::CompileResult glsl_result; - std::string glsl_error; - Check(ShaderRecompiler::TryRecompile(glsl, glsl_options, glsl_result, - &glsl_error), - glsl_error.c_str()); + auto glsl_result = ShaderRecompiler::Recompile(glsl, glsl_options); CheckSpirvBinaryValidates(glsl_result.spirv); Check(MeasureSpirv(glsl_result.spirv).ext_inst_imports == 1u, "multiple GLSL operations emitted duplicate imports"); @@ -9464,10 +9030,8 @@ void TestTypedEntryStateIsMinimal() { translate_options.wave_size = wave_size; translate_options.user_data_count = 0; translate_options.compute = &compute; - std::string error; - Check(Frontend::TranslateProgram(decoded, graph, translate_options, values, - &error), - error.c_str()); + values = ShaderRecompiler::Frontend::TranslateProgram(decoded, graph, + translate_options); uint32_t set_exec = 0; uint32_t set_exec_mask_tag = 0; @@ -9527,13 +9091,14 @@ void TestTypedEntryStateIsMinimal() { IR::RewriteToSsa(values.blocks); IR::RemoveIdentities(values.blocks); IR::EliminateDeadCode(values.blocks); - Check(IR::ValidateProgram(values, true, &error), error.c_str()); + IR::ValidateProgram(values, true); }; check(32u); check(64u); } +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS void TestFinalSsaRejectsRegisterStatePseudos() { using namespace ShaderRecompiler::IR; @@ -9548,15 +9113,14 @@ void TestFinalSsaRejectsRegisterStatePseudos() { } else { ir.Emit(ValueOpcode::ReferenceU32, {ir.GetScalarReg(ScalarReg{0})}); } - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "register-state pseudo"), - "final SSA accepted a register-state pseudo operation"); + ExpectFatal([&] { ValidateProgram(program, true); }, + "final SSA accepted a register-state pseudo operation"); }; check_rejected(false); check_rejected(true); } +#endif void TestValuePhiValidation() { using namespace ShaderRecompiler::IR; @@ -9571,7 +9135,12 @@ void TestValuePhiValidation() { AfterInstruction, }; - const auto validate = [](InvalidPhi invalid, const char *expected_error) { +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + const auto expect_invalid = [](const Program &program, const char *text) { + ExpectFatal([&] { ValidateProgram(program, true); }, text); + }; +#endif + const auto validate = [&](InvalidPhi invalid) { Program program; const auto add_block = [&](uint32_t id) { program.block_storage.push_back(std::make_unique()); @@ -9619,25 +9188,27 @@ void TestValuePhiValidation() { phi.AddPhiOperand(right, Value(9u)); } - std::string error; - const bool valid = ValidateProgram(program, true, &error); if (invalid == InvalidPhi::None) { - Check(valid, error.c_str()); - } else { - Check(!valid && Common::ContainsStr(error, expected_error), - "malformed Phi was not rejected by its structural invariant"); + ValidateProgram(program, true); } +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + else { + expect_invalid(program, + "malformed Phi did not terminate IR validation"); + } +#endif }; - validate(InvalidPhi::None, ""); - validate(InvalidPhi::Empty, "no incoming"); - validate(InvalidPhi::MissingParent, "cover every predecessor"); - validate(InvalidPhi::DuplicateParent, "duplicated"); - validate(InvalidPhi::NonPredecessorParent, "non-predecessor"); - validate(InvalidPhi::WrongType, "incoming type"); - validate(InvalidPhi::AfterInstruction, "after a non-Phi"); + validate(InvalidPhi::None); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + validate(InvalidPhi::Empty); + validate(InvalidPhi::MissingParent); + validate(InvalidPhi::DuplicateParent); + validate(InvalidPhi::NonPredecessorParent); + validate(InvalidPhi::WrongType); + validate(InvalidPhi::AfterInstruction); - const auto validate_cfg = [](bool duplicate_id) { + const auto validate_cfg = [&](bool duplicate_id) { Program program; for (uint32_t id = 0; id < 2; id++) { program.block_storage.push_back(std::make_unique()); @@ -9650,15 +9221,12 @@ void TestValuePhiValidation() { ShaderRecompiler::CFG::TerminatorKind::Branch; program.block_info[0].terminator.true_block = 1; } - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, duplicate_id - ? "id is duplicated" - : "successor graph disagree"), - "malformed Value CFG was not rejected before SPIR-V emission"); + expect_invalid(program, + "malformed Value CFG did not terminate IR validation"); }; validate_cfg(true); validate_cfg(false); + { Program program; for (uint32_t id = 0; id < 2; id++) { @@ -9674,10 +9242,8 @@ void TestValuePhiValidation() { program.block_info[1].terminator.true_block = 0; program.blocks[0]->AddBranch(program.blocks[1]); program.blocks[1]->AddBranch(program.blocks[0]); - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "entry block has a predecessor"), - "value IR accepted a backedge to its synthetic entry block"); + expect_invalid(program, + "entry predecessor did not terminate IR validation"); } { Program program; @@ -9698,22 +9264,17 @@ void TestValuePhiValidation() { indirect.indirect_target = Value(0u); program.blocks[0]->AddBranch(program.blocks[1]); program.blocks[1]->AddBranch(program.blocks[1]); - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, - "selector target is not a CFG successor"), - "value IR accepted an indirect selector target outside its target " - "set"); + expect_invalid(program, + "non-successor indirect selector did not terminate IR " + "validation"); } { Program program; program.block_storage.push_back(std::make_unique()); program.blocks.push_back(program.block_storage.back().get()); program.block_info.push_back({.id = UINT32_MAX}); - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "reserved exit id"), - "value IR accepted the dispatcher exit sentinel as a block id"); + expect_invalid(program, + "reserved exit block id did not terminate IR validation"); } { Program program; @@ -9732,10 +9293,8 @@ void TestValuePhiValidation() { indirect.indirect_target = Value(0u); program.blocks[0]->AddBranch(program.blocks[1]); program.blocks[1]->AddBranch(program.blocks[1]); - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "target is duplicated"), - "value IR accepted duplicate indirect branch targets"); + expect_invalid(program, + "duplicate indirect targets did not terminate IR validation"); } { Program program; @@ -9744,10 +9303,8 @@ void TestValuePhiValidation() { program.blocks.push_back(program.block_storage.back().get()); program.block_info.push_back({.id = id}); } - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "unreachable block"), - "value IR accepted an unreachable block"); + expect_invalid(program, + "unreachable block did not terminate IR validation"); } { Program program; @@ -9760,10 +9317,8 @@ void TestValuePhiValidation() { auto &definition = block->AppendNewInst(ValueOpcode::IAdd32, {Value(3u), Value(4u)}); use.SetArg(0, Value(&definition)); - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "same-block definition"), - "value IR accepted a same-block forward use"); + expect_invalid(program, + "same-block forward use did not terminate IR validation"); } { Program program; @@ -9790,10 +9345,8 @@ void TestValuePhiValidation() { {Value(1u), Value(2u)}); program.blocks[2]->AppendNewInst(ValueOpcode::IAdd32, {Value(&definition), Value(3u)}); - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "does not dominate its use"), - "value IR accepted a non-dominating ordinary definition"); + expect_invalid(program, + "non-dominating definition did not terminate IR validation"); } { Program program; @@ -9822,10 +9375,9 @@ void TestValuePhiValidation() { phi.SetFlags(Type::U32); phi.AddPhiOperand(program.blocks[1], Value(3u)); phi.AddPhiOperand(program.blocks[2], Value(&definition)); - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "does not dominate its edge"), - "value IR accepted an unavailable Phi-edge definition"); + expect_invalid(program, + "unavailable Phi-edge definition did not terminate IR " + "validation"); } { Program program; @@ -9858,32 +9410,27 @@ void TestValuePhiValidation() { auto &condition = program.blocks[1]->AppendNewInst(ValueOpcode::IEqual32, {Value(1u), Value(2u)}); program.block_info[2].condition = Value(&condition); - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, "branch condition definition"), - "value IR accepted a non-dominating branch condition"); + expect_invalid(program, + "non-dominating branch condition did not terminate IR " + "validation"); } +#endif } void TestWqmMaskSignatureAndU64ShiftConstantPropagation() { using namespace ShaderRecompiler::IR; - const auto check_invalid_signature = [](ValueOpcode opcode, Value valid, - Value invalid) { - Program malformed; - malformed.block_storage.push_back(std::make_unique()); - malformed.blocks.push_back(malformed.block_storage.back().get()); - malformed.block_info.emplace_back(); - IREmitter malformed_ir(malformed.blocks.front()); - auto value = malformed_ir.Emit(opcode, {valid}); - value.TryInstruction()->SetArg(0, invalid); - std::string error; - Check(!ValidateProgram(malformed, false, &error) && - Common::ContainsStr(error, "argument 0 has type"), - "value IR accepted a WQM operand that violates opcode metadata"); - }; - check_invalid_signature(ValueOpcode::WqmMask, Value(true), - Value(uint64_t{1})); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + Program malformed; + malformed.block_storage.push_back(std::make_unique()); + malformed.blocks.push_back(malformed.block_storage.back().get()); + malformed.block_info.emplace_back(); + IREmitter malformed_ir(malformed.blocks.front()); + auto malformed_value = malformed_ir.Emit(ValueOpcode::WqmMask, {Value(true)}); + malformed_value.TryInstruction()->SetArg(0, Value(uint64_t{1})); + ExpectFatal([&] { ValidateProgram(malformed, false); }, + "invalid WqmMask operand type did not terminate IR validation"); +#endif Program shifts; shifts.block_storage.push_back(std::make_unique()); @@ -9920,6 +9467,7 @@ void TestWqmMaskSignatureAndU64ShiftConstantPropagation() { } } +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS void TestNativeWideValueValidation() { using namespace ShaderRecompiler::IR; @@ -9955,19 +9503,15 @@ void TestNativeWideValueValidation() { ir.Emit(opcode, {resource, Value(0u), Value(0u), Value(true)}, flags); } }; - const auto check_rejected = [](const Program &program, - const char *expected) { - std::string error; - Check(!ValidateProgram(program, true, &error) && - Common::ContainsStr(error, expected), - "malformed native-wide value operation was not rejected"); + const auto check_rejected = [](const Program &program, const char *text) { + ExpectFatal([&] { ValidateProgram(program, true); }, text); }; { auto program = make_program(); append_scalar_read(program, ValueOpcode::ReadConstBuffer, MemoryFlags{.index = 1}); - check_rejected(program, "invalid memory-info index"); + check_rejected(program, "invalid scalar memory-info index was accepted"); } { auto program = make_program(); @@ -9975,7 +9519,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::ScalarAddress; program.memory_info.push_back(memory); append_scalar_read(program, ValueOpcode::ReadConstBuffer, {}); - check_rejected(program, "invalid scalar-memory resource kind"); + check_rejected(program, "invalid scalar-memory resource kind was accepted"); } { auto program = make_program(); @@ -9984,7 +9528,7 @@ void TestNativeWideValueValidation() { memory.data_bits = 16; program.memory_info.push_back(memory); append_scalar_read(program, ValueOpcode::LoadAddressU32, {}); - check_rejected(program, "inconsistent address-memory metadata"); + check_rejected(program, "inconsistent address-memory bits were accepted"); } { auto program = make_program(); @@ -9993,7 +9537,7 @@ void TestNativeWideValueValidation() { memory.data_dwords = 2; program.memory_info.push_back(memory); append_scalar_read(program, ValueOpcode::ReadConstBuffer, {}); - check_rejected(program, "inconsistent scalar-memory metadata"); + check_rejected(program, "inconsistent scalar-memory width was accepted"); } { auto program = make_program(); @@ -10002,7 +9546,8 @@ void TestNativeWideValueValidation() { memory.component_count = 3; program.memory_info.push_back(memory); append_scalar_read(program, ValueOpcode::ReadConstBuffer, {}); - check_rejected(program, "inconsistent scalar-memory metadata"); + check_rejected(program, + "inconsistent scalar-memory component count was accepted"); } { auto program = make_program(); @@ -10012,7 +9557,8 @@ void TestNativeWideValueValidation() { memory.component_index = 4; program.memory_info.push_back(memory); append_scalar_read(program, ValueOpcode::LoadAddressU32, {}); - check_rejected(program, "inconsistent address-memory metadata"); + check_rejected(program, + "inconsistent address-memory component index was accepted"); } { @@ -10022,7 +9568,7 @@ void TestNativeWideValueValidation() { memory.data_dwords = 3; program.memory_info.push_back(memory); append_load(program, ValueOpcode::LoadBufferU32x3, MemoryFlags{.index = 1}); - check_rejected(program, "invalid memory-info index"); + check_rejected(program, "invalid buffer memory-info index was accepted"); } { auto program = make_program(); @@ -10031,7 +9577,7 @@ void TestNativeWideValueValidation() { memory.data_dwords = 3; program.memory_info.push_back(memory); append_load(program, ValueOpcode::LoadBufferU32x3, {}); - check_rejected(program, "non-buffer resource kind"); + check_rejected(program, "non-buffer resource kind was accepted"); } { auto program = make_program(); @@ -10040,7 +9586,7 @@ void TestNativeWideValueValidation() { memory.data_dwords = 2; program.memory_info.push_back(memory); append_load(program, ValueOpcode::LoadBufferU32x3, {}); - check_rejected(program, "inconsistent native-wide metadata"); + check_rejected(program, "inconsistent native-wide metadata was accepted"); } { auto program = make_program(); @@ -10049,7 +9595,7 @@ void TestNativeWideValueValidation() { memory.data_dwords = 3; program.memory_info.push_back(memory); append_load(program, ValueOpcode::LoadBufferU32, {}); - check_rejected(program, "scalar-sibling width metadata"); + check_rejected(program, "scalar-sibling width metadata was accepted"); } { auto program = make_program(); @@ -10057,7 +9603,7 @@ void TestNativeWideValueValidation() { const auto vector = ir.Emit(ValueOpcode::CompositeConstructU32x3, {Value(1u), Value(2u), Value(3u)}); ir.Emit(ValueOpcode::CompositeExtractU32x3, {vector, Value(3u)}); - check_rejected(program, "invalid component index"); + check_rejected(program, "invalid U32x3 component index was accepted"); } { auto program = make_program(); @@ -10065,7 +9611,7 @@ void TestNativeWideValueValidation() { const auto vector = ir.Emit(ValueOpcode::CompositeConstructU64, {Value(1u), Value(2u)}); ir.Emit(ValueOpcode::CompositeExtractU64, {vector, Value(2u)}); - check_rejected(program, "invalid component index"); + check_rejected(program, "invalid U64 component index was accepted"); } { auto program = make_program(); @@ -10075,7 +9621,7 @@ void TestNativeWideValueValidation() { const auto dynamic_index = ir.Emit(ValueOpcode::IAdd32, {Value(0u), Value(1u)}); ir.Emit(ValueOpcode::CompositeExtractU64, {vector, dynamic_index}); - check_rejected(program, "invalid component index"); + check_rejected(program, "dynamic U64 component index was accepted"); } const auto append_shared = [](Program &program, ValueOpcode opcode, MemoryFlags flags) { @@ -10085,7 +9631,7 @@ void TestNativeWideValueValidation() { { auto program = make_program(); append_shared(program, ValueOpcode::LoadSharedU32, MemoryFlags{.index = 1}); - check_rejected(program, "invalid memory-info index"); + check_rejected(program, "invalid shared memory-info index was accepted"); } { auto program = make_program(); @@ -10093,7 +9639,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::Buffer; program.memory_info.push_back(memory); append_shared(program, ValueOpcode::LoadSharedU32, {}); - check_rejected(program, "invalid shared-memory metadata"); + check_rejected(program, "invalid shared-memory resource kind was accepted"); } { auto program = make_program(); @@ -10102,7 +9648,7 @@ void TestNativeWideValueValidation() { memory.resource = 1; program.memory_info.push_back(memory); append_shared(program, ValueOpcode::LoadSharedU32, {}); - check_rejected(program, "invalid shared-memory metadata"); + check_rejected(program, "invalid GDS resource metadata was accepted"); } { auto program = make_program(); @@ -10112,7 +9658,7 @@ void TestNativeWideValueValidation() { memory.component_count = 2; program.memory_info.push_back(memory); append_shared(program, ValueOpcode::LoadSharedU32x3, {}); - check_rejected(program, "inconsistent shared-memory width"); + check_rejected(program, "inconsistent shared-memory width was accepted"); } const auto append_address = [](Program &program, ValueOpcode opcode, MemoryFlags flags) { @@ -10130,7 +9676,7 @@ void TestNativeWideValueValidation() { auto program = make_program(); append_address(program, ValueOpcode::StoreAddressU32, MemoryFlags{.index = 1}); - check_rejected(program, "invalid memory-info index"); + check_rejected(program, "invalid address memory-info index was accepted"); } { auto program = make_program(); @@ -10138,7 +9684,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::Buffer; program.memory_info.push_back(memory); append_address(program, ValueOpcode::StoreAddressU32, {}); - check_rejected(program, "invalid address resource kind"); + check_rejected(program, "invalid address resource kind was accepted"); } { auto program = make_program(); @@ -10146,7 +9692,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::Global; program.memory_info.push_back(memory); append_address(program, ValueOpcode::LoadAddressU8, {}); - check_rejected(program, "inconsistent address-memory metadata"); + check_rejected(program, "inconsistent address load metadata was accepted"); } const auto append_image = [](Program &program, ValueOpcode opcode, MemoryFlags flags) { @@ -10180,7 +9726,7 @@ void TestNativeWideValueValidation() { { auto program = make_program(); append_image(program, ValueOpcode::ImageRead, MemoryFlags{.index = 1}); - check_rejected(program, "invalid memory-info index"); + check_rejected(program, "invalid image memory-info index was accepted"); } { auto program = make_program(); @@ -10188,7 +9734,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::Buffer; program.memory_info.push_back(memory); append_image(program, ValueOpcode::ImageRead, {}); - check_rejected(program, "invalid image-memory metadata"); + check_rejected(program, "buffer kind for image read was accepted"); } { auto program = make_program(); @@ -10196,7 +9742,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::StorageImage; program.memory_info.push_back(memory); append_image(program, ValueOpcode::ImageRead, {}); - check_rejected(program, "invalid image-memory metadata"); + check_rejected(program, "storage kind for image read was accepted"); } { auto program = make_program(); @@ -10204,7 +9750,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::StorageImageUint; program.memory_info.push_back(memory); append_image(program, ValueOpcode::ImageQueryDimensions, {}); - check_rejected(program, "invalid image-memory metadata"); + check_rejected(program, "storage uint kind for image query was accepted"); } { auto program = make_program(); @@ -10212,7 +9758,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::Image; program.memory_info.push_back(memory); append_image(program, ValueOpcode::ImageWrite, {}); - check_rejected(program, "invalid image-memory metadata"); + check_rejected(program, "sampled image kind for image write was accepted"); } { auto program = make_program(); @@ -10220,7 +9766,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::StorageImage; program.memory_info.push_back(memory); append_image(program, ValueOpcode::ImageSampleRaw, {}); - check_rejected(program, "invalid image-memory metadata"); + check_rejected(program, "storage kind for image sample was accepted"); } { auto program = make_program(); @@ -10228,7 +9774,7 @@ void TestNativeWideValueValidation() { memory.kind = ResourceKind::StorageImage; program.memory_info.push_back(memory); append_image(program, ValueOpcode::ImageAtomicIAdd32, {}); - check_rejected(program, "invalid image-memory metadata"); + check_rejected(program, "untyped storage kind for image atomic was accepted"); } { auto program = make_program(); @@ -10237,9 +9783,10 @@ void TestNativeWideValueValidation() { {Value(0u), Value(0u), Value(0u), Value(0u)}); ir.Emit(ValueOpcode::SetAttribute, {data, Value(true)}, ExportFlags{.index = 1}); - check_rejected(program, "invalid export-info index"); + check_rejected(program, "invalid export-info index was accepted"); } } +#endif void TestNewShaderRecompilerZeroInitialRegisterState() { const uint32_t shader[] = { @@ -10252,10 +9799,7 @@ void TestNewShaderRecompilerZeroInitialRegisterState() { auto options = MakeCompileOptions(ShaderType::Vertex); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(!Common::ContainsStr(result.ir_dump, "UndefU32"), "SSA initial state left guest registers undefined"); const auto source = DisassembleSpirvBinary(result.spirv); @@ -10272,10 +9816,8 @@ void TestNewShaderRecompilerZeroInitialRegisterState() { EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), // POS0 EncodeSopp(0x01), }; - ShaderRecompiler::CompileResult explicit_zero; - Check(ShaderRecompiler::TryRecompile(explicit_zero_shader, options, - explicit_zero, &error), - error.c_str()); + auto explicit_zero = + ShaderRecompiler::Recompile(explicit_zero_shader, options); Check(result.spirv == explicit_zero.spirv, "implicit and explicit zero register state emitted different SPIR-V"); } @@ -10296,10 +9838,7 @@ void TestNewShaderRecompilerVertexSystemInputsWithoutMirrors() { auto options = MakeCompileOptions(ShaderType::Vertex); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(ProgramHasInput(result.program, StageInputKind::VertexIndex), "vertex shader missing VertexIndex input"); Check(ProgramHasInput(result.program, StageInputKind::InstanceIndex), @@ -10327,10 +9866,7 @@ void TestNewShaderRecompilerVertexExportUsesInvocationExecMask() { auto options = MakeCompileOptions(ShaderType::Vertex); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); Check(!Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"), @@ -10355,10 +9891,7 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() { auto options = MakeCompileOptions(ShaderType::Vertex); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(local_shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(local_shader, options); CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); Check( @@ -10376,8 +9909,7 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() { EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), // POS0 EncodeSopp(0x01), }; - Check(ShaderRecompiler::TryRecompile(wqm_shader, options, result, &error), - error.c_str()); + result = ShaderRecompiler::Recompile(wqm_shader, options); CheckSpirvBinaryValidates(result.spirv); const auto wqm_source = DisassembleSpirvBinary(result.spirv); Check(!Common::ContainsStr(wqm_source, "OpGroupNonUniformBallot"), @@ -10385,9 +9917,7 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() { auto wave32_options = options; wave32_options.wave_size = 32u; - Check(ShaderRecompiler::TryRecompile(wqm_shader, wave32_options, result, - &error), - error.c_str()); + result = ShaderRecompiler::Recompile(wqm_shader, wave32_options); CheckSpirvBinaryValidates(result.spirv); Check(!Common::ContainsStr(DisassembleSpirvBinary(result.spirv), "OpGroupNonUniformBallot"), @@ -10401,9 +9931,7 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() { EncodeExp1(0, 1, 2, 3), // POS0 EncodeSopp(0x01), }; - Check(ShaderRecompiler::TryRecompile(cross_lane_shader, options, result, - &error), - error.c_str()); + result = ShaderRecompiler::Recompile(cross_lane_shader, options); CheckSpirvBinaryValidates(result.spirv); Check(Common::ContainsStr(DisassembleSpirvBinary(result.spirv), "OpGroupNonUniformBallot"), @@ -10425,10 +9953,7 @@ void TestNewShaderRecompilerPerInvocationU64Complement() { auto options = MakeCompileOptions(ShaderType::Vertex); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); const auto source = DisassembleSpirvBinary(result.spirv); Check(Common::ContainsStr(source, "OpLogicalNot"), @@ -10453,10 +9978,7 @@ void TestNewShaderRecompilerExpPixelOutputs() { auto options = MakeCompileOptions(ShaderType::Pixel); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(SpirvContainsOpcode(result.spirv, 62), "pixel export SPIR-V lacks OpStore"); Check(SpirvContainsExtInst(result.spirv, 62), @@ -10468,9 +9990,7 @@ void TestNewShaderRecompilerExpPixelOutputs() { ShaderPixelInputInfo uint16_info; uint16_info.target_output_mode[0] = 7; options.input_info.pixel = &uint16_info; - ShaderRecompiler::CompileResult uint16_result; - Check(ShaderRecompiler::TryRecompile(shader, options, uint16_result, &error), - error.c_str()); + auto uint16_result = ShaderRecompiler::Recompile(shader, options); const auto uint16_source = DisassembleSpirvBinary(uint16_result.spirv); Check(Common::ContainsStr(uint16_source, "OpVariable %_ptr_Output_v4uint Output"), @@ -10487,9 +10007,7 @@ void TestNewShaderRecompilerExpPixelOutputs() { ShaderPixelInputInfo unorm16_info; unorm16_info.target_output_mode[0] = 5; options.input_info.pixel = &unorm16_info; - ShaderRecompiler::CompileResult unorm16_result; - Check(ShaderRecompiler::TryRecompile(shader, options, unorm16_result, &error), - error.c_str()); + auto unorm16_result = ShaderRecompiler::Recompile(shader, options); const auto unorm16_source = DisassembleSpirvBinary(unorm16_result.spirv); Check(CountSourceOccurrences(unorm16_source, "UnpackUnorm2x16") == 2u, "compressed UNORM16 MRT export did not unpack two normalized pairs"); @@ -10504,10 +10022,7 @@ void TestNewShaderRecompilerExpPixelOutputs() { }; ShaderPixelInputInfo default_info; options.input_info.pixel = &default_info; - ShaderRecompiler::CompileResult partial_result; - Check(ShaderRecompiler::TryRecompile(partial_shader, options, partial_result, - &error), - error.c_str()); + auto partial_result = ShaderRecompiler::Recompile(partial_shader, options); const auto partial_source = DisassembleSpirvBinary(partial_result.spirv); Check(SpirvSourceHasInstructionOperand(partial_source, "OpBitcast", "%uint_1065353216"), @@ -10517,10 +10032,8 @@ void TestNewShaderRecompilerExpPixelOutputs() { ShaderPixelInputInfo uint_info; uint_info.target_output_mode[0] = 7; options.input_info.pixel = &uint_info; - ShaderRecompiler::CompileResult partial_uint_result; - Check(ShaderRecompiler::TryRecompile(partial_shader, options, - partial_uint_result, &error), - error.c_str()); + auto partial_uint_result = + ShaderRecompiler::Recompile(partial_shader, options); const auto partial_uint_source = DisassembleSpirvBinary(partial_uint_result.spirv); Check(SpirvSourceHasInstructionOperand(partial_uint_source, @@ -10533,10 +10046,8 @@ void TestNewShaderRecompilerExpPixelOutputs() { EncodeExp1(0, 1, 0, 0), 0xbf810000u, }; - ShaderRecompiler::CompileResult compressed_ba_result; - Check(ShaderRecompiler::TryRecompile(compressed_ba_shader, options, - compressed_ba_result, &error), - error.c_str()); + auto compressed_ba_result = + ShaderRecompiler::Recompile(compressed_ba_shader, options); const auto compressed_ba_source = DisassembleSpirvBinary(compressed_ba_result.spirv); Check(CountSourceOccurrences(compressed_ba_source, "OpCompositeExtract") == @@ -10548,10 +10059,8 @@ void TestNewShaderRecompilerExpPixelOutputs() { unorm16_info.target_output_mode[0] = 5; options.input_info.pixel = &unorm16_info; - ShaderRecompiler::CompileResult unorm16_ba_result; - Check(ShaderRecompiler::TryRecompile(compressed_ba_shader, options, - unorm16_ba_result, &error), - error.c_str()); + auto unorm16_ba_result = + ShaderRecompiler::Recompile(compressed_ba_shader, options); const auto unorm16_ba_source = DisassembleSpirvBinary(unorm16_ba_result.spirv); Check(CountSourceOccurrences(unorm16_ba_source, "UnpackUnorm2x16") == 1u, @@ -10697,35 +10206,24 @@ void TestRenderTargetReverseExportMapping() { auto options = MakeCompileOptions(ShaderType::Pixel); options.input_info.pixel = &identity_info; - ShaderRecompiler::CompileResult identity_result; - std::string error; - Check( - ShaderRecompiler::TryRecompile(shader, options, identity_result, &error), - error.c_str()); + auto identity_result = ShaderRecompiler::Recompile(shader, options); Check(SpirvInstructionOpcodeCount(identity_result.spirv, 79u) == 0u, "identity MRT export unexpectedly added a component shuffle"); options.input_info.pixel = &reversed_info; - ShaderRecompiler::CompileResult reversed_result; - Check( - ShaderRecompiler::TryRecompile(shader, options, reversed_result, &error), - error.c_str()); + auto reversed_result = ShaderRecompiler::Recompile(shader, options); Check(SpirvContainsVectorShuffle(reversed_result.spirv, {3u, 2u, 1u, 0u}), "reverse MRT export did not emit a WZYX component shuffle"); CheckSpirvBinaryValidates(reversed_result.spirv); reversed_info.target_export_mapping[0] = gr32.export_mapping; - ShaderRecompiler::CompileResult gr32_result; - Check(ShaderRecompiler::TryRecompile(shader, options, gr32_result, &error), - error.c_str()); + auto gr32_result = ShaderRecompiler::Recompile(shader, options); Check(SpirvContainsVectorShuffle(gr32_result.spirv, {1u, 0u, 2u, 3u}), "reverse GR32F MRT export did not emit a YXZW component shuffle"); CheckSpirvBinaryValidates(gr32_result.spirv); reversed_info.target_export_mapping[0] = argb.export_mapping; - ShaderRecompiler::CompileResult argb_result; - Check(ShaderRecompiler::TryRecompile(shader, options, argb_result, &error), - error.c_str()); + auto argb_result = ShaderRecompiler::Recompile(shader, options); Check(SpirvContainsVectorShuffle(argb_result.spirv, {3u, 0u, 1u, 2u}), "alternate-reversed MRT export did not emit an ARGB component shuffle"); CheckSpirvBinaryValidates(argb_result.spirv); @@ -10771,10 +10269,7 @@ void TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled() { auto options = MakeCompileOptions(ShaderType::Pixel); options.input_info.pixel = &ps_info; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(SpirvInstructionOpcodeCount(result.spirv, 252) != 0, "pixel valid-mask export should still lower to OpKill"); Check(!SpirvContainsExecutionMode(result.spirv, @@ -10789,10 +10284,7 @@ void TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled() { 0xbf810000u, }; ps_info.ps_pixel_kill_enable = false; - ShaderRecompiler::CompileResult ordinary_result; - Check(ShaderRecompiler::TryRecompile(ordinary_shader, options, - ordinary_result, &error), - error.c_str()); + auto ordinary_result = ShaderRecompiler::Recompile(ordinary_shader, options); const auto ordinary_source = DisassembleSpirvBinary(ordinary_result.spirv); Check(SpirvInstructionOpcodeCount(ordinary_result.spirv, 252) == 0, "ordinary pixel shader unexpectedly contains OpKill"); @@ -10830,11 +10322,7 @@ void TestNewShaderRecompilerNativeBindingPlan() { options.dump_ir = true; options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - std::string error; - const auto compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); const auto *buffers = ShaderRecompiler::IR::FindBinding( result.program.bindings, BindingKind::Buffers); const auto *sampled = ShaderRecompiler::IR::FindBinding( @@ -10874,31 +10362,20 @@ void TestNewShaderRecompilerNativeBindingPlan() { "SPIR-V resources do not all use descriptor set zero"); CheckSpirvBinaryValidates(result.spirv); - ShaderRecompiler::CompileResult malformed_result; - std::string malformed_compile_error; - Check(ShaderRecompiler::TryRecompile(shader, options, malformed_result, - &malformed_compile_error), - malformed_compile_error.c_str()); - auto malformed = std::move(malformed_result.program); - auto malformed_buffers = std::find_if( - malformed.bindings.descriptors.begin(), - malformed.bindings.descriptors.end(), - [](const auto &binding) { return binding.kind == BindingKind::Buffers; }); - Check(malformed_buffers != malformed.bindings.descriptors.end(), - "native validation fixture lacks a buffer group"); - malformed_buffers->resources.clear(); - std::vector rejected_spirv = {0xdeadbeefu}; - const auto rejected_before = rejected_spirv; - std::string rejected_error; - Check(!ShaderRecompiler::Spirv::EmitProgram( - malformed, malformed_result.resources, options.input_info, - rejected_spirv, - &rejected_error) && - rejected_spirv == rejected_before && - rejected_error.find("topology") != std::string::npos, - "malformed native binding plan was not rejected transactionally"); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + ExpectFatal( + [&] { + auto malformed = ShaderRecompiler::Recompile(shader, options); + auto malformed_buffers = std::ranges::find_if( + malformed.program.bindings.descriptors, [](const auto &binding) { + return binding.kind == BindingKind::Buffers; + }); + malformed_buffers->resources.clear(); + (void)ShaderRecompiler::Spirv::EmitProgram( + malformed.program, malformed.resources, options.input_info); + }, + "malformed native binding topology did not terminate SPIR-V emission"); - auto stale_resources = result.resources; uint32_t float_storage = UINT32_MAX; for (uint32_t i = 0; i < result.program.info.images.size(); i++) { if (result.program.info.images[i].kind == @@ -10909,26 +10386,22 @@ void TestNewShaderRecompilerNativeBindingPlan() { } Check(float_storage != UINT32_MAX, "native validation fixture lacks float storage image"); - stale_resources.images[float_storage].dwords[1] = 20u << 20u; - rejected_spirv = rejected_before; - rejected_error.clear(); - Check(!ShaderRecompiler::Spirv::EmitProgram( - result.program, stale_resources, options.input_info, rejected_spirv, - &rejected_error) && - rejected_spirv == rejected_before && - rejected_error.find("specialized format") != std::string::npos, - "stale runtime image format re-selected an absent descriptor group"); - - auto stale_dimension = result.resources; - stale_dimension.images[float_storage].dwords[3] &= 0x0fffffffu; - rejected_spirv = rejected_before; - rejected_error.clear(); - Check(!ShaderRecompiler::Spirv::EmitProgram( - result.program, stale_dimension, options.input_info, rejected_spirv, - &rejected_error) && - rejected_spirv == rejected_before && - rejected_error.find("specialized dimension") != std::string::npos, - "unsupported runtime image type bypassed specialization validation"); + ExpectFatal( + [&] { + auto stale_resources = result.resources; + stale_resources.images[float_storage].dwords[1] = 20u << 20u; + (void)ShaderRecompiler::Spirv::EmitProgram( + result.program, stale_resources, options.input_info); + }, + "stale specialized image format did not terminate SPIR-V emission"); + ExpectFatal( + [&] { + auto stale_dimension = result.resources; + stale_dimension.images[float_storage].dwords[3] &= 0x0fffffffu; + (void)ShaderRecompiler::Spirv::EmitProgram( + result.program, stale_dimension, options.input_info); + }, + "stale specialized image dimension did not terminate SPIR-V emission"); ShaderRecompiler::IR::Inst *buffer_handle = nullptr; for (auto *block : result.program.blocks) { @@ -10942,38 +10415,28 @@ void TestNewShaderRecompilerNativeBindingPlan() { } } Check(buffer_handle != nullptr, - "typed validation fixture lacks a buffer handle"); - const auto dense = buffer_handle->Flags(); - buffer_handle->SetFlags(UINT32_MAX); - rejected_spirv = rejected_before; - rejected_error.clear(); - const bool rejected = !ShaderRecompiler::Spirv::EmitProgram( - result.program, result.resources, options.input_info, rejected_spirv, - &rejected_error); - buffer_handle->SetFlags(dense); - Check(rejected && rejected_spirv == rejected_before && - rejected_error.find("invalid dense resource") != std::string::npos, - "invalid typed buffer handle was not rejected transactionally"); + "native validation fixture lacks a buffer handle"); + ExpectFatal( + [&] { + buffer_handle->SetFlags(UINT32_MAX); + (void)ShaderRecompiler::Spirv::EmitProgram( + result.program, result.resources, options.input_info); + }, + "invalid dense buffer resource did not terminate SPIR-V emission"); +#endif } -bool BuildTypedPlan(const uint32_t *code, uint32_t words, - ShaderRecompiler::IR::Program &ir, std::string *error) { +void BuildTypedPlan(const uint32_t *code, uint32_t words, + ShaderRecompiler::IR::Program &ir) { ShaderRecompiler::Decoder::Program decoded; - if (!ShaderRecompiler::Decoder::DecodeProgram(std::span{code, words}, decoded, - error)) { - return false; - } - ShaderRecompiler::CFG::Graph cfg; + ShaderRecompiler::Decoder::DecodeProgram(std::span{code, words}, decoded); + auto cfg = ShaderRecompiler::CFG::BuildGraph(decoded); ShaderComputeInputInfo compute; ShaderRecompiler::Frontend::TranslateOptions options{}; options.stage = ShaderType::Compute; options.wave_size = 64u; options.compute = &compute; - if (!ShaderRecompiler::CFG::BuildGraph(decoded, cfg, error) || - !ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options, ir, - error)) { - return false; - } + ir = ShaderRecompiler::Frontend::TranslateProgram(decoded, cfg, options); ShaderRecompiler::IR::RewriteToSsa(ir.blocks); ShaderRecompiler::IR::ConstantPropagationPass(ir.blocks); ShaderRecompiler::IR::ResolveControlFlowIdentities(ir); @@ -10987,11 +10450,8 @@ bool BuildTypedPlan(const uint32_t *code, uint32_t words, ShaderRecompiler::IR::RemoveIdentities(ir.blocks); ShaderRecompiler::IR::EliminateDeadCode(ir.blocks); } - if (!ShaderRecompiler::IR::BuildSrtPlan(ir, error)) { - return false; - } + ShaderRecompiler::IR::BuildSrtPlan(ir); ShaderRecompiler::IR::EliminateDeadCode(ir.blocks); - return true; } const ShaderRecompiler::IR::DescriptorSource * @@ -11088,13 +10548,9 @@ void TestTypedDescriptorRealWideMoveTranslation() { EncodeMubuf1(0, 0, 1), // buffer_store_dword via copied s[0:3] EncodeSopp(0x01), }; - - std::string error; ShaderRecompiler::IR::Program ir; - const auto translated = BuildTypedPlan( - shader, static_cast(std::size(shader)), ir, &error); - Check(translated, error.c_str()); - Check(ShaderRecompiler::IR::TrackResources(ir, &error), error.c_str()); + BuildTypedPlan(shader, static_cast(std::size(shader)), ir); + ShaderRecompiler::IR::TrackResources(ir); Check(ir.info.buffers.size() == 1, "real wide-move shader did not track one buffer use"); const auto *source = TypedDescriptorSource(ir, ir.info.buffers[0].source); @@ -11122,12 +10578,9 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { EncodeMubuf1(0, 0, 1), // buffer_store_dword via computed s[0:3] EncodeSopp(0x01), }; - std::string error; ShaderRecompiler::IR::Program carry_ir; - const auto carry_translated = BuildTypedPlan( - carry_shader, static_cast(std::size(carry_shader)), carry_ir, - &error); - Check(carry_translated, error.c_str()); + BuildTypedPlan(carry_shader, static_cast(std::size(carry_shader)), + carry_ir); const ShaderRecompiler::IR::Inst *carry_handle = nullptr; for (const auto *block : carry_ir.blocks) { for (const auto &inst : *block) { @@ -11167,8 +10620,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { nullptr, nullptr}; ShaderRecompiler::IR::DescriptorValue carry_value; Check(ShaderRecompiler::IR::EvaluateDescriptorSource( - carry_ir, carry_source_index, 0, carry_runtime, carry_value, - &error) && + carry_ir, carry_source_index, carry_runtime, carry_value) && carry_value.dwords[0] == static_cast(expected_pc) && carry_value.dwords[1] == static_cast(expected_pc >> 32u), "S_GETPC shader-base or add/addc carry evaluation was incorrect"); @@ -11185,10 +10637,8 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { EncodeSopp(0x01), }; ShaderRecompiler::IR::Program load_ir; - const auto load_translated = BuildTypedPlan( - load_shader, static_cast(std::size(load_shader)), load_ir, - &error); - Check(load_translated, error.c_str()); + BuildTypedPlan(load_shader, static_cast(std::size(load_shader)), + load_ir); Check(load_ir.srt_reads.size() == 8 && load_ir.dynamic_reads.empty(), "real scalar loads did not build eight flattened reads"); @@ -11205,7 +10655,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { } Check(address_reads == 4 && buffer_reads == 4, "real scalar loads used the wrong raw typed operations"); - Check(ShaderRecompiler::IR::TrackResources(load_ir, &error), error.c_str()); + ShaderRecompiler::IR::TrackResources(load_ir); Check(load_ir.info.buffers.size() == 2, "real scalar-load descriptor sources were not attached"); for (const auto &buffer : load_ir.info.buffers) { @@ -11233,13 +10683,10 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { EncodeSopp(0x01), }; ShaderRecompiler::IR::Program inline_sampler_ir; - error.clear(); - Check(BuildTypedPlan( - inline_sampler_shader, - static_cast(std::size(inline_sampler_shader)), - inline_sampler_ir, &error) && - ShaderRecompiler::IR::TrackResources(inline_sampler_ir, &error), - error.c_str()); + BuildTypedPlan(inline_sampler_shader, + static_cast(std::size(inline_sampler_shader)), + inline_sampler_ir); + ShaderRecompiler::IR::TrackResources(inline_sampler_ir); ShaderRecompiler::IR::DescriptorValue sampler; ShaderRecompiler::IR::SrtRuntime runtime; Check(inline_sampler_ir.info.samplers.size() == 1 && @@ -11248,7 +10695,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { nullptr && ShaderRecompiler::IR::EvaluateDescriptorSource( inline_sampler_ir, inline_sampler_ir.info.samplers[0].source, - 0x10, runtime, sampler, &error) && + runtime, sampler) && sampler.dwords[0] == 0 && sampler.dwords[1] == 0x00fff000u && sampler.dwords[2] == 0x09500000u && sampler.dwords[3] == 0, "real inline sampler construction was unresolved or evaluated " @@ -11257,11 +10704,7 @@ void TestTypedDescriptorRealCarryAndScalarLoads() { auto user_data = ImageTestUserData(); auto options = MakeCompileOptions(ShaderType::Pixel); options.user_data = user_data.data(); - ShaderRecompiler::CompileResult result; - error.clear(); - Check(ShaderRecompiler::TryRecompile(inline_sampler_shader, options, result, - &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(inline_sampler_shader, options); } void TestSrtWalkerRealSmemTranslation() { @@ -11274,11 +10717,8 @@ void TestSrtWalkerRealSmemTranslation() { EncodeMubuf1(0, 0, 1), EncodeSopp(0x01), }; - std::string error; ShaderRecompiler::IR::Program ir; - const auto translated = BuildTypedPlan( - shader, static_cast(std::size(shader)), ir, &error); - Check(translated, error.c_str()); + BuildTypedPlan(shader, static_cast(std::size(shader)), ir); Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(), "real SMEM translation did not build four compact SRT reads"); @@ -11291,8 +10731,8 @@ void TestSrtWalkerRealSmemTranslation() { std::vector flat; const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword, nullptr}; - const auto walked = ShaderRecompiler::IR::WalkSrt(ir, runtime, flat, &error); - Check(walked, error.c_str()); + const auto walked = ShaderRecompiler::IR::WalkSrt(ir, runtime, flat); + Check(walked, "SRT walk failed"); Check(flat.size() == table.size() && std::equal(flat.begin(), flat.end(), table.begin()), "real SMEM SRT walk did not apply component-level alignment"); @@ -11306,11 +10746,8 @@ void TestSrtWalkerVccBaseTranslation() { EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1), EncodeSopp(0x01), }; - std::string error; ShaderRecompiler::IR::Program ir; - Check(BuildTypedPlan(shader, static_cast(std::size(shader)), ir, - &error), - error.c_str()); + BuildTypedPlan(shader, static_cast(std::size(shader)), ir); Check(ir.srt_reads.size() == 4, "VCC-based SMEM translation did not build four SRT reads"); @@ -11323,8 +10760,7 @@ void TestSrtWalkerVccBaseTranslation() { const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword, nullptr}; std::vector flat; - Check(ShaderRecompiler::IR::WalkSrt(ir, runtime, flat, &error), - error.c_str()); + Check(ShaderRecompiler::IR::WalkSrt(ir, runtime, flat), "SRT walk failed"); Check(flat.size() == table.size() && std::equal(flat.begin(), flat.end(), table.begin()), "typed SSA lost an SMEM base copied through VCC"); @@ -11337,11 +10773,8 @@ void TestSrtWalkerRealSBufferTranslation() { (124u << 25u) | 2u, // s_buffer_load_dwordx4 s[0:3], s[8:11], m0 + 2 EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1), EncodeSopp(0x01), }; - std::string error; ShaderRecompiler::IR::Program ir; - const auto translated = BuildTypedPlan( - shader, static_cast(std::size(shader)), ir, &error); - Check(translated, error.c_str()); + BuildTypedPlan(shader, static_cast(std::size(shader)), ir); Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(), "real S_BUFFER_LOAD translation did not build four compact reads"); @@ -11355,18 +10788,16 @@ void TestSrtWalkerRealSBufferTranslation() { std::vector flat; const ShaderRecompiler::IR::SrtRuntime runtime{user_data, 0, ReadSrtHostDword, nullptr}; - const auto walked = ShaderRecompiler::IR::WalkSrt(ir, runtime, flat, &error); - Check(walked, error.c_str()); + const auto walked = ShaderRecompiler::IR::WalkSrt(ir, runtime, flat); + Check(walked, "SRT walk failed"); Check(flat.size() == 4 && std::equal(flat.begin(), flat.end(), table.begin() + 1), "real S_BUFFER_LOAD walk used the wrong final alignment"); user_data[10] = 4 * sizeof(uint32_t); const auto flat_before_failure = flat; - const auto bounds_walked = - ShaderRecompiler::IR::WalkSrt(ir, runtime, flat, &error); - Check(!bounds_walked && error.find("exceeds size 16") != std::string::npos, - "real S_BUFFER_LOAD walk ignored descriptor bounds"); + const auto bounds_walked = ShaderRecompiler::IR::WalkSrt(ir, runtime, flat); + Check(!bounds_walked, "real S_BUFFER_LOAD walk ignored descriptor bounds"); Check(flat == flat_before_failure, "failed real S_BUFFER_LOAD walk changed the prior flat snapshot"); CheckFlattenedReadSlots( @@ -11378,13 +10809,11 @@ void TestSrtWalkerRealSBufferTranslation() { EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1), EncodeSopp(0x01), }; ShaderRecompiler::IR::Program negative_ir; - const auto negative_translated = BuildTypedPlan( - negative_shader, static_cast(std::size(negative_shader)), - negative_ir, &error); - Check(negative_translated, error.c_str()); + BuildTypedPlan(negative_shader, + static_cast(std::size(negative_shader)), + negative_ir); user_data[10] = sizeof(table); - Check(!ShaderRecompiler::IR::WalkSrt(negative_ir, runtime, flat, &error) && - error.find("negative immediate") != std::string::npos, + Check(!ShaderRecompiler::IR::WalkSrt(negative_ir, runtime, flat), "real S_BUFFER_LOAD walk accepted a negative immediate"); } @@ -11402,11 +10831,8 @@ void TestScalarMemorySourcesCapturedBeforeWrites() { soffset << 25u, // load s[0:3] with overlapping source EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1), EncodeSopp(0x01)}); - std::string error; ShaderRecompiler::IR::Program ir; - Check(BuildTypedPlan(shader.data(), - static_cast(shader.size()), ir, &error), - error.c_str()); + BuildTypedPlan(shader.data(), static_cast(shader.size()), ir); Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(), opcode == 0x02 ? "overlapping S_LOAD operands were evaluated after a " "component write" @@ -11426,8 +10852,7 @@ void TestScalarMemorySourcesCapturedBeforeWrites() { const ShaderRecompiler::IR::SrtRuntime runtime{ user_data, 0, ReadSrtHostRangeDword, &range}; std::vector flat; - Check(ShaderRecompiler::IR::WalkSrt(ir, runtime, flat, &error), - error.c_str()); + Check(ShaderRecompiler::IR::WalkSrt(ir, runtime, flat), "SRT walk failed"); Check(flat.size() == table.size() && std::equal(flat.begin(), flat.end(), table.begin()), "overlapping scalar-memory load did not capture its sources before " @@ -11451,16 +10876,13 @@ void TestScalarMemoryLoadCrossesIntoVcc() { EncodeMubuf1(0, 26, 1), EncodeSopp(0x01), }; - std::string error; ShaderRecompiler::IR::Program ir; - Check(BuildTypedPlan(shader, static_cast(std::size(shader)), ir, - &error), - error.c_str()); + BuildTypedPlan(shader, static_cast(std::size(shader)), ir); Check(ir.srt_reads.size() == 4 && ir.dynamic_reads.empty(), "wide SMEM destination crossing into VCC lost scalar provenance"); CheckFlattenedReadSlots( ir, 4, "wide SMEM destination crossing into VCC used wrong flat offsets"); - Check(ShaderRecompiler::IR::TrackResources(ir, &error), error.c_str()); + ShaderRecompiler::IR::TrackResources(ir); Check(ir.info.buffers.size() == 1, "wide SMEM destination crossing into VCC lost its buffer use"); const auto *source = @@ -11485,10 +10907,7 @@ void TestScalarMemoryUnusedTailDce() { EncodeSopp(0x01), }; ShaderRecompiler::IR::Program ir; - std::string error; - Check(BuildTypedPlan(shader, static_cast(std::size(shader)), ir, - &error), - error.c_str()); + BuildTypedPlan(shader, static_cast(std::size(shader)), ir); uint32_t reads = 0; for (const auto *block : ir.blocks) { for (const auto &inst : *block) { @@ -11523,13 +10942,9 @@ void TestResourceTrackingRealDensePatching() { EncodeMimg1(8, 0, 0, 1), // keep sample live in storage view EncodeSopp(0x01), }; - std::string error; ShaderRecompiler::IR::Program ir; - const auto translated = BuildTypedPlan( - shader, static_cast(std::size(shader)), ir, &error); - Check(translated, error.c_str()); - const auto tracked = ShaderRecompiler::IR::TrackResources(ir, &error); - Check(tracked, error.c_str()); + BuildTypedPlan(shader, static_cast(std::size(shader)), ir); + ShaderRecompiler::IR::TrackResources(ir); Check(ir.info.buffers.size() == 2 && ir.info.images.size() == 2 && ir.info.samplers.size() == 1, "real resource tracking produced the wrong dense list sizes"); @@ -11593,27 +11008,20 @@ void TestResourceTrackingRealDensePatching() { void TestDirectTranslationResetsAnalysisState() { const uint32_t first_shader[] = {EncodeMubuf0(0x1c), EncodeMubuf1(0, 0, 1), EncodeSopp(0x01)}; - std::string error; ShaderRecompiler::IR::Program ir; - Check(BuildTypedPlan(first_shader, - static_cast(std::size(first_shader)), ir, - &error), - error.c_str()); - Check(ShaderRecompiler::IR::TrackResources(ir, &error), error.c_str()); + BuildTypedPlan(first_shader, static_cast(std::size(first_shader)), + ir); + ShaderRecompiler::IR::TrackResources(ir); ShaderComputeInputInfo compute; - Check(ShaderRecompiler::IR::CollectShaderInfo(ir, {.compute = &compute}, - &error), - error.c_str()); + ShaderRecompiler::IR::CollectShaderInfo(ir, {.compute = &compute}); Check(ir.resource_tracking_complete && ir.shader_info_complete && !ir.info.buffers.empty(), "analysis-reset fixture did not reach completed state"); ir.shader_hash = 0xdeadbeef; const uint32_t second_shader[] = {EncodeSopp(0x01)}; - Check(BuildTypedPlan(second_shader, - static_cast(std::size(second_shader)), ir, - &error), - error.c_str()); + BuildTypedPlan(second_shader, static_cast(std::size(second_shader)), + ir); Check(!ir.resource_tracking_complete && !ir.shader_info_complete && ir.srt_plan_complete && ir.srt_reads.empty() && ir.shader_hash == 0 && @@ -11636,10 +11044,7 @@ void TestNewShaderRecompilerStageInputInfo() { auto cs_options = MakeCompileOptions(ShaderType::Compute); cs_options.input_info.compute = &cs_info; - ShaderRecompiler::CompileResult cs_result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, cs_options, cs_result, &error), - error.c_str()); + auto cs_result = ShaderRecompiler::Recompile(shader, cs_options); Check(ProgramHasInput(cs_result.program, StageInputKind::WorkgroupId), "compute WorkgroupId input missing from reflection"); Check(ProgramHasInput(cs_result.program, StageInputKind::LocalInvocationId), @@ -11661,9 +11066,7 @@ void TestNewShaderRecompilerStageInputInfo() { auto vs_options = MakeCompileOptions(ShaderType::Vertex); - ShaderRecompiler::CompileResult vs_result; - Check(ShaderRecompiler::TryRecompile(shader, vs_options, vs_result, &error), - error.c_str()); + auto vs_result = ShaderRecompiler::Recompile(shader, vs_options); Check(ProgramHasInput(vs_result.program, StageInputKind::VertexIndex), "vertex VertexIndex input missing from reflection"); Check(ProgramHasInput(vs_result.program, StageInputKind::InstanceIndex), @@ -11684,9 +11087,7 @@ void TestNewShaderRecompilerStageInputInfo() { auto ps_options = MakeCompileOptions(ShaderType::Pixel); ps_options.input_info.pixel = &ps_info; - ShaderRecompiler::CompileResult ps_result; - Check(ShaderRecompiler::TryRecompile(shader, ps_options, ps_result, &error), - error.c_str()); + auto ps_result = ShaderRecompiler::Recompile(shader, ps_options); Check(ProgramHasInput(ps_result.program, StageInputKind::FragCoord), "pixel FragCoord input missing from reflection"); Check(ProgramInputCount(ps_result.program, StageInputKind::Parameter) == 2, @@ -11706,10 +11107,7 @@ void TestNewShaderRecompilerStageInputInfo() { SetIdentityInterpolatorSettings(&ps_pos_y_info); ps_options.input_info.pixel = &ps_pos_y_info; - ShaderRecompiler::CompileResult ps_pos_y_result; - Check(ShaderRecompiler::TryRecompile(shader, ps_options, ps_pos_y_result, - &error), - error.c_str()); + auto ps_pos_y_result = ShaderRecompiler::Recompile(shader, ps_options); Check(ProgramHasInput(ps_pos_y_result.program, StageInputKind::FragCoord), "pixel POS_Y-only FragCoord input missing from reflection"); Check(SpirvHasDecorationValue(ps_pos_y_result.spirv, 11u, 15u), @@ -11763,8 +11161,7 @@ void TestNewShaderRecompilerPixelPipelineEntry() { ShaderPixelInputInfo vcc_input{}; const auto vcc_params = PrepareProgram(vcc_regs, sh, vertex_info, mappings, vcc_input); - std::string error; - Check(CompilePixelRuntime(vcc_params, vcc_input, &error), error.c_str()); + CompilePixelRuntime(vcc_params, vcc_input); } void TestComputeLdsAllocationIdentity() { @@ -11798,10 +11195,7 @@ void TestComputeLdsAllocationIdentity() { options.input_info.compute = &input_info; options.wave_size = input_info.wave_size; options.scratch_dwords = input_info.scratch_size_dwords; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(MeasureSpirv(result.spirv).workgroup_variables == 1u, "LDS shader did not declare exactly one Workgroup variable"); Check(SpirvArrayLengthCount(result.spirv, expected_dwords) == 1u, @@ -11870,11 +11264,8 @@ void TestComputeLdsAllocationIdentity() { scratch_options.input_info.compute = &scratch_info; scratch_options.wave_size = scratch_info.wave_size; scratch_options.scratch_dwords = scratch_info.scratch_size_dwords; - ShaderRecompiler::CompileResult scratch_result; - std::string scratch_error; - Check(ShaderRecompiler::TryRecompile(scratch_params.code, scratch_options, - scratch_result, &scratch_error), - scratch_error.c_str()); + auto scratch_result = + ShaderRecompiler::Recompile(scratch_params.code, scratch_options); Check(scratch_result.program.scratch_dwords == 7, "AGC per-thread scratch size did not reach the compiler program"); Check(MakeStageStaticKey(scratch_info) != MakeStageStaticKey(lds_896), @@ -11890,11 +11281,8 @@ void TestComputeLdsAllocationIdentity() { append_info.lds_size_dwords = 1152u; auto append_options = MakeCompileOptions(ShaderType::Compute); append_options.input_info.compute = &append_info; - ShaderRecompiler::CompileResult append_result; - std::string error; - Check(ShaderRecompiler::TryRecompile(append_shader, append_options, - append_result, &error), - error.c_str()); + auto append_result = + ShaderRecompiler::Recompile(append_shader, append_options); Check(SpirvUnsignedLessThanBoundCount(append_result.spirv, 1152u) == 1u, "typed LDS append omitted the declared allocation bound"); CheckSpirvBinaryValidates(append_result.spirv); @@ -11919,10 +11307,7 @@ void TestWave64LdsSynchronization() { auto options = MakeCompileOptions(ShaderType::Compute); options.wave_size = wave_size; options.input_info.compute = &input_info; - ShaderRecompiler::CompileResult result; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), - error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); return result; }; @@ -12062,8 +11447,7 @@ void TestPixelProgramCacheBindingIdentity() { const auto first_params = PrepareProgram(regs, sh, vertex_info, identity_mappings, first_info); const auto first_key = MakeStageStaticKey(first_info); - std::string error; - Check(CompilePixelRuntime(first_params, first_info, &error), error.c_str()); + CompilePixelRuntime(first_params, first_info); ShaderPixelInputInfo second_info{}; const auto second_params = @@ -12114,23 +11498,19 @@ void TestPixelProgramCacheBindingIdentity() { const auto params_at_zero = PrepareProgram(push_regs, push_sh, vertex_at_zero, mappings, at_zero); const auto key_at_zero = MakeStageStaticKey(at_zero); - std::string error; - Check(CompilePixelRuntime(params_at_zero, at_zero, &error), error.c_str()); + CompilePixelRuntime(params_at_zero, at_zero); const auto params_at_36 = PrepareProgram(push_regs, push_sh, vertex_at_36, mappings, at_36); const auto key_at_36 = MakeStageStaticKey(at_36); - Check(CompilePixelRuntime(params_at_36, at_36, &error), error.c_str()); + CompilePixelRuntime(params_at_36, at_36); const auto params_at_zero_again = PrepareProgram(push_regs, push_sh, vertex_at_zero, mappings, at_zero_again); auto shifted_options = MakeCompileOptions(ShaderType::Pixel); shifted_options.input_info.pixel = &at_36; shifted_options.push_constant_offset = at_36.push_constant_offset; - ShaderRecompiler::CompileResult shifted_result; - std::string shifted_error; - Check(ShaderRecompiler::TryRecompile(push_shader, shifted_options, - shifted_result, &shifted_error), - shifted_error.c_str()); + auto shifted_result = + ShaderRecompiler::Recompile(push_shader, shifted_options); Check(at_zero.push_constant_offset == 0 && at_36.push_constant_offset == 36 && key_at_zero != key_at_36 && @@ -12161,10 +11541,7 @@ void TestGraphicsPushConstantPlacement() { options.user_data_count = static_cast(user_data.size()); options.push_constant_offset = 36; - ShaderRecompiler::CompileResult placed; - std::string error; - Check(ShaderRecompiler::TryRecompile(shader, options, placed, &error), - error.c_str()); + auto placed = ShaderRecompiler::Recompile(shader, options); Check(placed.program.bindings.push_constant_offset == 36 && placed.program.bindings.push_constant_size == sizeof(user_data) && ShaderRecompiler::IR::FindBinding(placed.program.bindings, @@ -12175,10 +11552,7 @@ void TestGraphicsPushConstantPlacement() { CheckSpirvBinaryValidates(placed.spirv); options.push_constant_offset = 124; - ShaderRecompiler::CompileResult spilled; - error.clear(); - Check(ShaderRecompiler::TryRecompile(shader, options, spilled, &error), - error.c_str()); + auto spilled = ShaderRecompiler::Recompile(shader, options); Check(spilled.program.bindings.push_constant_offset == 124 && spilled.program.bindings.push_constant_size == 0 && ShaderRecompiler::IR::FindBinding(spilled.program.bindings, @@ -12229,11 +11603,7 @@ void TestNewShaderRecompilerFlatUserPointerUsesDma() { options.user_data = user_data; options.user_data_count = static_cast(std::size(user_data)); - ShaderRecompiler::CompileResult result; - std::string error; - const bool compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); Check(result.program.info.uses_dma, "FLAT user pointer did not enable DMA"); CheckSpirvBinaryValidates(result.spirv); @@ -12255,11 +11625,7 @@ void TestNewShaderRecompilerFlatAddressDomainsUseDma() { options.user_data = user_data; options.user_data_count = static_cast(std::size(user_data)); options.scratch_dwords = 1; - ShaderRecompiler::CompileResult result; - std::string error; - const bool compiled = - ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(segmented_shader, options); Check(result.program.info.uses_dma, "GLOBAL null-SADDR did not enable DMA"); Check(Common::ContainsStr(result.ir_dump, "GetScratchResource") && @@ -12274,11 +11640,7 @@ void TestNewShaderRecompilerSpirvSizeBaselines() { auto options = MakeCompileOptions(stage); options.dump_ir = true; - ShaderRecompiler::CompileResult result; - std::string error; - const bool compiled = - ShaderRecompiler::TryRecompile(shader, options, result, &error); - Check(compiled, error.c_str()); + auto result = ShaderRecompiler::Recompile(shader, options); CheckSpirvBinaryValidates(result.spirv); CheckSpirvBudget(name, result.spirv, budget); return result; @@ -12555,8 +11917,8 @@ int main() { // here. TestNewShaderDecoderArchitecture(); TestNewShaderRecompilerCapturedVopcSdwaCmpxClass(); - TestNewShaderRecompilerRejectsDppOn64BitCompares(); TestNewShaderRecompilerIrLookupMissFailsExplicitly(); + TestNewShaderRecompilerRejectsDppOn64BitCompares(); TestPsInputCountRegisterDecode(); TestNewShaderRecompilerUnbasedFlatUsesBda(); TestNewShaderRecompilerFlatUserPointerUsesDma(); @@ -12568,8 +11930,10 @@ int main() { TestNewShaderRecompilerCfgTerminalExitMergePS(); TestNewShaderRecompilerCfgPostEndTargetMergePS(); TestNewShaderRecompilerCfgLoopBreakContinue(); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS TestNewShaderRecompilerCfgLoopHeaderDynamicScalarBufferLoadStructured(); TestNewShaderRecompilerCfgLoopHeaderBufferLoadDispatcher(); +#endif TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeStructured(); TestNewShaderRecompilerCfgLoopHeaderDsReadStructured(); TestNewShaderRecompilerCfgLoopHeaderDsRead2B64Structured(); @@ -12608,10 +11972,14 @@ int main() { TestNewShaderRecompilerPrunesUnreachableSetpcMetadata(); TestNewShaderRecompilerSetpcDwordJumpTable(); TestTypedEntryStateIsMinimal(); - TestWqmMaskSignatureAndU64ShiftConstantPropagation(); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS TestFinalSsaRejectsRegisterStatePseudos(); +#endif TestValuePhiValidation(); + TestWqmMaskSignatureAndU64ShiftConstantPropagation(); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS TestNativeWideValueValidation(); +#endif TestNewShaderRecompilerZeroInitialRegisterState(); TestNewShaderRecompilerVertexSystemInputsWithoutMirrors(); TestNewShaderRecompilerVertexExportUsesInvocationExecMask();