On Fri, Jul 17, 2026 at 4:16 PM Robin Dapp <[email protected]> wrote:
>
> >> I wonder how to deal with the situation
> >> where CHREC_RIGHT changes? Whenever we call chrec_fold_plus
> >> with CHRECs of the same loop we get { a + b, step_a + step_b }_loop
> >> which will invalidate nowrap bounds?
> >>
> >> Likewise if you start with
> >> { init, +, 1 }_0 and a bound of N, adding 5 requires to adjust N?
> >> So even changing CHREC_LEFT is problematic.
> >
> > Hmm, but the bound would/should then still be N because its derived from
> > the IV
> > type, even after adding 5? Or maybe I misunderstood?
>
> Ah I think I got your point now. The bound "N" is obtained in
> scev_probably_wraps which takes place after all the chrec plus/... things
> have already happened (I hope :)).
But we are incrementally building up a CHREC and cache results, consider
# _1 = PHI <_2(entry), _3(latch)>
_3 = _1 + 1;
_4 = _3 * 6;
if (...)
goto loop;
when we analyze _4 we recursively analyze _3 and then _1 which is
finally a loop PHI that will trigger the SCEV-DFS analysis (there we can't
do much). That result then, {_2, +, 1 } gets 1 added and the multiplied by 6.
Now consider the multiplication being done as unsigned, first
converting _3 to unsigned. That's where we'd need to create a runtime
check to avoid ((unsigned){_2 + 1, +, 1 }) * 6 but instead get
(unsigned){(_2 + 1)*6, +, 6}. You register a runtime check (with the global
SCEV state?). If you'd then analyze a _5 = _4 + 30 the (unsigned){(_2
+ 1)*6, +, 6}
CHREC is cached, so implicitly not overflowing. What do you do with the + 30?
Given the global runtime check, you'd require a scev_reset before each query?
How do you make sure all cached results have their runtime check registered
and not "lost"?
>
> > Right now the conjunction of two nowrap bounds happens through MIN. If we
> > add
> > one chrec to another, leading to a laxer bound, indeed no bounds update
> > happens. We would pessimize the bound, perhaps significantly so?, and
> > version
> > with it. I'll try to come up with a test case for this. During testing of
> > the
> > patch I haven't been particularly lucky with building test cases that show
> > the
> > specific behavior I want to test, though...
>
> Looks like "AI" is better than me (at that?)... it's even simple but not sure
> the example demonstrates a lot:
>
> __attribute__ ((noipa)) void
> f (int *a, unsigned char s1, unsigned char s2, int n)
> {
> unsigned char i1 = s1, i2 = s2;
> for (int j = 0; j < n; j++)
> {
> a[j + i1 - i2] += 1;
> i1++;
> i2++;
> }
> }
>
> We have chrecs for i1 and i2 here that are being combined during analysis:
>
> (instantiate_scev
> (instantiate_below = 5 -> 3)
> (evolution_loop = 1)
> (chrec = {(int) s1_14(D), +, 2}_1)
> (res = {(int) s1_14(D), +, 2}_1))
> (instantiate_scev
> (instantiate_below = 5 -> 3)
> (evolution_loop = 1)
> (chrec = {(int) s2_15(D), +, 1}_1)
> (res = {(int) s2_15(D), +, 1}_1))
>
> and eventually something like this:
> (instantiate_scev
> (instantiate_below = 5 -> 3)
> (evolution_loop = 1)
> (chrec = {a_18(D) + ((long unsigned int) s1_14(D) - (long unsigned int)
> s2_15(D)) * 4, +, 4}_1)
> (res = {a_18(D) + ((long unsigned int) s1_14(D) - (long unsigned int)
> s2_15(D)) * 4, +, 4}_1))
>
> I'm seeing:
> adding no-wrap assumption: (unsigned int) ~MAX_EXPR <s1_14(D), s2_15(D)> >=
> (unsigned int) n_17(D)
>
> I guess that's 255 - max (s1, s2) >= n? And if we choose s1 = s2 they cancel
> each other out we could actually vectorize but don't because the assumption is
> not "smart" enough. I'd say that's missed optimization (if at all)?
I'm worried about wrong-code, not so much about missed optimizations right now.
So you say for my example above, when seeing (unsigned){(_2 + 1)*6, +, 6} + 30
the nowrap condition computation does not assume that {(_2 + 1)*6, +,
6 } does not
wrap? I guess I have to look at this more closely.
That said, I'm still worried about the lose coupling of the cache and
the versioning condition.
Richard.
>
> --
> Regards
> Robin
>