// Copyright (c) 2016 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. #ifndef LIBSPIRV_UTIL_PARSE_NUMBER_H_ #define LIBSPIRV_UTIL_PARSE_NUMBER_H_ #include #include #include #include "spirv-tools/libspirv.h" #include "util/hex_float.h" namespace spvutils { // A struct to hold the expected type information for the number in text to be // parsed. struct NumberType { uint32_t bitwidth; // SPV_NUMBER_NONE means the type is unknown and is invalid to be used with // ParseAndEncode{|Integer|Floating}Number(). spv_number_kind_t kind; }; // Returns true if the type is a scalar integer type. inline bool IsIntegral(const NumberType& type) { return type.kind == SPV_NUMBER_UNSIGNED_INT || type.kind == SPV_NUMBER_SIGNED_INT; } // Returns true if the type is a scalar floating point type. inline bool IsFloating(const NumberType& type) { return type.kind == SPV_NUMBER_FLOATING; } // Returns true if the type is a signed value. inline bool IsSigned(const NumberType& type) { return type.kind == SPV_NUMBER_FLOATING || type.kind == SPV_NUMBER_SIGNED_INT; } // Returns true if the type is unknown. inline bool IsUnknown(const NumberType& type) { return type.kind == SPV_NUMBER_NONE; } // Returns the number of bits in the type. This is only valid for integer and // floating types. inline int AssumedBitWidth(const NumberType& type) { switch (type.kind) { case SPV_NUMBER_SIGNED_INT: case SPV_NUMBER_UNSIGNED_INT: case SPV_NUMBER_FLOATING: 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; } }; // Returns true 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, // returns false. template bool CheckRangeAndIfHexThenSignExtend(T value, const NumberType& type, bool is_hex, T* updated_value_for_hex) { // The encoded result has three regions of bits that are of interest, from // least to most significant: // - magnitude bits, where the magnitude of the number would be stored if // we were using a signed-magnitude representation. // - an optional sign bit // - overflow bits, up to bit 63 of a 64-bit number // For example: // Type Overflow Sign Magnitude // --------------- -------- ---- --------- // unsigned 8 bit 8-63 n/a 0-7 // signed 8 bit 8-63 7 0-6 // unsigned 16 bit 16-63 n/a 0-15 // signed 16 bit 16-63 15 0-14 // We'll use masks to define the three regions. // At first we'll assume the number is unsigned. const uint32_t bit_width = AssumedBitWidth(type); uint64_t magnitude_mask = (bit_width == 64) ? -1 : ((uint64_t(1) << bit_width) - 1); uint64_t sign_mask = 0; uint64_t overflow_mask = ~magnitude_mask; if (value < 0 || IsSigned(type)) { // Accommodate the sign bit. magnitude_mask >>= 1; sign_mask = magnitude_mask + 1; } bool failed = false; if (value < 0) { // The top bits must all be 1 for a negative signed value. failed = ((value & overflow_mask) != overflow_mask) || ((value & sign_mask) != sign_mask); } else { if (is_hex) { // Hex values are a bit special. They decode as unsigned values, but may // represent a negative number. In this case, the overflow bits should // be zero. failed = (value & overflow_mask) != 0; } else { const uint64_t value_as_u64 = static_cast(value); // Check overflow in the ordinary case. failed = (value_as_u64 & magnitude_mask) != value_as_u64; } } if (failed) { return false; } // Sign extend hex the number. if (is_hex && (value & sign_mask)) *updated_value_for_hex = (value | overflow_mask); return true; } // 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 true and populates the object referenced by // value_pointer. On failure, returns false. template bool ParseNumber(const char* text, T* value_pointer) { // 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; // We should have read something. bool 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); return ok; } // Enum to indicate the parsing and encoding status. enum class EncodeNumberStatus { kSuccess = 0, // Unsupported bit width etc. kUnsupported, // Expected type (NumberType) is not a scalar int or float, or putting a // negative number in an unsigned literal. kInvalidUsage, // Number value does not fit the bit width of the expected type etc. kInvalidText, }; // Parses an integer value of a given |type| from the given |text| and encodes // the number by the given |emit| function. On success, returns // EncodeNumberStatus::kSuccess and the parsed number will be consumed by the // given |emit| function word by word (least significant word first). On // failure, this function returns the error code of the encoding status and // |emit| function will not be called. If the string pointer |error_msg| is not // a nullptr, it will be overwritten with error messages in case of failure. In // case of success, |error_msg| will not be touched. Integers up to 64 bits are // supported. EncodeNumberStatus ParseAndEncodeIntegerNumber( const char* text, const NumberType& type, std::function emit, std::string* error_msg); // Parses a floating point value of a given |type| from the given |text| and // encodes the number by the given |emit| funciton. On success, returns // EncodeNumberStatus::kSuccess and the parsed number will be consumed by the // given |emit| function word by word (least significant word first). On // failure, this function returns the error code of the encoding status and // |emit| function will not be called. If the string pointer |error_msg| is not // a nullptr, it will be overwritten with error messages in case of failure. In // case of success, |error_msg| will not be touched. Only 16, 32 and 64 bit // floating point numbers are supported. EncodeNumberStatus ParseAndEncodeFloatingPointNumber( const char* text, const NumberType& type, std::function emit, std::string* error_msg); // Parses an integer or floating point number of a given |type| from the given // |text| and encodes the number by the given |emit| function. On success, // returns EncodeNumberStatus::kSuccess and the parsed number will be consumed // by the given |emit| function word by word (least significant word first). On // failure, this function returns the error code of the encoding status and // |emit| function will not be called. If the string pointer |error_msg| is not // a nullptr, it will be overwritten with error messages in case of failure. In // case of success, |error_msg| will not be touched. Integers up to 64 bits // and 16/32/64 bit floating point values are supported. EncodeNumberStatus ParseAndEncodeNumber(const char* text, const NumberType& type, std::function emit, std::string* error_msg); } // namespace spvutils #endif // LIBSPIRV_UTIL_PARSE_NUMBER_H_