https://gcc.gnu.org/g:f97d86242b86e4ad2bef3623c97e91481840a210
commit r15-3583-gf97d86242b86e4ad2bef3623c97e91481840a210 Author: Alex Coplan <alex.cop...@arm.com> Date: Fri Aug 2 09:52:50 2024 +0100 c++: Ensure ANNOTATE_EXPRs remain outermost expressions in conditions [PR116140] 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: --- gcc/cp/semantics.cc | 88 ++++++++++++++++++++++++- gcc/testsuite/g++.dg/ext/pragma-unroll-lambda.C | 17 +++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 3e117c216da5..63212afafb3b 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 000000000000..f410f6d8d255 --- /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); +}