/* Copyright (c) 2024-2026 LunarG, 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 "sanitizer_pass.h"
#include "containers/container_utils.h"
#include "cooperative_matrix.h"
#include "function_basic_block.h"
#include "gpuav/shaders/gpuav_error_codes.h"
#include "module.h"
#include <spirv/unified1/GLSL.std.450.h>
#include <cstdint>
#include <spirv/unified1/spirv.hpp>
#include <iostream>
#include <cstring>

#include "generated/gpuav_offline_spirv.h"

namespace gpuav {
namespace spirv {

const static OfflineModule kOfflineModule = {instrumentation_sanitizer_comp, instrumentation_sanitizer_comp_size,
                                             UseErrorPayloadVariable};

const static OfflineFunction kOfflineFunctions[glsl::kErrorSubCode_Sanitizer_Count] = {
    {"empty", 0},
    {"inst_sanitizer_divide_by_zero", instrumentation_sanitizer_comp_function_0_offset},
    {"inst_sanitizer_image_gather", instrumentation_sanitizer_comp_function_1_offset},
    {"inst_sanitizer_pow", instrumentation_sanitizer_comp_function_2_offset},
    {"inst_sanitizer_atan2", instrumentation_sanitizer_comp_function_3_offset},
    {"inst_sanitizer_fminmax", instrumentation_sanitizer_comp_function_4_offset},
    {"inst_sanitizer_coop_mat_alignment", instrumentation_sanitizer_comp_function_5_offset},
};

SanitizerPass::SanitizerPass(Module& module) : Pass(module, kOfflineModule) {
    for (uint32_t i = 0; i < glsl::kErrorSubCode_Sanitizer_Count; i++) {
        link_function_ids_[i] = 0;
    }
}

// By appending the LinkInfo, it will attempt at linking stage to add the function.
uint32_t SanitizerPass::GetLinkFunctionId(uint32_t sub_code) {
    return GetLinkFunction(link_function_ids_[sub_code], kOfflineFunctions[sub_code]);
}

// We do the check for zero in C++ to make it easier to handle the various cases of signed/unsigned/64bit/etc
// Returns an ID of type OpTypeBool
uint32_t SanitizerPass::DivideByZeroCheck(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta) {
    const Type& bool_type = type_manager_.GetTypeBool();
    const uint32_t vector_size = meta.result_type->VectorSize();
    const uint32_t divisor_id = meta.target_instruction->Word(4);

    const bool is_float = meta.target_instruction->Opcode() == spv::OpFMod || meta.target_instruction->Opcode() == spv::OpFRem;
    const spv::Op compare_op = is_float ? spv::OpFOrdEqual : spv::OpIEqual;

    if (vector_size == 0) {
        const uint32_t null_type_id = type_manager_.GetConstantNull(*meta.result_type).Id();
        const uint32_t compare_id = module_.TakeNextId();
        block.CreateInstruction(compare_op, {bool_type.Id(), compare_id, null_type_id, divisor_id}, inst_it);
        return compare_id;
    } else {
        const uint32_t bool_vector_type_id = type_manager_.GetTypeVector(bool_type, vector_size).Id();
        const uint32_t zero_id = type_manager_.GetConstantZeroVector(*meta.result_type).Id();

        const uint32_t compare_id = module_.TakeNextId();
        block.CreateInstruction(compare_op, {bool_vector_type_id, compare_id, zero_id, divisor_id}, inst_it);

        const uint32_t any_id = module_.TakeNextId();
        block.CreateInstruction(spv::OpAny, {bool_type.Id(), any_id, compare_id}, inst_it);
        return any_id;
    }
}

// Based off https://godbolt.org/z/dbToMGKTd - but found can hand-roll the spirv much better
// bool is_invalid = (x < 0.0 || (x == 0.0 && y <= 0.0));
// Returns an ID of type OpTypeBool
uint32_t SanitizerPass::PowCheck(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta) {
    const Type& bool_type = type_manager_.GetTypeBool();
    const uint32_t vector_size = meta.result_type->VectorSize();

    uint32_t bool_compare_type_id = 0;
    uint32_t null_type_id = 0;
    if (vector_size == 0) {
        bool_compare_type_id = bool_type.Id();
        null_type_id = type_manager_.GetConstantNull(*meta.result_type).Id();
    } else {
        bool_compare_type_id = type_manager_.GetTypeVector(bool_type, vector_size).Id();
        null_type_id = type_manager_.GetConstantZeroVector(*meta.result_type).Id();
    }

    const uint32_t x_value_id = meta.target_instruction->Word(5);
    const uint32_t y_value_id = meta.target_instruction->Word(6);

    const uint32_t compare_1_id = module_.TakeNextId();
    const uint32_t compare_2_id = module_.TakeNextId();
    const uint32_t compare_3_id = module_.TakeNextId();
    block.CreateInstruction(spv::OpFOrdLessThan, {bool_compare_type_id, compare_1_id, x_value_id, null_type_id}, inst_it);
    block.CreateInstruction(spv::OpFOrdEqual, {bool_compare_type_id, compare_2_id, x_value_id, null_type_id}, inst_it);
    block.CreateInstruction(spv::OpFOrdLessThanEqual, {bool_compare_type_id, compare_3_id, y_value_id, null_type_id}, inst_it);

    const uint32_t compare_and_id = module_.TakeNextId();
    const uint32_t compare_or_id = module_.TakeNextId();
    block.CreateInstruction(spv::OpLogicalAnd, {bool_compare_type_id, compare_and_id, compare_2_id, compare_3_id}, inst_it);
    block.CreateInstruction(spv::OpLogicalOr, {bool_compare_type_id, compare_or_id, compare_1_id, compare_and_id}, inst_it);

    uint32_t result_bool_id = 0;
    if (vector_size == 0) {
        result_bool_id = compare_or_id;
    } else {
        result_bool_id = module_.TakeNextId();
        block.CreateInstruction(spv::OpAny, {bool_type.Id(), result_bool_id, compare_or_id}, inst_it);
    }

    return result_bool_id;
}

// Returns an ID of type OpTypeBool
uint32_t SanitizerPass::Atan2Check(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta) {
    const Type& bool_type = type_manager_.GetTypeBool();
    const uint32_t vector_size = meta.result_type->VectorSize();

    uint32_t bool_compare_type_id = 0;
    uint32_t null_type_id = 0;
    if (vector_size == 0) {
        bool_compare_type_id = bool_type.Id();
        null_type_id = type_manager_.GetConstantNull(*meta.result_type).Id();
    } else {
        bool_compare_type_id = type_manager_.GetTypeVector(bool_type, vector_size).Id();
        null_type_id = type_manager_.GetConstantZeroVector(*meta.result_type).Id();
    }

    // Seems Atan flips the x/y order from other functions, doesn't really matter, but noting here
    const uint32_t y_value_id = meta.target_instruction->Word(5);
    const uint32_t x_value_id = meta.target_instruction->Word(6);

    const uint32_t compare_y_id = module_.TakeNextId();
    const uint32_t compare_x_id = module_.TakeNextId();
    block.CreateInstruction(spv::OpFOrdEqual, {bool_compare_type_id, compare_y_id, y_value_id, null_type_id}, inst_it);
    block.CreateInstruction(spv::OpFOrdEqual, {bool_compare_type_id, compare_x_id, x_value_id, null_type_id}, inst_it);
    const uint32_t compare_and_id = module_.TakeNextId();
    block.CreateInstruction(spv::OpLogicalAnd, {bool_compare_type_id, compare_and_id, compare_y_id, compare_x_id}, inst_it);

    uint32_t result_bool_id = 0;
    if (vector_size == 0) {
        result_bool_id = compare_and_id;
    } else {
        result_bool_id = module_.TakeNextId();
        block.CreateInstruction(spv::OpAny, {bool_type.Id(), result_bool_id, compare_and_id}, inst_it);
    }

    return result_bool_id;
}

// Returns an ID of type OpTypeBool for X and Y
BoolResultXY SanitizerPass::FminmaxCheck(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta) {
    BoolResultXY result_bool_id;
    const Type& bool_type = type_manager_.GetTypeBool();
    const uint32_t vector_size = meta.result_type->VectorSize();

    const uint32_t bool_result_type_id =
        (vector_size == 0) ? bool_type.Id() : type_manager_.GetTypeVector(bool_type, vector_size).Id();

    const uint32_t x_value_id = meta.target_instruction->Word(5);
    const uint32_t y_value_id = meta.target_instruction->Word(6);

    const uint32_t nan_x_id = module_.TakeNextId();
    const uint32_t nan_y_id = module_.TakeNextId();
    block.CreateInstruction(spv::OpIsNan, {bool_result_type_id, nan_x_id, x_value_id}, inst_it);
    block.CreateInstruction(spv::OpIsNan, {bool_result_type_id, nan_y_id, y_value_id}, inst_it);

    if (vector_size == 0) {
        result_bool_id.x = nan_x_id;
        result_bool_id.y = nan_y_id;
    } else {
        result_bool_id.x = module_.TakeNextId();
        result_bool_id.y = module_.TakeNextId();
        block.CreateInstruction(spv::OpAny, {bool_type.Id(), result_bool_id.x, nan_x_id}, inst_it);
        block.CreateInstruction(spv::OpAny, {bool_type.Id(), result_bool_id.y, nan_y_id}, inst_it);
    }

    return result_bool_id;
}

// For BDA pointers, converts the pointer to its low 32 bits. Returns 0 if not a BDA pointer.
uint32_t SanitizerPass::GetCoopMatPointerAddress(const Type* pointer_type, uint32_t pointer_id, BasicBlock& block,
                                                 InstructionIt* inst_it) {
    if (pointer_type && pointer_type->spv_type_ == SpvType::kPointer &&
        pointer_type->inst_.StorageClass() == spv::StorageClassPhysicalStorageBuffer) {
        module_.use_bda_ = true;
        const Type& uint32_type = type_manager_.GetTypeInt(32, false);
        const Type& uint64_type = type_manager_.GetTypeInt(64, false);
        const uint32_t ptr_uint64_id = module_.TakeNextId();
        block.CreateInstruction(spv::OpConvertPtrToU, {uint64_type.Id(), ptr_uint64_id, pointer_id}, inst_it);
        const uint32_t ptr_uint32_id = module_.TakeNextId();
        block.CreateInstruction(spv::OpUConvert, {uint32_type.Id(), ptr_uint32_id, ptr_uint64_id}, inst_it);
        return ptr_uint32_id;
    }
    return type_manager_.GetConstantZeroUint32().Id();
}

// For SSBO pointers via OpAccessChain, extracts the last index. Returns 0 if not an access chain.
uint32_t SanitizerPass::GetCoopMatElementIndex(const Instruction* pointer_inst, BasicBlock& block, InstructionIt* inst_it) {
    if (pointer_inst && pointer_inst->IsNonPtrAccessChain()) {
        const uint32_t last_index_id = pointer_inst->Word(pointer_inst->Length() - 1);
        return CastToUint32(last_index_id, block, inst_it);
    }
    return type_manager_.GetConstantZeroUint32().Id();
}

uint32_t SanitizerPass::CreateFunctionCall(BasicBlock& block, InstructionIt* inst_it, const InstructionMeta& meta) {
    const uint32_t function_result = module_.TakeNextId();
    const uint32_t function_def = GetLinkFunctionId(meta.sub_code);

    const uint32_t inst_position = meta.target_instruction->GetPositionOffset();
    const uint32_t inst_position_id = type_manager_.CreateConstantUInt32(inst_position).Id();

    if (meta.sub_code == glsl::kErrorSubCode_Sanitizer_DivideZero) {
        const uint32_t is_invalid_id = DivideByZeroCheck(block, inst_it, meta);

        const uint32_t bool_type = type_manager_.GetTypeBool().Id();
        const uint32_t opcode_id = type_manager_.CreateConstantUInt32(meta.target_instruction->Opcode()).Id();
        const uint32_t vector_size_id = type_manager_.CreateConstantUInt32(meta.result_type->VectorSize()).Id();

        block.CreateInstruction(
            spv::OpFunctionCall,
            {bool_type, function_result, function_def, is_invalid_id, inst_position_id, opcode_id, vector_size_id}, inst_it);
    } else if (meta.sub_code == glsl::kErrorSubCode_Sanitizer_ImageGather) {
        const uint32_t void_type = type_manager_.GetTypeVoid().Id();
        // If the OpConstant was a signed int, this will "cast" it by making a new OpConstant with the same value
        const uint32_t component_value_id = type_manager_.CreateConstantUInt32(meta.constant_value).Id();
        // Because we know this is 100% is an error if executed, just replace with a safe value
        const uint32_t safe_value_id = type_manager_.GetConstantZeroUint32().Id();
        const_cast<Instruction*>(meta.target_instruction)->UpdateWord(5, safe_value_id);
        block.CreateInstruction(spv::OpFunctionCall,
                                {void_type, function_result, function_def, inst_position_id, component_value_id}, inst_it);
    } else if (meta.sub_code == glsl::kErrorSubCode_Sanitizer_Pow) {
        const uint32_t is_invalid_id = PowCheck(block, inst_it, meta);

        const uint32_t bool_type = type_manager_.GetTypeBool().Id();
        const uint32_t vector_size = meta.result_type->VectorSize();
        const uint32_t vector_size_id = type_manager_.CreateConstantUInt32(vector_size).Id();

        uint32_t x_value_id = 0;
        uint32_t y_value_id = 0;
        if (vector_size == 0) {
            // cast as uint as that is how we are encoding the payload currently
            const Type& uint32_type = type_manager_.GetTypeInt(32, false);
            const uint32_t x_value_float = meta.target_instruction->Word(5);
            const uint32_t y_value_float = meta.target_instruction->Word(6);
            const uint32_t float_bit_width = meta.result_type->meta_.scalar.bit_width;
            if (float_bit_width == 16) {
                // Cast to f32 before OpBitCast
                const uint32_t float32_type_id = type_manager_.GetTypeFloat(32).Id();
                const uint32_t x_f32_id = module_.TakeNextId();
                const uint32_t y_f32_id = module_.TakeNextId();
                block.CreateInstruction(spv::OpFConvert, {float32_type_id, x_f32_id, x_value_float}, inst_it);
                block.CreateInstruction(spv::OpFConvert, {float32_type_id, y_f32_id, y_value_float}, inst_it);
                x_value_id = module_.TakeNextId();
                y_value_id = module_.TakeNextId();
                block.CreateInstruction(spv::OpBitcast, {uint32_type.Id(), x_value_id, x_f32_id}, inst_it);
                block.CreateInstruction(spv::OpBitcast, {uint32_type.Id(), y_value_id, y_f32_id}, inst_it);
            } else if (float_bit_width == 32) {
                x_value_id = module_.TakeNextId();
                y_value_id = module_.TakeNextId();
                block.CreateInstruction(spv::OpBitcast, {uint32_type.Id(), x_value_id, x_value_float}, inst_it);
                block.CreateInstruction(spv::OpBitcast, {uint32_type.Id(), y_value_id, y_value_float}, inst_it);
            } else {
                // GLSL.std.450 spec only allows 16 or 32 bit floats
                assert(false);
                x_value_id = type_manager_.GetConstantZeroUint32().Id();
                y_value_id = type_manager_.GetConstantZeroUint32().Id();
            }
        } else {
            // Put something valid, these are ignored on when printing error
            x_value_id = type_manager_.GetConstantZeroUint32().Id();
            y_value_id = type_manager_.GetConstantZeroUint32().Id();
        }

        block.CreateInstruction(
            spv::OpFunctionCall,
            {bool_type, function_result, function_def, is_invalid_id, inst_position_id, vector_size_id, x_value_id, y_value_id},
            inst_it);
    } else if (meta.sub_code == glsl::kErrorSubCode_Sanitizer_Atan2) {
        const uint32_t is_invalid_id = Atan2Check(block, inst_it, meta);
        const uint32_t bool_type = type_manager_.GetTypeBool().Id();
        block.CreateInstruction(spv::OpFunctionCall, {bool_type, function_result, function_def, is_invalid_id, inst_position_id},
                                inst_it);
    } else if (meta.sub_code == glsl::kErrorSubCode_Sanitizer_Fminmax) {
        const BoolResultXY is_invalid_id = FminmaxCheck(block, inst_it, meta);
        const uint32_t bool_type = type_manager_.GetTypeBool().Id();
        const uint32_t vector_size = meta.result_type->VectorSize();
        const uint32_t vector_size_id = type_manager_.CreateConstantUInt32(vector_size).Id();
        const uint32_t glsl_opcode_id = type_manager_.CreateConstantUInt32(meta.glsl_opcode).Id();
        block.CreateInstruction(spv::OpFunctionCall,
                                {bool_type, function_result, function_def, is_invalid_id.x, is_invalid_id.y, inst_position_id,
                                 vector_size_id, glsl_opcode_id},
                                inst_it);
    } else if (meta.sub_code == glsl::kErrorSubCode_Sanitizer_CoopMatAlignment) {
        const bool is_load = meta.target_instruction->Opcode() == spv::OpCooperativeMatrixLoadKHR;
        const uint32_t stride_id = is_load ? meta.target_instruction->Word(5) : meta.target_instruction->Word(4);
        const uint32_t pointer_id = is_load ? meta.target_instruction->Word(3) : meta.target_instruction->Word(1);
        const uint32_t stride_uint_id = CastToUint32(stride_id, block, inst_it);

        const Instruction* pointer_inst = block.function_->FindInstruction(pointer_id);
        const Type* pointer_type = pointer_inst ? type_manager_.FindTypeById(pointer_inst->TypeId()) : nullptr;
        const uint32_t pointer_address_id = GetCoopMatPointerAddress(pointer_type, pointer_id, block, inst_it);
        const uint32_t element_index_id = GetCoopMatElementIndex(pointer_inst, block, inst_it);

        const uint32_t bool_type = type_manager_.GetTypeBool().Id();
        const uint32_t component_size_id = type_manager_.CreateConstantUInt32(meta.component_size).Id();
        const uint32_t alignment_id = type_manager_.CreateConstantUInt32(meta.constant_value).Id();
        const uint32_t opcode_id = type_manager_.CreateConstantUInt32(meta.target_instruction->Opcode()).Id();
        block.CreateInstruction(spv::OpFunctionCall,
                                {bool_type, function_result, function_def, stride_uint_id, component_size_id, alignment_id,
                                 pointer_address_id, element_index_id, inst_position_id, opcode_id},
                                inst_it);
    } else {
        assert(false);
    }

    module_.need_log_error_ = true;
    return function_result;
}

bool SanitizerPass::IsConstantZero(const Constant& constant) const {
    if (constant.is_spec_constant_) {
        assert(false);
        return false;
    }
    const spv::Op opcode = (spv::Op)constant.inst_.Opcode();
    if (opcode == spv::OpConstantNull || opcode == spv::OpConstantFalse) {
        return true;
    } else if (opcode == spv::OpConstant) {
        // This works for signed ints and floats because zero is same for all
        if (constant.type_.Is64Bit()) {
            return constant.inst_.Word(3) == 0 && constant.inst_.Word(4) == 0;
        } else {
            return constant.inst_.Word(3) == 0;
        }
    } else if (opcode == spv::OpConstantComposite) {
        const size_t constituent_count = constant.inst_.Length() - 3;
        for (uint32_t i = 0; i < constituent_count; i++) {
            const Constant* component_constant = type_manager_.FindConstantById(constant.inst_.Operand(i));
            if (component_constant && IsConstantZero(*component_constant)) {
                return true;
            }
        }
    }
    return false;
}

bool SanitizerPass::RequiresInstrumentation(const Function& function, const Instruction& inst, InstructionMeta& meta) {
    const spv::Op opcode = (spv::Op)inst.Opcode();
    meta.target_instruction = &inst;

    if (IsValueIn(opcode, {spv::OpUDiv, spv::OpSDiv, spv::OpUMod, spv::OpSMod, spv::OpSRem, spv::OpFMod, spv::OpFRem})) {
        // Note - It is valid to divide by zero for a float (you get NaN), but invalid for an int.
        if (const Constant* constant = type_manager_.FindConstantById(inst.Word(4))) {
            // If its a constant, no reason to instrument, unless its a constant value of zero,
            // then it is only invalid if executed on the GPU.
            // (Tried to add to spirv-val, decided it should be a warning at most in VVL)
            if (!IsConstantZero(*constant)) {
                return false;
            }
        }

        // FMod/FRem are exceptions for float, they have undefined value if zero
        if (opcode == spv::OpFMod || opcode == spv::OpFRem) {
            meta.skip_safe_mode = true;
        }

        meta.result_type = type_manager_.FindTypeById(inst.TypeId());
        meta.sub_code = glsl::kErrorSubCode_Sanitizer_DivideZero;
        return true;
    } else if (opcode == spv::OpImageGather) {
        // 04664 requires this to be a constant
        if (const Constant* constant = type_manager_.FindConstantById(inst.Word(5))) {
            const uint32_t constant_value = constant->GetValueUint32();
            assert(!constant->is_spec_constant_);
            if (constant_value > 3) {
                meta.sub_code = glsl::kErrorSubCode_Sanitizer_ImageGather;
                meta.skip_safe_mode = true;
                meta.constant_value = constant_value;
                return true;
            }
        }
    } else if (opcode == spv::OpExtInst && inst.Word(3) == glsl_std450_id_) {
        uint32_t glsl_opcode = inst.Word(4);
        if (glsl_opcode == GLSLstd450Pow) {
            meta.sub_code = glsl::kErrorSubCode_Sanitizer_Pow;
        } else if (glsl_opcode == GLSLstd450Atan2) {
            meta.sub_code = glsl::kErrorSubCode_Sanitizer_Atan2;
        } else if (glsl_opcode == GLSLstd450FMin || glsl_opcode == GLSLstd450FMax) {
            meta.sub_code = glsl::kErrorSubCode_Sanitizer_Fminmax;
            meta.glsl_opcode = glsl_opcode;
        } else {
            return false;
        }

        // all of these only have results that are undefined
        meta.skip_safe_mode = true;
        meta.result_type = type_manager_.FindTypeById(inst.TypeId());
        return true;
    } else if (opcode == spv::OpCooperativeMatrixLoadKHR || opcode == spv::OpCooperativeMatrixStoreKHR) {
        CooperativeMatrixAccess cma = type_manager_.BuildCooperativeMatrixAccess(function, inst);
        const uint32_t natural_alignment = (cma.is_row_major ? cma.columns : cma.rows) * cma.component_size;
        const uint32_t required_alignment = natural_alignment < 16 ? natural_alignment : 16;
        if (required_alignment <= 1) {
            return false;
        }

        meta.sub_code = glsl::kErrorSubCode_Sanitizer_CoopMatAlignment;
        meta.constant_value = required_alignment;
        meta.component_size = cma.component_size;
        meta.skip_safe_mode = true;
        return true;
    }

    return false;
}

bool SanitizerPass::Instrument() {
    for (const auto& inst : module_.ext_inst_imports_) {
        const char* import_string = inst->GetAsString(2);
        if (strcmp(import_string, "GLSL.std.450") == 0) {
            glsl_std450_id_ = inst->ResultId();
            break;
        }
    }

    // Can safely loop function list as there is no injecting of new Functions until linking time
    for (Function& function : module_.functions_) {
        if (!function.called_from_target_) {
            continue;
        }
        for (auto block_it = function.blocks_.begin(); block_it != function.blocks_.end(); ++block_it) {
            BasicBlock& current_block = **block_it;

            cf_.Update(current_block);
            if (debug_disable_loops_ && cf_.in_loop) {
                continue;
            }

            if (current_block.IsLoopHeader()) {
                continue;  // Currently can't properly handle injecting CFG logic into a loop header block
            }
            auto& block_instructions = current_block.instructions_;

            for (auto inst_it = block_instructions.begin(); inst_it != block_instructions.end(); ++inst_it) {
                InstructionMeta meta;
                // Every instruction is analyzed by the specific pass and lets us know if we need to inject a function or not
                if (!RequiresInstrumentation(function, *(inst_it->get()), meta)) {
                    continue;
                }

                if (MaxInstrumentationsCountReached()) {
                    return instrumentations_count_ != 0;
                }
                instrumentations_count_++;

                if (!module_.settings_.safe_mode || meta.skip_safe_mode) {
                    CreateFunctionCall(current_block, &inst_it, meta);
                } else {
                    InjectConditionalData ic_data = InjectFunctionPre(function, block_it, inst_it);
                    ic_data.function_result_id = CreateFunctionCall(current_block, nullptr, meta);
                    InjectFunctionPost(current_block, ic_data);
                    // Skip the newly added valid and invalid block. Start searching again from newly split merge block
                    block_it++;
                    block_it++;
                    break;
                }
            }
        }
    }

    return instrumentations_count_ != 0;
}

void SanitizerPass::PrintDebugInfo() const {
    std::cout << "SanitizerPass instrumentation count: " << instrumentations_count_ << '\n';
}

}  // namespace spirv
}  // namespace gpuav