// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include "common/bit_cast.h" #include "common/bit_util.h" #include "shader_recompiler/exception.h" #include "shader_recompiler/frontend/ir/microinstruction.h" #include "shader_recompiler/ir_opt/passes.h" namespace Shader::Optimization { namespace { // Metaprogramming stuff to get arguments information out of a lambda template struct LambdaTraits : LambdaTraits::operator())> {}; template struct LambdaTraits { template using ArgType = std::tuple_element_t>; static constexpr size_t NUM_ARGS{sizeof...(Args)}; }; template [[nodiscard]] T Arg(const IR::Value& value) { if constexpr (std::is_same_v) { return value.U1(); } else if constexpr (std::is_same_v) { return value.U32(); } else if constexpr (std::is_same_v) { return value.F32(); } else if constexpr (std::is_same_v) { return value.U64(); } } template bool FoldCommutative(IR::Inst& inst, ImmFn&& imm_fn) { const auto arg = [](const IR::Value& value) { if constexpr (std::is_invocable_r_v) { return value.U1(); } else if constexpr (std::is_invocable_r_v) { return value.U32(); } else if constexpr (std::is_invocable_r_v) { return value.U64(); } }; const IR::Value lhs{inst.Arg(0)}; const IR::Value rhs{inst.Arg(1)}; const bool is_lhs_immediate{lhs.IsImmediate()}; const bool is_rhs_immediate{rhs.IsImmediate()}; if (is_lhs_immediate && is_rhs_immediate) { const auto result{imm_fn(arg(lhs), arg(rhs))}; inst.ReplaceUsesWith(IR::Value{result}); return false; } if (is_lhs_immediate && !is_rhs_immediate) { IR::Inst* const rhs_inst{rhs.InstRecursive()}; if (rhs_inst->Opcode() == inst.Opcode() && rhs_inst->Arg(1).IsImmediate()) { const auto combined{imm_fn(arg(lhs), arg(rhs_inst->Arg(1)))}; inst.SetArg(0, rhs_inst->Arg(0)); inst.SetArg(1, IR::Value{combined}); } else { // Normalize inst.SetArg(0, rhs); inst.SetArg(1, lhs); } } if (!is_lhs_immediate && is_rhs_immediate) { const IR::Inst* const lhs_inst{lhs.InstRecursive()}; if (lhs_inst->Opcode() == inst.Opcode() && lhs_inst->Arg(1).IsImmediate()) { const auto combined{imm_fn(arg(rhs), arg(lhs_inst->Arg(1)))}; inst.SetArg(0, lhs_inst->Arg(0)); inst.SetArg(1, IR::Value{combined}); } } return true; } void FoldGetRegister(IR::Inst& inst) { if (inst.Arg(0).Reg() == IR::Reg::RZ) { inst.ReplaceUsesWith(IR::Value{u32{0}}); } } void FoldGetPred(IR::Inst& inst) { if (inst.Arg(0).Pred() == IR::Pred::PT) { inst.ReplaceUsesWith(IR::Value{true}); } } template void FoldAdd(IR::Inst& inst) { if (inst.HasAssociatedPseudoOperation()) { return; } if (!FoldCommutative(inst, [](T a, T b) { return a + b; })) { return; } const IR::Value rhs{inst.Arg(1)}; if (rhs.IsImmediate() && Arg(rhs) == 0) { inst.ReplaceUsesWith(inst.Arg(0)); } } template void FoldSelect(IR::Inst& inst) { const IR::Value cond{inst.Arg(0)}; if (cond.IsImmediate()) { inst.ReplaceUsesWith(cond.U1() ? inst.Arg(1) : inst.Arg(2)); } } void FoldLogicalAnd(IR::Inst& inst) { if (!FoldCommutative(inst, [](bool a, bool b) { return a && b; })) { return; } const IR::Value rhs{inst.Arg(1)}; if (rhs.IsImmediate()) { if (rhs.U1()) { inst.ReplaceUsesWith(inst.Arg(0)); } else { inst.ReplaceUsesWith(IR::Value{false}); } } } template void FoldBitCast(IR::Inst& inst, IR::Opcode reverse) { const IR::Value value{inst.Arg(0)}; if (value.IsImmediate()) { inst.ReplaceUsesWith(IR::Value{Common::BitCast(Arg(value))}); return; } IR::Inst* const arg_inst{value.InstRecursive()}; if (value.InstRecursive()->Opcode() == reverse) { inst.ReplaceUsesWith(arg_inst->Arg(0)); } } template IR::Value EvalImmediates(const IR::Inst& inst, Func&& func, std::index_sequence) { using Traits = LambdaTraits; return IR::Value{func(Arg>(inst.Arg(I))...)}; } template void FoldWhenAllImmediates(IR::Inst& inst, Func&& func) { if (!inst.AreAllArgsImmediates() || inst.HasAssociatedPseudoOperation()) { return; } using Indices = std::make_index_sequence::NUM_ARGS>; inst.ReplaceUsesWith(EvalImmediates(inst, func, Indices{})); } void ConstantPropagation(IR::Inst& inst) { switch (inst.Opcode()) { case IR::Opcode::GetRegister: return FoldGetRegister(inst); case IR::Opcode::GetPred: return FoldGetPred(inst); case IR::Opcode::IAdd32: return FoldAdd(inst); case IR::Opcode::BitCastF32U32: return FoldBitCast(inst, IR::Opcode::BitCastU32F32); case IR::Opcode::BitCastU32F32: return FoldBitCast(inst, IR::Opcode::BitCastF32U32); case IR::Opcode::IAdd64: return FoldAdd(inst); case IR::Opcode::Select32: return FoldSelect(inst); case IR::Opcode::LogicalAnd: return FoldLogicalAnd(inst); case IR::Opcode::ULessThan: return FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a < b; }); case IR::Opcode::BitFieldUExtract: return FoldWhenAllImmediates(inst, [](u32 base, u32 shift, u32 count) { if (static_cast(shift) + static_cast(count) > Common::BitSize()) { throw LogicError("Undefined result in {}({}, {}, {})", IR::Opcode::BitFieldUExtract, base, shift, count); } return (base >> shift) & ((1U << count) - 1); }); default: break; } } } // Anonymous namespace void ConstantPropagationPass(IR::Block& block) { std::ranges::for_each(block, ConstantPropagation); } } // namespace Shader::Optimization