Commit 99bd5f40 by Olli Etuaho Committed by Commit Bot

Fix GLSL float parsing corner cases

This fixes parsing floats that are out-of-range, and floats that have more digits than the standard library float parsing functions can handle. In these cases, we now fall back to a custom implementation of float parsing. The custom parsing path can correctly process floats with up to hundreds of millions of digits in their mantissa part. Rounding behavior of the custom float parser may not be entirely consistent with the standard parser, but the error should be at most a few ULP. This can be considered acceptable since floating point operations are not expected to be exact in GLSL in general. Settling for lower accuracy also enables the parser to run in constant memory, instead of having to store all the significant digits of the decimal mantissa being parsed. BUG=angleproject:1613 TEST=angle_unittests Change-Id: I04a5d9ae5aaca48ef14b79cca5b997078614eb1c Reviewed-on: https://chromium-review.googlesource.com/412082 Commit-Queue: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 3377f51e
......@@ -8,17 +8,9 @@
#include <limits>
#include "common/utilities.h"
#include "compiler/preprocessor/numeric_lex.h"
#include "compiler/translator/SymbolTable.h"
#include "common/utilities.h"
bool strtof_clamp(const std::string &str, float *value)
{
bool success = pp::numeric_lex_float(str, value);
if (!success)
*value = std::numeric_limits<float>::max();
return success;
}
bool atoi_clamp(const char *str, unsigned int *value)
{
......@@ -31,6 +23,165 @@ bool atoi_clamp(const char *str, unsigned int *value)
namespace sh
{
float NumericLexFloat32OutOfRangeToInfinity(const std::string &str)
{
// Parses a decimal string using scientific notation into a floating point number.
// Out-of-range values are converted to infinity. Values that are too small to be
// represented are converted to zero.
// The mantissa in decimal scientific notation. The magnitude of the mantissa integer does not
// matter.
unsigned int decimalMantissa = 0;
size_t i = 0;
bool decimalPointSeen = false;
bool nonZeroSeenInMantissa = false;
// The exponent offset reflects the position of the decimal point.
int exponentOffset = -1;
while (i < str.length())
{
const char c = str[i];
if (c == 'e' || c == 'E')
{
break;
}
if (c == '.')
{
decimalPointSeen = true;
++i;
continue;
}
unsigned int digit = static_cast<unsigned int>(c - '0');
ASSERT(digit < 10u);
if (digit != 0u)
{
nonZeroSeenInMantissa = true;
}
if (nonZeroSeenInMantissa)
{
// Add bits to the mantissa until space runs out in 32-bit int. This should be
// enough precision to make the resulting binary mantissa accurate to 1 ULP.
if (decimalMantissa <= (std::numeric_limits<unsigned int>::max() - 9u) / 10u)
{
decimalMantissa = decimalMantissa * 10u + digit;
}
if (!decimalPointSeen)
{
++exponentOffset;
}
}
else if (decimalPointSeen)
{
--exponentOffset;
}
++i;
}
if (decimalMantissa == 0)
{
return 0.0f;
}
int exponent = 0;
if (i < str.length())
{
ASSERT(str[i] == 'e' || str[i] == 'E');
++i;
bool exponentOutOfRange = false;
bool negativeExponent = false;
if (str[i] == '-')
{
negativeExponent = true;
++i;
}
else if (str[i] == '+')
{
++i;
}
while (i < str.length())
{
const char c = str[i];
unsigned int digit = static_cast<unsigned int>(c - '0');
ASSERT(digit < 10u);
if (exponent <= (std::numeric_limits<int>::max() - 9) / 10)
{
exponent = exponent * 10 + digit;
}
else
{
exponentOutOfRange = true;
}
++i;
}
if (negativeExponent)
{
exponent = -exponent;
}
if (exponentOutOfRange)
{
if (negativeExponent)
{
return 0.0f;
}
else
{
return std::numeric_limits<float>::infinity();
}
}
}
// Do the calculation in 64-bit to avoid overflow.
long long exponentLong =
static_cast<long long>(exponent) + static_cast<long long>(exponentOffset);
if (exponentLong > std::numeric_limits<float>::max_exponent10)
{
return std::numeric_limits<float>::infinity();
}
else if (exponentLong < std::numeric_limits<float>::min_exponent10)
{
return 0.0f;
}
// The exponent is in range, so we need to actually evaluate the float.
exponent = static_cast<int>(exponentLong);
double value = decimalMantissa;
// Calculate the exponent offset to normalize the mantissa.
int normalizationExponentOffset = 0;
while (decimalMantissa >= 10u)
{
--normalizationExponentOffset;
decimalMantissa /= 10u;
}
// Apply the exponent.
value *= std::pow(10.0, static_cast<double>(exponent + normalizationExponentOffset));
if (value > static_cast<double>(std::numeric_limits<float>::max()))
{
return std::numeric_limits<float>::infinity();
}
if (value < static_cast<double>(std::numeric_limits<float>::min()))
{
return 0.0f;
}
return static_cast<float>(value);
}
bool strtof_clamp(const std::string &str, float *value)
{
// Try the standard float parsing path first.
bool success = pp::numeric_lex_float(str, value);
// If the standard path doesn't succeed, take the path that can handle the following corner
// cases:
// 1. The decimal mantissa is very small but the exponent is very large, putting the resulting
// number inside the float range.
// 2. The decimal mantissa is very large but the exponent is very small, putting the resulting
// number inside the float range.
// 3. The value is out-of-range and should be evaluated as infinity.
// 4. The value is too small and should be evaluated as zero.
// See ESSL 3.00.6 section 4.1.4 for the relevant specification.
if (!success)
*value = NumericLexFloat32OutOfRangeToInfinity(str);
return !gl::isInf(*value);
}
GLenum GLVariableType(const TType &type)
{
if (type.getBasicType() == EbtFloat)
......
......@@ -15,12 +15,6 @@
#include "compiler/translator/Operator.h"
#include "compiler/translator/Types.h"
// strtof_clamp is like strtof but
// 1. it forces C locale, i.e. forcing '.' as decimal point.
// 2. it clamps the value to -FLT_MAX or FLT_MAX if overflow happens.
// Return false if overflow happens.
bool strtof_clamp(const std::string &str, float *value);
// If overflow happens, clamp the value to UINT_MIN or UINT_MAX.
// Return false if overflow happens.
bool atoi_clamp(const char *str, unsigned int *value);
......@@ -29,6 +23,16 @@ namespace sh
{
class TSymbolTable;
float NumericLexFloat32OutOfRangeToInfinity(const std::string &str);
// strtof_clamp is like strtof but
// 1. it forces C locale, i.e. forcing '.' as decimal point.
// 2. it sets the value to infinity if overflow happens.
// 3. str should be guaranteed to be in the valid format for a floating point number as defined
// by the grammar in the ESSL 3.00.6 spec section 4.1.4.
// Return false if overflow happens.
bool strtof_clamp(const std::string &str, float *value);
GLenum GLVariableType(const TType &type);
GLenum GLVariablePrecision(const TType &type);
bool IsVaryingIn(TQualifier qualifier);
......
......@@ -48,6 +48,7 @@
'<(angle_path)/src/tests/compiler_tests/EmulateGLFragColorBroadcast_test.cpp',
'<(angle_path)/src/tests/compiler_tests/ExpressionLimit_test.cpp',
'<(angle_path)/src/tests/compiler_tests/EXT_blend_func_extended_test.cpp',
'<(angle_path)/src/tests/compiler_tests/FloatLex_test.cpp',
'<(angle_path)/src/tests/compiler_tests/FragDepth_test.cpp',
'<(angle_path)/src/tests/compiler_tests/GLSLCompatibilityOutput_test.cpp',
'<(angle_path)/src/tests/compiler_tests/IntermNode_test.cpp',
......
......@@ -1062,3 +1062,43 @@ TEST_F(ConstantFoldingTest, FoldShiftByZero)
ASSERT_TRUE(constantFoundInAST(3));
ASSERT_TRUE(constantFoundInAST(73));
}
// Test that folding IsInf results in true when the parameter is an out-of-range float literal.
// ESSL 3.00.6 section 4.1.4 Floats:
// "If the value of the floating point number is too large (small) to be stored as a single
// precision value, it is converted to positive (negative) infinity."
// ESSL 3.00.6 section 12.4:
// "Mandate support for signed infinities."
TEST_F(ConstantFoldingTest, FoldIsInfOutOfRangeFloatLiteral)
{
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 my_FragColor;\n"
"void main()\n"
"{\n"
" bool b = isinf(1.0e2048);\n"
" my_FragColor = vec4(b);\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(constantFoundInAST(true));
}
// Test that floats that are too small to be represented get flushed to zero.
// ESSL 3.00.6 section 4.1.4 Floats:
// "A value with a magnitude too small to be represented as a mantissa and exponent is converted to
// zero."
TEST_F(ConstantFoldingTest, FoldTooSmallFloat)
{
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 my_FragColor;\n"
"void main()\n"
"{\n"
" bool b = (0.0 == 1.0e-2048);\n"
" my_FragColor = vec4(b);\n"
"}\n";
compile(shaderString);
ASSERT_TRUE(constantFoundInAST(true));
}
//
// 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.
//
// FloatLex_test.cpp:
// Tests for parsing floats in GLSL source.
//
#include <sstream>
#include <string>
#include "common/debug.h"
#include "common/mathutil.h"
#include "compiler/translator/util.h"
#include "gtest/gtest.h"
namespace
{
class StrtofClampParser
{
public:
static float Parse(std::string str)
{
float value;
sh::strtof_clamp(str, &value);
return value;
}
};
// NumericLexFloat32OutOfRangeToInfinity usually only comes to play in corner cases of parsing, but
// it's useful to test that it works as expected across the whole range of floats.
class NumericLexFloatParser
{
public:
static float Parse(std::string str) { return sh::NumericLexFloat32OutOfRangeToInfinity(str); }
};
} // anonymous namespace
template <typename T>
class FloatLexTest : public ::testing::Test
{
public:
FloatLexTest() {}
protected:
void SetUp() override {}
void TearDown() override {}
static bool ParsedMatches(std::string str, float expected)
{
return (T::Parse(str) == expected);
}
static bool IsInfinity(std::string str)
{
float f = T::Parse(str);
return gl::isInf(f);
}
static std::string Zeros(size_t count) { return std::string(count, '0'); }
};
typedef ::testing::Types<StrtofClampParser, NumericLexFloatParser> FloatParserTypes;
TYPED_TEST_CASE(FloatLexTest, FloatParserTypes);
TYPED_TEST(FloatLexTest, One)
{
ASSERT_TRUE(TestFixture::ParsedMatches("1.0", 1.0f));
}
TYPED_TEST(FloatLexTest, Ten)
{
ASSERT_TRUE(TestFixture::ParsedMatches("10.0", 10.0f));
}
TYPED_TEST(FloatLexTest, TenScientific)
{
ASSERT_TRUE(TestFixture::ParsedMatches("1.0e1", 10.0f));
}
TYPED_TEST(FloatLexTest, ScientificWithSmallMantissa)
{
std::stringstream ss;
ss << "0." << TestFixture::Zeros(100) << "125e102";
ASSERT_TRUE(TestFixture::ParsedMatches(ss.str(), 12.5f));
}
TYPED_TEST(FloatLexTest, ScientificWithLargeMantissa)
{
std::stringstream ss;
ss << "9" << TestFixture::Zeros(100) << ".0e-100";
ASSERT_TRUE(TestFixture::ParsedMatches(ss.str(), 9.0f));
}
TYPED_TEST(FloatLexTest, ScientificWithVerySmallMantissa)
{
std::stringstream ss;
ss << "0." << TestFixture::Zeros(5000) << "125e5002";
ASSERT_TRUE(TestFixture::ParsedMatches(ss.str(), 12.5f));
}
TYPED_TEST(FloatLexTest, ScientificWithVeryLargeMantissa)
{
std::stringstream ss;
ss << "9" << TestFixture::Zeros(5000) << ".0e-5000";
ASSERT_TRUE(TestFixture::ParsedMatches(ss.str(), 9.0f));
}
TYPED_TEST(FloatLexTest, StartWithDecimalDot)
{
ASSERT_TRUE(TestFixture::ParsedMatches(".125", 0.125f));
}
TYPED_TEST(FloatLexTest, EndWithDecimalDot)
{
ASSERT_TRUE(TestFixture::ParsedMatches("123.", 123.0f));
}
TYPED_TEST(FloatLexTest, NoDecimalDot)
{
ASSERT_TRUE(TestFixture::ParsedMatches("125e-2", 1.25f));
}
TYPED_TEST(FloatLexTest, EndStartWithDecimalDotScientific)
{
ASSERT_TRUE(TestFixture::ParsedMatches(".625e-1", 0.0625f));
}
TYPED_TEST(FloatLexTest, EndWithDecimalDotScientific)
{
ASSERT_TRUE(TestFixture::ParsedMatches("102400.e-2", 1024.0f));
}
TYPED_TEST(FloatLexTest, UppercaseE)
{
ASSERT_TRUE(TestFixture::ParsedMatches("125E-2", 1.25f));
}
TYPED_TEST(FloatLexTest, PlusInExponent)
{
ASSERT_TRUE(TestFixture::ParsedMatches("1E+2", 100.0f));
}
TYPED_TEST(FloatLexTest, SlightlyAboveMaxFloat)
{
ASSERT_TRUE(TestFixture::IsInfinity("3.4029e38"));
}
TYPED_TEST(FloatLexTest, SlightlyBelowMaxFloat)
{
ASSERT_FALSE(TestFixture::IsInfinity("3.4028e38"));
}
TYPED_TEST(FloatLexTest, SlightlyBelowMinSubnormalFloat)
{
ASSERT_TRUE(TestFixture::ParsedMatches("1.0e-48", 0.0f));
}
TYPED_TEST(FloatLexTest, SlightlyAboveMinNormalFloat)
{
ASSERT_FALSE(TestFixture::ParsedMatches("1.0e-37", 0.0f));
}
TYPED_TEST(FloatLexTest, ManySignificantDigits)
{
ASSERT_TRUE(TestFixture::ParsedMatches("1.23456789", 1.23456789f));
}
TYPED_TEST(FloatLexTest, MantissaBitAboveMaxUint)
{
ASSERT_TRUE(TestFixture::ParsedMatches("4294967299.", 4294967299.0f));
}
TYPED_TEST(FloatLexTest, ExponentBitAboveMaxInt)
{
ASSERT_TRUE(TestFixture::IsInfinity("1.0e2147483649"));
}
TYPED_TEST(FloatLexTest, ExponentBitBelowMaxIntAndLargeMantissa)
{
std::stringstream ss;
ss << "1" << TestFixture::Zeros(32) << ".0e2147483640";
ASSERT_TRUE(TestFixture::IsInfinity(ss.str()));
}
TYPED_TEST(FloatLexTest, ExponentBitAboveMinIntAndSmallMantissa)
{
std::stringstream ss;
ss << "0." << TestFixture::Zeros(32) << "1e-2147483640";
ASSERT_TRUE(TestFixture::ParsedMatches(ss.str(), 0.0f));
}
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