// Copyright 2018 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include "common/assert.h" #include "common/common_types.h" #include "video_core/engines/shader_bytecode.h" #include "video_core/engines/shader_header.h" #include "video_core/shader/control_flow.h" #include "video_core/shader/node_helper.h" #include "video_core/shader/shader_ir.h" namespace VideoCommon::Shader { using Tegra::Shader::Instruction; using Tegra::Shader::OpCode; namespace { /** * Returns whether the instruction at the specified offset is a 'sched' instruction. * Sched instructions always appear before a sequence of 3 instructions. */ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) { constexpr u32 SchedPeriod = 4; u32 absolute_offset = offset - main_offset; return (absolute_offset % SchedPeriod) == 0; } } // namespace void ShaderIR::Decode() { std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); ShaderCharacteristics shader_info{}; bool can_proceed = ScanFlow(program_code, MAX_PROGRAM_LENGTH, main_offset, shader_info); if (can_proceed) { coverage_begin = shader_info.start; coverage_end = shader_info.end; if (shader_info.decompilable) { return; } // we can't decompile it, fallback to standard method for (const auto& block : shader_info.blocks) { basic_blocks.insert({block.start, DecodeRange(block.start, block.end + 1)}); } return; } LOG_CRITICAL(HW_GPU, "Flow Analysis failed, falling back to brute force compiling"); // Now we need to deal with an undecompilable shader. We need to brute force // a shader that captures every position. coverage_begin = shader_info.start; const u32 shader_end = static_cast(MAX_PROGRAM_LENGTH); coverage_end = shader_end; for (u32 label = main_offset; label < shader_end; label++) { basic_blocks.insert({label, DecodeRange(label, label + 1)}); } return; } NodeBlock ShaderIR::DecodeRange(u32 begin, u32 end) { NodeBlock basic_block; for (u32 pc = begin; pc < (begin > end ? MAX_PROGRAM_LENGTH : end);) { pc = DecodeInstr(basic_block, pc); } return basic_block; } u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) { // Ignore sched instructions when generating code. if (IsSchedInstruction(pc, main_offset)) { return pc + 1; } const Instruction instr = {program_code[pc]}; const auto opcode = OpCode::Decode(instr); // Decoding failure if (!opcode) { UNIMPLEMENTED_MSG("Unhandled instruction: {0:x}", instr.value); return pc + 1; } bb.push_back( Comment(fmt::format("{}: {} (0x{:016x})", pc, opcode->get().GetName(), instr.value))); using Tegra::Shader::Pred; UNIMPLEMENTED_IF_MSG(instr.pred.full_pred == Pred::NeverExecute, "NeverExecute predicate not implemented"); static const std::map decoders = { {OpCode::Type::Arithmetic, &ShaderIR::DecodeArithmetic}, {OpCode::Type::ArithmeticImmediate, &ShaderIR::DecodeArithmeticImmediate}, {OpCode::Type::Bfe, &ShaderIR::DecodeBfe}, {OpCode::Type::Bfi, &ShaderIR::DecodeBfi}, {OpCode::Type::Shift, &ShaderIR::DecodeShift}, {OpCode::Type::ArithmeticInteger, &ShaderIR::DecodeArithmeticInteger}, {OpCode::Type::ArithmeticIntegerImmediate, &ShaderIR::DecodeArithmeticIntegerImmediate}, {OpCode::Type::ArithmeticHalf, &ShaderIR::DecodeArithmeticHalf}, {OpCode::Type::ArithmeticHalfImmediate, &ShaderIR::DecodeArithmeticHalfImmediate}, {OpCode::Type::Ffma, &ShaderIR::DecodeFfma}, {OpCode::Type::Hfma2, &ShaderIR::DecodeHfma2}, {OpCode::Type::Conversion, &ShaderIR::DecodeConversion}, {OpCode::Type::Memory, &ShaderIR::DecodeMemory}, {OpCode::Type::Texture, &ShaderIR::DecodeTexture}, {OpCode::Type::Image, &ShaderIR::DecodeImage}, {OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate}, {OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate}, {OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate}, {OpCode::Type::PredicateSetRegister, &ShaderIR::DecodePredicateSetRegister}, {OpCode::Type::PredicateSetPredicate, &ShaderIR::DecodePredicateSetPredicate}, {OpCode::Type::RegisterSetPredicate, &ShaderIR::DecodeRegisterSetPredicate}, {OpCode::Type::FloatSet, &ShaderIR::DecodeFloatSet}, {OpCode::Type::IntegerSet, &ShaderIR::DecodeIntegerSet}, {OpCode::Type::HalfSet, &ShaderIR::DecodeHalfSet}, {OpCode::Type::Video, &ShaderIR::DecodeVideo}, {OpCode::Type::Xmad, &ShaderIR::DecodeXmad}, }; std::vector tmp_block; if (const auto decoder = decoders.find(opcode->get().GetType()); decoder != decoders.end()) { pc = (this->*decoder->second)(tmp_block, pc); } else { pc = DecodeOther(tmp_block, pc); } // Some instructions (like SSY) don't have a predicate field, they are always unconditionally // executed. const bool can_be_predicated = OpCode::IsPredicatedInstruction(opcode->get().GetId()); const auto pred_index = static_cast(instr.pred.pred_index); if (can_be_predicated && pred_index != static_cast(Pred::UnusedIndex)) { const Node conditional = Conditional(GetPredicate(pred_index, instr.negate_pred != 0), std::move(tmp_block)); global_code.push_back(conditional); bb.push_back(conditional); } else { for (auto& node : tmp_block) { global_code.push_back(node); bb.push_back(node); } } return pc + 1; } } // namespace VideoCommon::Shader