| // 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 <cassert> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #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" |
| |
| namespace spvtools { |
| 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 val::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 = |
| 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 val::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<uint32_t, uint32_t>( |
| inst.opcode(), index)][descriptor]; |
| } |
| } |
| ++index; |
| } |
| } |
| |
| // Collects statistics of enum words for operands of specific types. |
| void ProcessEnums() { |
| const val::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 val::Instruction& inst = GetCurrentInstruction(); |
| for (const auto& operand : inst.operands()) { |
| if (operand.type == SPV_OPERAND_TYPE_LITERAL_STRING) { |
| const std::string str = |
| reinterpret_cast<const char*>(&inst.words()[operand.offset]); |
| ++stats_->literal_strings_hist[inst.opcode()][str]; |
| } |
| } |
| } |
| |
| // Collects statistics of all single word non-id operand slots. |
| void ProcessNonIdWords() { |
| const val::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<uint32_t, uint32_t>( |
| inst.opcode(), index)][inst.word(operand.offset)]; |
| } |
| ++index; |
| } |
| } |
| |
| // Collects OpCapability statistics. |
| void ProcessCapability() { |
| const val::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 val::Instruction& inst = GetCurrentInstruction(); |
| if (inst.opcode() != SpvOpExtension) return; |
| const std::string extension = 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 val::Instruction& inst = GetCurrentInstruction(); |
| if (inst.opcode() != SpvOpConstant) return; |
| const uint32_t type_id = inst.GetOperandAs<uint32_t>(0); |
| const auto type_decl_it = vstate_->all_definitions().find(type_id); |
| assert(type_decl_it != vstate_->all_definitions().end()); |
| const val::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<uint32_t>(1); |
| const uint32_t is_signed = type_decl_inst.GetOperandAs<uint32_t>(2); |
| assert(is_signed == 0 || is_signed == 1); |
| if (bit_width == 16) { |
| if (is_signed) |
| ++stats_->s16_constant_hist[inst.GetOperandAs<int16_t>(2)]; |
| else |
| ++stats_->u16_constant_hist[inst.GetOperandAs<uint16_t>(2)]; |
| } else if (bit_width == 32) { |
| if (is_signed) |
| ++stats_->s32_constant_hist[inst.GetOperandAs<int32_t>(2)]; |
| else |
| ++stats_->u32_constant_hist[inst.GetOperandAs<uint32_t>(2)]; |
| } else if (bit_width == 64) { |
| if (is_signed) |
| ++stats_->s64_constant_hist[inst.GetOperandAs<int64_t>(2)]; |
| else |
| ++stats_->u64_constant_hist[inst.GetOperandAs<uint64_t>(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<uint32_t>(1); |
| if (bit_width == 32) { |
| ++stats_->f32_constant_hist[inst.GetOperandAs<float>(2)]; |
| } else if (bit_width == 64) { |
| ++stats_->f64_constant_hist[inst.GetOperandAs<double>(2)]; |
| } else { |
| assert(bit_width == 16); |
| } |
| } |
| } |
| |
| SpirvStats* stats() { return stats_; } |
| |
| private: |
| // Returns the current instruction (the one last processed by the validator). |
| const val::Instruction& GetCurrentInstruction() const { |
| return vstate_->ordered_instructions().back(); |
| } |
| |
| SpirvStats* stats_; |
| spv_validator_options_t validator_options_; |
| std::unique_ptr<val::ValidationState_t> 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<StatsAggregator*>(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<StatsAggregator*>(user_data); |
| return stats_aggregator->ProcessInstruction(inst); |
| } |
| |
| } // namespace |
| |
| 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 DiagnosticStream(position, context.consumer, "", |
| SPV_ERROR_INVALID_BINARY) |
| << "Invalid SPIR-V magic number."; |
| } |
| |
| spv_header_t header; |
| if (spvBinaryHeaderGet(&binary, endian, &header)) { |
| return 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 spvtools |