| // 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. | |
| #include "source/comp/markv_decoder.h" | |
| #include <cstring> | |
| #include <iterator> | |
| #include <numeric> | |
| #include "source/ext_inst.h" | |
| #include "source/opcode.h" | |
| #include "spirv-tools/libspirv.hpp" | |
| namespace spvtools { | |
| namespace comp { | |
| spv_result_t MarkvDecoder::DecodeNonIdWord(uint32_t* word) { | |
| auto* codec = model_->GetNonIdWordHuffmanCodec(inst_.opcode, operand_index_); | |
| if (codec) { | |
| uint64_t decoded_value = 0; | |
| if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to decode non-id word with Huffman"; | |
| if (decoded_value != MarkvModel::GetMarkvNoneOfTheAbove()) { | |
| // The word decoded successfully. | |
| *word = uint32_t(decoded_value); | |
| assert(*word == decoded_value); | |
| return SPV_SUCCESS; | |
| } | |
| // Received kMarkvNoneOfTheAbove signal, use fallback decoding. | |
| } | |
| const size_t chunk_length = | |
| model_->GetOperandVariableWidthChunkLength(operand_.type); | |
| if (chunk_length) { | |
| if (!reader_.ReadVariableWidthU32(word, chunk_length)) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to decode non-id word with varint"; | |
| } else { | |
| if (!reader_.ReadUnencoded(word)) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to read unencoded non-id word"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t MarkvDecoder::DecodeOpcodeAndNumberOfOperands( | |
| uint32_t* opcode, uint32_t* num_operands) { | |
| // First try to use the Markov chain codec. | |
| auto* codec = | |
| model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(GetPrevOpcode()); | |
| if (codec) { | |
| uint64_t decoded_value = 0; | |
| if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) | |
| return Diag(SPV_ERROR_INTERNAL) | |
| << "Failed to decode opcode_and_num_operands, previous opcode is " | |
| << spvOpcodeString(GetPrevOpcode()); | |
| if (decoded_value != MarkvModel::GetMarkvNoneOfTheAbove()) { | |
| // The word was successfully decoded. | |
| *opcode = uint32_t(decoded_value & 0xFFFF); | |
| *num_operands = uint32_t(decoded_value >> 16); | |
| return SPV_SUCCESS; | |
| } | |
| // Received kMarkvNoneOfTheAbove signal, use fallback decoding. | |
| } | |
| // Fallback to base-rate codec. | |
| codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(SpvOpNop); | |
| assert(codec); | |
| uint64_t decoded_value = 0; | |
| if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) | |
| return Diag(SPV_ERROR_INTERNAL) | |
| << "Failed to decode opcode_and_num_operands with global codec"; | |
| if (decoded_value == MarkvModel::GetMarkvNoneOfTheAbove()) { | |
| // Received kMarkvNoneOfTheAbove signal, fallback further. | |
| return SPV_UNSUPPORTED; | |
| } | |
| *opcode = uint32_t(decoded_value & 0xFFFF); | |
| *num_operands = uint32_t(decoded_value >> 16); | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t MarkvDecoder::DecodeMtfRankHuffman(uint64_t mtf, | |
| uint32_t fallback_method, | |
| uint32_t* rank) { | |
| const auto* codec = GetMtfHuffmanCodec(mtf); | |
| if (!codec) { | |
| assert(fallback_method != kMtfNone); | |
| codec = GetMtfHuffmanCodec(fallback_method); | |
| } | |
| if (!codec) return Diag(SPV_ERROR_INTERNAL) << "No codec to decode MTF rank"; | |
| uint32_t decoded_value = 0; | |
| if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) | |
| return Diag(SPV_ERROR_INTERNAL) << "Failed to decode MTF rank with Huffman"; | |
| if (decoded_value == kMtfRankEncodedByValueSignal) { | |
| // Decode by value. | |
| if (!reader_.ReadVariableWidthU32(rank, model_->mtf_rank_chunk_length())) | |
| return Diag(SPV_ERROR_INTERNAL) | |
| << "Failed to decode MTF rank with varint"; | |
| *rank += MarkvCodec::kMtfSmallestRankEncodedByValue; | |
| } else { | |
| // Decode using Huffman coding. | |
| assert(decoded_value < MarkvCodec::kMtfSmallestRankEncodedByValue); | |
| *rank = decoded_value; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t MarkvDecoder::DecodeIdWithDescriptor(uint32_t* id) { | |
| auto* codec = | |
| model_->GetIdDescriptorHuffmanCodec(inst_.opcode, operand_index_); | |
| uint64_t mtf = kMtfNone; | |
| if (codec) { | |
| uint64_t decoded_value = 0; | |
| if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) | |
| return Diag(SPV_ERROR_INTERNAL) | |
| << "Failed to decode descriptor with Huffman"; | |
| if (decoded_value != MarkvModel::GetMarkvNoneOfTheAbove()) { | |
| const uint32_t long_descriptor = uint32_t(decoded_value); | |
| mtf = GetMtfLongIdDescriptor(long_descriptor); | |
| } | |
| } | |
| if (mtf == kMtfNone) { | |
| if (model_->id_fallback_strategy() != | |
| MarkvModel::IdFallbackStrategy::kShortDescriptor) { | |
| return SPV_UNSUPPORTED; | |
| } | |
| uint64_t decoded_value = 0; | |
| if (!reader_.ReadBits(&decoded_value, MarkvCodec::kShortDescriptorNumBits)) | |
| return Diag(SPV_ERROR_INTERNAL) << "Failed to read short descriptor"; | |
| const uint32_t short_descriptor = uint32_t(decoded_value); | |
| if (short_descriptor == 0) { | |
| // Forward declared id. | |
| return SPV_UNSUPPORTED; | |
| } | |
| mtf = GetMtfShortIdDescriptor(short_descriptor); | |
| } | |
| return DecodeExistingId(mtf, id); | |
| } | |
| spv_result_t MarkvDecoder::DecodeExistingId(uint64_t mtf, uint32_t* id) { | |
| assert(multi_mtf_.GetSize(mtf) > 0); | |
| *id = 0; | |
| uint32_t rank = 0; | |
| if (multi_mtf_.GetSize(mtf) == 1) { | |
| rank = 1; | |
| } else { | |
| const spv_result_t result = | |
| DecodeMtfRankHuffman(mtf, kMtfGenericNonZeroRank, &rank); | |
| if (result != SPV_SUCCESS) return result; | |
| } | |
| assert(rank); | |
| if (!multi_mtf_.ValueFromRank(mtf, rank, id)) | |
| return Diag(SPV_ERROR_INTERNAL) << "MTF rank is out of bounds"; | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t MarkvDecoder::DecodeRefId(uint32_t* id) { | |
| { | |
| const spv_result_t result = DecodeIdWithDescriptor(id); | |
| if (result != SPV_UNSUPPORTED) return result; | |
| } | |
| const bool can_forward_declare = spvOperandCanBeForwardDeclaredFunction( | |
| SpvOp(inst_.opcode))(operand_index_); | |
| uint32_t rank = 0; | |
| *id = 0; | |
| if (model_->id_fallback_strategy() == | |
| MarkvModel::IdFallbackStrategy::kRuleBased) { | |
| uint64_t mtf = GetRuleBasedMtf(); | |
| if (mtf != kMtfNone && !can_forward_declare) { | |
| return DecodeExistingId(mtf, id); | |
| } | |
| if (mtf == kMtfNone) mtf = kMtfAll; | |
| { | |
| const spv_result_t result = DecodeMtfRankHuffman(mtf, kMtfAll, &rank); | |
| if (result != SPV_SUCCESS) return result; | |
| } | |
| if (rank == 0) { | |
| // This is the first occurrence of a forward declared id. | |
| *id = GetIdBound(); | |
| SetIdBound(*id + 1); | |
| multi_mtf_.Insert(kMtfAll, *id); | |
| multi_mtf_.Insert(kMtfForwardDeclared, *id); | |
| if (mtf != kMtfAll) multi_mtf_.Insert(mtf, *id); | |
| } else { | |
| if (!multi_mtf_.ValueFromRank(mtf, rank, id)) | |
| return Diag(SPV_ERROR_INTERNAL) << "MTF rank out of bounds"; | |
| } | |
| } else { | |
| assert(can_forward_declare); | |
| if (!reader_.ReadVariableWidthU32(&rank, model_->mtf_rank_chunk_length())) | |
| return Diag(SPV_ERROR_INTERNAL) | |
| << "Failed to decode MTF rank with varint"; | |
| if (rank == 0) { | |
| // This is the first occurrence of a forward declared id. | |
| *id = GetIdBound(); | |
| SetIdBound(*id + 1); | |
| multi_mtf_.Insert(kMtfForwardDeclared, *id); | |
| } else { | |
| if (!multi_mtf_.ValueFromRank(kMtfForwardDeclared, rank, id)) | |
| return Diag(SPV_ERROR_INTERNAL) << "MTF rank out of bounds"; | |
| } | |
| } | |
| assert(*id); | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t MarkvDecoder::DecodeTypeId() { | |
| if (inst_.opcode == SpvOpFunctionParameter) { | |
| assert(!remaining_function_parameter_types_.empty()); | |
| inst_.type_id = remaining_function_parameter_types_.front(); | |
| remaining_function_parameter_types_.pop_front(); | |
| return SPV_SUCCESS; | |
| } | |
| { | |
| const spv_result_t result = DecodeIdWithDescriptor(&inst_.type_id); | |
| if (result != SPV_UNSUPPORTED) return result; | |
| } | |
| assert(model_->id_fallback_strategy() == | |
| MarkvModel::IdFallbackStrategy::kRuleBased); | |
| uint64_t mtf = GetRuleBasedMtf(); | |
| assert(!spvOperandCanBeForwardDeclaredFunction(SpvOp(inst_.opcode))( | |
| operand_index_)); | |
| if (mtf == kMtfNone) { | |
| mtf = kMtfTypeNonFunction; | |
| // Function types should have been handled by GetRuleBasedMtf. | |
| assert(inst_.opcode != SpvOpFunction); | |
| } | |
| return DecodeExistingId(mtf, &inst_.type_id); | |
| } | |
| spv_result_t MarkvDecoder::DecodeResultId() { | |
| uint32_t rank = 0; | |
| const uint64_t num_still_forward_declared = | |
| multi_mtf_.GetSize(kMtfForwardDeclared); | |
| if (num_still_forward_declared) { | |
| // Some ids were forward declared. Check if this id is one of them. | |
| uint64_t id_was_forward_declared; | |
| if (!reader_.ReadBits(&id_was_forward_declared, 1)) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to read id_was_forward_declared flag"; | |
| if (id_was_forward_declared) { | |
| if (!reader_.ReadVariableWidthU32(&rank, model_->mtf_rank_chunk_length())) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to read MTF rank of forward declared id"; | |
| if (rank) { | |
| // The id was forward declared, recover it from kMtfForwardDeclared. | |
| if (!multi_mtf_.ValueFromRank(kMtfForwardDeclared, rank, | |
| &inst_.result_id)) | |
| return Diag(SPV_ERROR_INTERNAL) | |
| << "Forward declared MTF rank is out of bounds"; | |
| // We can now remove the id from kMtfForwardDeclared. | |
| if (!multi_mtf_.Remove(kMtfForwardDeclared, inst_.result_id)) | |
| return Diag(SPV_ERROR_INTERNAL) | |
| << "Failed to remove id from kMtfForwardDeclared"; | |
| } | |
| } | |
| } | |
| if (inst_.result_id == 0) { | |
| // The id was not forward declared, issue a new id. | |
| inst_.result_id = GetIdBound(); | |
| SetIdBound(inst_.result_id + 1); | |
| } | |
| if (model_->id_fallback_strategy() == | |
| MarkvModel::IdFallbackStrategy::kRuleBased) { | |
| if (!rank) { | |
| multi_mtf_.Insert(kMtfAll, inst_.result_id); | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t MarkvDecoder::DecodeLiteralNumber( | |
| const spv_parsed_operand_t& operand) { | |
| if (operand.number_bit_width <= 32) { | |
| uint32_t word = 0; | |
| const spv_result_t result = DecodeNonIdWord(&word); | |
| if (result != SPV_SUCCESS) return result; | |
| inst_words_.push_back(word); | |
| } else { | |
| assert(operand.number_bit_width <= 64); | |
| uint64_t word = 0; | |
| if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) { | |
| if (!reader_.ReadVariableWidthU64(&word, model_->u64_chunk_length())) | |
| return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal U64"; | |
| } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) { | |
| int64_t val = 0; | |
| if (!reader_.ReadVariableWidthS64(&val, model_->s64_chunk_length(), | |
| model_->s64_block_exponent())) | |
| return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal S64"; | |
| std::memcpy(&word, &val, 8); | |
| } else if (operand.number_kind == SPV_NUMBER_FLOATING) { | |
| if (!reader_.ReadUnencoded(&word)) | |
| return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal F64"; | |
| } else { | |
| return Diag(SPV_ERROR_INTERNAL) << "Unsupported bit length"; | |
| } | |
| inst_words_.push_back(static_cast<uint32_t>(word)); | |
| inst_words_.push_back(static_cast<uint32_t>(word >> 32)); | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| bool MarkvDecoder::ReadToByteBreak(size_t byte_break_if_less_than) { | |
| const size_t num_bits_to_next_byte = | |
| GetNumBitsToNextByte(reader_.GetNumReadBits()); | |
| if (num_bits_to_next_byte == 0 || | |
| num_bits_to_next_byte > byte_break_if_less_than) | |
| return true; | |
| uint64_t bits = 0; | |
| if (!reader_.ReadBits(&bits, num_bits_to_next_byte)) return false; | |
| assert(bits == 0); | |
| if (bits != 0) return false; | |
| return true; | |
| } | |
| spv_result_t MarkvDecoder::DecodeModule(std::vector<uint32_t>* spirv_binary) { | |
| const bool header_read_success = | |
| reader_.ReadUnencoded(&header_.magic_number) && | |
| reader_.ReadUnencoded(&header_.markv_version) && | |
| reader_.ReadUnencoded(&header_.markv_model) && | |
| reader_.ReadUnencoded(&header_.markv_length_in_bits) && | |
| reader_.ReadUnencoded(&header_.spirv_version) && | |
| reader_.ReadUnencoded(&header_.spirv_generator); | |
| if (!header_read_success) | |
| return Diag(SPV_ERROR_INVALID_BINARY) << "Unable to read MARK-V header"; | |
| if (header_.markv_length_in_bits == 0) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Header markv_length_in_bits field is zero"; | |
| if (header_.magic_number != MarkvCodec::kMarkvMagicNumber) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "MARK-V binary has incorrect magic number"; | |
| // TODO(atgoo@github.com): Print version strings. | |
| if (header_.markv_version != MarkvCodec::GetMarkvVersion()) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "MARK-V binary and the codec have different versions"; | |
| const uint32_t model_type = header_.markv_model >> 16; | |
| const uint32_t model_version = header_.markv_model & 0xFFFF; | |
| if (model_type != model_->model_type()) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "MARK-V binary and the codec use different MARK-V models"; | |
| if (model_version != model_->model_version()) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "MARK-V binary and the codec use different versions if the same " | |
| << "MARK-V model"; | |
| spirv_.reserve(header_.markv_length_in_bits / 2); // Heuristic. | |
| spirv_.resize(5, 0); | |
| spirv_[0] = SpvMagicNumber; | |
| spirv_[1] = header_.spirv_version; | |
| spirv_[2] = header_.spirv_generator; | |
| if (logger_) { | |
| reader_.SetCallback( | |
| [this](const std::string& str) { logger_->AppendBitSequence(str); }); | |
| } | |
| while (reader_.GetNumReadBits() < header_.markv_length_in_bits) { | |
| inst_ = {}; | |
| const spv_result_t decode_result = DecodeInstruction(); | |
| if (decode_result != SPV_SUCCESS) return decode_result; | |
| } | |
| if (validator_options_) { | |
| spv_const_binary_t validation_binary = {spirv_.data(), spirv_.size()}; | |
| const spv_result_t result = spvValidateWithOptions( | |
| context_, validator_options_, &validation_binary, nullptr); | |
| if (result != SPV_SUCCESS) return result; | |
| } | |
| // Validate the decode binary | |
| if (reader_.GetNumReadBits() != header_.markv_length_in_bits || | |
| !reader_.OnlyZeroesLeft()) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "MARK-V binary has wrong stated bit length " | |
| << reader_.GetNumReadBits() << " " << header_.markv_length_in_bits; | |
| } | |
| // Decoding of the module is finished, validation state should have correct | |
| // id bound. | |
| spirv_[3] = GetIdBound(); | |
| *spirv_binary = std::move(spirv_); | |
| return SPV_SUCCESS; | |
| } | |
| // TODO(atgoo@github.com): The implementation borrows heavily from | |
| // Parser::parseOperand. | |
| // Consider coupling them together in some way once MARK-V codec is more mature. | |
| // For now it's better to keep the code independent for experimentation | |
| // purposes. | |
| spv_result_t MarkvDecoder::DecodeOperand( | |
| size_t operand_offset, const spv_operand_type_t type, | |
| spv_operand_pattern_t* expected_operands) { | |
| const SpvOp opcode = static_cast<SpvOp>(inst_.opcode); | |
| memset(&operand_, 0, sizeof(operand_)); | |
| assert((operand_offset >> 16) == 0); | |
| operand_.offset = static_cast<uint16_t>(operand_offset); | |
| operand_.type = type; | |
| // Set default values, may be updated later. | |
| operand_.number_kind = SPV_NUMBER_NONE; | |
| operand_.number_bit_width = 0; | |
| const size_t first_word_index = inst_words_.size(); | |
| switch (type) { | |
| case SPV_OPERAND_TYPE_RESULT_ID: { | |
| const spv_result_t result = DecodeResultId(); | |
| if (result != SPV_SUCCESS) return result; | |
| inst_words_.push_back(inst_.result_id); | |
| SetIdBound(std::max(GetIdBound(), inst_.result_id + 1)); | |
| PromoteIfNeeded(inst_.result_id); | |
| break; | |
| } | |
| case SPV_OPERAND_TYPE_TYPE_ID: { | |
| const spv_result_t result = DecodeTypeId(); | |
| if (result != SPV_SUCCESS) return result; | |
| inst_words_.push_back(inst_.type_id); | |
| SetIdBound(std::max(GetIdBound(), inst_.type_id + 1)); | |
| PromoteIfNeeded(inst_.type_id); | |
| break; | |
| } | |
| case SPV_OPERAND_TYPE_ID: | |
| case SPV_OPERAND_TYPE_OPTIONAL_ID: | |
| case SPV_OPERAND_TYPE_SCOPE_ID: | |
| case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: { | |
| uint32_t id = 0; | |
| const spv_result_t result = DecodeRefId(&id); | |
| if (result != SPV_SUCCESS) return result; | |
| if (id == 0) return Diag(SPV_ERROR_INVALID_BINARY) << "Decoded id is 0"; | |
| if (type == SPV_OPERAND_TYPE_ID || type == SPV_OPERAND_TYPE_OPTIONAL_ID) { | |
| operand_.type = SPV_OPERAND_TYPE_ID; | |
| if (opcode == SpvOpExtInst && operand_.offset == 3) { | |
| // The current word is the extended instruction set id. | |
| // Set the extended instruction set type for the current | |
| // instruction. | |
| auto ext_inst_type_iter = import_id_to_ext_inst_type_.find(id); | |
| if (ext_inst_type_iter == import_id_to_ext_inst_type_.end()) { | |
| return Diag(SPV_ERROR_INVALID_ID) | |
| << "OpExtInst set id " << id | |
| << " does not reference an OpExtInstImport result Id"; | |
| } | |
| inst_.ext_inst_type = ext_inst_type_iter->second; | |
| } | |
| } | |
| inst_words_.push_back(id); | |
| SetIdBound(std::max(GetIdBound(), id + 1)); | |
| PromoteIfNeeded(id); | |
| break; | |
| } | |
| case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: { | |
| uint32_t word = 0; | |
| const spv_result_t result = DecodeNonIdWord(&word); | |
| if (result != SPV_SUCCESS) return result; | |
| inst_words_.push_back(word); | |
| assert(SpvOpExtInst == opcode); | |
| assert(inst_.ext_inst_type != SPV_EXT_INST_TYPE_NONE); | |
| spv_ext_inst_desc ext_inst; | |
| if (grammar_.lookupExtInst(inst_.ext_inst_type, word, &ext_inst)) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Invalid extended instruction number: " << word; | |
| spvPushOperandTypes(ext_inst->operandTypes, expected_operands); | |
| break; | |
| } | |
| case SPV_OPERAND_TYPE_LITERAL_INTEGER: | |
| case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: { | |
| // These are regular single-word literal integer operands. | |
| // Post-parsing validation should check the range of the parsed value. | |
| operand_.type = SPV_OPERAND_TYPE_LITERAL_INTEGER; | |
| // It turns out they are always unsigned integers! | |
| operand_.number_kind = SPV_NUMBER_UNSIGNED_INT; | |
| operand_.number_bit_width = 32; | |
| uint32_t word = 0; | |
| const spv_result_t result = DecodeNonIdWord(&word); | |
| if (result != SPV_SUCCESS) return result; | |
| inst_words_.push_back(word); | |
| break; | |
| } | |
| case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: | |
| case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: { | |
| operand_.type = SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER; | |
| if (opcode == SpvOpSwitch) { | |
| // The literal operands have the same type as the value | |
| // referenced by the selector Id. | |
| const uint32_t selector_id = inst_words_.at(1); | |
| const auto type_id_iter = id_to_type_id_.find(selector_id); | |
| if (type_id_iter == id_to_type_id_.end() || type_id_iter->second == 0) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Invalid OpSwitch: selector id " << selector_id | |
| << " has no type"; | |
| } | |
| uint32_t type_id = type_id_iter->second; | |
| if (selector_id == type_id) { | |
| // Recall that by convention, a result ID that is a type definition | |
| // maps to itself. | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Invalid OpSwitch: selector id " << selector_id | |
| << " is a type, not a value"; | |
| } | |
| if (auto error = SetNumericTypeInfoForType(&operand_, type_id)) | |
| return error; | |
| if (operand_.number_kind != SPV_NUMBER_UNSIGNED_INT && | |
| operand_.number_kind != SPV_NUMBER_SIGNED_INT) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Invalid OpSwitch: selector id " << selector_id | |
| << " is not a scalar integer"; | |
| } | |
| } else { | |
| assert(opcode == SpvOpConstant || opcode == SpvOpSpecConstant); | |
| // The literal number type is determined by the type Id for the | |
| // constant. | |
| assert(inst_.type_id); | |
| if (auto error = SetNumericTypeInfoForType(&operand_, inst_.type_id)) | |
| return error; | |
| } | |
| if (auto error = DecodeLiteralNumber(operand_)) return error; | |
| break; | |
| } | |
| case SPV_OPERAND_TYPE_LITERAL_STRING: | |
| case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: { | |
| operand_.type = SPV_OPERAND_TYPE_LITERAL_STRING; | |
| std::vector<char> str; | |
| auto* codec = model_->GetLiteralStringHuffmanCodec(inst_.opcode); | |
| if (codec) { | |
| std::string decoded_string; | |
| const bool huffman_result = | |
| codec->DecodeFromStream(GetReadBitCallback(), &decoded_string); | |
| assert(huffman_result); | |
| if (!huffman_result) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to read literal string"; | |
| if (decoded_string != "kMarkvNoneOfTheAbove") { | |
| std::copy(decoded_string.begin(), decoded_string.end(), | |
| std::back_inserter(str)); | |
| str.push_back('\0'); | |
| } | |
| } | |
| // The loop is expected to terminate once we encounter '\0' or exhaust | |
| // the bit stream. | |
| if (str.empty()) { | |
| while (true) { | |
| char ch = 0; | |
| if (!reader_.ReadUnencoded(&ch)) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to read literal string"; | |
| str.push_back(ch); | |
| if (ch == '\0') break; | |
| } | |
| } | |
| while (str.size() % 4 != 0) str.push_back('\0'); | |
| inst_words_.resize(inst_words_.size() + str.size() / 4); | |
| std::memcpy(&inst_words_[first_word_index], str.data(), str.size()); | |
| if (SpvOpExtInstImport == opcode) { | |
| // Record the extended instruction type for the ID for this import. | |
| // There is only one string literal argument to OpExtInstImport, | |
| // so it's sufficient to guard this just on the opcode. | |
| const spv_ext_inst_type_t ext_inst_type = | |
| spvExtInstImportTypeGet(str.data()); | |
| if (SPV_EXT_INST_TYPE_NONE == ext_inst_type) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Invalid extended instruction import '" << str.data() | |
| << "'"; | |
| } | |
| // We must have parsed a valid result ID. It's a condition | |
| // of the grammar, and we only accept non-zero result Ids. | |
| assert(inst_.result_id); | |
| const bool inserted = | |
| import_id_to_ext_inst_type_.emplace(inst_.result_id, ext_inst_type) | |
| .second; | |
| (void)inserted; | |
| assert(inserted); | |
| } | |
| break; | |
| } | |
| case SPV_OPERAND_TYPE_CAPABILITY: | |
| 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_FP_ROUNDING_MODE: | |
| case SPV_OPERAND_TYPE_LINKAGE_TYPE: | |
| case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: | |
| case SPV_OPERAND_TYPE_OPTIONAL_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: { | |
| // A single word that is a plain enum value. | |
| uint32_t word = 0; | |
| const spv_result_t result = DecodeNonIdWord(&word); | |
| if (result != SPV_SUCCESS) return result; | |
| inst_words_.push_back(word); | |
| // Map an optional operand type to its corresponding concrete type. | |
| if (type == SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER) | |
| operand_.type = SPV_OPERAND_TYPE_ACCESS_QUALIFIER; | |
| spv_operand_desc entry; | |
| if (grammar_.lookupOperand(type, word, &entry)) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Invalid " << spvOperandTypeStr(operand_.type) | |
| << " operand: " << word; | |
| } | |
| // Prepare to accept operands to this operand, if needed. | |
| spvPushOperandTypes(entry->operandTypes, expected_operands); | |
| break; | |
| } | |
| case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: | |
| case SPV_OPERAND_TYPE_FUNCTION_CONTROL: | |
| case SPV_OPERAND_TYPE_LOOP_CONTROL: | |
| case SPV_OPERAND_TYPE_IMAGE: | |
| case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: | |
| case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: | |
| case SPV_OPERAND_TYPE_SELECTION_CONTROL: { | |
| // This operand is a mask. | |
| uint32_t word = 0; | |
| const spv_result_t result = DecodeNonIdWord(&word); | |
| if (result != SPV_SUCCESS) return result; | |
| inst_words_.push_back(word); | |
| // Map an optional operand type to its corresponding concrete type. | |
| if (type == SPV_OPERAND_TYPE_OPTIONAL_IMAGE) | |
| operand_.type = SPV_OPERAND_TYPE_IMAGE; | |
| else if (type == SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS) | |
| operand_.type = SPV_OPERAND_TYPE_MEMORY_ACCESS; | |
| // Check validity of set mask bits. Also prepare for operands for those | |
| // masks if they have any. To get operand order correct, scan from | |
| // MSB to LSB since we can only prepend operands to a pattern. | |
| // The only case in the grammar where you have more than one mask bit | |
| // having an operand is for image operands. See SPIR-V 3.14 Image | |
| // Operands. | |
| uint32_t remaining_word = word; | |
| for (uint32_t mask = (1u << 31); remaining_word; mask >>= 1) { | |
| if (remaining_word & mask) { | |
| spv_operand_desc entry; | |
| if (grammar_.lookupOperand(type, mask, &entry)) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Invalid " << spvOperandTypeStr(operand_.type) | |
| << " operand: " << word << " has invalid mask component " | |
| << mask; | |
| } | |
| remaining_word ^= mask; | |
| spvPushOperandTypes(entry->operandTypes, expected_operands); | |
| } | |
| } | |
| if (word == 0) { | |
| // An all-zeroes mask *might* also be valid. | |
| spv_operand_desc entry; | |
| if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry)) { | |
| // Prepare for its operands, if any. | |
| spvPushOperandTypes(entry->operandTypes, expected_operands); | |
| } | |
| } | |
| break; | |
| } | |
| default: | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Internal error: Unhandled operand type: " << type; | |
| } | |
| operand_.num_words = uint16_t(inst_words_.size() - first_word_index); | |
| assert(spvOperandIsConcrete(operand_.type)); | |
| parsed_operands_.push_back(operand_); | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t MarkvDecoder::DecodeInstruction() { | |
| parsed_operands_.clear(); | |
| inst_words_.clear(); | |
| // Opcode/num_words placeholder, the word will be filled in later. | |
| inst_words_.push_back(0); | |
| bool num_operands_still_unknown = true; | |
| { | |
| uint32_t opcode = 0; | |
| uint32_t num_operands = 0; | |
| const spv_result_t opcode_decoding_result = | |
| DecodeOpcodeAndNumberOfOperands(&opcode, &num_operands); | |
| if (opcode_decoding_result < 0) return opcode_decoding_result; | |
| if (opcode_decoding_result == SPV_SUCCESS) { | |
| inst_.num_operands = static_cast<uint16_t>(num_operands); | |
| num_operands_still_unknown = false; | |
| } else { | |
| if (!reader_.ReadVariableWidthU32(&opcode, | |
| model_->opcode_chunk_length())) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to read opcode of instruction"; | |
| } | |
| } | |
| inst_.opcode = static_cast<uint16_t>(opcode); | |
| } | |
| const SpvOp opcode = static_cast<SpvOp>(inst_.opcode); | |
| spv_opcode_desc opcode_desc; | |
| if (grammar_.lookupOpcode(opcode, &opcode_desc) != SPV_SUCCESS) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) << "Invalid opcode"; | |
| } | |
| spv_operand_pattern_t expected_operands; | |
| expected_operands.reserve(opcode_desc->numTypes); | |
| for (auto i = 0; i < opcode_desc->numTypes; i++) { | |
| expected_operands.push_back( | |
| opcode_desc->operandTypes[opcode_desc->numTypes - i - 1]); | |
| } | |
| if (num_operands_still_unknown) { | |
| if (!OpcodeHasFixedNumberOfOperands(opcode)) { | |
| if (!reader_.ReadVariableWidthU16(&inst_.num_operands, | |
| model_->num_operands_chunk_length())) | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Failed to read num_operands of instruction"; | |
| } else { | |
| inst_.num_operands = static_cast<uint16_t>(expected_operands.size()); | |
| } | |
| } | |
| for (operand_index_ = 0; | |
| operand_index_ < static_cast<size_t>(inst_.num_operands); | |
| ++operand_index_) { | |
| assert(!expected_operands.empty()); | |
| const spv_operand_type_t type = | |
| spvTakeFirstMatchableOperand(&expected_operands); | |
| const size_t operand_offset = inst_words_.size(); | |
| const spv_result_t decode_result = | |
| DecodeOperand(operand_offset, type, &expected_operands); | |
| if (decode_result != SPV_SUCCESS) return decode_result; | |
| } | |
| assert(inst_.num_operands == parsed_operands_.size()); | |
| // Only valid while inst_words_ and parsed_operands_ remain unchanged (until | |
| // next DecodeInstruction call). | |
| inst_.words = inst_words_.data(); | |
| inst_.operands = parsed_operands_.empty() ? nullptr : parsed_operands_.data(); | |
| inst_.num_words = static_cast<uint16_t>(inst_words_.size()); | |
| inst_words_[0] = spvOpcodeMake(inst_.num_words, SpvOp(inst_.opcode)); | |
| std::copy(inst_words_.begin(), inst_words_.end(), std::back_inserter(spirv_)); | |
| assert(inst_.num_words == | |
| std::accumulate( | |
| parsed_operands_.begin(), parsed_operands_.end(), 1, | |
| [](int num_words, const spv_parsed_operand_t& operand) { | |
| return num_words += operand.num_words; | |
| }) && | |
| "num_words in instruction doesn't correspond to the sum of num_words" | |
| "in the operands"); | |
| RecordNumberType(); | |
| ProcessCurInstruction(); | |
| if (!ReadToByteBreak(MarkvCodec::kByteBreakAfterInstIfLessThanUntilNextByte)) | |
| return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read to byte break"; | |
| if (logger_) { | |
| logger_->NewLine(); | |
| std::stringstream ss; | |
| ss << spvOpcodeString(opcode) << " "; | |
| for (size_t index = 1; index < inst_words_.size(); ++index) | |
| ss << inst_words_[index] << " "; | |
| logger_->AppendText(ss.str()); | |
| logger_->NewLine(); | |
| logger_->NewLine(); | |
| if (!logger_->DebugInstruction(inst_)) return SPV_REQUESTED_TERMINATION; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t MarkvDecoder::SetNumericTypeInfoForType( | |
| spv_parsed_operand_t* parsed_operand, uint32_t type_id) { | |
| assert(type_id != 0); | |
| auto type_info_iter = type_id_to_number_type_info_.find(type_id); | |
| if (type_info_iter == type_id_to_number_type_info_.end()) { | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Type Id " << type_id << " is not a type"; | |
| } | |
| const NumberType& info = type_info_iter->second; | |
| if (info.type == SPV_NUMBER_NONE) { | |
| // This is a valid type, but for something other than a scalar number. | |
| return Diag(SPV_ERROR_INVALID_BINARY) | |
| << "Type Id " << type_id << " is not a scalar numeric type"; | |
| } | |
| parsed_operand->number_kind = info.type; | |
| parsed_operand->number_bit_width = info.bit_width; | |
| // Round up the word count. | |
| parsed_operand->num_words = static_cast<uint16_t>((info.bit_width + 31) / 32); | |
| return SPV_SUCCESS; | |
| } | |
| void MarkvDecoder::RecordNumberType() { | |
| const SpvOp opcode = static_cast<SpvOp>(inst_.opcode); | |
| if (spvOpcodeGeneratesType(opcode)) { | |
| NumberType info = {SPV_NUMBER_NONE, 0}; | |
| if (SpvOpTypeInt == opcode) { | |
| info.bit_width = inst_.words[inst_.operands[1].offset]; | |
| info.type = inst_.words[inst_.operands[2].offset] | |
| ? SPV_NUMBER_SIGNED_INT | |
| : SPV_NUMBER_UNSIGNED_INT; | |
| } else if (SpvOpTypeFloat == opcode) { | |
| info.bit_width = inst_.words[inst_.operands[1].offset]; | |
| info.type = SPV_NUMBER_FLOATING; | |
| } | |
| // The *result* Id of a type generating instruction is the type Id. | |
| type_id_to_number_type_info_[inst_.result_id] = info; | |
| } | |
| } | |
| } // namespace comp | |
| } // namespace spvtools |