code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <memory>

#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/type.h"
#include "shader_recompiler/frontend/ir/value.h"

namespace Shader::IR {
namespace {
void CheckPseudoInstruction(IR::Inst* inst, IR::Opcode opcode) {
    if (inst && inst->GetOpcode() != opcode) {
        throw LogicError("Invalid pseudo-instruction");
    }
}

void SetPseudoInstruction(IR::Inst*& dest_inst, IR::Inst* pseudo_inst) {
    if (dest_inst) {
        throw LogicError("Only one of each type of pseudo-op allowed");
    }
    dest_inst = pseudo_inst;
}

void RemovePseudoInstruction(IR::Inst*& inst, IR::Opcode expected_opcode) {
    if (inst->GetOpcode() != expected_opcode) {
        throw LogicError("Undoing use of invalid pseudo-op");
    }
    inst = nullptr;
}

void AllocAssociatedInsts(std::unique_ptr<AssociatedInsts>& associated_insts) {
    if (!associated_insts) {
        associated_insts = std::make_unique<AssociatedInsts>();
    }
}
} // Anonymous namespace

Inst::Inst(IR::Opcode op_, u32 flags_) noexcept : op{op_}, flags{flags_} {
    if (op == Opcode::Phi) {
        std::construct_at(&phi_args);
    } else {
        std::construct_at(&args);
    }
}

Inst::Inst(const Inst& base) : op{base.op}, flags{base.flags} {
    if (base.op == Opcode::Phi) {
        throw NotImplementedException("Copying phi node");
    }
    std::construct_at(&args);
    const size_t num_args{base.NumArgs()};
    for (size_t index = 0; index < num_args; ++index) {
        SetArg(index, base.Arg(index));
    }
}

Inst::~Inst() {
    if (op == Opcode::Phi) {
        std::destroy_at(&phi_args);
    } else {
        std::destroy_at(&args);
    }
}

bool Inst::MayHaveSideEffects() const noexcept {
    switch (op) {
    case Opcode::ConditionRef:
    case Opcode::Reference:
    case Opcode::PhiMove:
    case Opcode::Prologue:
    case Opcode::Epilogue:
    case Opcode::Join:
    case Opcode::DemoteToHelperInvocation:
    case Opcode::Barrier:
    case Opcode::WorkgroupMemoryBarrier:
    case Opcode::DeviceMemoryBarrier:
    case Opcode::EmitVertex:
    case Opcode::EndPrimitive:
    case Opcode::SetAttribute:
    case Opcode::SetAttributeIndexed:
    case Opcode::SetPatch:
    case Opcode::SetFragColor:
    case Opcode::SetSampleMask:
    case Opcode::SetFragDepth:
    case Opcode::WriteGlobalU8:
    case Opcode::WriteGlobalS8:
    case Opcode::WriteGlobalU16:
    case Opcode::WriteGlobalS16:
    case Opcode::WriteGlobal32:
    case Opcode::WriteGlobal64:
    case Opcode::WriteGlobal128:
    case Opcode::WriteStorageU8:
    case Opcode::WriteStorageS8:
    case Opcode::WriteStorageU16:
    case Opcode::WriteStorageS16:
    case Opcode::WriteStorage32:
    case Opcode::WriteStorage64:
    case Opcode::WriteStorage128:
    case Opcode::WriteLocal:
    case Opcode::WriteSharedU8:
    case Opcode::WriteSharedU16:
    case Opcode::WriteSharedU32:
    case Opcode::WriteSharedU64:
    case Opcode::WriteSharedU128:
    case Opcode::SharedAtomicIAdd32:
    case Opcode::SharedAtomicSMin32:
    case Opcode::SharedAtomicUMin32:
    case Opcode::SharedAtomicSMax32:
    case Opcode::SharedAtomicUMax32:
    case Opcode::SharedAtomicInc32:
    case Opcode::SharedAtomicDec32:
    case Opcode::SharedAtomicAnd32:
    case Opcode::SharedAtomicOr32:
    case Opcode::SharedAtomicXor32:
    case Opcode::SharedAtomicExchange32:
    case Opcode::SharedAtomicExchange64:
    case Opcode::SharedAtomicExchange32x2:
    case Opcode::GlobalAtomicIAdd32:
    case Opcode::GlobalAtomicSMin32:
    case Opcode::GlobalAtomicUMin32:
    case Opcode::GlobalAtomicSMax32:
    case Opcode::GlobalAtomicUMax32:
    case Opcode::GlobalAtomicInc32:
    case Opcode::GlobalAtomicDec32:
    case Opcode::GlobalAtomicAnd32:
    case Opcode::GlobalAtomicOr32:
    case Opcode::GlobalAtomicXor32:
    case Opcode::GlobalAtomicExchange32:
    case Opcode::GlobalAtomicIAdd64:
    case Opcode::GlobalAtomicSMin64:
    case Opcode::GlobalAtomicUMin64:
    case Opcode::GlobalAtomicSMax64:
    case Opcode::GlobalAtomicUMax64:
    case Opcode::GlobalAtomicAnd64:
    case Opcode::GlobalAtomicOr64:
    case Opcode::GlobalAtomicXor64:
    case Opcode::GlobalAtomicExchange64:
    case Opcode::GlobalAtomicIAdd32x2:
    case Opcode::GlobalAtomicSMin32x2:
    case Opcode::GlobalAtomicUMin32x2:
    case Opcode::GlobalAtomicSMax32x2:
    case Opcode::GlobalAtomicUMax32x2:
    case Opcode::GlobalAtomicAnd32x2:
    case Opcode::GlobalAtomicOr32x2:
    case Opcode::GlobalAtomicXor32x2:
    case Opcode::GlobalAtomicExchange32x2:
    case Opcode::GlobalAtomicAddF32:
    case Opcode::GlobalAtomicAddF16x2:
    case Opcode::GlobalAtomicAddF32x2:
    case Opcode::GlobalAtomicMinF16x2:
    case Opcode::GlobalAtomicMinF32x2:
    case Opcode::GlobalAtomicMaxF16x2:
    case Opcode::GlobalAtomicMaxF32x2:
    case Opcode::StorageAtomicIAdd32:
    case Opcode::StorageAtomicSMin32:
    case Opcode::StorageAtomicUMin32:
    case Opcode::StorageAtomicSMax32:
    case Opcode::StorageAtomicUMax32:
    case Opcode::StorageAtomicInc32:
    case Opcode::StorageAtomicDec32:
    case Opcode::StorageAtomicAnd32:
    case Opcode::StorageAtomicOr32:
    case Opcode::StorageAtomicXor32:
    case Opcode::StorageAtomicExchange32:
    case Opcode::StorageAtomicIAdd64:
    case Opcode::StorageAtomicSMin64:
    case Opcode::StorageAtomicUMin64:
    case Opcode::StorageAtomicSMax64:
    case Opcode::StorageAtomicUMax64:
    case Opcode::StorageAtomicAnd64:
    case Opcode::StorageAtomicOr64:
    case Opcode::StorageAtomicXor64:
    case Opcode::StorageAtomicExchange64:
    case Opcode::StorageAtomicIAdd32x2:
    case Opcode::StorageAtomicSMin32x2:
    case Opcode::StorageAtomicUMin32x2:
    case Opcode::StorageAtomicSMax32x2:
    case Opcode::StorageAtomicUMax32x2:
    case Opcode::StorageAtomicAnd32x2:
    case Opcode::StorageAtomicOr32x2:
    case Opcode::StorageAtomicXor32x2:
    case Opcode::StorageAtomicExchange32x2:
    case Opcode::StorageAtomicAddF32:
    case Opcode::StorageAtomicAddF16x2:
    case Opcode::StorageAtomicAddF32x2:
    case Opcode::StorageAtomicMinF16x2:
    case Opcode::StorageAtomicMinF32x2:
    case Opcode::StorageAtomicMaxF16x2:
    case Opcode::StorageAtomicMaxF32x2:
    case Opcode::BindlessImageWrite:
    case Opcode::BoundImageWrite:
    case Opcode::ImageWrite:
    case IR::Opcode::BindlessImageAtomicIAdd32:
    case IR::Opcode::BindlessImageAtomicSMin32:
    case IR::Opcode::BindlessImageAtomicUMin32:
    case IR::Opcode::BindlessImageAtomicSMax32:
    case IR::Opcode::BindlessImageAtomicUMax32:
    case IR::Opcode::BindlessImageAtomicInc32:
    case IR::Opcode::BindlessImageAtomicDec32:
    case IR::Opcode::BindlessImageAtomicAnd32:
    case IR::Opcode::BindlessImageAtomicOr32:
    case IR::Opcode::BindlessImageAtomicXor32:
    case IR::Opcode::BindlessImageAtomicExchange32:
    case IR::Opcode::BoundImageAtomicIAdd32:
    case IR::Opcode::BoundImageAtomicSMin32:
    case IR::Opcode::BoundImageAtomicUMin32:
    case IR::Opcode::BoundImageAtomicSMax32:
    case IR::Opcode::BoundImageAtomicUMax32:
    case IR::Opcode::BoundImageAtomicInc32:
    case IR::Opcode::BoundImageAtomicDec32:
    case IR::Opcode::BoundImageAtomicAnd32:
    case IR::Opcode::BoundImageAtomicOr32:
    case IR::Opcode::BoundImageAtomicXor32:
    case IR::Opcode::BoundImageAtomicExchange32:
    case IR::Opcode::ImageAtomicIAdd32:
    case IR::Opcode::ImageAtomicSMin32:
    case IR::Opcode::ImageAtomicUMin32:
    case IR::Opcode::ImageAtomicSMax32:
    case IR::Opcode::ImageAtomicUMax32:
    case IR::Opcode::ImageAtomicInc32:
    case IR::Opcode::ImageAtomicDec32:
    case IR::Opcode::ImageAtomicAnd32:
    case IR::Opcode::ImageAtomicOr32:
    case IR::Opcode::ImageAtomicXor32:
    case IR::Opcode::ImageAtomicExchange32:
        return true;
    default:
        return false;
    }
}

bool Inst::IsPseudoInstruction() const noexcept {
    switch (op) {
    case Opcode::GetZeroFromOp:
    case Opcode::GetSignFromOp:
    case Opcode::GetCarryFromOp:
    case Opcode::GetOverflowFromOp:
    case Opcode::GetSparseFromOp:
    case Opcode::GetInBoundsFromOp:
        return true;
    default:
        return false;
    }
}

bool Inst::AreAllArgsImmediates() const {
    if (op == Opcode::Phi) {
        throw LogicError("Testing for all arguments are immediates on phi instruction");
    }
    return std::all_of(args.begin(), args.begin() + NumArgs(),
                       [](const IR::Value& value) { return value.IsImmediate(); });
}

Inst* Inst::GetAssociatedPseudoOperation(IR::Opcode opcode) {
    if (!associated_insts) {
        return nullptr;
    }
    switch (opcode) {
    case Opcode::GetZeroFromOp:
        CheckPseudoInstruction(associated_insts->zero_inst, Opcode::GetZeroFromOp);
        return associated_insts->zero_inst;
    case Opcode::GetSignFromOp:
        CheckPseudoInstruction(associated_insts->sign_inst, Opcode::GetSignFromOp);
        return associated_insts->sign_inst;
    case Opcode::GetCarryFromOp:
        CheckPseudoInstruction(associated_insts->carry_inst, Opcode::GetCarryFromOp);
        return associated_insts->carry_inst;
    case Opcode::GetOverflowFromOp:
        CheckPseudoInstruction(associated_insts->overflow_inst, Opcode::GetOverflowFromOp);
        return associated_insts->overflow_inst;
    case Opcode::GetSparseFromOp:
        CheckPseudoInstruction(associated_insts->sparse_inst, Opcode::GetSparseFromOp);
        return associated_insts->sparse_inst;
    case Opcode::GetInBoundsFromOp:
        CheckPseudoInstruction(associated_insts->in_bounds_inst, Opcode::GetInBoundsFromOp);
        return associated_insts->in_bounds_inst;
    default:
        throw InvalidArgument("{} is not a pseudo-instruction", opcode);
    }
}

IR::Type Inst::Type() const {
    if (op == IR::Opcode::Phi) {
        // The type of a phi node is stored in its flags
        return Flags<IR::Type>();
    }
    return TypeOf(op);
}

void Inst::SetArg(size_t index, Value value) {
    if (index >= NumArgs()) {
        throw InvalidArgument("Out of bounds argument index {} in opcode {}", index, op);
    }
    const IR::Value arg{Arg(index)};
    if (!arg.IsImmediate()) {
        UndoUse(arg);
    }
    if (!value.IsImmediate()) {
        Use(value);
    }
    if (op == Opcode::Phi) {
        phi_args[index].second = value;
    } else {
        args[index] = value;
    }
}

Block* Inst::PhiBlock(size_t index) const {
    if (op != Opcode::Phi) {
        throw LogicError("{} is not a Phi instruction", op);
    }
    if (index >= phi_args.size()) {
        throw InvalidArgument("Out of bounds argument index {} in phi instruction");
    }
    return phi_args[index].first;
}

void Inst::AddPhiOperand(Block* predecessor, const Value& value) {
    if (!value.IsImmediate()) {
        Use(value);
    }
    phi_args.emplace_back(predecessor, value);
}

void Inst::OrderPhiArgs() {
    if (op != Opcode::Phi) {
        throw LogicError("{} is not a Phi instruction", op);
    }
    std::sort(phi_args.begin(), phi_args.end(),
              [](const std::pair<Block*, Value>& a, const std::pair<Block*, Value>& b) {
                  return a.first->GetOrder() < b.first->GetOrder();
              });
}

void Inst::Invalidate() {
    ClearArgs();
    ReplaceOpcode(Opcode::Void);
}

void Inst::ClearArgs() {
    if (op == Opcode::Phi) {
        for (auto& pair : phi_args) {
            IR::Value& value{pair.second};
            if (!value.IsImmediate()) {
                UndoUse(value);
            }
        }
        phi_args.clear();
    } else {
        for (auto& value : args) {
            if (!value.IsImmediate()) {
                UndoUse(value);
            }
        }
        // Reset arguments to null
        // std::memset was measured to be faster on MSVC than std::ranges:fill
        std::memset(reinterpret_cast<char*>(&args), 0, sizeof(args));
    }
}

void Inst::ReplaceUsesWith(Value replacement) {
    Invalidate();
    ReplaceOpcode(Opcode::Identity);
    if (!replacement.IsImmediate()) {
        Use(replacement);
    }
    args[0] = replacement;
}

void Inst::ReplaceOpcode(IR::Opcode opcode) {
    if (opcode == IR::Opcode::Phi) {
        throw LogicError("Cannot transition into Phi");
    }
    if (op == Opcode::Phi) {
        // Transition out of phi arguments into non-phi
        std::destroy_at(&phi_args);
        std::construct_at(&args);
    }
    op = opcode;
}

void Inst::Use(const Value& value) {
    Inst* const inst{value.Inst()};
    ++inst->use_count;

    std::unique_ptr<AssociatedInsts>& assoc_inst{inst->associated_insts};
    switch (op) {
    case Opcode::GetZeroFromOp:
        AllocAssociatedInsts(assoc_inst);
        SetPseudoInstruction(assoc_inst->zero_inst, this);
        break;
    case Opcode::GetSignFromOp:
        AllocAssociatedInsts(assoc_inst);
        SetPseudoInstruction(assoc_inst->sign_inst, this);
        break;
    case Opcode::GetCarryFromOp:
        AllocAssociatedInsts(assoc_inst);
        SetPseudoInstruction(assoc_inst->carry_inst, this);
        break;
    case Opcode::GetOverflowFromOp:
        AllocAssociatedInsts(assoc_inst);
        SetPseudoInstruction(assoc_inst->overflow_inst, this);
        break;
    case Opcode::GetSparseFromOp:
        AllocAssociatedInsts(assoc_inst);
        SetPseudoInstruction(assoc_inst->sparse_inst, this);
        break;
    case Opcode::GetInBoundsFromOp:
        AllocAssociatedInsts(assoc_inst);
        SetPseudoInstruction(assoc_inst->in_bounds_inst, this);
        break;
    default:
        break;
    }
}

void Inst::UndoUse(const Value& value) {
    Inst* const inst{value.Inst()};
    --inst->use_count;

    std::unique_ptr<AssociatedInsts>& assoc_inst{inst->associated_insts};
    switch (op) {
    case Opcode::GetZeroFromOp:
        AllocAssociatedInsts(assoc_inst);
        RemovePseudoInstruction(assoc_inst->zero_inst, Opcode::GetZeroFromOp);
        break;
    case Opcode::GetSignFromOp:
        AllocAssociatedInsts(assoc_inst);
        RemovePseudoInstruction(assoc_inst->sign_inst, Opcode::GetSignFromOp);
        break;
    case Opcode::GetCarryFromOp:
        AllocAssociatedInsts(assoc_inst);
        RemovePseudoInstruction(assoc_inst->carry_inst, Opcode::GetCarryFromOp);
        break;
    case Opcode::GetOverflowFromOp:
        AllocAssociatedInsts(assoc_inst);
        RemovePseudoInstruction(assoc_inst->overflow_inst, Opcode::GetOverflowFromOp);
        break;
    case Opcode::GetSparseFromOp:
        AllocAssociatedInsts(assoc_inst);
        RemovePseudoInstruction(assoc_inst->sparse_inst, Opcode::GetSparseFromOp);
        break;
    case Opcode::GetInBoundsFromOp:
        AllocAssociatedInsts(assoc_inst);
        RemovePseudoInstruction(assoc_inst->in_bounds_inst, Opcode::GetInBoundsFromOp);
        break;
    default:
        break;
    }
}

} // namespace Shader::IR


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/bit_field.h"
#include "common/common_types.h"
#include "shader_recompiler/shader_info.h"

namespace Shader::IR {

enum class FmzMode : u8 {
    DontCare, // Not specified for this instruction
    FTZ,      // Flush denorms to zero, NAN is propagated (D3D11, NVN, GL, VK)
    FMZ,      // Flush denorms to zero, x * 0 == 0 (D3D9)
    None,     // Denorms are not flushed, NAN is propagated (nouveau)
};

enum class FpRounding : u8 {
    DontCare, // Not specified for this instruction
    RN,       // Round to nearest even,
    RM,       // Round towards negative infinity
    RP,       // Round towards positive infinity
    RZ,       // Round towards zero
};

struct FpControl {
    bool no_contraction{false};
    FpRounding rounding{FpRounding::DontCare};
    FmzMode fmz_mode{FmzMode::DontCare};
};
static_assert(sizeof(FpControl) <= sizeof(u32));

union TextureInstInfo {
    u32 raw;
    BitField<0, 16, u32> descriptor_index;
    BitField<16, 3, TextureType> type;
    BitField<19, 1, u32> is_depth;
    BitField<20, 1, u32> has_bias;
    BitField<21, 1, u32> has_lod_clamp;
    BitField<22, 1, u32> relaxed_precision;
    BitField<23, 2, u32> gather_component;
    BitField<25, 2, u32> num_derivatives;
    BitField<27, 3, ImageFormat> image_format;
    BitField<30, 1, u32> ndv_is_active;
};
static_assert(sizeof(TextureInstInfo) <= sizeof(u32));

} // namespace Shader::IR

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "shader_recompiler/frontend/ir/opcodes.h"

namespace Shader::IR {

std::string_view NameOf(Opcode op) {
    return Detail::META_TABLE[static_cast<size_t>(op)].name;
}

} // namespace Shader::IR


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <algorithm>
#include <array>

#include <fmt/format.h>

#include "common/polyfill_ranges.h"
#include "shader_recompiler/frontend/ir/type.h"

namespace Shader::IR {

enum class Opcode {
#define OPCODE(name, ...) name,
#include "opcodes.inc"
#undef OPCODE
};

namespace Detail {
struct OpcodeMeta {
    std::string_view name;
    Type type;
    std::array<Type, 5> arg_types;
};

// using enum Type;
constexpr Type Void{Type::Void};
constexpr Type Opaque{Type::Opaque};
constexpr Type Reg{Type::Reg};
constexpr Type Pred{Type::Pred};
constexpr Type Attribute{Type::Attribute};
constexpr Type Patch{Type::Patch};
constexpr Type U1{Type::U1};
constexpr Type U8{Type::U8};
constexpr Type U16{Type::U16};
constexpr Type U32{Type::U32};
constexpr Type U64{Type::U64};
constexpr Type F16{Type::F16};
constexpr Type F32{Type::F32};
constexpr Type F64{Type::F64};
constexpr Type U32x2{Type::U32x2};
constexpr Type U32x3{Type::U32x3};
constexpr Type U32x4{Type::U32x4};
constexpr Type F16x2{Type::F16x2};
constexpr Type F16x3{Type::F16x3};
constexpr Type F16x4{Type::F16x4};
constexpr Type F32x2{Type::F32x2};
constexpr Type F32x3{Type::F32x3};
constexpr Type F32x4{Type::F32x4};
constexpr Type F64x2{Type::F64x2};
constexpr Type F64x3{Type::F64x3};
constexpr Type F64x4{Type::F64x4};

constexpr OpcodeMeta META_TABLE[]{
#define OPCODE(name_token, type_token, ...)                                                        \
    {                                                                                \
        .name{#name_token},                                                                        \
        .type = type_token,                                                                        \
        .arg_types{__VA_ARGS__},                                                                   \
    },
#include "opcodes.inc"
#undef OPCODE
};
constexpr size_t CalculateNumArgsOf(Opcode op) {
    const auto& arg_types{META_TABLE[static_cast<size_t>(op)].arg_types};
    return static_cast<size_t>(
        std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void)));
}

constexpr u8 NUM_ARGS[]{
#define OPCODE(name_token, type_token, ...) static_cast<u8>(CalculateNumArgsOf(Opcode::name_token)),
#include "opcodes.inc"
#undef OPCODE
};
} // namespace Detail

/// Get return type of an opcode
[[nodiscard]] inline Type TypeOf(Opcode op) noexcept {
    return Detail::META_TABLE[static_cast<size_t>(op)].type;
}

/// Get the number of arguments an opcode accepts
[[nodiscard]] inline size_t NumArgsOf(Opcode op) noexcept {
    return static_cast<size_t>(Detail::NUM_ARGS[static_cast<size_t>(op)]);
}

/// Get the required type of an argument of an opcode
[[nodiscard]] inline Type ArgTypeOf(Opcode op, size_t arg_index) noexcept {
    return Detail::META_TABLE[static_cast<size_t>(op)].arg_types[arg_index];
}

/// Get the name of an opcode
[[nodiscard]] std::string_view NameOf(Opcode op);

} // namespace Shader::IR

template <>
struct fmt::formatter<Shader::IR::Opcode> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
    }
    template <typename FormatContext>
    auto format(const Shader::IR::Opcode& op, FormatContext& ctx) {
        return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(op));
    }
};