| | |
| | |
| |
|
| | #pragma once |
| |
|
| | #include <array> |
| | #include <cstring> |
| | #include <memory> |
| | #include <type_traits> |
| | #include <utility> |
| | #include <vector> |
| |
|
| | #include <boost/container/small_vector.hpp> |
| | #include <boost/intrusive/list.hpp> |
| |
|
| | #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" |
| |
|
| | 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<u32>(IR::Type::Void) == 0, "memset relies on IR::Type being zero"); |
| | static_assert(std::is_trivially_copyable_v<Value>); |
| |
|
| | template <IR::Type type_> |
| | class TypedValue : public Value { |
| | public: |
| | TypedValue() = default; |
| |
|
| | template <IR::Type other_type> |
| | requires((other_type & type_) != IR::Type::Void) |
| | explicit(false) TypedValue(const TypedValue<other_type>& 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; |
| |
|
| | |
| | [[nodiscard]] int UseCount() const noexcept { |
| | return use_count; |
| | } |
| |
|
| | |
| | [[nodiscard]] bool HasUses() const noexcept { |
| | return use_count > 0; |
| | } |
| |
|
| | |
| | [[nodiscard]] IR::Opcode GetOpcode() const noexcept { |
| | return op; |
| | } |
| |
|
| | |
| | [[nodiscard]] bool HasAssociatedPseudoOperation() const noexcept { |
| | return associated_insts != nullptr; |
| | } |
| |
|
| | |
| | [[nodiscard]] bool MayHaveSideEffects() const noexcept; |
| |
|
| | |
| | |
| | [[nodiscard]] bool IsPseudoInstruction() const noexcept; |
| |
|
| | |
| | [[nodiscard]] bool AreAllArgsImmediates() const; |
| |
|
| | |
| | [[nodiscard]] Inst* GetAssociatedPseudoOperation(IR::Opcode opcode); |
| |
|
| | |
| | [[nodiscard]] IR::Type Type() const; |
| |
|
| | |
| | [[nodiscard]] size_t NumArgs() const { |
| | return op == IR::Opcode::Phi ? phi_args.size() : NumArgsOf(op); |
| | } |
| |
|
| | |
| | [[nodiscard]] Value Arg(size_t index) const noexcept { |
| | if (op == IR::Opcode::Phi) { |
| | return phi_args[index].second; |
| | } else { |
| | return args[index]; |
| | } |
| | } |
| |
|
| | |
| | void SetArg(size_t index, Value value); |
| |
|
| | |
| | [[nodiscard]] Block* PhiBlock(size_t index) const; |
| | |
| | void AddPhiOperand(Block* predecessor, const Value& value); |
| |
|
| | |
| | void OrderPhiArgs(); |
| |
|
| | void Invalidate(); |
| | void ClearArgs(); |
| |
|
| | void ReplaceUsesWith(Value replacement); |
| |
|
| | void ReplaceOpcode(IR::Opcode opcode); |
| |
|
| | template <typename FlagsType> |
| | requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>) |
| | [[nodiscard]] FlagsType Flags() const noexcept { |
| | FlagsType ret; |
| | std::memcpy(reinterpret_cast<char*>(&ret), &flags, sizeof(ret)); |
| | return ret; |
| | } |
| |
|
| | template <typename FlagsType> |
| | requires(sizeof(FlagsType) <= sizeof(u32) && std::is_trivially_copyable_v<FlagsType>) |
| | void SetFlags(FlagsType value) noexcept { |
| | std::memcpy(&flags, &value, sizeof(value)); |
| | } |
| |
|
| | |
| | template <typename DefinitionType> |
| | void SetDefinition(DefinitionType def) { |
| | definition = Common::BitCast<u32>(def); |
| | } |
| |
|
| | |
| | template <typename DefinitionType> |
| | [[nodiscard]] DefinitionType Definition() const noexcept { |
| | return Common::BitCast<DefinitionType>(definition); |
| | } |
| |
|
| | |
| | |
| | void DestructiveRemoveUsage() { |
| | --use_count; |
| | } |
| |
|
| | |
| | |
| | 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<std::pair<Block*, Value>, 2> phi_args; |
| | std::array<Value, 5> args; |
| | }; |
| | std::unique_ptr<AssociatedInsts> 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<Type::U1>; |
| | using U8 = TypedValue<Type::U8>; |
| | using U16 = TypedValue<Type::U16>; |
| | using U32 = TypedValue<Type::U32>; |
| | using U64 = TypedValue<Type::U64>; |
| | using F16 = TypedValue<Type::F16>; |
| | using F32 = TypedValue<Type::F32>; |
| | using F64 = TypedValue<Type::F64>; |
| | using U32U64 = TypedValue<Type::U32 | Type::U64>; |
| | using F32F64 = TypedValue<Type::F32 | Type::F64>; |
| | using U16U32U64 = TypedValue<Type::U16 | Type::U32 | Type::U64>; |
| | using F16F32F64 = TypedValue<Type::F16 | Type::F32 | Type::F64>; |
| | using UAny = TypedValue<Type::U8 | Type::U16 | Type::U32 | Type::U64>; |
| |
|
| | 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; |
| | } |
| |
|
| | } |
| |
|