> On Jan 9, 2025, at 03:17, Sam James <s...@gentoo.org> wrote: > > Richard Biener <richard.guent...@gmail.com> writes: > >> 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 > > Maybe even with -fdiagnostics-details in 15 as a late Christmas present? > :) > > The only issues I've found when using Qing's patch are cases I need to > reduce where we may be able to diagnose more, Is there any example here? Some other compiler optimizations that are not included currently? (For example, loop unrolling?)
thanks. Qing > but nothing else. Kees is > using it for kernel issues already too.