/* Copyright (c) 2022-2023 The Khronos Group Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <spirv/unified1/spirv.hpp>
#include <sstream>
#include "state_tracker/shader_instruction.h"
#include <vulkan/vulkan_core.h>
#include "generated/spirv_grammar_helper.h"
#include "state_tracker/shader_module.h"

namespace spirv {

Instruction::Instruction(std::vector<uint32_t>::const_iterator it)
    : position_offset_(0), operand_info_(GetOperandInfo(*it & 0x0ffffu)) {
    // Get Length manually to save allocation of vector
    const uint32_t length = (*it >> 16);
    words_.reserve(length);
    for (uint32_t i = 0; i < length; i++) {
        words_.emplace_back(*it++);
    }
    SetResultTypeIndex();
    UpdateDebugInfo();
}

Instruction::Instruction(const uint32_t* it) : position_offset_(0), operand_info_(GetOperandInfo(*it & 0x0ffffu)) {
    // Get Length manually to save allocation of vector
    const uint32_t length = (*it >> 16);
    words_.reserve(length);
    for (uint32_t i = 0; i < length; i++) {
        words_.emplace_back(*it++);
    }
    SetResultTypeIndex();
    UpdateDebugInfo();
}

Instruction::Instruction(spirv_iterator it, uint32_t position_offset)
    : position_offset_(position_offset), operand_info_(GetOperandInfo(*it & 0x0ffffu)) {
    // Get Length manually to save allocation of vector
    const uint32_t length = (*it >> 16);
    words_.reserve(length);
    for (uint32_t i = 0; i < length; i++) {
        words_.emplace_back(*it++);
    }
    SetResultTypeIndex();
    UpdateDebugInfo();
}

Instruction::Instruction(uint32_t length, spv::Op opcode) : position_offset_(0), operand_info_(GetOperandInfo(opcode)) {
    words_.reserve(length);
    uint32_t first_word = (length << 16) | opcode;
    words_.emplace_back(first_word);

    SetResultTypeIndex();
}

void Instruction::SetResultTypeIndex() {
    const bool has_result = OpcodeHasResult(Opcode());
    if (OpcodeHasType(Opcode())) {
        type_id_index_ = 1;
        operand_index_++;
        if (has_result) {
            result_id_index_ = 2;
            operand_index_++;
        }
    } else if (has_result) {
        result_id_index_ = 1;
        operand_index_++;
    }
}

void Instruction::UpdateDebugInfo() {
#ifndef NDEBUG
    d_opcode_ = std::string(string_SpvOpcode(Opcode()));
    d_length_ = Length();
    d_result_id_ = ResultId();
    d_type_id_ = TypeId();
    // the words might not all be filled in yet
    for (uint32_t i = 0; i < words_.size() && i < 12; i++) {
        d_words_[i] = words_[i];
    }
#endif
}

std::string Instruction::Describe() const {
    std::ostringstream ss;
    const uint32_t opcode = Opcode();
    const uint32_t length = Length();
    const bool has_result = ResultId() != 0;
    const bool has_type = TypeId() != 0;
    uint32_t operand_offset = 1;  // where to start printing operands
    // common disassembled for SPIR-V is
    // %result = Opcode %result_type %operands
    if (has_result) {
        operand_offset++;
        ss << "%" << (has_type ? Word(2) : Word(1)) << " = ";
    }

    ss << string_SpvOpcode(opcode);

    if (has_type) {
        operand_offset++;
        ss << " %" << Word(1);
    }

    // Exception for some opcode
    if (opcode == spv::OpEntryPoint) {
        ss << " " << string_SpvExecutionModel(Word(1)) << " %" << Word(2) << " [Unknown]";
    } else {
        const OperandInfo& info = GetOperandInfo(opcode);
        const uint32_t operands = static_cast<uint32_t>(info.types.size());
        const uint32_t remaining_words = length - operand_offset;
        for (uint32_t i = 0; i < remaining_words; i++) {
            OperandKind kind = (i < operands) ? info.types[i] : info.types.back();
            if (kind == OperandKind::LiteralString) {
                ss << " [string]";
                break;
            }
            ss << ((kind == OperandKind::Id) ? " %" : " ") << Word(operand_offset + i);
        }
    }

    return ss.str();
}

// While simple, function name provides a more human readable description why Word(3) is used.
//
// The current various uses for constant values (OpAccessChain, OpTypeArray, LocalSize, etc) all have spec langauge making sure they
// are scalar ints. It is also not valid for any of these use cases to have a negative value. While it is valid SPIR-V to use 64-bit
// int, found writting test there is no way to create something valid that also calls this function. So until a use-case is found,
// we can safely assume returning a uint32_t is ok.
uint32_t Instruction::GetConstantValue() const {
    // This should be a OpConstant (not a OpSpecConstant), if this asserts then 2 things are happening
    // 1. This function is being used where we don't actually know it is a constant and is a bug in the validation layers
    // 2. The CreateFoldSpecConstantOpAndCompositePass didn't fully fold everything and is a bug in spirv-opt
    assert(Opcode() == spv::OpConstant || Opcode() == spv::OpConstantNull);
    return Opcode() == spv::OpConstantNull ? 0 : Word(3);
}

// The idea of this function is to not have to constantly lookup which operand for the width
// inst.Word(2) -> inst.GetBitWidth()
uint32_t Instruction::GetBitWidth() const {
    const uint32_t opcode = Opcode();
    uint32_t bit_width = 0;
    switch (opcode) {
        case spv::Op::OpTypeFloat:
        case spv::Op::OpTypeInt:
            bit_width = Word(2);
            break;
        case spv::Op::OpTypeBool:
            // The Spec states:
            // "Boolean values considered as 32-bit integer values for the purpose of this calculation"
            // when getting the size for the limits
            bit_width = 32;
            break;
        default:
            // Most likely the caller is not checking for this being a matrix/array/struct/etc
            // This class only knows a single instruction's information
            assert(false);
            break;
    }
    return bit_width;
}

spv::FPEncoding Instruction::GetFPEncoding() const {
    if (Opcode() == spv::Op::OpTypeFloat && Length() > 3) {
        return static_cast<spv::FPEncoding>(Word(3));
    }
    return spv::FPEncoding::FPEncodingMax;
}

spv::BuiltIn Instruction::GetBuiltIn() const {
    if (Opcode() == spv::OpDecorate) {
        return static_cast<spv::BuiltIn>(Word(3));
    } else if (Opcode() == spv::OpMemberDecorate) {
        return static_cast<spv::BuiltIn>(Word(4));
    } else {
        assert(false);  // non valid Opcode
        return spirv::kInvalidBuiltIn;
    }
}

bool Instruction::IsArray() const { return (Opcode() == spv::OpTypeArray || Opcode() == spv::OpTypeRuntimeArray); }

bool Instruction::IsVector() const { return (Opcode() == spv::OpTypeVector || Opcode() == spv::OpTypeVectorIdEXT); }

bool Instruction::IsNonPtrAccessChain() const {
    const uint32_t opcode = Opcode();
    return opcode == spv::OpAccessChain || opcode == spv::OpInBoundsAccessChain || opcode == spv::OpUntypedAccessChainKHR ||
           opcode == spv::OpUntypedInBoundsAccessChainKHR;
}

bool Instruction::IsAccessChain() const {
    const uint32_t opcode = Opcode();
    return IsNonPtrAccessChain() || opcode == spv::OpPtrAccessChain || opcode == spv::OpInBoundsPtrAccessChain ||
           opcode == spv::OpUntypedPtrAccessChainKHR || opcode == spv::OpUntypedInBoundsPtrAccessChainKHR;
}

bool Instruction::IsUntypedAccessChain() const {
    const uint32_t opcode = Opcode();
    return opcode == spv::OpUntypedAccessChainKHR || opcode == spv::OpUntypedInBoundsAccessChainKHR ||
           opcode == spv::OpUntypedPtrAccessChainKHR || opcode == spv::OpUntypedInBoundsPtrAccessChainKHR;
}

// These are all things known that do a memory access
bool Instruction::IsMemoryAccess() const {
    const uint32_t opcode = Opcode();
    if (opcode == spv::OpImageTexelPointer || opcode == spv::OpImage) {
        // OpImageTexelPointer is for image atomics handled at the atomic instruction
        // OpImage is describing an action, rather than the input to or the output from an action.
        return false;
    }

    return opcode == spv::OpLoad || opcode == spv::OpStore || opcode == spv::OpCooperativeMatrixLoadKHR ||
           opcode == spv::OpCooperativeMatrixStoreKHR || AtomicOperation(opcode) || (OpcodeImageAccessPosition(opcode) != 0);
}

// As defined in https://github.khronos.org/SPIRV-Registry/extensions/EXT/SPV_EXT_descriptor_heap.html
bool Instruction::IsDescriptorType() const {
    const uint32_t opcode = Opcode();
    return opcode == spv::OpTypeSampler || opcode == spv::OpTypeImage || opcode == spv::OpTypeBufferEXT ||
           opcode == spv::OpTypeAccelerationStructureKHR || opcode == spv::OpTypeTensorARM;
}

VkDescriptorType Instruction::GetImageType() const {
    assert(Opcode() == spv::OpTypeImage);
    const bool is_sampled_without_sampler = Word(7) == 2;
    spv::Dim image_dim = spv::Dim(Word(3));
    if (is_sampled_without_sampler) {
        if (image_dim == spv::DimSubpassData) {
            return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
        } else if (image_dim == spv::DimBuffer) {
            return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
        } else {
            return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
        }
    } else if (image_dim == spv::DimBuffer) {
        return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
    } else {
        return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
    }
}

bool Instruction::IsTensor() const { return (Opcode() == spv::OpTypeTensorARM); }

// Returns "any" constant
bool Instruction::IsConstant() const {
    switch (Opcode()) {
        case spv::OpConstantTrue:
        case spv::OpConstantFalse:
        case spv::OpConstant:
        case spv::OpConstantComposite:
        case spv::OpConstantSampler:
        case spv::OpConstantNull:
        case spv::OpSpecConstantTrue:
        case spv::OpSpecConstantFalse:
        case spv::OpSpecConstant:
        case spv::OpSpecConstantComposite:
        case spv::OpSpecConstantOp:
            return true;
        default:
            break;
    }
    return false;
}

bool Instruction::IsSpecConstant() const {
    switch (Opcode()) {
        case spv::OpSpecConstantTrue:
        case spv::OpSpecConstantFalse:
        case spv::OpSpecConstant:
        case spv::OpSpecConstantComposite:
        case spv::OpSpecConstantOp:
            return true;
        default:
            break;
    }
    return false;
}

spv::StorageClass Instruction::StorageClass() const {
    spv::StorageClass storage_class = spv::StorageClassMax;
    switch (Opcode()) {
        case spv::OpTypePointer:
        case spv::OpTypeForwardPointer:
        case spv::OpTypeUntypedPointerKHR:
        case spv::OpTypeBufferEXT:
            storage_class = static_cast<spv::StorageClass>(Word(2));
            break;
        case spv::OpVariable:
        case spv::OpUntypedVariableKHR:
            storage_class = static_cast<spv::StorageClass>(Word(3));
            break;

        default:
            break;
    }
    return storage_class;
}

// OpEntryPoint are annoying because the offset to the interface variable requires you to first detect how big the "Name" string is
uint32_t Instruction::GetEntryPointInterfaceStart() const {
    assert(Opcode() == spv::OpEntryPoint || Opcode() == spv::OpGraphEntryPointARM);
    uint32_t word = 3;  // operand Name operand starts
    // Find the end of the entrypoint's name string. additional zero bytes follow the actual null terminator, to fill out the rest
    // of the word - so we only need to look at the last byte in the word to determine which word contains the terminator.
    while (Word(word) & 0xff000000u) {
        ++word;
    }
    ++word;
    return word;
}

void Instruction::Fill(const std::vector<uint32_t>& words) {
    for (uint32_t word : words) {
        words_.emplace_back(word);
    }
    UpdateDebugInfo();
}

void Instruction::UpdateWord(uint32_t index, uint32_t data) {
    words_[index] = data;
#ifndef NDEBUG
    d_words_[index] = data;
#endif
}

void Instruction::AppendWord(uint32_t word) {
    words_.emplace_back(word);
    const uint32_t new_length = Length() + 1;
    uint32_t first_word = (new_length << 16) | Opcode();
    words_[0] = first_word;
    UpdateDebugInfo();
}

void Instruction::ToBinary(std::vector<uint32_t>& out) const {
    for (auto word : words_) {
        out.push_back(word);
    }
}

void Instruction::ReplaceResultId(uint32_t new_result_id) {
    words_[result_id_index_] = new_result_id;
    UpdateDebugInfo();
}

void Instruction::ReplaceOperandId(uint32_t old_word, uint32_t new_word) {
    const uint32_t length = Length();
    uint32_t type_index = 0;
    // Use length as some operands can be optional at the end
    for (uint32_t word_index = operand_index_; word_index < length; word_index++, type_index++) {
        if (words_[word_index] != old_word) {
            continue;
        }

        OperandKind kind = OperandKind::Invalid;
        if (type_index < operand_info_.types.size()) {
            kind = operand_info_.types[type_index];
        } else {
            // If the last operands are a wildcard use the last kind for the remaining words
            kind = operand_info_.types.back();
            if (kind == OperandKind::BitEnum) {
                // ImageOperands may be found, their optional parameters will always have an Id
                const uint32_t image_operand_position = OpcodeImageOperandsPosition(Opcode());
                if (image_operand_position != 0 && word_index > image_operand_position) {
                    kind = OperandKind::Id;
                }
            }
        }

        // insructions like OpPhi will be Composite which are just groups of Ids
        // We are not trying to replace/mess with with Control Flow, so all OperandKind::Label are ignored on purpose
        if (kind == OperandKind::Id || kind == OperandKind::Composite) {
            words_[word_index] = new_word;
            UpdateDebugInfo();
        }
    }
}

// The main challenge with linking to functions from 2 modules is the IDs overlap.
// TODO - Use the new generated operand to find the IDs.
void Instruction::ReplaceLinkedId(vvl::unordered_map<uint32_t, uint32_t>& id_swap_map) {
    auto swap = [this, &id_swap_map](uint32_t index) {
        uint32_t old_id = words_[index];
        uint32_t new_id = id_swap_map[old_id];
        assert(new_id != 0);
        words_[index] = new_id;
    };

    auto swap_to_end = [this, swap](uint32_t start_index) {
        for (uint32_t i = start_index; i < Length(); i++) {
            swap(i);
        }
    };

    // Swap all Reference IDs (ignores Result ID)
    const uint32_t op_code = Opcode();
    switch (op_code) {
        case spv::OpCompositeExtract:
        case spv::OpLoad:
        case spv::OpArrayLength:
        case spv::OpBitcast:
        case spv::OpUConvert:
        case spv::OpAny:
        case spv::OpLogicalNot:
        case spv::OpIsNan:
        case spv::OpIsInf:
        case spv::OpIsFinite:
        case spv::OpConvertFToU:
        case spv::OpConvertFToS:
        case spv::OpConvertSToF:
        case spv::OpConvertUToF:
        case spv::OpConvertUToPtr:
        case spv::OpGroupNonUniformElect:
            swap(1);
            swap(3);
            break;
        case spv::OpFAdd:
        case spv::OpIAdd:
        case spv::OpISub:
        case spv::OpFSub:
        case spv::OpIMul:
        case spv::OpFMul:
        case spv::OpUDiv:
        case spv::OpSDiv:
        case spv::OpFDiv:
        case spv::OpUMod:
        case spv::OpSRem:
        case spv::OpSMod:
        case spv::OpFRem:
        case spv::OpFMod:
        case spv::OpIEqual:
        case spv::OpINotEqual:
        case spv::OpUGreaterThan:
        case spv::OpSGreaterThan:
        case spv::OpUGreaterThanEqual:
        case spv::OpSGreaterThanEqual:
        case spv::OpULessThan:
        case spv::OpSLessThan:
        case spv::OpULessThanEqual:
        case spv::OpSLessThanEqual:
        case spv::OpFOrdEqual:
        case spv::OpFUnordEqual:
        case spv::OpFOrdNotEqual:
        case spv::OpFUnordNotEqual:
        case spv::OpFOrdLessThan:
        case spv::OpFUnordLessThan:
        case spv::OpFOrdGreaterThan:
        case spv::OpFUnordGreaterThan:
        case spv::OpFOrdLessThanEqual:
        case spv::OpFUnordLessThanEqual:
        case spv::OpFOrdGreaterThanEqual:
        case spv::OpFUnordGreaterThanEqual:
        case spv::OpLogicalEqual:
        case spv::OpLogicalNotEqual:
        case spv::OpLogicalOr:
        case spv::OpLogicalAnd:
        case spv::OpShiftRightLogical:
        case spv::OpShiftRightArithmetic:
        case spv::OpShiftLeftLogical:
        case spv::OpBitwiseOr:
        case spv::OpBitwiseXor:
        case spv::OpBitwiseAnd:
            swap(1);
            swap(3);
            swap(4);
            break;
        case spv::OpStore:
        case spv::OpLoopMerge:
            swap(1);
            swap(2);
            break;
        case spv::OpReturnValue:
        case spv::OpFunctionParameter:
        case spv::OpVariable:  // never use optional initializer
        case spv::OpConstantTrue:
        case spv::OpSpecConstantTrue:
        case spv::OpConstantFalse:
        case spv::OpSpecConstantFalse:
        case spv::OpConstant:
        case spv::OpSpecConstant:
        case spv::OpConstantNull:
        case spv::OpSelectionMerge:
        case spv::OpBranch:
        case spv::OpDecorate:
        case spv::OpMemberDecorate:
            swap(1);
            break;
        case spv::OpTypePointer:
            swap(3);
            break;
        case spv::OpAtomicStore:
        case spv::OpBranchConditional:
        case spv::OpControlBarrier:
            swap_to_end(1);
            break;
        case spv::OpAtomicLoad:
        case spv::OpAtomicExchange:
        case spv::OpAtomicCompareExchange:
        case spv::OpAtomicCompareExchangeWeak:
        case spv::OpAtomicIIncrement:
        case spv::OpAtomicIDecrement:
        case spv::OpAtomicIAdd:
        case spv::OpAtomicISub:
        case spv::OpAtomicSMin:
        case spv::OpAtomicUMin:
        case spv::OpAtomicSMax:
        case spv::OpAtomicUMax:
        case spv::OpAtomicAnd:
        case spv::OpAtomicOr:
        case spv::OpAtomicXor:
        case spv::OpPhi:
        case spv::OpAccessChain:
        case spv::OpConstantComposite:
        case spv::OpSpecConstantComposite:
        case spv::OpSelect:
        case spv::OpCompositeConstruct:
            swap(1);
            swap_to_end(3);
            break;
        case spv::OpTypeStruct:
        case spv::OpTypeFunction:
            swap_to_end(2);
            break;
        case spv::OpExtInst:
            swap(1);
            swap(3);
            swap_to_end(5);
            break;
        case spv::OpReturn:
        case spv::OpLabel:
        case spv::OpFunctionEnd:
        case spv::OpExtInstImport:
        case spv::OpString:
            break;  // Instructions aware of, but nothing to swap
        default:
            assert(false && "Need to add support for new instruction");
    }

    UpdateDebugInfo();
}

void Instruction::FreezeSpecConstant() {
    const uint32_t opcode = Opcode();
    if (opcode == spv::OpSpecConstant) {
        SetNewOpcode(spv::OpConstant);
    } else if (opcode == spv::OpSpecConstantTrue) {
        SetNewOpcode(spv::OpConstantTrue);
    } else if (opcode == spv::OpSpecConstantFalse) {
        SetNewOpcode(spv::OpConstantFalse);
    } else if (opcode == spv::OpSpecConstantComposite) {
        SetNewOpcode(spv::OpConstantComposite);
    }
}

void Instruction::SetNewOpcode(uint32_t opcode) {
    words_[0] = (words_[0] & 0xffff0000u) | (opcode & 0x0ffffu);
    UpdateDebugInfo();
}

static inline bool IsImageOperandsBiasOffset(uint32_t type) {
    return (type & (spv::ImageOperandsBiasMask | spv::ImageOperandsConstOffsetMask | spv::ImageOperandsOffsetMask |
                    spv::ImageOperandsConstOffsetsMask)) != 0;
}

// Need to do this all with a raw pointer as GPU-AV doesn't have time to recreate the Instruction class
ImageInstruction::ImageInstruction(const uint32_t* words) {
    const uint32_t image_opcode = words[0] & 0x0ffffu;
    switch (image_opcode) {
        case spv::OpImageDrefGather:
        case spv::OpImageSparseDrefGather:
            is_dref = true;
            break;

        case spv::OpImageSampleDrefImplicitLod:
        case spv::OpImageSampleDrefExplicitLod:
        case spv::OpImageSampleProjDrefImplicitLod:
        case spv::OpImageSampleProjDrefExplicitLod:
        case spv::OpImageSparseSampleDrefImplicitLod:
        case spv::OpImageSparseSampleDrefExplicitLod:
        case spv::OpImageSparseSampleProjDrefImplicitLod:
        case spv::OpImageSparseSampleProjDrefExplicitLod: {
            is_dref = true;
            is_sampler_implicitLod_dref_proj = true;
            is_sampler_sampled = true;
            break;
        }

        case spv::OpImageSampleImplicitLod:
        case spv::OpImageSampleProjImplicitLod:
        case spv::OpImageSampleProjExplicitLod:
        case spv::OpImageSparseSampleImplicitLod:
        case spv::OpImageSparseSampleProjImplicitLod:
        case spv::OpImageSparseSampleProjExplicitLod: {
            is_sampler_implicitLod_dref_proj = true;
            is_sampler_sampled = true;
            break;
        }

        case spv::OpImageSampleExplicitLod:
        case spv::OpImageSparseSampleExplicitLod: {
            is_sampler_sampled = true;
            break;
        }

        case spv::OpImageWrite:
        case spv::OpImageRead:
        case spv::OpImageSparseRead:
        case spv::OpImageTexelPointer:
        case spv::OpImageFetch:
        case spv::OpImageSparseFetch:
        case spv::OpImageGather:
        case spv::OpImageSparseGather:
        case spv::OpImageQueryLod:
        case spv::OpFragmentFetchAMD:
        case spv::OpFragmentMaskFetchAMD:
        case spv::OpAtomicLoad:
        case spv::OpAtomicStore:
        case spv::OpAtomicExchange:
        case spv::OpAtomicCompareExchange:
        case spv::OpAtomicCompareExchangeWeak:
        case spv::OpAtomicIIncrement:
        case spv::OpAtomicIDecrement:
        case spv::OpAtomicIAdd:
        case spv::OpAtomicISub:
        case spv::OpAtomicSMin:
        case spv::OpAtomicUMin:
        case spv::OpAtomicSMax:
        case spv::OpAtomicUMax:
        case spv::OpAtomicAnd:
        case spv::OpAtomicOr:
        case spv::OpAtomicXor:
        case spv::OpAtomicFMinEXT:
        case spv::OpAtomicFMaxEXT:
        case spv::OpAtomicFAddEXT:
            break;

        case spv::OpImageSampleWeightedQCOM:
            image_proc_usage_mask |= ImageProcUsageBit::kSampleWeighted;
            break;
        case spv::OpImageBoxFilterQCOM:
            image_proc_usage_mask |= ImageProcUsageBit::kBoxFilter;
            break;
        case spv::OpImageBlockMatchSADQCOM:
            image_proc_usage_mask |= ImageProcUsageBit::kBlockMatchSad;
            break;
        case spv::OpImageBlockMatchSSDQCOM:
            image_proc_usage_mask |= ImageProcUsageBit::kBlockMatchSsd;
            break;
        case spv::OpImageBlockMatchGatherSADQCOM:
            image_proc_usage_mask |= ImageProcUsageBit::kBlockMatchGatherSad;
            break;
        case spv::OpImageBlockMatchGatherSSDQCOM:
            image_proc_usage_mask |= ImageProcUsageBit::kBlockMatchGatherSsd;
            break;
        case spv::OpImageBlockMatchWindowSADQCOM:
            image_proc_usage_mask |= ImageProcUsageBit::kBlockMatchWindowSad;
            break;
        case spv::OpImageBlockMatchWindowSSDQCOM:
            image_proc_usage_mask |= ImageProcUsageBit::kBlockMatchWindowSsd;
            break;

        case spv::OpImageSparseTexelsResident:
            assert(false);  // This is not a proper OpImage* instruction, has no OpImage operand
            break;

        default:
            assert(false);  // This is an OpImage* we are not catching
            break;
    }

    // Find any optional Image Operands
    const uint32_t image_operand_position = OpcodeImageOperandsPosition(image_opcode);
    const uint32_t length = words[0] >> 16;
    if (image_operand_position != 0 && length > image_operand_position) {
        const uint32_t image_operand_word = words[image_operand_position];

        if (is_sampler_sampled) {
            if (IsImageOperandsBiasOffset(image_operand_word)) {
                is_sampler_bias_offset = true;
            }
            if ((image_operand_word & (spv::ImageOperandsConstOffsetMask | spv::ImageOperandsOffsetMask)) != 0) {
                is_sampler_offset = true;
            }
        }

        if ((image_operand_word & spv::ImageOperandsSignExtendMask) != 0) {
            is_sign_extended = true;
        } else if ((image_operand_word & spv::ImageOperandsZeroExtendMask) != 0) {
            is_zero_extended = true;
        }
    }
}

uint32_t Instruction::GetCalledFunctionId() const {
    const uint32_t opcode = Opcode();
    if (opcode == spv::OpFunctionCall) {
        return Word(3);
    } else if (opcode == spv::OpCooperativeMatrixPerElementOpNV) {
        return Word(4);
    } else if (opcode == spv::OpCooperativeMatrixReduceNV) {
        return Word(5);
    } else if (opcode == spv::OpCooperativeMatrixLoadTensorNV) {
        uint32_t idx = 7;
        const uint32_t mem_operand = Word(6);
        if (mem_operand & spv::MemoryAccessAlignedMask) idx++;
        if (mem_operand & spv::MemoryAccessMakePointerAvailableMask) idx++;
        if (mem_operand & spv::MemoryAccessMakePointerVisibleMask) idx++;
        if (idx < Length()) {
            const uint32_t tensor_operand = Word(idx);
            idx++;
            if (tensor_operand & spv::TensorAddressingOperandsTensorViewMask) idx++;
            if ((tensor_operand & spv::TensorAddressingOperandsDecodeFuncMask) && idx < Length()) {
                return Word(idx);
            }
        }
    }
    return 0;
}

}  // namespace spirv
