Bug 107097

Summary: Implement floating point excess precision in C++
Product: gcc Reporter: Jason Merrill <jason>
Component: c++Assignee: Not yet assigned to anyone <unassigned>
Status: RESOLVED FIXED    
Severity: enhancement CC: jakub, mpolacek, webrown.cpp
Priority: P3    
Version: 12.0   
Target Milestone: ---   
See Also: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106652
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323
Host: Target:
Build: Known to work:
Known to fail: Last reconfirmed: 2022-09-30 00:00:00
Attachments: gcc13-pr107097-wip.patch
gcc13-pr107097-wip.patch

Description Jason Merrill 2022-09-30 13:32:33 UTC
Joseph Myers implemented C99 excess precision support for C in 4.5:
https://gcc.gnu.org/legacy-ml/gcc-patches/2008-11/msg00105.html

C++ has a similar rule in [expr.pre]p6:

"The values of the floating-point operands and the results of floating-point expressions may be represented in greater precision and range than that required by the type; the types are not changed thereby. [Footnote: The cast and assignment operators must still perform their specific conversions.]"

Implementing this seems particularly important for users wanting to use 16-bit floating point types in C++.
Comment 1 Jakub Jelinek 2022-10-04 17:20:56 UTC
Created attachment 53667 [details]
gcc13-pr107097-wip.patch

Untested WIP.  ?: handling isn't there, neither call argument, will need to check assignments and whether I got all the cast spots that need tweaking.
Comment 2 Marek Polacek 2022-10-04 17:27:14 UTC
I know it's just a WIP, but could we share the setting of may_need_excess_precision between C and C++?
Comment 3 Jakub Jelinek 2022-10-04 17:30:57 UTC
The code is different though.  C has that
    case EQ_EXPR:
    case NE_EXPR:
    case LE_EXPR:
    case GE_EXPR:
    case LT_EXPR:
    case GT_EXPR:
      /* Excess precision for implicit conversions of integers to
         floating point in C11 and later.  */
      may_need_excess_precision = (flag_isoc11
                                   && (ANY_INTEGRAL_TYPE_P (type0)
                                       || ANY_INTEGRAL_TYPE_P (type1)));
      break;
part in which is from PR82071 and I can't find in the C++ standard a similar change to what has changed from C99 to C11.
If you mean to share just
    case PLUS_EXPR:
    case MINUS_EXPR:
    case MULT_EXPR:
    case TRUNC_DIV_EXPR:
    case CEIL_DIV_EXPR:
    case FLOOR_DIV_EXPR:
    case ROUND_DIV_EXPR:
    case EXACT_DIV_EXPR:
then that sounds something not worth sharing to me.
Comment 4 Jakub Jelinek 2022-10-05 16:33:24 UTC
Created attachment 53669 [details]
gcc13-pr107097-wip.patch

Updated patch that handles ?:.  excess-precision-1.C now passes at runtime...
Next problem is excess-precision-2.C, where we rely on
volatile long double ld11f = 1.1f;
actually being the same as
volatile long double ld11f = 1.1L;
Comment 5 Jakub Jelinek 2022-10-05 16:47:31 UTC
Seems in that case we loose the precision in:
#0  fold_convert_loc (loc=0, type=<real_type 0x7fffea1582a0 float>, arg=<real_cst 0x7fffea2af480>) at ../../gcc/fold-const.cc:2436
#1  0x000000000049c414 in cxx_eval_constant_expression (ctx=0x7fffffffcc90, t=<excess_precision_expr 0x7fffea290960>, lval=vc_prvalue, non_constant_p=0x7fffffffcdaf, 
    overflow_p=0x7fffffffcdae, jump_target=0x0) at ../../gcc/cp/constexpr.cc:7541
#2  0x000000000049e566 in cxx_eval_outermost_constant_expr (t=<excess_precision_expr 0x7fffea290960>, allow_non_constant=true, strict=true, manifestly_const_eval=false, 
    constexpr_dtor=false, object=<tree 0x0>) at ../../gcc/cp/constexpr.cc:7970
#3  0x000000000049f3b3 in maybe_constant_value (t=<excess_precision_expr 0x7fffea290960>, decl=<tree 0x0>, manifestly_const_eval=false) at ../../gcc/cp/constexpr.cc:8240
#4  0x00000000004e0a81 in cp_fully_fold (x=<excess_precision_expr 0x7fffea290960>) at ../../gcc/cp/cp-gimplify.cc:2367
#5  0x00000000004ef3ad in cp_convert_and_check (type=<real_type 0x7fffea1583f0 long double>, expr=<excess_precision_expr 0x7fffea290960>, complain=3) at ../../gcc/cp/cvt.cc:666
#6  0x000000000042b4e1 in convert_like_internal (convs=0x3b516a0, expr=<excess_precision_expr 0x7fffea290960>, fn=<tree 0x0>, argnum=0, issue_conversion_warnings=true, 
    c_cast_p=false, complain=3) at ../../gcc/cp/call.cc:8549
#7  0x000000000042b765 in convert_like (convs=0x3b516a0, expr=<excess_precision_expr 0x7fffea290960>, fn=<tree 0x0>, argnum=0, issue_conversion_warnings=true, c_cast_p=false, 
    complain=3) at ../../gcc/cp/call.cc:8604
#8  0x000000000042b7d8 in convert_like (convs=0x3b516a0, expr=<excess_precision_expr 0x7fffea290960>, complain=3) at ../../gcc/cp/call.cc:8616
#9  0x000000000043da51 in perform_implicit_conversion_flags (type=<real_type 0x7fffea1583f0 long double>, expr=<excess_precision_expr 0x7fffea290960>, complain=3, flags=262149)
    at ../../gcc/cp/call.cc:12999
#10 0x0000000000845343 in convert_for_assignment (type=<real_type 0x7fffea29abd0 long double>, rhs=<excess_precision_expr 0x7fffea290960>, errtype=ICR_INIT, fndecl=<tree 0x0>, 
    parmnum=0, complain=3, flags=262149) at ../../gcc/cp/typeck.cc:10332
#11 0x00000000008459f4 in convert_for_initialization (exp=<tree 0x0>, type=<real_type 0x7fffea29abd0 long double>, rhs=<excess_precision_expr 0x7fffea290960>, flags=262149, 
    errtype=ICR_INIT, fndecl=<tree 0x0>, parmnum=0, complain=3) at ../../gcc/cp/typeck.cc:10423
#12 0x000000000085075d in digest_init_r (type=<real_type 0x7fffea29abd0 long double>, init=<excess_precision_expr 0x7fffea290960>, nested=0, flags=262149, complain=3)
    at ../../gcc/cp/typeck2.cc:1276
#13 0x0000000000850e84 in digest_init_flags (type=<real_type 0x7fffea29abd0 long double>, init=<excess_precision_expr 0x7fffea290960>, flags=262149, complain=3)
    at ../../gcc/cp/typeck2.cc:1380
#14 0x000000000084eaff in store_init_value (decl=<var_decl 0x7fffea13be10 ld11f>, init=<excess_precision_expr 0x7fffea290960>, cleanups=0x7fffffffd668, flags=262149)
    at ../../gcc/cp/typeck2.cc:829
#15 0x00000000005270c3 in check_initializer (decl=<var_decl 0x7fffea13be10 ld11f>, init=<excess_precision_expr 0x7fffea290960>, flags=5, cleanups=0x7fffffffd668)
    at ../../gcc/cp/decl.cc:7466
#16 0x000000000052d248 in cp_finish_decl (decl=<var_decl 0x7fffea13be10 ld11f>, init=<excess_precision_expr 0x7fffea290960>, init_const_expr_p=true, asmspec_tree=<tree 0x0>, 
    flags=5) at ../../gcc/cp/decl.cc:8468

Supposedly we should somewhere (temporarily) strip away the EXCESS_PRECISION_EXPR and readd it after conversion, but it is unclear to me where and under what conditions.
Somewhere where we know it is an implicit conversion (because explicit conversion should round to semantic type)?
And only when converting (implicitly) to some other REAL_TYPE/COMPLEX_TYPE?
I mean, if we say try to initialize a class from some floating point value, we should determine that conversion from the semantic type.
On the other side, e.g. for implicit conversion to bool, shouldn't we do the != 0 comparison in excess precision?

The C patch strips EXCESS_PRECISION_EXPR unconditionally at the start of the function.
So perhaps strip it in convert_for_assignment or perform_implicit_conversion_flags if type is arithmetic type (dunno about enums) only?
Comment 6 Jakub Jelinek 2022-10-05 17:29:48 UTC
Perhaps try to look up the implicit conversion using the semantic type (i.e. with EXCESS_PRECISION_EXPR not stripped) and then if it is a standard conversion (which exact?) from EXCESS_PRECISION to arithmetic/enumeral type or so, strip away the excess precision and actually convert from the excess precision?
Comment 7 GCC Commits 2022-10-14 07:32:02 UTC
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>:

https://gcc.gnu.org/g:98e341130f87984af07c884fea773c0bb3cc8821

commit r13-3290-g98e341130f87984af07c884fea773c0bb3cc8821
Author: Jakub Jelinek <jakub@redhat.com>
Date:   Fri Oct 14 09:28:57 2022 +0200

    c++: Implement excess precision support for C++ [PR107097, PR323]
    
    The following patch implements excess precision support for C++.
    Like for C, it uses EXCESS_PRECISION_EXPR tree to say that its operand
    is evaluated in excess precision and what the semantic type of the
    expression is.
    In most places I've followed what the C FE does in similar spots, so
    e.g. for binary ops if one or both operands are already
    EXCESS_PRECISION_EXPR, strip those away or for operations that might need
    excess precision (+, -, *, /) check if the operands should use excess
    precision and convert to that type and at the end wrap into
    EXCESS_PRECISION_EXPR with the common semantic type.
    This patch follows the C99 handling where it differs from C11 handling.
    
    There are some cases which needed to be handled differently, the C FE can
    just strip EXCESS_PRECISION_EXPR (replace it with its operand) when handling
    explicit cast, but that IMHO isn't right for C++ - the discovery what exact
    conversion should be used (e.g. if user conversion or standard or their
    sequence) should be decided based on the semantic type (i.e. type of
    EXCESS_PRECISION_EXPR), and that decision continues in convert_like* where
    we pick the right user conversion, again, if say some class has ctor
    from double and long double and we are on ia32 with standard excess
    precision promoting float/double to long double, then we should pick the
    ctor from double.  Or when some other class has ctor from just double,
    and EXCESS_PRECISION_EXPR semantic type is float, we should choose the
    user ctor from double, but actually just convert the long double excess
    precision to double and not to float first.  We need to make sure
    even identity conversion converts from excess precision to the semantic one
    though, but if identity is chained with other conversions, we don't want
    the identity next_conversion to drop to semantic precision only to widen
    afterwards.
    
    The existing testcases tweaks were for cases on i686-linux where excess
    precision breaks those tests, e.g. if we have
      double d = 4.2;
      if (d == 4.2)
    then it does the expected thing only with -fexcess-precision=fast,
    because with -fexcess-precision=standard it is actually
      double d = 4.2;
      if ((long double) d == 4.2L)
    where 4.2L is different from 4.2.  I've added -fexcess-precision=fast
    to some tests and changed other tests to use constants that are exactly
    representable and don't suffer from these excess precision issues.
    
    There is one exception, pr68180.C looks like a bug in the patch which is
    also present in the C FE (so I'd like to get it resolved incrementally
    in both).  Reduced testcase:
    typedef float __attribute__((vector_size (16))) float32x4_t;
    float32x4_t foo(float32x4_t x, float y) { return x + y; }
    with -m32 -std=c11 -Wno-psabi or -m32 -std=c++17 -Wno-psabi
    it is rejected with:
    pr68180.c:2:52: error: conversion of scalar âlong doubleâ to vector âfloat32x4_tâ {aka â__vector(4) floatâ} involves truncation
    but without excess precision (say just -std=c11 -Wno-psabi or -std=c++17 -Wno-psabi)
    it is accepted.  Perhaps we should pass down the semantic type to
    scalar_to_vector and use the semantic type rather than excess precision type
    in the diagnostics.
    
    2022-10-14  Jakub Jelinek  <jakub@redhat.com>
    
            PR middle-end/323
            PR c++/107097
    gcc/
            * doc/invoke.texi (-fexcess-precision=standard): Mention that the
            option now also works in C++.
    gcc/c-family/
            * c-common.def (EXCESS_PRECISION_EXPR): Remove comment part about
            the tree being specific to C/ObjC.
            * c-opts.cc (c_common_post_options): Handle flag_excess_precision
            in C++ the same as in C.
            * c-lex.cc (interpret_float): Set const_type to excess_precision ()
            even for C++.
    gcc/cp/
            * parser.cc (cp_parser_primary_expression): Handle
            EXCESS_PRECISION_EXPR with REAL_CST operand the same as REAL_CST.
            * cvt.cc (cp_ep_convert_and_check): New function.
            * call.cc (build_conditional_expr): Add excess precision support.
            When type_after_usual_arithmetic_conversions returns error_mark_node,
            use gcc_checking_assert that it is because of uncomparable floating
            point ranks instead of checking all those conditions and make it
            work also with complex types.
            (convert_like_internal): Likewise.  Add NESTED_P argument, pass true
            to recursive calls to convert_like.
            (convert_like): Add NESTED_P argument, pass it through to
            convert_like_internal.  For other overload pass false to it.
            (convert_like_with_context): Pass false to NESTED_P.
            (convert_arg_to_ellipsis): Add excess precision support.
            (magic_varargs_p): For __builtin_is{finite,inf,inf_sign,nan,normal}
            and __builtin_fpclassify return 2 instead of 1, document what it
            means.
            (build_over_call): Don't handle former magic 2 which is no longer
            used, instead for magic 1 remove EXCESS_PRECISION_EXPR.
            (perform_direct_initialization_if_possible): Pass false to NESTED_P
            convert_like argument.
            * constexpr.cc (cxx_eval_constant_expression): Handle
            EXCESS_PRECISION_EXPR.
            (potential_constant_expression_1): Likewise.
            * pt.cc (tsubst_copy, tsubst_copy_and_build): Likewise.
            * cp-tree.h (cp_ep_convert_and_check): Declare.
            * cp-gimplify.cc (cp_fold): Handle EXCESS_PRECISION_EXPR.
            * typeck.cc (cp_common_type): For COMPLEX_TYPEs, return error_mark_node
            if recursive call returned it.
            (convert_arguments): For magic 1 remove EXCESS_PRECISION_EXPR.
            (cp_build_binary_op): Add excess precision support.  When
            cp_common_type returns error_mark_node, use gcc_checking_assert that
            it is because of uncomparable floating point ranks instead of checking
            all those conditions and make it work also with complex types.
            (cp_build_unary_op): Likewise.
            (cp_build_compound_expr): Likewise.
            (build_static_cast_1): Remove EXCESS_PRECISION_EXPR.
    gcc/testsuite/
            * gcc.target/i386/excess-precision-1.c: For C++ wrap abort and
            exit declarations into extern "C" block.
            * gcc.target/i386/excess-precision-2.c: Likewise.
            * gcc.target/i386/excess-precision-3.c: Likewise.  Remove
            check_float_nonproto and check_double_nonproto tests for C++.
            * gcc.target/i386/excess-precision-7.c: For C++ wrap abort and
            exit declarations into extern "C" block.
            * gcc.target/i386/excess-precision-9.c: Likewise.
            * g++.target/i386/excess-precision-1.C: New test.
            * g++.target/i386/excess-precision-2.C: New test.
            * g++.target/i386/excess-precision-3.C: New test.
            * g++.target/i386/excess-precision-4.C: New test.
            * g++.target/i386/excess-precision-5.C: New test.
            * g++.target/i386/excess-precision-6.C: New test.
            * g++.target/i386/excess-precision-7.C: New test.
            * g++.target/i386/excess-precision-9.C: New test.
            * g++.target/i386/excess-precision-11.C: New test.
            * c-c++-common/dfp/convert-bfp-10.c: Add -fexcess-precision=fast
            as dg-additional-options.
            * c-c++-common/dfp/compare-eq-const.c: Likewise.
            * g++.dg/cpp1z/constexpr-96862.C: Likewise.
            * g++.dg/cpp1z/decomp12.C (main): Use 2.25 instead of 2.3 to
            avoid excess precision differences.
            * g++.dg/other/thunk1.C: Add -fexcess-precision=fast
            as dg-additional-options.
            * g++.dg/vect/pr64410.cc: Likewise.
            * g++.dg/cpp1y/pr68180.C: Likewise.
            * g++.dg/vect/pr89653.cc: Likewise.
            * g++.dg/cpp0x/variadic-tuple.C: Likewise.
            * g++.dg/cpp0x/nsdmi-union1.C: Use 4.25 instead of 4.2 to
            avoid excess precision differences.
            * g++.old-deja/g++.brendan/copy9.C: Add -fexcess-precision=fast
            as dg-additional-options.
            * g++.old-deja/g++.brendan/overload7.C: Likewise.
Comment 8 GCC Commits 2022-10-14 07:34:43 UTC
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>:

https://gcc.gnu.org/g:16ec267063c8ce60769888d4097bcd158410adc8

commit r13-3291-g16ec267063c8ce60769888d4097bcd158410adc8
Author: Jakub Jelinek <jakub@redhat.com>
Date:   Fri Oct 14 09:33:23 2022 +0200

    c++: Excess precision for ? int : float or int == float [PR107097, PR82071, PR87390]
    
    The following incremental patch implements the C11 behavior (for all C++
    versions) for
    cond ? int : float
    cond ? float : int
    int cmp float
    float cmp int
    where int is any integral type, float any floating point type with
    excess precision and cmp ==, !=, >, <, >=, <= and <=>.
    
    2022-10-14  Jakub Jelinek  <jakub@redhat.com>
    
            PR c/82071
            PR c/87390
            PR c++/107097
    gcc/cp/
            * cp-tree.h (cp_ep_convert_and_check): Remove.
            * cvt.cc (cp_ep_convert_and_check): Remove.
            * call.cc (build_conditional_expr): Use excess precision for ?: with
            one arm floating and another integral.  Don't convert first to
            semantic result type from integral types.
            (convert_like_internal): Don't call cp_ep_convert_and_check, instead
            just strip EXCESS_PRECISION_EXPR before calling cp_convert_and_check
            or cp_convert.
            * typeck.cc (cp_build_binary_op): Set may_need_excess_precision
            for comparisons or SPACESHIP_EXPR with at least one operand integral.
            Don't compute semantic_result_type if build_type is non-NULL.  Call
            cp_convert_and_check instead of cp_ep_convert_and_check.
    gcc/testsuite/
            * gcc.target/i386/excess-precision-8.c: For C++ wrap abort and
            exit declarations into extern "C" block.
            * gcc.target/i386/excess-precision-10.c: Likewise.
            * g++.target/i386/excess-precision-7.C: Remove.
            * g++.target/i386/excess-precision-8.C: New test.
            * g++.target/i386/excess-precision-9.C: Remove.
            * g++.target/i386/excess-precision-10.C: New test.
            * g++.target/i386/excess-precision-12.C: New test.
Comment 9 Jakub Jelinek 2022-10-14 07:38:21 UTC
Now implemented.