// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include #include #include #include #include "common/assert.h" #include "common/bit_cast.h" #include "common/common_types.h" #include "shader_recompiler/exception.h" #include "shader_recompiler/frontend/ir/attribute.h" #include "shader_recompiler/frontend/ir/opcodes.h" #include "shader_recompiler/frontend/ir/patch.h" #include "shader_recompiler/frontend/ir/pred.h" #include "shader_recompiler/frontend/ir/reg.h" #include "shader_recompiler/frontend/ir/type.h" #include "shader_recompiler/frontend/ir/value.h" namespace Shader::IR { class Block; class Inst; struct AssociatedInsts; class Value { public: Value() noexcept = default; explicit Value(IR::Inst* value) noexcept; explicit Value(IR::Reg value) noexcept; explicit Value(IR::Pred value) noexcept; explicit Value(IR::Attribute value) noexcept; explicit Value(IR::Patch value) noexcept; explicit Value(bool value) noexcept; explicit Value(u8 value) noexcept; explicit Value(u16 value) noexcept; explicit Value(u32 value) noexcept; explicit Value(f32 value) noexcept; explicit Value(u64 value) noexcept; explicit Value(f64 value) noexcept; [[nodiscard]] bool IsIdentity() const noexcept; [[nodiscard]] bool IsPhi() const noexcept; [[nodiscard]] bool IsEmpty() const noexcept; [[nodiscard]] bool IsImmediate() const noexcept; [[nodiscard]] IR::Type Type() const noexcept; [[nodiscard]] IR::Inst* Inst() const; [[nodiscard]] IR::Inst* InstRecursive() const; [[nodiscard]] IR::Inst* TryInstRecursive() const; [[nodiscard]] IR::Value Resolve() const; [[nodiscard]] IR::Reg Reg() const; [[nodiscard]] IR::Pred Pred() const; [[nodiscard]] IR::Attribute Attribute() const; [[nodiscard]] IR::Patch Patch() const; [[nodiscard]] bool U1() const; [[nodiscard]] u8 U8() const; [[nodiscard]] u16 U16() const; [[nodiscard]] u32 U32() const; [[nodiscard]] f32 F32() const; [[nodiscard]] u64 U64() const; [[nodiscard]] f64 F64() const; [[nodiscard]] bool operator==(const Value& other) const; [[nodiscard]] bool operator!=(const Value& other) const; private: IR::Type type{}; union { IR::Inst* inst{}; IR::Reg reg; IR::Pred pred; IR::Attribute attribute; IR::Patch patch; bool imm_u1; u8 imm_u8; u16 imm_u16; u32 imm_u32; f32 imm_f32; u64 imm_u64; f64 imm_f64; }; }; static_assert(static_cast(IR::Type::Void) == 0, "memset relies on IR::Type being zero"); static_assert(std::is_trivially_copyable_v); template class TypedValue : public Value { public: TypedValue() = default; template requires((other_type & type_) != IR::Type::Void) explicit(false) TypedValue(const TypedValue& value) : Value(value) {} explicit TypedValue(const Value& value) : Value(value) { if ((value.Type() & type_) == IR::Type::Void) { throw InvalidArgument("Incompatible types {} and {}", type_, value.Type()); } } explicit TypedValue(IR::Inst* inst_) : TypedValue(Value(inst_)) {} }; class Inst : public boost::intrusive::list_base_hook<> { public: explicit Inst(IR::Opcode op_, u32 flags_) noexcept; explicit Inst(const Inst& base); ~Inst(); Inst& operator=(const Inst&) = delete; Inst& operator=(Inst&&) = delete; Inst(Inst&&) = delete; /// Get the number of uses this instruction has. [[nodiscard]] int UseCount() const noexcept { return use_count; } /// Determines whether this instruction has uses or not. [[nodiscard]] bool HasUses() const noexcept { return use_count > 0; } /// Get the opcode this microinstruction represents. [[nodiscard]] IR::Opcode GetOpcode() const noexcept { return op; } /// Determines if there is a pseudo-operation associated with this instruction. [[nodiscard]] bool HasAssociatedPseudoOperation() const noexcept { return associated_insts != nullptr; } /// Determines whether or not this instruction may have side effects. [[nodiscard]] bool MayHaveSideEffects() const noexcept; /// Determines whether or not this instruction is a pseudo-instruction. /// Pseudo-instructions depend on their parent instructions for their semantics. [[nodiscard]] bool IsPseudoInstruction() const noexcept; /// Determines if all arguments of this instruction are immediates. [[nodiscard]] bool AreAllArgsImmediates() const; /// Gets a pseudo-operation associated with this instruction [[nodiscard]] Inst* GetAssociatedPseudoOperation(IR::Opcode opcode); /// Get the type this instruction returns. [[nodiscard]] IR::Type Type() const; /// Get the number of arguments this instruction has. [[nodiscard]] size_t NumArgs() const { return op == IR::Opcode::Phi ? phi_args.size() : NumArgsOf(op); } /// Get the value of a given argument index. [[nodiscard]] Value Arg(size_t index) const noexcept { if (op == IR::Opcode::Phi) { return phi_args[index].second; } else { return args[index]; } } /// Set the value of a given argument index. void SetArg(size_t index, Value value); /// Get a pointer to the block of a phi argument. [[nodiscard]] Block* PhiBlock(size_t index) const; /// Add phi operand to a phi instruction. void AddPhiOperand(Block* predecessor, const Value& value); /// Orders the Phi arguments from farthest away to nearest. void OrderPhiArgs(); void Invalidate(); void ClearArgs(); void ReplaceUsesWith(Value replacement); void ReplaceOpcode(IR::Opcode opcode); template requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v) [[nodiscard]] FlagsType Flags() const noexcept { FlagsType ret; std::memcpy(reinterpret_cast(&ret), &flags, sizeof(ret)); return ret; } template requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v) void SetFlags(FlagsType value) noexcept { std::memcpy(&flags, &value, sizeof(value)); } /// Intrusively store the host definition of this instruction. template void SetDefinition(DefinitionType def) { definition = Common::BitCast(def); } /// Return the intrusively stored host definition of this instruction. template [[nodiscard]] DefinitionType Definition() const noexcept { return Common::BitCast(definition); } /// Destructively remove one reference count from the instruction /// Useful for register allocation void DestructiveRemoveUsage() { --use_count; } /// Destructively add usages to the instruction /// Useful for register allocation void DestructiveAddUsage(int count) { use_count += count; } private: struct NonTriviallyDummy { NonTriviallyDummy() noexcept {} }; void Use(const Value& value); void UndoUse(const Value& value); IR::Opcode op{}; int use_count{}; u32 flags{}; u32 definition{}; union { NonTriviallyDummy dummy{}; boost::container::small_vector, 2> phi_args; std::array args; }; std::unique_ptr associated_insts; }; static_assert(sizeof(Inst) <= 128, "Inst size unintentionally increased"); struct AssociatedInsts { union { Inst* in_bounds_inst; Inst* sparse_inst; Inst* zero_inst{}; }; Inst* sign_inst{}; Inst* carry_inst{}; Inst* overflow_inst{}; }; using U1 = TypedValue; using U8 = TypedValue; using U16 = TypedValue; using U32 = TypedValue; using U64 = TypedValue; using F16 = TypedValue; using F32 = TypedValue; using F64 = TypedValue; using U32U64 = TypedValue; using F32F64 = TypedValue; using U16U32U64 = TypedValue; using F16F32F64 = TypedValue; using UAny = TypedValue; inline bool Value::IsIdentity() const noexcept { return type == Type::Opaque && inst->GetOpcode() == Opcode::Identity; } inline bool Value::IsPhi() const noexcept { return type == Type::Opaque && inst->GetOpcode() == Opcode::Phi; } inline bool Value::IsEmpty() const noexcept { return type == Type::Void; } inline bool Value::IsImmediate() const noexcept { IR::Type current_type{type}; const IR::Inst* current_inst{inst}; while (current_type == Type::Opaque && current_inst->GetOpcode() == Opcode::Identity) { const Value& arg{current_inst->Arg(0)}; current_type = arg.type; current_inst = arg.inst; } return current_type != Type::Opaque; } inline IR::Inst* Value::Inst() const { DEBUG_ASSERT(type == Type::Opaque); return inst; } inline IR::Inst* Value::InstRecursive() const { DEBUG_ASSERT(type == Type::Opaque); if (IsIdentity()) { return inst->Arg(0).InstRecursive(); } return inst; } inline IR::Inst* Value::TryInstRecursive() const { if (IsIdentity()) { return inst->Arg(0).TryInstRecursive(); } return type == Type::Opaque ? inst : nullptr; } inline IR::Value Value::Resolve() const { if (IsIdentity()) { return inst->Arg(0).Resolve(); } return *this; } inline IR::Reg Value::Reg() const { DEBUG_ASSERT(type == Type::Reg); return reg; } inline IR::Pred Value::Pred() const { DEBUG_ASSERT(type == Type::Pred); return pred; } inline IR::Attribute Value::Attribute() const { DEBUG_ASSERT(type == Type::Attribute); return attribute; } inline IR::Patch Value::Patch() const { DEBUG_ASSERT(type == Type::Patch); return patch; } inline bool Value::U1() const { if (IsIdentity()) { return inst->Arg(0).U1(); } DEBUG_ASSERT(type == Type::U1); return imm_u1; } inline u8 Value::U8() const { if (IsIdentity()) { return inst->Arg(0).U8(); } DEBUG_ASSERT(type == Type::U8); return imm_u8; } inline u16 Value::U16() const { if (IsIdentity()) { return inst->Arg(0).U16(); } DEBUG_ASSERT(type == Type::U16); return imm_u16; } inline u32 Value::U32() const { if (IsIdentity()) { return inst->Arg(0).U32(); } DEBUG_ASSERT(type == Type::U32); return imm_u32; } inline f32 Value::F32() const { if (IsIdentity()) { return inst->Arg(0).F32(); } DEBUG_ASSERT(type == Type::F32); return imm_f32; } inline u64 Value::U64() const { if (IsIdentity()) { return inst->Arg(0).U64(); } DEBUG_ASSERT(type == Type::U64); return imm_u64; } inline f64 Value::F64() const { if (IsIdentity()) { return inst->Arg(0).F64(); } DEBUG_ASSERT(type == Type::F64); return imm_f64; } [[nodiscard]] inline bool IsPhi(const Inst& inst) { return inst.GetOpcode() == Opcode::Phi; } } // namespace Shader::IR