On Mon, 1 May 2023, Jason Merrill wrote: > On 4/28/23 15:40, Patrick Palka wrote: > > On Fri, 28 Apr 2023, Patrick Palka wrote: > > > > > On Fri, 28 Apr 2023, Patrick Palka wrote: > > > > > > > After mechanically replacing RESULT_DECL within a constexpr call result > > > > (for sake of RVO), we can in some cases simplify the call result > > > > further. > > > > > > > > In the below testcase the result of get() during evaluation of a's > > > > initializer is the self-referential CONSTRUCTOR: > > > > > > > > {._M_p=(char *) &<retval>._M_local_buf} > > > > > > > > which after replacing RESULT_DECL with ctx->object (aka *D.2603, where > > > > the D.2603 temporary points to the current element of _M_elems under > > > > construction) becomes: > > > > > > > > {._M_p=(char *) &D.2603->_M_local_buf} > > > > > > > > but what we really want is: > > > > > > > > {._M_p=(char *) &a._M_elems[0]._M_local_buf}. > > > > > > > > so that the value of _M_p is independent of the value of the mutable > > > > D.2603 temporary. > > > > > > > > So to that end, it seems we should constexpr evaluate the result again > > > > after RESULT_DECL replacement, which is what this patch implements. > > > > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > > > > trunk? > > > > > > > > PR libstdc++/105440 > > > > > > > > gcc/cp/ChangeLog: > > > > > > > > * constexpr.cc (cxx_eval_call_expression): If any RESULT_DECLs > > > > get > > > > replaced in the call result, try further evaluating the result. > > > > > > > > gcc/testsuite/ChangeLog: > > > > > > > > * g++.dg/cpp2a/constexpr-dtor16.C: New test. > > > > --- > > > > gcc/cp/constexpr.cc | 12 +++++- > > > > gcc/testsuite/g++.dg/cpp2a/constexpr-dtor16.C | 39 +++++++++++++++++++ > > > > 2 files changed, 49 insertions(+), 2 deletions(-) > > > > create mode 100644 gcc/testsuite/g++.dg/cpp2a/constexpr-dtor16.C > > > > > > > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc > > > > index d1097764b10..22a1609e664 100644 > > > > --- a/gcc/cp/constexpr.cc > > > > +++ b/gcc/cp/constexpr.cc > > > > @@ -3213,7 +3213,12 @@ cxx_eval_call_expression (const constexpr_ctx > > > > *ctx, tree t, > > > > && CLASS_TYPE_P (TREE_TYPE (res)) > > > > && !is_empty_class (TREE_TYPE (res))) > > > > if (replace_decl (&result, res, ctx->object)) > > > > - cacheable = false; > > > > + { > > > > + cacheable = false; > > > > + result = cxx_eval_constant_expression (ctx, result, > > > > lval, > > > > + non_constant_p, > > > > + overflow_p); > > > > + } > > > > } > > > > else > > > > /* Couldn't get a function copy to evaluate. */ > > > > @@ -5988,9 +5993,12 @@ cxx_eval_store_expression (const constexpr_ctx > > > > *ctx, tree t, > > > > object = probe; > > > > else > > > > { > > > > + tree orig_probe = probe; > > > > probe = cxx_eval_constant_expression (ctx, probe, > > > > vc_glvalue, > > > > non_constant_p, > > > > overflow_p); > > > > evaluated = true; > > > > + if (orig_probe == target) > > > > + target = probe; > > > > > > Whoops, thanks to an accidental git commit --amend this patch contains > > > an alternative approach that I considered: in cxx_eval_store_expression, > > > ensure that we always set ctx->object to a fully reduced result (so > > > &a._M_elems[0] instead of of *D.2603 in this case), which means later > > > RESULT_DECL replacement with ctx->object should yield an already reduced > > > result as well. But with this approach I ran into a bogus "modifying > > > const object" error on cpp1y/constexpr-tracking-const23.C so I gave up > > > on it :( > > > > Ah, the problem was that later in cxx_eval_store_expression we were > > suppressing a TREE_READONLY update via pattern matching on 'target', > > but if we are now updating 'target' to its reduced value the pattern > > matching needs to consider the shape of the original 'target' instead. > > Here's an alternative fix for this PR that passes regression testing, > > not sure which approach would be preferable. > > With this patch I think we'd still miss the case where the target is a > subobject (so probe != target). > > And we would miss clearing cacheable in the case where we the value depends on > ctx->object. OTOH, that seems like it might already be an issue when we go > through here: > > > if (lval == vc_glvalue) > > { > > /* If we want to return a reference to the target, we need to evaluate > > it as a whole; > > otherwise, only evaluate the innermost piece to avoid > > building up unnecessary *_REFs. */ > > target = cxx_eval_constant_expression (ctx, target, lval, > > non_constant_p, overflow_p); > > So I think it makes sense to try to avoid the need for the replace_decl at all > by doing the replacement earlier along the lines of this patch, and replace > the replace_decl with looking for references to ctx->object in the result?
Sorry, I lost track of this patch... That does seem better but admittedly tricky to get right. Would the original approach (i.e. the second patch in this thread, that just re-evaluates the result after doing replace_decl) be OK to go in for GCC 15? While not ideal, it seems like a safe improvement to our current mechanism. > > > PR c++/105440 > > > > gcc/cp/ChangeLog: > > > > * constexpr.cc (cxx_eval_store_expression): Save the original > > target in 'orig_target'. Update 'target' after evaluating it in > > the 'probe' loop. Use 'orig_target' instead of 'target' when > > appropriate. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/cpp2a/constexpr-dtor16.C: New test. > > --- > > gcc/cp/constexpr.cc | 15 ++++++++++----- > > 1 file changed, 10 insertions(+), 5 deletions(-) > > > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc > > index 9dbbf6eec03..2939ac89a98 100644 > > --- a/gcc/cp/constexpr.cc > > +++ b/gcc/cp/constexpr.cc > > @@ -5902,8 +5902,10 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, > > tree t, > > /* Just ignore clobbers. */ > > return void_node; > > + const tree orig_target = TREE_OPERAND (t, 0); > > + > > /* First we figure out where we're storing to. */ > > - tree target = TREE_OPERAND (t, 0); > > + tree target = orig_target; > > maybe_simplify_trivial_copy (target, init); > > @@ -5993,9 +5995,12 @@ cxx_eval_store_expression (const constexpr_ctx > > *ctx, tree t, > > object = probe; > > else > > { > > + bool is_target = (probe == target); > > probe = cxx_eval_constant_expression (ctx, probe, vc_glvalue, > > non_constant_p, > > overflow_p); > > evaluated = true; > > + if (is_target) > > + target = probe; > > if (*non_constant_p) > > return t; > > } > > @@ -6159,7 +6164,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, > > tree t, > > if (!empty_base && !(same_type_ignoring_top_level_qualifiers_p > > (initialized_type (init), type))) > > { > > - gcc_assert (is_empty_class (TREE_TYPE (target))); > > + gcc_assert (is_empty_class (TREE_TYPE (orig_target))); > > empty_base = true; > > } > > @@ -6294,14 +6299,14 @@ cxx_eval_store_expression (const constexpr_ctx > > *ctx, tree t, > > && TREE_CODE (*valp) == CONSTRUCTOR > > && TYPE_READONLY (type)) > > { > > - if (INDIRECT_REF_P (target) > > + if (INDIRECT_REF_P (orig_target) > > && (is_this_parameter > > - (tree_strip_nop_conversions (TREE_OPERAND (target, 0))))) > > + (tree_strip_nop_conversions (TREE_OPERAND (orig_target, 0))))) > > /* We've just initialized '*this' (perhaps via the target > > constructor of a delegating constructor). Leave it up to the > > caller that set 'this' to set TREE_READONLY appropriately. */ > > gcc_checking_assert (same_type_ignoring_top_level_qualifiers_p > > - (TREE_TYPE (target), type) || empty_base); > > + (TREE_TYPE (orig_target), type) || empty_base); > > else > > TREE_READONLY (*valp) = true; > > } > > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-dtor16.C > > b/gcc/testsuite/g++.dg/cpp2a/constexpr-dtor16.C > > new file mode 100644 > > index 00000000000..707a3e025b1 > > --- /dev/null > > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-dtor16.C > > @@ -0,0 +1,39 @@ > > +// PR c++/105440 > > +// { dg-do compile { target c++20 } } > > + > > +struct basic_string { > > + char _M_local_buf[32]; > > + char* _M_p; > > + constexpr basic_string() : _M_p{_M_local_buf} { } > > + constexpr void f() { if (_M_p) { } } > > + constexpr ~basic_string() { if (_M_p) { } } > > +}; > > + > > +template<int N> > > +struct array { > > + basic_string _M_elems[N]; > > +}; > > + > > +constexpr basic_string get() { return {}; } > > + > > +constexpr bool f1() { > > + array<1> a{get()}; > > + a._M_elems[0].f(); > > + > > + return true; > > +} > > + > > +constexpr bool f2() { > > + array<2> a2{get(), get()}; > > + array<3> a3{get(), get(), get()}; > > + > > + for (basic_string& e : a2._M_elems) > > + e.f(); > > + for (basic_string& e : a3._M_elems) > > + e.f(); > > + > > + return true; > > +} > > + > > +static_assert(f1()); > > +static_assert(f2()); > >