| /*------------------------------------------------------------------------ |
| * Vulkan Conformance Tests |
| * ------------------------ |
| * |
| * Copyright (c) 2018 The Khronos Group Inc. |
| * Copyright (c) 2015 Samsung Electronics Co., Ltd. |
| * Copyright (c) 2016 The Android Open Source Project |
| * |
| * 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. |
| * |
| *//*! |
| * \file |
| * \brief Precision and range tests for builtins and types. |
| * |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "vktShaderBuiltinPrecisionTests.hpp" |
| #include "vktShaderExecutor.hpp" |
| #include "amber/vktAmberTestCase.hpp" |
| |
| #include "deMath.h" |
| #include "deMemory.h" |
| #include "deFloat16.h" |
| #include "deDefs.hpp" |
| #include "deRandom.hpp" |
| #include "deSTLUtil.hpp" |
| #include "deStringUtil.hpp" |
| #include "deUniquePtr.hpp" |
| #include "deSharedPtr.hpp" |
| #include "deArrayUtil.hpp" |
| |
| #include "tcuCommandLine.hpp" |
| #include "tcuFloatFormat.hpp" |
| #include "tcuInterval.hpp" |
| #include "tcuTestLog.hpp" |
| #include "tcuVector.hpp" |
| #include "tcuMatrix.hpp" |
| #include "tcuResultCollector.hpp" |
| #include "tcuMaybe.hpp" |
| |
| #include "gluContextInfo.hpp" |
| #include "gluVarType.hpp" |
| #include "gluRenderContext.hpp" |
| #include "glwDefs.hpp" |
| |
| #include <cmath> |
| #include <string> |
| #include <sstream> |
| #include <iostream> |
| #include <map> |
| #include <utility> |
| #include <limits> |
| |
| // Uncomment this to get evaluation trace dumps to std::cerr |
| // #define GLS_ENABLE_TRACE |
| |
| // set this to true to dump even passing results |
| #define GLS_LOG_ALL_RESULTS false |
| |
| #define FLOAT16_1_0 0x3C00 //1.0 float16bit |
| #define FLOAT16_180_0 0x59A0 //180.0 float16bit |
| #define FLOAT16_2_0 0x4000 //2.0 float16bit |
| #define FLOAT16_3_0 0x4200 //3.0 float16bit |
| #define FLOAT16_0_5 0x3800 //0.5 float16bit |
| #define FLOAT16_0_0 0x0000 //0.0 float16bit |
| |
| |
| using tcu::Vector; |
| typedef Vector<deFloat16, 1> Vec1_16Bit; |
| typedef Vector<deFloat16, 2> Vec2_16Bit; |
| typedef Vector<deFloat16, 3> Vec3_16Bit; |
| typedef Vector<deFloat16, 4> Vec4_16Bit; |
| |
| typedef Vector<double, 1> Vec1_64Bit; |
| typedef Vector<double, 2> Vec2_64Bit; |
| typedef Vector<double, 3> Vec3_64Bit; |
| typedef Vector<double, 4> Vec4_64Bit; |
| |
| enum |
| { |
| // Computing reference intervals can take a non-trivial amount of time, especially on |
| // platforms where toggling floating-point rounding mode is slow (emulated arm on x86). |
| // As a workaround watchdog is kept happy by touching it periodically during reference |
| // interval computation. |
| TOUCH_WATCHDOG_VALUE_FREQUENCY = 512 |
| }; |
| |
| namespace vkt |
| { |
| namespace shaderexecutor |
| { |
| |
| using std::string; |
| using std::map; |
| using std::ostream; |
| using std::ostringstream; |
| using std::pair; |
| using std::vector; |
| using std::set; |
| |
| using de::MovePtr; |
| using de::Random; |
| using de::SharedPtr; |
| using de::UniquePtr; |
| using tcu::Interval; |
| using tcu::FloatFormat; |
| using tcu::MessageBuilder; |
| using tcu::TestLog; |
| using tcu::Vector; |
| using tcu::Matrix; |
| using glu::Precision; |
| using glu::VarType; |
| using glu::DataType; |
| using glu::ShaderType; |
| |
| enum PrecisionTestFeatureBits |
| { |
| PRECISION_TEST_FEATURES_NONE = 0u, |
| PRECISION_TEST_FEATURES_16BIT_BUFFER_ACCESS = (1u << 1), |
| PRECISION_TEST_FEATURES_16BIT_UNIFORM_AND_STORAGE_BUFFER_ACCESS = (1u << 2), |
| PRECISION_TEST_FEATURES_16BIT_PUSH_CONSTANT = (1u << 3), |
| PRECISION_TEST_FEATURES_16BIT_INPUT_OUTPUT = (1u << 4), |
| PRECISION_TEST_FEATURES_16BIT_SHADER_FLOAT = (1u << 5), |
| PRECISION_TEST_FEATURES_64BIT_SHADER_FLOAT = (1u << 6), |
| }; |
| typedef deUint32 PrecisionTestFeatures; |
| |
| |
| void areFeaturesSupported (const Context& context, deUint32 toCheck) |
| { |
| if (toCheck == PRECISION_TEST_FEATURES_NONE) return; |
| |
| const vk::VkPhysicalDevice16BitStorageFeatures& extensionFeatures = context.get16BitStorageFeatures(); |
| |
| if ((toCheck & PRECISION_TEST_FEATURES_16BIT_BUFFER_ACCESS) != 0 && extensionFeatures.storageBuffer16BitAccess == VK_FALSE) |
| TCU_THROW(NotSupportedError, "Requested 16bit storage features not supported"); |
| |
| if ((toCheck & PRECISION_TEST_FEATURES_16BIT_UNIFORM_AND_STORAGE_BUFFER_ACCESS) != 0 && extensionFeatures.uniformAndStorageBuffer16BitAccess == VK_FALSE) |
| TCU_THROW(NotSupportedError, "Requested 16bit storage features not supported"); |
| |
| if ((toCheck & PRECISION_TEST_FEATURES_16BIT_PUSH_CONSTANT) != 0 && extensionFeatures.storagePushConstant16 == VK_FALSE) |
| TCU_THROW(NotSupportedError, "Requested 16bit storage features not supported"); |
| |
| if ((toCheck & PRECISION_TEST_FEATURES_16BIT_INPUT_OUTPUT) != 0 && extensionFeatures.storageInputOutput16 == VK_FALSE) |
| TCU_THROW(NotSupportedError, "Requested 16bit storage features not supported"); |
| |
| if ((toCheck & PRECISION_TEST_FEATURES_16BIT_SHADER_FLOAT) != 0 && context.getShaderFloat16Int8Features().shaderFloat16 == VK_FALSE) |
| TCU_THROW(NotSupportedError, "Requested 16-bit floats (halfs) are not supported in shader code"); |
| |
| if ((toCheck & PRECISION_TEST_FEATURES_64BIT_SHADER_FLOAT) != 0 && context.getDeviceFeatures().shaderFloat64 == VK_FALSE) |
| TCU_THROW(NotSupportedError, "Requested 64-bit floats are not supported in shader code"); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Generic singleton creator. |
| * |
| * instance<T>() returns a reference to a unique default-constructed instance |
| * of T. This is mainly used for our GLSL function implementations: each |
| * function is implemented by an object, and each of the objects has a |
| * distinct class. It would be extremely toilsome to maintain a separate |
| * context object that contained individual instances of the function classes, |
| * so we have to resort to global singleton instances. |
| * |
| *//*--------------------------------------------------------------------*/ |
| template <typename T> |
| const T& instance (void) |
| { |
| static const T s_instance = T(); |
| return s_instance; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Empty placeholder type for unused template parameters. |
| * |
| * In the precision tests we are dealing with functions of different arities. |
| * To minimize code duplication, we only define templates with the maximum |
| * number of arguments, currently four. If a function's arity is less than the |
| * maximum, Void us used as the type for unused arguments. |
| * |
| * Although Voids are not used at run-time, they still must be compilable, so |
| * they must support all operations that other types do. |
| * |
| *//*--------------------------------------------------------------------*/ |
| struct Void |
| { |
| typedef Void Element; |
| enum |
| { |
| SIZE = 0, |
| }; |
| |
| template <typename T> |
| explicit Void (const T&) {} |
| Void (void) {} |
| operator double (void) const { return TCU_NAN; } |
| |
| // These are used to make Voids usable as containers in container-generic code. |
| Void& operator[] (int) { return *this; } |
| const Void& operator[] (int) const { return *this; } |
| }; |
| |
| ostream& operator<< (ostream& os, Void) { return os << "()"; } |
| |
| //! Returns true for all other types except Void |
| template <typename T> bool isTypeValid (void) { return true; } |
| template <> bool isTypeValid<Void> (void) { return false; } |
| |
| template <typename T> bool isInteger (void) { return false; } |
| template <> bool isInteger<int> (void) { return true; } |
| template <> bool isInteger<tcu::IVec2> (void) { return true; } |
| template <> bool isInteger<tcu::IVec3> (void) { return true; } |
| template <> bool isInteger<tcu::IVec4> (void) { return true; } |
| |
| //! Utility function for getting the name of a data type. |
| //! This is used in vector and matrix constructors. |
| template <typename T> |
| const char* dataTypeNameOf (void) |
| { |
| return glu::getDataTypeName(glu::dataTypeOf<T>()); |
| } |
| |
| template <> |
| const char* dataTypeNameOf<Void> (void) |
| { |
| DE_FATAL("Impossible"); |
| return DE_NULL; |
| } |
| |
| template <typename T> |
| VarType getVarTypeOf (Precision prec = glu::PRECISION_LAST) |
| { |
| return glu::varTypeOf<T>(prec); |
| } |
| |
| //! A hack to get Void support for VarType. |
| template <> |
| VarType getVarTypeOf<Void> (Precision) |
| { |
| DE_FATAL("Impossible"); |
| return VarType(); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Type traits for generalized interval types. |
| * |
| * We are trying to compute sets of acceptable values not only for |
| * float-valued expressions but also for compound values: vectors and |
| * matrices. We approximate a set of vectors as a vector of intervals and |
| * likewise for matrices. |
| * |
| * We now need generalized operations for each type and its interval |
| * approximation. These are given in the type Traits<T>. |
| * |
| * The type Traits<T>::IVal is the approximation of T: it is `Interval` for |
| * scalar types, and a vector or matrix of intervals for container types. |
| * |
| * To allow template inference to take place, there are function wrappers for |
| * the actual operations in Traits<T>. Hence we can just use: |
| * |
| * makeIVal(someFloat) |
| * |
| * instead of: |
| * |
| * Traits<float>::doMakeIVal(value) |
| * |
| *//*--------------------------------------------------------------------*/ |
| |
| template <typename T> struct Traits; |
| |
| //! Create container from elementwise singleton values. |
| template <typename T> |
| typename Traits<T>::IVal makeIVal (const T& value) |
| { |
| return Traits<T>::doMakeIVal(value); |
| } |
| |
| //! Elementwise union of intervals. |
| template <typename T> |
| typename Traits<T>::IVal unionIVal (const typename Traits<T>::IVal& a, |
| const typename Traits<T>::IVal& b) |
| { |
| return Traits<T>::doUnion(a, b); |
| } |
| |
| //! Returns true iff every element of `ival` contains the corresponding element of `value`. |
| template <typename T, typename U = Void> |
| bool contains (const typename Traits<T>::IVal& ival, const T& value, bool is16Bit = false, const tcu::Maybe<U>& modularDivisor = tcu::nothing<U>()) |
| { |
| return Traits<T>::doContains(ival, value, is16Bit, modularDivisor); |
| } |
| |
| //! Print out an interval with the precision of `fmt`. |
| template <typename T> |
| void printIVal (const FloatFormat& fmt, const typename Traits<T>::IVal& ival, ostream& os) |
| { |
| Traits<T>::doPrintIVal(fmt, ival, os); |
| } |
| |
| template <typename T> |
| string intervalToString (const FloatFormat& fmt, const typename Traits<T>::IVal& ival) |
| { |
| ostringstream oss; |
| printIVal<T>(fmt, ival, oss); |
| return oss.str(); |
| } |
| |
| //! Print out a value with the precision of `fmt`. |
| template <typename T> |
| void printValue16 (const FloatFormat& fmt, const T& value, ostream& os) |
| { |
| Traits<T>::doPrintValue16(fmt, value, os); |
| } |
| |
| template <typename T> |
| string value16ToString(const FloatFormat& fmt, const T& val) |
| { |
| ostringstream oss; |
| printValue16(fmt, val, oss); |
| return oss.str(); |
| } |
| |
| const std::string getComparisonOperation(const int ndx) |
| { |
| const int operationCount = 10; |
| DE_ASSERT(de::inBounds(ndx, 0, operationCount)); |
| const std::string operations[operationCount] = |
| { |
| "OpFOrdEqual\t\t\t", |
| "OpFOrdGreaterThan\t", |
| "OpFOrdLessThan\t\t", |
| "OpFOrdGreaterThanEqual", |
| "OpFOrdLessThanEqual\t", |
| "OpFUnordEqual\t\t", |
| "OpFUnordGreaterThan\t", |
| "OpFUnordLessThan\t", |
| "OpFUnordGreaterThanEqual", |
| "OpFUnordLessThanEqual" |
| }; |
| return operations[ndx]; |
| } |
| |
| template <typename T> |
| string comparisonMessage(const T& val) |
| { |
| DE_UNREF(val); |
| return ""; |
| } |
| |
| template <> |
| string comparisonMessage(const int& val) |
| { |
| ostringstream oss; |
| |
| int flags = val; |
| for(int ndx = 0; ndx < 10; ++ndx) |
| { |
| oss << getComparisonOperation(ndx) << "\t:\t" << ((flags & 1) == 1 ? "TRUE" : "FALSE") << "\n"; |
| flags = flags >> 1; |
| } |
| return oss.str(); |
| } |
| |
| template <> |
| string comparisonMessage(const tcu::IVec2& val) |
| { |
| ostringstream oss; |
| tcu::IVec2 flags = val; |
| for (int ndx = 0; ndx < 10; ++ndx) |
| { |
| oss << getComparisonOperation(ndx) << "\t:\t" << ((flags.x() & 1) == 1 ? "TRUE" : "FALSE") << "\t" << ((flags.y() & 1) == 1 ? "TRUE" : "FALSE") << "\n"; |
| flags.x() = flags.x() >> 1; |
| flags.y() = flags.y() >> 1; |
| } |
| return oss.str(); |
| } |
| |
| template <> |
| string comparisonMessage(const tcu::IVec3& val) |
| { |
| ostringstream oss; |
| tcu::IVec3 flags = val; |
| for (int ndx = 0; ndx < 10; ++ndx) |
| { |
| oss << getComparisonOperation(ndx) << "\t:\t" << ((flags.x() & 1) == 1 ? "TRUE" : "FALSE") << "\t" |
| << ((flags.y() & 1) == 1 ? "TRUE" : "FALSE") << "\t" |
| << ((flags.z() & 1) == 1 ? "TRUE" : "FALSE") << "\n"; |
| flags.x() = flags.x() >> 1; |
| flags.y() = flags.y() >> 1; |
| flags.z() = flags.z() >> 1; |
| } |
| return oss.str(); |
| } |
| |
| template <> |
| string comparisonMessage(const tcu::IVec4& val) |
| { |
| ostringstream oss; |
| tcu::IVec4 flags = val; |
| for (int ndx = 0; ndx < 10; ++ndx) |
| { |
| oss << getComparisonOperation(ndx) << "\t:\t" << ((flags.x() & 1) == 1 ? "TRUE" : "FALSE") << "\t" |
| << ((flags.y() & 1) == 1 ? "TRUE" : "FALSE") << "\t" |
| << ((flags.z() & 1) == 1 ? "TRUE" : "FALSE") << "\t" |
| << ((flags.w() & 1) == 1 ? "TRUE" : "FALSE") << "\n"; |
| flags.x() = flags.x() >> 1; |
| flags.y() = flags.y() >> 1; |
| flags.z() = flags.z() >> 1; |
| flags.w() = flags.z() >> 1; |
| } |
| return oss.str(); |
| } |
| //! Print out a value with the precision of `fmt`. |
| template <typename T> |
| void printValue32 (const FloatFormat& fmt, const T& value, ostream& os) |
| { |
| Traits<T>::doPrintValue32(fmt, value, os); |
| } |
| |
| template <typename T> |
| string value32ToString (const FloatFormat& fmt, const T& val) |
| { |
| ostringstream oss; |
| printValue32(fmt, val, oss); |
| return oss.str(); |
| } |
| |
| template <typename T> |
| void printValue64 (const FloatFormat& fmt, const T& value, ostream& os) |
| { |
| Traits<T>::doPrintValue64(fmt, value, os); |
| } |
| |
| template <typename T> |
| string value64ToString (const FloatFormat& fmt, const T& val) |
| { |
| ostringstream oss; |
| printValue64(fmt, val, oss); |
| return oss.str(); |
| } |
| |
| //! Approximate `value` elementwise to the float precision defined in `fmt`. |
| //! The resulting interval might not be a singleton if rounding in both |
| //! directions is allowed. |
| template <typename T> |
| typename Traits<T>::IVal round (const FloatFormat& fmt, const T& value) |
| { |
| return Traits<T>::doRound(fmt, value); |
| } |
| |
| template <typename T> |
| typename Traits<T>::IVal convert (const FloatFormat& fmt, |
| const typename Traits<T>::IVal& value) |
| { |
| return Traits<T>::doConvert(fmt, value); |
| } |
| |
| // Matching input and output types. We may be in a modulo case and modularDivisor may have an actual value. |
| template <typename T> |
| bool intervalContains (const Interval& interval, T value, const tcu::Maybe<T>& modularDivisor) |
| { |
| bool contained = interval.contains(value); |
| |
| if (!contained && modularDivisor) |
| { |
| const T divisor = modularDivisor.get(); |
| |
| // In a modulo operation, if the calculated answer contains the divisor, allow exactly 0.0 as a replacement. Alternatively, |
| // if the calculated answer contains 0.0, allow exactly the divisor as a replacement. |
| if (interval.contains(static_cast<double>(divisor))) |
| contained |= (value == 0.0); |
| if (interval.contains(0.0)) |
| contained |= (value == divisor); |
| } |
| return contained; |
| } |
| |
| // When the input and output types do not match, we are not in a real modulo operation. Do not take the divisor into account. This |
| // version is provided for syntactical compatibility only. |
| template <typename T, typename U> |
| bool intervalContains (const Interval& interval, T value, const tcu::Maybe<U>& modularDivisor) |
| { |
| DE_UNREF(modularDivisor); // For release builds. |
| DE_ASSERT(!modularDivisor); |
| return interval.contains(value); |
| } |
| |
| //! Common traits for scalar types. |
| template <typename T> |
| struct ScalarTraits |
| { |
| typedef Interval IVal; |
| |
| static Interval doMakeIVal (const T& value) |
| { |
| // Thankfully all scalar types have a well-defined conversion to `double`, |
| // hence Interval can represent their ranges without problems. |
| return Interval(double(value)); |
| } |
| |
| static Interval doUnion (const Interval& a, const Interval& b) |
| { |
| return a | b; |
| } |
| |
| static bool doContains (const Interval& a, T value) |
| { |
| return a.contains(double(value)); |
| } |
| |
| static Interval doConvert (const FloatFormat& fmt, const IVal& ival) |
| { |
| return fmt.convert(ival); |
| } |
| |
| static Interval doConvert (const FloatFormat& fmt, const IVal& ival, bool is16Bit) |
| { |
| DE_UNREF(is16Bit); |
| return fmt.convert(ival); |
| } |
| |
| static Interval doRound (const FloatFormat& fmt, T value) |
| { |
| return fmt.roundOut(double(value), false); |
| } |
| }; |
| |
| template <> |
| struct ScalarTraits<deUint16> |
| { |
| typedef Interval IVal; |
| |
| static Interval doMakeIVal (const deUint16& value) |
| { |
| // Thankfully all scalar types have a well-defined conversion to `double`, |
| // hence Interval can represent their ranges without problems. |
| return Interval(double(deFloat16To32(value))); |
| } |
| |
| static Interval doUnion (const Interval& a, const Interval& b) |
| { |
| return a | b; |
| } |
| |
| static Interval doConvert (const FloatFormat& fmt, const IVal& ival) |
| { |
| return fmt.convert(ival); |
| } |
| |
| static Interval doRound (const FloatFormat& fmt, deUint16 value) |
| { |
| return fmt.roundOut(double(deFloat16To32(value)), false); |
| } |
| }; |
| |
| template<> |
| struct Traits<float> : ScalarTraits<float> |
| { |
| static void doPrintIVal (const FloatFormat& fmt, |
| const Interval& ival, |
| ostream& os) |
| { |
| os << fmt.intervalToHex(ival); |
| } |
| |
| static void doPrintValue16 (const FloatFormat& fmt, |
| const float& value, |
| ostream& os) |
| { |
| const deUint32 iRep = reinterpret_cast<const deUint32 & >(value); |
| float res0 = deFloat16To32((deFloat16)(iRep & 0xFFFF)); |
| float res1 = deFloat16To32((deFloat16)(iRep >> 16)); |
| os << fmt.floatToHex(res0) << " " << fmt.floatToHex(res1); |
| } |
| |
| static void doPrintValue32 (const FloatFormat& fmt, |
| const float& value, |
| ostream& os) |
| { |
| os << fmt.floatToHex(value); |
| } |
| |
| static void doPrintValue64 (const FloatFormat& fmt, |
| const float& value, |
| ostream& os) |
| { |
| os << fmt.floatToHex(value); |
| } |
| |
| template <typename U> |
| static bool doContains (const Interval& a, const float& value, bool is16Bit, const tcu::Maybe<U>& modularDivisor) |
| { |
| if(is16Bit) |
| { |
| // Note: for deFloat16s packed in 32 bits, the original divisor is provided as a float to the shader in the input |
| // buffer, so U is also float here and we call the right interlvalContains() version. |
| const deUint32 iRep = reinterpret_cast<const deUint32&>(value); |
| float res0 = deFloat16To32((deFloat16)(iRep & 0xFFFF)); |
| float res1 = deFloat16To32((deFloat16)(iRep >> 16)); |
| return intervalContains(a, res0, modularDivisor) && (res1 == -1.0); |
| } |
| return intervalContains(a, value, modularDivisor); |
| } |
| }; |
| |
| template<> |
| struct Traits<double> : ScalarTraits<double> |
| { |
| static void doPrintIVal (const FloatFormat& fmt, |
| const Interval& ival, |
| ostream& os) |
| { |
| os << fmt.intervalToHex(ival); |
| } |
| |
| static void doPrintValue16 (const FloatFormat& fmt, |
| const double& value, |
| ostream& os) |
| { |
| const deUint64 iRep = reinterpret_cast<const deUint64&>(value); |
| double byte0 = deFloat16To64((deFloat16)((iRep ) & 0xffff)); |
| double byte1 = deFloat16To64((deFloat16)((iRep >> 16) & 0xffff)); |
| double byte2 = deFloat16To64((deFloat16)((iRep >> 32) & 0xffff)); |
| double byte3 = deFloat16To64((deFloat16)((iRep >> 48) & 0xffff)); |
| os << fmt.floatToHex(byte0) << " " << fmt.floatToHex(byte1) << " " << fmt.floatToHex(byte2) << " " << fmt.floatToHex(byte3); |
| } |
| |
| static void doPrintValue32 (const FloatFormat& fmt, |
| const double& value, |
| ostream& os) |
| { |
| const deUint64 iRep = reinterpret_cast<const deUint64&>(value); |
| double res0 = static_cast<double>((float)((iRep ) & 0xffffffff)); |
| double res1 = static_cast<double>((float)((iRep >> 32) & 0xffffffff)); |
| os << fmt.floatToHex(res0) << " " << fmt.floatToHex(res1); |
| } |
| |
| static void doPrintValue64 (const FloatFormat& fmt, |
| const double& value, |
| ostream& os) |
| { |
| os << fmt.floatToHex(value); |
| } |
| |
| template <class U> |
| static bool doContains (const Interval& a, const double& value, bool is16Bit, const tcu::Maybe<U>& modularDivisor) |
| { |
| DE_UNREF(is16Bit); |
| DE_ASSERT(!is16Bit); |
| return intervalContains(a, value, modularDivisor); |
| } |
| }; |
| |
| template<> |
| struct Traits<deFloat16> : ScalarTraits<deFloat16> |
| { |
| static void doPrintIVal (const FloatFormat& fmt, |
| const Interval& ival, |
| ostream& os) |
| { |
| os << fmt.intervalToHex(ival); |
| } |
| |
| static void doPrintValue16 (const FloatFormat& fmt, |
| const deFloat16& value, |
| ostream& os) |
| { |
| const float res0 = deFloat16To32(value); |
| os << fmt.floatToHex(static_cast<double>(res0)); |
| } |
| static void doPrintValue32 (const FloatFormat& fmt, |
| const deFloat16& value, |
| ostream& os) |
| { |
| const float res0 = deFloat16To32(value); |
| os << fmt.floatToHex(static_cast<double>(res0)); |
| } |
| |
| static void doPrintValue64 (const FloatFormat& fmt, |
| const deFloat16& value, |
| ostream& os) |
| { |
| const double res0 = deFloat16To64(value); |
| os << fmt.floatToHex(res0); |
| } |
| |
| // When the value and divisor are both deFloat16, convert both to float to call the right intervalContains version. |
| static bool doContains (const Interval& a, const deFloat16& value, bool is16Bit, const tcu::Maybe<deFloat16>& modularDivisor) |
| { |
| DE_UNREF(is16Bit); |
| float res0 = deFloat16To32(value); |
| const tcu::Maybe<float> convertedDivisor = (modularDivisor ? tcu::just(deFloat16To32(modularDivisor.get())) : tcu::nothing<float>()); |
| return intervalContains(a, res0, convertedDivisor); |
| } |
| |
| // If the types don't match we should not be in a modulo operation, so no conversion should take place. |
| template <class U> |
| static bool doContains (const Interval& a, const deFloat16& value, bool is16Bit, const tcu::Maybe<U>& modularDivisor) |
| { |
| DE_UNREF(is16Bit); |
| float res0 = deFloat16To32(value); |
| return intervalContains(a, res0, modularDivisor); |
| } |
| }; |
| |
| template<> |
| struct Traits<bool> : ScalarTraits<bool> |
| { |
| static void doPrintValue16 (const FloatFormat&, |
| const float& value, |
| ostream& os) |
| { |
| os << (value != 0.0f ? "true" : "false"); |
| } |
| |
| static void doPrintValue32 (const FloatFormat&, |
| const float& value, |
| ostream& os) |
| { |
| os << (value != 0.0f ? "true" : "false"); |
| } |
| |
| static void doPrintValue64 (const FloatFormat&, |
| const float& value, |
| ostream& os) |
| { |
| os << (value != 0.0f ? "true" : "false"); |
| } |
| |
| static void doPrintIVal (const FloatFormat&, |
| const Interval& ival, |
| ostream& os) |
| { |
| os << "{"; |
| if (ival.contains(false)) |
| os << "false"; |
| if (ival.contains(false) && ival.contains(true)) |
| os << ", "; |
| if (ival.contains(true)) |
| os << "true"; |
| os << "}"; |
| } |
| }; |
| |
| template<> |
| struct Traits<int> : ScalarTraits<int> |
| { |
| static void doPrintValue16 (const FloatFormat&, |
| const int& value, |
| ostream& os) |
| { |
| int res0 = value & 0xFFFF; |
| int res1 = value >> 16; |
| os << res0 << " " << res1; |
| } |
| |
| static void doPrintValue32 (const FloatFormat&, |
| const int& value, |
| ostream& os) |
| { |
| os << value; |
| } |
| |
| static void doPrintValue64 (const FloatFormat&, |
| const int& value, |
| ostream& os) |
| { |
| os << value; |
| } |
| |
| static void doPrintIVal (const FloatFormat&, |
| const Interval& ival, |
| ostream& os) |
| { |
| os << "[" << int(ival.lo()) << ", " << int(ival.hi()) << "]"; |
| } |
| |
| template <typename U> |
| static bool doContains (const Interval& a, const int& value, bool is16Bit, const tcu::Maybe<U>& modularDivisor) |
| { |
| DE_UNREF(is16Bit); |
| return intervalContains(a, value, modularDivisor); |
| } |
| }; |
| |
| //! Common traits for containers, i.e. vectors and matrices. |
| //! T is the container type itself, I is the same type with interval elements. |
| template <typename T, typename I> |
| struct ContainerTraits |
| { |
| typedef typename T::Element Element; |
| typedef I IVal; |
| |
| static IVal doMakeIVal (const T& value) |
| { |
| IVal ret; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| ret[ndx] = makeIVal(value[ndx]); |
| |
| return ret; |
| } |
| |
| static IVal doUnion (const IVal& a, const IVal& b) |
| { |
| IVal ret; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| ret[ndx] = unionIVal<Element>(a[ndx], b[ndx]); |
| |
| return ret; |
| } |
| |
| // When the input and output types match, we may be in a modulo operation. If the divisor is provided, use each of its |
| // components to determine if the obtained result is fine. |
| static bool doContains (const IVal& ival, const T& value, bool is16Bit, const tcu::Maybe<T>& modularDivisor) |
| { |
| using DivisorElement = typename T::Element; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| { |
| const tcu::Maybe<DivisorElement> divisorElement = (modularDivisor ? tcu::just((*modularDivisor)[ndx]) : tcu::nothing<DivisorElement>()); |
| if (!contains(ival[ndx], value[ndx], is16Bit, divisorElement)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // When the input and output types do not match we should not be in a modulo operation. This version is provided for syntactical |
| // compatibility. |
| template <typename U> |
| static bool doContains (const IVal& ival, const T& value, bool is16Bit, const tcu::Maybe<U>& modularDivisor) |
| { |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| { |
| if (!contains(ival[ndx], value[ndx], is16Bit, modularDivisor)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void doPrintIVal (const FloatFormat& fmt, const IVal ival, ostream& os) |
| { |
| os << "("; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| { |
| if (ndx > 0) |
| os << ", "; |
| |
| printIVal<Element>(fmt, ival[ndx], os); |
| } |
| |
| os << ")"; |
| } |
| |
| static void doPrintValue16 (const FloatFormat& fmt, const T& value, ostream& os) |
| { |
| os << dataTypeNameOf<T>() << "("; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| { |
| if (ndx > 0) |
| os << ", "; |
| |
| printValue16<Element>(fmt, value[ndx], os); |
| } |
| |
| os << ")"; |
| } |
| |
| static void doPrintValue32 (const FloatFormat& fmt, const T& value, ostream& os) |
| { |
| os << dataTypeNameOf<T>() << "("; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| { |
| if (ndx > 0) |
| os << ", "; |
| |
| printValue32<Element>(fmt, value[ndx], os); |
| } |
| |
| os << ")"; |
| } |
| |
| static void doPrintValue64 (const FloatFormat& fmt, const T& value, ostream& os) |
| { |
| os << dataTypeNameOf<T>() << "("; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| { |
| if (ndx > 0) |
| os << ", "; |
| |
| printValue64<Element>(fmt, value[ndx], os); |
| } |
| |
| os << ")"; |
| } |
| |
| static IVal doConvert (const FloatFormat& fmt, const IVal& value) |
| { |
| IVal ret; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| ret[ndx] = convert<Element>(fmt, value[ndx]); |
| |
| return ret; |
| } |
| |
| static IVal doRound (const FloatFormat& fmt, T value) |
| { |
| IVal ret; |
| |
| for (int ndx = 0; ndx < T::SIZE; ++ndx) |
| ret[ndx] = round(fmt, value[ndx]); |
| |
| return ret; |
| } |
| }; |
| |
| template <typename T, int Size> |
| struct Traits<Vector<T, Size> > : |
| ContainerTraits<Vector<T, Size>, Vector<typename Traits<T>::IVal, Size> > |
| { |
| }; |
| |
| template <typename T, int Rows, int Cols> |
| struct Traits<Matrix<T, Rows, Cols> > : |
| ContainerTraits<Matrix<T, Rows, Cols>, Matrix<typename Traits<T>::IVal, Rows, Cols> > |
| { |
| }; |
| |
| //! Void traits. These are just dummies, but technically valid: a Void is a |
| //! unit type with a single possible value. |
| template<> |
| struct Traits<Void> |
| { |
| typedef Void IVal; |
| |
| static Void doMakeIVal (const Void& value) { return value; } |
| static Void doUnion (const Void&, const Void&) { return Void(); } |
| static bool doContains (const Void&, Void) { return true; } |
| template <typename U> |
| static bool doContains (const Void&, const Void& value, bool is16Bit, const tcu::Maybe<U>& modularDivisor) { DE_UNREF(value); DE_UNREF(is16Bit); DE_UNREF(modularDivisor); return true; } |
| static Void doRound (const FloatFormat&, const Void& value) { return value; } |
| static Void doConvert (const FloatFormat&, const Void& value) { return value; } |
| |
| static void doPrintValue16 (const FloatFormat&, const Void&, ostream& os) |
| { |
| os << "()"; |
| } |
| |
| static void doPrintValue32 (const FloatFormat&, const Void&, ostream& os) |
| { |
| os << "()"; |
| } |
| |
| static void doPrintValue64 (const FloatFormat&, const Void&, ostream& os) |
| { |
| os << "()"; |
| } |
| |
| static void doPrintIVal (const FloatFormat&, const Void&, ostream& os) |
| { |
| os << "()"; |
| } |
| }; |
| |
| //! This is needed for container-generic operations. |
| //! We want a scalar type T to be its own "one-element vector". |
| template <typename T, int Size> struct ContainerOf { typedef Vector<T, Size> Container; }; |
| |
| template <typename T> struct ContainerOf<T, 1> { typedef T Container; }; |
| template <int Size> struct ContainerOf<Void, Size> { typedef Void Container; }; |
| |
| // This is a kludge that is only needed to get the ExprP::operator[] syntactic sugar to work. |
| template <typename T> struct ElementOf { typedef typename T::Element Element; }; |
| template <> struct ElementOf<float> { typedef void Element; }; |
| template <> struct ElementOf<double>{ typedef void Element; }; |
| template <> struct ElementOf<bool> { typedef void Element; }; |
| template <> struct ElementOf<int> { typedef void Element; }; |
| |
| template <typename T> |
| string comparisonMessageInterval(const typename Traits<T>::IVal& val) |
| { |
| DE_UNREF(val); |
| return ""; |
| } |
| |
| template <> |
| string comparisonMessageInterval<int>(const Traits<int>::IVal& val) |
| { |
| return comparisonMessage(static_cast<int>(val.lo())); |
| } |
| |
| template <> |
| string comparisonMessageInterval<float>(const Traits<float>::IVal& val) |
| { |
| return comparisonMessage(static_cast<int>(val.lo())); |
| } |
| |
| template <> |
| string comparisonMessageInterval<tcu::Vector<int, 2> >(const tcu::Vector<tcu::Interval, 2> & val) |
| { |
| tcu::IVec2 result(static_cast<int>(val[0].lo()), static_cast<int>(val[1].lo())); |
| return comparisonMessage(result); |
| } |
| |
| template <> |
| string comparisonMessageInterval<tcu::Vector<int, 3> >(const tcu::Vector<tcu::Interval, 3> & val) |
| { |
| tcu::IVec3 result(static_cast<int>(val[0].lo()), static_cast<int>(val[1].lo()), static_cast<int>(val[2].lo())); |
| return comparisonMessage(result); |
| } |
| |
| template <> |
| string comparisonMessageInterval<tcu::Vector<int, 4> >(const tcu::Vector<tcu::Interval, 4> & val) |
| { |
| tcu::IVec4 result(static_cast<int>(val[0].lo()), static_cast<int>(val[1].lo()), static_cast<int>(val[2].lo()), static_cast<int>(val[3].lo())); |
| return comparisonMessage(result); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * |
| * \name Abstract syntax for expressions and statements. |
| * |
| * We represent GLSL programs as syntax objects: an Expr<T> represents an |
| * expression whose GLSL type corresponds to the C++ type T, and a Statement |
| * represents a statement. |
| * |
| * To ease memory management, we use shared pointers to refer to expressions |
| * and statements. ExprP<T> is a shared pointer to an Expr<T>, and StatementP |
| * is a shared pointer to a Statement. |
| * |
| * \{ |
| * |
| *//*--------------------------------------------------------------------*/ |
| |
| class ExprBase; |
| class ExpandContext; |
| class Statement; |
| class StatementP; |
| class FuncBase; |
| template <typename T> class ExprP; |
| template <typename T> class Variable; |
| template <typename T> class VariableP; |
| template <typename T> class DefaultSampling; |
| |
| typedef set<const FuncBase*> FuncSet; |
| |
| template <typename T> |
| VariableP<T> variable (const string& name); |
| StatementP compoundStatement (const vector<StatementP>& statements); |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief A variable environment. |
| * |
| * An Environment object maintains the mapping between variables of the |
| * abstract syntax tree and their values. |
| * |
| * \todo [2014-03-28 lauri] At least run-time type safety. |
| * |
| *//*--------------------------------------------------------------------*/ |
| class Environment |
| { |
| public: |
| template<typename T> |
| void bind (const Variable<T>& variable, |
| const typename Traits<T>::IVal& value) |
| { |
| deUint8* const data = new deUint8[sizeof(value)]; |
| |
| deMemcpy(data, &value, sizeof(value)); |
| de::insert(m_map, variable.getName(), SharedPtr<deUint8>(data, de::ArrayDeleter<deUint8>())); |
| } |
| |
| template<typename T> |
| typename Traits<T>::IVal& lookup (const Variable<T>& variable) const |
| { |
| deUint8* const data = de::lookup(m_map, variable.getName()).get(); |
| |
| return *reinterpret_cast<typename Traits<T>::IVal*>(data); |
| } |
| |
| private: |
| map<string, SharedPtr<deUint8> > m_map; |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Evaluation context. |
| * |
| * The evaluation context contains everything that separates one execution of |
| * an expression from the next. Currently this means the desired floating |
| * point precision and the current variable environment. |
| * |
| *//*--------------------------------------------------------------------*/ |
| struct EvalContext |
| { |
| EvalContext (const FloatFormat& format_, |
| Precision floatPrecision_, |
| Environment& env_, |
| int callDepth_) |
| : format (format_) |
| , floatPrecision (floatPrecision_) |
| , env (env_) |
| , callDepth (callDepth_) {} |
| |
| FloatFormat format; |
| Precision floatPrecision; |
| Environment& env; |
| int callDepth; |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Simple incremental counter. |
| * |
| * This is used to make sure that different ExpandContexts will not produce |
| * overlapping temporary names. |
| * |
| *//*--------------------------------------------------------------------*/ |
| class Counter |
| { |
| public: |
| Counter (int count = 0) : m_count(count) {} |
| int operator() (void) { return m_count++; } |
| |
| private: |
| int m_count; |
| }; |
| |
| class ExpandContext |
| { |
| public: |
| ExpandContext (Counter& symCounter) : m_symCounter(symCounter) {} |
| ExpandContext (const ExpandContext& parent) |
| : m_symCounter(parent.m_symCounter) {} |
| |
| template<typename T> |
| VariableP<T> genSym (const string& baseName) |
| { |
| return variable<T>(baseName + de::toString(m_symCounter())); |
| } |
| |
| void addStatement (const StatementP& stmt) |
| { |
| m_statements.push_back(stmt); |
| } |
| |
| vector<StatementP> getStatements (void) const |
| { |
| return m_statements; |
| } |
| private: |
| Counter& m_symCounter; |
| vector<StatementP> m_statements; |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief A statement or declaration. |
| * |
| * Statements have no values. Instead, they are executed for their side |
| * effects only: the execute() method should modify at least one variable in |
| * the environment. |
| * |
| * As a bit of a kludge, a Statement object can also represent a declaration: |
| * when it is evaluated, it can add a variable binding to the environment |
| * instead of modifying a current one. |
| * |
| *//*--------------------------------------------------------------------*/ |
| class Statement |
| { |
| public: |
| virtual ~Statement (void) { } |
| //! Execute the statement, modifying the environment of `ctx` |
| void execute (EvalContext& ctx) const { this->doExecute(ctx); } |
| void print (ostream& os) const { this->doPrint(os); } |
| //! Add the functions used in this statement to `dst`. |
| void getUsedFuncs (FuncSet& dst) const { this->doGetUsedFuncs(dst); } |
| void failed (EvalContext& ctx) const { this->doFail(ctx); } |
| |
| protected: |
| virtual void doPrint (ostream& os) const = 0; |
| virtual void doExecute (EvalContext& ctx) const = 0; |
| virtual void doGetUsedFuncs (FuncSet& dst) const = 0; |
| virtual void doFail (EvalContext& ctx) const { DE_UNREF(ctx); } |
| }; |
| |
| ostream& operator<<(ostream& os, const Statement& stmt) |
| { |
| stmt.print(os); |
| return os; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Smart pointer for statements (and declarations) |
| * |
| *//*--------------------------------------------------------------------*/ |
| class StatementP : public SharedPtr<const Statement> |
| { |
| public: |
| typedef SharedPtr<const Statement> Super; |
| |
| StatementP (void) {} |
| explicit StatementP (const Statement* ptr) : Super(ptr) {} |
| StatementP (const Super& ptr) : Super(ptr) {} |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief |
| * |
| * A statement that modifies a variable or a declaration that binds a variable. |
| * |
| *//*--------------------------------------------------------------------*/ |
| template <typename T> |
| class VariableStatement : public Statement |
| { |
| public: |
| VariableStatement (const VariableP<T>& variable, const ExprP<T>& value, |
| bool isDeclaration) |
| : m_variable (variable) |
| , m_value (value) |
| , m_isDeclaration (isDeclaration) {} |
| |
| protected: |
| void doPrint (ostream& os) const |
| { |
| if (m_isDeclaration) |
| os << glu::declare(getVarTypeOf<T>(), m_variable->getName()); |
| else |
| os << m_variable->getName(); |
| |
| os << " = "; |
| os<< *m_value << ";\n"; |
| } |
| |
| void doExecute (EvalContext& ctx) const |
| { |
| if (m_isDeclaration) |
| ctx.env.bind(*m_variable, m_value->evaluate(ctx)); |
| else |
| ctx.env.lookup(*m_variable) = m_value->evaluate(ctx); |
| } |
| |
| void doGetUsedFuncs (FuncSet& dst) const |
| { |
| m_value->getUsedFuncs(dst); |
| } |
| |
| virtual void doFail (EvalContext& ctx) const |
| { |
| if (m_isDeclaration) |
| ctx.env.bind(*m_variable, m_value->fails(ctx)); |
| else |
| ctx.env.lookup(*m_variable) = m_value->fails(ctx); |
| } |
| |
| VariableP<T> m_variable; |
| ExprP<T> m_value; |
| bool m_isDeclaration; |
| }; |
| |
| template <typename T> |
| StatementP variableStatement (const VariableP<T>& variable, |
| const ExprP<T>& value, |
| bool isDeclaration) |
| { |
| return StatementP(new VariableStatement<T>(variable, value, isDeclaration)); |
| } |
| |
| template <typename T> |
| StatementP variableDeclaration (const VariableP<T>& variable, const ExprP<T>& definiens) |
| { |
| return variableStatement(variable, definiens, true); |
| } |
| |
| template <typename T> |
| StatementP variableAssignment (const VariableP<T>& variable, const ExprP<T>& value) |
| { |
| return variableStatement(variable, value, false); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief A compound statement, i.e. a block. |
| * |
| * A compound statement is executed by executing its constituent statements in |
| * sequence. |
| * |
| *//*--------------------------------------------------------------------*/ |
| class CompoundStatement : public Statement |
| { |
| public: |
| CompoundStatement (const vector<StatementP>& statements) |
| : m_statements (statements) {} |
| |
| protected: |
| void doPrint (ostream& os) const |
| { |
| os << "{\n"; |
| |
| for (size_t ndx = 0; ndx < m_statements.size(); ++ndx) |
| os << *m_statements[ndx]; |
| |
| os << "}\n"; |
| } |
| |
| void doExecute (EvalContext& ctx) const |
| { |
| for (size_t ndx = 0; ndx < m_statements.size(); ++ndx) |
| m_statements[ndx]->execute(ctx); |
| } |
| |
| void doGetUsedFuncs (FuncSet& dst) const |
| { |
| for (size_t ndx = 0; ndx < m_statements.size(); ++ndx) |
| m_statements[ndx]->getUsedFuncs(dst); |
| } |
| |
| vector<StatementP> m_statements; |
| }; |
| |
| StatementP compoundStatement(const vector<StatementP>& statements) |
| { |
| return StatementP(new CompoundStatement(statements)); |
| } |
| |
| //! Common base class for all expressions regardless of their type. |
| class ExprBase |
| { |
| public: |
| virtual ~ExprBase (void) {} |
| void printExpr (ostream& os) const { this->doPrintExpr(os); } |
| |
| //! Output the functions that this expression refers to |
| void getUsedFuncs (FuncSet& dst) const |
| { |
| this->doGetUsedFuncs(dst); |
| } |
| |
| protected: |
| virtual void doPrintExpr (ostream&) const {} |
| virtual void doGetUsedFuncs (FuncSet&) const {} |
| }; |
| |
| //! Type-specific operations for an expression representing type T. |
| template <typename T> |
| class Expr : public ExprBase |
| { |
| public: |
| typedef T Val; |
| typedef typename Traits<T>::IVal IVal; |
| |
| IVal evaluate (const EvalContext& ctx) const; |
| IVal fails (const EvalContext& ctx) const { return this->doFails(ctx); } |
| |
| protected: |
| virtual IVal doEvaluate (const EvalContext& ctx) const = 0; |
| virtual IVal doFails (const EvalContext& ctx) const {return doEvaluate(ctx);} |
| }; |
| |
| //! Evaluate an expression with the given context, optionally tracing the calls to stderr. |
| template <typename T> |
| typename Traits<T>::IVal Expr<T>::evaluate (const EvalContext& ctx) const |
| { |
| #ifdef GLS_ENABLE_TRACE |
| static const FloatFormat highpFmt (-126, 127, 23, true, |
| tcu::MAYBE, |
| tcu::YES, |
| tcu::MAYBE); |
| EvalContext newCtx (ctx.format, ctx.floatPrecision, |
| ctx.env, ctx.callDepth + 1); |
| const IVal ret = this->doEvaluate(newCtx); |
| |
| if (isTypeValid<T>()) |
| { |
| std::cerr << string(ctx.callDepth, ' '); |
| this->printExpr(std::cerr); |
| std::cerr << " -> " << intervalToString<T>(highpFmt, ret) << std::endl; |
| } |
| return ret; |
| #else |
| return this->doEvaluate(ctx); |
| #endif |
| } |
| |
| template <typename T> |
| class ExprPBase : public SharedPtr<const Expr<T> > |
| { |
| public: |
| }; |
| |
| ostream& operator<< (ostream& os, const ExprBase& expr) |
| { |
| expr.printExpr(os); |
| return os; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Shared pointer to an expression of a container type. |
| * |
| * Container types (i.e. vectors and matrices) support the subscription |
| * operator. This class provides a bit of syntactic sugar to allow us to use |
| * the C++ subscription operator to create a subscription expression. |
| *//*--------------------------------------------------------------------*/ |
| template <typename T> |
| class ContainerExprPBase : public ExprPBase<T> |
| { |
| public: |
| ExprP<typename T::Element> operator[] (int i) const; |
| }; |
| |
| template <typename T> |
| class ExprP : public ExprPBase<T> {}; |
| |
| // We treat Voids as containers since the unused parameters in generalized |
| // vector functions are represented as Voids. |
| template <> |
| class ExprP<Void> : public ContainerExprPBase<Void> {}; |
| |
| template <typename T, int Size> |
| class ExprP<Vector<T, Size> > : public ContainerExprPBase<Vector<T, Size> > {}; |
| |
| template <typename T, int Rows, int Cols> |
| class ExprP<Matrix<T, Rows, Cols> > : public ContainerExprPBase<Matrix<T, Rows, Cols> > {}; |
| |
| template <typename T> ExprP<T> exprP (void) |
| { |
| return ExprP<T>(); |
| } |
| |
| template <typename T> |
| ExprP<T> exprP (const SharedPtr<const Expr<T> >& ptr) |
| { |
| ExprP<T> ret; |
| static_cast<SharedPtr<const Expr<T> >&>(ret) = ptr; |
| return ret; |
| } |
| |
| template <typename T> |
| ExprP<T> exprP (const Expr<T>* ptr) |
| { |
| return exprP(SharedPtr<const Expr<T> >(ptr)); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief A shared pointer to a variable expression. |
| * |
| * This is just a narrowing of ExprP for the operations that require a variable |
| * instead of an arbitrary expression. |
| * |
| *//*--------------------------------------------------------------------*/ |
| template <typename T> |
| class VariableP : public SharedPtr<const Variable<T> > |
| { |
| public: |
| typedef SharedPtr<const Variable<T> > Super; |
| explicit VariableP (const Variable<T>* ptr) : Super(ptr) {} |
| VariableP (void) {} |
| VariableP (const Super& ptr) : Super(ptr) {} |
| |
| operator ExprP<T> (void) const { return exprP(SharedPtr<const Expr<T> >(*this)); } |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \name Syntactic sugar operators for expressions. |
| * |
| * @{ |
| * |
| * These operators allow the use of C++ syntax to construct GLSL expressions |
| * containing operators: e.g. "a+b" creates an addition expression with |
| * operands a and b, and so on. |
| * |
| *//*--------------------------------------------------------------------*/ |
| ExprP<float> operator+ (const ExprP<float>& arg0, |
| const ExprP<float>& arg1); |
| ExprP<deFloat16> operator+ (const ExprP<deFloat16>& arg0, |
| const ExprP<deFloat16>& arg1); |
| ExprP<double> operator+ (const ExprP<double>& arg0, |
| const ExprP<double>& arg1); |
| template <typename T> |
| ExprP<T> operator- (const ExprP<T>& arg0); |
| template <typename T> |
| ExprP<T> operator- (const ExprP<T>& arg0, |
| const ExprP<T>& arg1); |
| template<int Left, int Mid, int Right, typename T> |
| ExprP<Matrix<T, Left, Right> > operator* (const ExprP<Matrix<T, Left, Mid> >& left, |
| const ExprP<Matrix<T, Mid, Right> >& right); |
| ExprP<float> operator* (const ExprP<float>& arg0, |
| const ExprP<float>& arg1); |
| ExprP<deFloat16> operator* (const ExprP<deFloat16>& arg0, |
| const ExprP<deFloat16>& arg1); |
| ExprP<double> operator* (const ExprP<double>& arg0, |
| const ExprP<double>& arg1); |
| template <typename T> |
| ExprP<T> operator/ (const ExprP<T>& arg0, |
| const ExprP<T>& arg1); |
| template<typename T, int Size> |
| ExprP<Vector<T, Size> > operator- (const ExprP<Vector<T, Size> >& arg0); |
| template<typename T, int Size> |
| ExprP<Vector<T, Size> > operator- (const ExprP<Vector<T, Size> >& arg0, |
| const ExprP<Vector<T, Size> >& arg1); |
| template<int Size, typename T> |
| ExprP<Vector<T, Size> > operator* (const ExprP<Vector<T, Size> >& arg0, |
| const ExprP<T>& arg1); |
| template<typename T, int Size> |
| ExprP<Vector<T, Size> > operator* (const ExprP<Vector<T, Size> >& arg0, |
| const ExprP<Vector<T, Size> >& arg1); |
| template<int Rows, int Cols, typename T> |
| ExprP<Vector<T, Rows> > operator* (const ExprP<Vector<T, Cols> >& left, |
| const ExprP<Matrix<T, Rows, Cols> >& right); |
| template<int Rows, int Cols, typename T> |
| ExprP<Vector<T, Cols> > operator* (const ExprP<Matrix<T, Rows, Cols> >& left, |
| const ExprP<Vector<T, Rows> >& right); |
| template<int Rows, int Cols, typename T> |
| ExprP<Matrix<T, Rows, Cols> > operator* (const ExprP<Matrix<T, Rows, Cols> >& left, |
| const ExprP<T>& right); |
| template<int Rows, int Cols> |
| ExprP<Matrix<float, Rows, Cols> > operator+ (const ExprP<Matrix<float, Rows, Cols> >& left, |
| const ExprP<Matrix<float, Rows, Cols> >& right); |
| template<int Rows, int Cols> |
| ExprP<Matrix<deFloat16, Rows, Cols> > operator+ (const ExprP<Matrix<deFloat16, Rows, Cols> >& left, |
| const ExprP<Matrix<deFloat16, Rows, Cols> >& right); |
| template<int Rows, int Cols> |
| ExprP<Matrix<double, Rows, Cols> > operator+ (const ExprP<Matrix<double, Rows, Cols> >& left, |
| const ExprP<Matrix<double, Rows, Cols> >& right); |
| template<typename T, int Rows, int Cols> |
| ExprP<Matrix<T, Rows, Cols> > operator- (const ExprP<Matrix<T, Rows, Cols> >& mat); |
| |
| //! @} |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Variable expression. |
| * |
| * A variable is evaluated by looking up its range of possible values from an |
| * environment. |
| *//*--------------------------------------------------------------------*/ |
| template <typename T> |
| class Variable : public Expr<T> |
| { |
| public: |
| typedef typename Expr<T>::IVal IVal; |
| |
| Variable (const string& name) : m_name (name) {} |
| string getName (void) const { return m_name; } |
| |
| protected: |
| void doPrintExpr (ostream& os) const { os << m_name; } |
| IVal doEvaluate (const EvalContext& ctx) const |
| { |
| return ctx.env.lookup<T>(*this); |
| } |
| |
| private: |
| string m_name; |
| }; |
| |
| template <typename T> |
| VariableP<T> variable (const string& name) |
| { |
| return VariableP<T>(new Variable<T>(name)); |
| } |
| |
| template <typename T> |
| VariableP<T> bindExpression (const string& name, ExpandContext& ctx, const ExprP<T>& expr) |
| { |
| VariableP<T> var = ctx.genSym<T>(name); |
| ctx.addStatement(variableDeclaration(var, expr)); |
| return var; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Constant expression. |
| * |
| * A constant is evaluated by rounding it to a set of possible values allowed |
| * by the current floating point precision. |
| *//*--------------------------------------------------------------------*/ |
| template <typename T> |
| class Constant : public Expr<T> |
| { |
| public: |
| typedef typename Expr<T>::IVal IVal; |
| |
| Constant (const T& value) : m_value(value) {} |
| |
| protected: |
| void doPrintExpr (ostream& os) const { os << m_value; } |
| IVal doEvaluate (const EvalContext&) const { return makeIVal(m_value); } |
| |
| private: |
| T m_value; |
| }; |
| |
| template <typename T> |
| ExprP<T> constant (const T& value) |
| { |
| return exprP(new Constant<T>(value)); |
| } |
| |
| //! Return a reference to a singleton void constant. |
| const ExprP<Void>& voidP (void) |
| { |
| static const ExprP<Void> singleton = constant(Void()); |
| |
| return singleton; |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Four-element tuple. |
| * |
| * This is used for various things where we need one thing for each possible |
| * function parameter. Currently the maximum supported number of parameters is |
| * four. |
| *//*--------------------------------------------------------------------*/ |
| template <typename T0 = Void, typename T1 = Void, typename T2 = Void, typename T3 = Void> |
| struct Tuple4 |
| { |
| explicit Tuple4 (const T0 e0 = T0(), |
| const T1 e1 = T1(), |
| const T2 e2 = T2(), |
| const T3 e3 = T3()) |
| : a (e0) |
| , b (e1) |
| , c (e2) |
| , d (e3) |
| { |
| } |
| |
| T0 a; |
| T1 b; |
| T2 c; |
| T3 d; |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Function signature. |
| * |
| * This is a purely compile-time structure used to bundle all types in a |
| * function signature together. This makes passing the signature around in |
| * templates easier, since we only need to take and pass a single Sig instead |
| * of a bunch of parameter types and a return type. |
| * |
| *//*--------------------------------------------------------------------*/ |
| template <typename R, |
| typename P0 = Void, typename P1 = Void, |
| typename P2 = Void, typename P3 = Void> |
| struct Signature |
| { |
| typedef R Ret; |
| typedef P0 Arg0; |
| typedef P1 Arg1; |
| typedef P2 Arg2; |
| typedef P3 Arg3; |
| typedef typename Traits<Ret>::IVal IRet; |
| typedef typename Traits<Arg0>::IVal IArg0; |
| typedef typename Traits<Arg1>::IVal IArg1; |
| typedef typename Traits<Arg2>::IVal IArg2; |
| typedef typename Traits<Arg3>::IVal IArg3; |
| |
| typedef Tuple4< const Arg0&, const Arg1&, const Arg2&, const Arg3&> Args; |
| typedef Tuple4< const IArg0&, const IArg1&, const IArg2&, const IArg3&> IArgs; |
| typedef Tuple4< ExprP<Arg0>, ExprP<Arg1>, ExprP<Arg2>, ExprP<Arg3> > ArgExprs; |
| }; |
| |
| typedef vector<const ExprBase*> BaseArgExprs; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Type-independent operations for function objects. |
| * |
| *//*--------------------------------------------------------------------*/ |
| class FuncBase |
| { |
| public: |
| virtual ~FuncBase (void) {} |
| virtual string getName (void) const = 0; |
| //! Name of extension that this function requires, or empty. |
| virtual string getRequiredExtension (void) const { return ""; } |
| virtual Interval getInputRange (const bool is16bit) const {DE_UNREF(is16bit); return Interval(true, -TCU_INFINITY, TCU_INFINITY); } |
| virtual void print (ostream&, |
| const BaseArgExprs&) const = 0; |
| //! Index of output parameter, or -1 if none of the parameters is output. |
| virtual int getOutParamIndex (void) const { return -1; } |
| |
| virtual SpirVCaseT getSpirvCase (void) const { return SPIRV_CASETYPE_NONE; } |
| |
| void printDefinition (ostream& os) const |
| { |
| doPrintDefinition(os); |
| } |
| |
| void getUsedFuncs (FuncSet& dst) const |
| { |
| this->doGetUsedFuncs(dst); |
| } |
| |
| protected: |
| virtual void doPrintDefinition (ostream& os) const = 0; |
| virtual void doGetUsedFuncs (FuncSet& dst) const = 0; |
| }; |
| |
| typedef Tuple4<string, string, string, string> ParamNames; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Function objects. |
| * |
| * Each Func object represents a GLSL function. It can be applied to interval |
| * arguments, and it returns the an interval that is a conservative |
| * approximation of the image of the GLSL function over the argument |
| * intervals. That is, it is given a set of possible arguments and it returns |
| * the set of possible values. |
| * |
| *//*--------------------------------------------------------------------*/ |
| template <typename Sig_> |
| class Func : public FuncBase |
| { |
| public: |
| typedef Sig_ Sig; |
| typedef typename Sig::Ret Ret; |
| typedef typename Sig::Arg0 Arg0; |
| typedef typename Sig::Arg1 Arg1; |
| typedef typename Sig::Arg2 Arg2; |
| typedef typename Sig::Arg3 Arg3; |
| typedef typename Sig::IRet IRet; |
| typedef typename Sig::IArg0 IArg0; |
| typedef typename Sig::IArg1 IArg1; |
| typedef typename Sig::IArg2 IArg2; |
| typedef typename Sig::IArg3 IArg3; |
| typedef typename Sig::Args Args; |
| typedef typename Sig::IArgs IArgs; |
| typedef typename Sig::ArgExprs ArgExprs; |
| |
| void print (ostream& os, |
| const BaseArgExprs& args) const |
| { |
| this->doPrint(os, args); |
| } |
| |
| IRet apply (const EvalContext& ctx, |
| const IArg0& arg0 = IArg0(), |
| const IArg1& arg1 = IArg1(), |
| const IArg2& arg2 = IArg2(), |
| const IArg3& arg3 = IArg3()) const |
| { |
| return this->applyArgs(ctx, IArgs(arg0, arg1, arg2, arg3)); |
| } |
| |
| IRet fail (const EvalContext& ctx, |
| const IArg0& arg0 = IArg0(), |
| const IArg1& arg1 = IArg1(), |
| const IArg2& arg2 = IArg2(), |
| const IArg3& arg3 = IArg3()) const |
| { |
| return this->doFail(ctx, IArgs(arg0, arg1, arg2, arg3)); |
| } |
| IRet applyArgs (const EvalContext& ctx, |
| const IArgs& args) const |
| { |
| return this->doApply(ctx, args); |
| } |
| ExprP<Ret> operator() (const ExprP<Arg0>& arg0 = voidP(), |
| const ExprP<Arg1>& arg1 = voidP(), |
| const ExprP<Arg2>& arg2 = voidP(), |
| const ExprP<Arg3>& arg3 = voidP()) const; |
| |
| const ParamNames& getParamNames (void) const |
| { |
| return this->doGetParamNames(); |
| } |
| |
| protected: |
| virtual IRet doApply (const EvalContext&, |
| const IArgs&) const = 0; |
| virtual IRet doFail (const EvalContext& ctx, |
| const IArgs& args) const |
| { |
| return this->doApply(ctx, args); |
| } |
| virtual void doPrint (ostream& os, const BaseArgExprs& args) const |
| { |
| os << getName() << "("; |
| |
| if (isTypeValid<Arg0>()) |
| os << *args[0]; |
| |
| if (isTypeValid<Arg1>()) |
| os << ", " << *args[1]; |
| |
| if (isTypeValid<Arg2>()) |
| os << ", " << *args[2]; |
| |
| if (isTypeValid<Arg3>()) |
| os << ", " << *args[3]; |
| |
| os << ")"; |
| } |
| |
| virtual const ParamNames& doGetParamNames (void) const |
| { |
| static ParamNames names ("a", "b", "c", "d"); |
| return names; |
| } |
| }; |
| |
| template <typename Sig> |
| class Apply : public Expr<typename Sig::Ret> |
| { |
| public: |
| typedef typename Sig::Ret Ret; |
| typedef typename Sig::Arg0 Arg0; |
| typedef typename Sig::Arg1 Arg1; |
| typedef typename Sig::Arg2 Arg2; |
| typedef typename Sig::Arg3 Arg3; |
| typedef typename Expr<Ret>::Val Val; |
| typedef typename Expr<Ret>::IVal IVal; |
| typedef Func<Sig> ApplyFunc; |
| typedef typename ApplyFunc::ArgExprs ArgExprs; |
| |
| Apply (const ApplyFunc& func, |
| const ExprP<Arg0>& arg0 = voidP(), |
| const ExprP<Arg1>& arg1 = voidP(), |
| const ExprP<Arg2>& arg2 = voidP(), |
| const ExprP<Arg3>& arg3 = voidP()) |
| : m_func (func), |
| m_args (arg0, arg1, arg2, arg3) {} |
| |
| Apply (const ApplyFunc& func, |
| const ArgExprs& args) |
| : m_func (func), |
| m_args (args) {} |
| protected: |
| void doPrintExpr (ostream& os) const |
| { |
| BaseArgExprs args; |
| args.push_back(m_args.a.get()); |
| args.push_back(m_args.b.get()); |
| args.push_back(m_args.c.get()); |
| args.push_back(m_args.d.get()); |
| m_func.print(os, args); |
| } |
| |
| IVal doEvaluate (const EvalContext& ctx) const |
| { |
| return m_func.apply(ctx, |
| m_args.a->evaluate(ctx), m_args.b->evaluate(ctx), |
| m_args.c->evaluate(ctx), m_args.d->evaluate(ctx)); |
| } |
| |
| void doGetUsedFuncs (FuncSet& dst) const |
| { |
| m_func.getUsedFuncs(dst); |
| m_args.a->getUsedFuncs(dst); |
| m_args.b->getUsedFuncs(dst); |
| m_args.c->getUsedFuncs(dst); |
| m_args.d->getUsedFuncs(dst); |
| } |
| |
| const ApplyFunc& m_func; |
| ArgExprs m_args; |
| }; |
| |
| template<typename T> |
| class Alternatives : public Func<Signature<T, T, T> > |
| { |
| public: |
| typedef typename Alternatives::Sig Sig; |
| |
| protected: |
| typedef typename Alternatives::IRet IRet; |
| typedef typename Alternatives::IArgs IArgs; |
| |
| virtual string getName (void) const { return "alternatives"; } |
| virtual void doPrintDefinition (std::ostream&) const {} |
| void doGetUsedFuncs (FuncSet&) const {} |
| |
| virtual IRet doApply (const EvalContext&, const IArgs& args) const |
| { |
| return unionIVal<T>(args.a, args.b); |
| } |
| |
| virtual void doPrint (ostream& os, const BaseArgExprs& args) const |
| { |
| os << "{" << *args[0] << " | " << *args[1] << "}"; |
| } |
| }; |
| |
| template <typename Sig> |
| ExprP<typename Sig::Ret> createApply (const Func<Sig>& func, |
| const typename Func<Sig>::ArgExprs& args) |
| { |
| return exprP(new Apply<Sig>(func, args)); |
| } |
| |
| template <typename Sig> |
| ExprP<typename Sig::Ret> createApply ( |
| const Func<Sig>& func, |
| const ExprP<typename Sig::Arg0>& arg0 = voidP(), |
| const ExprP<typename Sig::Arg1>& arg1 = voidP(), |
| const ExprP<typename Sig::Arg2>& arg2 = voidP(), |
| const ExprP<typename Sig::Arg3>& arg3 = voidP()) |
| { |
| return exprP(new Apply<Sig>(func, arg0, arg1, arg2, arg3)); |
| } |
| |
| template <typename Sig> |
| ExprP<typename Sig::Ret> Func<Sig>::operator() (const ExprP<typename Sig::Arg0>& arg0, |
| const ExprP<typename Sig::Arg1>& arg1, |
| const ExprP<typename Sig::Arg2>& arg2, |
| const ExprP<typename Sig::Arg3>& arg3) const |
| { |
| return createApply(*this, arg0, arg1, arg2, arg3); |
| } |
| |
| template <typename F> |
| ExprP<typename F::Ret> app (const ExprP<typename F::Arg0>& arg0 = voidP(), |
| const ExprP<typename F::Arg1>& arg1 = voidP(), |
| const ExprP<typename F::Arg2>& arg2 = voidP(), |
| const ExprP<typename F::Arg3>& arg3 = voidP()) |
| { |
| return createApply(instance<F>(), arg0, arg1, arg2, arg3); |
| } |
| |
| template <typename F> |
| typename F::IRet call (const EvalContext& ctx, |
| const typename F::IArg0& arg0 = Void(), |
| const typename F::IArg1& arg1 = Void(), |
| const typename F::IArg2& arg2 = Void(), |
| const typename F::IArg3& arg3 = Void()) |
| { |
| return instance<F>().apply(ctx, arg0, arg1, arg2, arg3); |
| } |
| |
| template <typename T> |
| ExprP<T> alternatives (const ExprP<T>& arg0, |
| const ExprP<T>& arg1) |
| { |
| return createApply<typename Alternatives<T>::Sig>(instance<Alternatives<T> >(), arg0, arg1); |
| } |
| |
| template <typename Sig> |
| class ApplyVar : public Apply<Sig> |
| { |
| public: |
| typedef typename Sig::Ret Ret; |
| typedef typename Sig::Arg0 Arg0; |
| typedef typename Sig::Arg1 Arg1; |
| typedef typename Sig::Arg2 Arg2; |
| typedef typename Sig::Arg3 Arg3; |
| typedef typename Expr<Ret>::Val Val; |
| typedef typename Expr<Ret>::IVal IVal; |
| typedef Func<Sig> ApplyFunc; |
| typedef typename ApplyFunc::ArgExprs ArgExprs; |
| |
| ApplyVar (const ApplyFunc& func, |
| const VariableP<Arg0>& arg0, |
| const VariableP<Arg1>& arg1, |
| const VariableP<Arg2>& arg2, |
| const VariableP<Arg3>& arg3) |
| : Apply<Sig> (func, arg0, arg1, arg2, arg3) {} |
| protected: |
| IVal doEvaluate (const EvalContext& ctx) const |
| { |
| const Variable<Arg0>& var0 = static_cast<const Variable<Arg0>&>(*this->m_args.a); |
| const Variable<Arg1>& var1 = static_cast<const Variable<Arg1>&>(*this->m_args.b); |
| const Variable<Arg2>& var2 = static_cast<const Variable<Arg2>&>(*this->m_args.c); |
| const Variable<Arg3>& var3 = static_cast<const Variable<Arg3>&>(*this->m_args.d); |
| return this->m_func.apply(ctx, |
| ctx.env.lookup(var0), ctx.env.lookup(var1), |
| ctx.env.lookup(var2), ctx.env.lookup(var3)); |
| } |
| |
| IVal doFails (const EvalContext& ctx) const |
| { |
| const Variable<Arg0>& var0 = static_cast<const Variable<Arg0>&>(*this->m_args.a); |
| const Variable<Arg1>& var1 = static_cast<const Variable<Arg1>&>(*this->m_args.b); |
| const Variable<Arg2>& var2 = static_cast<const Variable<Arg2>&>(*this->m_args.c); |
| const Variable<Arg3>& var3 = static_cast<const Variable<Arg3>&>(*this->m_args.d); |
| return this->m_func.fail(ctx, |
| ctx.env.lookup(var0), ctx.env.lookup(var1), |
| ctx.env.lookup(var2), ctx.env.lookup(var3)); |
| } |
| }; |
| |
| template <typename Sig> |
| ExprP<typename Sig::Ret> applyVar (const Func<Sig>& func, |
| const VariableP<typename Sig::Arg0>& arg0, |
| const VariableP<typename Sig::Arg1>& arg1, |
| const VariableP<typename Sig::Arg2>& arg2, |
| const VariableP<typename Sig::Arg3>& arg3) |
| { |
| return exprP(new ApplyVar<Sig>(func, arg0, arg1, arg2, arg3)); |
| } |
| |
| template <typename Sig_> |
| class DerivedFunc : public Func<Sig_> |
| { |
| public: |
| typedef typename DerivedFunc::ArgExprs ArgExprs; |
| typedef typename DerivedFunc::IRet IRet; |
| typedef typename DerivedFunc::IArgs IArgs; |
| typedef typename DerivedFunc::Ret Ret; |
| typedef typename DerivedFunc::Arg0 Arg0; |
| typedef typename DerivedFunc::Arg1 Arg1; |
| typedef typename DerivedFunc::Arg2 Arg2; |
| typedef typename DerivedFunc::Arg3 Arg3; |
| typedef typename DerivedFunc::IArg0 IArg0; |
| typedef typename DerivedFunc::IArg1 IArg1; |
| typedef typename DerivedFunc::IArg2 IArg2; |
| typedef typename DerivedFunc::IArg3 IArg3; |
| |
| protected: |
| void doPrintDefinition (ostream& os) const |
| { |
| const ParamNames& paramNames = this->getParamNames(); |
| |
| initialize(); |
| |
| os << dataTypeNameOf<Ret>() << " " << this->getName() |
| << "("; |
| if (isTypeValid<Arg0>()) |
| os << dataTypeNameOf<Arg0>() << " " << paramNames.a; |
| if (isTypeValid<Arg1>()) |
| os << ", " << dataTypeNameOf<Arg1>() << " " << paramNames.b; |
| if (isTypeValid<Arg2>()) |
| os << ", " << dataTypeNameOf<Arg2>() << " " << paramNames.c; |
| if (isTypeValid<Arg3>()) |
| os << ", " << dataTypeNameOf<Arg3>() << " " << paramNames.d; |
| os << ")\n{\n"; |
| |
| for (size_t ndx = 0; ndx < m_body.size(); ++ndx) |
| os << *m_body[ndx]; |
| os << "return " << *m_ret << ";\n"; |
| os << "}\n"; |
| } |
| |
| IRet doApply (const EvalContext& ctx, |
| const IArgs& args) const |
| { |
| Environment funEnv; |
| IArgs& mutArgs = const_cast<IArgs&>(args); |
| IRet ret; |
| |
| initialize(); |
| |
| funEnv.bind(*m_var0, args.a); |
| funEnv.bind(*m_var1, args.b); |
| funEnv.bind(*m_var2, args.c); |
| funEnv.bind(*m_var3, args.d); |
| |
| { |
| EvalContext funCtx(ctx.format, ctx.floatPrecision, funEnv, ctx.callDepth); |
| |
| for (size_t ndx = 0; ndx < m_body.size(); ++ndx) |
| m_body[ndx]->execute(funCtx); |
| |
| ret = m_ret->evaluate(funCtx); |
| } |
| |
| // \todo [lauri] Store references instead of values in environment |
| const_cast<IArg0&>(mutArgs.a) = funEnv.lookup(*m_var0); |
| const_cast<IArg1&>(mutArgs.b) = funEnv.lookup(*m_var1); |
| const_cast<IArg2&>(mutArgs.c) = funEnv.lookup(*m_var2); |
| const_cast<IArg3&>(mutArgs.d) = funEnv.lookup(*m_var3); |
| |
| return ret; |
| } |
| |
| void doGetUsedFuncs (FuncSet& dst) const |
| { |
| initialize(); |
| if (dst.insert(this).second) |
| { |
| for (size_t ndx = 0; ndx < m_body.size(); ++ndx) |
| m_body[ndx]->getUsedFuncs(dst); |
| m_ret->getUsedFuncs(dst); |
| } |
| } |
| |
| virtual ExprP<Ret> doExpand (ExpandContext& ctx, const ArgExprs& args_) const = 0; |
| |
| // These are transparently initialized when first needed. They cannot be |
| // initialized in the constructor because they depend on the doExpand |
| // method of the subclass. |
| |
| mutable VariableP<Arg0> m_var0; |
| mutable VariableP<Arg1> m_var1; |
| mutable VariableP<Arg2> m_var2; |
| mutable VariableP<Arg3> m_var3; |
| mutable vector<StatementP> m_body; |
| mutable ExprP<Ret> m_ret; |
| |
| private: |
| |
| void initialize (void) const |
| { |
| if (!m_ret) |
| { |
| const ParamNames& paramNames = this->getParamNames(); |
| Counter symCounter; |
| ExpandContext ctx (symCounter); |
| ArgExprs args; |
| |
| args.a = m_var0 = variable<Arg0>(paramNames.a); |
| args.b = m_var1 = variable<Arg1>(paramNames.b); |
| args.c = m_var2 = variable<Arg2>(paramNames.c); |
| args.d = m_var3 = variable<Arg3>(paramNames.d); |
| |
| m_ret = this->doExpand(ctx, args); |
| m_body = ctx.getStatements(); |
| } |
| } |
| }; |
| |
| template <typename Sig> |
| class PrimitiveFunc : public Func<Sig> |
| { |
| public: |
| typedef typename PrimitiveFunc::Ret Ret; |
| typedef typename PrimitiveFunc::ArgExprs ArgExprs; |
| |
| protected: |
| void doPrintDefinition (ostream&) const {} |
| void doGetUsedFuncs (FuncSet&) const {} |
| }; |
| |
| template <typename T> |
| class Cond : public PrimitiveFunc<Signature<T, bool, T, T> > |
| { |
| public: |
| typedef typename Cond::IArgs IArgs; |
| typedef typename Cond::IRet IRet; |
| |
| string getName (void) const |
| { |
| return "_cond"; |
| } |
| |
| protected: |
| |
| void doPrint (ostream& os, const BaseArgExprs& args) const |
| { |
| os << "(" << *args[0] << " ? " << *args[1] << " : " << *args[2] << ")"; |
| } |
| |
| IRet doApply (const EvalContext&, const IArgs& iargs)const |
| { |
| IRet ret; |
| |
| if (iargs.a.contains(true)) |
| ret = unionIVal<T>(ret, iargs.b); |
| |
| if (iargs.a.contains(false)) |
| ret = unionIVal<T>(ret, iargs.c); |
| |
| return ret; |
| } |
| }; |
| |
| template <typename T> |
| class CompareOperator : public PrimitiveFunc<Signature<bool, T, T> > |
| { |
| public: |
| typedef typename CompareOperator::IArgs IArgs; |
| typedef typename CompareOperator::IArg0 IArg0; |
| typedef typename CompareOperator::IArg1 IArg1; |
| typedef typename CompareOperator::IRet IRet; |
| |
| protected: |
| void doPrint (ostream& os, const BaseArgExprs& args) const |
| { |
| os << "(" << *args[0] << getSymbol() << *args[1] << ")"; |
| } |
| |
| Interval doApply (const EvalContext&, const IArgs& iargs) const |
| { |
| const IArg0& arg0 = iargs.a; |
| const IArg1& arg1 = iargs.b; |
| IRet ret; |
| |
| if (canSucceed(arg0, arg1)) |
| ret |= true; |
| if (canFail(arg0, arg1)) |
| ret |= false; |
| |
| return ret; |
| } |
| |
| virtual string getSymbol (void) const = 0; |
| virtual bool canSucceed (const IArg0&, const IArg1&) const = 0; |
| virtual bool canFail (const IArg0&, const IArg1&) const = 0; |
| }; |
| |
| template <typename T> |
| class LessThan : public CompareOperator<T> |
| { |
| public: |
| string getName (void) const { return "lessThan"; } |
| |
| protected: |
| string getSymbol (void) const { return "<"; } |
| |
| bool canSucceed (const Interval& a, const Interval& b) const |
| { |
| return (a.lo() < b.hi()); |
| } |
| |
| bool canFail (const Interval& a, const Interval& b) const |
| { |
| return !(a.hi() < b.lo()); |
| } |
| }; |
| |
| template <typename T> |
| ExprP<bool> operator< (const ExprP<T>& a, const ExprP<T>& b) |
| { |
| return app<LessThan<T> >(a, b); |
| } |
| |
| template <typename T> |
| ExprP<T> cond (const ExprP<bool>& test, |
| const ExprP<T>& consequent, |
| const ExprP<T>& alternative) |
| { |
| return app<Cond<T> >(test, consequent, alternative); |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * |
| * @} |
| * |
| *//*--------------------------------------------------------------------*/ |
| //Proper parameters for template T |
| // Signature<float, float> 32bit tests |
| // Signature<float, deFloat16> 16bit tests |
| // Signature<double, double> 64bit tests |
| template< class T> |
| class FloatFunc1 : public PrimitiveFunc<T> |
| { |
| protected: |
| Interval doApply (const EvalContext& ctx, const typename Signature<typename T::Ret, typename T::Arg0>::IArgs& iargs) const |
| { |
| return this->applyMonotone(ctx, iargs.a); |
| } |
| |
| Interval applyMonotone (const EvalContext& ctx, const Interval& iarg0) const |
| { |
| Interval ret; |
| |
| TCU_INTERVAL_APPLY_MONOTONE1(ret, arg0, iarg0, val, |
| TCU_SET_INTERVAL(val, point, |
| point = this->applyPoint(ctx, arg0))); |
| |
| ret |= innerExtrema(ctx, iarg0); |
| ret &= (this->getCodomain(ctx) | TCU_NAN); |
| |
| return ctx.format.convert(ret); |
| } |
| |
| virtual Interval innerExtrema (const EvalContext&, const Interval&) const |
| { |
| return Interval(); // empty interval, i.e. no extrema |
| } |
| |
| virtual Interval applyPoint (const EvalContext& ctx, double arg0) const |
| { |
| const double exact = this->applyExact(arg0); |
| const double prec = this->precision(ctx, exact, arg0); |
| |
| return exact + Interval(-prec, prec); |
| } |
| |
| virtual double applyExact (double) const |
| { |
| TCU_THROW(InternalError, "Cannot apply"); |
| } |
| |
| virtual Interval getCodomain (const EvalContext&) const |
| { |
| return Interval::unbounded(true); |
| } |
| |
| virtual double precision (const EvalContext& ctx, double, double) const = 0; |
| }; |
| |
| /*Proper parameters for template T |
| Signature<double, double> 64bit tests |
| Signature<float, float> 32bit tests |
| Signature<float, deFloat16> 16bit tests*/ |
| template <class T> |
| class CFloatFunc1 : public FloatFunc1<T> |
| { |
| public: |
| CFloatFunc1 (const string& name, tcu::DoubleFunc1& func) |
| : m_name(name), m_func(func) {} |
| |
| string getName (void) const { return m_name; } |
| |
| protected: |
| double applyExact (double x) const { return m_func(x); } |
| |
| const string m_name; |
| tcu::DoubleFunc1& m_func; |
| }; |
| |
| //<Signature<float, deFloat16, deFloat16> > |
| //<Signature<float, float, float> > |
| //<Signature<double, double, double> > |
| template <class T> |
| class FloatFunc2 : public PrimitiveFunc<T> |
| { |
| protected: |
| Interval doApply (const EvalContext& ctx, const typename Signature<typename T::Ret, typename T::Arg0, typename T::Arg1>::IArgs& iargs) const |
| { |
| return this->applyMonotone(ctx, iargs.a, iargs.b); |
| } |
| |
| Interval applyMonotone (const EvalContext& ctx, |
| const Interval& xi, |
| const Interval& yi) const |
| { |
| Interval reti; |
| |
| TCU_INTERVAL_APPLY_MONOTONE2(reti, x, xi, y, yi, ret, |
| TCU_SET_INTERVAL(ret, point, |
| point = this->applyPoint(ctx, x, y))); |
| reti |= innerExtrema(ctx, xi, yi); |
| reti &= (this->getCodomain(ctx) | TCU_NAN); |
| |
| return ctx.format.convert(reti); |
| } |
| |
| virtual Interval innerExtrema (const EvalContext&, |
| const Interval&, |
| const Interval&) const |
| { |
| return Interval(); // empty interval, i.e. no extrema |
| } |
| |
| virtual Interval applyPoint (const EvalContext& ctx, |
| double x, |
| double y) const |
| { |
| const double exact = this->applyExact(x, y); |
| const double prec = this->precision(ctx, exact, x, y); |
| |
| return exact + Interval(-prec, prec); |
| } |
| |
| virtual double applyExact (double, double) const |
| { |
| TCU_THROW(InternalError, "Cannot apply"); |
| } |
| |
| virtual Interval getCodomain (const EvalContext&) const |
| { |
| return Interval::unbounded(true); |
| } |
| |
| virtual double precision (const EvalContext& ctx, |
| double ret, |
| double x, |
| double y) const = 0; |
| }; |
| |
| template <class T> |
| class CFloatFunc2 : public FloatFunc2<T> |
| { |
| public: |
| CFloatFunc2 (const string& name, |
| tcu::DoubleFunc2& func) |
| : m_name(name) |
| , m_func(func) |
| { |
| } |
| |
| string getName (void) const { return m_name; } |
| |
| protected: |
| double applyExact (double x, double y) const { return m_func(x, y); } |
| |
| const string m_name; |
| tcu::DoubleFunc2& m_func; |
| }; |
| |
| template <class T> |
| class InfixOperator : public FloatFunc2<T> |
| { |
| protected: |
| virtual string getSymbol (void) const = 0; |
| |
| void doPrint (ostream& os, const BaseArgExprs& args) const |
| { |
| os << "(" << *args[0] << " " << getSymbol() << " " << *args[1] << ")"; |
| } |
| |
| Interval applyPoint (const EvalContext& ctx, |
| double x, |
| double y) const |
| { |
| const double exact = this->applyExact(x, y); |
| |
| // Allow either representable number on both sides of the exact value, |
| // but require exactly representable values to be preserved. |
| return ctx.format.roundOut(exact, !deIsInf(x) && !deIsInf(y)); |
| } |
| |
| double precision (const EvalContext&, double, double, double) const |
| { |
| return 0.0; |
| } |
| }; |
| |
| class InfixOperator16Bit : public FloatFunc2 <Signature<float, deFloat16, deFloat16> > |
| { |
| protected: |
| virtual string getSymbol (void) const = 0; |
| |
| void doPrint (ostream& os, const BaseArgExprs& args) const |
| { |
| os << "(" << *args[0] << " " << getSymbol() << " " << *args[1] << ")"; |
| } |
| |
| Interval applyPoint (const EvalContext& ctx, |
| double x, |
| double y) const |
| { |
| const double exact = this->applyExact(x, y); |
| |
| // Allow either representable number on both sides of the exact value, |
| // but require exactly representable values to be preserved. |
| return ctx.format.roundOut(exact, !deIsInf(x) && !deIsInf(y)); |
| } |
| |
| double precision (const EvalContext&, double, double, double) const |
| { |
| return 0.0; |
| } |
| }; |
| |
| template <class T> |
| class FloatFunc3 : public PrimitiveFunc<T> |
| { |
| protected: |
| Interval doApply (const EvalContext& ctx, const typename Signature<typename T::Ret, typename T::Arg0, typename T::Arg1, typename T::Arg2>::IArgs& iargs) const |
| { |
| return this->applyMonotone(ctx, iargs.a, iargs.b, iargs.c); |
| } |
| |
| Interval applyMonotone (const EvalContext& ctx, |
| const Interval& xi, |
| const Interval& yi, |
| const Interval& zi) const |
| { |
| Interval reti; |
| TCU_INTERVAL_APPLY_MONOTONE3(reti, x, xi, y, yi, z, zi, ret, |
| TCU_SET_INTERVAL(ret, point, |
| point = this->applyPoint(ctx, x, y, z))); |
| return ctx.format.convert(reti); |
| } |
| |
| virtual Interval applyPoint (const EvalContext& ctx, |
| double x, |
| double y, |
| double z) const |
| { |
| const double exact = this->applyExact(x, y, z); |
| const double prec = this->precision(ctx, exact, x, y, z); |
| return exact + Interval(-prec, prec); |
| } |
| |
| virtual double applyExact (double, double, double) const |
| { |
| TCU_THROW(InternalError, "Cannot apply"); |
| } |
| |
| virtual double precision (const EvalContext& ctx, |
| double result, |
| double x, |
| double y, |
| double z) const = 0; |
| }; |
| |
| // We define syntactic sugar functions for expression constructors. Since |
| // these have the same names as ordinary mathematical operations (sin, log |
| // etc.), it's better to give them a dedicated namespace. |
| namespace Functions |
| { |
| |
| using namespace tcu; |
| |
| template <class T> |
| class Comparison : public InfixOperator < T > |
| { |
| public: |
| string getName (void) const { return "comparison"; } |
| string getSymbol (void) const { return ""; } |
| |
| SpirVCaseT getSpirvCase () const { return SPIRV_CASETYPE_COMPARE; } |
| |
| Interval doApply (const EvalContext& ctx, |
| const typename Comparison<T>::IArgs& iargs) const |
| { |
| DE_UNREF(ctx); |
| if (iargs.a.hasNaN() || iargs.b.hasNaN()) |
| { |
| return TCU_NAN; // one of the floats is NaN: block analysis |
| } |
| |
| int operationFlag = 1; |
| int result = 0; |
| const double a = iargs.a.midpoint(); |
| const double b = iargs.b.midpoint(); |
| |
| for (int i = 0; i<2; ++i) |
| { |
| if (a == b) |
| result += operationFlag; |
| operationFlag = operationFlag << 1; |
| |
| if (a > b) |
| result += operationFlag; |
| operationFlag = operationFlag << 1; |
| |
| if (a < b) |
| result += operationFlag; |
| operationFlag = operationFlag << 1; |
| |
| if (a >= b) |
| result += operationFlag; |
| operationFlag = operationFlag << 1; |
| |
| if (a <= b) |
| result += operationFlag; |
| operationFlag = operationFlag << 1; |
| } |
| return result; |
| } |
| }; |
| |
| template <class T> |
| class Add : public InfixOperator < T > |
| { |
| public: |
| string getName (void) const { return "add"; } |
| string getSymbol (void) const { return "+"; } |
| |
| Interval doApply (const EvalContext& ctx, |
| const typename Signature<typename T::Ret, typename T::Arg0, typename T::Arg1>::IArgs& iargs) const |
| { |
| // Fast-path for common case |
| if (iargs.a.isOrdinary(ctx.format.getMaxValue()) && iargs.b.isOrdinary(ctx.format.getMaxValue())) |
| { |
| Interval ret; |
| TCU_SET_INTERVAL_BOUNDS(ret, sum, |
| sum = iargs.a.lo() + iargs.b.lo(), |
| sum = iargs.a.hi() + iargs.b.hi()); |
| return ctx.format.convert(ctx.format.roundOut(ret, true)); |
| } |
| return this->applyMonotone(ctx, iargs.a, iargs.b); |
| } |
| |
| protected: |
| double applyExact (double x, double y) const { return x + y; } |
| }; |
| |
| template<class T> |
| class Mul : public InfixOperator<T> |
| { |
| public: |
| string getName (void) const { return "mul"; } |
| string getSymbol (void) const { return "*"; } |
| |
| Interval doApply (const EvalContext& ctx, const typename Signature<typename T::Ret, typename T::Arg0, typename T::Arg1>::IArgs& iargs) const |
| { |
| Interval a = iargs.a; |
| Interval b = iargs.b; |
| |
| // Fast-path for common case |
| if (a.isOrdinary(ctx.format.getMaxValue()) && b.isOrdinary(ctx.format.getMaxValue())) |
| { |
| Interval ret; |
| if (a.hi() < 0) |
| { |
| a = -a; |
| b = -b; |
| } |
| if (a.lo() >= 0 && b.lo() >= 0) |
| { |
| TCU_SET_INTERVAL_BOUNDS(ret, prod, |
| prod = a.lo() * b.lo(), |
| prod = a.hi() * b.hi()); |
| return ctx.format.convert(ctx.format.roundOut(ret, true)); |
| } |
| if (a.lo() >= 0 && b.hi() <= 0) |
| { |
| TCU_SET_INTERVAL_BOUNDS(ret, prod, |
| prod = a.hi() * b.lo(), |
| prod = a.lo() * b.hi()); |
| return ctx.format.convert(ctx.format.roundOut(ret, true)); |
| } |
| } |
| return this->applyMonotone(ctx, iargs.a, iargs.b); |
| } |
| |
| protected: |
| double applyExact (double x, double y) const { return x * y; } |
| |
| Interval innerExtrema(const EvalContext&, const Interval& xi, const Interval& yi) const |
| { |
| if (((xi.contains(-TCU_INFINITY) || xi.contains(TCU_INFINITY)) && yi.contains(0.0)) || |
| ((yi.contains(-TCU_INFINITY) || yi.contains(TCU_INFINITY)) && xi.contains(0.0))) |
| return Interval(TCU_NAN); |
| |
| return Interval(); |
| } |
| }; |
| |
| template<class T> |
| class Sub : public InfixOperator <T> |
| { |
| public: |
| string getName (void) const { return "sub"; } |
| string getSymbol (void) const { return "-"; } |
| |
| Interval doApply (const EvalContext& ctx, const typename Signature<typename T::Ret, typename T::Arg0, typename T::Arg1>::IArgs& iargs) const |
| { |
| // Fast-path for common case |
| if (iargs.a.isOrdinary(ctx.format.getMaxValue()) && iargs.b.isOrdinary(ctx.format.getMaxValue())) |
| { |
| Interval ret; |
| |
| TCU_SET_INTERVAL_BOUNDS(ret, diff, |
| diff = iargs.a.lo() - iargs.b.hi(), |
| diff = iargs.a.hi() - iargs.b.lo()); |
| return ctx.format.convert(ctx.format.roundOut(ret, true)); |
| |
| } |
| else |
| { |
| return this->applyMonotone(ctx, iargs.a, iargs.b); |
| } |
| } |
| |
| protected: |
| double applyExact (double x, double y) const { return x - y; } |
| }; |
| |
| template <class T> |
| class Negate : public FloatFunc1<T> |
| { |
| public: |
| string getName (void) const { return "_negate"; } |
| void doPrint (ostream& os, const BaseArgExprs& args) const { os << "-" << *args[0]; } |
| |
| protected: |
| double precision (const EvalContext&, double, double) const { return 0.0; } |
| double applyExact (double x) const { return -x; } |
| }; |
| |
| template <class T> |
| class Div : public InfixOperator<T> |
| { |
| public: |
| string getName (void) const { return "div"; } |
| |
| protected: |
| string getSymbol (void) const { return "/"; } |
| |
| Interval innerExtrema (const EvalContext&, |
| const Interval& nom, |
| const Interval& den) const |
| { |
| Interval ret; |
| |
| if (den.contains(0.0)) |
| { |
| if (nom.contains(0.0)) |
| ret |= TCU_NAN; |
| |
| if (nom.lo() < 0.0 || nom.hi() > 0.0) |
| ret |= Interval::unbounded(); |
| } |
| |
| return ret; |
| } |
| |
| double applyExact (double x, double y) const { return x / y; } |
| |
| Interval applyPoint (const EvalContext& ctx, double x, double y) const |
| { |
| Interval ret = FloatFunc2<T>::applyPoint(ctx, x, y); |
| |
| if (!deIsInf(x) && !deIsInf(y) && y != 0.0) |
| { |
| const Interval dst = ctx.format.convert(ret); |
| if (dst.contains(-TCU_INFINITY)) ret |= -ctx.format.getMaxValue(); |
| if (dst.contains(+TCU_INFINITY)) ret |= +ctx.format.getMaxValue(); |
| } |
| |
| return ret; |
| } |
| |
| double precision (const EvalContext& ctx, double ret, double, double den) const |
| { |
| const FloatFormat& fmt = ctx.format; |
| |
<
|