[cfe-dev] Implementing OpenMP function variants
John McCall via cfe-dev
cfe-dev at lists.llvm.org
Wed Dec 18 09:31:15 PST 2019
On 18 Dec 2019, at 11:16, Doerfert, Johannes wrote:
> On 12/18, John McCall wrote:
>> 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?
>
> For now, which means OpenMP 5.0 and TR8, it is conceptually
> immediately
> decidable. It only depends on compilation parameters, e.g., the target
> triple, and the lexical context.
>
>> How is this expected to interact with C++ overloading? Can you
>> independently declare variants of each overload?
>
> While the standard is not specific on this, I don't see why not.
>
> I tried to summarize what the standard says yesterday [0]:
>
> OpenMP basically says, if you have a call to a (base)function* that
> has
> variants with contexts that match at the call site, call the variant
> with the highest score. The variants are specified by a
> variant-func-id,
> which is a base language identifier or C++ template-id. For C++, the
> variant declaration is identified by *performing the base language
> lookup rules on the variant-func-id with arguments that correspond to
> the base function argument types*.
>
> * However you figured out that the base function is the one called in
> the first place.
>
> [0] https://reviews.llvm.org/D71241#1788003
>
>> 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?
>
> The latter. As mentioned above, you first find the "normal" call
> target
> and then apply the variant logic from there.
>
>
>> 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.
>
> I think your assumptions are met.
>
> Is there a good reason to make 5.1-type variants different from
> multi-versions (as we have them)? They do not depend on the lexical
> call
> context but only on the compilation parameters.
Are multi-versions yet another feature? Do they interact with this one?
John.
>
>
>>>
>>> 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
>
> --
>
> Johannes Doerfert
> Researcher
>
> Argonne National Laboratory
> Lemont, IL 60439, USA
>
> jdoerfert at anl.gov
More information about the cfe-dev
mailing list