> On Oct 26, 2023, at 1:05 PM, Martin Uecker <uec...@tugraz.at> wrote:
> 
> Am Donnerstag, dem 26.10.2023 um 16:41 +0000 schrieb Qing Zhao:
>> 
>>> On Oct 26, 2023, at 5:20 AM, Martin Uecker <uec...@tugraz.at> wrote:
>>> 
>>> 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.
>> 
>> The “counted_by” attribute is necessary at this moment since
>> it will be much easier to be adopted by the existing source code,
>> for example, the Linux Kernel. 
> 
> Yes, this is understood.
> 
>> 
>> Though I agree that embedding the bound information into TYPE 
>> system  should be the ultimate goal. 
>> 
>>> 
>>> 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.
>> 
>> By variable-modified type, you mean the VLA?
> 
> I mean a pointer to a VLA type.
> 
>> 
>> There is one major difference between VLA and (FAM or Pointer array):
>> 
>> For VLA, the compiler is responsible for allocating the memory for it, 
>> the size assignment and the memory allocation are both done by the
>> compiler at the same time and tied together. 
> 
> A VLA can also exist on the heap:
> 
> char (*buf)[n] = malloc(sizeof(*buf));

Okay. I see.
> 
>> 
>> But for FAM and pointer arrays, right now, users allocate the memory for them
>> In the source code, so, when we add the “counted_by” attribute, we need to
>> specify the additional requirement for the order of size assignment and 
>> memory
>> allocation into the source code, and specify this requirement in the user 
>> documentation.
>> 
>> Later, if we try to make the bound information of FAM/pointer array into 
>> TYPE 
>> system, similar as the current VLA, should we also need to move the memory 
>> allocation 
>> of the FAM/pointer arrays into compiler (similar as VLA too)? 
> 
> I think memory allocation can be done either
> as an automatic variable or by malloc.
> 
> The following works today in GNU C:
> 
> int N = ..;
> struct foo { char buf[N]; } x;
> struct foo *p = malloc(sizeof(struct foo));

Yes, tried this, did work. -:) thanks. 

Qing
> 
> The only limitation today is that the size 'n' 
> can not refer to the field member.
> 
> struct foo { int n; char buf[.n]; };
> 
> I am not yet sure how we would set the size for
> an automatic object, but I have some ideas.  Maybe
> simply using an initializer:
> 
> struct foo x = { .n = 10 };
> 
> 
> Martin
> 
>>> The later can be built on
>>> vm-types easily because all the FE semantics already
>>> exists.
>> 
>> Except the memory allocation part…
>> 
>> Do I miss anything here?
>> 
>> Qing
>>> 
>>> 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