This is an archive of the discontinued LLVM Phabricator instance.

Support for PowerPC vector type
AbandonedPublic

Authored by DanielCChen on Mar 23 2023, 12:46 PM.

Details

Summary

This RFC is for adding the PowerPC vector type support as a language extension.

It has the following syntax:

VECTOR(element-type-spec)

where

element-type-spec

is:

integer-type-spec

or

real-type-spec

or

unsigned-type-spec

A finite set of functionalities are implemented in order to support the PowerPC vector type:

  1. declare VECTOR type objects.
  2. declare VECTOR type function result.
  3. declare VECTOR type dummy arguments.
  4. intrinsic assignment between VECTOR type objects. (e.g. v1 = v2)
  5. reference VECTOR functions.

This patch only supports the functionalities mentioned at the above. Others, such as intrinsic operators are not extended to support vectors as all operations on vector types (such as addition, subtraction, construction, ... etc) are done via calls to vector intrinsic functions. The support of constant expressions, derived component, POINTER or ALLOCATABLE attribute, I/O, and etc is not included in this patch.

The purpose of this RFC is to get comments from the community on our implementation approach. We plan to add the LIT tests in this patch as a revision soon. We also plan to add semantic checking in a separate patch in the near future.

Diff Detail

Event Timeline

DanielCChen created this revision.Mar 23 2023, 12:46 PM
DanielCChen requested review of this revision.Mar 23 2023, 12:46 PM
DanielCChen edited the summary of this revision. (Show Details)Mar 23 2023, 12:50 PM
DanielCChen added reviewers: kkwli0, pscoro, madanial, tislam.
DanielCChen edited the summary of this revision. (Show Details)
DanielCChen edited the summary of this revision. (Show Details)Mar 23 2023, 12:52 PM

It is a nice implementation dealing with all compilation stages, kudos for that! I am not in favor of extending TypeCategory with Vector, which is rather central in your patch after parsing, and I am suggesting an alternative (please wait for more feedback before spending too much time trying it though).

flang/include/flang/Common/Fortran.h
23

Personally, I would not favor making "Vector" a type category: it is a bit orthogonal to the element type (it looks more like a very special shape attribute to me that would create a constant size rank one array of element with special semantic (e.g: cannot be used in intrinsic operators)).

The TypeCategory is also very central to all the type system in the compiler after parsing, I feel it makes this extension very intrusive.

Could the "Vector" aspect be represented as a special attributes that could be added to the vector dummy or result symbols? (It would probably need to be added to the characteristics DummyDataObject and FunctionResult attributes in https://github.com/llvm/llvm-project/blob/main/flang/include/flang/Evaluate/characteristics.h).

I have not thought this in detail, and cannot be sure this would work, but I wonder if you could represent the vector type as a builtin kind parametrized derived type in semantics (inside evaluate:: and semantics::) that would look like:

type __builtin_ppc_vector(element_category, element_kind)
  integer, kind :: element_category, element_kind
  integer(16) :: storage ! Ensure alignment and type size is correct in inquiries.
end type

That way, it would be transparent in semantics (there may be a few places where it would need to be forbidden explicitly in places where a parametrized derived type is allowed (e.g parentheses)). As a funny extension, this would allow you to define the ppc vector intrinsic as type bound procedures for +,-,... operators if you ever wanted to allow the usage of operators for ppc vectors.

In lowering, the builtin type would be recognized and translated to the fir.vector type (some code might expect to get a fir.record type from a semantics Derived, but not many places should be impacted).

In any case, I would wait for more feedback from @klausler here on this point.

Is this the first of ten vendors types? Do I need a flag to enable the PowerPC vector type or does it also work on other architectures?

tislam added inline comments.Mar 24 2023, 1:39 PM
flang/include/flang/Common/Fortran.h
23

Thanks for the quick review and suggestion! We actually looked at both approaches at the beginning.
Adding a new TypeCategory for vector type seems to have less complexity and better separation of vector type from others.
Our implementation is trying to isolate the vector code as much as possible. It can be compiled out if it is desired.
Using the derived type wrapper approach seems to require code splitting when the specialized derived type is encountered to handle vector types.
That being said, we are definitely seeking more comments.

Is this the first of ten vendors types? Do I need a flag to enable the PowerPC vector type or does it also work on other architectures?

We have two more PowerPC opaque types coming, namely __VECTOR_PAIR and __VECTOR_QUAD, of 32 bytes and 64 bytes in length respectively. These vector and opaque types should only be enabled on PowerPC architecture, and disabled on other platforms.

Do any operations on these vector types need to be folded to constant values for use in constant expressions or specification expressions?

The syntax of the vector type specifications allows an element type to appear (e.g. VECTOR(INTEGER)). This design appears to discard the element type, treating the vector essentially as a typeless entity, so that this element type is not used or needed to select the right code to emit for an operation; the operations themselves are fully typed by the names of the intrinsic procedures, and the vector operands are just raw data to them. Do I have that right?

So you basically just need a container type with a fixed size so that you can declare vector objects and validate the operands to vector intrinsic procedures. You don't really have vector operations, apart from calls to new vector intrinsic procedures. And these vector types are not intended to be applicable to multiple target architectures, much less part of a future Fortran language standard.

(If on the other hand you wanted to distinguish a VECTOR(INTEGER) from a VECTOR(REAL) in the type system of the compiler and leverage those types to automatically insert conversion operations, the situation would be different.)

How close am I to understanding your situation?

DanielCChen added a comment.EditedMar 26 2023, 5:19 PM

Thanks for the review and the comments!

Do any operations on these vector types need to be folded to constant values for use in constant expressions or specification expressions?

No. These vector types cannot be used in constant expr or specification expr, so there is no folding for it. The implementation in this patch intentionally avoids instantiating the Constant class for the vector type.

The syntax of the vector type specifications allows an element type to appear (e.g. VECTOR(INTEGER)). This design appears to discard the element type, treating the vector essentially as a typeless entity, so that this element type is not used or needed to select the right code to emit for an operation; the operations themselves are fully typed by the names of the intrinsic procedures, and the vector operands are just raw data to them. Do I have that right?

We support vector type assignment (e.g. v1 = v2), The element type and the KIND parameter must be the same for LHS and RHS. For example, VECTOR(INTEGER(4)) = VECTOR(INTEGER(8)) would be an invalid usage. So in the FE, the element type and its KIND parameter will be used for semantic checking (not included in this patch). We also support vector type arguments. The same constraint will be checked for actual arguments against the corresponding dummy arguments. In code gen, we will generate different IR depending on the element type and its KIND parameter. For example, global <8 x i16> for VECTOR(INTEGER(2)) and global <4 x i32> for VECTOR(INTEGER(4)). We also generate different IR for vector intrinsic functions (e.g add vs fadd for integer vs real for intrinsic function vec_add).

So you basically just need a container type with a fixed size so that you can declare vector objects and validate the operands to vector intrinsic procedures. You don't really have vector operations, apart from calls to new vector intrinsic procedures. And these vector types are not intended to be applicable to multiple target architectures, much less part of a future Fortran language standard.

The vector type that has element type (integer, real or unsigned) is 16 bytes in length. The other two opaque vector types, __VECTOR_PAIR and __VECTOR_QUAD is 32 bytes and 64 bytes in length respectively although it doesn't not have any element types (e.g. __VECTOR_PAIR :: VP1). There is a different set of intrinsic functions that operate on these two opaque vector types. Our plan is to use a new vector type category in VectorTypeSpec for these two new types.

(If on the other hand you wanted to distinguish a VECTOR(INTEGER) from a VECTOR(REAL) in the type system of the compiler and leverage those types to automatically insert conversion operations, the situation would be different.)

How close am I to understanding your situation?

In the future, we also plan to add support for vector type derived type components and to have allocatable and pointer attributes on vector type objects.

Thank you so much for the detailed response to my questions.

As I see it, you have basically four alternatives: add Vector to the top level of the typing system (as in this patch); add Vector as an orthogonal attribute to the current types; add Vector as an object attribute (akin to VOLATILE); or add derived types or.a parameterized derived type to distinguish all of the possible vector types.

With Vector as a type category, or type or object attribute, you'll need to extend the procedure characterization module to handle compatibility checks with vector arguments; extend expression semantics to handle assignment compatibility checking; extend pointer assignment checking, if pointers to vectors are allowed; and a fair amount of work in the runtime to cope with I/O, interoperability, memory allocation, &c. Even where vectors are not permitted (formatted I/O? namelist? allocatables? I'm sorry that I don't know this extension better) you'll need to have stubs and asserts. And somebody is probably going to ask you to wrap some or all of this extension support with #ifdef or constexpr ifs.

Using derived types would have the smallest overall code footprint, I think, and you'd immediately get implementations of argument compatibility checking, assignment compatibility checks, I/O, debugging tables, and so on basically for free. It might complicate lowering a little, but using a PDT with a kind parameter would help that. You might have the problem of having vector features working automatically that you don't intend to support (pointers to vectors? user-defined I/O?) and might want to disable. For error messages of the best quality, and to get data alignment right, you'd probably want to extend the DerivedTypeSpec implementation to detect and handle vectors as special cases.

I'd like to encourage you to reconsider whether you could do this with derived types. I think it would result in significantly less work, less extension-specific code, and way fewer #ifdefs or constexpr ifs marbled throughout the compiler.

flang/runtime/type-info.cpp
52

RUNTIME_CHECK and RUNTIME_CRASH are how we assert and crash in the runtime.

Thanks for the quick response!

Thank you so much for the detailed response to my questions.

As I see it, you have basically four alternatives: add Vector to the top level of the typing system (as in this patch); add Vector as an orthogonal attribute to the current types; add Vector as an object attribute (akin to VOLATILE); or add derived types or.a parameterized derived type to distinguish all of the possible vector types.

With Vector as a type category, or type or object attribute, you'll need to extend the procedure characterization module to handle compatibility checks with vector arguments; extend expression semantics to handle assignment compatibility checking; extend pointer assignment checking, if pointers to vectors are allowed; and a fair amount of work in the runtime to cope with I/O, interoperability, memory allocation, &c. Even where vectors are not permitted (formatted I/O? namelist? allocatables? I'm sorry that I don't know this extension better) you'll need to have stubs and asserts. And somebody is probably going to ask you to wrap some or all of this extension support with #ifdef or constexpr ifs.

Right. we currently have semantic checking for argument passing (from the operator(==)), but we do need to add code for assignment. We don't plan to support I/O for vector types.

Using derived types would have the smallest overall code footprint, I think, and you'd immediately get implementations of argument compatibility checking, assignment compatibility checks, I/O, debugging tables, and so on basically for free. It might complicate lowering a little, but using a PDT with a kind parameter would help that. You might have the problem of having vector features working automatically that you don't intend to support (pointers to vectors? user-defined I/O?) and might want to disable. For error messages of the best quality, and to get data alignment right, you'd probably want to extend the DerivedTypeSpec implementation to detect and handle vectors as special cases.

Is the following mapping something you have in mind? This is based on @jeanPerier 's suggestion.

vector(integer)     ->        type __builtin_ppc_intrinsic_vector(element_category, element_kind)
vector(real)                    integer, kind :: element_category, element_kind
vector(unsigned)                integer(16) :: storage
                              end type  

__vector_pair       ->        type __builtin_ppc_pair_vector
                                integer(16) :: storage1
                                integer(16) :: storage2
                              end type

__vector_quad       ->        type __builtin_ppc_quad_vector
                                integer(16) :: storage1
                                integer(16) :: storage2
                                integer(16) :: storage3
                                integer(16) :: storage4
                              end type

If so, what would be the best place to construct these derived types? They are not scoped (i.e. the definition should be global). I think the lowering should be OK as long as v1 = v2 is not expanded into v1%storage = v2%storage in FE.

I'd like to encourage you to reconsider whether you could do this with derived types. I think it would result in significantly less work, less extension-specific code, and way fewer #ifdefs or constexpr ifs marbled throughout the compiler.

DanielCChen added inline comments.Mar 27 2023, 10:30 AM
flang/runtime/type-info.cpp
52

Thanks! Good to know.

DanielCChen added a comment.EditedMar 28 2023, 8:50 AM

@klausler @jeanPerier We have been looking into the derived type wrapper approach. Besides the question in my response to Peter's comments regarding if the derived types layout is OK to represent those 3 different vector types, I have another question of type compatibility checking.
Consider the following vector code:

module m1
vector(integer) :: v1
end module

module m2
vector(integer) :: v2
end module

program main
use m1
contains
subroutine sub()
use m2
v1 = v2
end subroutine
end program

With derived wrapper approach, we will get something like:

module m1
type __builtin_ppc_intrinsic_vector(element_category, element_kind)
integer, kind :: element_category, element_kind
integer(16) :: storage
end type
end module

module m2
type __builtin_ppc_intrinsic_vector(element_category, element_kind)
integer, kind :: element_category, element_kind
integer(16) :: storage
end type
end module

program main
use m1
type(__builtin_ppc_intrinsic_vector(1, 4)) :: v1
call sub()
contains
subroutine sub()
use m2
type(__builtin_ppc_intrinsic_vector(1, 4)) :: v2
v1 = v2
end subroutine
end program

Currently, it complains at v1 = v2 as their types are coming from different module. Is this something we could just simply 'skip' the semantic checking for the specialized derived type for vector? This also applies to the argument passing case.

@klausler @jeanPerier We have been looking into the derived type wrapper approach. Besides the question in my response to Peter's comments regarding if the derived types layout is OK to represent those 3 different vector types, I have another question of type compatibility checking.
Consider the following vector code:

module m1
vector(integer) :: v1
end module

module m2
vector(integer) :: v2
end module

I recommend that you define "builtin" derived types in one intrinsic module -- perhaps the same one in which the PPC vector intrinsic procedure interfaces are defined -- and reference those derived type definitions via USE association.

@klausler @jeanPerier We have been looking into the derived type wrapper approach. Besides the question in my response to Peter's comments regarding if the derived types layout is OK to represent those 3 different vector types, I have another question of type compatibility checking.
Consider the following vector code:

module m1
vector(integer) :: v1
end module

module m2
vector(integer) :: v2
end module

I recommend that you define "builtin" derived types in one intrinsic module -- perhaps the same one in which the PPC vector intrinsic procedure interfaces are defined -- and reference those derived type definitions via USE association.

Ok. I see. Thanks for the pointer!

DanielCChen abandoned this revision.Aug 16 2023, 9:53 AM

Close this review as a different implementation that uses derived type wrapper approach has been landed.