diff --git a/src/frontend/ir/value.cpp b/src/frontend/ir/value.cpp index d5ba2c62..a7ef2e65 100644 --- a/src/frontend/ir/value.cpp +++ b/src/frontend/ir/value.cpp @@ -174,4 +174,24 @@ u64 Value::GetImmediateAsU64() const { } } +bool Value::HasAllBitsSet() const { + ASSERT(IsImmediate()); + + switch (GetType()) { + case IR::Type::U1: + return GetU1(); + case IR::Type::U8: + return GetU8() == 0xFF; + case IR::Type::U16: + return GetU16() == 0xFFFF; + case IR::Type::U32: + return GetU32() == 0xFFFFFFFF; + case IR::Type::U64: + return GetU64() == 0xFFFFFFFFFFFFFFFF; + default: + ASSERT_MSG(false, "HasAllBitsSet called on an incompatible Value type."); + return false; + } +} + } // namespace Dynarmic::IR diff --git a/src/frontend/ir/value.h b/src/frontend/ir/value.h index 4984ebdd..80f5970c 100644 --- a/src/frontend/ir/value.h +++ b/src/frontend/ir/value.h @@ -74,6 +74,14 @@ public: */ u64 GetImmediateAsU64() const; + /** + * Determines whether or not the contained constant value has all bits set. + * + * @pre The value contains either a U1, U8, U16, U32, or U64 value. + * Breaking this precondition will cause an assertion to be invoked. + */ + bool HasAllBitsSet() const; + private: Type type; diff --git a/src/ir_opt/constant_propagation_pass.cpp b/src/ir_opt/constant_propagation_pass.cpp index 62d9bf01..4d323bf3 100644 --- a/src/ir_opt/constant_propagation_pass.cpp +++ b/src/ir_opt/constant_propagation_pass.cpp @@ -11,10 +11,121 @@ #include "ir_opt/passes.h" namespace Dynarmic::Optimization { +namespace { + +// Folds AND operations based on the following: +// +// 1. imm_x & imm_y -> result +// 2. x & 0 -> 0 +// 3. 0 & y -> 0 +// 4. x & y -> y (where x has all bits set to 1) +// 5. x & y -> x (where y has all bits set to 1) +// +void FoldAND(IR::Inst& inst, bool is_32_bit) { + const auto lhs = inst.GetArg(0); + const auto rhs = inst.GetArg(1); + + const bool is_lhs_immediate = lhs.IsImmediate(); + const bool is_rhs_immediate = rhs.IsImmediate(); + + if (is_lhs_immediate && is_rhs_immediate) { + const u64 result = lhs.GetImmediateAsU64() & rhs.GetImmediateAsU64(); + + if (is_32_bit) { + inst.ReplaceUsesWith(IR::Value{static_cast(result)}); + } else { + inst.ReplaceUsesWith(IR::Value{result}); + } + } else if ((is_lhs_immediate && lhs.GetImmediateAsU64() == 0) || (is_rhs_immediate && rhs.GetImmediateAsU64() == 0)) { + if (is_32_bit) { + inst.ReplaceUsesWith(IR::Value{u32{0}}); + } else { + inst.ReplaceUsesWith(IR::Value{u64{0}}); + } + } else if (is_lhs_immediate && lhs.HasAllBitsSet()) { + inst.ReplaceUsesWith(rhs); + } else if (is_rhs_immediate && rhs.HasAllBitsSet()) { + inst.ReplaceUsesWith(lhs); + } +} + +// Folds EOR operations based on the following: +// +// 1. imm_x ^ imm_y -> result +// 2. x ^ 0 -> x +// 3. 0 ^ y -> y +// +void FoldEOR(IR::Inst& inst, bool is_32_bit) { + const auto lhs = inst.GetArg(0); + const auto rhs = inst.GetArg(1); + + const bool is_lhs_immediate = lhs.IsImmediate(); + const bool is_rhs_immediate = rhs.IsImmediate(); + + if (is_lhs_immediate && is_rhs_immediate) { + const u64 result = lhs.GetImmediateAsU64() ^ rhs.GetImmediateAsU64(); + + if (is_32_bit) { + inst.ReplaceUsesWith(IR::Value{static_cast(result)}); + } else { + inst.ReplaceUsesWith(IR::Value{result}); + } + } else if (is_lhs_immediate && lhs.GetImmediateAsU64() == 0) { + inst.ReplaceUsesWith(rhs); + } else if (is_rhs_immediate && rhs.GetImmediateAsU64() == 0) { + inst.ReplaceUsesWith(lhs); + } +} + +// Folds NOT operations if the contained value is an immediate. +void FoldNOT(IR::Inst& inst, bool is_32_bit) { + const auto operand = inst.GetArg(0); + + if (operand.IsImmediate()) { + const u64 result = ~operand.GetImmediateAsU64(); + + if (is_32_bit) { + inst.ReplaceUsesWith(IR::Value{static_cast(result)}); + } else { + inst.ReplaceUsesWith(IR::Value{result}); + } + } +} + +// Folds OR operations based on the following: +// +// 1. imm_x | imm_y -> result +// 2. x | 0 -> x +// 3. 0 | y -> y +// +void FoldOR(IR::Inst& inst, bool is_32_bit) { + const auto lhs = inst.GetArg(0); + const auto rhs = inst.GetArg(1); + + const bool is_lhs_immediate = lhs.IsImmediate(); + const bool is_rhs_immediate = rhs.IsImmediate(); + + if (is_lhs_immediate && is_rhs_immediate) { + const u64 result = lhs.GetImmediateAsU64() | rhs.GetImmediateAsU64(); + + if (is_32_bit) { + inst.ReplaceUsesWith(IR::Value{static_cast(result)}); + } else { + inst.ReplaceUsesWith(IR::Value{result}); + } + } else if (is_lhs_immediate && lhs.GetImmediateAsU64() == 0) { + inst.ReplaceUsesWith(rhs); + } else if (is_rhs_immediate && rhs.GetImmediateAsU64() == 0) { + inst.ReplaceUsesWith(lhs); + } +} +} // Anonymous namespace void ConstantPropagation(IR::Block& block) { for (auto& inst : block) { - switch (inst.GetOpcode()) { + const auto opcode = inst.GetOpcode(); + + switch (opcode) { case IR::Opcode::LogicalShiftLeft32: case IR::Opcode::LogicalShiftRight32: case IR::Opcode::ArithmeticShiftRight32: @@ -33,6 +144,22 @@ void ConstantPropagation(IR::Block& block) { } break; } + case IR::Opcode::And32: + case IR::Opcode::And64: + FoldAND(inst, opcode == IR::Opcode::And32); + break; + case IR::Opcode::Eor32: + case IR::Opcode::Eor64: + FoldEOR(inst, opcode == IR::Opcode::Eor32); + break; + case IR::Opcode::Or32: + case IR::Opcode::Or64: + FoldOR(inst, opcode == IR::Opcode::Or32); + break; + case IR::Opcode::Not32: + case IR::Opcode::Not64: + FoldNOT(inst, opcode == IR::Opcode::Not32); + break; case IR::Opcode::ZeroExtendByteToWord: { if (!inst.AreAllArgsImmediates()) break;