// 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/bit_stream.h" #include "source/comp/markv.h" #include "source/comp/markv_codec.h" #include "source/comp/markv_logger.h" #include "source/util/make_unique.h" #ifndef SOURCE_COMP_MARKV_ENCODER_H_ #define SOURCE_COMP_MARKV_ENCODER_H_ #include namespace spvtools { namespace comp { // SPIR-V to MARK-V encoder. Exposes functions EncodeHeader and // EncodeInstruction which can be used as callback by spvBinaryParse. // Encoded binary is written to an internally maintained bitstream. // After the last instruction is encoded, the resulting MARK-V binary can be // acquired by calling GetMarkvBinary(). // // The encoder uses SPIR-V validator to keep internal state, therefore // SPIR-V binary needs to be able to pass validator checks. // CreateCommentsLogger() can be used to enable the encoder to write comments // on how encoding was done, which can later be accessed with GetComments(). class MarkvEncoder : public MarkvCodec { public: // |model| is owned by the caller, must be not null and valid during the // lifetime of MarkvEncoder. MarkvEncoder(spv_const_context context, const MarkvCodecOptions& options, const MarkvModel* model) : MarkvCodec(context, GetValidatorOptions(options), model), options_(options) {} ~MarkvEncoder() override = default; // Writes data from SPIR-V header to MARK-V header. spv_result_t EncodeHeader(spv_endianness_t /* endian */, uint32_t /* magic */, uint32_t version, uint32_t generator, uint32_t id_bound, uint32_t /* schema */) { SetIdBound(id_bound); header_.spirv_version = version; header_.spirv_generator = generator; return SPV_SUCCESS; } // Creates an internal logger which writes comments on the encoding process. void CreateLogger(MarkvLogConsumer log_consumer, MarkvDebugConsumer debug_consumer) { logger_ = MakeUnique(log_consumer, debug_consumer); writer_.SetCallback( [this](const std::string& str) { logger_->AppendBitSequence(str); }); } // Encodes SPIR-V instruction to MARK-V and writes to bit stream. // Operation can fail if the instruction fails to pass the validator or if // the encoder stubmles on something unexpected. spv_result_t EncodeInstruction(const spv_parsed_instruction_t& inst); // Concatenates MARK-V header and the bit stream with encoded instructions // into a single buffer and returns it as spv_markv_binary. The returned // value is owned by the caller and needs to be destroyed with // spvMarkvBinaryDestroy(). std::vector GetMarkvBinary() { header_.markv_length_in_bits = static_cast(sizeof(header_) * 8 + writer_.GetNumBits()); header_.markv_model = (model_->model_type() << 16) | model_->model_version(); const size_t num_bytes = sizeof(header_) + writer_.GetDataSizeBytes(); std::vector markv(num_bytes); assert(writer_.GetData()); std::memcpy(markv.data(), &header_, sizeof(header_)); std::memcpy(markv.data() + sizeof(header_), writer_.GetData(), writer_.GetDataSizeBytes()); return markv; } // Optionally adds disassembly to the comments. // Disassembly should contain all instructions in the module separated by // \n, and no header. void SetDisassembly(std::string&& disassembly) { disassembly_ = MakeUnique(std::move(disassembly)); } // Extracts the next instruction line from the disassembly and logs it. void LogDisassemblyInstruction() { if (logger_ && disassembly_) { std::string line; std::getline(*disassembly_, line, '\n'); logger_->AppendTextNewLine(line); } } private: // Creates and returns validator options. Returned value owned by the caller. static spv_validator_options GetValidatorOptions( const MarkvCodecOptions& options) { return options.validate_spirv_binary ? spvValidatorOptionsCreate() : nullptr; } // Writes a single word to bit stream. operand_.type determines if the word is // encoded and how. spv_result_t EncodeNonIdWord(uint32_t word); // Writes both opcode and num_operands as a single code. // Returns SPV_UNSUPPORTED iff no suitable codec was found. spv_result_t EncodeOpcodeAndNumOperands(uint32_t opcode, uint32_t num_operands); // Writes mtf rank to bit stream. |mtf| is used to determine the codec // scheme. |fallback_method| is used if no codec defined for |mtf|. spv_result_t EncodeMtfRankHuffman(uint32_t rank, uint64_t mtf, uint64_t fallback_method); // Writes id using coding based on mtf associated with the id descriptor. // Returns SPV_UNSUPPORTED iff fallback method needs to be used. spv_result_t EncodeIdWithDescriptor(uint32_t id); // Writes id using coding based on the given |mtf|, which is expected to // contain the given |id|. spv_result_t EncodeExistingId(uint64_t mtf, uint32_t id); // Writes type id of the current instruction if can't be inferred. spv_result_t EncodeTypeId(); // Writes result id of the current instruction if can't be inferred. spv_result_t EncodeResultId(); // Writes ids which are neither type nor result ids. spv_result_t EncodeRefId(uint32_t id); // Writes bits to the stream until the beginning of the next byte if the // number of bits until the next byte is less than |byte_break_if_less_than|. void AddByteBreak(size_t byte_break_if_less_than); // Encodes a literal number operand and writes it to the bit stream. spv_result_t EncodeLiteralNumber(const spv_parsed_operand_t& operand); MarkvCodecOptions options_; // Bit stream where encoded instructions are written. BitWriterWord64 writer_; // If not nullptr, disassembled instruction lines will be written to comments. // Format: \n separated instruction lines, no header. std::unique_ptr disassembly_; }; } // namespace comp } // namespace spvtools #endif // SOURCE_COMP_MARKV_ENCODER_H_