On Mon, Dec 15, 2025 at 9:50 PM Andrew MacLeod <[email protected]> wrote:
>
> I am experimenting with integrating VRP's Points-to analysis into
> prange.  Its currently managed on the side via the class in
> value_pointer_equiv.{cc,h} which maintains a global side table and a
> push/pop for conditions during the dominator walk.
>
> I have managed to get it mostly working, but Im running into an issue
> with runtime and the Object Size Checking Built-in Functions such as
> __builtin___memcpy_chk
>
> I see we often generate code like
>
>    _1 = __builtin_memcpy (&buf1, "ABCDEFGHI", 9);
>    if (_1 != &buf1)
>      goto abort ();
>
> but the builtin function fnspec string is "1cO313" which says that _1
> will always return the first argument,, which is &buf1.  It therefore
> seems like we can fold away the condition in this case...   Which ranger
> and the enhanced prange does.  This seems to cause no problem.
>
> Where i do run into a problem is in
> gcc.c-torture/execute/builtins/memcpy-chk.c ,   everything passes fine
> up until this point in test2_sub () :
>
>
>    /* buf3 points to an unknown object, so __memcpy_chk should not be
> done.  */
>    if (memcpy ((char *) buf3 + 4, buf5, n + 6) != (char *) buf1 + 4
>        || memcmp (buf1, "aBcdRSTUVWklmnopq\0", 19))
>      abort ();
>
> With my changes, this sequence goes from
>
>   <bb 31> :
>    __builtin___memcpy_chk (buf3_60(D), "aBcdEFghijklmnopq\x00", 19, _5);
>    _47 = n_73(D) + 6;
>    _48 = (long unsigned int) _47;
>    _50 = __builtin___memcpy_chk (_19, &buf5, _48, _20);
>    if (_50 != &MEM <long int[64]> [(void *)&buf1 + 4B])
>      goto <bb 33>; [INV]
>    else
>      goto <bb 32>; [INV]
>
>    <bb 32> :
>    _52 = memcmp (&buf1, "aBcdRSTUVWklmnopq\x00", 19);
>    if (_52 != 0)
>      goto <bb 33>; [INV]
>    else
>      goto <bb 34>; [INV]
>
>    <bb 33> :
>    abort ();
>
> to
>
>   <bb 23> :
>    __builtin___memcpy_chk (&buf1, "aBcdEFghijklmnopq\x00", 19, _5);
>    _47 = n_73(D) + 6;
>    _48 = (long unsigned int) _47;
>    _50 = __builtin___memcpy_chk (&MEM <long int[64]> [(void *)&buf1 +
> 4B], &buf5, _48, 508);
>    _52 = memcmp (&buf1, "aBcdRSTUVWklmnopq\x00", 19);
>    if (_52 != 0)
>      goto <bb 24>; [INV]
>    else
>      goto <bb 25>; [INV]
>
>    <bb 24> :
>    abort ();
>
>
> The new integrated PTA figures out a couple of things.. first, it knows
>    *  from earlier that buf3 and buf1 points to the same thing,
>    * and can also figure out that buf3 + 4 == buf1 + 4, so it can fold
> away the condition afterwards since arg1 is the result
>    * Since it knows buf3 points to buf1, it knows the object size if 512
> so buf3+4 gives an object size of 508 which is propagates into the
> memcpy_chk size field.
>
> This is where I get confused.   The range if _48 is unknown, but known
> to be   [irange] long unsigned int [0, 2147483647][18446744071562067974,
> +INF]
>
> I think this fails some of the logic in gimple_fold_builtin_memory_chk..
> because the size is known to be 508 now (and before it was "all_ones")
> gimple_fold_builtin_memory_chk refuses to replace it with memcpy ..  and
> the testcase then goes and fails.    At the top of test2_sub() I see:
>
>    /* All the memcpy/__builtin_memcpy/__builtin___memcpy_chk
>       calls in this routine are either fixed length, or have
>       side-effects in __builtin_object_size arguments, or
>       dst doesn't point into a known object.  */
>    chk_calls = 0;
>
> And then at the end, we abort on
>    if (chk_calls)
>      abort ();
>
> Presumably because we called memcpy_chk.
>
> Is this just a "faulty" test situation now if we can identify that buf3
> and buf1 points to the same thing?
>
> Or am I missing something...?

IIRC the execute/builtins tests play tricks behind our backs, introducing
extra side-effects of the builtin calls.  That harness should be re-architected.

But your problem at hand seems to be that you are confusing
the objsz pass and the test expects to optimize _chk to no-_chk
variants?

Richard.

> Thanks
>
> Andrew
>
> PS.  Why do we never eliminate those checks after calls to memcpy and
> friends?  ie the pattern
>
>    _1 = __builtin_memcpy (&buf1, "ABCDEFGHI", 9);
>    if (_1 != &buf1)
>      goto <bb 4>; [INV]
>    else
>      goto <bb 3>; [INV]
>
> Shouldn't that be trivially removable?     the fnspec string:
>       case BUILT_IN_STRNCPY:
>        case BUILT_IN_MEMCPY:
>        case BUILT_IN_MEMMOVE:
>        case BUILT_IN_TM_MEMCPY:
>        case BUILT_IN_TM_MEMMOVE:
>        case BUILT_IN_STRNCPY_CHK:
>        case BUILT_IN_MEMCPY_CHK:
>        case BUILT_IN_MEMMOVE_CHK:
>          return "1cO313";
>
> Shows that arg 1 is *always* the return range, and prange is now quite
> happy to propagate that points to info and triviaslly removes them in EVRP
>
> 2        range_of_stmt (_1) at stmt _1 = __builtin_memcpy (&buf1,
> "ABCDEFGHI", 9);
>            TRUE : (2) range_of_stmt (_1) [prange] void * [1, +INF] ->
> &buf1                            <<-- non-zero and points to &buf1
>
>
> But up to now, those branches go right through past  .optimized... and
> since that seems like very low hanging fruit, I figure I have to be
> missing something
>

Reply via email to