// Copyright (c) 2017 Google 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_stats.h" #include #include #include #include #include #include "binary.h" #include "diagnostic.h" #include "enum_string_mapping.h" #include "extensions.h" #include "id_descriptor.h" #include "instruction.h" #include "opcode.h" #include "operand.h" #include "spirv-tools/libspirv.h" #include "spirv_endian.h" #include "spirv_validator_options.h" #include "val/instruction.h" #include "val/validation_state.h" #include "validate.h" using libspirv::IdDescriptorCollection; using libspirv::Instruction; using libspirv::SpirvStats; using libspirv::ValidationState_t; namespace { // Helper class for stats aggregation. Receives as in/out parameter. // Constructs ValidationState and updates it by running validator for each // instruction. class StatsAggregator { public: StatsAggregator(SpirvStats* in_out_stats, const spv_const_context context, const uint32_t* words, size_t num_words) { stats_ = in_out_stats; vstate_.reset( new ValidationState_t(context, &validator_options_, words, num_words)); } // Collects header statistics and sets correct id_bound. spv_result_t ProcessHeader(spv_endianness_t /* endian */, uint32_t /* magic */, uint32_t version, uint32_t generator, uint32_t id_bound, uint32_t /* schema */) { vstate_->setIdBound(id_bound); ++stats_->version_hist[version]; ++stats_->generator_hist[generator]; return SPV_SUCCESS; } // Runs validator to validate the instruction and update vstate_, // then procession the instruction to collect stats. spv_result_t ProcessInstruction(const spv_parsed_instruction_t* inst) { const spv_result_t validation_result = spvtools::ValidateInstructionAndUpdateValidationState(vstate_.get(), inst); if (validation_result != SPV_SUCCESS) return validation_result; ProcessOpcode(); ProcessCapability(); ProcessExtension(); ProcessConstant(); ProcessEnums(); ProcessLiteralStrings(); ProcessNonIdWords(); ProcessIdDescriptors(); return SPV_SUCCESS; } // Collects statistics of descriptors generated by IdDescriptorCollection. void ProcessIdDescriptors() { const Instruction& inst = GetCurrentInstruction(); const uint32_t new_descriptor = id_descriptors_.ProcessInstruction(inst.c_inst()); if (new_descriptor) { std::stringstream ss; ss << spvOpcodeString(inst.opcode()); for (size_t i = 1; i < inst.words().size(); ++i) { ss << " " << inst.word(i); } stats_->id_descriptor_labels.emplace(new_descriptor, ss.str()); } uint32_t index = 0; for (const auto& operand : inst.operands()) { if (spvIsIdType(operand.type)) { const uint32_t descriptor = id_descriptors_.GetDescriptor(inst.word(operand.offset)); if (descriptor) { ++stats_->id_descriptor_hist[descriptor]; ++stats_ ->operand_slot_id_descriptor_hist[std::pair( inst.opcode(), index)][descriptor]; } } ++index; } } // Collects statistics of enum words for operands of specific types. void ProcessEnums() { const Instruction& inst = GetCurrentInstruction(); for (const auto& operand : inst.operands()) { switch (operand.type) { case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: case SPV_OPERAND_TYPE_EXECUTION_MODEL: case SPV_OPERAND_TYPE_ADDRESSING_MODEL: case SPV_OPERAND_TYPE_MEMORY_MODEL: case SPV_OPERAND_TYPE_EXECUTION_MODE: case SPV_OPERAND_TYPE_STORAGE_CLASS: case SPV_OPERAND_TYPE_DIMENSIONALITY: case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE: case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE: case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT: case SPV_OPERAND_TYPE_IMAGE_CHANNEL_ORDER: case SPV_OPERAND_TYPE_IMAGE_CHANNEL_DATA_TYPE: case SPV_OPERAND_TYPE_FP_ROUNDING_MODE: case SPV_OPERAND_TYPE_LINKAGE_TYPE: case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE: case SPV_OPERAND_TYPE_DECORATION: case SPV_OPERAND_TYPE_BUILT_IN: case SPV_OPERAND_TYPE_GROUP_OPERATION: case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS: case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: case SPV_OPERAND_TYPE_CAPABILITY: { ++stats_->enum_hist[operand.type][inst.word(operand.offset)]; break; } default: break; } } } // Collects statistics of literal strings used by opcodes. void ProcessLiteralStrings() { const Instruction& inst = GetCurrentInstruction(); for (const auto& operand : inst.operands()) { if (operand.type == SPV_OPERAND_TYPE_LITERAL_STRING) { const std::string str = reinterpret_cast(&inst.words()[operand.offset]); ++stats_->literal_strings_hist[inst.opcode()][str]; } } } // Collects statistics of all single word non-id operand slots. void ProcessNonIdWords() { const Instruction& inst = GetCurrentInstruction(); uint32_t index = 0; for (const auto& operand : inst.operands()) { if (operand.num_words == 1 && !spvIsIdType(operand.type)) { ++stats_->operand_slot_non_id_words_hist[std::pair( inst.opcode(), index)][inst.word(operand.offset)]; } ++index; } } // Collects OpCapability statistics. void ProcessCapability() { const Instruction& inst = GetCurrentInstruction(); if (inst.opcode() != SpvOpCapability) return; const uint32_t capability = inst.word(inst.operands()[0].offset); ++stats_->capability_hist[capability]; } // Collects OpExtension statistics. void ProcessExtension() { const Instruction& inst = GetCurrentInstruction(); if (inst.opcode() != SpvOpExtension) return; const std::string extension = libspirv::GetExtensionString(&inst.c_inst()); ++stats_->extension_hist[extension]; } // Collects OpCode statistics. void ProcessOpcode() { auto inst_it = vstate_->ordered_instructions().rbegin(); const SpvOp opcode = inst_it->opcode(); ++stats_->opcode_hist[opcode]; const uint32_t opcode_and_num_operands = (uint32_t(inst_it->operands().size()) << 16) | uint32_t(opcode); ++stats_->opcode_and_num_operands_hist[opcode_and_num_operands]; ++inst_it; if (inst_it != vstate_->ordered_instructions().rend()) { const SpvOp prev_opcode = inst_it->opcode(); ++stats_->opcode_and_num_operands_markov_hist[prev_opcode] [opcode_and_num_operands]; } auto step_it = stats_->opcode_markov_hist.begin(); for (; inst_it != vstate_->ordered_instructions().rend() && step_it != stats_->opcode_markov_hist.end(); ++inst_it, ++step_it) { auto& hist = (*step_it)[inst_it->opcode()]; ++hist[opcode]; } } // Collects OpConstant statistics. void ProcessConstant() { const Instruction& inst = GetCurrentInstruction(); if (inst.opcode() != SpvOpConstant) return; const uint32_t type_id = inst.GetOperandAs(0); const auto type_decl_it = vstate_->all_definitions().find(type_id); assert(type_decl_it != vstate_->all_definitions().end()); const Instruction& type_decl_inst = *type_decl_it->second; const SpvOp type_op = type_decl_inst.opcode(); if (type_op == SpvOpTypeInt) { const uint32_t bit_width = type_decl_inst.GetOperandAs(1); const uint32_t is_signed = type_decl_inst.GetOperandAs(2); assert(is_signed == 0 || is_signed == 1); if (bit_width == 16) { if (is_signed) ++stats_->s16_constant_hist[inst.GetOperandAs(2)]; else ++stats_->u16_constant_hist[inst.GetOperandAs(2)]; } else if (bit_width == 32) { if (is_signed) ++stats_->s32_constant_hist[inst.GetOperandAs(2)]; else ++stats_->u32_constant_hist[inst.GetOperandAs(2)]; } else if (bit_width == 64) { if (is_signed) ++stats_->s64_constant_hist[inst.GetOperandAs(2)]; else ++stats_->u64_constant_hist[inst.GetOperandAs(2)]; } else { assert(false && "TypeInt bit width is not 16, 32 or 64"); } } else if (type_op == SpvOpTypeFloat) { const uint32_t bit_width = type_decl_inst.GetOperandAs(1); if (bit_width == 32) { ++stats_->f32_constant_hist[inst.GetOperandAs(2)]; } else if (bit_width == 64) { ++stats_->f64_constant_hist[inst.GetOperandAs(2)]; } else { assert(bit_width == 16); } } } SpirvStats* stats() { return stats_; } private: // Returns the current instruction (the one last processed by the validator). const Instruction& GetCurrentInstruction() const { return vstate_->ordered_instructions().back(); } SpirvStats* stats_; spv_validator_options_t validator_options_; std::unique_ptr vstate_; IdDescriptorCollection id_descriptors_; }; spv_result_t ProcessHeader(void* user_data, spv_endianness_t endian, uint32_t magic, uint32_t version, uint32_t generator, uint32_t id_bound, uint32_t schema) { StatsAggregator* stats_aggregator = reinterpret_cast(user_data); return stats_aggregator->ProcessHeader(endian, magic, version, generator, id_bound, schema); } spv_result_t ProcessInstruction(void* user_data, const spv_parsed_instruction_t* inst) { StatsAggregator* stats_aggregator = reinterpret_cast(user_data); return stats_aggregator->ProcessInstruction(inst); } } // namespace namespace libspirv { spv_result_t AggregateStats(const spv_context_t& context, const uint32_t* words, const size_t num_words, spv_diagnostic* pDiagnostic, SpirvStats* stats) { spv_const_binary_t binary = {words, num_words}; spv_endianness_t endian; spv_position_t position = {}; if (spvBinaryEndianness(&binary, &endian)) { return libspirv::DiagnosticStream(position, context.consumer, "", SPV_ERROR_INVALID_BINARY) << "Invalid SPIR-V magic number."; } spv_header_t header; if (spvBinaryHeaderGet(&binary, endian, &header)) { return libspirv::DiagnosticStream(position, context.consumer, "", SPV_ERROR_INVALID_BINARY) << "Invalid SPIR-V header."; } StatsAggregator stats_aggregator(stats, &context, words, num_words); return spvBinaryParse(&context, &stats_aggregator, words, num_words, ProcessHeader, ProcessInstruction, pDiagnostic); } } // namespace libspirv