And a followup as I looked at the strcat_chk variant failures which are all  of this form:

char dst[64];

<...>

  /* These __strncat_chk calls should be optimized into __strcat_chk,
     as strlen (src) <= len.  */

  strcpy (dst, s1);
  if (strncat (dst, "foo", 3) != dst || strcmp (dst, "hello worldfoo"))
    abort ();
  strcpy (dst, s1);
  if (strncat (dst, "foo", 100) != dst || strcmp (dst, "hello worldfoo"))
    abort ();
  strcpy (dst, s1);
  if (strncat (dst, s1, 100) != dst || strcmp (dst, "hello worldhello world"))
    abort ();

where we use to produce:


   __builtin_memcpy (&dst, "hello world", 12);
  _1 = __builtin___strcat_chk (&dst, "foo", 64);
  if (_1 != &dst)
    goto <bb 4>; [0.00%]
  else
    goto <bb 3>; [100.00%]

  <bb 3> [local count: 1073741824]:
  _2 = __builtin_strcmp_eq (&dst, "hello worldfoo", 15);
  if (_2 != 0)
    goto <bb 4>; [0.00%]
  else
    goto <bb 5>; [100.00%]

  <bb 4> [count: 0]:
  abort ();


And the prange PTA  recognizes that dst is 64 bytes, and , and .optimized produces:

  __builtin_memcpy (&dst, "hello world", 12);
  __builtin_memcpy (&MEM <char[64]> [(void *)&dst + 11B], "foo", 4);
  _1 = __builtin_strcmp_eq (&dst, "hello worldfoo", 15);
  if (_1 != 0)
    goto <bb 3>; [0.00%]
  else
    goto <bb 4>; [100.00%]

  <bb 3> [count: 0]:
  abort ();

This I also assume is fine?

Andrew


On 12/15/25 15:50, Andrew MacLeod 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...?

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