From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 1EC28385840A for ; Thu, 4 Nov 2021 14:08:04 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 1EC28385840A Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-260-ex0Igz92O8uNZqYm7__Hiw-1; Thu, 04 Nov 2021 10:08:02 -0400 X-MC-Unique: ex0Igz92O8uNZqYm7__Hiw-1 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 4F1BB1808304 for ; Thu, 4 Nov 2021 14:08:01 +0000 (UTC) Received: from tucnak.zalov.cz (unknown [10.39.193.172]) by smtp.corp.redhat.com (Postfix) with ESMTPS id D8F935FC25; Thu, 4 Nov 2021 14:08:00 +0000 (UTC) Received: from tucnak.zalov.cz (localhost [127.0.0.1]) by tucnak.zalov.cz (8.16.1/8.16.1) with ESMTPS id 1A4E7wek3354819 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT); Thu, 4 Nov 2021 15:07:58 +0100 Received: (from jakub@localhost) by tucnak.zalov.cz (8.16.1/8.16.1/Submit) id 1A4E7vnt3354818; Thu, 4 Nov 2021 15:07:57 +0100 Date: Thu, 4 Nov 2021 15:07:57 +0100 From: Jakub Jelinek To: Jason Merrill Cc: gcc-patches@gcc.gnu.org Subject: [PATCH] c++: Fix up -fstrong-eval-order handling of call arguments [PR70796] Message-ID: <20211104140757.GX304296@tucnak> Reply-To: Jakub Jelinek MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=us-ascii Content-Disposition: inline X-Spam-Status: No, score=-5.7 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 04 Nov 2021 14:08:06 -0000 Hi! For -fstrong-eval-order (default for C++17 and later) we make sure to gimplify arguments in the right order, but as the following testcase shows that is not enough. The problem is that some lvalues can satisfy the is_gimple_val / fb_rvalue predicate used by gimplify_arg for is_gimple_reg_type typed expressions, or is_gimple_lvalue / fb_either used for other types. E.g. in foo we have: C::C (&p, ++i, ++i) before gimplification where i is an automatic int variable and without this patch gimplify that as: i = i + 1; i = i + 1; C::C (&p, i, i); which means that the ctor is called with the original i value incremented by 2 in both arguments, while because the call is CALL_EXPR_ORDERED_ARGS the first argument should be different. Similarly in qux we have: B::B (&p, TARGET_EXPR , TARGET_EXPR ) and gimplify it as: _1 = A::operator++ (&i); _2 = A::operator++ (&i); B::B (&p, MEM[(const struct A &)_1], MEM[(const struct A &)_2]); but because A::operator++ returns the passed in argument, again we have the same value in both cases due to gimplify_arg doing: /* Also strip a TARGET_EXPR that would force an extra copy. */ if (TREE_CODE (*arg_p) == TARGET_EXPR) { tree init = TARGET_EXPR_INITIAL (*arg_p); if (init && !VOID_TYPE_P (TREE_TYPE (init))) *arg_p = init; } which is perfectly fine optimization for calls with unordered arguments, but breaks the ordered ones. Lastly, in corge, we have before gimplification: D::foo (NON_LVALUE_EXPR

, 3, ++p) and gimplify it as p = p + 4; D::foo (p, 3, p); which is again wrong, because the this argument isn't before the side-effects but after it. The following patch adds cp_gimplify_arg wrapper, which if ordered and is_gimple_reg_type forces non-SSA_NAME is_gimple_variable result into a temporary, and if ordered, not is_gimple_reg_type and argument is TARGET_EXPR bypasses the gimplify_arg optimization. So, in foo with this patch we gimplify it as: i = i + 1; i.0_1 = i; i = i + 1; C::C (&p, i.0_1, i); in qux as: _1 = A::operator++ (&i); D.2312 = MEM[(const struct A &)_1]; _2 = A::operator++ (&i); B::B (&p, D.2312, MEM[(const struct A &)_2]); where D.2312 is a temporary and in corge as: p.9_1 = p; p = p + 4; D::foo (p.9_1, 3, p); The is_gimple_reg_type forcing into a temporary should be really cheap (I think even at -O0 it should be optimized if there is no modification in between), the aggregate copies might be more expensive but I think e.g. SRA or FRE should be able to deal with those if there are no intervening changes. But still, the patch tries to avoid those when it is cheaply provable that nothing bad happens (if no argument following it in the strong evaluation order doesn't have TREE_SIDE_EFFECTS, then even VAR_DECLs etc. shouldn't be modified after it). For the METHOD_TYPE first argument I use a temporary always though, that should be always is_gimple_reg_type... I've tried if e.g. int i = 1; return i << ++i; doesn't suffer from this problem as well, but it doesn't, the FE uses SAVE_EXPR , SAVE_EXPR << ++i; in that case which gimplifies the way we want (temporary in the first operand). Ok for trunk if it passes bootstrap/regtest? 2021-11-04 Jakub Jelinek PR c++/70796 * cp-gimplify.c (cp_gimplify_arg): New function. (cp_gimplify_expr): Use cp_gimplify_arg instead of gimplify_arg, pass true as last argument to it if there are any following arguments in strong evaluation order with side-effects. * g++.dg/cpp1z/eval-order11.C: New test. --- gcc/cp/cp-gimplify.c.jj 2021-10-29 19:33:10.542344939 +0200 +++ gcc/cp/cp-gimplify.c 2021-11-04 14:55:46.473306970 +0100 @@ -398,6 +398,42 @@ gimplify_to_rvalue (tree *expr_p, gimple return t; } +/* Like gimplify_arg, but if ORDERED is set (which should be set if + any of the arguments this argument is sequenced before has + TREE_SIDE_EFFECTS set, make sure expressions with is_gimple_reg_type type + are gimplified into SSA_NAME or a fresh temporary and for + non-is_gimple_reg_type we don't optimize away TARGET_EXPRs. */ + +static enum gimplify_status +cp_gimplify_arg (tree *arg_p, gimple_seq *pre_p, location_t call_location, + bool ordered) +{ + enum gimplify_status t; + if (ordered + && !is_gimple_reg_type (TREE_TYPE (*arg_p)) + && TREE_CODE (*arg_p) == TARGET_EXPR) + { + /* gimplify_arg would strip away the TARGET_EXPR, but + that can mean we don't copy the argument and some following + argument with side-effect could modify it. */ + protected_set_expr_location (*arg_p, call_location); + return gimplify_expr (arg_p, pre_p, NULL, is_gimple_lvalue, fb_either); + } + else + { + t = gimplify_arg (arg_p, pre_p, call_location); + if (t == GS_ERROR) + return GS_ERROR; + else if (ordered + && is_gimple_reg_type (TREE_TYPE (*arg_p)) + && is_gimple_variable (*arg_p) + && TREE_CODE (*arg_p) != SSA_NAME) + *arg_p = get_initialized_tmp_var (*arg_p, pre_p); + return t; + } + +} + /* Do C++-specific gimplification. Args are as for gimplify_expr. */ int @@ -613,7 +649,8 @@ cp_gimplify_expr (tree *expr_p, gimple_s gcc_assert (call_expr_nargs (*expr_p) == 2); gcc_assert (!CALL_EXPR_ORDERED_ARGS (*expr_p)); enum gimplify_status t - = gimplify_arg (&CALL_EXPR_ARG (*expr_p, 1), pre_p, loc); + = cp_gimplify_arg (&CALL_EXPR_ARG (*expr_p, 1), pre_p, loc, + TREE_SIDE_EFFECTS (CALL_EXPR_ARG (*expr_p, 0))); if (t == GS_ERROR) ret = GS_ERROR; } @@ -622,10 +659,18 @@ cp_gimplify_expr (tree *expr_p, gimple_s /* Leave the last argument for gimplify_call_expr, to avoid problems with __builtin_va_arg_pack(). */ int nargs = call_expr_nargs (*expr_p) - 1; + int last_side_effects_arg = -1; + for (int i = nargs; i > 0; --i) + if (TREE_SIDE_EFFECTS (CALL_EXPR_ARG (*expr_p, i))) + { + last_side_effects_arg = i; + break; + } for (int i = 0; i < nargs; ++i) { enum gimplify_status t - = gimplify_arg (&CALL_EXPR_ARG (*expr_p, i), pre_p, loc); + = cp_gimplify_arg (&CALL_EXPR_ARG (*expr_p, i), pre_p, loc, + i < last_side_effects_arg); if (t == GS_ERROR) ret = GS_ERROR; } @@ -640,7 +685,8 @@ cp_gimplify_expr (tree *expr_p, gimple_s if (TREE_CODE (fntype) == METHOD_TYPE) { enum gimplify_status t - = gimplify_arg (&CALL_EXPR_ARG (*expr_p, 0), pre_p, loc); + = cp_gimplify_arg (&CALL_EXPR_ARG (*expr_p, 0), pre_p, loc, + true); if (t == GS_ERROR) ret = GS_ERROR; } --- gcc/testsuite/g++.dg/cpp1z/eval-order11.C.jj 2021-11-04 14:02:50.439482571 +0100 +++ gcc/testsuite/g++.dg/cpp1z/eval-order11.C 2021-11-04 14:15:43.850479169 +0100 @@ -0,0 +1,89 @@ +// PR c++/70796 +// { dg-do run { target c++11 } } +// { dg-options "-fstrong-eval-order" { target c++14_down } } + +struct A +{ + int x = 0; + A & operator ++ () { ++x; return *this; } +}; +struct B +{ + A first, second; + B (A x, A y) : first{x}, second{y} {} +}; +struct C +{ + int first, second; + C (int x, int y) : first{x}, second{y} {} +}; +struct D +{ + int d; + void foo (int x, D *y) + { + if (y != this + 1) + __builtin_abort (); + d = x; + } +}; +D d[2] = { { 1 }, { 2 } }; + +void +foo () +{ + int i = 0; + C p{++i, ++i}; + if (p.first != 1 || p.second != 2) + __builtin_abort (); +} + +void +bar () +{ + int i = 0; + C p{++i, ++i}; + if (p.first != 1 || p.second != 2) + __builtin_abort (); + int &j = i; + C q{++j, ++j}; + if (q.first != 3 || q.second != 4) + __builtin_abort (); +} + +void +baz () +{ + int i = 0; + C p{(int &) ++i, (int &) ++i}; + if (p.first != 1 || p.second != 2) + __builtin_abort (); +} + +void +qux () +{ + A i; + B p{++i, ++i}; + if (p.first.x != 1 || p.second.x != 2) + __builtin_abort (); +} + +void +corge () +{ + D *p = &d[0]; + p->foo (3, ++p); + if (d[0].d != 3 || d[1].d != 2) + __builtin_abort (); +} + +int +main () +{ + bar (); + baz (); + foo (); + qux (); + corge (); +} Jakub