SPIRV-Tools/source/spirv_stats.cpp
dan sinclair a504656dad
Remove std::deque in favour of std::vector. (#1755)
This CL removes the two deque's from ValidationState and converts them
into std::vectors. In order to maintain the stability of instructions we
walk over the binary and counter the instructions and functions in the
ValidationState constructor and reserve the required number of items in
the module_functions_ and ordered_instructions_ vectors.

Issue #1176.
2018-08-01 10:37:36 -04:00

323 lines
11 KiB
C++

// 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/validate.h"
#include "val/validation_state.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