[cfe-dev] Implementing OpenMP function variants

John McCall via cfe-dev cfe-dev at lists.llvm.org
Tue Dec 17 23:08:22 PST 2019

On 13 Dec 2019, at 10:08, Finkel, Hal J. wrote:
> Richard, John, et al.,
> Let me top-post here quickly to add that this question comes directly
> from a disagreement about the application of Clang's design principle 
> of
> keeping the AST faithful to the source code (and I suspect that such a
> question may be of wider interest to the community).
> Johannes has summarized the OpenMP language semantics below (thanks,
> Johannes!).
> The relevant review thread is here: https://reviews.llvm.org/D71241
> Alexey's position is that, because the source code contains what 
> appears
> to be a call to the base() function, the AST should always reflect 
> that
> fact by having getCallee() return a reference to base(), and we should
> lower the call to the selected variant using logic in CodeGen. Some
> other member function should be used by AST-level tools to retrieve 
> the
> actually-called variant. This has the benefit that the primary AST
> representation is independent of the compilation target and other
> relevant OpenMP context.
> My position is that, like other cases where we perform overload
> resolution and specializaton selection (including host/device 
> overloads
> in CUDA), we should resolve the variant selected in Sema, and
> getCallee() should return a reference to the function that will 
> actually
> be called (even if that function has a different name from the name 
> used
> syntactically to form the call expression). This will ensure that
> static-analysis tools see the correct call-site <-> callee 
> relationship.
> We should, I think, also keep a reference to the original OpenMP base
> function in the AST, but some other member function should be used to
> retrieve it.
> We say that we keep Clang's AST faithful to the source code, but how 
> to
> best apply that philosophy in this case is now under debate.

Is it always immediately decidable when parsing a reference to a 
function which variant should be used, or is it sometimes dynamic or at 
least delayed?

How is this expected to interact with C++ overloading?  Can you 
independently declare variants of each overload?

How does this interact with nested scopes?  If all variants are 
unacceptable for the use context, is it like the declaration just 
doesn’t exist, and so lookup continues to outer scopes?  Or is this 
impossible because there always has to be a non-variant declaration in 
the current scope?

My immediate intuition is that, assuming the semantics are always static 
and that there’s always a non-variant function, this should be handled 
as a sort of second level of overload resolution.  The variant 
declarations should be considered more-or-less independent functions; 
they are not redeclarations of the original.  5.1-type variants should 
be hidden from lookup, so that only the original function is found.  
When we resolve a use of a declaration with variants we then pick the 
appropriate variant and treat the declaration as if it was using that 
originally.  The fact that we resolved it via a variant should be 
recorded in the FoundDecl, which would now have an additional possible 
state: we could have looked through a using declaration, and we could 
have resolved a variant.  This shouldn’t be a problem for FoundDecl.


> Thanks again,
> Hal
> On 12/12/19 11:28 PM, Doerfert, Johannes wrote:
>> Background:
>> The prototype for OpenMPs 5.1 feature `begin/end declare variant` 
>> caused
>> a discussion that seems to be stuck. I'll briefly outline the 
>> semantics
>> of the OpenMP 5.0 `declare variant` and the `begin/end declare 
>> variant`
>> here before I present the two competing designs on a very high level. 
>> If
>> there is a need for more information, a summary of what was said, or
>> anything else, please feel free to ask for it.
>> ---
>> OpenMP 5.0 `declare variant` allows you to define a variant of a base
>> function that is called instead of the base function if the context
>> specified with the variant matches at a call site.
>> As an example, the call to base below is replaced by a call to 
>> special
>> if the code is compiled with a compiler that identifies as "llvm".
>> ```
>> void special();
>> #pragma omp declare variant(special) 
>> match(implementation={vendor(llvm)})
>> void base();
>> void test() {
>>    base();
>> }
>> ```
>> Context selectors for the match clause can be specific to the target,
>> the implementation, or the context in which the call is executed, 
>> e.g.,
>> syntactic inside a #omp parallel.
>> ---
>> OpenMP 5.1 `begin/end declare variant` is an extension of the 5.0
>> version that (1) makes the user specific mangling obsolete, and (2)
>> guards the code in between the begin/end if the context selector does
>> not match at compile time. The use case can be simplified to the code
>> below in which we want the `sin` call to resolve to the NVIDIA 
>> specific
>> version if we are compiling for nvptx and to the one in math.h
>> otherwise. Note that the variant in the begin/end range has the same
>> name as the base it overloads.
>> ```
>> #include "math.h"
>> #pragma omp begin declare variant match(device={arch(nvptx)})
>> double sin(double s) { return __nv_sin(s); }
>> #pragma omp end declare variant
>> double calc(double s) {
>>    return sin(sin(sin(s)));
>> }
>> ```
>> ---
>> Now that we are all OpenMP experts, we need to decide how/where to
>> implement this. Oversimplified, these are the options being 
>> discussed:
>> Proposal 1, CodeGen:
>>    Introduce aliases that redirect from one function to the other or
>>    replace the callee in the CodeGen.
>> Proposal 2), SemaOverload:
>>    Use the multi-version overloading for the 5.1 feature and 
>> introduce
>>    another level of overload resolution for the 5.0 feature.
>> ---
>> Thanks in advance for your time and input,
>>    Johannes
> -- 
> Hal Finkel
> Lead, Compiler Technology and Programming Languages
> Leadership Computing Facility
> Argonne National Laboratory

More information about the cfe-dev mailing list