yuzu/src/shader_recompiler/frontend/ir/value.h
Morph 99ceb03a1c general: Convert source file copyright comments over to SPDX
This formats all copyright comments according to SPDX formatting guidelines.
Additionally, this resolves the remaining GPLv2 only licensed files by relicensing them to GPLv2.0-or-later.
2022-04-23 05:55:32 -04:00

412 lines
12 KiB
C++

// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#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"
#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<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;
/// 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);
// Erase the phi operand at the given index.
void ErasePhiOperand(size_t index);
/// 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 <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));
}
/// Intrusively store the host definition of this instruction.
template <typename DefinitionType>
void SetDefinition(DefinitionType def) {
definition = Common::BitCast<u32>(def);
}
/// Return the intrusively stored host definition of this instruction.
template <typename DefinitionType>
[[nodiscard]] DefinitionType Definition() const noexcept {
return Common::BitCast<DefinitionType>(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<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;
}
} // namespace Shader::IR