> On Oct 26, 2023, at 10:05 AM, Richard Biener <richard.guent...@gmail.com> 
> wrote:
> 
> 
> 
>> Am 26.10.2023 um 12:14 schrieb Martin Uecker <uec...@tugraz.at>:
>> 
>> Am Donnerstag, dem 26.10.2023 um 11:20 +0200 schrieb Martin Uecker:
>>>> Am Donnerstag, dem 26.10.2023 um 10:45 +0200 schrieb Richard Biener:
>>>> On Wed, Oct 25, 2023 at 8:16 PM Martin Uecker <uec...@tugraz.at> wrote:
>>>>> 
>>>>> Am Mittwoch, dem 25.10.2023 um 13:13 +0200 schrieb Richard Biener:
>>>>>> 
>>>>>>> Am 25.10.2023 um 12:47 schrieb Martin Uecker <uec...@tugraz.at>:
>>>>>>> 
>>>>>>> Am Mittwoch, dem 25.10.2023 um 06:25 -0400 schrieb Siddhesh Poyarekar:
>>>>>>>>> On 2023-10-25 04:16, Martin Uecker wrote:
>>>>>>>>> Am Mittwoch, dem 25.10.2023 um 08:43 +0200 schrieb Richard Biener:
>>>>>>>>>> 
>>>>>>>>>>> Am 24.10.2023 um 22:38 schrieb Martin Uecker <uec...@tugraz.at>:
>>>>>>>>>>> 
>>>>>>>>>>> Am Dienstag, dem 24.10.2023 um 20:30 +0000 schrieb Qing Zhao:
>>>>>>>>>>>> Hi, Sid,
>>>>>>>>>>>> 
>>>>>>>>>>>> Really appreciate for your example and detailed explanation. Very 
>>>>>>>>>>>> helpful.
>>>>>>>>>>>> I think that this example is an excellent example to show (almost) 
>>>>>>>>>>>> all the issues we need to consider.
>>>>>>>>>>>> 
>>>>>>>>>>>> I slightly modified this example to make it to be compilable and 
>>>>>>>>>>>> run-able, as following:
>>>>>>>>>>>> (but I still cannot make the incorrect reordering or DSE 
>>>>>>>>>>>> happening, anyway, the potential reordering possibility is there…)
>>>>>>>>>>>> 
>>>>>>>>>>>> 1 #include <malloc.h>
>>>>>>>>>>>> 2 struct A
>>>>>>>>>>>> 3 {
>>>>>>>>>>>> 4  size_t size;
>>>>>>>>>>>> 5  char buf[] __attribute__((counted_by(size)));
>>>>>>>>>>>> 6 };
>>>>>>>>>>>> 7
>>>>>>>>>>>> 8 static size_t
>>>>>>>>>>>> 9 get_size_from (void *ptr)
>>>>>>>>>>>> 10 {
>>>>>>>>>>>> 11  return __builtin_dynamic_object_size (ptr, 1);
>>>>>>>>>>>> 12 }
>>>>>>>>>>>> 13
>>>>>>>>>>>> 14 void
>>>>>>>>>>>> 15 foo (size_t sz)
>>>>>>>>>>>> 16 {
>>>>>>>>>>>> 17  struct A *obj = __builtin_malloc (sizeof(struct A) + sz * 
>>>>>>>>>>>> sizeof(char));
>>>>>>>>>>>> 18  obj->size = sz;
>>>>>>>>>>>> 19  obj->buf[0] = 2;
>>>>>>>>>>>> 20  __builtin_printf (“%d\n", get_size_from (obj->buf));
>>>>>>>>>>>> 21  return;
>>>>>>>>>>>> 22 }
>>>>>>>>>>>> 23
>>>>>>>>>>>> 24 int main ()
>>>>>>>>>>>> 25 {
>>>>>>>>>>>> 26  foo (20);
>>>>>>>>>>>> 27  return 0;
>>>>>>>>>>>> 28 }
>>>>>>>>>>>> 
>>>>>>>> 
>>>>>>>> <snip>
>>>>>>>> 
>>>>>>>>>> When it’s set I suppose.  Turn
>>>>>>>>>> 
>>>>>>>>>> X.l = n;
>>>>>>>>>> 
>>>>>>>>>> Into
>>>>>>>>>> 
>>>>>>>>>> X.l = __builtin_with_size (x.buf, n);
>>>>>>>>> 
>>>>>>>>> It would turn
>>>>>>>>> 
>>>>>>>>> some_variable = (&) x.buf
>>>>>>>>> 
>>>>>>>>> into
>>>>>>>>> 
>>>>>>>>> some_variable = __builtin_with_size ( (&) x.buf. x.len)
>>>>>>>>> 
>>>>>>>>> 
>>>>>>>>> So the later access to x.buf and not the initialization
>>>>>>>>> of a member of the struct (which is too early).
>>>>>>>>> 
>>>>>>>> 
>>>>>>>> Hmm, so with Qing's example above, are you suggesting the 
>>>>>>>> transformation
>>>>>>>> be to foo like so:
>>>>>>>> 
>>>>>>>> 14 void
>>>>>>>> 15 foo (size_t sz)
>>>>>>>> 16 {
>>>>>>>> 16.5  void * _1;
>>>>>>>> 17  struct A *obj = __builtin_malloc (sizeof(struct A) + sz * 
>>>>>>>> sizeof(char));
>>>>>>>> 18  obj->size = sz;
>>>>>>>> 19  obj->buf[0] = 2;
>>>>>>>> 19.5  _1 = __builtin_with_size (obj->buf, obj->size);
>>>>>>>> 20  __builtin_printf (“%d\n", get_size_from (_1));
>>>>>>>> 21  return;
>>>>>>>> 22 }
>>>>>>>> 
>>>>>>>> If yes then this could indeed work.  I think I got thrown off by the
>>>>>>>> reference to __bdos.
>>>>>>> 
>>>>>>> Yes. I think it is important not to evaluate the size at the
>>>>>>> access to buf and not the allocation, because the point is to
>>>>>>> recover it from the size member even when the compiler can't
>>>>>>> see the original allocation.
>>>>>> 
>>>>>> But if the access is through a pointer without the attribute visible
>>>>>> even the Frontend cannot recover?
>>>>> 
>>>>> Yes, if the access is using a struct-with-FAM without the attribute
>>>>> the FE would not be insert the builtin.  BDOS could potentially
>>>>> still see the original allocation but if it doesn't, then there is
>>>>> no information.
>>>>> 
>>>>>> We’d need to force type correctness and give up on indirecting
>>>>>> through an int * when it can refer to two diffenent container types.
>>>>>> The best we can do I think is mark allocation sites and hope for
>>>>>> some basic code hygiene (not clobbering size or array pointer
>>>>>> through pointers without the appropriately attributed type)
>>>>> 
>>>>> I am do not fully understand what you are referring to.
>>>> 
>>>> struct A { int n; int data[n]; };
>>>> struct B { long n; int data[n]; };
>>>> 
>>>> int *p = flag ? a->data : b->data;
>>>> 
>>>> access *p;
>>>> 
>>>> Since we need to allow interoperability of pointers (a->data is
>>>> convertible to a non-fat pointer of type int *) this leaves us with
>>>> ambiguity we need to conservatively handle to avoid false positives.
>>> 
>>> For BDOS, I would expect this to work exactly like:
>>> 
>>> char aa[n1];
>>> char bb[n2];
>>> char *p = flag ? aa : bb;
>>> 
>>> (or similar code with malloc). In fact it does:
>>> 
>>> https://godbolt.org/z/bK68YKqhe
>>> (cheating a bit and also the sub-object version of
>>> BDOS does not seem to work)
>>> 
>>>> 
>>>> We _might_ want to diagnose decay of a->data to int *, but IIRC
>>>> there's no way (or proposal) to allow declaring a corresponding
>>>> fat pointer, so it's not a good designed feature.
>>> 
>>> As a language feature, I fully agree.  I see the
>>> counted_by attribute has a makeshift solution.
>>> 
>>> But we can already do:
>>> 
>>> auto p = flag ? &aa : &bb;
>>> 
>>> and this already works perfectly:
>>> 
>>> https://godbolt.org/z/rvb6xWWPj
>>> 
>>> We can also name the variably-modifed type: 
>>> 
>>> char (*p)[flag ? n1 : n2] = flag ? &aa : &bb;
>>> https://godbolt.org/z/13cTT1vGP
>>> 
>>> The problem with this version is that consistency
>>> is not checked. (I have patch for adding run-time
>>> checks).
>>> 
>>> And then the next step would be to allow
>>> 
>>> char (*p)[:] = flag ? &aa : &bb;
>>> 
>>> or similar.  Dennis Ritchie proposed this himself
>>> a long time ago.
>>> 
>>> So far this seems straightfoward.
>>> 
>>> If we then want to allow such wide pointers as
>>> function arguments or in structs, we would need
>>> to define an ABI. But the ABI could just be
>>> 
>>> struct { char (*p)[.s]; size_t s; };
>>> 
>>> Maybe we could try to make the following
>>> ABI compatible:
>>> 
>>> int foo(int p[s], size_t s);
>>> int foo(int p[:]);
>>> 
>>> 
>>>> Having __builtin_with_size at allocation would possibly make
>>>> the BOS use-def walk discover both objects.
>>> 
>>> Yes. But I do not think this there is any fundamental
>>> difference to discovering allocation functions.
>>> 
>>>> I think you can't
>>>> insert __builtin_with_size at the access to *p, but in practice
>>>> that would be very much needed.
>>> 
>>> Usually the access to *p would follow directly the
>>> access x.buf, so BDOS should find it.
>>> 
>>> But yes, to get full bounds safety, the pointer type 
>>> has to change to a variably-modified type (which would work
>>> today) or a fat pointer type. The later can be built on
>>> vm-types easily because all the FE semantics already
>>> exists.
>> 
>> We could insert the __builtin_with_size everywhere
>> we have to convert a wide pointer or let an array
>> decay to traditional pointer for reason of compatibility 
>> with legacy code.
> 
> That sounds like a nice idea.  Note I’d like to see the consumer side 
> implemented so we can play with different points of insertion (and I’ll try 
> to show corner cases where it goes wrong).

Giving the example I mentioned previously:

  1 #include <malloc.h>
  2 struct A
  3 {
  4  size_t size;
  5  char buf[] __attribute__((counted_by(size)));
  6 };
  7 
  8 static size_t
  9 get_size_from (void *ptr)
 10 {
 11  return __builtin_dynamic_object_size (ptr, 1);
 12 }
 13 
 14 void
 15 foo (size_t sz)
 16 {
 17  struct A *obj = __builtin_malloc (sizeof(struct A) + sz * sizeof(char));
 18  obj->size = sz;
 19  obj->buf[0] = 2;
 20  __builtin_printf (“%d\n", get_size_from (obj->buf));
 21  return;
 22 }

So, the different points of insertion the new __builtin_with_size in FE include 
the following points: (per my understanding so far)

Point 1. When the “obj->buf” is referenced at line 19, and line 20?
Point 2. When the “obj” is allocated at line 17? 

Are these correct?

Any other points we need to consider?


>  It all seems a bit late for GCC 14 though.

Yes, I agree.  Given the potential impact on the code generation and other 
potential issues, it’s better to be put in the early stage of the next release.

Qing
> 
> Richard 
> 
>> Martin
>> 
>>> 
>>> Martin
>>> 
>>>> 
>>>> Richard.
>>>> 
>>>>> But yes,
>>>>> for full bounds safety we would need the language feature.
>>>>> In C people should start to variably-modified types
>>>>> more.  I think we can build perfect bounds safety on top of
>>>>> them in a very good way with only FE changes.
>>>>> 
>>>>> All these attributes are just a best effort.  But for a while,
>>>>> this will be necessary.
>>>>> 
>>>>> Martin
>>>>> 
>>>>>> 
>>>>>>> Evaluating at this point requires that the size is correctly set
>>>>>>> before the access to the FAM and the user has to make sure
>>>>>>> this is the case. But to me this requirement would make sense.
>>>>>>> 
>>>>>>> Semantically, it could aöso make sense to evaluate the size at a
>>>>>>> later time.  But then the reordering becomes problematic again.
>>>>>>> 
>>>>>>> Also I think this would make this feature generally more useful.
>>>>>>> For example, it could work also for others pointers in the struct
>>>>>>> and not just for FAMs.  In this case, the struct may already be
>>>>>>> freed when  BDOS is called, so it might also not possible to
>>>>>>> access the size member at a later time.
>>>>>>> 
>>>>>>> Martin

Reply via email to