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

#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/profile.h"
#include "shader_recompiler/runtime_info.h"

namespace Shader::Backend::GLASM {
namespace {
void StorageOp(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset,
               std::string_view then_expr, std::string_view else_expr = {}) {
    // Operate on bindless SSBO, call the expression with bounds checking
    // address = c[binding].xy
    // length  = c[binding].z
    const u32 sb_binding{binding.U32()};
    ctx.Add("PK64.U DC,c[{}];"           // pointer = address
            "CVT.U64.U32 DC.z,{};"       // offset = uint64_t(offset)
            "ADD.U64 DC.x,DC.x,DC.z;"    // pointer += offset
            "SLT.U.CC RC.x,{},c[{}].z;", // cc = offset < length
            sb_binding, offset, offset, sb_binding);
    if (else_expr.empty()) {
        ctx.Add("IF NE.x;{}ENDIF;", then_expr);
    } else {
        ctx.Add("IF NE.x;{}ELSE;{}ENDIF;", then_expr, else_expr);
    }
}

void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std::string_view expr,
                     std::string_view else_expr = {}) {
    const size_t num_buffers{ctx.info.storage_buffers_descriptors.size()};
    for (size_t index = 0; index < num_buffers; ++index) {
        if (!ctx.info.nvn_buffer_used[index]) {
            continue;
        }
        const auto& ssbo{ctx.info.storage_buffers_descriptors[index]};
        const u64 ssbo_align_mask{~(ctx.profile.min_ssbo_alignment - 1U)};
        ctx.Add("LDC.U64 DC.x,c{}[{}];"    // unaligned_ssbo_addr
                "AND.U64 DC.x,DC.x,{};"    // ssbo_addr = unaligned_ssbo_addr & ssbo_align_mask
                "LDC.U32 RC.x,c{}[{}];"    // ssbo_size_u32
                "CVT.U64.U32 DC.y,RC.x;"   // ssbo_size = ssbo_size_u32
                "ADD.U64 DC.y,DC.y,DC.x;"  // ssbo_end = ssbo_addr + ssbo_size
                "SGE.U64 RC.x,{}.x,DC.x;"  // a = input_addr >= ssbo_addr ? -1 : 0
                "SLT.U64 RC.y,{}.x,DC.y;"  // b = input_addr < ssbo_end   ? -1 : 0
                "AND.U.CC RC.x,RC.x,RC.y;" // cond = a && b
                "IF NE.x;"                 // if cond
                "SUB.U64 DC.x,{}.x,DC.x;", // offset = input_addr - ssbo_addr
                ssbo.cbuf_index, ssbo.cbuf_offset, ssbo_align_mask, ssbo.cbuf_index,
                ssbo.cbuf_offset + 8, address, address, address);
        if (pointer_based) {
            ctx.Add("PK64.U DC.y,c[{}];"      // host_ssbo = cbuf
                    "ADD.U64 DC.x,DC.x,DC.y;" // host_addr = host_ssbo + offset
                    "{}"
                    "ELSE;",
                    index, expr);
        } else {
            ctx.Add("CVT.U32.U64 RC.x,DC.x;"
                    "{},ssbo{}[RC.x];"
                    "ELSE;",
                    expr, index);
        }
    }
    if (!else_expr.empty()) {
        ctx.Add("{}", else_expr);
    }
    const size_t num_used_buffers{ctx.info.nvn_buffer_used.count()};
    for (size_t index = 0; index < num_used_buffers; ++index) {
        ctx.Add("ENDIF;");
    }
}

template <typename ValueType>
void Write(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset, ValueType value,
           std::string_view size) {
    if (ctx.runtime_info.glasm_use_storage_buffers) {
        ctx.Add("STB.{} {},ssbo{}[{}];", size, value, binding.U32(), offset);
    } else {
        StorageOp(ctx, binding, offset, fmt::format("STORE.{} {},DC.x;", size, value));
    }
}

void Load(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding, ScalarU32 offset,
          std::string_view size) {
    const Register ret{ctx.reg_alloc.Define(inst)};
    if (ctx.runtime_info.glasm_use_storage_buffers) {
        ctx.Add("LDB.{} {},ssbo{}[{}];", size, ret, binding.U32(), offset);
    } else {
        StorageOp(ctx, binding, offset, fmt::format("LOAD.{} {},DC.x;", size, ret),
                  fmt::format("MOV.U {},{{0,0,0,0}};", ret));
    }
}

template <typename ValueType>
void GlobalWrite(EmitContext& ctx, Register address, ValueType value, std::string_view size) {
    if (ctx.runtime_info.glasm_use_storage_buffers) {
        GlobalStorageOp(ctx, address, false, fmt::format("STB.{} {}", size, value));
    } else {
        GlobalStorageOp(ctx, address, true, fmt::format("STORE.{} {},DC.x;", size, value));
    }
}

void GlobalLoad(EmitContext& ctx, IR::Inst& inst, Register address, std::string_view size) {
    const Register ret{ctx.reg_alloc.Define(inst)};
    if (ctx.runtime_info.glasm_use_storage_buffers) {
        GlobalStorageOp(ctx, address, false, fmt::format("LDB.{} {}", size, ret));
    } else {
        GlobalStorageOp(ctx, address, true, fmt::format("LOAD.{} {},DC.x;", size, ret),
                        fmt::format("MOV.S {},0;", ret));
    }
}

template <typename ValueType>
void Atom(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding, ScalarU32 offset,
          ValueType value, std::string_view operation, std::string_view size) {
    const Register ret{ctx.reg_alloc.Define(inst)};
    if (ctx.runtime_info.glasm_use_storage_buffers) {
        ctx.Add("ATOMB.{}.{} {},{},ssbo{}[{}];", operation, size, ret, value, binding.U32(),
                offset);
    } else {
        StorageOp(ctx, binding, offset,
                  fmt::format("ATOM.{}.{} {},{},DC.x;", operation, size, ret, value));
    }
}
} // Anonymous namespace

void EmitLoadGlobalU8(EmitContext& ctx, IR::Inst& inst, Register address) {
    GlobalLoad(ctx, inst, address, "U8");
}

void EmitLoadGlobalS8(EmitContext& ctx, IR::Inst& inst, Register address) {
    GlobalLoad(ctx, inst, address, "S8");
}

void EmitLoadGlobalU16(EmitContext& ctx, IR::Inst& inst, Register address) {
    GlobalLoad(ctx, inst, address, "U16");
}

void EmitLoadGlobalS16(EmitContext& ctx, IR::Inst& inst, Register address) {
    GlobalLoad(ctx, inst, address, "S16");
}

void EmitLoadGlobal32(EmitContext& ctx, IR::Inst& inst, Register address) {
    GlobalLoad(ctx, inst, address, "U32");
}

void EmitLoadGlobal64(EmitContext& ctx, IR::Inst& inst, Register address) {
    GlobalLoad(ctx, inst, address, "U32X2");
}

void EmitLoadGlobal128(EmitContext& ctx, IR::Inst& inst, Register address) {
    GlobalLoad(ctx, inst, address, "U32X4");
}

void EmitWriteGlobalU8(EmitContext& ctx, Register address, Register value) {
    GlobalWrite(ctx, address, value, "U8");
}

void EmitWriteGlobalS8(EmitContext& ctx, Register address, Register value) {
    GlobalWrite(ctx, address, value, "S8");
}

void EmitWriteGlobalU16(EmitContext& ctx, Register address, Register value) {
    GlobalWrite(ctx, address, value, "U16");
}

void EmitWriteGlobalS16(EmitContext& ctx, Register address, Register value) {
    GlobalWrite(ctx, address, value, "S16");
}

void EmitWriteGlobal32(EmitContext& ctx, Register address, ScalarU32 value) {
    GlobalWrite(ctx, address, value, "U32");
}

void EmitWriteGlobal64(EmitContext& ctx, Register address, Register value) {
    GlobalWrite(ctx, address, value, "U32X2");
}

void EmitWriteGlobal128(EmitContext& ctx, Register address, Register value) {
    GlobalWrite(ctx, address, value, "U32X4");
}

void EmitLoadStorageU8(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                       ScalarU32 offset) {
    Load(ctx, inst, binding, offset, "U8");
}

void EmitLoadStorageS8(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                       ScalarU32 offset) {
    Load(ctx, inst, binding, offset, "S8");
}

void EmitLoadStorageU16(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                        ScalarU32 offset) {
    Load(ctx, inst, binding, offset, "U16");
}

void EmitLoadStorageS16(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                        ScalarU32 offset) {
    Load(ctx, inst, binding, offset, "S16");
}

void EmitLoadStorage32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                       ScalarU32 offset) {
    Load(ctx, inst, binding, offset, "U32");
}

void EmitLoadStorage64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                       ScalarU32 offset) {
    Load(ctx, inst, binding, offset, "U32X2");
}

void EmitLoadStorage128(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                        ScalarU32 offset) {
    Load(ctx, inst, binding, offset, "U32X4");
}

void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset,
                        ScalarU32 value) {
    Write(ctx, binding, offset, value, "U8");
}

void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset,
                        ScalarS32 value) {
    Write(ctx, binding, offset, value, "S8");
}

void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset,
                         ScalarU32 value) {
    Write(ctx, binding, offset, value, "U16");
}

void EmitWriteStorageS16(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset,
                         ScalarS32 value) {
    Write(ctx, binding, offset, value, "S16");
}

void EmitWriteStorage32(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset,
                        ScalarU32 value) {
    Write(ctx, binding, offset, value, "U32");
}

void EmitWriteStorage64(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset,
                        Register value) {
    Write(ctx, binding, offset, value, "U32X2");
}

void EmitWriteStorage128(EmitContext& ctx, const IR::Value& binding, ScalarU32 offset,
                         Register value) {
    Write(ctx, binding, offset, value, "U32X4");
}

void EmitSharedAtomicIAdd32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                            ScalarU32 value) {
    ctx.Add("ATOMS.ADD.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicSMin32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                            ScalarS32 value) {
    ctx.Add("ATOMS.MIN.S32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicUMin32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                            ScalarU32 value) {
    ctx.Add("ATOMS.MIN.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicSMax32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                            ScalarS32 value) {
    ctx.Add("ATOMS.MAX.S32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicUMax32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                            ScalarU32 value) {
    ctx.Add("ATOMS.MAX.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicInc32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                           ScalarU32 value) {
    ctx.Add("ATOMS.IWRAP.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicDec32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                           ScalarU32 value) {
    ctx.Add("ATOMS.DWRAP.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicAnd32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                           ScalarU32 value) {
    ctx.Add("ATOMS.AND.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicOr32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                          ScalarU32 value) {
    ctx.Add("ATOMS.OR.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicXor32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                           ScalarU32 value) {
    ctx.Add("ATOMS.XOR.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicExchange32(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                                ScalarU32 value) {
    ctx.Add("ATOMS.EXCH.U32 {},{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicExchange64(EmitContext& ctx, IR::Inst& inst, ScalarU32 pointer_offset,
                                Register value) {
    ctx.LongAdd("ATOMS.EXCH.U64 {}.x,{},shared_mem[{}];", inst, value, pointer_offset);
}

void EmitSharedAtomicExchange32x2([[maybe_unused]] EmitContext& ctx,
                                  [[maybe_unused]] IR::Inst& inst,
                                  [[maybe_unused]] ScalarU32 pointer_offset,
                                  [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicIAdd32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "ADD", "U32");
}

void EmitStorageAtomicSMin32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, ScalarS32 value) {
    Atom(ctx, inst, binding, offset, value, "MIN", "S32");
}

void EmitStorageAtomicUMin32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "MIN", "U32");
}

void EmitStorageAtomicSMax32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, ScalarS32 value) {
    Atom(ctx, inst, binding, offset, value, "MAX", "S32");
}

void EmitStorageAtomicUMax32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "MAX", "U32");
}

void EmitStorageAtomicInc32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                            ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "IWRAP", "U32");
}

void EmitStorageAtomicDec32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                            ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "DWRAP", "U32");
}

void EmitStorageAtomicAnd32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                            ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "AND", "U32");
}

void EmitStorageAtomicOr32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                           ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "OR", "U32");
}

void EmitStorageAtomicXor32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                            ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "XOR", "U32");
}

void EmitStorageAtomicExchange32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                                 ScalarU32 offset, ScalarU32 value) {
    Atom(ctx, inst, binding, offset, value, "EXCH", "U32");
}

void EmitStorageAtomicIAdd64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "ADD", "U64");
}

void EmitStorageAtomicSMin64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "MIN", "S64");
}

void EmitStorageAtomicUMin64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "MIN", "U64");
}

void EmitStorageAtomicSMax64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "MAX", "S64");
}

void EmitStorageAtomicUMax64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "MAX", "U64");
}

void EmitStorageAtomicAnd64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                            ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "AND", "U64");
}

void EmitStorageAtomicOr64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                           ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "OR", "U64");
}

void EmitStorageAtomicXor64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                            ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "XOR", "U64");
}

void EmitStorageAtomicExchange64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                                 ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "EXCH", "U64");
}

void EmitStorageAtomicIAdd32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                               [[maybe_unused]] const IR::Value& binding,
                               [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicSMin32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                               [[maybe_unused]] const IR::Value& binding,
                               [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicUMin32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                               [[maybe_unused]] const IR::Value& binding,
                               [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicSMax32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                               [[maybe_unused]] const IR::Value& binding,
                               [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicUMax32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                               [[maybe_unused]] const IR::Value& binding,
                               [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicAnd32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                              [[maybe_unused]] const IR::Value& binding,
                              [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicOr32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                             [[maybe_unused]] const IR::Value& binding,
                             [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicXor32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                              [[maybe_unused]] const IR::Value& binding,
                              [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicExchange32x2([[maybe_unused]] EmitContext& ctx,
                                   [[maybe_unused]] IR::Inst& inst,
                                   [[maybe_unused]] const IR::Value& binding,
                                   [[maybe_unused]] ScalarU32 offset,
                                   [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicAddF32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                             ScalarU32 offset, ScalarF32 value) {
    Atom(ctx, inst, binding, offset, value, "ADD", "F32");
}

void EmitStorageAtomicAddF16x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                               ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "ADD", "F16x2");
}

void EmitStorageAtomicAddF32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                               [[maybe_unused]] const IR::Value& binding,
                               [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicMinF16x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                               ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "MIN", "F16x2");
}

void EmitStorageAtomicMinF32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                               [[maybe_unused]] const IR::Value& binding,
                               [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitStorageAtomicMaxF16x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
                               ScalarU32 offset, Register value) {
    Atom(ctx, inst, binding, offset, value, "MAX", "F16x2");
}

void EmitStorageAtomicMaxF32x2([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
                               [[maybe_unused]] const IR::Value& binding,
                               [[maybe_unused]] ScalarU32 offset, [[maybe_unused]] Register value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicIAdd32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicSMin32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicUMin32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicSMax32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicUMax32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicInc32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicDec32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicAnd32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicOr32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicXor32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicExchange32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicIAdd64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicSMin64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicUMin64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicSMax64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicUMax64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicInc64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicDec64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicAnd64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicOr64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicXor64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicExchange64(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicIAdd32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicSMin32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicUMin32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicSMax32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicUMax32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicInc32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicDec32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicAnd32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicOr32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicXor32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicExchange32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicAddF32(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicAddF16x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicAddF32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicMinF16x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicMinF32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicMaxF16x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

void EmitGlobalAtomicMaxF32x2(EmitContext&) {
    throw NotImplementedException("GLASM instruction");
}

} // namespace Shader::Backend::GLASM


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

#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/value.h"

namespace Shader::Backend::GLASM {

#define NotImplemented() throw NotImplementedException("GLASM instruction {}", __LINE__)

void EmitGetRegister(EmitContext& ctx) {
    NotImplemented();
}

void EmitSetRegister(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetPred(EmitContext& ctx) {
    NotImplemented();
}

void EmitSetPred(EmitContext& ctx) {
    NotImplemented();
}

void EmitSetGotoVariable(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetGotoVariable(EmitContext& ctx) {
    NotImplemented();
}

void EmitSetIndirectBranchVariable(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetIndirectBranchVariable(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetZFlag(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetSFlag(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetCFlag(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetOFlag(EmitContext& ctx) {
    NotImplemented();
}

void EmitSetZFlag(EmitContext& ctx) {
    NotImplemented();
}

void EmitSetSFlag(EmitContext& ctx) {
    NotImplemented();
}

void EmitSetCFlag(EmitContext& ctx) {
    NotImplemented();
}

void EmitSetOFlag(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetZeroFromOp(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetSignFromOp(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetCarryFromOp(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetOverflowFromOp(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetSparseFromOp(EmitContext& ctx) {
    NotImplemented();
}

void EmitGetInBoundsFromOp(EmitContext& ctx) {
    NotImplemented();
}

} // namespace Shader::Backend::GLASM

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/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/value.h"

namespace Shader::Backend::GLASM {

void EmitSelectU1(EmitContext& ctx, IR::Inst& inst, ScalarS32 cond, ScalarS32 true_value,
                  ScalarS32 false_value) {
    ctx.Add("CMP.S {},{},{},{};", inst, cond, true_value, false_value);
}

void EmitSelectU8([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] ScalarS32 cond,
                  [[maybe_unused]] ScalarS32 true_value, [[maybe_unused]] ScalarS32 false_value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitSelectU16([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] ScalarS32 cond,
                   [[maybe_unused]] ScalarS32 true_value, [[maybe_unused]] ScalarS32 false_value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitSelectU32(EmitContext& ctx, IR::Inst& inst, ScalarS32 cond, ScalarS32 true_value,
                   ScalarS32 false_value) {
    ctx.Add("CMP.S {},{},{},{};", inst, cond, true_value, false_value);
}

void EmitSelectU64(EmitContext& ctx, IR::Inst& inst, ScalarS32 cond, Register true_value,
                   Register false_value) {
    ctx.reg_alloc.InvalidateConditionCodes();
    const Register ret{ctx.reg_alloc.LongDefine(inst)};
    if (ret == true_value) {
        ctx.Add("MOV.S.CC RC.x,{};"
                "MOV.U64 {}.x(EQ.x),{};",
                cond, ret, false_value);
    } else if (ret == false_value) {
        ctx.Add("MOV.S.CC RC.x,{};"
                "MOV.U64 {}.x(NE.x),{};",
                cond, ret, true_value);
    } else {
        ctx.Add("MOV.S.CC RC.x,{};"
                "MOV.U64 {}.x,{};"
                "MOV.U64 {}.x(NE.x),{};",
                cond, ret, false_value, ret, true_value);
    }
}

void EmitSelectF16([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] ScalarS32 cond,
                   [[maybe_unused]] Register true_value, [[maybe_unused]] Register false_value) {
    throw NotImplementedException("GLASM instruction");
}

void EmitSelectF32(EmitContext& ctx, IR::Inst& inst, ScalarS32 cond, ScalarS32 true_value,
                   ScalarS32 false_value) {
    ctx.Add("CMP.S {},{},{},{};", inst, cond, true_value, false_value);
}

void EmitSelectF64([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] ScalarS32 cond,
                   [[maybe_unused]] Register true_value, [[maybe_unused]] Register false_value) {
    throw NotImplementedException("GLASM instruction");
}

} // namespace Shader::Backend::GLASM


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

#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/value.h"

namespace Shader::Backend::GLASM {
void EmitLoadSharedU8(EmitContext& ctx, IR::Inst& inst, ScalarU32 offset) {
    ctx.Add("LDS.U8 {},shared_mem[{}];", inst, offset);
}

void EmitLoadSharedS8(EmitContext& ctx, IR::Inst& inst, ScalarU32 offset) {
    ctx.Add("LDS.S8 {},shared_mem[{}];", inst, offset);
}

void EmitLoadSharedU16(EmitContext& ctx, IR::Inst& inst, ScalarU32 offset) {
    ctx.Add("LDS.U16 {},shared_mem[{}];", inst, offset);
}

void EmitLoadSharedS16(EmitContext& ctx, IR::Inst& inst, ScalarU32 offset) {
    ctx.Add("LDS.S16 {},shared_mem[{}];", inst, offset);
}

void EmitLoadSharedU32(EmitContext& ctx, IR::Inst& inst, ScalarU32 offset) {
    ctx.Add("LDS.U32 {},shared_mem[{}];", inst, offset);
}

void EmitLoadSharedU64(EmitContext& ctx, IR::Inst& inst, ScalarU32 offset) {
    ctx.Add("LDS.U32X2 {},shared_mem[{}];", inst, offset);
}

void EmitLoadSharedU128(EmitContext& ctx, IR::Inst& inst, ScalarU32 offset) {
    ctx.Add("LDS.U32X4 {},shared_mem[{}];", inst, offset);
}

void EmitWriteSharedU8(EmitContext& ctx, ScalarU32 offset, ScalarU32 value) {
    ctx.Add("STS.U8 {},shared_mem[{}];", value, offset);
}

void EmitWriteSharedU16(EmitContext& ctx, ScalarU32 offset, ScalarU32 value) {
    ctx.Add("STS.U16 {},shared_mem[{}];", value, offset);
}

void EmitWriteSharedU32(EmitContext& ctx, ScalarU32 offset, ScalarU32 value) {
    ctx.Add("STS.U32 {},shared_mem[{}];", value, offset);
}

void EmitWriteSharedU64(EmitContext& ctx, ScalarU32 offset, Register value) {
    ctx.Add("STS.U32X2 {},shared_mem[{}];", value, offset);
}

void EmitWriteSharedU128(EmitContext& ctx, ScalarU32 offset, Register value) {
    ctx.Add("STS.U32X4 {},shared_mem[{}];", value, offset);
}
} // namespace Shader::Backend::GLASM


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

#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/value.h"

namespace Shader::Backend::GLASM {

static void DefinePhi(EmitContext& ctx, IR::Inst& phi) {
    switch (phi.Type()) {
    case IR::Type::U1:
    case IR::Type::U32:
    case IR::Type::F32:
        ctx.reg_alloc.Define(phi);
        break;
    case IR::Type::U64:
    case IR::Type::F64:
        ctx.reg_alloc.LongDefine(phi);
        break;
    default:
        throw NotImplementedException("Phi node type {}", phi.Type());
    }
}

void EmitPhi(EmitContext& ctx, IR::Inst& phi) {
    const size_t num_args{phi.NumArgs()};
    for (size_t i = 0; i < num_args; ++i) {
        ctx.reg_alloc.Consume(phi.Arg(i));
    }
    if (!phi.Definition<Id>().is_valid) {
        // The phi node wasn't forward defined
        DefinePhi(ctx, phi);
    }
}

void EmitVoid(EmitContext&) {}

void EmitReference(EmitContext& ctx, const IR::Value& value) {
    ctx.reg_alloc.Consume(value);
}

void EmitPhiMove(EmitContext& ctx, const IR::Value& phi_value, const IR::Value& value) {
    IR::Inst& phi{RegAlloc::AliasInst(*phi_value.Inst())};
    if (!phi.Definition<Id>().is_valid) {
        // The phi node wasn't forward defined
        DefinePhi(ctx, phi);
    }
    const Register phi_reg{ctx.reg_alloc.Consume(IR::Value{&phi})};
    const Value eval_value{ctx.reg_alloc.Consume(value)};

    if (phi_reg == eval_value) {
        return;
    }
    switch (phi.Flags<IR::Type>()) {
    case IR::Type::U1:
    case IR::Type::U32:
    case IR::Type::F32:
        ctx.Add("MOV.S {}.x,{};", phi_reg, ScalarS32{eval_value});
        break;
    case IR::Type::U64:
    case IR::Type::F64:
        ctx.Add("MOV.U64 {}.x,{};", phi_reg, ScalarRegister{eval_value});
        break;
    default:
        throw NotImplementedException("Phi node type {}", phi.Type());
    }
}

void EmitPrologue(EmitContext&) {
    // TODO
}

void EmitEpilogue(EmitContext&) {
    // TODO
}

void EmitEmitVertex(EmitContext& ctx, ScalarS32 stream) {
    if (stream.type == Type::U32 && stream.imm_u32 == 0) {
        ctx.Add("EMIT;");
    } else {
        ctx.Add("EMITS {};", stream);
    }
}

void EmitEndPrimitive(EmitContext& ctx, const IR::Value& stream) {
    if (!stream.IsImmediate()) {
        LOG_WARNING(Shader_GLASM, "Stream is not immediate");
    }
    ctx.reg_alloc.Consume(stream);
    ctx.Add("ENDPRIM;");
}

} // namespace Shader::Backend::GLASM


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

#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"

namespace Shader::Backend::GLASM {

void EmitUndefU1(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.S {}.x,0;", inst);
}

void EmitUndefU8(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.S {}.x,0;", inst);
}

void EmitUndefU16(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.S {}.x,0;", inst);
}

void EmitUndefU32(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.S {}.x,0;", inst);
}

void EmitUndefU64(EmitContext& ctx, IR::Inst& inst) {
    ctx.LongAdd("MOV.S64 {}.x,0;", inst);
}

} // namespace Shader::Backend::GLASM


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

#include "shader_recompiler/backend/glasm/emit_glasm_instructions.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/profile.h"

namespace Shader::Backend::GLASM {

void EmitLaneId(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.S {}.x,{}.threadid;", inst, ctx.stage_name);
}

void EmitVoteAll(EmitContext& ctx, IR::Inst& inst, ScalarS32 pred) {
    ctx.Add("TGALL.S {}.x,{};", inst, pred);
}

void EmitVoteAny(EmitContext& ctx, IR::Inst& inst, ScalarS32 pred) {
    ctx.Add("TGANY.S {}.x,{};", inst, pred);
}

void EmitVoteEqual(EmitContext& ctx, IR::Inst& inst, ScalarS32 pred) {
    ctx.Add("TGEQ.S {}.x,{};", inst, pred);
}

void EmitSubgroupBallot(EmitContext& ctx, IR::Inst& inst, ScalarS32 pred) {
    ctx.Add("TGBALLOT {}.x,{};", inst, pred);
}

void EmitSubgroupEqMask(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.U {},{}.threadeqmask;", inst, ctx.stage_name);
}

void EmitSubgroupLtMask(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.U {},{}.threadltmask;", inst, ctx.stage_name);
}

void EmitSubgroupLeMask(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.U {},{}.threadlemask;", inst, ctx.stage_name);
}

void EmitSubgroupGtMask(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.U {},{}.threadgtmask;", inst, ctx.stage_name);
}

void EmitSubgroupGeMask(EmitContext& ctx, IR::Inst& inst) {
    ctx.Add("MOV.U {},{}.threadgemask;", inst, ctx.stage_name);
}

static void Shuffle(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
                    const IR::Value& clamp, const IR::Value& segmentation_mask,
                    std::string_view op) {
    IR::Inst* const in_bounds{inst.GetAssociatedPseudoOperation(IR::Opcode::GetInBoundsFromOp)};
    if (in_bounds) {
        in_bounds->Invalidate();
    }
    std::string mask;
    if (clamp.IsImmediate() && segmentation_mask.IsImmediate()) {
        mask = fmt::to_string(clamp.U32() | (segmentation_mask.U32() << 8));
    } else {
        mask = "RC";
        ctx.Add("BFI.U RC.x,{{5,8,0,0}},{},{};",
                ScalarU32{ctx.reg_alloc.Consume(segmentation_mask)},
                ScalarU32{ctx.reg_alloc.Consume(clamp)});
    }
    const Register value_ret{ctx.reg_alloc.Define(inst)};
    if (in_bounds) {
        const Register bounds_ret{ctx.reg_alloc.Define(*in_bounds)};
        ctx.Add("SHF{}.U {},{},{},{};"
                "MOV.U {}.x,{}.y;",
                op, bounds_ret, value, index, mask, value_ret, bounds_ret);
    } else {
        ctx.Add("SHF{}.U {},{},{},{};"
                "MOV.U {}.x,{}.y;",
                op, value_ret, value, index, mask, value_ret, value_ret);
    }
}

void EmitShuffleIndex(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
                      const IR::Value& clamp, const IR::Value& segmentation_mask) {
    Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "IDX");
}

void EmitShuffleUp(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
                   const IR::Value& clamp, const IR::Value& segmentation_mask) {
    Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "UP");
}

void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
                     const IR::Value& clamp, const IR::Value& segmentation_mask) {
    Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "DOWN");
}

void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
                          const IR::Value& clamp, const IR::Value& segmentation_mask) {
    Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "XOR");
}

void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
                     ScalarU32 swizzle) {
    const auto ret{ctx.reg_alloc.Define(inst)};
    ctx.Add("AND.U RC.z,{}.threadid,3;"
            "SHL.U RC.z,RC.z,1;"
            "SHR.U RC.z,{},RC.z;"
            "AND.U RC.z,RC.z,3;"
            "MUL.F RC.x,{},FSWZA[RC.z];"
            "MUL.F RC.y,{},FSWZB[RC.z];"
            "ADD.F {}.x,RC.x,RC.y;",
            ctx.stage_name, swizzle, op_a, op_b, ret);
}

void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, ScalarF32 p) {
    if (ctx.profile.support_derivative_control) {
        ctx.Add("DDX.FINE {}.x,{};", inst, p);
    } else {
        LOG_WARNING(Shader_GLASM, "Fine derivatives not supported by device");
        ctx.Add("DDX {}.x,{};", inst, p);
    }
}

void EmitDPdyFine(EmitContext& ctx, IR::Inst& inst, ScalarF32 p) {
    if (ctx.profile.support_derivative_control) {
        ctx.Add("DDY.FINE {}.x,{};", inst, p);
    } else {
        LOG_WARNING(Shader_GLASM, "Fine derivatives not supported by device");
        ctx.Add("DDY {}.x,{};", inst, p);
    }
}

void EmitDPdxCoarse(EmitContext& ctx, IR::Inst& inst, ScalarF32 p) {
    if (ctx.profile.support_derivative_control) {
        ctx.Add("DDX.COARSE {}.x,{};", inst, p);
    } else {
        LOG_WARNING(Shader_GLASM, "Coarse derivatives not supported by device");
        ctx.Add("DDX {}.x,{};", inst, p);
    }
}

void EmitDPdyCoarse(EmitContext& ctx, IR::Inst& inst, ScalarF32 p) {
    if (ctx.profile.support_derivative_control) {
        ctx.Add("DDY.COARSE {}.x,{};", inst, p);
    } else {
        LOG_WARNING(Shader_GLASM, "Coarse derivatives not supported by device");
        ctx.Add("DDY {}.x,{};", inst, p);
    }
}

} // namespace Shader::Backend::GLASM


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

#include "shader_recompiler/backend/bindings.h"
#include "shader_recompiler/backend/glasm/emit_glasm.h"
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/profile.h"
#include "shader_recompiler/runtime_info.h"

namespace Shader::Backend::GLASM {
namespace {
std::string_view InterpDecorator(Interpolation interp) {
    switch (interp) {
    case Interpolation::Smooth:
        return "";
    case Interpolation::Flat:
        return "FLAT ";
    case Interpolation::NoPerspective:
        return "NOPERSPECTIVE ";
    }
    throw InvalidArgument("Invalid interpolation {}", interp);
}

bool IsInputArray(Stage stage) {
    return stage == Stage::Geometry || stage == Stage::TessellationControl ||
           stage == Stage::TessellationEval;
}
} // Anonymous namespace

EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile& profile_,
                         const RuntimeInfo& runtime_info_)
    : info{program.info}, profile{profile_}, runtime_info{runtime_info_} {
    // FIXME: Temporary partial implementation
    u32 cbuf_index{};
    for (const auto& desc : info.constant_buffer_descriptors) {
        if (desc.count != 1) {
            throw NotImplementedException("Constant buffer descriptor array");
        }
        Add("CBUFFER c{}[]={{program.buffer[{}]}};", desc.index, cbuf_index);
        ++cbuf_index;
    }
    u32 ssbo_index{};
    for (const auto& desc : info.storage_buffers_descriptors) {
        if (desc.count != 1) {
            throw NotImplementedException("Storage buffer descriptor array");
        }
        if (runtime_info.glasm_use_storage_buffers) {
            Add("STORAGE ssbo{}[]={{program.storage[{}]}};", ssbo_index, bindings.storage_buffer);
            ++bindings.storage_buffer;
            ++ssbo_index;
        }
    }
    if (!runtime_info.glasm_use_storage_buffers) {
        if (const size_t num = info.storage_buffers_descriptors.size(); num > 0) {
            const size_t index{num + PROGRAM_LOCAL_PARAMETER_STORAGE_BUFFER_BASE};
            Add("PARAM c[{}]={{program.local[0..{}]}};", index, index - 1);
        }
    }
    stage = program.stage;
    switch (program.stage) {
    case Stage::VertexA:
    case Stage::VertexB:
        stage_name = "vertex";
        attrib_name = "vertex";
        break;
    case Stage::TessellationControl:
    case Stage::TessellationEval:
        stage_name = "primitive";
        attrib_name = "primitive";
        break;
    case Stage::Geometry:
        stage_name = "primitive";
        attrib_name = "vertex";
        break;
    case Stage::Fragment:
        stage_name = "fragment";
        attrib_name = "fragment";
        break;
    case Stage::Compute:
        stage_name = "invocation";
        break;
    }
    const std::string_view attr_stage{stage == Stage::Fragment ? "fragment" : "vertex"};
    const VaryingState loads{info.loads.mask | info.passthrough.mask};
    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
        if (loads.Generic(index)) {
            Add("{}ATTRIB in_attr{}[]={{{}.attrib[{}..{}]}};",
                InterpDecorator(info.interpolation[index]), index, attr_stage, index, index);
        }
    }
    if (IsInputArray(stage) && loads.AnyComponent(IR::Attribute::PositionX)) {
        Add("ATTRIB vertex_position=vertex.position;");
    }
    if (info.uses_invocation_id) {
        Add("ATTRIB primitive_invocation=primitive.invocation;");
    }
    if (info.uses_invocation_info &&
        (stage == Stage::TessellationControl || stage == Stage::TessellationEval)) {
        Add("ATTRIB primitive_vertexcount = primitive.vertexcount;");
    }
    if (info.stores_tess_level_outer) {
        Add("OUTPUT result_patch_tessouter[]={{result.patch.tessouter[0..3]}};");
    }
    if (info.stores_tess_level_inner) {
        Add("OUTPUT result_patch_tessinner[]={{result.patch.tessinner[0..1]}};");
    }
    if (info.stores.ClipDistances()) {
        Add("OUTPUT result_clip[]={{result.clip[0..7]}};");
    }
    for (size_t index = 0; index < info.uses_patches.size(); ++index) {
        if (!info.uses_patches[index]) {
            continue;
        }
        if (stage == Stage::TessellationControl) {
            Add("OUTPUT result_patch_attrib{}[]={{result.patch.attrib[{}..{}]}};"
                "ATTRIB primitive_out_patch_attrib{}[]={{primitive.out.patch.attrib[{}..{}]}};",
                index, index, index, index, index, index);
        } else {
            Add("ATTRIB primitive_patch_attrib{}[]={{primitive.patch.attrib[{}..{}]}};", index,
                index, index);
        }
    }
    if (stage == Stage::Fragment) {
        Add("OUTPUT frag_color0=result.color;");
        for (size_t index = 1; index < info.stores_frag_color.size(); ++index) {
            Add("OUTPUT frag_color{}=result.color[{}];", index, index);
        }
    }
    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
        if (info.stores.Generic(index)) {
            Add("OUTPUT out_attr{}[]={{result.attrib[{}..{}]}};", index, index, index);
        }
    }
    image_buffer_bindings.reserve(info.image_buffer_descriptors.size());
    for (const auto& desc : info.image_buffer_descriptors) {
        image_buffer_bindings.push_back(bindings.image);
        bindings.image += desc.count;
    }
    image_bindings.reserve(info.image_descriptors.size());
    for (const auto& desc : info.image_descriptors) {
        image_bindings.push_back(bindings.image);
        bindings.image += desc.count;
    }
    texture_buffer_bindings.reserve(info.texture_buffer_descriptors.size());
    for (const auto& desc : info.texture_buffer_descriptors) {
        texture_buffer_bindings.push_back(bindings.texture);
        bindings.texture += desc.count;
    }
    texture_bindings.reserve(info.texture_descriptors.size());
    for (const auto& desc : info.texture_descriptors) {
        texture_bindings.push_back(bindings.texture);
        bindings.texture += desc.count;
    }
}

} // namespace Shader::Backend::GLASM


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

#pragma once

#include <string>
#include <utility>
#include <vector>

#include <fmt/format.h>

#include "shader_recompiler/backend/glasm/reg_alloc.h"
#include "shader_recompiler/stage.h"

namespace Shader {
struct Info;
struct Profile;
struct RuntimeInfo;
} // namespace Shader

namespace Shader::Backend {
struct Bindings;
}

namespace Shader::IR {
class Inst;
struct Program;
} // namespace Shader::IR

namespace Shader::Backend::GLASM {

class EmitContext {
public:
    explicit EmitContext(IR::Program& program, Bindings& bindings, const Profile& profile_,
                         const RuntimeInfo& runtime_info_);

    template <typename... Args>
    void Add(const char* format_str, IR::Inst& inst, Args&&... args) {
        code += fmt::format(fmt::runtime(format_str), reg_alloc.Define(inst),
                            std::forward<Args>(args)...);
        // TODO: Remove this
        code += '\n';
    }

    template <typename... Args>
    void LongAdd(const char* format_str, IR::Inst& inst, Args&&... args) {
        code += fmt::format(fmt::runtime(format_str), reg_alloc.LongDefine(inst),
                            std::forward<Args>(args)...);
        // TODO: Remove this
        code += '\n';
    }

    template <typename... Args>
    void Add(const char* format_str, Args&&... args) {
        code += fmt::format(fmt::runtime(format_str), std::forward<Args>(args)...);
        // TODO: Remove this
        code += '\n';
    }

    std::string code;
    RegAlloc reg_alloc{};
    const Info& info;
    const Profile& profile;
    const RuntimeInfo& runtime_info;

    std::vector<u32> texture_buffer_bindings;
    std::vector<u32> image_buffer_bindings;
    std::vector<u32> texture_bindings;
    std::vector<u32> image_bindings;

    Stage stage{};
    std::string_view stage_name = "invalid";
    std::string_view attrib_name = "invalid";

    u32 num_safety_loop_vars{};
    bool uses_y_direction{};
};

} // namespace Shader::Backend::GLASM


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

#include <fmt/format.h>

#include "shader_recompiler/backend/glasm/reg_alloc.h"
#include "shader_recompiler/exception.h"
#include "shader_recompiler/frontend/ir/value.h"

namespace Shader::Backend::GLASM {

Register RegAlloc::Define(IR::Inst& inst) {
    return Define(inst, false);
}

Register RegAlloc::LongDefine(IR::Inst& inst) {
    return Define(inst, true);
}

Value RegAlloc::Peek(const IR::Value& value) {
    if (value.IsImmediate()) {
        return MakeImm(value);
    } else {
        return PeekInst(*value.Inst());
    }
}

Value RegAlloc::Consume(const IR::Value& value) {
    if (value.IsImmediate()) {
        return MakeImm(value);
    } else {
        return ConsumeInst(*value.Inst());
    }
}

void RegAlloc::Unref(IR::Inst& inst) {
    IR::Inst& value_inst{AliasInst(inst)};
    value_inst.DestructiveRemoveUsage();
    if (!value_inst.HasUses()) {
        Free(value_inst.Definition<Id>());
    }
}

Register RegAlloc::AllocReg() {
    Register ret;
    ret.type = Type::Register;
    ret.id = Alloc(false);
    return ret;
}

Register RegAlloc::AllocLongReg() {
    Register ret;
    ret.type = Type::Register;
    ret.id = Alloc(true);
    return ret;
}

void RegAlloc::FreeReg(Register reg) {
    Free(reg.id);
}

Value RegAlloc::MakeImm(const IR::Value& value) {
    Value ret;
    switch (value.Type()) {
    case IR::Type::Void:
        ret.type = Type::Void;
        break;
    case IR::Type::U1:
        ret.type = Type::U32;
        ret.imm_u32 = value.U1() ? 0xffffffff : 0;
        break;
    case IR::Type::U32:
        ret.type = Type::U32;
        ret.imm_u32 = value.U32();
        break;
    case IR::Type::F32:
        ret.type = Type::U32;
        ret.imm_u32 = Common::BitCast<u32>(value.F32());
        break;
    case IR::Type::U64:
        ret.type = Type::U64;
        ret.imm_u64 = value.U64();
        break;
    case IR::Type::F64:
        ret.type = Type::U64;
        ret.imm_u64 = Common::BitCast<u64>(value.F64());
        break;
    default:
        throw NotImplementedException("Immediate type {}", value.Type());
    }
    return ret;
}

Register RegAlloc::Define(IR::Inst& inst, bool is_long) {
    if (inst.HasUses()) {
        inst.SetDefinition<Id>(Alloc(is_long));
    } else {
        Id id{};
        id.is_long.Assign(is_long ? 1 : 0);
        id.is_null.Assign(1);
        inst.SetDefinition<Id>(id);
    }
    return Register{PeekInst(inst)};
}

Value RegAlloc::PeekInst(IR::Inst& inst) {
    Value ret;
    ret.type = Type::Register;
    ret.id = inst.Definition<Id>();
    return ret;
}

Value RegAlloc::ConsumeInst(IR::Inst& inst) {
    Unref(inst);
    return PeekInst(inst);
}

Id RegAlloc::Alloc(bool is_long) {
    size_t& num_regs{is_long ? num_used_long_registers : num_used_registers};
    std::bitset<NUM_REGS>& use{is_long ? long_register_use : register_use};
    if (num_used_registers + num_used_long_registers < NUM_REGS) {
        for (size_t reg = 0; reg < NUM_REGS; ++reg) {
            if (use[reg]) {
                continue;
            }
            num_regs = std::max(num_regs, reg + 1);
            use[reg] = true;
            Id ret{};
            ret.is_valid.Assign(1);
            ret.is_long.Assign(is_long ? 1 : 0);
            ret.is_spill.Assign(0);
            ret.is_condition_code.Assign(0);
            ret.is_null.Assign(0);
            ret.index.Assign(static_cast<u32>(reg));
            return ret;
        }
    }
    throw NotImplementedException("Register spilling");
}

void RegAlloc::Free(Id id) {
    if (id.is_valid == 0) {
        throw LogicError("Freeing invalid register");
    }
    if (id.is_spill != 0) {
        throw NotImplementedException("Free spill");
    }
    if (id.is_long != 0) {
        long_register_use[id.index] = false;
    } else {
        register_use[id.index] = false;
    }
}

/*static*/ bool RegAlloc::IsAliased(const IR::Inst& inst) {
    switch (inst.GetOpcode()) {
    case IR::Opcode::Identity:
    case IR::Opcode::BitCastU16F16:
    case IR::Opcode::BitCastU32F32:
    case IR::Opcode::BitCastU64F64:
    case IR::Opcode::BitCastF16U16:
    case IR::Opcode::BitCastF32U32:
    case IR::Opcode::BitCastF64U64:
        return true;
    default:
        return false;
    }
}

/*static*/ IR::Inst& RegAlloc::AliasInst(IR::Inst& inst) {
    IR::Inst* it{&inst};
    while (IsAliased(*it)) {
        const IR::Value arg{it->Arg(0)};
        if (arg.IsImmediate()) {
            break;
        }
        it = arg.InstRecursive();
    }
    return *it;
}

} // namespace Shader::Backend::GLASM


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

#pragma once

#include <bitset>

#include <fmt/format.h>

#include "common/bit_cast.h"
#include "common/bit_field.h"
#include "common/common_types.h"
#include "shader_recompiler/exception.h"

namespace Shader::IR {
class Inst;
class Value;
} // namespace Shader::IR

namespace Shader::Backend::GLASM {

class EmitContext;

enum class Type : u32 {
    Void,
    Register,
    U32,
    U64,
};

struct Id {
    union {
        u32 raw;
        BitField<0, 1, u32> is_valid;
        BitField<1, 1, u32> is_long;
        BitField<2, 1, u32> is_spill;
        BitField<3, 1, u32> is_condition_code;
        BitField<4, 1, u32> is_null;
        BitField<5, 27, u32> index;
    };

    bool operator==(Id rhs) const noexcept {
        return raw == rhs.raw;
    }
    bool operator!=(Id rhs) const noexcept {
        return !operator==(rhs);
    }
};
static_assert(sizeof(Id) == sizeof(u32));

struct Value {
    Type type;
    union {
        Id id;
        u32 imm_u32;
        u64 imm_u64;
    };

    bool operator==(const Value& rhs) const noexcept {
        if (type != rhs.type) {
            return false;
        }
        switch (type) {
        case Type::Void:
            return true;
        case Type::Register:
            return id == rhs.id;
        case Type::U32:
            return imm_u32 == rhs.imm_u32;
        case Type::U64:
            return imm_u64 == rhs.imm_u64;
        }
        return false;
    }
    bool operator!=(const Value& rhs) const noexcept {
        return !operator==(rhs);
    }
};
struct Register : Value {};
struct ScalarRegister : Value {};
struct ScalarU32 : Value {};
struct ScalarS32 : Value {};
struct ScalarF32 : Value {};
struct ScalarF64 : Value {};

class RegAlloc {
public:
    RegAlloc() = default;

    Register Define(IR::Inst& inst);

    Register LongDefine(IR::Inst& inst);

    [[nodiscard]] Value Peek(const IR::Value& value);

    Value Consume(const IR::Value& value);

    void Unref(IR::Inst& inst);

    [[nodiscard]] Register AllocReg();

    [[nodiscard]] Register AllocLongReg();

    void FreeReg(Register reg);

    void InvalidateConditionCodes() {
        // This does nothing for now
    }

    [[nodiscard]] size_t NumUsedRegisters() const noexcept {
        return num_used_registers;
    }

    [[nodiscard]] size_t NumUsedLongRegisters() const noexcept {
        return num_used_long_registers;
    }

    [[nodiscard]] bool IsEmpty() const noexcept {
        return register_use.none() && long_register_use.none();
    }

    /// Returns true if the instruction is expected to be aliased to another
    static bool IsAliased(const IR::Inst& inst);

    /// Returns the underlying value out of an alias sequence
    static IR::Inst& AliasInst(IR::Inst& inst);

private:
    static constexpr size_t NUM_REGS = 4096;
    static constexpr size_t NUM_ELEMENTS = 4;

    Value MakeImm(const IR::Value& value);

    Register Define(IR::Inst& inst, bool is_long);

    Value PeekInst(IR::Inst& inst);

    Value ConsumeInst(IR::Inst& inst);

    Id Alloc(bool is_long);

    void Free(Id id);

    size_t num_used_registers{};
    size_t num_used_long_registers{};
    std::bitset<NUM_REGS> register_use{};
    std::bitset<NUM_REGS> long_register_use{};
};

template <bool scalar, typename FormatContext>
auto FormatTo(FormatContext& ctx, Id id) {
    if (id.is_condition_code != 0) {
        throw NotImplementedException("Condition code emission");
    }
    if (id.is_spill != 0) {
        throw NotImplementedException("Spill emission");
    }
    if constexpr (scalar) {
        if (id.is_null != 0) {
            return fmt::format_to(ctx.out(), "{}", id.is_long != 0 ? "DC.x" : "RC.x");
        }
        if (id.is_long != 0) {
            return fmt::format_to(ctx.out(), "D{}.x", id.index.Value());
        } else {
            return fmt::format_to(ctx.out(), "R{}.x", id.index.Value());
        }
    } else {
        if (id.is_null != 0) {
            return fmt::format_to(ctx.out(), "{}", id.is_long != 0 ? "DC" : "RC");
        }
        if (id.is_long != 0) {
            return fmt::format_to(ctx.out(), "D{}", id.index.Value());
        } else {
            return fmt::format_to(ctx.out(), "R{}", id.index.Value());
        }
    }
}

} // namespace Shader::Backend::GLASM

template <>
struct fmt::formatter<Shader::Backend::GLASM::Id> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
    }
    template <typename FormatContext>
    auto format(Shader::Backend::GLASM::Id id, FormatContext& ctx) {
        return Shader::Backend::GLASM::FormatTo<true>(ctx, id);
    }
};

template <>
struct fmt::formatter<Shader::Backend::GLASM::Register> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
    }
    template <typename FormatContext>
    auto format(const Shader::Backend::GLASM::Register& value, FormatContext& ctx) {
        if (value.type != Shader::Backend::GLASM::Type::Register) {
            throw Shader::InvalidArgument("Register value type is not register");
        }
        return Shader::Backend::GLASM::FormatTo<false>(ctx, value.id);
    }
};

template <>
struct fmt::formatter<Shader::Backend::GLASM::ScalarRegister> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
    }
    template <typename FormatContext>
    auto format(const Shader::Backend::GLASM::ScalarRegister& value, FormatContext& ctx) {
        if (value.type != Shader::Backend::GLASM::Type::Register) {
            throw Shader::InvalidArgument("Register value type is not register");
        }
        return Shader::Backend::GLASM::FormatTo<true>(ctx, value.id);
    }
};

template <>
struct fmt::formatter<Shader::Backend::GLASM::ScalarU32> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
    }
    template <typename FormatContext>
    auto format(const Shader::Backend::GLASM::ScalarU32& value, FormatContext& ctx) {
        switch (value.type) {
        case Shader::Backend::GLASM::Type::Void:
            break;
        case Shader::Backend::GLASM::Type::Register:
            return Shader::Backend::GLASM::FormatTo<true>(ctx, value.id);
        case Shader::Backend::GLASM::Type::U32:
            return fmt::format_to(ctx.out(), "{}", value.imm_u32);
        case Shader::Backend::GLASM::Type::U64:
            break;
        }
        throw Shader::InvalidArgument("Invalid value type {}", value.type);
    }
};

template <>
struct fmt::formatter<Shader::Backend::GLASM::ScalarS32> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
    }
    template <typename FormatContext>
    auto format(const Shader::Backend::GLASM::ScalarS32& value, FormatContext& ctx) {
        switch (value.type) {
        case Shader::Backend::GLASM::Type::Void:
            break;
        case Shader::Backend::GLASM::Type::Register:
            return Shader::Backend::GLASM::FormatTo<true>(ctx, value.id);
        case Shader::Backend::GLASM::Type::U32:
            return fmt::format_to(ctx.out(), "{}", static_cast<s32>(value.imm_u32));
        case Shader::Backend::GLASM::Type::U64:
            break;
        }
        throw Shader::InvalidArgument("Invalid value type {}", value.type);
    }
};

template <>
struct fmt::formatter<Shader::Backend::GLASM::ScalarF32> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
    }
    template <typename FormatContext>
    auto format(const Shader::Backend::GLASM::ScalarF32& value, FormatContext& ctx) {
        switch (value.type) {
        case Shader::Backend::GLASM::Type::Void:
            break;
        case Shader::Backend::GLASM::Type::Register:
            return Shader::Backend::GLASM::FormatTo<true>(ctx, value.id);
        case Shader::Backend::GLASM::Type::U32:
            return fmt::format_to(ctx.out(), "{}", Common::BitCast<f32>(value.imm_u32));
        case Shader::Backend::GLASM::Type::U64:
            break;
        }
        throw Shader::InvalidArgument("Invalid value type {}", value.type);
    }
};

template <>
struct fmt::formatter<Shader::Backend::GLASM::ScalarF64> {
    constexpr auto parse(format_parse_context& ctx) {
        return ctx.begin();
    }
    template <typename FormatContext>
    auto format(const Shader::Backend::GLASM::ScalarF64& value, FormatContext& ctx) {
        switch (value.type) {
        case Shader::Backend::GLASM::Type::Void:
            break;
        case Shader::Backend::GLASM::Type::Register:
            return Shader::Backend::GLASM::FormatTo<true>(ctx, value.id);
        case Shader::Backend::GLASM::Type::U32:
            break;
        case Shader::Backend::GLASM::Type::U64:
            return fmt::format_to(ctx.out(), "{}", Common::BitCast<f64>(value.imm_u64));
        }
        throw Shader::InvalidArgument("Invalid value type {}", value.type);
    }
};


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

#include <algorithm>
#include <string>
#include <tuple>
#include <type_traits>

#include "common/div_ceil.h"
#include "common/settings.h"
#include "shader_recompiler/backend/glsl/emit_glsl.h"
#include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
#include "shader_recompiler/backend/glsl/glsl_emit_context.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"

namespace Shader::Backend::GLSL {
namespace {
template <class Func>
struct FuncTraits {};

template <class ReturnType_, class... Args>
struct FuncTraits<ReturnType_ (*)(Args...)> {
    using ReturnType = ReturnType_;

    static constexpr size_t NUM_ARGS = sizeof...(Args);

    template <size_t I>
    using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
};

template <auto func, typename... Args>
void SetDefinition(EmitContext& ctx, IR::Inst* inst, Args... args) {
    inst->SetDefinition<Id>(func(ctx, std::forward<Args>(args)...));
}

template <typename ArgType>
auto Arg(EmitContext& ctx, const IR::Value& arg) {
    if constexpr (std::is_same_v<ArgType, std::string_view>) {
        return ctx.var_alloc.Consume(arg);
    } else if constexpr (std::is_same_v<ArgType, const IR::Value&>) {
        return arg;
    } else if constexpr (std::is_same_v<ArgType, u32>) {
        return arg.U32();
    } else if constexpr (std::is_same_v<ArgType, IR::Attribute>) {
        return arg.Attribute();
    } else if constexpr (std::is_same_v<ArgType, IR::Patch>) {
        return arg.Patch();
    } else if constexpr (std::is_same_v<ArgType, IR::Reg>) {
        return arg.Reg();
    }
}

template <auto func, bool is_first_arg_inst, size_t... I>
void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
    using Traits = FuncTraits<decltype(func)>;
    if constexpr (std::is_same_v<typename Traits::ReturnType, Id>) {
        if constexpr (is_first_arg_inst) {
            SetDefinition<func>(
                ctx, inst, *inst,
                Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
        } else {
            SetDefinition<func>(
                ctx, inst, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
        }
    } else {
        if constexpr (is_first_arg_inst) {
            func(ctx, *inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
        } else {
            func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
        }
    }
}

template <auto func>
void Invoke(EmitContext& ctx, IR::Inst* inst) {
    using Traits = FuncTraits<decltype(func)>;
    static_assert(Traits::NUM_ARGS >= 1, "Insufficient arguments");
    if constexpr (Traits::NUM_ARGS == 1) {
        Invoke<func, false>(ctx, inst, std::make_index_sequence<0>{});
    } else {
        using FirstArgType = typename Traits::template ArgType<1>;
        static constexpr bool is_first_arg_inst = std::is_same_v<FirstArgType, IR::Inst&>;
        using Indices = std::make_index_sequence<Traits::NUM_ARGS - (is_first_arg_inst ? 2 : 1)>;
        Invoke<func, is_first_arg_inst>(ctx, inst, Indices{});
    }
}

void EmitInst(EmitContext& ctx, IR::Inst* inst) {
    switch (inst->GetOpcode()) {
#define OPCODE(name, result_type, ...)                                                             \
    case IR::Opcode::name:                                                                         \
        return Invoke<&Emit##name>(ctx, inst);
#include "shader_recompiler/frontend/ir/opcodes.inc"
#undef OPCODE
    }
    throw LogicError("Invalid opcode {}", inst->GetOpcode());
}

bool IsReference(IR::Inst& inst) {
    return inst.GetOpcode() == IR::Opcode::Reference;
}

void PrecolorInst(IR::Inst& phi) {
    // Insert phi moves before references to avoid overwriting other phis
    const size_t num_args{phi.NumArgs()};
    for (size_t i = 0; i < num_args; ++i) {
        IR::Block& phi_block{*phi.PhiBlock(i)};
        auto it{std::find_if_not(phi_block.rbegin(), phi_block.rend(), IsReference).base()};
        IR::IREmitter ir{phi_block, it};
        const IR::Value arg{phi.Arg(i)};
        if (arg.IsImmediate()) {
            ir.PhiMove(phi, arg);
        } else {
            ir.PhiMove(phi, IR::Value{arg.InstRecursive()});
        }
    }
    for (size_t i = 0; i < num_args; ++i) {
        IR::IREmitter{*phi.PhiBlock(i)}.Reference(IR::Value{&phi});
    }
}

void Precolor(const IR::Program& program) {
    for (IR::Block* const block : program.blocks) {
        for (IR::Inst& phi : block->Instructions()) {
            if (!IR::IsPhi(phi)) {
                break;
            }
            PrecolorInst(phi);
        }
    }
}

void EmitCode(EmitContext& ctx, const IR::Program& program) {
    for (const IR::AbstractSyntaxNode& node : program.syntax_list) {
        switch (node.type) {
        case IR::AbstractSyntaxNode::Type::Block:
            for (IR::Inst& inst : node.data.block->Instructions()) {
                EmitInst(ctx, &inst);
            }
            break;
        case IR::AbstractSyntaxNode::Type::If:
            ctx.Add("if({}){{", ctx.var_alloc.Consume(node.data.if_node.cond));
            break;
        case IR::AbstractSyntaxNode::Type::EndIf:
            ctx.Add("}}");
            break;
        case IR::AbstractSyntaxNode::Type::Break:
            if (node.data.break_node.cond.IsImmediate()) {
                if (node.data.break_node.cond.U1()) {
                    ctx.Add("break;");
                }
            } else {
                ctx.Add("if({}){{break;}}", ctx.var_alloc.Consume(node.data.break_node.cond));
            }
            break;
        case IR::AbstractSyntaxNode::Type::Return:
        case IR::AbstractSyntaxNode::Type::Unreachable:
            ctx.Add("return;");
            break;
        case IR::AbstractSyntaxNode::Type::Loop:
            ctx.Add("for(;;){{");
            break;
        case IR::AbstractSyntaxNode::Type::Repeat:
            if (Settings::values.disable_shader_loop_safety_checks) {
                ctx.Add("if(!{}){{break;}}}}", ctx.var_alloc.Consume(node.data.repeat.cond));
            } else {
                ctx.Add("if(--loop{}<0 || !{}){{break;}}}}", ctx.num_safety_loop_vars++,
                        ctx.var_alloc.Consume(node.data.repeat.cond));
            }
            break;
        default:
            throw NotImplementedException("AbstractSyntaxNode Type {}", node.type);
        }
    }
}

std::string GlslVersionSpecifier(const EmitContext& ctx) {
    if (ctx.uses_y_direction) {
        return " compatibility";
    }
    return "";
}

bool IsPreciseType(GlslVarType type) {
    switch (type) {
    case GlslVarType::PrecF32:
    case GlslVarType::PrecF64:
        return true;
    default:
        return false;
    }
}

void DefineVariables(const EmitContext& ctx, std::string& header) {
    for (u32 i = 0; i < static_cast<u32>(GlslVarType::Void); ++i) {
        const auto type{static_cast<GlslVarType>(i)};
        const auto& tracker{ctx.var_alloc.GetUseTracker(type)};
        const auto type_name{ctx.var_alloc.GetGlslType(type)};
        const bool has_precise_bug{ctx.stage == Stage::Fragment && ctx.profile.has_gl_precise_bug};
        const auto precise{!has_precise_bug && IsPreciseType(type) ? "precise " : ""};
        // Temps/return types that are never used are stored at index 0
        if (tracker.uses_temp) {
            header += fmt::format("{}{} t{}={}(0);", precise, type_name,
                                  ctx.var_alloc.Representation(0, type), type_name);
        }
        for (u32 index = 0; index < tracker.num_used; ++index) {
            header += fmt::format("{}{} {}={}(0);", precise, type_name,
                                  ctx.var_alloc.Representation(index, type), type_name);
        }
    }
    for (u32 i = 0; i < ctx.num_safety_loop_vars; ++i) {
        header += fmt::format("int loop{}=0x2000;", i);
    }
}
} // Anonymous namespace

std::string EmitGLSL(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program,
                     Bindings& bindings) {
    EmitContext ctx{program, bindings, profile, runtime_info};
    Precolor(program);
    EmitCode(ctx, program);
    const std::string version{fmt::format("#version 460{}\n", GlslVersionSpecifier(ctx))};
    ctx.header.insert(0, version);
    if (program.shared_memory_size > 0) {
        const auto requested_size{program.shared_memory_size};
        const auto max_size{profile.gl_max_compute_smem_size};
        const bool needs_clamp{requested_size > max_size};
        if (needs_clamp) {
            LOG_WARNING(Shader_GLSL, "Requested shared memory size ({}) exceeds device limit ({})",
                        requested_size, max_size);
        }
        const auto smem_size{needs_clamp ? max_size : requested_size};
        ctx.header += fmt::format("shared uint smem[{}];", Common::DivCeil(smem_size, 4U));
    }
    ctx.header += "void main(){\n";
    if (program.local_memory_size > 0) {
        ctx.header += fmt::format("uint lmem[{}];", Common::DivCeil(program.local_memory_size, 4U));
    }
    DefineVariables(ctx, ctx.header);
    if (ctx.uses_cc_carry) {
        ctx.header += "uint carry;";
    }
    if (program.info.uses_subgroup_shuffles) {
        ctx.header += "bool shfl_in_bounds;";
        ctx.header += "uint shfl_result;";
    }
    ctx.code.insert(0, ctx.header);
    ctx.code += '}';
    return ctx.code;
}

} // namespace Shader::Backend::GLSL


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

#pragma once

#include <string>

#include "shader_recompiler/backend/bindings.h"
#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/profile.h"
#include "shader_recompiler/runtime_info.h"

namespace Shader::Backend::GLSL {

[[nodiscard]] std::string EmitGLSL(const Profile& profile, const RuntimeInfo& runtime_info,
                                   IR::Program& program, Bindings& bindings);

[[nodiscard]] inline std::string EmitGLSL(const Profile& profile, IR::Program& program) {
    Bindings binding;
    return EmitGLSL(profile, {}, program, binding);
}

} // namespace Shader::Backend::GLSL