blob: 3dfb7873eac2e0f163e6c72b73e24c92f31f7190 [file] [log] [blame]
/*------------------------------------------------------------------------
* 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;
<