[foreign] RFR 8218052: Don't throw an exception when encountering a type with a flexible array member

Jorn Vernee jbvernee at xs4all.nl
Tue Feb 26 13:20:37 UTC 2019


After some private discussion, here is the updated webrev that that 
implements the check as an actual visitor instead.

Updated webrev: 
http://cr.openjdk.java.net/~jvernee/panama/webrevs/8218052/webrev.05/

Jorn

Maurizio Cimadamore schreef op 2019-02-26 02:24:
> Thanks this looks much simpler.
> 
> And also points at another possible simplification - why don't we
> generate such warning in the parser? After all, is the parser that
> knows when a StructTree will be created, so it is in an ideal
> situation to intercept that event and log a warning accordingly (and,
> the parser already has logging support). Btw, I know that the actual
> StructTree creation occurs in TreeMaker - we could either add context
> to TreeMaker, or just peek for a struct cursor in Parser.
> 
> But overall, adding the check on earlier visitors might be better than
> using peek().
> 
> Maurizio
> 
> On 26/02/2019 00:30, Jorn Vernee wrote:
>> Hi Maurizio,
>> 
>> I think you're right. I replaced the exception with a warning since 
>> the 2 are similar, but throwing an exception/warning at that point is 
>> probably wrong in the first place, like you say.
>> 
>> Based on your suggestion, I've implemented the warning separately as a 
>> .peek() operation on the stream of Trees.
>> 
>> Updated webrev: 
>> http://cr.openjdk.java.net/~jvernee/panama/webrevs/8218052/webrev.04/
>> 
>> Jorn
>> 
>> Maurizio Cimadamore schreef op 2019-02-26 00:34:
>>> Hi Jorn,
>>> while in principle making context immutable is the right to do on
>>> paper, I believe that we probably ended up curing a symptom and not
>>> the disease :-)
>>> 
>>> Which means: I find it very strange to see that a layout computer -
>>> that is, a function which takes a clang Cursor and emits a Layout is
>>> now suddenly starting to give side-effects. Given I've worked on 
>>> javac
>>> for many years, I can assure you that this will almost surely lead to
>>> problems down the road. For instance, if this apparently harmless
>>> stateless function is called twice on the same cursor, we would get
>>> two warnings; another issue is that the code will fail to scale if 
>>> new
>>> warnings will need to be added - e.g. if record layout computer needs
>>> to check for other problematic stuff. And I'm not even mentioning
>>> adding logic to enable/disable specific classes of warnings, which is
>>> another thing that might happen, at some point.
>>> 
>>> What works in these contexts, is to separate the logic that does the
>>> stateless computation (e.g. layout computation, in this case), from
>>> the logic that issues the warning. For instance, we could have a
>>> separate visitor in jextract which looks at the clang cursor, looking
>>> for things that are suspicious or not well supported (such as
>>> incomplete arrays) and report a warning _only once_.
>>> 
>>> This visitor will probably write itself, and, longer term would be a
>>> much more maintainable option than inlining the check in the layout
>>> computer function itself.
>>> 
>>> After which, the decision as to whether make the context mutable or
>>> not is largely orthogonal - we could go there, although it's not 
>>> clear
>>> if the extra visitor I'm proposing would significantly switch the
>>> balance one way or another (probably not, as we have plenty other
>>> visitors which do logging).
>>> 
>>> Maurizio
>>> 
>>> 
>>> On 25/02/2019 23:16, Jorn Vernee wrote:
>>>> I've split this into 2 to try and make reviewing easier.
>>>> 
>>>> Here's the part that makes Context immutable (this required quite a 
>>>> bit of refactoring in Main):
>>>> http://cr.openjdk.java.net/~jvernee/panama/webrevs/context/webrev.00/
>>>> 
>>>> Here's the original part that returns a layout reference when 
>>>> encountering a type with a flexible array (applies on top of the 
>>>> first one):
>>>> http://cr.openjdk.java.net/~jvernee/panama/webrevs/8218052/webrev.03/
>>>> 
>>>> Hope that helps,
>>>> Jorn
>>>> 
>>>> Jorn Vernee schreef op 2019-02-25 23:06:
>>>>> I talked a bit with Sundar;
>>>>> 
>>>>> We can not create a global static method to return the Context 
>>>>> since
>>>>> there could be more than 1 Context, due to multiple instances of
>>>>> Jextract being created through the ToolProvider interface. But, as
>>>>> long as Context is Immutable, passing it down to other code should 
>>>>> be
>>>>> OK.
>>>>> 
>>>>> I have made Context immutable by adding a Context.Builder, and
>>>>> creating the Context in a separate method in Main (createContext).
>>>>> Some places in that code were prematurely exiting and returning an
>>>>> error code. This is replaced by throwing a custom exception 
>>>>> instead.
>>>>> 
>>>>> Update webrev:
>>>>> http://cr.openjdk.java.net/~jvernee/panama/webrevs/8218052/webrev.02/
>>>>> 
>>>>> Thanks,
>>>>> Jorn
>>>>> 
>>>>> Jorn Vernee schreef op 2019-02-16 15:30:
>>>>>> In other words; we can't get the layout for a type with a flexible
>>>>>> array from libclang at all. A good way to resolve that is probably 
>>>>>> to
>>>>>> submit a patch for it to libclang. I've done a little bit of
>>>>>> searching, and there doesn't seem to be a bug report in their bug
>>>>>> database for it either:
>>>>>> https://bugs.llvm.org/buglist.cgi?quicksearch=flexible%20array so 
>>>>>> we
>>>>>> could submit a bug about it as well. But, currently you need to
>>>>>> manually request an account in order to post bugs, so this might 
>>>>>> take
>>>>>> a while. I've also send an email just now to the cfe-dev mailing 
>>>>>> list,
>>>>>> maybe someone there can offer some help.
>>>>>> 
>>>>>> In the meantime we can work around structs with flexible arrays.
>>>>>> Currently an exception is thrown, but this makes the whole 
>>>>>> extracted
>>>>>> interface unusable, so emitting an undefined layout as a default 
>>>>>> value
>>>>>> and printing a warning seems like a better option to me until the
>>>>>> issue is fixed in libclang.
>>>>>> 
>>>>>> There's also the idea of introducing a jextract option to manually
>>>>>> override a descriptor, e.g. `--patch-descriptor
>>>>>> struct:MyStruct=[i32(x)i32(y)]`. We could use something like that 
>>>>>> to
>>>>>> manually provide the layout descriptor for structs with flexible 
>>>>>> array
>>>>>> members in the meantime.
>>>>>> 
>>>>>> Jorn
>>>>>> 
>>>>>> Jorn Vernee schreef op 2019-02-16 09:07:
>>>>>>>>> Now to the real discussion about incomplete array support, 
>>>>>>>>> instead of undefined layout, I prefer to have limited support, 
>>>>>>>>> we can either strip that field or generate a 0-length array for 
>>>>>>>>> now. >> For jextract, C only allow such field at end of struct, 
>>>>>>>>> and sizeof() operator simply ignore that trailing array field. 
>>>>>>>>> This should give us a good match as first step.
>>>>>>>> Agree
>>>>>>> 
>>>>>>> Sure, that would be preferable. But, as discussed before [1], 
>>>>>>> libclang
>>>>>>> does not handle incomplete arrays properly, that's why it was 
>>>>>>> changed
>>>>>>> to emit an exception in the first place.
>>>>>>> 
>>>>>>> It is not possible to filter out structs with any jextract 
>>>>>>> option, so
>>>>>>> if you have an incomplete array in a header file that is 
>>>>>>> otherwise
>>>>>>> usable, you're out of luck.
>>>>>>> 
>>>>>>> Jorn
>>>>>>> 
>>>>>>> [1] :
>>>>>>> https://mail.openjdk.java.net/pipermail/panama-dev/2019-January/003975.html 
>>>>>>> Maurizio Cimadamore schreef op 2019-02-16 01:29:
>>>>>>>> On 16/02/2019 00:00, Henry Jen wrote:
>>>>>>>>> Now to the real discussion about incomplete array support, 
>>>>>>>>> instead of undefined layout, I prefer to have limited support, 
>>>>>>>>> we can either strip that field or generate a 0-length array for 
>>>>>>>>> now. For jextract, C only allow such field at end of struct, 
>>>>>>>>> and sizeof() operator simply ignore that trailing array field. 
>>>>>>>>> This should give us a good match as first step.
>>>>>>>> Agree
>>>>>>>>> 
>>>>>>>>> Move on to more general support, where incomplete array can be 
>>>>>>>>> in-between layouts. Before that, we probably need to validate 
>>>>>>>>> some assumption,
>>>>>>>>> 
>>>>>>>>> Any incomplete array must have length specified in the same 
>>>>>>>>> struct before the incomplete array. I believe this will pretty 
>>>>>>>>> much cover most cases if not all.
>>>>>>>> 
>>>>>>>> To reinforce this point, I believe most compilers even give 
>>>>>>>> error if
>>>>>>>> the incomplete array is the only member of the struct, or if 
>>>>>>>> it's
>>>>>>>> followed by other stuff:
>>>>>>>> 
>>>>>>>> $ cat testInc.h
>>>>>>>> 
>>>>>>>> struct A {
>>>>>>>>    int arr[];
>>>>>>>> };
>>>>>>>> 
>>>>>>>> struct B {
>>>>>>>>    int l;
>>>>>>>>    int arr[];
>>>>>>>> };
>>>>>>>> 
>>>>>>>> struct C {
>>>>>>>>    int l;
>>>>>>>>    int arr[];
>>>>>>>>    int l2;
>>>>>>>> };
>>>>>>>> 
>>>>>>>> $ gcc -c testInc.h
>>>>>>>> 
>>>>>>>> testInc.h:2:8: error: flexible array member in a struct with no 
>>>>>>>> named members
>>>>>>>>     int arr[];
>>>>>>>>         ^~~
>>>>>>>> testInc.h:12:8: error: flexible array member not at end of 
>>>>>>>> struct
>>>>>>>>     int arr[];
>>>>>>>>         ^~~
>>>>>>>> 
>>>>>>>> $ clang -c testInc.h
>>>>>>>> testInc.h:2:8: error: flexible array member 'arr' not allowed in 
>>>>>>>> otherwise empty
>>>>>>>>       struct
>>>>>>>>    int arr[];
>>>>>>>>        ^
>>>>>>>> testInc.h:12:8: error: flexible array member 'arr' with type 
>>>>>>>> 'int []' is not at
>>>>>>>>       the end of struct
>>>>>>>>    int arr[];
>>>>>>>>        ^
>>>>>>>> testInc.h:13:8: note: next field declaration is here
>>>>>>>>    int l2;
>>>>>>>>        ^
>>>>>>>> 2 errors generated.
>>>>>>>> 
>>>>>>>>> 
>>>>>>>>> With that, I think following should work well enough,
>>>>>>>> 
>>>>>>>> What you describe is what I've dubbed 'dependent layout' 
>>>>>>>> approach -
>>>>>>>> e.g. have one or more values in a struct provide more info for 
>>>>>>>> certain
>>>>>>>> layout elements in same struct. This is fairly frequent business 
>>>>>>>> with
>>>>>>>> message protocols - almost all representation for variable-sized 
>>>>>>>> data
>>>>>>>> is expressed as length + data array (sometimes compressed, as in
>>>>>>>> protobuf's VarInt).
>>>>>>>> 
>>>>>>>> I agree that's where we need to land, longer term. Short term it 
>>>>>>>> feels
>>>>>>>> like the best move would be to just strip the array. Creating a
>>>>>>>> 0-length array might be a  move with subtle consequences: the 
>>>>>>>> array
>>>>>>>> occurs within a region with some boundaries (e.g. a struct 
>>>>>>>> region).
>>>>>>>> The boundaries of the enclosing region are usually computed 
>>>>>>>> using
>>>>>>>> sizeof(enclosing type). Meaning that the enclosing region won't 
>>>>>>>> be
>>>>>>>> 'big enough' to host anything but a zero-length array. If we 
>>>>>>>> cast the
>>>>>>>> array to something else, what we get back is an array whose 
>>>>>>>> boundaries
>>>>>>>> would exceed those of the enclosing struct - so if you try e.g. 
>>>>>>>> to
>>>>>>>> write to the array, the operation would fail.
>>>>>>>> 
>>>>>>>> To do this stuff properly in Panama you would need to allocate a
>>>>>>>> bigger chunk of memory, of the desired size (pretty much as you 
>>>>>>>> would
>>>>>>>> in C), and then cast the memory to the struct type - now you 
>>>>>>>> have a
>>>>>>>> memory region that is big enough to do the struct + the array.
>>>>>>>> 
>>>>>>>> The alternative, which does look simpler, is to just allocate a 
>>>>>>>> struct
>>>>>>>> (with array stripped) followed by an array of desired size in 
>>>>>>>> the same
>>>>>>>> scope - e.g. compare this:
>>>>>>>> 
>>>>>>>> try (Scope s : Scope.globalScope().fork()) {
>>>>>>>> 
>>>>>>>>     Pointer<Byte> slab = s.allocateArray(NativeTypes.UINT8,
>>>>>>>> Struct.sizeOf(StructWithIncompleteArray.class) + 40);
>>>>>>>>     Pointer<StructWithIncompleteArray> pstruct =
>>>>>>>> slab.cast(LayoutType.ofStruct(StructWithIncompleteArray.class));
>>>>>>>>     Array<Integer> data = 
>>>>>>>> pstruct.data$get().cast(NativeTypes.INT32.array(10));
>>>>>>>> 
>>>>>>>> }
>>>>>>>> 
>>>>>>>> With this:
>>>>>>>> 
>>>>>>>> try (Scope s : Scope.globalScope().fork()) {
>>>>>>>> 
>>>>>>>>     StructWithoutIncompleteArray struct =
>>>>>>>> s.allocateStruct(StructWithoutIncompleteArray.class);
>>>>>>>>     Array<Integer> data = s.allocateArray(NativeTypes.INT32, 
>>>>>>>> 10);
>>>>>>>> 
>>>>>>>> }
>>>>>>>> 
>>>>>>>> 
>>>>>>>> The code looks similar - and a client can, after the allocation, 
>>>>>>>> use
>>>>>>>> the array and the struct at will. But there's a subtle 
>>>>>>>> difference
>>>>>>>> between the two: in the first snippet, the array is allocated
>>>>>>>> immediately after the struct bits - that's how allocation 
>>>>>>>> happened. In
>>>>>>>> the second snippet there's no guarantee that the array will be 
>>>>>>>> after
>>>>>>>> the struct; in fact, the native scope might have run out of 
>>>>>>>> space with
>>>>>>>> the struct and needed to allocate a new slab of native memory 
>>>>>>>> via
>>>>>>>> unsafe before the array allocation happens.
>>>>>>>> 
>>>>>>>> 
>>>>>>>> Which seems to suggest that the right way of approaching the 
>>>>>>>> problem,
>>>>>>>> even if more verbose, is the first one.
>>>>>>>> 
>>>>>>>> Maurizio


More information about the panama-dev mailing list