[llvm-dev] [RFC] Enable "#pragma omp declare simd" in the LoopVectorizer

Tian, Xinmin via llvm-dev llvm-dev at lists.llvm.org
Thu Dec 8 10:11:18 PST 2016


Hi Francesco, a bit more information.  GCC veclib is implemented based on GCC VectorABI for declare simd as well. 

For name mangling, we have to follow certain rules of C/C++ (e.g. prefix needs to _ZVG ....).  David Majnemer who is the owner and stakeholder for approval for Clang and LLVM.  Also,  we need to pay attention to GCC compatibility.  I would suggest you look into how GCC VectorABI can be extended support your Arch. 

Thanks,
Xinmin

-----Original Message-----
From: Odeh, Saher 
Sent: Thursday, December 8, 2016 3:49 AM
To: Tian, Xinmin <xinmin.tian at intel.com>; llvm-dev at lists.llvm.org; Francesco.Petrogalli at arm.com
Cc: nd <nd at arm.com>; Masten, Matt <matt.masten at intel.com>; Hal Finkel <hfinkel at anl.gov>; Zaks, Ayal <ayal.zaks at intel.com>; a.bataev at hotmail.com
Subject: RE: [llvm-dev] [RFC] Enable "#pragma omp declare simd" in the LoopVectorizer

Hi Francesco,

As you stated in the RFC, when vectorizing a scalar function (e.g. when using omp declare simd), one needs to incorporate attributes to the resulting vectorized-function. 
These attributes describe a) the behavior of the function, e.g. mask-able or not, and b) the type of the parameters, e.g. scalar or linear or any other option.

As this list is extensive, it is only logical to use an existing infrastructure of ICC and GCC vectorABI which already covers all of these options as stated in Xinmin's RFC [http://lists.llvm.org/pipermail/cfe-dev/2016-March/047732.html].
Moreover, when considering other compilers such as GCC, I do see that the resulting assembly actually does incorporate this exact infrastructure. So if we wish to link different parts of the program using clang and GCC we'll need to adhere to the same name mangling/ABI. Please see the below result after compiling an omp declare simd function using GCC.
Lastly, please note the two out of the three components of the implementation have already been committed or submitted, and both are adhering the name mangling proposed by Xinmin's RFC. A) committed - the FE portion by Alexey [https://reviews.llvm.org/rL264853], it generates mangled names in the manner described by Xinmin's RFC, See below B) Submitted - the callee side by Matt [https://reviews.llvm.org/D22792], it uses these mangled names. and C) caller which is covered by this patch.

In order to mitigate the needed effort and possible issues when implementing, I believe it is best to follow the name mangling proposed in Xinmin's RFC. What do you think?

GCC Example
----------------
Compiler version: GCC 6.1.0
Compile line: gcc -c omp.c -fopenmp -Wall -S -o - -O3 > omp.s

omp.c
#include <omp.h>

#pragma omp declare simd
int dowork(int* a, int idx)
{
 return a[idx] * a[idx]*7;
}

less omp.s | grep @function
        .type   dowork, @function
        .type   _ZGVbN4vv_dowork, @function
        .type   _ZGVbM4vv_dowork, @function
        .type   _ZGVcN4vv_dowork, @function
        .type   _ZGVcM4vv_dowork, @function
        .type   _ZGVdN8vv_dowork, @function
        .type   _ZGVdM8vv_dowork, @function
        .type   _ZGVeN16vv_dowork, @function
        .type   _ZGVeM16vv_dowork, @function

Clang on FE using Alexey's patch
---------------------------------------
Compile line: clang -c tst/omp_fun.c -fopenmp -mllvm -print-after-all >& out

#pragma omp declare simd
extern int dowork(int* a, int idx)
{
  return a[idx]*7;
}


int main() {
  dowork(0,1);
}

attributes #0 = { nounwind uwtable "_ZGVbM4vv_dowork" "_ZGVbN4vv_dowork" "_ZGVcM8vv_dowork" "_ZGVcN8vv_dowork" "_ZGVdM8vv_dowork" "_ZGVdN8vv_dowork" "_ZGVeM16vv_dowork" "_ZGVeN16vv_dowork" "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }


Thanks Saher

-----Original Message-----
From: Francesco Petrogalli [mailto:Francesco.Petrogalli at arm.com] 
Sent: Tuesday, December 06, 2016 17:22
To: Tian, Xinmin <xinmin.tian at intel.com>; llvm-dev at lists.llvm.org
Cc: nd <nd at arm.com>; Masten, Matt <matt.masten at intel.com>; Hal Finkel <hfinkel at anl.gov>; Zaks, Ayal <ayal.zaks at intel.com>; a.bataev at hotmail.com
Subject: Re: [llvm-dev] [RFC] Enable "#pragma omp declare simd" in the LoopVectorizer

Hi Xinmin,

Thank you for your email.

I have been catching up with the content of your proposal, and I have some questions/remarks below that I'd like to discuss with you - see the final section in the proposal.

I have specifically added Alexey B. to the mail so we can move our conversation from phabricator to the mailing list.

Before we start, I just want to mention that the initial idea of using llvm::FunctionType for vector function generation and matching has been proposed by a colleague, Paul Walker, when we first tried out supporting this on AArch64 on an internal version of llvm. I received some input also from Amara Emerson.

In our case we had a slightly different problem to solve: we wanted to support in the vectorizer a rich set of vector math routines provided with an external library. We managed to do this by adding the pragma to the (scalar) function declaration of the header file provided with the library, and as shown by the patches I have submitted, by generating vector function signatures that the vectorizer can search in the TargetLibraryInfo.

Here is an updated version of the proposal. Please let me know what you think, and if you have any solution we could use for the final section.

# RFC for "pragma omp declare simd"

Hight level components:

A) Global variable generator (clang FE)
B) Parameter descriptors (as new enumerations in llvm::Attribute)
C) TLII methods and fields for the multimap (llvm middle-end)

## Workflow

Example user input, with a declaration and definition:

    #pragma omp declare simd
    #pragma omp declare simd uniform(y)
    extern double pow(double x, double y);

    #pragma omp declare simd
    #pragma omp declare simd linear(x:2)
    float foo(float x) {....}

    /// code using both functions

### Step 1


The compiler FE process these definition and declaration and generates a list of globals as follows:

    @prefix_vector_pow1_midfix_pow_postfix = external global
                                             <4 x double>(<4 x double>,
                                                          <4 x double>)
    @prefix_vector_pow2_midfix_pow_postfix = external global
                                             <4 x double>(<4 x double>,
                                                          double)
    @prefix_vector_foo1_midfix_foo_postfix = external global
                                             <8 x float>(<8 x float>,
                                                         <8 x float>)
    @prefix_vector_foo1_midfix_foo_postfix = external global
                                             <8 x float>(<8 x float>,
                                                         <8 x float> #0)
    ...
    attribute #0  = {linear = 2}


Notes about step 1:

1. The mapping scalar name <-> vector name is in the
   prefix/midfix/postfix mangled name of the global variable.
2. The examples shows only a set of possible vector function for a
   sizeof(<4 x double>) vector extension. If multiple vector extension
   live in the same target (eg. NEON 64-bit or NEON 128-bit, or SSE
   and AVX512) the front end takes care to generate each of the
   associated functions (like it is done now).
3. Vector function parameters are rendered using the same
   Characteristic Data Type (CDT) rule already in the compiler FE.
4. Uniform parameters are rendered with the original scalar type.
5. Linear parameters are rendered with vectors using the same
   CDT-generated vector length, and decorated with proper
   attributes. I think we could extent the llvm::Attribute enumeration adding the following:
   - linear : numeric, specify_the step
   - linear_var : numeric, specify the position of the uniform variable holding the step
   - linear_uval[_var]: numeric as before, but for the "uval" modifier (both constant step or variable step)
   - linear_val[_var]: numeric, as before, but for "val" modifier
   - linear_ref[_var] numeric, for "ref" modifier.

   For example, "attribute #0 = {linear = 2}" says that the vector of
   the associated parameter in the function signature has a linear
   step of 2.

### Step 2

The compiler FE invokes a TLII method in BackendUtils.cpp that populate a multimap in the TLII by checking the globals created in the previous step.

Each global is processed, demangling the [pre/mid/post]fix name and generate a mapping in the TLII as follows:

    struct VectorFnInfo {
       std::string Name;
       FunctionType *Signature;
    };
    std::multimap<std:string, VectorFnInfo> VFInfo;


For the initial example, the multimap in the TLI is populated as follows:

    "pow" -> [(vector_pow1, <4 x double>(<4 x double>, <4 x double>)),
              (vector_pow2, <4 x double>(<4 x double>, double))]

    "foo" -> [(vector_foo1, <8 x float>(<8 x float>, <8 x float>)),
              (vector_foo2, <8 x float>(<8 x float>, <8 x float> #0))]

Notes about step 2:

Given the fact that the external globals that the FE have generated are removed _before_ the vectorizer kicks in, I am not sure if the "attribute #0" needed for one of the parameter is still present at this point. IF NOT, I think that in this case we could enrich the "VectorFnInfo" as follows:

    struct VectorFnInfo {
       std::string Name;
       FunctionType *Signature;
       std::set<unsigned, llvm:Attribute> Attrs;
    };

The field "Attrs" maps the position of the parameter with the correspondent llvm::Attribute present in the global variable.

I have added this note for the sake of completeness. I *think* that we won't be needing this additional Attrs field: I have already shown in the llvm patch I submitted that the function type "survives" after the global gets removed, I don't see why the parameter attribute shouldn't survive too (last famous words?).

### Step 3

This step happens in the LoopVectorizer. The InnerLoopVectorizer queries the TargetLibraryInfo looking for a vectorized version of the function by scalar name and function signature with the following method:

    TargetLibraryInfo::isFunctionVectorizable(std::string ScalarName, FuncionType *FTy);

This is done in a way similar to what my current llvm patch does: the loop vectorizer makes up the function signature it needs and look for it in the TLI. If a match is found, vectorization is possible. Right now the compiler is not aware of uniform/linear function attributes, but it still can refer to them in a target agnostic way, by using scalar signatures for the uniform ones and using llvm::Attributes for the linear ones.

Notice that the vector name here is not used at all, which is good as any architecture can come up with it's own name mangling for vector functions, without breaking the ability of the vectorizer to vectorize the same code with the new name mangling.

## External libraries vs user provided code

The example with "pow" and "foo" I have provided before shows a function declaration and a function definition. Although the TLII mechanism I have described seems to be valid only for the former case, I think that it is valid also for the latter.  In fact, in case of a function definition, the compiler would have to generate also the body of the vector function, but that external global variable could still be used to inform the TLII of such function. The fact that the vector function needed by the vectorizer is in some module instead of in an external library doesn't seems to make all that difference at compile time to me.

# Some final notes (call for ideas!)

There is one level of target dependence that I still have to sort out, and for this I need input from the community and in particular from the Intel folks.

I will start with this example:

    #pragma omp declare simd
    float foo(float x);

In case of NEON, this would generate 2 globals, one for vectors holding 2 floats, and one for vector holding 4 floats, corresponding to NEON 64-bit and 128-bit respectively. This means that the vectorizer have a unique function it could choose from the list the TLI provides.

This is not the same on Intel, for example when this code generates vector names for AVX and AVX2. The register width for these architecture extensions are the same, so all the TLI has is a mapping between scalar name and (vectro_name, function_type) who's two elements differ only in the vector_name string.

This breaks the target independence of the vectorizer, as it would require it to parse the vector_name to be able to choose between the AVX or the AVX2 implementation.

Now, to make this work one should have to encode the SSE/SSE2/AVX/AVX2 information in the VectorFnInfo structure. Does anybody have an idea on how best to do it? For the sake of keeping the vectorizer target independent, I would like to avoid encoding this piece of information in the VectorFnInfo struct. I have seen that in your code you are generating SSE/AVX/AVX2/AVX512 vector functions, how do you plan to choose between them in the vectorizer? I could not find how you planned to solve this problem in your proposal, or have I just missed it?

Is there a way to do this in the TLII? The function type of the vector function could use the "target-feature" attribute of function definitions, but how coudl the vectorizer decide which one to use?

Anyway, that's it. Your feedback will be much appreciated.

Cheers,
Francesco

________________________________________
From: Tian, Xinmin <xinmin.tian at intel.com>
Sent: 30 November 2016 17:16:12
To: Francesco Petrogalli; llvm-dev at lists.llvm.org
Cc: nd; Masten, Matt; Hal Finkel; Zaks, Ayal
Subject: RE: [llvm-dev] [RFC] Enable "#pragma omp declare simd" in the  LoopVectorizer

Hi Francesco,

Good to know, you are working on the support for this feature. I assume you knew the RFC below.  The VectorABI mangling we proposed were approved by C++ Clang FE name mangling owner David M from Google,  the ClangFE support was committed in its main trunk by Alexey.

"Proposal for function vectorization and loop vectorization with function calls", March 2, 2016. Intel Corp.  http://lists.llvm.org/pipermail/cfe-dev/2016-March/047732.html.

Matt submitted patch to generate vector variants for function definitions, not just function declarations. You may want to take a look.  Ayal's RFC will be also needed to support vectorization of function body in general.

I agreed, we should have an option -fopenmp-simd to enable SIMD only, both GCC and ICC have similar options.

I would suggest we shall sync-up on these work, so we don't duplicate the effort.

Thanks,
Xinmin

-----Original Message-----
From: llvm-dev [mailto:llvm-dev-bounces at lists.llvm.org] On Behalf Of Francesco Petrogalli via llvm-dev
Sent: Wednesday, November 30, 2016 7:11 AM
To: llvm-dev at lists.llvm.org
Cc: nd <nd at arm.com>
Subject: [llvm-dev] [RFC] Enable "#pragma omp declare simd" in the LoopVectorizer

Dear all,

I have just created a couple of differential reviews to enable the vectorisation of loops that have function calls to routines marked with "#pragma omp declare simd".

They can be (re)viewed here:

* https://reviews.llvm.org/D27249

* https://reviews.llvm.org/D27250

The current implementation allows the loop vectorizer to generate vector code for source file as:

  #pragma omp declare simd
  double f(double x);

  void aaa(double *x, double *y, int N) {
    for (int i = 0; i < N; ++i) {
      x[i] = f(y[i]);
    }
  }


by invoking clang with arguments:

  $> clang -fopenmp -c -O3 file.c [...]


Such functionality should provide a nice interface for vector libraries developers that can be used to inform the loop vectorizer of the availability of an external library with the vector implementation of the scalar functions in the loops. For this, all is needed to do is to mark with "#pragma omp declare simd" the function declaration in the header file of the library and generate the associated symbols in the object file of the library according to the name scheme of the vector ABI (see notes below).

I am interested in any feedback/suggestion/review the community might have regarding this behaviour.

Below you find a description of the implementation and some notes.

Thanks,

Francesco

-----------

The functionality is implemented as follow:

1. Clang CodeGen generates a set of global external variables for each of the function declarations marked with the OpenMP pragma. Each of such globals are named according a mangling that is generated by llvm::TargetLibraryInfoImpl (TLII), and holds the vector signature of the associated vector function. (See examples in the tests of the clang patch.
Each scalar function can generate multiple vector functions depending on the clauses of the declare simd directives) 2. When clang created the TLII, it processes the llvm::Module and finds out which of the globals of the module have the correct mangling and type so that they be added to the TLII as a list of vector function that can be associated to the original scalar one.
3. The LoopVectorizer looks for the available vector functions through the TLII not by scalar name and vectorisation factor but by scalar name and vector function signature, thus enabling the vectorizer to be able to distinguish a "vector vpow1(vector x, vector y)" from a "vector vpow2(vector x, scalar y)". (The second one corresponds to a "declare simd uniform(y)" for a "scalar pow(scalar x, scalar y)" declaration). (Notice that the changes in the loop vectorizer are minimal.)


Notes:

1. To enable SIMD only for OpenMP, leaving all the multithread/target behaviour behind, we should enable this also with a new option:
-fopenmp-simd
2. The AArch64 vector ABI in the code is essentially the same as for the Intel one (apart from the prefix and the masking argument), and it is based on the clauses associated to "declare simd" in OpenMP 4.0. For OpenMP4.5, the parameters section of the mangled name should be updated.
This update will not change the vectorizer behaviour as all the vectorizer needs to detect a vectorizable function is the original scalar name and a compatible vector function signature. Of course, any changes/updates in the ABI will have to be reflected in the symbols of the binary file of the library.
3. Whistle this is working only for function declaration, the same functionality can be used when (if) clang will implement the declare simd OpenMP pragma for function definitions.
4. I have enabled this for any loop that invokes the scalar function call, not just for those annotated with "#pragma omp for simd". I don't have any preference here, but at the same time I don't see any reason why this shouldn't be enabled by default for non annotated loops. Let me know if you disagree, I'd happily change the functionality if there are sound reasons behind that.

_______________________________________________
LLVM Developers mailing list
llvm-dev at lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev


More information about the llvm-dev mailing list