diff --git a/src/tint/ast/float_literal_expression.cc b/src/tint/ast/float_literal_expression.cc index 5be5a19946..ab2d6cf3c6 100644 --- a/src/tint/ast/float_literal_expression.cc +++ b/src/tint/ast/float_literal_expression.cc @@ -22,15 +22,18 @@ TINT_INSTANTIATE_TYPEINFO(tint::ast::FloatLiteralExpression); namespace tint::ast { -FloatLiteralExpression::FloatLiteralExpression(ProgramID pid, const Source& src, float val) - : Base(pid, src), value(val) {} +FloatLiteralExpression::FloatLiteralExpression(ProgramID pid, + const Source& src, + double val, + Suffix suf) + : Base(pid, src), value(val), suffix(suf) {} FloatLiteralExpression::~FloatLiteralExpression() = default; const FloatLiteralExpression* FloatLiteralExpression::Clone(CloneContext* ctx) const { // Clone arguments outside of create() call to have deterministic ordering auto src = ctx->Clone(source); - return ctx->dst->create(src, value); + return ctx->dst->create(src, value, suffix); } } // namespace tint::ast diff --git a/src/tint/ast/float_literal_expression.h b/src/tint/ast/float_literal_expression.h index d1e1a6442a..321efc891c 100644 --- a/src/tint/ast/float_literal_expression.h +++ b/src/tint/ast/float_literal_expression.h @@ -24,11 +24,20 @@ namespace tint::ast { /// A float literal class FloatLiteralExpression final : public Castable { public: + /// Literal suffix + enum class Suffix { + /// No suffix + kNone, + /// 'f' suffix (f32) + kF, + }; + /// Constructor /// @param pid the identifier of the program that owns this node /// @param src the source of this node - /// @param value the float literals value - FloatLiteralExpression(ProgramID pid, const Source& src, float value); + /// @param val the literal value + /// @param suf the literal suffix + FloatLiteralExpression(ProgramID pid, const Source& src, double val, Suffix suf); ~FloatLiteralExpression() override; /// Clones this node and all transitive child nodes using the `CloneContext` @@ -37,8 +46,11 @@ class FloatLiteralExpression final : public Castable(47.2f); - ASSERT_TRUE(f->Is()); - EXPECT_EQ(f->value, 47.2f); +TEST_F(FloatLiteralExpressionTest, SuffixNone) { + auto* i = create(42.0, FloatLiteralExpression::Suffix::kNone); + ASSERT_TRUE(i->Is()); + EXPECT_EQ(i->value, 42); + EXPECT_EQ(i->suffix, FloatLiteralExpression::Suffix::kNone); +} + +TEST_F(FloatLiteralExpressionTest, SuffixF) { + auto* i = create(42.0, FloatLiteralExpression::Suffix::kF); + ASSERT_TRUE(i->Is()); + EXPECT_EQ(i->value, 42); + EXPECT_EQ(i->suffix, FloatLiteralExpression::Suffix::kF); } } // namespace diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc index bdc14b019b..36fcfb7c26 100644 --- a/src/tint/inspector/inspector.cc +++ b/src/tint/inspector/inspector.cc @@ -259,7 +259,7 @@ std::map Inspector::GetConstantIDs() { } if (auto* l = literal->As()) { - result[constant_id] = Scalar(l->value); + result[constant_id] = Scalar(static_cast(l->value)); continue; } diff --git a/src/tint/inspector/inspector_test.cc b/src/tint/inspector/inspector_test.cc index 8e3662b570..a9b133b0ac 100644 --- a/src/tint/inspector/inspector_test.cc +++ b/src/tint/inspector/inspector_test.cc @@ -1027,15 +1027,15 @@ TEST_F(InspectorGetConstantIDsTest, Float) { ASSERT_TRUE(result.find(20) != result.end()); EXPECT_TRUE(result[20].IsFloat()); - EXPECT_FLOAT_EQ(0.0, result[20].AsFloat()); + EXPECT_FLOAT_EQ(0.0f, result[20].AsFloat()); ASSERT_TRUE(result.find(300) != result.end()); EXPECT_TRUE(result[300].IsFloat()); - EXPECT_FLOAT_EQ(-10.0, result[300].AsFloat()); + EXPECT_FLOAT_EQ(-10.0f, result[300].AsFloat()); ASSERT_TRUE(result.find(4000) != result.end()); EXPECT_TRUE(result[4000].IsFloat()); - EXPECT_FLOAT_EQ(15.0, result[4000].AsFloat()); + EXPECT_FLOAT_EQ(15.0f, result[4000].AsFloat()); } TEST_F(InspectorGetConstantNameToIdMapTest, WithAndWithoutIds) { diff --git a/src/tint/number.h b/src/tint/number.h index 165a00a969..2eca79caa8 100644 --- a/src/tint/number.h +++ b/src/tint/number.h @@ -16,6 +16,7 @@ #define SRC_TINT_NUMBER_H_ #include +#include namespace tint { @@ -27,7 +28,13 @@ struct Number { /// Constructor. /// @param v the value to initialize this Number to - explicit Number(T v) : value(v) {} + template + explicit Number(U v) : value(static_cast(v)) {} + + /// Constructor. + /// @param v the value to initialize this Number to + template + explicit Number(Number v) : value(static_cast(v.value)) {} /// Conversion operator /// @returns the value as T @@ -47,25 +54,26 @@ struct Number { template bool operator==(Number a, Number b) { - return a.value == b.value; + using T = decltype(a.value + b.value); + return std::equal_to()(a.value, b.value); } template bool operator==(Number a, B b) { - return a.value == b; + return a == Number(b); } template bool operator==(A a, Number b) { - return a == b.value; + return Number(a) == b; } /// `i32` is a type alias to `Number`. using i32 = Number; /// `u32` is a type alias to `Number`. using u32 = Number; -/// `f32` is a type alias to `float` -using f32 = float; +/// `f32` is a type alias to `Number` +using f32 = Number; } // namespace tint @@ -81,6 +89,16 @@ inline u32 operator"" _u(unsigned long long int value) { // NOLINT return u32(static_cast(value)); } +/// Literal suffix for f32 literals +inline f32 operator"" _f(long double value) { // NOLINT + return f32(static_cast(value)); +} + +/// Literal suffix for f32 literals +inline f32 operator"" _f(unsigned long long int value) { // NOLINT + return f32(static_cast(value)); +} + } // namespace tint::number_suffixes #endif // SRC_TINT_NUMBER_H_ diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h index 088efc9561..5aeba3db9f 100644 --- a/src/tint/program_builder.h +++ b/src/tint/program_builder.h @@ -986,41 +986,58 @@ class ProgramBuilder { /// @param source the source information /// @param value the float value - /// @return a Scalar constructor for the given value - const ast::FloatLiteralExpression* Expr(const Source& source, f32 value) { - return create(source, value); + /// @return a unsuffixed FloatLiteralExpression for the float value + const ast::FloatLiteralExpression* Expr(const Source& source, float value) { + return create(source, static_cast(value), + ast::FloatLiteralExpression::Suffix::kNone); } /// @param value the float value - /// @return a Scalar constructor for the given value + /// @return a unsuffixed FloatLiteralExpression for the float value + const ast::FloatLiteralExpression* Expr(float value) { + return create(static_cast(value), + ast::FloatLiteralExpression::Suffix::kNone); + } + + /// @param source the source information + /// @param value the float value + /// @return a 'f'-suffixed FloatLiteralExpression for the f32 value + const ast::FloatLiteralExpression* Expr(const Source& source, f32 value) { + return create(source, static_cast(value.value), + ast::FloatLiteralExpression::Suffix::kF); + } + + /// @param value the float value + /// @return a 'f'-suffixed FloatLiteralExpression for the f32 value const ast::FloatLiteralExpression* Expr(f32 value) { - return create(value); + return create(static_cast(value.value), + ast::FloatLiteralExpression::Suffix::kF); } /// @param source the source information /// @param value the integer value - /// @return a 'i'-suffixed IntLiteralExpression for the given value + /// @return a signed 'i'-suffixed IntLiteralExpression for the i32 value const ast::IntLiteralExpression* Expr(const Source& source, i32 value) { return create(source, value, ast::IntLiteralExpression::Suffix::kI); } /// @param value the integer value - /// @return a 'i'-suffixed IntLiteralExpression for the given value + /// @return a signed 'i'-suffixed IntLiteralExpression for the i32 value const ast::IntLiteralExpression* Expr(i32 value) { return create(value, ast::IntLiteralExpression::Suffix::kI); } /// @param source the source information /// @param value the unsigned int value - /// @return a 'u'-suffixed IntLiteralExpression for the given value + /// @return an unsigned 'u'-suffixed IntLiteralExpression for the u32 value const ast::IntLiteralExpression* Expr(const Source& source, u32 value) { return create(source, value, ast::IntLiteralExpression::Suffix::kU); } /// @param value the unsigned int value - /// @return a 'u'-suffixed IntLiteralExpression for the given value + /// @return an unsigned 'u'-suffixed IntLiteralExpression for the u32 value const ast::IntLiteralExpression* Expr(u32 value) { return create(value, ast::IntLiteralExpression::Suffix::kU); } diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc index b416a6a9ad..4a96c7f336 100644 --- a/src/tint/reader/spirv/function.cc +++ b/src/tint/reader/spirv/function.cc @@ -2499,7 +2499,8 @@ TypedExpression FunctionEmitter::MakeExpression(uint32_t id) { return source_expr; } case SkipReason::kPointSizeBuiltinValue: { - return {ty_.F32(), create(Source{}, 1.0f)}; + return {ty_.F32(), create( + Source{}, 1.0, ast::FloatLiteralExpression::Suffix::kF)}; } case SkipReason::kPointSizeBuiltinPointer: Fail() << "unhandled use of a pointer to the PointSize builtin, with ID: " << id; @@ -3982,13 +3983,13 @@ TypedExpression FunctionEmitter::EmitGlslStd450ExtInst(const spvtools::opt::Inst return {}; } const Type* f32 = eta.type; - return {f32, - builder_.MemberAccessor( - builder_.Call(Source{}, "refract", - ast::ExpressionList{ - builder_.vec2(incident.expr, 0.0f), - builder_.vec2(normal.expr, 0.0f), eta.expr}), - "x")}; + return {f32, builder_.MemberAccessor( + builder_.Call( + Source{}, "refract", + ast::ExpressionList{ + builder_.vec2(incident.expr, 0.0f), + builder_.vec2(normal.expr, 0.0f), eta.expr}), + "x")}; } default: break; diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc index 28dc8569a5..bdc52e695e 100644 --- a/src/tint/reader/spirv/parser_impl.cc +++ b/src/tint/reader/spirv/parser_impl.cc @@ -1343,7 +1343,9 @@ bool ParserImpl::EmitScalarSpecConstants() { float float_value; // Copy the bits so we can read them as a float. std::memcpy(&float_value, &literal_value, sizeof(float_value)); - return create(Source{}, float_value); + return create( + Source{}, static_cast(float_value), + ast::FloatLiteralExpression::Suffix::kF); }); if (ast_expr == nullptr) { return Fail() << " invalid result type for OpSpecConstant " @@ -1905,18 +1907,22 @@ TypedExpression ParserImpl::MakeConstantExpressionForScalarSpirvConstant( return Switch( ast_type, [&](const I32*) { - return TypedExpression{ty_.I32(), create( - source, spirv_const->GetS32(), - ast::IntLiteralExpression::Suffix::kI)}; + return TypedExpression{ty_.I32(), + create( + source, static_cast(spirv_const->GetS32()), + ast::IntLiteralExpression::Suffix::kI)}; }, [&](const U32*) { - return TypedExpression{ty_.U32(), create( - source, spirv_const->GetU32(), - ast::IntLiteralExpression::Suffix::kU)}; + return TypedExpression{ty_.U32(), + create( + source, static_cast(spirv_const->GetU32()), + ast::IntLiteralExpression::Suffix::kU)}; }, [&](const F32*) { - return TypedExpression{ - ty_.F32(), create(source, spirv_const->GetFloat())}; + return TypedExpression{ty_.F32(), + create( + source, static_cast(spirv_const->GetFloat()), + ast::FloatLiteralExpression::Suffix::kF)}; }, [&](const Bool*) { const bool value = @@ -1953,7 +1959,10 @@ const ast::Expression* ParserImpl::MakeNullValue(const Type* type) { return create(Source{}, 0, ast::IntLiteralExpression::Suffix::kU); }, - [&](const F32*) { return create(Source{}, 0.0f); }, + [&](const F32*) { + return create(Source{}, 0, + ast::FloatLiteralExpression::Suffix::kF); + }, [&](const Vector*) { return builder_.Construct(Source{}, type->Build(builder_)); }, [&](const Matrix*) { return builder_.Construct(Source{}, type->Build(builder_)); }, [&](const Array*) { return builder_.Construct(Source{}, type->Build(builder_)); }, diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc index d9a9f34540..1feddc5915 100644 --- a/src/tint/reader/wgsl/lexer.cc +++ b/src/tint/reader/wgsl/lexer.cc @@ -94,7 +94,7 @@ enum class LimitCheck { template LimitCheck check_limits(int64_t value) { static_assert(std::is_integral_v, "T must be an integer"); - if (value < static_cast(std::numeric_limits::min())) { + if (value < static_cast(std::numeric_limits::lowest())) { return LimitCheck::kTooSmall; } if (value > static_cast(std::numeric_limits::max())) { @@ -103,6 +103,19 @@ LimitCheck check_limits(int64_t value) { return LimitCheck::kWithinLimits; } +/// Checks whether the value fits within the floating point type `T` +template +LimitCheck check_limits(double value) { + static_assert(std::is_floating_point_v, "T must be a floating point"); + if (value < static_cast(std::numeric_limits::lowest())) { + return LimitCheck::kTooSmall; + } + if (value > static_cast(std::numeric_limits::max())) { + return LimitCheck::kTooLarge; + } + return LimitCheck::kWithinLimits; +} + } // namespace Lexer::Lexer(const Source::File* file) : file_(file), location_{1, 1} {} @@ -378,23 +391,40 @@ Token Lexer::try_float() { advance(end - start); end_source(source); - auto res = strtod(&at(start), nullptr); - // This errors out if a non-zero magnitude is too small to represent in a - // float. It can't be represented faithfully in an f32. - const auto magnitude = std::fabs(res); - if (0.0 < magnitude && magnitude < static_cast(std::numeric_limits::min())) { - return {Token::Type::kError, source, - "f32 (" + str + ") magnitude too small, not representable"}; - } - // This handles if the number is really large negative number - if (res < static_cast(std::numeric_limits::lowest())) { - return {Token::Type::kError, source, "f32 (" + str + ") too large (negative)"}; - } - if (res > static_cast(std::numeric_limits::max())) { - return {Token::Type::kError, source, "f32 (" + str + ") too large (positive)"}; + double value = strtod(&at(start), nullptr); + const double magnitude = std::abs(value); + + if (has_f_suffix) { + // This errors out if a non-zero magnitude is too small to represent in a + // float. It can't be represented faithfully in an f32. + if (0.0 < magnitude && magnitude < static_cast(std::numeric_limits::min())) { + return {Token::Type::kError, source, "magnitude too small to be represented as f32"}; + } + switch (check_limits(value)) { + case LimitCheck::kTooSmall: + return {Token::Type::kError, source, "value too small for f32"}; + case LimitCheck::kTooLarge: + return {Token::Type::kError, source, "value too large for f32"}; + default: + return {Token::Type::kFloatFLiteral, source, value}; + } } - return {source, static_cast(res)}; + // TODO(crbug.com/tint/1504): Properly support abstract float: + // Change `AbstractFloatType` to `double`, update errors to say 'abstract int'. + using AbstractFloatType = float; + if (0.0 < magnitude && + magnitude < static_cast(std::numeric_limits::min())) { + return {Token::Type::kError, source, "magnitude too small to be represented as f32"}; + } + switch (check_limits(value)) { + case LimitCheck::kTooSmall: + return {Token::Type::kError, source, "value too small for f32"}; + case LimitCheck::kTooLarge: + return {Token::Type::kError, source, "value too large for f32"}; + default: + return {Token::Type::kFloatLiteral, source, value}; + } } Token Lexer::try_hex_float() { @@ -566,6 +596,7 @@ Token Lexer::try_hex_float() { uint32_t input_exponent = 0; // Defaults to 0 if not present int32_t exponent_sign = 1; // If the 'p' part is present, the rest of the exponent must exist. + bool has_f_suffix = false; if (has_exponent) { // Parse the rest of the exponent. // (+|-)? @@ -597,6 +628,7 @@ Token Lexer::try_hex_float() { // when the exponent is present. Otherwise it will look like // one of the mantissa digits. if (end < length() && matches(end, "f")) { + has_f_suffix = true; end++; } @@ -680,25 +712,22 @@ Token Lexer::try_hex_float() { result_u32 |= (static_cast(signed_exponent) & kExponentMask) << kExponentLeftShift; // Reinterpret as float and return - float result; - std::memcpy(&result, &result_u32, sizeof(result)); - return {source, static_cast(result)}; + float result_f32; + std::memcpy(&result_f32, &result_u32, sizeof(result_f32)); + double result_f64 = static_cast(result_f32); + return {has_f_suffix ? Token::Type::kFloatFLiteral : Token::Type::kFloatLiteral, source, + result_f64}; } -Token Lexer::build_token_from_int_if_possible(Source source, - size_t start, - size_t end, - int32_t base) { +Token Lexer::build_token_from_int_if_possible(Source source, size_t start, int32_t base) { int64_t res = strtoll(&at(start), nullptr, base); - auto str = [&] { return std::string{substr(start, end - start)}; }; - if (matches(pos(), "u")) { switch (check_limits(res)) { case LimitCheck::kTooSmall: return {Token::Type::kError, source, "unsigned literal cannot be negative"}; case LimitCheck::kTooLarge: - return {Token::Type::kError, source, str() + " too large for u32"}; + return {Token::Type::kError, source, "value too large for u32"}; default: advance(1); end_source(source); @@ -709,9 +738,9 @@ Token Lexer::build_token_from_int_if_possible(Source source, if (matches(pos(), "i")) { switch (check_limits(res)) { case LimitCheck::kTooSmall: - return {Token::Type::kError, source, str() + " too small for i32"}; + return {Token::Type::kError, source, "value too small for i32"}; case LimitCheck::kTooLarge: - return {Token::Type::kError, source, str() + " too large for i32"}; + return {Token::Type::kError, source, "value too large for i32"}; default: break; } @@ -725,9 +754,9 @@ Token Lexer::build_token_from_int_if_possible(Source source, using AbstractIntType = int32_t; switch (check_limits(res)) { case LimitCheck::kTooSmall: - return {Token::Type::kError, source, str() + " too small for i32"}; + return {Token::Type::kError, source, "value too small for i32"}; case LimitCheck::kTooLarge: - return {Token::Type::kError, source, str() + " too large for i32"}; + return {Token::Type::kError, source, "value too large for i32"}; default: end_source(source); return {Token::Type::kIntLiteral, source, res}; @@ -769,7 +798,7 @@ Token Lexer::try_hex_integer() { advance(end - start); - return build_token_from_int_if_possible(source, start, end, 16); + return build_token_from_int_if_possible(source, start, 16); } Token Lexer::try_integer() { @@ -812,7 +841,7 @@ Token Lexer::try_integer() { advance(end - start); - return build_token_from_int_if_possible(source, start, end, 10); + return build_token_from_int_if_possible(source, start, 10); } Token Lexer::try_ident() { diff --git a/src/tint/reader/wgsl/lexer.h b/src/tint/reader/wgsl/lexer.h index 11ac9cf1d2..d93848ff5d 100644 --- a/src/tint/reader/wgsl/lexer.h +++ b/src/tint/reader/wgsl/lexer.h @@ -43,7 +43,8 @@ class Lexer { /// @returns uninitialized token on success, or error Token skip_comment(); - Token build_token_from_int_if_possible(Source source, size_t start, size_t end, int32_t base); + Token build_token_from_int_if_possible(Source source, size_t start, int32_t base); + Token check_keyword(const Source&, std::string_view); /// The try_* methods have the following in common: diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc index 6bcea15fc8..219990db46 100644 --- a/src/tint/reader/wgsl/lexer_test.cc +++ b/src/tint/reader/wgsl/lexer_test.cc @@ -303,7 +303,7 @@ TEST_F(LexerTest, Null_InIdentifier_IsError) { struct FloatData { const char* input; - float result; + double result; }; inline std::ostream& operator<<(std::ostream& out, FloatData data) { out << std::string(data.input); @@ -316,8 +316,12 @@ TEST_P(FloatTest, Parse) { Lexer l(&file); auto t = l.next(); - EXPECT_TRUE(t.Is(Token::Type::kFloatLiteral)); - EXPECT_EQ(t.to_f32(), params.result); + if (std::string(params.input).back() == 'f') { + EXPECT_TRUE(t.Is(Token::Type::kFloatFLiteral)); + } else { + EXPECT_TRUE(t.Is(Token::Type::kFloatLiteral)); + } + EXPECT_EQ(t.to_f64(), params.result); EXPECT_EQ(t.source().range.begin.line, 1u); EXPECT_EQ(t.source().range.begin.column, 1u); EXPECT_EQ(t.source().range.end.line, 1u); @@ -330,63 +334,63 @@ INSTANTIATE_TEST_SUITE_P(LexerTest, FloatTest, testing::Values( // No decimal, with 'f' suffix - FloatData{"0f", 0.0f}, - FloatData{"1f", 1.0f}, - FloatData{"-0f", 0.0f}, - FloatData{"-1f", -1.0f}, + FloatData{"0f", 0.0}, + FloatData{"1f", 1.0}, + FloatData{"-0f", 0.0}, + FloatData{"-1f", -1.0}, // Zero, with decimal. - FloatData{"0.0", 0.0f}, - FloatData{"0.", 0.0f}, - FloatData{".0", 0.0f}, - FloatData{"-0.0", 0.0f}, - FloatData{"-0.", 0.0f}, - FloatData{"-.0", 0.0f}, + FloatData{"0.0", 0.0}, + FloatData{"0.", 0.0}, + FloatData{".0", 0.0}, + FloatData{"-0.0", 0.0}, + FloatData{"-0.", 0.0}, + FloatData{"-.0", 0.0}, // Zero, with decimal and 'f' suffix - FloatData{"0.0f", 0.0f}, - FloatData{"0.f", 0.0f}, - FloatData{".0f", 0.0f}, - FloatData{"-0.0f", 0.0f}, - FloatData{"-0.f", 0.0f}, - FloatData{"-.0", 0.0f}, + FloatData{"0.0f", 0.0}, + FloatData{"0.f", 0.0}, + FloatData{".0f", 0.0}, + FloatData{"-0.0f", 0.0}, + FloatData{"-0.f", 0.0}, + FloatData{"-.0", 0.0}, // Non-zero with decimal - FloatData{"5.7", 5.7f}, - FloatData{"5.", 5.f}, - FloatData{".7", .7f}, - FloatData{"-5.7", -5.7f}, - FloatData{"-5.", -5.f}, - FloatData{"-.7", -.7f}, + FloatData{"5.7", 5.7}, + FloatData{"5.", 5.}, + FloatData{".7", .7}, + FloatData{"-5.7", -5.7}, + FloatData{"-5.", -5.}, + FloatData{"-.7", -.7}, // Non-zero with decimal and 'f' suffix - FloatData{"5.7f", 5.7f}, - FloatData{"5.f", 5.f}, - FloatData{".7f", .7f}, - FloatData{"-5.7f", -5.7f}, - FloatData{"-5.f", -5.f}, - FloatData{"-.7f", -.7f}, + FloatData{"5.7f", 5.7}, + FloatData{"5.f", 5.}, + FloatData{".7f", .7}, + FloatData{"-5.7f", -5.7}, + FloatData{"-5.f", -5.}, + FloatData{"-.7f", -.7}, // No decimal, with exponent - FloatData{"1e5", 1e5f}, - FloatData{"1E5", 1e5f}, - FloatData{"1e-5", 1e-5f}, - FloatData{"1E-5", 1e-5f}, + FloatData{"1e5", 1e5}, + FloatData{"1E5", 1e5}, + FloatData{"1e-5", 1e-5}, + FloatData{"1E-5", 1e-5}, // No decimal, with exponent and 'f' suffix - FloatData{"1e5f", 1e5f}, - FloatData{"1E5f", 1e5f}, - FloatData{"1e-5f", 1e-5f}, - FloatData{"1E-5f", 1e-5f}, + FloatData{"1e5f", 1e5}, + FloatData{"1E5f", 1e5}, + FloatData{"1e-5f", 1e-5}, + FloatData{"1E-5f", 1e-5}, // With decimal and exponents - FloatData{"0.2e+12", 0.2e12f}, - FloatData{"1.2e-5", 1.2e-5f}, - FloatData{"2.57e23", 2.57e23f}, - FloatData{"2.5e+0", 2.5f}, - FloatData{"2.5e-0", 2.5f}, + FloatData{"0.2e+12", 0.2e12}, + FloatData{"1.2e-5", 1.2e-5}, + FloatData{"2.57e23", 2.57e23}, + FloatData{"2.5e+0", 2.5}, + FloatData{"2.5e-0", 2.5}, // With decimal and exponents and 'f' suffix - FloatData{"0.2e+12f", 0.2e12f}, - FloatData{"1.2e-5f", 1.2e-5f}, - FloatData{"2.57e23f", 2.57e23f}, - FloatData{"2.5e+0f", 2.5f}, - FloatData{"2.5e-0f", 2.5f})); + FloatData{"0.2e+12f", 0.2e12}, + FloatData{"1.2e-5f", 1.2e-5}, + FloatData{"2.57e23f", 2.57e23}, + FloatData{"2.5e+0f", 2.5}, + FloatData{"2.5e-0f", 2.5})); using FloatTest_Invalid = testing::TestWithParam; TEST_P(FloatTest_Invalid, Handles) { @@ -676,7 +680,7 @@ TEST_F(LexerTest, IntegerTest_HexSignedTooLarge) { auto t = l.next(); ASSERT_TRUE(t.Is(Token::Type::kError)); - EXPECT_EQ(t.to_str(), "0x80000000 too large for i32"); + EXPECT_EQ(t.to_str(), "value too large for i32"); } TEST_F(LexerTest, IntegerTest_HexSignedTooSmall) { @@ -685,7 +689,7 @@ TEST_F(LexerTest, IntegerTest_HexSignedTooSmall) { auto t = l.next(); ASSERT_TRUE(t.Is(Token::Type::kError)); - EXPECT_EQ(t.to_str(), "-0x8000000F too small for i32"); + EXPECT_EQ(t.to_str(), "value too small for i32"); } TEST_F(LexerTest, IntegerTest_HexSignedTooManyDigits) { diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc index 8661a00a98..f3ce4977ff 100644 --- a/src/tint/reader/wgsl/parser_impl.cc +++ b/src/tint/reader/wgsl/parser_impl.cc @@ -2790,7 +2790,12 @@ Maybe ParserImpl::const_literal() { ast::IntLiteralExpression::Suffix::kU); } if (match(Token::Type::kFloatLiteral)) { - return create(t.source(), t.to_f32()); + return create(t.source(), t.to_f64(), + ast::FloatLiteralExpression::Suffix::kNone); + } + if (match(Token::Type::kFloatFLiteral)) { + return create(t.source(), t.to_f64(), + ast::FloatLiteralExpression::Suffix::kF); } if (match(Token::Type::kTrue)) { return create(t.source(), true); diff --git a/src/tint/reader/wgsl/parser_impl_const_expr_test.cc b/src/tint/reader/wgsl/parser_impl_const_expr_test.cc index 9d58058c4b..80536bd6e6 100644 --- a/src/tint/reader/wgsl/parser_impl_const_expr_test.cc +++ b/src/tint/reader/wgsl/parser_impl_const_expr_test.cc @@ -31,10 +31,14 @@ TEST_F(ParserImplTest, ConstExpr_TypeDecl) { ASSERT_EQ(t->args.size(), 2u); ASSERT_TRUE(t->args[0]->Is()); - EXPECT_FLOAT_EQ(t->args[0]->As()->value, 1.); + EXPECT_DOUBLE_EQ(t->args[0]->As()->value, 1.); + EXPECT_EQ(t->args[0]->As()->suffix, + ast::FloatLiteralExpression::Suffix::kNone); ASSERT_TRUE(t->args[1]->Is()); - EXPECT_FLOAT_EQ(t->args[1]->As()->value, 2.); + EXPECT_DOUBLE_EQ(t->args[1]->As()->value, 2.); + EXPECT_EQ(t->args[1]->As()->suffix, + ast::FloatLiteralExpression::Suffix::kNone); } TEST_F(ParserImplTest, ConstExpr_TypeDecl_Empty) { diff --git a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc index 3e3709270c..cbddce3e4f 100644 --- a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc +++ b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc @@ -26,7 +26,7 @@ namespace { // - 0 sign if sign is 0, 1 otherwise // - 'exponent_bits' is placed in the exponent space. // So, the exponent bias must already be included. -float MakeFloat(int sign, int biased_exponent, int mantissa) { +float MakeFloat(uint32_t sign, uint32_t biased_exponent, uint32_t mantissa) { const uint32_t sign_bit = sign ? 0x80000000u : 0u; // The binary32 exponent is 8 bits, just below the sign. const uint32_t exponent_bits = (biased_exponent & 0xffu) << 23; @@ -41,6 +41,25 @@ float MakeFloat(int sign, int biased_exponent, int mantissa) { return result; } +// Makes an IEEE 754 binary64 floating point number with +// - 0 sign if sign is 0, 1 otherwise +// - 'exponent_bits' is placed in the exponent space. +// So, the exponent bias must already be included. +double MakeDouble(uint64_t sign, uint64_t biased_exponent, uint64_t mantissa) { + const uint64_t sign_bit = sign ? 0x8000000000000000u : 0u; + // The binary64 exponent is 11 bits, just below the sign. + const uint64_t exponent_bits = (biased_exponent & 0x7FFull) << 52; + // The mantissa is the bottom 52 bits. + const uint64_t mantissa_bits = (mantissa & 0xFFFFFFFFFFFFFull); + + uint64_t bits = sign_bit | exponent_bits | mantissa_bits; + double result = 0.0; + static_assert(sizeof(result) == sizeof(bits), + "expected double and uint64_t to be the same size"); + std::memcpy(&result, &bits, sizeof(bits)); + return result; +} + TEST_F(ParserImplTest, ConstLiteral_Int) { { auto p = parser("234"); @@ -126,10 +145,26 @@ TEST_F(ParserImplTest, ConstLiteral_Float) { EXPECT_FALSE(p->has_error()) << p->error(); ASSERT_NE(c.value, nullptr); ASSERT_TRUE(c->Is()); - EXPECT_FLOAT_EQ(c->As()->value, 234e12f); + EXPECT_DOUBLE_EQ(c->As()->value, 234e12); + EXPECT_EQ(c->As()->suffix, + ast::FloatLiteralExpression::Suffix::kNone); EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 8u}})); } +TEST_F(ParserImplTest, ConstLiteral_FloatF) { + auto p = parser("234.e12f"); + auto c = p->const_literal(); + EXPECT_TRUE(c.matched); + EXPECT_FALSE(c.errored); + EXPECT_FALSE(p->has_error()) << p->error(); + ASSERT_NE(c.value, nullptr); + ASSERT_TRUE(c->Is()); + EXPECT_DOUBLE_EQ(c->As()->value, 234e12); + EXPECT_EQ(c->As()->suffix, + ast::FloatLiteralExpression::Suffix::kF); + EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 9u}})); +} + TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_IncompleteExponent) { auto p = parser("1.0e+"); auto c = p->const_literal(); @@ -144,7 +179,7 @@ TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooSmallMagnitude) { auto c = p->const_literal(); EXPECT_FALSE(c.matched); EXPECT_TRUE(c.errored); - EXPECT_EQ(p->error(), "1:1: f32 (1e-256) magnitude too small, not representable"); + EXPECT_EQ(p->error(), "1:1: magnitude too small to be represented as f32"); ASSERT_EQ(c.value, nullptr); } @@ -153,7 +188,7 @@ TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooLargeNegative) { auto c = p->const_literal(); EXPECT_FALSE(c.matched); EXPECT_TRUE(c.errored); - EXPECT_EQ(p->error(), "1:1: f32 (-1.2e+256) too large (negative)"); + EXPECT_EQ(p->error(), "1:1: value too small for f32"); ASSERT_EQ(c.value, nullptr); } @@ -162,22 +197,15 @@ TEST_F(ParserImplTest, ConstLiteral_InvalidFloat_TooLargePositive) { auto c = p->const_literal(); EXPECT_FALSE(c.matched); EXPECT_TRUE(c.errored); - EXPECT_EQ(p->error(), "1:1: f32 (1.2e+256) too large (positive)"); + EXPECT_EQ(p->error(), "1:1: value too large for f32"); ASSERT_EQ(c.value, nullptr); } -// Returns true if the given non-Nan float numbers are equal. -bool FloatEqual(float a, float b) { - // Avoid Clang complaining about equality test on float. - // -Wfloat-equal. - return (a <= b) && (a >= b); -} - struct FloatLiteralTestCase { std::string input; - float expected; + double expected; bool operator==(const FloatLiteralTestCase& other) const { - return (input == other.input) && FloatEqual(expected, other.expected); + return (input == other.input) && std::equal_to()(expected, other.expected); } }; @@ -197,22 +225,28 @@ TEST_P(ParserImplFloatLiteralTest, Parse) { EXPECT_FALSE(p->has_error()) << p->error(); ASSERT_NE(c.value, nullptr); ASSERT_TRUE(c->Is()); - EXPECT_FLOAT_EQ(c->As()->value, params.expected); + EXPECT_DOUBLE_EQ(c->As()->value, params.expected); + if (params.input.back() == 'f') { + EXPECT_EQ(c->As()->suffix, + ast::FloatLiteralExpression::Suffix::kF); + } else { + EXPECT_EQ(c->As()->suffix, + ast::FloatLiteralExpression::Suffix::kNone); + } } - using FloatLiteralTestCaseList = std::vector; FloatLiteralTestCaseList DecimalFloatCases() { return FloatLiteralTestCaseList{ - {"0.0", 0.0f}, // Zero - {"1.0", 1.0f}, // One - {"-1.0", -1.0f}, // MinusOne - {"1000000000.0", 1e9f}, // Billion - {"-0.0", std::copysign(0.0f, -5.0f)}, // NegativeZero - {"0.0", MakeFloat(0, 0, 0)}, // Zero - {"-0.0", MakeFloat(1, 0, 0)}, // NegativeZero - {"1.0", MakeFloat(0, 127, 0)}, // One - {"-1.0", MakeFloat(1, 127, 0)}, // NegativeOne + {"0.0", 0.0}, // Zero + {"1.0", 1.0}, // One + {"-1.0", -1.0}, // MinusOne + {"1000000000.0", 1e9}, // Billion + {"-0.0", std::copysign(0.0, -5.0)}, // NegativeZero + {"0.0", MakeDouble(0, 0, 0)}, // Zero + {"-0.0", MakeDouble(1, 0, 0)}, // NegativeZero + {"1.0", MakeDouble(0, 1023, 0)}, // One + {"-1.0", MakeDouble(1, 1023, 0)}, // NegativeOne }; } @@ -220,75 +254,76 @@ INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_Float, ParserImplFloatLiteralTest, testing::ValuesIn(DecimalFloatCases())); -const float NegInf = MakeFloat(1, 255, 0); -const float PosInf = MakeFloat(0, 255, 0); +const double NegInf = MakeDouble(1, 0x7FF, 0); +const double PosInf = MakeDouble(0, 0x7FF, 0); FloatLiteralTestCaseList HexFloatCases() { return FloatLiteralTestCaseList{ // Regular numbers - {"0x0p+0", 0.f}, - {"0x1p+0", 1.f}, - {"0x1p+1", 2.f}, - {"0x1.8p+1", 3.f}, - {"0x1.99999ap-4", 0.1f}, - {"0x1p-1", 0.5f}, - {"0x1p-2", 0.25f}, - {"0x1.8p-1", 0.75f}, - {"-0x0p+0", -0.f}, - {"-0x1p+0", -1.f}, - {"-0x1p-1", -0.5f}, - {"-0x1p-2", -0.25f}, - {"-0x1.8p-1", -0.75f}, + {"0x0p+0", 0.0}, + {"0x1p+0", 1.0}, + {"0x1p+1", 2.0}, + {"0x1.8p+1", 3.0}, + {"0x1.99999ap-4", 0.10000000149011612}, + {"0x1p-1", 0.5}, + {"0x1p-2", 0.25}, + {"0x1.8p-1", 0.75}, + {"-0x0p+0", -0.0}, + {"-0x1p+0", -1.0}, + {"-0x1p-1", -0.5}, + {"-0x1p-2", -0.25}, + {"-0x1.8p-1", -0.75}, // Large numbers - {"0x1p+9", 512.f}, - {"0x1p+10", 1024.f}, - {"0x1.02p+10", 1024.f + 8.f}, - {"-0x1p+9", -512.f}, - {"-0x1p+10", -1024.f}, - {"-0x1.02p+10", -1024.f - 8.f}, + {"0x1p+9", 512.0}, + {"0x1p+10", 1024.0}, + {"0x1.02p+10", 1024.0 + 8.0}, + {"-0x1p+9", -512.0}, + {"-0x1p+10", -1024.0}, + {"-0x1.02p+10", -1024.0 - 8.0}, // Small numbers - {"0x1p-9", 1.0f / 512.f}, - {"0x1p-10", 1.0f / 1024.f}, - {"0x1.02p-3", 1.0f / 1024.f + 1.0f / 8.f}, - {"-0x1p-9", 1.0f / -512.f}, - {"-0x1p-10", 1.0f / -1024.f}, - {"-0x1.02p-3", 1.0f / -1024.f - 1.0f / 8.f}, + {"0x1p-9", 1.0 / 512.0}, + {"0x1p-10", 1.0 / 1024.0}, + {"0x1.02p-3", 1.0 / 1024.0 + 1.0 / 8.0}, + {"-0x1p-9", 1.0 / -512.0}, + {"-0x1p-10", 1.0 / -1024.0}, + {"-0x1.02p-3", 1.0 / -1024.0 - 1.0 / 8.0}, // Near lowest non-denorm - {"0x1p-124", std::ldexp(1.f * 8.f, -127)}, - {"0x1p-125", std::ldexp(1.f * 4.f, -127)}, - {"-0x1p-124", -std::ldexp(1.f * 8.f, -127)}, - {"-0x1p-125", -std::ldexp(1.f * 4.f, -127)}, + {"0x1p-124", std::ldexp(1.0 * 8.0, -127)}, + {"0x1p-125", std::ldexp(1.0 * 4.0, -127)}, + {"-0x1p-124", -std::ldexp(1.0 * 8.0, -127)}, + {"-0x1p-125", -std::ldexp(1.0 * 4.0, -127)}, // Lowest non-denorm - {"0x1p-126", std::ldexp(1.f * 2.f, -127)}, - {"-0x1p-126", -std::ldexp(1.f * 2.f, -127)}, + {"0x1p-126", std::ldexp(1.0 * 2.0, -127)}, + {"-0x1p-126", -std::ldexp(1.0 * 2.0, -127)}, // Denormalized values - {"0x1p-127", std::ldexp(1.f, -127)}, - {"0x1p-128", std::ldexp(1.f / 2.f, -127)}, - {"0x1p-129", std::ldexp(1.f / 4.f, -127)}, - {"0x1p-130", std::ldexp(1.f / 8.f, -127)}, - {"-0x1p-127", -std::ldexp(1.f, -127)}, - {"-0x1p-128", -std::ldexp(1.f / 2.f, -127)}, - {"-0x1p-129", -std::ldexp(1.f / 4.f, -127)}, - {"-0x1p-130", -std::ldexp(1.f / 8.f, -127)}, + {"0x1p-127", std::ldexp(1.0, -127)}, + {"0x1p-128", std::ldexp(1.0 / 2.0, -127)}, + {"0x1p-129", std::ldexp(1.0 / 4.0, -127)}, + {"0x1p-130", std::ldexp(1.0 / 8.0, -127)}, + {"-0x1p-127", -std::ldexp(1.0, -127)}, + {"-0x1p-128", -std::ldexp(1.0 / 2.0, -127)}, + {"-0x1p-129", -std::ldexp(1.0 / 4.0, -127)}, + {"-0x1p-130", -std::ldexp(1.0 / 8.0, -127)}, - {"0x1.8p-127", std::ldexp(1.f, -127) + (std::ldexp(1.f, -127) / 2.f)}, - {"0x1.8p-128", std::ldexp(1.f, -127) / 2.f + (std::ldexp(1.f, -127) / 4.f)}, + {"0x1.8p-127", std::ldexp(1.0, -127) + (std::ldexp(1.0, -127) / 2.0)}, + {"0x1.8p-128", std::ldexp(1.0, -127) / 2.0 + (std::ldexp(1.0, -127) / 4.0)}, - {"0x1p-149", MakeFloat(0, 0, 1)}, // +SmallestDenormal - {"0x1p-148", MakeFloat(0, 0, 2)}, // +BiggerDenormal - {"0x1.fffffcp-127", MakeFloat(0, 0, 0x7fffff)}, // +LargestDenormal - {"-0x1p-149", MakeFloat(1, 0, 1)}, // -SmallestDenormal - {"-0x1p-148", MakeFloat(1, 0, 2)}, // -BiggerDenormal - {"-0x1.fffffcp-127", MakeFloat(1, 0, 0x7fffff)}, // -LargestDenormal + // F32 extremities + {"0x1p-149", static_cast(MakeFloat(0, 0, 1))}, // +SmallestDenormal + {"0x1p-148", static_cast(MakeFloat(0, 0, 2))}, // +BiggerDenormal + {"0x1.fffffcp-127", static_cast(MakeFloat(0, 0, 0x7fffff))}, // +LargestDenormal + {"-0x1p-149", static_cast(MakeFloat(1, 0, 1))}, // -SmallestDenormal + {"-0x1p-148", static_cast(MakeFloat(1, 0, 2))}, // -BiggerDenormal + {"-0x1.fffffcp-127", static_cast(MakeFloat(1, 0, 0x7fffff))}, // -LargestDenormal - {"0x1.2bfaf8p-127", MakeFloat(0, 0, 0xcafebe)}, // +Subnormal - {"-0x1.2bfaf8p-127", MakeFloat(1, 0, 0xcafebe)}, // -Subnormal - {"0x1.55554p-130", MakeFloat(0, 0, 0xaaaaa)}, // +Subnormal - {"-0x1.55554p-130", MakeFloat(1, 0, 0xaaaaa)}, // -Subnormal + {"0x1.2bfaf8p-127", static_cast(MakeFloat(0, 0, 0xcafebe))}, // +Subnormal + {"-0x1.2bfaf8p-127", static_cast(MakeFloat(1, 0, 0xcafebe))}, // -Subnormal + {"0x1.55554p-130", static_cast(MakeFloat(0, 0, 0xaaaaa))}, // +Subnormal + {"-0x1.55554p-130", static_cast(MakeFloat(1, 0, 0xaaaaa))}, // -Subnormal // Nan -> Infinity {"0x1.8p+128", PosInf}, @@ -318,76 +353,76 @@ FloatLiteralTestCaseList HexFloatCases() { {"0x1.0p2147483520", PosInf}, // INT_MAX - 127 (largest valid exponent) // Underflow -> Zero - {"0x1p-500", 0.f}, // Exponent underflows - {"-0x1p-500", -0.f}, - {"0x0.00000000001p-126", 0.f}, // Fraction causes underflow - {"-0x0.0000000001p-127", -0.f}, - {"0x0.01p-142", 0.f}, - {"-0x0.01p-142", -0.f}, // Fraction causes additional underflow + {"0x1p-500", 0.0}, // Exponent underflows + {"-0x1p-500", -0.0}, + {"0x0.00000000001p-126", 0.0}, // Fraction causes underflow + {"-0x0.0000000001p-127", -0.0}, + {"0x0.01p-142", 0.0}, + {"-0x0.01p-142", -0.0}, // Fraction causes additional underflow {"0x1.0p-2147483520", 0}, // -(INT_MAX - 127) (smallest valid exponent) // Zero with non-zero exponent -> Zero - {"0x0p+0", 0.f}, - {"0x0p+1", 0.f}, - {"0x0p-1", 0.f}, - {"0x0p+9999999999", 0.f}, - {"0x0p-9999999999", 0.f}, + {"0x0p+0", 0.0}, + {"0x0p+1", 0.0}, + {"0x0p-1", 0.0}, + {"0x0p+9999999999", 0.0}, + {"0x0p-9999999999", 0.0}, // Same, but with very large positive exponents that would cause overflow // if the mantissa were non-zero. - {"0x0p+4000000000", 0.f}, // 4 billion: - {"0x0p+40000000000", 0.f}, // 40 billion - {"-0x0p+40000000000", 0.f}, // As above 2, but negative mantissa - {"-0x0p+400000000000", 0.f}, - {"0x0.00p+4000000000", 0.f}, // As above 4, but with fractional part - {"0x0.00p+40000000000", 0.f}, - {"-0x0.00p+40000000000", 0.f}, - {"-0x0.00p+400000000000", 0.f}, - {"0x0p-4000000000", 0.f}, // As above 8, but with negative exponents - {"0x0p-40000000000", 0.f}, - {"-0x0p-40000000000", 0.f}, - {"-0x0p-400000000000", 0.f}, - {"0x0.00p-4000000000", 0.f}, - {"0x0.00p-40000000000", 0.f}, - {"-0x0.00p-40000000000", 0.f}, - {"-0x0.00p-400000000000", 0.f}, + {"0x0p+4000000000", 0.0}, // 4 billion: + {"0x0p+40000000000", 0.0}, // 40 billion + {"-0x0p+40000000000", 0.0}, // As above 2, but negative mantissa + {"-0x0p+400000000000", 0.0}, + {"0x0.00p+4000000000", 0.0}, // As above 4, but with fractional part + {"0x0.00p+40000000000", 0.0}, + {"-0x0.00p+40000000000", 0.0}, + {"-0x0.00p+400000000000", 0.0}, + {"0x0p-4000000000", 0.0}, // As above 8, but with negative exponents + {"0x0p-40000000000", 0.0}, + {"-0x0p-40000000000", 0.0}, + {"-0x0p-400000000000", 0.0}, + {"0x0.00p-4000000000", 0.0}, + {"0x0.00p-40000000000", 0.0}, + {"-0x0.00p-40000000000", 0.0}, + {"-0x0.00p-400000000000", 0.0}, // Test parsing - {"0x0p0", 0.f}, - {"0x0p-0", 0.f}, - {"0x0p+000", 0.f}, - {"0x00000000000000p+000000000000000", 0.f}, - {"0x00000000000000p-000000000000000", 0.f}, - {"0x00000000000001p+000000000000000", 1.f}, - {"0x00000000000001p-000000000000000", 1.f}, - {"0x0000000000000000000001.99999ap-000000000000000004", 0.1f}, - {"0x2p+0", 2.f}, - {"0xFFp+0", 255.f}, - {"0x0.8p+0", 0.5f}, - {"0x0.4p+0", 0.25f}, - {"0x0.4p+1", 2 * 0.25f}, - {"0x0.4p+2", 4 * 0.25f}, - {"0x123Ep+1", 9340.f}, - {"-0x123Ep+1", -9340.f}, - {"0x1a2b3cP12", 7.024656e+09f}, - {"-0x1a2b3cP12", -7.024656e+09f}, + {"0x0p0", 0.0}, + {"0x0p-0", 0.0}, + {"0x0p+000", 0.0}, + {"0x00000000000000p+000000000000000", 0.0}, + {"0x00000000000000p-000000000000000", 0.0}, + {"0x00000000000001p+000000000000000", 1.0}, + {"0x00000000000001p-000000000000000", 1.0}, + {"0x0000000000000000000001.99999ap-000000000000000004", 0.10000000149011612}, + {"0x2p+0", 2.0}, + {"0xFFp+0", 255.0}, + {"0x0.8p+0", 0.5}, + {"0x0.4p+0", 0.25}, + {"0x0.4p+1", 2 * 0.25}, + {"0x0.4p+2", 4 * 0.25}, + {"0x123Ep+1", 9340.0}, + {"-0x123Ep+1", -9340.0}, + {"0x1a2b3cP12", 7.024656384e+09}, + {"-0x1a2b3cP12", -7.024656384e+09}, // Examples without a binary exponent part. - {"0x1.", 1.0f}, - {"0x.8", 0.5f}, - {"0x1.8", 1.5f}, - {"-0x1.", -1.0f}, - {"-0x.8", -0.5f}, - {"-0x1.8", -1.5f}, + {"0x1.", 1.0}, + {"0x.8", 0.5}, + {"0x1.8", 1.5}, + {"-0x1.", -1.0}, + {"-0x.8", -0.5}, + {"-0x1.8", -1.5}, // Examples with a binary exponent and a 'f' suffix. - {"0x1.p0f", 1.0f}, - {"0x.8p2f", 2.0f}, - {"0x1.8p-1f", 0.75f}, - {"0x2p-2f", 0.5f}, // No binary point - {"-0x1.p0f", -1.0f}, - {"-0x.8p2f", -2.0f}, - {"-0x1.8p-1f", -0.75f}, - {"-0x2p-2f", -0.5f}, // No binary point + {"0x1.p0f", 1.0}, + {"0x.8p2f", 2.0}, + {"0x1.8p-1f", 0.75}, + {"0x2p-2f", 0.5}, // No binary point + {"-0x1.p0f", -1.0}, + {"-0x.8p2f", -2.0}, + {"-0x1.8p-1f", -0.75}, + {"-0x2p-2f", -0.5}, // No binary point }; } INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_HexFloat, @@ -501,7 +536,10 @@ TEST_F(ParserImplTest, ConstLiteral_FloatHighest) { EXPECT_FALSE(p->has_error()) << p->error(); ASSERT_NE(c.value, nullptr); ASSERT_TRUE(c->Is()); - EXPECT_FLOAT_EQ(c->As()->value, std::numeric_limits::max()); + EXPECT_DOUBLE_EQ(c->As()->value, + std::numeric_limits::max()); + EXPECT_EQ(c->As()->suffix, + ast::FloatLiteralExpression::Suffix::kNone); EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 42u}})); } @@ -522,8 +560,10 @@ TEST_F(ParserImplTest, ConstLiteral_FloatLowest) { EXPECT_FALSE(p->has_error()) << p->error(); ASSERT_NE(c.value, nullptr); ASSERT_TRUE(c->Is()); - EXPECT_FLOAT_EQ(c->As()->value, - std::numeric_limits::lowest()); + EXPECT_DOUBLE_EQ(c->As()->value, + std::numeric_limits::lowest()); + EXPECT_EQ(c->As()->suffix, + ast::FloatLiteralExpression::Suffix::kNone); EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 43u}})); } diff --git a/src/tint/reader/wgsl/token.cc b/src/tint/reader/wgsl/token.cc index 2f51a85b21..4e29a709af 100644 --- a/src/tint/reader/wgsl/token.cc +++ b/src/tint/reader/wgsl/token.cc @@ -26,7 +26,9 @@ std::string_view Token::TypeToName(Type type) { case Token::Type::kIdentifier: return "identifier"; case Token::Type::kFloatLiteral: - return "float literal"; + return "abstract float literal"; + case Token::Type::kFloatFLiteral: + return "'f'-suffixed float literal"; case Token::Type::kIntLiteral: return "abstract integer literal"; case Token::Type::kIntILiteral: @@ -278,8 +280,8 @@ Token::Token(Type type, const Source& source, const char* str) Token::Token(Type type, const Source& source, int64_t val) : type_(type), source_(source), value_(val) {} -Token::Token(const Source& source, float val) - : type_(Type::kFloatLiteral), source_(source), value_(val) {} +Token::Token(Type type, const Source& source, double val) + : type_(type), source_(source), value_(val) {} Token::Token(Type type, const Source& source) : type_(type), source_(source) {} @@ -304,7 +306,9 @@ bool Token::operator==(std::string_view ident) { std::string Token::to_str() const { switch (type_) { case Type::kFloatLiteral: - return std::to_string(std::get(value_)); + return std::to_string(std::get(value_)); + case Type::kFloatFLiteral: + return std::to_string(std::get(value_)) + "f"; case Type::kIntLiteral: return std::to_string(std::get(value_)); case Type::kIntILiteral: @@ -322,8 +326,8 @@ std::string Token::to_str() const { } } -float Token::to_f32() const { - return std::get(value_); +double Token::to_f64() const { + return std::get(value_); } int64_t Token::to_i64() const { diff --git a/src/tint/reader/wgsl/token.h b/src/tint/reader/wgsl/token.h index 106d1a6ab9..839ec40ec7 100644 --- a/src/tint/reader/wgsl/token.h +++ b/src/tint/reader/wgsl/token.h @@ -38,8 +38,10 @@ class Token { /// An identifier kIdentifier, - /// A float value + /// A float literal with no suffix kFloatLiteral, + /// A float literal with an 'f' suffix + kFloatFLiteral, /// An integer literal with no suffix kIntLiteral, /// An integer literal with an 'i' suffix @@ -304,10 +306,11 @@ class Token { /// @param source the source of the token /// @param val the source unsigned for the token Token(Type type, const Source& source, int64_t val); - /// Create a float Token + /// Create a double Token + /// @param type the Token::Type of the token /// @param source the source of the token - /// @param val the source float for the token - Token(const Source& source, float val); + /// @param val the source double for the token + Token(Type type, const Source& source, double val); /// Move constructor Token(Token&&); /// Copy constructor @@ -341,7 +344,7 @@ class Token { bool IsLiteral() const { return type_ == Type::kIntLiteral || type_ == Type::kIntILiteral || type_ == Type::kIntULiteral || type_ == Type::kFalse || type_ == Type::kTrue || - type_ == Type::kFloatLiteral; + type_ == Type::kFloatLiteral || type_ == Type::kFloatFLiteral; } /// @returns true if token is a 'matNxM' bool IsMatrix() const { @@ -379,8 +382,8 @@ class Token { std::string to_str() const; /// Returns the float value of the token. 0 is returned if the token does not /// contain a float value. - /// @return float - float to_f32() const; + /// @return double + double to_f64() const; /// Returns the int64_t value of the token. 0 is returned if the token does /// not contain an integer value. /// @return int64_t @@ -395,7 +398,7 @@ class Token { /// The source where the token appeared Source source_; /// The value represented by the token - std::variant value_; + std::variant value_; }; #ifndef NDEBUG diff --git a/src/tint/reader/wgsl/token_test.cc b/src/tint/reader/wgsl/token_test.cc index dd46616974..43a35e12ef 100644 --- a/src/tint/reader/wgsl/token_test.cc +++ b/src/tint/reader/wgsl/token_test.cc @@ -16,46 +16,53 @@ #include -#include "gtest/gtest.h" +#include "gmock/gmock.h" namespace tint::reader::wgsl { namespace { +using ::testing::EndsWith; +using ::testing::Not; +using ::testing::StartsWith; + using TokenTest = testing::Test; -TEST_F(TokenTest, ReturnsF32) { - Token t1(Source{}, -2.345f); - EXPECT_EQ(t1.to_f32(), -2.345f); +TEST_F(TokenTest, ReturnsF64) { + Token t1(Token::Type::kFloatFLiteral, Source{}, -2.345); + EXPECT_EQ(t1.to_f64(), -2.345); - Token t2(Source{}, 2.345f); - EXPECT_EQ(t2.to_f32(), 2.345f); + Token t2(Token::Type::kFloatFLiteral, Source{}, 2.345); + EXPECT_EQ(t2.to_f64(), 2.345); } TEST_F(TokenTest, ReturnsI32) { - Token t1(Token::Type::kIntILiteral, Source{}, -2345); + Token t1(Token::Type::kIntILiteral, Source{}, static_cast(-2345)); EXPECT_EQ(t1.to_i64(), -2345); - Token t2(Token::Type::kIntILiteral, Source{}, 2345); + Token t2(Token::Type::kIntILiteral, Source{}, static_cast(2345)); EXPECT_EQ(t2.to_i64(), 2345); } TEST_F(TokenTest, HandlesMaxI32) { - Token t1(Token::Type::kIntILiteral, Source{}, std::numeric_limits::max()); + Token t1(Token::Type::kIntILiteral, Source{}, + static_cast(std::numeric_limits::max())); EXPECT_EQ(t1.to_i64(), std::numeric_limits::max()); } TEST_F(TokenTest, HandlesMinI32) { - Token t1(Token::Type::kIntILiteral, Source{}, std::numeric_limits::min()); + Token t1(Token::Type::kIntILiteral, Source{}, + static_cast(std::numeric_limits::min())); EXPECT_EQ(t1.to_i64(), std::numeric_limits::min()); } TEST_F(TokenTest, ReturnsU32) { - Token t2(Token::Type::kIntULiteral, Source{}, 2345u); + Token t2(Token::Type::kIntULiteral, Source{}, static_cast(2345u)); EXPECT_EQ(t2.to_i64(), 2345u); } TEST_F(TokenTest, ReturnsMaxU32) { - Token t1(Token::Type::kIntULiteral, Source{}, std::numeric_limits::max()); + Token t1(Token::Type::kIntULiteral, Source{}, + static_cast(std::numeric_limits::max())); EXPECT_EQ(t1.to_i64(), std::numeric_limits::max()); } @@ -71,5 +78,19 @@ TEST_F(TokenTest, Source) { EXPECT_EQ(t.source().range.end.column, 3u); } +TEST_F(TokenTest, ToStr) { + double d = 123.0; + int64_t i = 123; + EXPECT_THAT(Token(Token::Type::kFloatLiteral, Source{}, d).to_str(), StartsWith("123")); + EXPECT_THAT(Token(Token::Type::kFloatLiteral, Source{}, d).to_str(), Not(EndsWith("f"))); + EXPECT_THAT(Token(Token::Type::kFloatFLiteral, Source{}, d).to_str(), StartsWith("123")); + EXPECT_THAT(Token(Token::Type::kFloatFLiteral, Source{}, d).to_str(), EndsWith("f")); + EXPECT_EQ(Token(Token::Type::kIntLiteral, Source{}, i).to_str(), "123"); + EXPECT_EQ(Token(Token::Type::kIntILiteral, Source{}, i).to_str(), "123i"); + EXPECT_EQ(Token(Token::Type::kIntULiteral, Source{}, i).to_str(), "123u"); + EXPECT_EQ(Token(Token::Type::kIdentifier, Source{}, "blah").to_str(), "blah"); + EXPECT_EQ(Token(Token::Type::kError, Source{}, "blah").to_str(), "blah"); +} + } // namespace } // namespace tint::reader::wgsl diff --git a/src/tint/resolver/entry_point_validation_test.cc b/src/tint/resolver/entry_point_validation_test.cc index d1d9a48399..c3f1ff5305 100644 --- a/src/tint/resolver/entry_point_validation_test.cc +++ b/src/tint/resolver/entry_point_validation_test.cc @@ -501,7 +501,7 @@ TEST_F(LocationAttributeTests, BadType_Input_Struct_RuntimeArray) { // @stage(fragment) // fn main(param : Input) {} auto* input = - Structure("Input", {Member(Source{{13, 43}}, "a", ty.array(), {Location(0)})}); + Structure("Input", {Member(Source{{13, 43}}, "a", ty.array(), {Location(0)})}); auto* param = Param("param", ty.Of(input)); Func(Source{{12, 34}}, "main", {param}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)}); @@ -633,7 +633,7 @@ TEST_F(LocationAttributeTests, ReturnType_Struct_RuntimeArray) { // fn main() -> Output { // return Output(); // } - auto* output = Structure("Output", {Member(Source{{13, 43}}, "a", ty.array(), + auto* output = Structure("Output", {Member(Source{{13, 43}}, "a", ty.array(), {Location(Source{{12, 34}}, 0)})}); Func(Source{{12, 34}}, "main", {}, ty.Of(output), {Return(Construct(ty.Of(output)))}, {Stage(ast::PipelineStage::kFragment)}); diff --git a/src/tint/resolver/resolver_constants.cc b/src/tint/resolver/resolver_constants.cc index f5346d18a6..14009265d5 100644 --- a/src/tint/resolver/resolver_constants.cc +++ b/src/tint/resolver/resolver_constants.cc @@ -38,12 +38,12 @@ sem::Constant Resolver::EvaluateConstantValue(const ast::LiteralExpression* lite literal, [&](const ast::IntLiteralExpression* lit) { if (lit->suffix == ast::IntLiteralExpression::Suffix::kU) { - return sem::Constant{type, {u32(static_cast(lit->value))}}; + return sem::Constant{type, {u32(lit->value)}}; } - return sem::Constant{type, {i32(static_cast(lit->value))}}; + return sem::Constant{type, {i32(lit->value)}}; }, [&](const ast::FloatLiteralExpression* lit) { - return sem::Constant{type, {lit->value}}; + return sem::Constant{type, {f32(lit->value)}}; }, [&](const ast::BoolLiteralExpression* lit) { return sem::Constant{type, {lit->value}}; @@ -71,7 +71,7 @@ sem::Constant Resolver::EvaluateConstantValue(const ast::CallExpression* call, return sem::Constant(type, sem::Constant::Scalars(result_size, 0_u)); } if (elem_type->Is()) { - return sem::Constant(type, sem::Constant::Scalars(result_size, 0.f)); + return sem::Constant(type, sem::Constant::Scalars(result_size, 0._f)); } if (elem_type->Is()) { return sem::Constant(type, sem::Constant::Scalars(result_size, false)); diff --git a/src/tint/resolver/resolver_constants_test.cc b/src/tint/resolver/resolver_constants_test.cc index 798de0a3ac..585aaf578a 100644 --- a/src/tint/resolver/resolver_constants_test.cc +++ b/src/tint/resolver/resolver_constants_test.cc @@ -139,9 +139,9 @@ TEST_F(ResolverConstantsTest, Vec3_ZeroInit_f32) { EXPECT_EQ(sem->ConstantValue().Type(), sem->Type()); EXPECT_TRUE(sem->ConstantValue().ElementType()->Is()); ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u); - EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 0u); - EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 0u); - EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 0u); + EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 0.0f); + EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 0.0f); + EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 0.0f); } TEST_F(ResolverConstantsTest, Vec3_ZeroInit_bool) { diff --git a/src/tint/resolver/var_let_validation_test.cc b/src/tint/resolver/var_let_validation_test.cc index ff63918d8e..99cb8020df 100644 --- a/src/tint/resolver/var_let_validation_test.cc +++ b/src/tint/resolver/var_let_validation_test.cc @@ -127,7 +127,7 @@ TEST_F(ResolverVarLetValidationTest, LetOfPtrConstructedWithRef) { // let b : ptr = a; const auto priv = ast::StorageClass::kFunction; auto* var_a = Var("a", ty.f32(), priv); - auto* var_b = Let(Source{{12, 34}}, "b", ty.pointer(priv), Expr("a"), {}); + auto* var_b = Let(Source{{12, 34}}, "b", ty.pointer(priv), Expr("a"), {}); WrapInFunction(var_a, var_b); ASSERT_FALSE(r()->Resolve()); diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc index 29451e7797..dd11c72b08 100644 --- a/src/tint/writer/glsl/generator_impl.cc +++ b/src/tint/writer/glsl/generator_impl.cc @@ -2177,7 +2177,7 @@ bool GeneratorImpl::EmitLiteral(std::ostream& out, const ast::LiteralExpression* } else if (std::isnan(l->value)) { out << "uintBitsToFloat(0x7fc00000u)"; } else { - out << FloatToString(l->value) << "f"; + out << FloatToString(static_cast(l->value)) << "f"; } return true; }, diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc index a64630b4dd..7ee5337f9f 100644 --- a/src/tint/writer/hlsl/generator_impl.cc +++ b/src/tint/writer/hlsl/generator_impl.cc @@ -3040,7 +3040,7 @@ bool GeneratorImpl::EmitLiteral(std::ostream& out, const ast::LiteralExpression* } else if (std::isnan(fl->value)) { out << "asfloat(0x7fc00000u)"; } else { - out << FloatToString(fl->value) << "f"; + out << FloatToString(static_cast(fl->value)) << "f"; } return true; }, diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc index 8665cdb851..8f5800c565 100644 --- a/src/tint/writer/msl/generator_impl.cc +++ b/src/tint/writer/msl/generator_impl.cc @@ -1517,7 +1517,7 @@ bool GeneratorImpl::EmitLiteral(std::ostream& out, const ast::LiteralExpression* } else if (std::isnan(l->value)) { out << "NAN"; } else { - out << FloatToString(l->value) << "f"; + out << FloatToString(static_cast(l->value)) << "f"; } return true; }, diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc index 72a0962814..7d232c079b 100644 --- a/src/tint/writer/spirv/builder.cc +++ b/src/tint/writer/spirv/builder.cc @@ -746,7 +746,8 @@ bool Builder::GenerateGlobalVariable(const ast::Variable* var) { init_id = Switch( type, // [&](const sem::F32*) { - ast::FloatLiteralExpression l(ProgramID{}, Source{}, 0.0f); + ast::FloatLiteralExpression l(ProgramID{}, Source{}, 0, + ast::FloatLiteralExpression::Suffix::kF); return GenerateLiteralIfNeeded(var, &l); }, [&](const sem::U32*) { @@ -1621,8 +1622,13 @@ uint32_t Builder::GenerateLiteralIfNeeded(const ast::Variable* var, } }, [&](const ast::FloatLiteralExpression* f) { - constant.kind = ScalarConstant::Kind::kF32; - constant.value.f32 = f->value; + switch (f->suffix) { + case ast::FloatLiteralExpression::Suffix::kNone: + case ast::FloatLiteralExpression::Suffix::kF: + constant.kind = ScalarConstant::Kind::kF32; + constant.value.f32 = static_cast(f->value); + return; + } }, [&](Default) { error_ = "unknown literal type"; }); @@ -2878,7 +2884,8 @@ bool Builder::GenerateTextureBuiltin(const sem::Call* call, } spirv_params.emplace_back(gen_arg(Usage::kDepthRef)); - ast::FloatLiteralExpression float_0(ProgramID(), Source{}, 0.0); + ast::FloatLiteralExpression float_0(ProgramID(), Source{}, 0.0, + ast::FloatLiteralExpression::Suffix::kF); image_operands.emplace_back(ImageOperand{ SpvImageOperandsLodMask, Operand(GenerateLiteralIfNeeded(nullptr, &float_0))}); break; diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc index 901abad89d..04dde77449 100644 --- a/src/tint/writer/spirv/builder_builtin_test.cc +++ b/src/tint/writer/spirv/builder_builtin_test.cc @@ -2104,8 +2104,8 @@ TEST_P(Builtin_Builtin_DataPacking_Test, Binary) { auto param = GetParam(); bool pack4 = param.name == "pack4x8snorm" || param.name == "pack4x8unorm"; - auto* call = pack4 ? Call(param.name, vec4(1.0f, 1.0f, 1.0f, 1.0f)) - : Call(param.name, vec2(1.0f, 1.0f)); + auto* call = pack4 ? Call(param.name, vec4(1.0f, 1.0f, 1.0f, 1.0f)) + : Call(param.name, vec2(1.0f, 1.0f)); auto* func = Func("a_func", {}, ty.void_(), {CallStmt(call)}); spirv::Builder& b = Build(); diff --git a/src/tint/writer/spirv/builder_literal_test.cc b/src/tint/writer/spirv/builder_literal_test.cc index d8a0311dc1..00c21b0efe 100644 --- a/src/tint/writer/spirv/builder_literal_test.cc +++ b/src/tint/writer/spirv/builder_literal_test.cc @@ -133,7 +133,7 @@ TEST_F(BuilderTest, Literal_U32_Dedup) { } TEST_F(BuilderTest, Literal_F32) { - auto* i = create(23.245f); + auto* i = create(23.245, ast::FloatLiteralExpression::Suffix::kF); WrapInFunction(i); spirv::Builder& b = Build(); @@ -148,8 +148,8 @@ TEST_F(BuilderTest, Literal_F32) { } TEST_F(BuilderTest, Literal_F32_Dedup) { - auto* i1 = create(23.245f); - auto* i2 = create(23.245f); + auto* i1 = create(23.245, ast::FloatLiteralExpression::Suffix::kF); + auto* i2 = create(23.245, ast::FloatLiteralExpression::Suffix::kF); WrapInFunction(i1, i2); spirv::Builder& b = Build(); diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc index 588a6e9b9e..3ef6269448 100644 --- a/src/tint/writer/wgsl/generator_impl.cc +++ b/src/tint/writer/wgsl/generator_impl.cc @@ -262,7 +262,7 @@ bool GeneratorImpl::EmitLiteral(std::ostream& out, const ast::LiteralExpression* return true; }, [&](const ast::FloatLiteralExpression* l) { // - out << FloatToBitPreservingString(l->value); + out << FloatToBitPreservingString(static_cast(l->value)); return true; }, [&](const ast::IntLiteralExpression* l) { // diff --git a/test/tint/expressions/literals/nan.spvasm.expected.spvasm b/test/tint/expressions/literals/nan.spvasm.expected.spvasm index c9e283d078..4f6a1247c9 100644 --- a/test/tint/expressions/literals/nan.spvasm.expected.spvasm +++ b/test/tint/expressions/literals/nan.spvasm.expected.spvasm @@ -25,8 +25,8 @@ %out_var_SV_TARGET = OpVariable %_ptr_Private_v4float Private %5 %void = OpTypeVoid %8 = OpTypeFunction %void -%float_0x1_1p_128 = OpConstant %float 0x1.1p+128 - %13 = OpConstantComposite %v4float %float_0x1_1p_128 %float_0x1_1p_128 %float_0x1_1p_128 %float_0x1_1p_128 +%float_0x1_9p_128 = OpConstant %float 0x1.9p+128 + %13 = OpConstantComposite %v4float %float_0x1_9p_128 %float_0x1_9p_128 %float_0x1_9p_128 %float_0x1_9p_128 %main_out = OpTypeStruct %v4float %14 = OpTypeFunction %main_out %main_1 = OpFunction %void None %8 diff --git a/test/tint/expressions/literals/nan.spvasm.expected.wgsl b/test/tint/expressions/literals/nan.spvasm.expected.wgsl index be2a3af17d..b1e77023dc 100644 --- a/test/tint/expressions/literals/nan.spvasm.expected.wgsl +++ b/test/tint/expressions/literals/nan.spvasm.expected.wgsl @@ -1,7 +1,7 @@ var out_var_SV_TARGET : vec4; fn main_1() { - out_var_SV_TARGET = vec4(0x1.1p+128, 0x1.1p+128, 0x1.1p+128, 0x1.1p+128); + out_var_SV_TARGET = vec4(0x1.9p+128, 0x1.9p+128, 0x1.9p+128, 0x1.9p+128); return; }