On Thu, Jan 9, 2025 at 9:08 AM Richard Biener <richard.guent...@gmail.com> wrote: > > On Wed, Jan 8, 2025 at 5:34 PM Qing Zhao <qing.z...@oracle.com> wrote: > > > > > > > > > On Jan 7, 2025, at 07:29, Richard Biener <richard.guent...@gmail.com> > > > wrote: > > > > > > On Mon, Jan 6, 2025 at 5:40 PM Qing Zhao <qing.z...@oracle.com> wrote: > > >> > > >> > > >> > > >>> On Jan 6, 2025, at 11:01, Richard Biener <richard.guent...@gmail.com> > > >>> wrote: > > >>> > > >>> On Mon, Jan 6, 2025 at 3:43 PM Qing Zhao <qing.z...@oracle.com> wrote: > > >>>> > > >>>> > > >>>> > > >>>>> On Jan 6, 2025, at 09:21, Jeff Law <jeffreya...@gmail.com> wrote: > > >>>>> > > >>>>> > > >>>>> > > >>>>> On 1/6/25 7:11 AM, Qing Zhao wrote: > > >>>>>>> > > >>>>>>> Given it doesn't cause user visible UB, we could insert the trap > > >>>>>>> *before* the UB inducing statement. That would then make the > > >>>>>>> statement unreachable and it'd get removed avoiding the false > > >>>>>>> positive diagnostic. > > >>>>>> Yes, that’s a good idea. > > >>>>>> However, in order to distinguish a user visible UB and a UB in the > > >>>>>> IL that is introduced purely by compiler, we might need some new > > >>>>>> marking in the IR? > > >>>>> I don't think we've ever really tackled that question; the closest I > > >>>>> can think of would be things like integer overflow which we try to > > >>>>> avoid allowing the compiler to introduce. If we take the integer > > >>>>> overflow as the model, then that would say we should be tackling this > > >>>>> during loop unrolling. > > >>>> > > >>>> UB that is introduced by compiler transformation is one important > > >>>> cause of false positive warnings. > > >>>> > > >>>> There are two approaches to tackle this problem from my understanding: > > >>>> > > >>>> 1. Avoid generating such UB from the beginning. i.e, for every > > >>>> compiler transformation that might introduce such UB, we should add > > >>>> check to avoid generating it. > > >>>> > > >>>> 2. Marking the IR portion that were generated by compiler > > >>>> transformations, then check whether the UB is compiler generated when > > >>>> issue static checker warnings. > > >>>> > > >>>> Are there other approaches? > > >>> > > >>> Note unrolling doesn't introduce UB - it makes conditional UB > > >>> "obvious”. > > >> > > >> So, you mean this is the same issue as PR109071 (and PR85788, PR88771, > > >> etc), i.e, the compiler optimization make the conditional UB that’s > > >> originally in the source code “obvious” after code duplication? > > >> > > >> (I need to study the testing case in PR92539 more carefully to make sure > > >> this is the case...) > > >> > > >> If so, then the claimed false positive warning in PR92539 actually is a > > >> real bug in the original source code, and my patch that introduced the > > >> new option “--fdiagnostics-details” should also include loop unrolling > > >> to provide more details on the warning introduced by loop unrolling. > > >> > > >> > > >>> Note -Warray-bounds wants to > > >>> diagnose UB, so doing path isolation and removing the UB would make > > >>> -Warray-bounds useless. > > >>> > > >>> So unless the condition guarding the UB unrolling exposes is visibly > > >>> false to the compiler but we fail > > >>> to exploit that (missed optimization) there's not much that we can do. > > >>> I think "folding" away the UB > > >>> like what Jeff proposes trades false negatives for the false positive > > >>> diagnostics. > > >>> > > >>> Note the unroller knows UB that effectively bounds the number of > > >>> iterations, even on conditional > > >>> paths and it uses this to limit the number of copies _and_ to prune > > >>> unreachable paths (exploiting > > >>> UB, avoiding diagnostics). But one of the limitations is that it only > > >>> prunes paths in the last unrolled > > >>> copy which can be insufficient (ISTR some PR where I noticed this). > > >>> > > >>> That said - I think for these unroller exposed cases of apparent false > > >>> positives we should improve > > >>> the path pruning in the unroller itself. For the other cases the path > > >>> diagnostic might help clarify > > >>> that the UB happens on the 'n-th' iteration of the loop when some > > >>> additional condition is true/false. > > >> > > >> So, the “other cases” refer to the situation similar as PR109071, i.e, > > >> “conditional UB” in the original source code is made obvious after loop > > >> unrolling? > > >> Yes, for such cases, the new option I have been trying to add, > > >> “-fdiagnostic-details” should be able to track and provide more details > > >> on the conditions that lead to the UB. > > >> Is this understanding correct? > > > > > > I think so, but I didn't look into the testcase of the referenced PR. > > > > I took a detailed study of the test case of PR92539 yesterday. The > > following is a brief summary: > > > > 1. The pass that caused the issue is: cunrolli. > > Adding -fdisable-tree-cunrolli eliminate the false positive warnings. > > > > 2. The IR Before cunrolli: > > > > const char *local_iterator = beginning address of string "aa"; > > const char *last = last address of string "aa"; > > > > for (int i = 0; i < 3; ++i) > > if (local_iterator != last) // pointer comparison 1 > > { > > local_iterator++; > > if (local_iterator != last) // pointer comparison 2 > > local_iterator++; > > } > > > > I think that the IR has NO UB at this moment; > > (The pointer comparison 2 in the 2nd iteration of “I” loop and > > both pointer comparison in the 3rd iteration of “I” loop will > > NOT execute at all) > > > > **After cunrolli (fully unrolling of the above i loop): > > > > const char *local_iterator = beginning address of string "aa"; > > const char *last = last address of string "aa"; > > int i = 0; > > > > if (local_iterator != last) // pointer comparison 1 > > { > > local_iterator++; > > if (local_iterator != last) // pointer comparison 2 > > { > > local_iterator++; > > i++; > > if (local_iterator != last) // pointer comparison 3 > > { > > local_iterator++; > > if (local_iterator != last) // pointer comparison 4 > > { > > local_iterator++; > > i++; > > if (local_iterator != last) // pointer comparison 5 > > { > > local_iterator++; > > if (local_iterator != last) // pointer comparison 6 > > { > > local_iterator++; > > i++; > > } > > } > > } > > } > > } > > } > > > > Also, I think that the IR has NO UB at this moment too. > > (The pointer comparison 4 and later will NOT execute at all) > > > > ** The false positive warnings claimed for pointer comparison 4, 5, and 6 > > assuming that these comparison will be executed during runtime but actually > > they will not be executed at all. > > > > > > So, based on the above study, my impression is: > > > > A. Unrolling transformation didn’t do anything wrong. > > B. Only after const propagation, the compiler will know that the pointer > > “local_iterator” will > > point to an out-of-bound position of string “aa”, the pointer > > comparison 4, 5, 6 is invalid > > After that. > > > > So, I guess that Jeff’s patch to identify the invalid pointer address at > > “vrp1” is reasonable. > > > > What’s your opinion on this? > > Your analysis is correct - the long-standing design issue is that > -Warray-bounds > does not distinguish conditional UB from unconditional UB (but that concept is > somewhat wishful thinking given a function isn't guaranteed to be invoked > either > unless it is main()). The other thing is that while -Warray-bounds > without unrolling > likely sees the value-range of i_1 for &a[i_1] includes out-of-bound values it > chooses to only diagnose cases where the whole value-range is out-of-bound > (correctly so to avoid false positives just because of conservative ranges). > > I think Jeff's patch is not reasonable since it boils down to not diagnose > -Warray-bounds but instead remove those stmts. > > The problem in this particular testcase is that we fail to optimize the > compares - not because we don't exploit the visible UB but because > we fail to compute the final value of 'last' from > > auto last = "aa"; > while (*last) > ++last; > > one way to attack this is to recognize this pattern in niter analysis > and record niter as strlen (last) (but it would be a "first" to have > a niter pattern with a memory operation), another would be to > handle this in loop_niter_by_eval and allow us to take advantage > of the constant &'aa' initial value (that would only scale so much). > > > > > > Note the unroller explicitly tries to > > > prune those "bugs" given it happily exploits UB when computing the > > > number of iterations of a loop, > > > like for > > > > > > int a[6]; > > > for (int i = 0; i < n; ++i) > > > a[i] = 1; > > > > > > it knows the loop can at most iterate 5 times because a[i] = 1 with i > > > == 6 would invoke UB. > > > > So, the unroller has some heuristic to limit the potential UB invoked by > > unrolling? Could you point me the corresponding routine for such heuristic > > if convenient? > > It cuts off those via > tree-ssa-loop-ivcanon.cc:remove_exits_and_undefined_stmts, but this is > invoked only for the last copy, > and it only handles stmts that we recorded bounds for via > infer_loop_bounds_from_undefined. > > It's a bit difficult to extend this to non-last copies, we'd have to > incrementally copy and we'll possibly disrupt the > batching of transforms, causing compile-time issues. But I haven't > had time to investigate. > > To summarize the following things should be done to improve things in > this and similar situations: > a) improve niter analysis / final value replacement to constant fold > 'last' in such cases > b) improve unroll to cut out UB stmts of earlier iterations than the > last (size estimates also do not > consider this, so it's also a missed optimization) > c) better explain diagnostics
d) I checked, and niter analysis doesn't consider the UB with the {(const char *) "aa" + 1, +, 2}_1 IV - in infer_loop_bounds_from_pointer_arith we bail out early for !expr_invariant_in_loop_p (for unknown reason). But we also only use conservative low/high instead of deriving low/high from the CHRECs initial value if it is as in this case based on a STRING_CST (or in others on the address of a decl). But then the code in record_nonwrapping_iv might not be able to deal with a low &"aa" and a high &"aa" + 2. diff --git a/gcc/tree-ssa-loop-niter.cc b/gcc/tree-ssa-loop-niter.cc index 9e966266ca3..8c5c0852420 100644 --- a/gcc/tree-ssa-loop-niter.cc +++ b/gcc/tree-ssa-loop-niter.cc @@ -4307,10 +4312,6 @@ infer_loop_bounds_from_pointer_arith (class loop *loop, g imple *stmt) if (!nowrap_type_p (type)) return; - ptr = gimple_assign_rhs1 (stmt); - if (!expr_invariant_in_loop_p (loop, ptr)) - return; - var = gimple_assign_rhs2 (stmt); if (TYPE_PRECISION (type) != TYPE_PRECISION (TREE_TYPE (var))) return; Richard. > > There's also the possibility to disable UB diagnostics for all unroll > copies when unroll used the > iteration bound rather than the exact number of iterations (I think we > don't record whether the > bound was from UB or from sth else). Possibly we can try to disable > UB diagnostic only for > stmts with UB as recorded from bounds, similar to > remove_exits_and_undefined_stmts, but > with easier to guarantee to not affect batching, since we're not > inserting stmts or removing edges. > > Richard. > > > thanks. > > > > Qing > > > > > > > > Richard. > > > > > >> > > >> thanks. > > >> > > >> Qing > > >>> > > >>> So in the end Jeff - I think your patch isn't a good approach for the > > >>> issue at hand. > > >>> > > >>> Richard. > > >>> > > >>>> The above is very rough and initial idea at this moment. > > >>>> > > >>>> Qing > > >>>> > > >>>>> > > >>>>> jeff > > > >