// Copyright (c) 2018 Google LLC // // 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. // MARK-V is a compression format for SPIR-V binaries. It strips away // non-essential information (such as result IDs which can be regenerated) and // uses various bit reduction techniques to reduce the size of the binary. #include "source/comp/markv_codec.h" #include "source/comp/markv_logger.h" #include "source/latest_version_glsl_std_450_header.h" #include "source/latest_version_opencl_std_header.h" #include "source/opcode.h" #include "source/util/make_unique.h" namespace spvtools { namespace comp { namespace { // Custom hash function used to produce short descriptors. uint32_t ShortHashU32Array(const std::vector<uint32_t>& words) { // The hash function is a sum of hashes of each word seeded by word index. // Knuth's multiplicative hash is used to hash the words. const uint32_t kKnuthMulHash = 2654435761; uint32_t val = 0; for (uint32_t i = 0; i < words.size(); ++i) { val += (words[i] + i + 123) * kKnuthMulHash; } return 1 + val % ((1 << MarkvCodec::kShortDescriptorNumBits) - 1); } // Returns a set of mtf rank codecs based on a plausible hand-coded // distribution. std::map<uint64_t, std::unique_ptr<HuffmanCodec<uint32_t>>> GetMtfHuffmanCodecs() { std::map<uint64_t, std::unique_ptr<HuffmanCodec<uint32_t>>> codecs; std::unique_ptr<HuffmanCodec<uint32_t>> codec; codec = MakeUnique<HuffmanCodec<uint32_t>>(std::map<uint32_t, uint32_t>({ {0, 5}, {1, 40}, {2, 10}, {3, 5}, {4, 5}, {5, 5}, {6, 3}, {7, 3}, {8, 3}, {9, 3}, {MarkvCodec::kMtfRankEncodedByValueSignal, 10}, })); codecs.emplace(kMtfAll, std::move(codec)); codec = MakeUnique<HuffmanCodec<uint32_t>>(std::map<uint32_t, uint32_t>({ {1, 50}, {2, 20}, {3, 5}, {4, 5}, {5, 2}, {6, 1}, {7, 1}, {8, 1}, {9, 1}, {MarkvCodec::kMtfRankEncodedByValueSignal, 10}, })); codecs.emplace(kMtfGenericNonZeroRank, std::move(codec)); return codecs; } } // namespace const uint32_t MarkvCodec::kMarkvMagicNumber = 0x07230303; const uint32_t MarkvCodec::kMtfSmallestRankEncodedByValue = 10; const uint32_t MarkvCodec::kMtfRankEncodedByValueSignal = std::numeric_limits<uint32_t>::max(); const uint32_t MarkvCodec::kShortDescriptorNumBits = 8; const size_t MarkvCodec::kByteBreakAfterInstIfLessThanUntilNextByte = 8; MarkvCodec::MarkvCodec(spv_const_context context, spv_validator_options validator_options, const MarkvModel* model) : validator_options_(validator_options), grammar_(context), model_(model), short_id_descriptors_(ShortHashU32Array), mtf_huffman_codecs_(GetMtfHuffmanCodecs()), context_(context) {} MarkvCodec::~MarkvCodec() { spvValidatorOptionsDestroy(validator_options_); } MarkvCodec::MarkvHeader::MarkvHeader() : magic_number(MarkvCodec::kMarkvMagicNumber), markv_version(MarkvCodec::GetMarkvVersion()) {} // Defines and returns current MARK-V version. // static uint32_t MarkvCodec::GetMarkvVersion() { const uint32_t kVersionMajor = 1; const uint32_t kVersionMinor = 4; return kVersionMinor | (kVersionMajor << 16); } size_t MarkvCodec::GetNumBitsToNextByte(size_t bit_pos) const { return (8 - (bit_pos % 8)) % 8; } // Returns true if the opcode has a fixed number of operands. May return a // false negative. bool MarkvCodec::OpcodeHasFixedNumberOfOperands(SpvOp opcode) const { switch (opcode) { // TODO(atgoo@github.com) This is not a complete list. case SpvOpNop: case SpvOpName: case SpvOpUndef: case SpvOpSizeOf: case SpvOpLine: case SpvOpNoLine: case SpvOpDecorationGroup: case SpvOpExtension: case SpvOpExtInstImport: case SpvOpMemoryModel: case SpvOpCapability: case SpvOpTypeVoid: case SpvOpTypeBool: case SpvOpTypeInt: case SpvOpTypeFloat: case SpvOpTypeVector: case SpvOpTypeMatrix: case SpvOpTypeSampler: case SpvOpTypeSampledImage: case SpvOpTypeArray: case SpvOpTypePointer: case SpvOpConstantTrue: case SpvOpConstantFalse: case SpvOpLabel: case SpvOpBranch: case SpvOpFunction: case SpvOpFunctionParameter: case SpvOpFunctionEnd: case SpvOpBitcast: case SpvOpCopyObject: case SpvOpTranspose: case SpvOpSNegate: case SpvOpFNegate: case SpvOpIAdd: case SpvOpFAdd: case SpvOpISub: case SpvOpFSub: case SpvOpIMul: case SpvOpFMul: case SpvOpUDiv: case SpvOpSDiv: case SpvOpFDiv: case SpvOpUMod: case SpvOpSRem: case SpvOpSMod: case SpvOpFRem: case SpvOpFMod: case SpvOpVectorTimesScalar: case SpvOpMatrixTimesScalar: case SpvOpVectorTimesMatrix: case SpvOpMatrixTimesVector: case SpvOpMatrixTimesMatrix: case SpvOpOuterProduct: case SpvOpDot: return true; default: break; } return false; } void MarkvCodec::ProcessCurInstruction() { instructions_.emplace_back(new val::Instruction(&inst_)); const SpvOp opcode = SpvOp(inst_.opcode); if (inst_.result_id) { id_to_def_instruction_.emplace(inst_.result_id, instructions_.back().get()); // Collect ids local to the current function. if (cur_function_id_) { ids_local_to_cur_function_.push_back(inst_.result_id); } // Starting new function. if (opcode == SpvOpFunction) { cur_function_id_ = inst_.result_id; cur_function_return_type_ = inst_.type_id; if (model_->id_fallback_strategy() == MarkvModel::IdFallbackStrategy::kRuleBased) { multi_mtf_.Insert(GetMtfFunctionWithReturnType(inst_.type_id), inst_.result_id); } // Store function parameter types in a queue, so that we know which types // to expect in the following OpFunctionParameter instructions. const val::Instruction* def_inst = FindDef(inst_.words[4]); assert(def_inst); assert(def_inst->opcode() == SpvOpTypeFunction); for (uint32_t i = 3; i < def_inst->words().size(); ++i) { remaining_function_parameter_types_.push_back(def_inst->word(i)); } } } // Remove local ids from MTFs if function end. if (opcode == SpvOpFunctionEnd) { cur_function_id_ = 0; for (uint32_t id : ids_local_to_cur_function_) multi_mtf_.RemoveFromAll(id); ids_local_to_cur_function_.clear(); assert(remaining_function_parameter_types_.empty()); } if (!inst_.result_id) return; { // Save the result ID to type ID mapping. // In the grammar, type ID always appears before result ID. // A regular value maps to its type. Some instructions (e.g. OpLabel) // have no type Id, and will map to 0. The result Id for a // type-generating instruction (e.g. OpTypeInt) maps to itself. auto insertion_result = id_to_type_id_.emplace( inst_.result_id, spvOpcodeGeneratesType(SpvOp(inst_.opcode)) ? inst_.result_id : inst_.type_id); (void)insertion_result; assert(insertion_result.second); } // Add result_id to MTFs. if (model_->id_fallback_strategy() == MarkvModel::IdFallbackStrategy::kRuleBased) { switch (opcode) { case SpvOpTypeFloat: case SpvOpTypeInt: case SpvOpTypeBool: case SpvOpTypeVector: case SpvOpTypePointer: case SpvOpExtInstImport: case SpvOpTypeSampledImage: case SpvOpTypeImage: case SpvOpTypeSampler: multi_mtf_.Insert(GetMtfIdGeneratedByOpcode(opcode), inst_.result_id); break; default: break; } if (spvOpcodeIsComposite(opcode)) { multi_mtf_.Insert(kMtfTypeComposite, inst_.result_id); } if (opcode == SpvOpLabel) { multi_mtf_.InsertOrPromote(kMtfLabel, inst_.result_id); } if (opcode == SpvOpTypeInt) { multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id); multi_mtf_.Insert(kMtfTypeIntScalarOrVector, inst_.result_id); } if (opcode == SpvOpTypeFloat) { multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id); multi_mtf_.Insert(kMtfTypeFloatScalarOrVector, inst_.result_id); } if (opcode == SpvOpTypeBool) { multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id); multi_mtf_.Insert(kMtfTypeBoolScalarOrVector, inst_.result_id); } if (opcode == SpvOpTypeVector) { const uint32_t component_type_id = inst_.words[2]; const uint32_t size = inst_.words[3]; if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeFloat), component_type_id)) { multi_mtf_.Insert(kMtfTypeFloatScalarOrVector, inst_.result_id); } else if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeInt), component_type_id)) { multi_mtf_.Insert(kMtfTypeIntScalarOrVector, inst_.result_id); } else if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeBool), component_type_id)) { multi_mtf_.Insert(kMtfTypeBoolScalarOrVector, inst_.result_id); } multi_mtf_.Insert(GetMtfTypeVectorOfSize(size), inst_.result_id); } if (inst_.opcode == SpvOpTypeFunction) { const uint32_t return_type = inst_.words[2]; multi_mtf_.Insert(kMtfTypeReturnedByFunction, return_type); multi_mtf_.Insert(GetMtfFunctionTypeWithReturnType(return_type), inst_.result_id); } if (inst_.type_id) { const val::Instruction* type_inst = FindDef(inst_.type_id); assert(type_inst); multi_mtf_.Insert(kMtfObject, inst_.result_id); multi_mtf_.Insert(GetMtfIdOfType(inst_.type_id), inst_.result_id); if (multi_mtf_.HasValue(kMtfTypeFloatScalarOrVector, inst_.type_id)) { multi_mtf_.Insert(kMtfFloatScalarOrVector, inst_.result_id); } if (multi_mtf_.HasValue(kMtfTypeIntScalarOrVector, inst_.type_id)) multi_mtf_.Insert(kMtfIntScalarOrVector, inst_.result_id); if (multi_mtf_.HasValue(kMtfTypeBoolScalarOrVector, inst_.type_id)) multi_mtf_.Insert(kMtfBoolScalarOrVector, inst_.result_id); if (multi_mtf_.HasValue(kMtfTypeComposite, inst_.type_id)) multi_mtf_.Insert(kMtfComposite, inst_.result_id); switch (type_inst->opcode()) { case SpvOpTypeInt: case SpvOpTypeBool: case SpvOpTypePointer: case SpvOpTypeVector: case SpvOpTypeImage: case SpvOpTypeSampledImage: case SpvOpTypeSampler: multi_mtf_.Insert( GetMtfIdWithTypeGeneratedByOpcode(type_inst->opcode()), inst_.result_id); break; default: break; } if (type_inst->opcode() == SpvOpTypeVector) { const uint32_t component_type = type_inst->word(2); multi_mtf_.Insert(GetMtfVectorOfComponentType(component_type), inst_.result_id); } if (type_inst->opcode() == SpvOpTypePointer) { assert(type_inst->operands().size() > 2); assert(type_inst->words().size() > type_inst->operands()[2].offset); const uint32_t data_type = type_inst->word(type_inst->operands()[2].offset); multi_mtf_.Insert(GetMtfPointerToType(data_type), inst_.result_id); if (multi_mtf_.HasValue(kMtfTypeComposite, data_type)) multi_mtf_.Insert(kMtfTypePointerToComposite, inst_.result_id); } } if (spvOpcodeGeneratesType(opcode)) { if (opcode != SpvOpTypeFunction) { multi_mtf_.Insert(kMtfTypeNonFunction, inst_.result_id); } } } if (model_->AnyDescriptorHasCodingScheme()) { const uint32_t long_descriptor = long_id_descriptors_.ProcessInstruction(inst_); if (model_->DescriptorHasCodingScheme(long_descriptor)) multi_mtf_.Insert(GetMtfLongIdDescriptor(long_descriptor), inst_.result_id); } if (model_->id_fallback_strategy() == MarkvModel::IdFallbackStrategy::kShortDescriptor) { const uint32_t short_descriptor = short_id_descriptors_.ProcessInstruction(inst_); multi_mtf_.Insert(GetMtfShortIdDescriptor(short_descriptor), inst_.result_id); } } uint64_t MarkvCodec::GetRuleBasedMtf() { // This function is only called for id operands (but not result ids). assert(spvIsIdType(operand_.type) || operand_.type == SPV_OPERAND_TYPE_OPTIONAL_ID); assert(operand_.type != SPV_OPERAND_TYPE_RESULT_ID); const SpvOp opcode = static_cast<SpvOp>(inst_.opcode); // All operand slots which expect label id. if ((inst_.opcode == SpvOpLoopMerge && operand_index_ <= 1) || (inst_.opcode == SpvOpSelectionMerge && operand_index_ == 0) || (inst_.opcode == SpvOpBranch && operand_index_ == 0) || (inst_.opcode == SpvOpBranchConditional && (operand_index_ == 1 || operand_index_ == 2)) || (inst_.opcode == SpvOpPhi && operand_index_ >= 3 && operand_index_ % 2 == 1) || (inst_.opcode == SpvOpSwitch && operand_index_ > 0)) { return kMtfLabel; } switch (opcode) { case SpvOpFAdd: case SpvOpFSub: case SpvOpFMul: case SpvOpFDiv: case SpvOpFRem: case SpvOpFMod: case SpvOpFNegate: { if (operand_index_ == 0) return kMtfTypeFloatScalarOrVector; return GetMtfIdOfType(inst_.type_id); } case SpvOpISub: case SpvOpIAdd: case SpvOpIMul: case SpvOpSDiv: case SpvOpUDiv: case SpvOpSMod: case SpvOpUMod: case SpvOpSRem: case SpvOpSNegate: { if (operand_index_ == 0) return kMtfTypeIntScalarOrVector; return kMtfIntScalarOrVector; } // TODO(atgoo@github.com) Add OpConvertFToU and other opcodes. case SpvOpFOrdEqual: case SpvOpFUnordEqual: case SpvOpFOrdNotEqual: case SpvOpFUnordNotEqual: case SpvOpFOrdLessThan: case SpvOpFUnordLessThan: case SpvOpFOrdGreaterThan: case SpvOpFUnordGreaterThan: case SpvOpFOrdLessThanEqual: case SpvOpFUnordLessThanEqual: case SpvOpFOrdGreaterThanEqual: case SpvOpFUnordGreaterThanEqual: { if (operand_index_ == 0) return kMtfTypeBoolScalarOrVector; if (operand_index_ == 2) return kMtfFloatScalarOrVector; if (operand_index_ == 3) { const uint32_t first_operand_id = GetInstWords()[3]; const uint32_t first_operand_type = id_to_type_id_.at(first_operand_id); return GetMtfIdOfType(first_operand_type); } break; } case SpvOpVectorShuffle: { if (operand_index_ == 0) { assert(inst_.num_operands > 4); return GetMtfTypeVectorOfSize(inst_.num_operands - 4); } assert(inst_.type_id); if (operand_index_ == 2 || operand_index_ == 3) return GetMtfVectorOfComponentType( GetVectorComponentType(inst_.type_id)); break; } case SpvOpVectorTimesScalar: { if (operand_index_ == 0) { // TODO(atgoo@github.com) Could be narrowed to vector of floats. return GetMtfIdGeneratedByOpcode(SpvOpTypeVector); } assert(inst_.type_id); if (operand_index_ == 2) return GetMtfIdOfType(inst_.type_id); if (operand_index_ == 3) return GetMtfIdOfType(GetVectorComponentType(inst_.type_id)); break; } case SpvOpDot: { if (operand_index_ == 0) return GetMtfIdGeneratedByOpcode(SpvOpTypeFloat); assert(inst_.type_id); if (operand_index_ == 2) return GetMtfVectorOfComponentType(inst_.type_id); if (operand_index_ == 3) { const uint32_t vector_id = GetInstWords()[3]; const uint32_t vector_type = id_to_type_id_.at(vector_id); return GetMtfIdOfType(vector_type); } break; } case SpvOpTypeVector: { if (operand_index_ == 1) { return kMtfTypeScalar; } break; } case SpvOpTypeMatrix: { if (operand_index_ == 1) { return GetMtfIdGeneratedByOpcode(SpvOpTypeVector); } break; } case SpvOpTypePointer: { if (operand_index_ == 2) { return kMtfTypeNonFunction; } break; } case SpvOpTypeStruct: { if (operand_index_ >= 1) { return kMtfTypeNonFunction; } break; } case SpvOpTypeFunction: { if (operand_index_ == 1) { return kMtfTypeNonFunction; } if (operand_index_ >= 2) { return kMtfTypeNonFunction; } break; } case SpvOpLoad: { if (operand_index_ == 0) return kMtfTypeNonFunction; if (operand_index_ == 2) { assert(inst_.type_id); return GetMtfPointerToType(inst_.type_id); } break; } case SpvOpStore: { if (operand_index_ == 0) return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypePointer); if (operand_index_ == 1) { const uint32_t pointer_id = GetInstWords()[1]; const uint32_t pointer_type = id_to_type_id_.at(pointer_id); const val::Instruction* pointer_inst = FindDef(pointer_type); assert(pointer_inst); assert(pointer_inst->opcode() == SpvOpTypePointer); const uint32_t data_type = pointer_inst->word(pointer_inst->operands()[2].offset); return GetMtfIdOfType(data_type); } break; } case SpvOpVariable: { if (operand_index_ == 0) return GetMtfIdGeneratedByOpcode(SpvOpTypePointer); break; } case SpvOpAccessChain: { if (operand_index_ == 0) return GetMtfIdGeneratedByOpcode(SpvOpTypePointer); if (operand_index_ == 2) return kMtfTypePointerToComposite; if (operand_index_ >= 3) return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeInt); break; } case SpvOpCompositeConstruct: { if (operand_index_ == 0) return kMtfTypeComposite; if (operand_index_ >= 2) { const uint32_t composite_type = GetInstWords()[1]; if (multi_mtf_.HasValue(kMtfTypeFloatScalarOrVector, composite_type)) return kMtfFloatScalarOrVector; if (multi_mtf_.HasValue(kMtfTypeIntScalarOrVector, composite_type)) return kMtfIntScalarOrVector; if (multi_mtf_.HasValue(kMtfTypeBoolScalarOrVector, composite_type)) return kMtfBoolScalarOrVector; } break; } case SpvOpCompositeExtract: { if (operand_index_ == 2) return kMtfComposite; break; } case SpvOpConstantComposite: { if (operand_index_ == 0) return kMtfTypeComposite; if (operand_index_ >= 2) { const val::Instruction* composite_type_inst = FindDef(inst_.type_id); assert(composite_type_inst); if (composite_type_inst->opcode() == SpvOpTypeVector) { return GetMtfIdOfType(composite_type_inst->word(2)); } } break; } case SpvOpExtInst: { if (operand_index_ == 2) return GetMtfIdGeneratedByOpcode(SpvOpExtInstImport); if (operand_index_ >= 4) { const uint32_t return_type = GetInstWords()[1]; const uint32_t ext_inst_type = inst_.ext_inst_type; const uint32_t ext_inst_index = GetInstWords()[4]; // TODO(atgoo@github.com) The list of extended instructions is // incomplete. Only common instructions and low-hanging fruits listed. if (ext_inst_type == SPV_EXT_INST_TYPE_GLSL_STD_450) { switch (ext_inst_index) { case GLSLstd450FAbs: case GLSLstd450FClamp: case GLSLstd450FMax: case GLSLstd450FMin: case GLSLstd450FMix: case GLSLstd450Step: case GLSLstd450SmoothStep: case GLSLstd450Fma: case GLSLstd450Pow: case GLSLstd450Exp: case GLSLstd450Exp2: case GLSLstd450Log: case GLSLstd450Log2: case GLSLstd450Sqrt: case GLSLstd450InverseSqrt: case GLSLstd450Fract: case GLSLstd450Floor: case GLSLstd450Ceil: case GLSLstd450Radians: case GLSLstd450Degrees: case GLSLstd450Sin: case GLSLstd450Cos: case GLSLstd450Tan: case GLSLstd450Sinh: case GLSLstd450Cosh: case GLSLstd450Tanh: case GLSLstd450Asin: case GLSLstd450Acos: case GLSLstd450Atan: case GLSLstd450Atan2: case GLSLstd450Asinh: case GLSLstd450Acosh: case GLSLstd450Atanh: case GLSLstd450MatrixInverse: case GLSLstd450Cross: case GLSLstd450Normalize: case GLSLstd450Reflect: case GLSLstd450FaceForward: return GetMtfIdOfType(return_type); case GLSLstd450Length: case GLSLstd450Distance: case GLSLstd450Refract: return kMtfFloatScalarOrVector; default: break; } } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_STD) { switch (ext_inst_index) { case OpenCLLIB::Fabs: case OpenCLLIB::FClamp: case OpenCLLIB::Fmax: case OpenCLLIB::Fmin: case OpenCLLIB::Step: case OpenCLLIB::Smoothstep: case OpenCLLIB::Fma: case OpenCLLIB::Pow: case OpenCLLIB::Exp: case OpenCLLIB::Exp2: case OpenCLLIB::Log: case OpenCLLIB::Log2: case OpenCLLIB::Sqrt: case OpenCLLIB::Rsqrt: case OpenCLLIB::Fract: case OpenCLLIB::Floor: case OpenCLLIB::Ceil: case OpenCLLIB::Radians: case OpenCLLIB::Degrees: case OpenCLLIB::Sin: case OpenCLLIB::Cos: case OpenCLLIB::Tan: case OpenCLLIB::Sinh: case OpenCLLIB::Cosh: case OpenCLLIB::Tanh: case OpenCLLIB::Asin: case OpenCLLIB::Acos: case OpenCLLIB::Atan: case OpenCLLIB::Atan2: case OpenCLLIB::Asinh: case OpenCLLIB::Acosh: case OpenCLLIB::Atanh: case OpenCLLIB::Cross: case OpenCLLIB::Normalize: return GetMtfIdOfType(return_type); case OpenCLLIB::Length: case OpenCLLIB::Distance: return kMtfFloatScalarOrVector; default: break; } } } break; } case SpvOpFunction: { if (operand_index_ == 0) return kMtfTypeReturnedByFunction; if (operand_index_ == 3) { const uint32_t return_type = GetInstWords()[1]; return GetMtfFunctionTypeWithReturnType(return_type); } break; } case SpvOpFunctionCall: { if (operand_index_ == 0) return kMtfTypeReturnedByFunction; if (operand_index_ == 2) { const uint32_t return_type = GetInstWords()[1]; return GetMtfFunctionWithReturnType(return_type); } if (operand_index_ >= 3) { const uint32_t function_id = GetInstWords()[3]; const val::Instruction* function_inst = FindDef(function_id); if (!function_inst) return kMtfObject; assert(function_inst->opcode() == SpvOpFunction); const uint32_t function_type_id = function_inst->word(4); const val::Instruction* function_type_inst = FindDef(function_type_id); assert(function_type_inst); assert(function_type_inst->opcode() == SpvOpTypeFunction); const uint32_t argument_type = function_type_inst->word(operand_index_); return GetMtfIdOfType(argument_type); } break; } case SpvOpReturnValue: { if (operand_index_ == 0) return GetMtfIdOfType(cur_function_return_type_); break; } case SpvOpBranchConditional: { if (operand_index_ == 0) return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeBool); break; } case SpvOpSampledImage: { if (operand_index_ == 0) return GetMtfIdGeneratedByOpcode(SpvOpTypeSampledImage); if (operand_index_ == 2) return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeImage); if (operand_index_ == 3) return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeSampler); break; } case SpvOpImageSampleImplicitLod: { if (operand_index_ == 0) return GetMtfIdGeneratedByOpcode(SpvOpTypeVector); if (operand_index_ == 2) return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeSampledImage); if (operand_index_ == 3) return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeVector); break; } default: break; } return kMtfNone; } } // namespace comp } // namespace spvtools