// Copyright (c) 2015 The Khronos Group Inc. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and/or associated documentation files (the // "Materials"), to deal in the Materials without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Materials, and to // permit persons to whom the Materials are furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Materials. // // MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS // KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS // SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT // https://www.khronos.org/registry/ // // THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. #ifndef LIBSPIRV_TEXT_HANDLER_H_ #define LIBSPIRV_TEXT_HANDLER_H_ #include #include #include #include #include "diagnostic.h" #include "instruction.h" #include "libspirv/libspirv.h" #include "text.h" namespace libspirv { // Structures // This is a lattice for tracking types. enum class IdTypeClass { kBottom = 0, // We have no information yet. kScalarIntegerType, kScalarFloatType, kOtherType }; // Contains ID type information that needs to be tracked across all Ids. // Bitwidth is only valid when type_class is kScalarIntegerType or // kScalarFloatType. struct IdType { uint32_t bitwidth; // Safe to assume that we will not have > 2^32 bits. bool isSigned; // This is only significant if type_class is integral. IdTypeClass type_class; }; // Default equality operator for IdType. Tests if all members are the same. inline bool operator==(const IdType& first, const IdType& second) { return (first.bitwidth == second.bitwidth) && (first.isSigned == second.isSigned) && (first.type_class == second.type_class); } // Tests whether any member of the IdTypes do not match. inline bool operator!=(const IdType& first, const IdType& second) { return !(first == second); } // A value representing an unknown type. extern const IdType kUnknownType; // Returns true if the type is a scalar integer type. inline bool isScalarIntegral(const IdType& type) { return type.type_class == IdTypeClass::kScalarIntegerType; } // Returns true if the type is a scalar floating point type. inline bool isScalarFloating(const IdType& type) { return type.type_class == IdTypeClass::kScalarFloatType; } // Returns the number of bits in the type. // This is only valid for bottom, scalar integer, and scalar floating // classes. For bottom, assume 32 bits. inline int assumedBitWidth(const IdType& type) { switch (type.type_class) { case IdTypeClass::kBottom: return 32; case IdTypeClass::kScalarIntegerType: case IdTypeClass::kScalarFloatType: return type.bitwidth; default: break; } // We don't care about this case. return 0; } // A templated class with a static member function Clamp, where Clamp // sets a referenced value of type T to 0 if T is an unsigned // integer type, and returns true if it modified the referenced // value. template class ClampToZeroIfUnsignedType { public: // The default specialization does not clamp the value. static bool Clamp(T*) { return false; } }; // The specialization of ClampToZeroIfUnsignedType for unsigned integer // types. template class ClampToZeroIfUnsignedType< T, typename std::enable_if::value>::type> { public: static bool Clamp(T* value_pointer) { if (*value_pointer) { *value_pointer = 0; return true; } return false; } }; // Encapsulates the data used during the assembly of a SPIR-V module. class AssemblyContext { public: AssemblyContext(spv_text text, spv_diagnostic* diagnostic) : current_position_({}), pDiagnostic_(diagnostic), text_(text), bound_(1) {} // Assigns a new integer value to the given text ID, or returns the previously // assigned integer value if the ID has been seen before. uint32_t spvNamedIdAssignOrGet(const char* textValue); // Returns the largest largest numeric ID that has been assigned. uint32_t getBound() const; // Advances position to point to the next word in the input stream. // Returns SPV_SUCCESS on success. spv_result_t advance(); // Sets word to the next word in the input text. Fills endPosition with // the next location past the end of the word. spv_result_t getWord(std::string& word, spv_position endPosition); // Returns the next word in the input stream. It is invalid to call this // method if position has been set to a location in the stream that does not // exist. If there are no subsequent words, the empty string will be returend. std::string getWord() const; // Returns true if the next word in the input is the start of a new Opcode. bool startsWithOp(); // Returns true if the next word in the input is the start of a new // instruction. bool isStartOfNewInst(); // Returns a diagnostic object initialized with current position in the input // stream, and for the given error code. Any data written to this object will // show up in pDiagnsotic on destruction. DiagnosticStream diagnostic(spv_result_t error) { return DiagnosticStream(current_position_, pDiagnostic_, error); } // Returns a diagnostic object with the default assembly error code. DiagnosticStream diagnostic() { // The default failure for assembly is invalid text. return diagnostic(SPV_ERROR_INVALID_TEXT); } // Returns then next characted in the input stream. char peek() const; // Returns true if there is more text in the input stream. bool hasText() const; // Seeks the input stream forward by 'size' characters. void seekForward(uint32_t size); // Sets the current position in the input stream to the given position. void setPosition(const spv_position_t& newPosition) { current_position_ = newPosition; } // Returns the current position in the input stream. const spv_position_t& position() const { return current_position_; } // Appends the given 32-bit value to the given instruction. // Returns SPV_SUCCESS if the value could be correctly inserted in the // instruction. spv_result_t binaryEncodeU32(const uint32_t value, spv_instruction_t* pInst); // Appends the given string to the given instruction. // Returns SPV_SUCCESS if the value could be correctly inserted in the // instruction. spv_result_t binaryEncodeString(const char* value, spv_instruction_t* pInst); // Appends the given numeric literal to the given instruction. // Validates and respects the bitwidth supplied in the IdType argument. // If the type is of class kBottom the value will be encoded as a // 32-bit integer. // Returns SPV_SUCCESS if the value could be correctly added to the // instruction. Returns the given error code on failure, and emits // a diagnotic if that error code is not SPV_FAILED_MATCH. spv_result_t binaryEncodeNumericLiteral(const char* numeric_literal, spv_result_t error_code, const IdType& type, spv_instruction_t* pInst); // Returns the IdType associated with this type-generating value. // If the type has not been previously recorded with recordTypeDefinition, // kUnknownType will be returned. IdType getTypeOfTypeGeneratingValue(uint32_t value) const; // Returns the IdType that represents the return value of this Value // generating instruction. // If the value has not been recorded with recordTypeIdForValue, or the type // could not be determined kUnknownType will be returned. IdType getTypeOfValueInstruction(uint32_t value) const; // Tracks the type-defining instruction. The result of the tracking can // later be queried using getValueType. // pInst is expected to be completely filled in by the time this instruction // is called. // Returns SPV_SUCCESS on success, or SPV_ERROR_INVALID_VALUE on error. spv_result_t recordTypeDefinition(const spv_instruction_t* pInst); // Tracks the relationship between the value and its type. spv_result_t recordTypeIdForValue(uint32_t value, uint32_t type); // Records the given Id as being the import of the given extended instruction // type. spv_result_t recordIdAsExtInstImport(uint32_t id, spv_ext_inst_type_t type); // Returns the extended instruction type corresponding to the import with // the given Id, if it exists. Returns SPV_EXT_INST_TYPE_NONE if the // id is not the id for an extended instruction type. spv_ext_inst_type_t getExtInstTypeForId(uint32_t id) const; // Parses a numeric value of a given type from the given text. The number // should take up the entire string, and should be within bounds for the // target type. On success, returns SPV_SUCCESS and populates the object // referenced by value_pointer. On failure, returns the given error code, // and emits a diagnostic if that error code is not SPV_FAILED_MATCH. template spv_result_t parseNumber(const char* text, spv_result_t error_code, T* value_pointer, const char* error_message_fragment) { // C++11 doesn't define std::istringstream(int8_t&), so calling this method // with a single-byte type leads to implementation-defined behaviour. // Similarly for uint8_t. static_assert(sizeof(T) > 1, "Don't use a single-byte type this parse method"); std::istringstream text_stream(text); // Allow both decimal and hex input for integers. // It also allows octal input, but we don't care about that case. text_stream >> std::setbase(0); text_stream >> *value_pointer; bool ok = true; // We should have read something. ok = (text[0] != 0) && !text_stream.bad(); // It should have been all the text. ok = ok && text_stream.eof(); // It should have been in range. ok = ok && !text_stream.fail(); // Work around a bug in the GNU C++11 library. It will happily parse // "-1" for uint16_t as 65535. if (ok && text[0] == '-') ok = !ClampToZeroIfUnsignedType::Clamp(value_pointer); if (ok) return SPV_SUCCESS; return diagnostic(error_code) << error_message_fragment << text; } private: // Appends the given floating point literal to the given instruction. // Returns SPV_SUCCESS if the value was correctly parsed. Otherwise // returns the given error code, and emits a diagnostic if that error // code is not SPV_FAILED_MATCH. // Only 32 and 64 bit floating point numbers are supported. spv_result_t binaryEncodeFloatingPointLiteral(const char* numeric_literal, spv_result_t error_code, const IdType& type, spv_instruction_t* pInst); // Appends the given integer literal to the given instruction. // Returns SPV_SUCCESS if the value was correctly parsed. Otherwise // returns the given error code, and emits a diagnostic if that error // code is not SPV_FAILED_MATCH. // Integers up to 64 bits are supported. spv_result_t binaryEncodeIntegerLiteral(const char* numeric_literal, spv_result_t error_code, const IdType& type, spv_instruction_t* pInst); // Returns SPV_SUCCESS if the given value fits within the target scalar // integral type. The target type may have an unusual bit width. // If the value was originally specified as a hexadecimal number, then // the overflow bits should be zero. If it was hex and the target type is // signed, then return the sign-extended value through the // updated_value_for_hex pointer argument. // On failure, return the given error code and emit a diagnostic if that error // code is not SPV_FAILED_MATCH. template spv_result_t checkRangeAndIfHexThenSignExtend(T value, spv_result_t error_code, const IdType& type, bool is_hex, T* updated_value_for_hex); // Writes the given 64-bit literal value into the instruction. // return SPV_SUCCESS if the value could be written in the instruction. spv_result_t binaryEncodeU64(const uint64_t value, spv_instruction_t* pInst); // Maps ID names to their corresponding numerical ids. using spv_named_id_table = std::unordered_map; // Maps type-defining IDs to their IdType. using spv_id_to_type_map = std::unordered_map; // Maps Ids to the id of their type. using spv_id_to_type_id = std::unordered_map; spv_named_id_table named_ids_; spv_id_to_type_map types_; spv_id_to_type_id value_types_; // Maps an extended instruction import Id to the extended instruction type. std::unordered_map import_id_to_ext_inst_type_; spv_position_t current_position_; spv_diagnostic* pDiagnostic_; spv_text text_; uint32_t bound_; }; } #endif // _LIBSPIRV_TEXT_HANDLER_H_