diff mbox series

[v3] c++: Ensure ANNOTATE_EXPRs remain outermost expressions in conditions [PR116140]

Message ID Zs2ifl4ZIVxXq2SE@arm.com
State New
Headers show
Series [v3] c++: Ensure ANNOTATE_EXPRs remain outermost expressions in conditions [PR116140] | expand

Commit Message

Alex Coplan Aug. 27, 2024, 9:55 a.m. UTC
Hi,

This is a v3 that hopefully addresses the feedback from both Jason and
Jakub.  v2 was posted here:
https://gcc.gnu.org/pipermail/gcc-patches/2024-August/660191.html

(Sorry for the delay in posting the re-spin, I was away last week.)

In this version we refactor to introudce a helper class (annotate_saver)
which is much less invasive to the caller (maybe_convert_cond) and
should (at least in theory) be reuseable elsewhere.

This version also relies on the assumption that operands 1 and 2 of
ANNOTATE_EXPRs are INTEGER_CSTs, which simplifies the flag updates
without having to rely on assumptions about the specific changes made
in maybe_convert_cond.

Bootstrapped/regtested on aarch64-linux-gnu, OK for trunk?

Thanks,
Alex

-- >8 --

For the testcase added with this patch, we would end up losing the:

  #pragma GCC unroll 4

and emitting "warning: ignoring loop annotation".  That warning comes
from tree-cfg.cc:replace_loop_annotate, and means that we failed to
process the ANNOTATE_EXPR in tree-cfg.cc:replace_loop_annotate_in_block.
That function walks backwards over the GIMPLE in an exiting BB for a
loop, skipping over the final gcond, and looks for any ANNOTATE_EXPRS
immediately preceding the gcond.

The function documents the following pre-condition:

   /* [...] We assume that the annotations come immediately before the
      condition in BB, if any.  */

now looking at the exiting BB of the loop, we have:

  <bb 8> :
  D.4524 = .ANNOTATE (iftmp.1, 1, 4);
  retval.0 = D.4524;
  if (retval.0 != 0)
    goto <bb 3>; [INV]
  else
    goto <bb 9>; [INV]

and crucially there is an intervening assignment between the gcond and
the preceding .ANNOTATE ifn call.  To see where this comes from, we can
look to the IR given by -fdump-tree-original:

  if (<<cleanup_point ANNOTATE_EXPR <first != last && !use_find(short
    int*)::<lambda(short int)>::operator() (&pred, *first), unroll 4>>>)
    goto <D.4518>;
  else
    goto <D.4516>;

here the problem is that we've wrapped a CLEANUP_POINT_EXPR around the
ANNOTATE_EXPR, meaning the ANNOTATE_EXPR is no longer the outermost
expression in the condition.

The CLEANUP_POINT_EXPR gets added by the following call chain:

finish_while_stmt_cond
 -> maybe_convert_cond
 -> condition_conversion
 -> fold_build_cleanup_point_expr

this patch chooses to fix the issue by first introducing a new helper
class (annotate_saver) to save and restore outer chains of
ANNOTATE_EXPRs and then using it in maybe_convert_cond.

With this patch, we don't get any such warning and the loop gets unrolled as
expected at -O2.

gcc/cp/ChangeLog:

        PR libstdc++/116140
        * semantics.cc (anotate_saver): New. Use it ...
        (maybe_convert_cond): ... here, to ensure any ANNOTATE_EXPRs
        remain the outermost expression(s) of the condition.

gcc/testsuite/ChangeLog:

        PR libstdc++/116140
        * g++.dg/ext/pragma-unroll-lambda.C: New test.

Comments

Alex Coplan Sept. 10, 2024, 10:10 a.m. UTC | #1
On 27/08/2024 10:55, Alex Coplan wrote:
> Hi,
> 
> This is a v3 that hopefully addresses the feedback from both Jason and
> Jakub.  v2 was posted here:
> https://gcc.gnu.org/pipermail/gcc-patches/2024-August/660191.html

Gentle ping on this C++ patch:
https://gcc.gnu.org/pipermail/gcc-patches/2024-August/661559.html

Jason, are you OK with this approach, or would you prefer to not make the
INTEGER_CST assumption and do something along the lines of your last suggestion
instead:

> Perhaps we want a recompute_expr_flags like the existing 
> recompute_constructor_flags, so we don't need to duplicate PROCESS_ARG 
> logic elsewhere.

?  Sorry, I'd missed that reply when I wrote the v3 patch.

Thanks,
Alex

> 
> (Sorry for the delay in posting the re-spin, I was away last week.)
> 
> In this version we refactor to introudce a helper class (annotate_saver)
> which is much less invasive to the caller (maybe_convert_cond) and
> should (at least in theory) be reuseable elsewhere.
> 
> This version also relies on the assumption that operands 1 and 2 of
> ANNOTATE_EXPRs are INTEGER_CSTs, which simplifies the flag updates
> without having to rely on assumptions about the specific changes made
> in maybe_convert_cond.
> 
> Bootstrapped/regtested on aarch64-linux-gnu, OK for trunk?
> 
> Thanks,
> Alex
> 
> -- >8 --
> 
> For the testcase added with this patch, we would end up losing the:
> 
>   #pragma GCC unroll 4
> 
> and emitting "warning: ignoring loop annotation".  That warning comes
> from tree-cfg.cc:replace_loop_annotate, and means that we failed to
> process the ANNOTATE_EXPR in tree-cfg.cc:replace_loop_annotate_in_block.
> That function walks backwards over the GIMPLE in an exiting BB for a
> loop, skipping over the final gcond, and looks for any ANNOTATE_EXPRS
> immediately preceding the gcond.
> 
> The function documents the following pre-condition:
> 
>    /* [...] We assume that the annotations come immediately before the
>       condition in BB, if any.  */
> 
> now looking at the exiting BB of the loop, we have:
> 
>   <bb 8> :
>   D.4524 = .ANNOTATE (iftmp.1, 1, 4);
>   retval.0 = D.4524;
>   if (retval.0 != 0)
>     goto <bb 3>; [INV]
>   else
>     goto <bb 9>; [INV]
> 
> and crucially there is an intervening assignment between the gcond and
> the preceding .ANNOTATE ifn call.  To see where this comes from, we can
> look to the IR given by -fdump-tree-original:
> 
>   if (<<cleanup_point ANNOTATE_EXPR <first != last && !use_find(short
>     int*)::<lambda(short int)>::operator() (&pred, *first), unroll 4>>>)
>     goto <D.4518>;
>   else
>     goto <D.4516>;
> 
> here the problem is that we've wrapped a CLEANUP_POINT_EXPR around the
> ANNOTATE_EXPR, meaning the ANNOTATE_EXPR is no longer the outermost
> expression in the condition.
> 
> The CLEANUP_POINT_EXPR gets added by the following call chain:
> 
> finish_while_stmt_cond
>  -> maybe_convert_cond
>  -> condition_conversion
>  -> fold_build_cleanup_point_expr
> 
> this patch chooses to fix the issue by first introducing a new helper
> class (annotate_saver) to save and restore outer chains of
> ANNOTATE_EXPRs and then using it in maybe_convert_cond.
> 
> With this patch, we don't get any such warning and the loop gets unrolled as
> expected at -O2.
> 
> gcc/cp/ChangeLog:
> 
>         PR libstdc++/116140
>         * semantics.cc (anotate_saver): New. Use it ...
>         (maybe_convert_cond): ... here, to ensure any ANNOTATE_EXPRs
>         remain the outermost expression(s) of the condition.
> 
> gcc/testsuite/ChangeLog:
> 
>         PR libstdc++/116140
>         * g++.dg/ext/pragma-unroll-lambda.C: New test.

> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 5ab2076b673..b1a49b14238 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -951,6 +951,86 @@ maybe_warn_unparenthesized_assignment (tree t, bool nested_p,
>      }
>  }
>  
> +/* Helper class for saving/restoring ANNOTATE_EXPRs.  For a tree node t, users
> +   can construct one of these like so:
> +
> +     annotate_saver s (&t);
> +
> +   and t will be updated to have any annotations removed.  The user can then
> +   transform t, and later restore the ANNOTATE_EXPRs with:
> +
> +     t = s.restore (t).
> +
> +   The intent is to ensure that any ANNOTATE_EXPRs remain the outermost
> +   expressions following any operations on t.  */
> +
> +class annotate_saver {
> +  /* The chain of saved annotations, if there were any.  Otherwise null.  */
> +  tree m_annotations;
> +
> +  /* If M_ANNOTATIONS is non-null, then M_INNER points to TREE_OPERAND (A, 0)
> +     for the innermost annotation A.  */
> +  tree *m_inner;
> +
> +public:
> +  annotate_saver (tree *);
> +  tree restore (tree);
> +};
> +
> +/* If *COND is an ANNOTATE_EXPR, walk through the chain of annotations, and set
> +   *COND equal to the first non-ANNOTATE_EXPR (saving a pointer to the
> +   original chain of annotations for later use in restore).  */
> +
> +annotate_saver::annotate_saver (tree *cond) : m_annotations (nullptr)
> +{
> +  tree *t = cond;
> +  while (TREE_CODE (*t) == ANNOTATE_EXPR)
> +    t = &TREE_OPERAND (*t, 0);
> +
> +  if (t != cond)
> +    {
> +      m_annotations = *cond;
> +      *cond = *t;
> +      m_inner = t;
> +    }
> +}
> +
> +/* If we didn't strip any annotations on construction, return NEW_INNER
> +   unmodified.  Otherwise, wrap the saved annotations around NEW_INNER (updating
> +   the types and flags of the annotations if needed) and return the resulting
> +   expression.  */
> +
> +tree
> +annotate_saver::restore (tree new_inner)
> +{
> +  if (!m_annotations)
> +    return new_inner;
> +
> +  /* If the type of the inner expression changed, we need to update the types
> +     of all the ANNOTATE_EXPRs.  We may need to update the flags too, but we
> +     assume they only change if the type of the inner expression changes.
> +     The flag update logic assumes that the other operands to the
> +     ANNOTATE_EXPRs are always INTEGER_CSTs.  */
> +  if (TREE_TYPE (new_inner) != TREE_TYPE (*m_inner))
> +    {
> +      const bool new_readonly
> +	= TREE_READONLY (new_inner) || CONSTANT_CLASS_P (new_inner);
> +
> +      for (tree c = m_annotations; c != *m_inner; c = TREE_OPERAND (c, 0))
> +	{
> +	  gcc_checking_assert (TREE_CODE (c) == ANNOTATE_EXPR
> +			       && TREE_CODE (TREE_OPERAND (c, 1)) == INTEGER_CST
> +			       && TREE_CODE (TREE_OPERAND (c, 2)) == INTEGER_CST);
> +	  TREE_TYPE (c) = TREE_TYPE (new_inner);
> +	  TREE_SIDE_EFFECTS (c) = TREE_SIDE_EFFECTS (new_inner);
> +	  TREE_READONLY (c) = new_readonly;
> +	}
> +    }
> +
> +  *m_inner = new_inner;
> +  return m_annotations;
> +}
> +
>  /* COND is the condition-expression for an if, while, etc.,
>     statement.  Convert it to a boolean value, if appropriate.
>     In addition, verify sequence points if -Wsequence-point is enabled.  */
> @@ -966,6 +1046,9 @@ maybe_convert_cond (tree cond)
>    if (type_dependent_expression_p (cond))
>      return cond;
>  
> +  /* Strip any ANNOTATE_EXPRs from COND.  */
> +  annotate_saver annotations (&cond);
> +
>    /* For structured binding used in condition, the conversion needs to be
>       evaluated before the individual variables are initialized in the
>       std::tuple_{size,elemenet} case.  cp_finish_decomp saved the conversion
> @@ -984,7 +1067,10 @@ maybe_convert_cond (tree cond)
>  
>    /* Do the conversion.  */
>    cond = convert_from_reference (cond);
> -  return condition_conversion (cond);
> +  cond = condition_conversion (cond);
> +
> +  /* Restore any ANNOTATE_EXPRs around COND.  */
> +  return annotations.restore (cond);
>  }
>  
>  /* Finish an expression-statement, whose EXPRESSION is as indicated.  */
> diff --git a/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C b/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C
> new file mode 100644
> index 00000000000..f410f6d8d25
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile { target c++11 } }
> +
> +template<typename Iter, typename Pred>
> +inline Iter
> +my_find(Iter first, Iter last, Pred pred)
> +{
> +#pragma GCC unroll 4
> +    while (first != last && !pred(*first))
> +        ++first;
> +    return first;
> +}
> +
> +short *use_find(short *p)
> +{
> +    auto pred = [](short x) { return x == 42; };
> +    return my_find(p, p + 1024, pred);
> +}
Jason Merrill Sept. 10, 2024, 2:29 p.m. UTC | #2
On 9/10/24 6:10 AM, Alex Coplan wrote:
> On 27/08/2024 10:55, Alex Coplan wrote:
>> Hi,
>>
>> This is a v3 that hopefully addresses the feedback from both Jason and
>> Jakub.  v2 was posted here:
>> https://gcc.gnu.org/pipermail/gcc-patches/2024-August/660191.html
> 
> Gentle ping on this C++ patch:
> https://gcc.gnu.org/pipermail/gcc-patches/2024-August/661559.html
> 
> Jason, are you OK with this approach, or would you prefer to not make the
> INTEGER_CST assumption and do something along the lines of your last suggestion
> instead:
> 
>> Perhaps we want a recompute_expr_flags like the existing
>> recompute_constructor_flags, so we don't need to duplicate PROCESS_ARG
>> logic elsewhere.
> 
> ?  Sorry, I'd missed that reply when I wrote the v3 patch.

I still think that function would be nice to have, but the patch is OK 
as is.

> Thanks,
> Alex
> 
>>
>> (Sorry for the delay in posting the re-spin, I was away last week.)
>>
>> In this version we refactor to introudce a helper class (annotate_saver)
>> which is much less invasive to the caller (maybe_convert_cond) and
>> should (at least in theory) be reuseable elsewhere.
>>
>> This version also relies on the assumption that operands 1 and 2 of
>> ANNOTATE_EXPRs are INTEGER_CSTs, which simplifies the flag updates
>> without having to rely on assumptions about the specific changes made
>> in maybe_convert_cond.
>>
>> Bootstrapped/regtested on aarch64-linux-gnu, OK for trunk?
>>
>> Thanks,
>> Alex
>>
>> -- >8 --
>>
>> For the testcase added with this patch, we would end up losing the:
>>
>>    #pragma GCC unroll 4
>>
>> and emitting "warning: ignoring loop annotation".  That warning comes
>> from tree-cfg.cc:replace_loop_annotate, and means that we failed to
>> process the ANNOTATE_EXPR in tree-cfg.cc:replace_loop_annotate_in_block.
>> That function walks backwards over the GIMPLE in an exiting BB for a
>> loop, skipping over the final gcond, and looks for any ANNOTATE_EXPRS
>> immediately preceding the gcond.
>>
>> The function documents the following pre-condition:
>>
>>     /* [...] We assume that the annotations come immediately before the
>>        condition in BB, if any.  */
>>
>> now looking at the exiting BB of the loop, we have:
>>
>>    <bb 8> :
>>    D.4524 = .ANNOTATE (iftmp.1, 1, 4);
>>    retval.0 = D.4524;
>>    if (retval.0 != 0)
>>      goto <bb 3>; [INV]
>>    else
>>      goto <bb 9>; [INV]
>>
>> and crucially there is an intervening assignment between the gcond and
>> the preceding .ANNOTATE ifn call.  To see where this comes from, we can
>> look to the IR given by -fdump-tree-original:
>>
>>    if (<<cleanup_point ANNOTATE_EXPR <first != last && !use_find(short
>>      int*)::<lambda(short int)>::operator() (&pred, *first), unroll 4>>>)
>>      goto <D.4518>;
>>    else
>>      goto <D.4516>;
>>
>> here the problem is that we've wrapped a CLEANUP_POINT_EXPR around the
>> ANNOTATE_EXPR, meaning the ANNOTATE_EXPR is no longer the outermost
>> expression in the condition.
>>
>> The CLEANUP_POINT_EXPR gets added by the following call chain:
>>
>> finish_while_stmt_cond
>>   -> maybe_convert_cond
>>   -> condition_conversion
>>   -> fold_build_cleanup_point_expr
>>
>> this patch chooses to fix the issue by first introducing a new helper
>> class (annotate_saver) to save and restore outer chains of
>> ANNOTATE_EXPRs and then using it in maybe_convert_cond.
>>
>> With this patch, we don't get any such warning and the loop gets unrolled as
>> expected at -O2.
>>
>> gcc/cp/ChangeLog:
>>
>>          PR libstdc++/116140
>>          * semantics.cc (anotate_saver): New. Use it ...
>>          (maybe_convert_cond): ... here, to ensure any ANNOTATE_EXPRs
>>          remain the outermost expression(s) of the condition.
>>
>> gcc/testsuite/ChangeLog:
>>
>>          PR libstdc++/116140
>>          * g++.dg/ext/pragma-unroll-lambda.C: New test.
> 
>> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
>> index 5ab2076b673..b1a49b14238 100644
>> --- a/gcc/cp/semantics.cc
>> +++ b/gcc/cp/semantics.cc
>> @@ -951,6 +951,86 @@ maybe_warn_unparenthesized_assignment (tree t, bool nested_p,
>>       }
>>   }
>>   
>> +/* Helper class for saving/restoring ANNOTATE_EXPRs.  For a tree node t, users
>> +   can construct one of these like so:
>> +
>> +     annotate_saver s (&t);
>> +
>> +   and t will be updated to have any annotations removed.  The user can then
>> +   transform t, and later restore the ANNOTATE_EXPRs with:
>> +
>> +     t = s.restore (t).
>> +
>> +   The intent is to ensure that any ANNOTATE_EXPRs remain the outermost
>> +   expressions following any operations on t.  */
>> +
>> +class annotate_saver {
>> +  /* The chain of saved annotations, if there were any.  Otherwise null.  */
>> +  tree m_annotations;
>> +
>> +  /* If M_ANNOTATIONS is non-null, then M_INNER points to TREE_OPERAND (A, 0)
>> +     for the innermost annotation A.  */
>> +  tree *m_inner;
>> +
>> +public:
>> +  annotate_saver (tree *);
>> +  tree restore (tree);
>> +};
>> +
>> +/* If *COND is an ANNOTATE_EXPR, walk through the chain of annotations, and set
>> +   *COND equal to the first non-ANNOTATE_EXPR (saving a pointer to the
>> +   original chain of annotations for later use in restore).  */
>> +
>> +annotate_saver::annotate_saver (tree *cond) : m_annotations (nullptr)
>> +{
>> +  tree *t = cond;
>> +  while (TREE_CODE (*t) == ANNOTATE_EXPR)
>> +    t = &TREE_OPERAND (*t, 0);
>> +
>> +  if (t != cond)
>> +    {
>> +      m_annotations = *cond;
>> +      *cond = *t;
>> +      m_inner = t;
>> +    }
>> +}
>> +
>> +/* If we didn't strip any annotations on construction, return NEW_INNER
>> +   unmodified.  Otherwise, wrap the saved annotations around NEW_INNER (updating
>> +   the types and flags of the annotations if needed) and return the resulting
>> +   expression.  */
>> +
>> +tree
>> +annotate_saver::restore (tree new_inner)
>> +{
>> +  if (!m_annotations)
>> +    return new_inner;
>> +
>> +  /* If the type of the inner expression changed, we need to update the types
>> +     of all the ANNOTATE_EXPRs.  We may need to update the flags too, but we
>> +     assume they only change if the type of the inner expression changes.
>> +     The flag update logic assumes that the other operands to the
>> +     ANNOTATE_EXPRs are always INTEGER_CSTs.  */
>> +  if (TREE_TYPE (new_inner) != TREE_TYPE (*m_inner))
>> +    {
>> +      const bool new_readonly
>> +	= TREE_READONLY (new_inner) || CONSTANT_CLASS_P (new_inner);
>> +
>> +      for (tree c = m_annotations; c != *m_inner; c = TREE_OPERAND (c, 0))
>> +	{
>> +	  gcc_checking_assert (TREE_CODE (c) == ANNOTATE_EXPR
>> +			       && TREE_CODE (TREE_OPERAND (c, 1)) == INTEGER_CST
>> +			       && TREE_CODE (TREE_OPERAND (c, 2)) == INTEGER_CST);
>> +	  TREE_TYPE (c) = TREE_TYPE (new_inner);
>> +	  TREE_SIDE_EFFECTS (c) = TREE_SIDE_EFFECTS (new_inner);
>> +	  TREE_READONLY (c) = new_readonly;
>> +	}
>> +    }
>> +
>> +  *m_inner = new_inner;
>> +  return m_annotations;
>> +}
>> +
>>   /* COND is the condition-expression for an if, while, etc.,
>>      statement.  Convert it to a boolean value, if appropriate.
>>      In addition, verify sequence points if -Wsequence-point is enabled.  */
>> @@ -966,6 +1046,9 @@ maybe_convert_cond (tree cond)
>>     if (type_dependent_expression_p (cond))
>>       return cond;
>>   
>> +  /* Strip any ANNOTATE_EXPRs from COND.  */
>> +  annotate_saver annotations (&cond);
>> +
>>     /* For structured binding used in condition, the conversion needs to be
>>        evaluated before the individual variables are initialized in the
>>        std::tuple_{size,elemenet} case.  cp_finish_decomp saved the conversion
>> @@ -984,7 +1067,10 @@ maybe_convert_cond (tree cond)
>>   
>>     /* Do the conversion.  */
>>     cond = convert_from_reference (cond);
>> -  return condition_conversion (cond);
>> +  cond = condition_conversion (cond);
>> +
>> +  /* Restore any ANNOTATE_EXPRs around COND.  */
>> +  return annotations.restore (cond);
>>   }
>>   
>>   /* Finish an expression-statement, whose EXPRESSION is as indicated.  */
>> diff --git a/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C b/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C
>> new file mode 100644
>> index 00000000000..f410f6d8d25
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C
>> @@ -0,0 +1,17 @@
>> +// { dg-do compile { target c++11 } }
>> +
>> +template<typename Iter, typename Pred>
>> +inline Iter
>> +my_find(Iter first, Iter last, Pred pred)
>> +{
>> +#pragma GCC unroll 4
>> +    while (first != last && !pred(*first))
>> +        ++first;
>> +    return first;
>> +}
>> +
>> +short *use_find(short *p)
>> +{
>> +    auto pred = [](short x) { return x == 42; };
>> +    return my_find(p, p + 1024, pred);
>> +}
>
Alex Coplan Sept. 11, 2024, 10:59 a.m. UTC | #3
On 10/09/2024 10:29, Jason Merrill wrote:
> On 9/10/24 6:10 AM, Alex Coplan wrote:
> > On 27/08/2024 10:55, Alex Coplan wrote:
> > > Hi,
> > > 
> > > This is a v3 that hopefully addresses the feedback from both Jason and
> > > Jakub.  v2 was posted here:
> > > https://gcc.gnu.org/pipermail/gcc-patches/2024-August/660191.html
> > 
> > Gentle ping on this C++ patch:
> > https://gcc.gnu.org/pipermail/gcc-patches/2024-August/661559.html
> > 
> > Jason, are you OK with this approach, or would you prefer to not make the
> > INTEGER_CST assumption and do something along the lines of your last suggestion
> > instead:
> > 
> > > Perhaps we want a recompute_expr_flags like the existing
> > > recompute_constructor_flags, so we don't need to duplicate PROCESS_ARG
> > > logic elsewhere.
> > 
> > ?  Sorry, I'd missed that reply when I wrote the v3 patch.
> 
> I still think that function would be nice to have, but the patch is OK as
> is.

Thanks, I've pushed the patch and the rest of the series as:

3fd07d4f04f libstdc++: Restore unrolling in std::find using pragma [PR116140]
9759f6299d9 lto: Stream has_unroll flag during LTO [PR116140]
31ff173c708 testsuite: Ensure ltrans dump files get cleaned up properly [PR116140]
f97d86242b8 c++: Ensure ANNOTATE_EXPRs remain outermost expressions in conditions [PR116140]

Alex

> 
> > Thanks,
> > Alex
> > 
> > > 
> > > (Sorry for the delay in posting the re-spin, I was away last week.)
> > > 
> > > In this version we refactor to introudce a helper class (annotate_saver)
> > > which is much less invasive to the caller (maybe_convert_cond) and
> > > should (at least in theory) be reuseable elsewhere.
> > > 
> > > This version also relies on the assumption that operands 1 and 2 of
> > > ANNOTATE_EXPRs are INTEGER_CSTs, which simplifies the flag updates
> > > without having to rely on assumptions about the specific changes made
> > > in maybe_convert_cond.
> > > 
> > > Bootstrapped/regtested on aarch64-linux-gnu, OK for trunk?
> > > 
> > > Thanks,
> > > Alex
> > > 
> > > -- >8 --
> > > 
> > > For the testcase added with this patch, we would end up losing the:
> > > 
> > >    #pragma GCC unroll 4
> > > 
> > > and emitting "warning: ignoring loop annotation".  That warning comes
> > > from tree-cfg.cc:replace_loop_annotate, and means that we failed to
> > > process the ANNOTATE_EXPR in tree-cfg.cc:replace_loop_annotate_in_block.
> > > That function walks backwards over the GIMPLE in an exiting BB for a
> > > loop, skipping over the final gcond, and looks for any ANNOTATE_EXPRS
> > > immediately preceding the gcond.
> > > 
> > > The function documents the following pre-condition:
> > > 
> > >     /* [...] We assume that the annotations come immediately before the
> > >        condition in BB, if any.  */
> > > 
> > > now looking at the exiting BB of the loop, we have:
> > > 
> > >    <bb 8> :
> > >    D.4524 = .ANNOTATE (iftmp.1, 1, 4);
> > >    retval.0 = D.4524;
> > >    if (retval.0 != 0)
> > >      goto <bb 3>; [INV]
> > >    else
> > >      goto <bb 9>; [INV]
> > > 
> > > and crucially there is an intervening assignment between the gcond and
> > > the preceding .ANNOTATE ifn call.  To see where this comes from, we can
> > > look to the IR given by -fdump-tree-original:
> > > 
> > >    if (<<cleanup_point ANNOTATE_EXPR <first != last && !use_find(short
> > >      int*)::<lambda(short int)>::operator() (&pred, *first), unroll 4>>>)
> > >      goto <D.4518>;
> > >    else
> > >      goto <D.4516>;
> > > 
> > > here the problem is that we've wrapped a CLEANUP_POINT_EXPR around the
> > > ANNOTATE_EXPR, meaning the ANNOTATE_EXPR is no longer the outermost
> > > expression in the condition.
> > > 
> > > The CLEANUP_POINT_EXPR gets added by the following call chain:
> > > 
> > > finish_while_stmt_cond
> > >   -> maybe_convert_cond
> > >   -> condition_conversion
> > >   -> fold_build_cleanup_point_expr
> > > 
> > > this patch chooses to fix the issue by first introducing a new helper
> > > class (annotate_saver) to save and restore outer chains of
> > > ANNOTATE_EXPRs and then using it in maybe_convert_cond.
> > > 
> > > With this patch, we don't get any such warning and the loop gets unrolled as
> > > expected at -O2.
> > > 
> > > gcc/cp/ChangeLog:
> > > 
> > >          PR libstdc++/116140
> > >          * semantics.cc (anotate_saver): New. Use it ...
> > >          (maybe_convert_cond): ... here, to ensure any ANNOTATE_EXPRs
> > >          remain the outermost expression(s) of the condition.
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > >          PR libstdc++/116140
> > >          * g++.dg/ext/pragma-unroll-lambda.C: New test.
> > 
> > > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > > index 5ab2076b673..b1a49b14238 100644
> > > --- a/gcc/cp/semantics.cc
> > > +++ b/gcc/cp/semantics.cc
> > > @@ -951,6 +951,86 @@ maybe_warn_unparenthesized_assignment (tree t, bool nested_p,
> > >       }
> > >   }
> > > +/* Helper class for saving/restoring ANNOTATE_EXPRs.  For a tree node t, users
> > > +   can construct one of these like so:
> > > +
> > > +     annotate_saver s (&t);
> > > +
> > > +   and t will be updated to have any annotations removed.  The user can then
> > > +   transform t, and later restore the ANNOTATE_EXPRs with:
> > > +
> > > +     t = s.restore (t).
> > > +
> > > +   The intent is to ensure that any ANNOTATE_EXPRs remain the outermost
> > > +   expressions following any operations on t.  */
> > > +
> > > +class annotate_saver {
> > > +  /* The chain of saved annotations, if there were any.  Otherwise null.  */
> > > +  tree m_annotations;
> > > +
> > > +  /* If M_ANNOTATIONS is non-null, then M_INNER points to TREE_OPERAND (A, 0)
> > > +     for the innermost annotation A.  */
> > > +  tree *m_inner;
> > > +
> > > +public:
> > > +  annotate_saver (tree *);
> > > +  tree restore (tree);
> > > +};
> > > +
> > > +/* If *COND is an ANNOTATE_EXPR, walk through the chain of annotations, and set
> > > +   *COND equal to the first non-ANNOTATE_EXPR (saving a pointer to the
> > > +   original chain of annotations for later use in restore).  */
> > > +
> > > +annotate_saver::annotate_saver (tree *cond) : m_annotations (nullptr)
> > > +{
> > > +  tree *t = cond;
> > > +  while (TREE_CODE (*t) == ANNOTATE_EXPR)
> > > +    t = &TREE_OPERAND (*t, 0);
> > > +
> > > +  if (t != cond)
> > > +    {
> > > +      m_annotations = *cond;
> > > +      *cond = *t;
> > > +      m_inner = t;
> > > +    }
> > > +}
> > > +
> > > +/* If we didn't strip any annotations on construction, return NEW_INNER
> > > +   unmodified.  Otherwise, wrap the saved annotations around NEW_INNER (updating
> > > +   the types and flags of the annotations if needed) and return the resulting
> > > +   expression.  */
> > > +
> > > +tree
> > > +annotate_saver::restore (tree new_inner)
> > > +{
> > > +  if (!m_annotations)
> > > +    return new_inner;
> > > +
> > > +  /* If the type of the inner expression changed, we need to update the types
> > > +     of all the ANNOTATE_EXPRs.  We may need to update the flags too, but we
> > > +     assume they only change if the type of the inner expression changes.
> > > +     The flag update logic assumes that the other operands to the
> > > +     ANNOTATE_EXPRs are always INTEGER_CSTs.  */
> > > +  if (TREE_TYPE (new_inner) != TREE_TYPE (*m_inner))
> > > +    {
> > > +      const bool new_readonly
> > > +	= TREE_READONLY (new_inner) || CONSTANT_CLASS_P (new_inner);
> > > +
> > > +      for (tree c = m_annotations; c != *m_inner; c = TREE_OPERAND (c, 0))
> > > +	{
> > > +	  gcc_checking_assert (TREE_CODE (c) == ANNOTATE_EXPR
> > > +			       && TREE_CODE (TREE_OPERAND (c, 1)) == INTEGER_CST
> > > +			       && TREE_CODE (TREE_OPERAND (c, 2)) == INTEGER_CST);
> > > +	  TREE_TYPE (c) = TREE_TYPE (new_inner);
> > > +	  TREE_SIDE_EFFECTS (c) = TREE_SIDE_EFFECTS (new_inner);
> > > +	  TREE_READONLY (c) = new_readonly;
> > > +	}
> > > +    }
> > > +
> > > +  *m_inner = new_inner;
> > > +  return m_annotations;
> > > +}
> > > +
> > >   /* COND is the condition-expression for an if, while, etc.,
> > >      statement.  Convert it to a boolean value, if appropriate.
> > >      In addition, verify sequence points if -Wsequence-point is enabled.  */
> > > @@ -966,6 +1046,9 @@ maybe_convert_cond (tree cond)
> > >     if (type_dependent_expression_p (cond))
> > >       return cond;
> > > +  /* Strip any ANNOTATE_EXPRs from COND.  */
> > > +  annotate_saver annotations (&cond);
> > > +
> > >     /* For structured binding used in condition, the conversion needs to be
> > >        evaluated before the individual variables are initialized in the
> > >        std::tuple_{size,elemenet} case.  cp_finish_decomp saved the conversion
> > > @@ -984,7 +1067,10 @@ maybe_convert_cond (tree cond)
> > >     /* Do the conversion.  */
> > >     cond = convert_from_reference (cond);
> > > -  return condition_conversion (cond);
> > > +  cond = condition_conversion (cond);
> > > +
> > > +  /* Restore any ANNOTATE_EXPRs around COND.  */
> > > +  return annotations.restore (cond);
> > >   }
> > >   /* Finish an expression-statement, whose EXPRESSION is as indicated.  */
> > > diff --git a/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C b/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C
> > > new file mode 100644
> > > index 00000000000..f410f6d8d25
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C
> > > @@ -0,0 +1,17 @@
> > > +// { dg-do compile { target c++11 } }
> > > +
> > > +template<typename Iter, typename Pred>
> > > +inline Iter
> > > +my_find(Iter first, Iter last, Pred pred)
> > > +{
> > > +#pragma GCC unroll 4
> > > +    while (first != last && !pred(*first))
> > > +        ++first;
> > > +    return first;
> > > +}
> > > +
> > > +short *use_find(short *p)
> > > +{
> > > +    auto pred = [](short x) { return x == 42; };
> > > +    return my_find(p, p + 1024, pred);
> > > +}
> > 
>
diff mbox series

Patch

diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 5ab2076b673..b1a49b14238 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -951,6 +951,86 @@  maybe_warn_unparenthesized_assignment (tree t, bool nested_p,
     }
 }
 
+/* Helper class for saving/restoring ANNOTATE_EXPRs.  For a tree node t, users
+   can construct one of these like so:
+
+     annotate_saver s (&t);
+
+   and t will be updated to have any annotations removed.  The user can then
+   transform t, and later restore the ANNOTATE_EXPRs with:
+
+     t = s.restore (t).
+
+   The intent is to ensure that any ANNOTATE_EXPRs remain the outermost
+   expressions following any operations on t.  */
+
+class annotate_saver {
+  /* The chain of saved annotations, if there were any.  Otherwise null.  */
+  tree m_annotations;
+
+  /* If M_ANNOTATIONS is non-null, then M_INNER points to TREE_OPERAND (A, 0)
+     for the innermost annotation A.  */
+  tree *m_inner;
+
+public:
+  annotate_saver (tree *);
+  tree restore (tree);
+};
+
+/* If *COND is an ANNOTATE_EXPR, walk through the chain of annotations, and set
+   *COND equal to the first non-ANNOTATE_EXPR (saving a pointer to the
+   original chain of annotations for later use in restore).  */
+
+annotate_saver::annotate_saver (tree *cond) : m_annotations (nullptr)
+{
+  tree *t = cond;
+  while (TREE_CODE (*t) == ANNOTATE_EXPR)
+    t = &TREE_OPERAND (*t, 0);
+
+  if (t != cond)
+    {
+      m_annotations = *cond;
+      *cond = *t;
+      m_inner = t;
+    }
+}
+
+/* If we didn't strip any annotations on construction, return NEW_INNER
+   unmodified.  Otherwise, wrap the saved annotations around NEW_INNER (updating
+   the types and flags of the annotations if needed) and return the resulting
+   expression.  */
+
+tree
+annotate_saver::restore (tree new_inner)
+{
+  if (!m_annotations)
+    return new_inner;
+
+  /* If the type of the inner expression changed, we need to update the types
+     of all the ANNOTATE_EXPRs.  We may need to update the flags too, but we
+     assume they only change if the type of the inner expression changes.
+     The flag update logic assumes that the other operands to the
+     ANNOTATE_EXPRs are always INTEGER_CSTs.  */
+  if (TREE_TYPE (new_inner) != TREE_TYPE (*m_inner))
+    {
+      const bool new_readonly
+	= TREE_READONLY (new_inner) || CONSTANT_CLASS_P (new_inner);
+
+      for (tree c = m_annotations; c != *m_inner; c = TREE_OPERAND (c, 0))
+	{
+	  gcc_checking_assert (TREE_CODE (c) == ANNOTATE_EXPR
+			       && TREE_CODE (TREE_OPERAND (c, 1)) == INTEGER_CST
+			       && TREE_CODE (TREE_OPERAND (c, 2)) == INTEGER_CST);
+	  TREE_TYPE (c) = TREE_TYPE (new_inner);
+	  TREE_SIDE_EFFECTS (c) = TREE_SIDE_EFFECTS (new_inner);
+	  TREE_READONLY (c) = new_readonly;
+	}
+    }
+
+  *m_inner = new_inner;
+  return m_annotations;
+}
+
 /* COND is the condition-expression for an if, while, etc.,
    statement.  Convert it to a boolean value, if appropriate.
    In addition, verify sequence points if -Wsequence-point is enabled.  */
@@ -966,6 +1046,9 @@  maybe_convert_cond (tree cond)
   if (type_dependent_expression_p (cond))
     return cond;
 
+  /* Strip any ANNOTATE_EXPRs from COND.  */
+  annotate_saver annotations (&cond);
+
   /* For structured binding used in condition, the conversion needs to be
      evaluated before the individual variables are initialized in the
      std::tuple_{size,elemenet} case.  cp_finish_decomp saved the conversion
@@ -984,7 +1067,10 @@  maybe_convert_cond (tree cond)
 
   /* Do the conversion.  */
   cond = convert_from_reference (cond);
-  return condition_conversion (cond);
+  cond = condition_conversion (cond);
+
+  /* Restore any ANNOTATE_EXPRs around COND.  */
+  return annotations.restore (cond);
 }
 
 /* Finish an expression-statement, whose EXPRESSION is as indicated.  */
diff --git a/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C b/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C
new file mode 100644
index 00000000000..f410f6d8d25
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C
@@ -0,0 +1,17 @@ 
+// { dg-do compile { target c++11 } }
+
+template<typename Iter, typename Pred>
+inline Iter
+my_find(Iter first, Iter last, Pred pred)
+{
+#pragma GCC unroll 4
+    while (first != last && !pred(*first))
+        ++first;
+    return first;
+}
+
+short *use_find(short *p)
+{
+    auto pred = [](short x) { return x == 42; };
+    return my_find(p, p + 1024, pred);
+}