On 3/28/23 13:17, David Blaikie wrote:
DW_AT[_GNU]_vector is best understood not as "a hardware vector register" but rather as a 
marker that "this type is eligible to be passed in hardware vector registers at function 
boundaries according to the platform ABI".
My 2c would not be to describe these in terms of
hardware/implementations (that gets confusing/blurs the line between
variable/types and locations - as you say, these things can be stored
in memory, so they aren't uniquely in registers - you might have a
member of this type in a struct passed in memory and need to know the
ABI/struct layout for that, etc), but at the source level - which the
ABI is defined in those same terms. Overloading, for instance, still
applies if these are different types - so other debugger features need
to work based on this type information.

So it seems like a simpler question is:

How should DWARF producers/consumers expect to encode the source
example Ben provided (well, simplified a bit):

#include <x86intrin.h>

void f( __m128 a){
}

What DWARF should be used to describe the type of 'a'? And how does
this encoding scale to all the other similar intrinsic types?

As a person who has spent a crazy amount of time doing ABI work and static analysis this is what I would like: (I'm kind of assembling this by cutting and pasting and editing so please excuse minor errors like sizes and ignoring siblings. It is hand written hand-wavy DWARF)

*Factored out preamble to all of them:*
[0] base_type            abbrev: 3
       byte_size            (data1) 4
       encoding             (data1) float (4)
       name                 (strp) "float"
[5] base_type            abbrev: 3
       byte_size            (data1) 4
       encoding             (data1) unsigned (4)
       name                 (strp) "unsigned int"
[8] base_type            abbrev: 3
       byte_size            (data1) 4
       encoding             (data1) signed (4)
       name                 (strp) "int"
[10] base_type            abbrev: 3
       byte_size            (data1) 8
       encoding             (data1) double (4)
       name                 (strp) "double float"
[15] subprogram abbrev: 32
   external             (flag_present) yes
   name                 (string) "f"
[20]      formal_parameter     abbrev: 15
         name                 (string) "a"
         type                 (ref4) [30]

*void f( float *a){}**
*[30] pointer_type         abbrev: 5
       byte_size            (implicit_const) 8
       type                 (ref4) [0]

*void f( float a[]){}*
[30] array_type         abbrev: 5
       type                 (ref4) [0]

*void f( float a[4]){}
*[30] array_type         abbrev: 5
       type                 (ref4) [0]
[40] subrange_type        abbrev: 31
       upper_bound          (data1) 3*
*

*void f( float a[s], **unsigned s**){} // C only*
[30] array_type         abbrev: 5
       type                 (ref4) [0]
[40] subrange_type        abbrev: 31
       upper_bound          (data1) DW_OP_reg reg4
[50]formal_parameter     abbrev: 15
         name                 (string) "s"
         type                 (ref4) [5]

*void f( __m128 a){} // floats implied*
[30] typedef              abbrev: 8
        name                 (strp) "__m128"
             decl_file            (data1) xmmintrin.h (2)
             decl_line            (data1) 69
             decl_column          (data1) 15
             type                 (ref4) [40]
[40] array_type         abbrev: 5
vector           (flag_present) yes
       type                 (ref4) [0]
[50] subrange_type        abbrev: 31
       upper_bound          (data1) 3*
*

*void f( __m128i a){} // integer*
[30] typedef              abbrev: 8
        name                 (strp) "__m128i"
             decl_file            (data1) xmmintrin.h (2)
             decl_line            (data1) 69
             decl_column          (data1) 15
             type                 (ref4) [40]
[40] array_type         abbrev: 5
vector           (flag_present) yes
       type                 (ref4) [8]
[50] subrange_type        abbrev: 31
       upper_bound          (data1) 3

*void f( __m128d a){}*
[30] typedef              abbrev: 8
        name                 (strp) "__m128d"
             decl_file            (data1) xmmintrin.h (2)
             decl_line            (data1) 69
             decl_column          (data1) 15
             type                 (ref4) [40]
[40] array_type         abbrev: 5
vector           (flag_present) yes
       type                 (ref4) [10]
[50] subrange_type        abbrev: 31
       upper_bound          (data1) 2

*void f( __m128u a){} // unsigned*
[30] typedef              abbrev: 8
        name                 (strp) "__m128u"
             decl_file            (data1) xmmintrin.h (2)
             decl_line            (data1) 69
             decl_column          (data1) 15
             type                 (ref4) [40]
[40] array_type         abbrev: 5
vector           (flag_present) yes
       type                 (ref4) [5]
[50] subrange_type        abbrev: 31
       upper_bound          (data1) 3

Then I would continue on with all various encodings and base types that can be in a the various kinds of vectors. Because the instructions that load the base types into the vector register do not care about encodings, they are just moving bytes, the Intel vector intrinsics type names kind of merge encoding and length in a way that I'm not fond of as someone who cares about ABI. For example I invented __m128u above to go along with the __m128i already defined in intel's intrinsics manual.

I would also expand that the larger vector sizes. For example:

*void f( __mm256 a){} // floats implied*
[30] typedef              abbrev: 8
        name                 (strp) "__mm256"
             decl_file            (data1) xmmintrin.h (2)
             decl_line            (data1) 69
             decl_column          (data1) 15
             type                 (ref4) [40]
[40] array_type         abbrev: 5
vector           (flag_present) yes
       type                 (ref4) [0]
[50] subrange_type        abbrev: 31
       upper_bound          (data1) 7

As you can see, much of what I really would like is to:

1. Not have arrays degenerate into pointers when the source code is
   explicit about this. Consider a linked lists vs an arrays, both
   could have the same ABI fingerprint. - I believe that this should be
   written into the standard as a best practice example.I will write
   this up and file it.
2. When it is an array and the bound is specified, this also should be
   included in the ABI fingerprint. - I believe that this to should be
   written into the standard as a best practice example.I will write
   this up and file it.
3. In my ABI work I need a way to disambiguate "typedef float[4]
   __m128" from "__m128" as it is now defined in xmmintrin.h. The
   difference is important because in libabigail we do not look at the
   location of the parameters, we just assume that the platform ABI as
   implemented by the compiler takes care of that. Thus if arrays
   didn't degenerate into pointers (introducing the ambiguity between
   the head of a linked list and an array), then:

typedef float[4] __m128;
void f( __m128 a){}

Would stick a pointer to a in general purpose register. While:

#include "xmmintin.h"
void f( __m128 a){}

Would stick a in a vector register because calling convention is different and libabigail wouldn't be able to tell the difference.

We don't process the formal parameter's location at least in part because it is hard. We would have to add code in libabigail to process the location list but also because the quality of location information from different compilers has been inconsistent. And the purpose of libabigail the tool was not to check how well the compilers implemented the platform ABI but to test libraries for ABI compatibility.

I spent a notable portion of yesterday writing various bits of arguments against Cary's DW_TAG_vector and then throwing them away because they really were not at all convincing even to myself. The only argument that I found convincing to even myself was parsimony. We currently have DW_TAG_array and I couldn't come up with how it would be different in any way from DW_TAG_array + DW_AT_vector. So based on that rather weak argument, I'll say that I really don't care if it is:

DW_TAG_vector

or

DW_TAG_array
  DW_AT_vector

Tony let me know he's become convinced that they do not need a vector type for their GPU work and are planning to drop their vectors as base type proposal. That leaves my needs around ABI as the only pending concern and that may be handled by Kyle's proposal to make the location of the return value something encoded in the DWARF rather than having to infer it from the platform ABI.

If we didn't get something like DW_TAG_vector or DW_TAG_array + DW_AT_vector, and instead only went with just Kyle's proposal specifying the location of the return value, then libabigail would then need to be taught to process location information.

-ben


**
-- 
Dwarf-discuss mailing list
Dwarf-discuss@lists.dwarfstd.org
https://lists.dwarfstd.org/mailman/listinfo/dwarf-discuss

Reply via email to