Commit 2d73665d by Olli Etuaho Committed by Commit Bot

Handle constant folding arithmetic involving infinity

Constant folding arithmetic operations that involve infinity are now handled correctly in the cases where the result is infinity or zero. The implementation mostly relies on C++ to implement IEEE float arithmetic correctly so that unnecessary overhead is avoided. Constant folding arithmetic operations that result in overflow now issue a warning but result in infinity. This is not mandated by the spec but is a reasonable choice since it is the behavior of the default IEEE rounding mode. Constant folding arithmetic operations that result in NaN in IEEE will generate a warning but the NaN is kept. This is also not mandated by the spec, but is among the allowed behaviors. There's no special handling for ESSL 1.00. ESSL 1.00 doesn't really have the concept of NaN, but since it is not feasible to control generating NaNs at shader run time either way, it should not be a big issue if constant folding may generate them as well. TEST=angle_unittests BUG=chromium:661857 Change-Id: I06116c6fdd02f224939d4a651e4e62f2fd4c98a8 Reviewed-on: https://chromium-review.googlesource.com/414911Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
parent b5e997fb
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
#include "compiler/translator/ConstantUnion.h" #include "compiler/translator/ConstantUnion.h"
#include "base/numerics/safe_math.h"
#include "common/mathutil.h" #include "common/mathutil.h"
#include "compiler/translator/Diagnostics.h" #include "compiler/translator/Diagnostics.h"
...@@ -17,52 +16,46 @@ namespace sh ...@@ -17,52 +16,46 @@ namespace sh
namespace namespace
{ {
template <typename T> float CheckedSum(float lhs, float rhs, TDiagnostics *diag, const TSourceLoc &line)
T CheckedSum(base::CheckedNumeric<T> lhs,
base::CheckedNumeric<T> rhs,
TDiagnostics *diag,
const TSourceLoc &line)
{ {
ASSERT(lhs.IsValid() && rhs.IsValid()); float result = lhs + rhs;
auto result = lhs + rhs; if (gl::isNaN(result) && !gl::isNaN(lhs) && !gl::isNaN(rhs))
if (!result.IsValid())
{ {
diag->error(line, "Addition out of range", "*", ""); diag->warning(line, "Constant folded undefined addition generated NaN", "+", "");
return 0;
} }
return result.ValueOrDefault(0); else if (gl::isInf(result) && !gl::isInf(lhs) && !gl::isInf(rhs))
{
diag->warning(line, "Constant folded addition overflowed to infinity", "+", "");
}
return result;
} }
template <typename T> float CheckedDiff(float lhs, float rhs, TDiagnostics *diag, const TSourceLoc &line)
T CheckedDiff(base::CheckedNumeric<T> lhs,
base::CheckedNumeric<T> rhs,
TDiagnostics *diag,
const TSourceLoc &line)
{ {
ASSERT(lhs.IsValid() && rhs.IsValid()); float result = lhs - rhs;
auto result = lhs - rhs; if (gl::isNaN(result) && !gl::isNaN(lhs) && !gl::isNaN(rhs))
if (!result.IsValid()) {
diag->warning(line, "Constant folded undefined subtraction generated NaN", "-", "");
}
else if (gl::isInf(result) && !gl::isInf(lhs) && !gl::isInf(rhs))
{ {
diag->error(line, "Difference out of range", "*", ""); diag->warning(line, "Constant folded subtraction overflowed to infinity", "-", "");
return 0;
} }
return result.ValueOrDefault(0); return result;
} }
template <typename T> float CheckedMul(float lhs, float rhs, TDiagnostics *diag, const TSourceLoc &line)
T CheckedMul(base::CheckedNumeric<T> lhs,
base::CheckedNumeric<T> rhs,
TDiagnostics *diag,
const TSourceLoc &line)
{ {
ASSERT(lhs.IsValid() && rhs.IsValid()); float result = lhs * rhs;
auto result = lhs * rhs; if (gl::isNaN(result) && !gl::isNaN(lhs) && !gl::isNaN(rhs))
if (!result.IsValid()) {
diag->warning(line, "Constant folded undefined multiplication generated NaN", "*", "");
}
else if (gl::isInf(result) && !gl::isInf(lhs) && !gl::isInf(rhs))
{ {
diag->error(line, "Multiplication out of range", "*", ""); diag->warning(line, "Constant folded multiplication overflowed to infinity", "*", "");
return 0;
} }
return result.ValueOrDefault(0); return result;
} }
} // anonymous namespace } // anonymous namespace
...@@ -293,7 +286,7 @@ TConstantUnion TConstantUnion::add(const TConstantUnion &lhs, ...@@ -293,7 +286,7 @@ TConstantUnion TConstantUnion::add(const TConstantUnion &lhs,
returnValue.setUConst(gl::WrappingSum<unsigned int>(lhs.uConst, rhs.uConst)); returnValue.setUConst(gl::WrappingSum<unsigned int>(lhs.uConst, rhs.uConst));
break; break;
case EbtFloat: case EbtFloat:
returnValue.setFConst(CheckedSum<float>(lhs.fConst, rhs.fConst, diag, line)); returnValue.setFConst(CheckedSum(lhs.fConst, rhs.fConst, diag, line));
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
...@@ -319,7 +312,7 @@ TConstantUnion TConstantUnion::sub(const TConstantUnion &lhs, ...@@ -319,7 +312,7 @@ TConstantUnion TConstantUnion::sub(const TConstantUnion &lhs,
returnValue.setUConst(gl::WrappingDiff<unsigned int>(lhs.uConst, rhs.uConst)); returnValue.setUConst(gl::WrappingDiff<unsigned int>(lhs.uConst, rhs.uConst));
break; break;
case EbtFloat: case EbtFloat:
returnValue.setFConst(CheckedDiff<float>(lhs.fConst, rhs.fConst, diag, line)); returnValue.setFConst(CheckedDiff(lhs.fConst, rhs.fConst, diag, line));
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
...@@ -347,7 +340,7 @@ TConstantUnion TConstantUnion::mul(const TConstantUnion &lhs, ...@@ -347,7 +340,7 @@ TConstantUnion TConstantUnion::mul(const TConstantUnion &lhs,
returnValue.setUConst(lhs.uConst * rhs.uConst); returnValue.setUConst(lhs.uConst * rhs.uConst);
break; break;
case EbtFloat: case EbtFloat:
returnValue.setFConst(CheckedMul<float>(lhs.fConst, rhs.fConst, diag, line)); returnValue.setFConst(CheckedMul(lhs.fConst, rhs.fConst, diag, line));
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
......
...@@ -1312,19 +1312,51 @@ TConstantUnion *TIntermConstantUnion::foldBinary(TOperator op, ...@@ -1312,19 +1312,51 @@ TConstantUnion *TIntermConstantUnion::foldBinary(TOperator op,
switch (getType().getBasicType()) switch (getType().getBasicType())
{ {
case EbtFloat: case EbtFloat:
if (rightArray[i] == 0.0f) {
{ ASSERT(op == EOpDiv);
diagnostics->warning( float dividend = leftArray[i].getFConst();
getLine(), "Divide by zero error during constant folding", "/", ""); float divisor = rightArray[i].getFConst();
resultArray[i].setFConst(leftArray[i].getFConst() < 0 ? -FLT_MAX : FLT_MAX); if (divisor == 0.0f)
} {
else if (dividend == 0.0f)
{ {
ASSERT(op == EOpDiv); diagnostics->warning(
resultArray[i].setFConst(leftArray[i].getFConst() / rightArray[i].getFConst()); getLine(),
} "Zero divided by zero during constant folding generated NaN", "/",
break; "");
resultArray[i].setFConst(std::numeric_limits<float>::quiet_NaN());
}
else
{
diagnostics->warning(
getLine(), "Divide by zero during constant folding", "/", "");
bool negativeResult = std::signbit(dividend) != std::signbit(divisor);
resultArray[i].setFConst(
negativeResult ? -std::numeric_limits<float>::infinity()
: std::numeric_limits<float>::infinity());
}
}
else if (gl::isInf(dividend) && gl::isInf(divisor))
{
diagnostics->warning(
getLine(),
"Infinity divided by infinity during constant folding generated NaN",
"/", "");
resultArray[i].setFConst(std::numeric_limits<float>::quiet_NaN());
}
else
{
float result = dividend / divisor;
if (!gl::isInf(dividend) && gl::isInf(result))
{
diagnostics->warning(
getLine(), "Constant folded division overflowed to infinity", "/",
"");
}
resultArray[i].setFConst(result);
}
break;
}
case EbtInt: case EbtInt:
if (rightArray[i] == 0) if (rightArray[i] == 0)
{ {
......
...@@ -46,6 +46,8 @@ ...@@ -46,6 +46,8 @@
'<(angle_path)/src/tests/compiler_tests/API_test.cpp', '<(angle_path)/src/tests/compiler_tests/API_test.cpp',
'<(angle_path)/src/tests/compiler_tests/CollectVariables_test.cpp', '<(angle_path)/src/tests/compiler_tests/CollectVariables_test.cpp',
'<(angle_path)/src/tests/compiler_tests/ConstantFolding_test.cpp', '<(angle_path)/src/tests/compiler_tests/ConstantFolding_test.cpp',
'<(angle_path)/src/tests/compiler_tests/ConstantFoldingNaN_test.cpp',
'<(angle_path)/src/tests/compiler_tests/ConstantFoldingOverflow_test.cpp',
'<(angle_path)/src/tests/compiler_tests/ConstructCompiler_test.cpp', '<(angle_path)/src/tests/compiler_tests/ConstructCompiler_test.cpp',
'<(angle_path)/src/tests/compiler_tests/DebugShaderPrecision_test.cpp', '<(angle_path)/src/tests/compiler_tests/DebugShaderPrecision_test.cpp',
'<(angle_path)/src/tests/compiler_tests/EmulateGLFragColorBroadcast_test.cpp', '<(angle_path)/src/tests/compiler_tests/EmulateGLFragColorBroadcast_test.cpp',
......
//
// Copyright (c) 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// ConstantFoldingNaN_test.cpp:
// Tests for constant folding that results in NaN according to IEEE and should also generate a
// warning. The ESSL spec does not mandate generating NaNs, but this is reasonable behavior in
// this case.
//
#include "tests/test_utils/ConstantFoldingTest.h"
using namespace sh;
namespace
{
class ConstantFoldingNaNExpressionTest : public ConstantFoldingExpressionTest
{
public:
ConstantFoldingNaNExpressionTest() {}
void evaluateFloatNaN(const std::string &floatString)
{
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::quiet_NaN()));
ASSERT_TRUE(hasWarning());
}
};
} // anonymous namespace
// Test that infinity - infinity evaluates to NaN.
TEST_F(ConstantFoldingNaNExpressionTest, FoldInfinityMinusInfinity)
{
const std::string &floatString = "1.0e2048 - 1.0e2048";
evaluateFloatNaN(floatString);
}
// Test that infinity + negative infinity evaluates to NaN.
TEST_F(ConstantFoldingNaNExpressionTest, FoldInfinityPlusNegativeInfinity)
{
const std::string &floatString = "1.0e2048 + (-1.0e2048)";
evaluateFloatNaN(floatString);
}
// Test that infinity multiplied by zero evaluates to NaN.
TEST_F(ConstantFoldingNaNExpressionTest, FoldInfinityMultipliedByZero)
{
const std::string &floatString = "1.0e2048 * 0.0";
evaluateFloatNaN(floatString);
}
// Test that infinity divided by infinity evaluates to NaN.
TEST_F(ConstantFoldingNaNExpressionTest, FoldInfinityDividedByInfinity)
{
const std::string &floatString = "1.0e2048 / 1.0e2048";
evaluateFloatNaN(floatString);
}
// Test that zero divided by zero evaluates to NaN.
TEST_F(ConstantFoldingNaNExpressionTest, FoldZeroDividedByZero)
{
const std::string &floatString = "0.0 / 0.0";
evaluateFloatNaN(floatString);
}
//
// Copyright (c) 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// ConstantFoldingOverflow_test.cpp:
// Tests for constant folding that results in floating point overflow.
// In IEEE floating point, the overflow result depends on which of the various rounding modes is
// chosen - it's either the maximum representable value or infinity.
// ESSL 3.00.6 section 4.5.1 says that the rounding mode cannot be set and is undefined, so the
// result in this case is not defined by the spec.
// We decide to overflow to infinity and issue a warning.
//
#include "tests/test_utils/ConstantFoldingTest.h"
using namespace sh;
namespace
{
class ConstantFoldingOverflowExpressionTest : public ConstantFoldingExpressionTest
{
public:
ConstantFoldingOverflowExpressionTest() {}
void evaluateFloatOverflow(const std::string &floatString, bool positive)
{
evaluateFloat(floatString);
float expected = positive ? std::numeric_limits<float>::infinity()
: -std::numeric_limits<float>::infinity();
ASSERT_TRUE(constantFoundInAST(expected));
ASSERT_TRUE(hasWarning());
}
};
} // anonymous namespace
// Test that addition that overflows is evaluated correctly.
TEST_F(ConstantFoldingOverflowExpressionTest, Add)
{
const std::string &floatString = "2.0e38 + 2.0e38";
evaluateFloatOverflow(floatString, true);
}
// Test that subtraction that overflows is evaluated correctly.
TEST_F(ConstantFoldingOverflowExpressionTest, Subtract)
{
const std::string &floatString = "2.0e38 - (-2.0e38)";
evaluateFloatOverflow(floatString, true);
}
// Test that multiplication that overflows is evaluated correctly.
TEST_F(ConstantFoldingOverflowExpressionTest, Multiply)
{
const std::string &floatString = "1.0e30 * 1.0e10";
evaluateFloatOverflow(floatString, true);
}
// Test that division that overflows is evaluated correctly.
TEST_F(ConstantFoldingOverflowExpressionTest, Divide)
{
const std::string &floatString = "1.0e30 / 1.0e-10";
evaluateFloatOverflow(floatString, true);
}
...@@ -1158,3 +1158,89 @@ TEST_F(ConstantFoldingExpressionTest, FoldFaceForwardInfinity2) ...@@ -1158,3 +1158,89 @@ TEST_F(ConstantFoldingExpressionTest, FoldFaceForwardInfinity2)
evaluateFloat(floatString); evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(-4.0f)); ASSERT_TRUE(constantFoundInAST(-4.0f));
} }
// Test that infinity - finite value evaluates to infinity.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
TEST_F(ConstantFoldingExpressionTest, FoldInfinityMinusFinite)
{
const std::string &floatString = "1.0e2048 - 1.0e20";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
}
// Test that -infinity + finite value evaluates to -infinity.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
TEST_F(ConstantFoldingExpressionTest, FoldMinusInfinityPlusFinite)
{
const std::string &floatString = "(-1.0e2048) + 1.0e20";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
}
// Test that infinity * finite value evaluates to infinity.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
TEST_F(ConstantFoldingExpressionTest, FoldInfinityMultipliedByFinite)
{
const std::string &floatString = "1.0e2048 * 1.0e-20";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
}
// Test that infinity * infinity evaluates to infinity.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
TEST_F(ConstantFoldingExpressionTest, FoldInfinityMultipliedByInfinity)
{
const std::string &floatString = "1.0e2048 * 1.0e2048";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
}
// Test that infinity * negative infinity evaluates to negative infinity.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
TEST_F(ConstantFoldingExpressionTest, FoldInfinityMultipliedByNegativeInfinity)
{
const std::string &floatString = "1.0e2048 * (-1.0e2048)";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
}
// Test that dividing by minus zero results in the appropriately signed infinity.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
// "If both positive and negative zeros are implemented, the correctly signed Inf will be
// generated".
TEST_F(ConstantFoldingExpressionTest, FoldDivideByNegativeZero)
{
const std::string &floatString = "1.0 / (-0.0)";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
ASSERT_TRUE(hasWarning());
}
// Test that infinity divided by zero evaluates to infinity.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
TEST_F(ConstantFoldingExpressionTest, FoldInfinityDividedByZero)
{
const std::string &floatString = "1.0e2048 / 0.0";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(std::numeric_limits<float>::infinity()));
ASSERT_TRUE(hasWarning());
}
// Test that negative infinity divided by zero evaluates to negative infinity.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
TEST_F(ConstantFoldingExpressionTest, FoldMinusInfinityDividedByZero)
{
const std::string &floatString = "(-1.0e2048) / 0.0";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(-std::numeric_limits<float>::infinity()));
ASSERT_TRUE(hasWarning());
}
// Test that dividing a finite number by infinity results in zero.
// ESSL 3.00.6 section 4.5.1: "Infinities and zeroes are generated as dictated by IEEE".
TEST_F(ConstantFoldingExpressionTest, FoldDivideByInfinity)
{
const std::string &floatString = "1.0e30 / 1.0e2048";
evaluateFloat(floatString);
ASSERT_TRUE(constantFoundInAST(0.0f));
}
...@@ -45,6 +45,12 @@ void ConstantFoldingTest::compile(const std::string &shaderString) ...@@ -45,6 +45,12 @@ void ConstantFoldingTest::compile(const std::string &shaderString)
} }
} }
bool ConstantFoldingTest::hasWarning()
{
TInfoSink &infoSink = mTranslatorESSL->getInfoSink();
return infoSink.info.str().find("WARNING:") != std::string::npos;
}
void ConstantFoldingExpressionTest::evaluateFloat(const std::string &floatExpression) void ConstantFoldingExpressionTest::evaluateFloat(const std::string &floatExpression)
{ {
std::stringstream shaderStream; std::stringstream shaderStream;
......
...@@ -85,6 +85,11 @@ class ConstantFinder : public TIntermTraverser ...@@ -85,6 +85,11 @@ class ConstantFinder : public TIntermTraverser
{ {
return gl::isInf(node.getFConst()) && node.getFConst() < 0; return gl::isInf(node.getFConst()) && node.getFConst() < 0;
} }
else if (gl::isNaN(value))
{
// All NaNs are treated as equal.
return gl::isNaN(node.getFConst());
}
return mFaultTolerance >= fabsf(node.getFConst() - value); return mFaultTolerance >= fabsf(node.getFConst() - value);
} }
...@@ -139,6 +144,9 @@ class ConstantFoldingTest : public testing::Test ...@@ -139,6 +144,9 @@ class ConstantFoldingTest : public testing::Test
void compile(const std::string &shaderString); void compile(const std::string &shaderString);
// Must be called after compile()
bool hasWarning();
template <typename T> template <typename T>
bool constantFoundInAST(T constant) bool constantFoundInAST(T constant)
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment