Hello

It has been several months since I posted my WIP patch, and my current patch set (which I will post separately) has evolved considerably since then. I have added C++ and Fortran support, as well as dynamic selectors from the OpenMP 5.1 spec (currently only the 'user={condition(<expr>)}' selector is implemented, target_device is TBD).

On 26/07/2021 3:29 pm, Jakub Jelinek wrote:
Note, there is a partial overlap with the attribute syntax changes, see below.
c-family/c-omp.c now has omp_directives table that should be updated for
changes like this and then c_omp_categorize_directive that returns some
information about the directives given a directive name (though, that name
can be one, two or three tokens long, consider e.g. target enter data
or cancellation point directives).

I have modified the C/C++ parser code to lookup the type of the directive using c_omp_categorize_directive.

For metadirective, I think very special case are declarative directives in
them, I'd tend to sorry for them at least for now, I'm pretty sure many
cases with them are just unimplementable and will need to be restricted in
the standard, others can be implemented with lots of effort.
Whether it is e.g. metadirective guarding declare target ... end declare
target pair that would only conditionally set declare target and instead of
a single bit to find out if something is declare target or not we'd until
resolved need to compute it for all possibilities, or e.g. conditional
declare reduction/declare mapper where the name lookup for reduction or map
directives would be dependent on metadirective resolution later on, etc.
I'm afraid a total nightmare nobody has really thought about details for it.

The parsers currently emit a sorry if a C_OMP_DIR_DECLARATIVE directive is encountered in a metadirective, though I am sure there are many remaining ways that one could break it!

As an optimisation, identical body trees could be merged together, but that
can come later.

I'm afraid it isn't just an optimization and we need to be as smart as
possible.  I'm not sure it is possible to parse everything many times,
consider e.g. labels in the blocks, nested function definitions, variable
definitions, etc.
While OpenMP requires that essentially the code must be valid if the
metadirective is replaced by any of those mentioned directives which rules
quite some weirdo corner cases, nothing prevents e.g. two or more
when directives to be standalone directives (which don't have any body and
so whatever comes after them should be left parsed for later as normal
statement sequence), one or more to be normal constructs that accept a
structured block and one or more to be e.g. looping constructs (simd, for,
distribute, taskloop or combined versions of those).
Even when issues with labels etc. are somehow solved (e.g. for structured
blocks we have the restriction that goto, break, continue, or switch into
a case/default label, etc. can't be used to enter or exit the structured
block which could mean some cases can be handled through renaming seen
labels in all but one bodies), most important is to sync on where parsing
should continue after the metadirective.
I think it would be nice if the metadirective parsing at least made quick
analysis on what kind of bodies the directives will want and can use the new
c-omp.c infrastructure or if needed extend it (e.g. separate the 
C_OMP_DIR_CONSTRUCT
category into C_OMP_DIR_CONSTRUCT and C_OMP_DIR_LOOPING_CONSTRUCT where
the latter would be used for those that expect some omp loop after it).
One option would be then to parse the body as the most restricted construct
(looping (and determine highest needed collapse and ordered), then construct,
then standalone) and be able to adjust what we parsed into what the
different constructs need, but another option is the separate parsing of
the code after the directive multiple times, but at least in the order of
most restricted to least restricted, remember where to stop and don't parse
it multiple times at least for directives that need the same thing.


After some experimentation, I'm not sure if it is possible in the general case to share bodies between variants. For one thing, it complicates the OMP region outlining and lowering, and becomes rather invasive to implement in the parser. Another is the possibility of having metadirectives nested within metadirective bodies. e.g. Something of the form:

#pragma omp metadirective \
    when (cond1: dir1) \
    when (cond2: dir2)
  {
    #pragma omp metadirective \
      when (construct dir1: dirA)
      when (construct dir2: dirB)
        (body)
  }

in which case the way the inner metadirective is resolved depends on the outer metadirective, leading to different bodies.

In my current patch set, I have implemented a limited form of statement body sharing when the body is not part of an OMP directive (e.g. an 'omp flush' followed by the body). Variables declarations and local functions in the body are handled by the usual scoping rules, and labels are handled by declaring them as __local__ (C and C++) or by renaming (Fortran). I have also added assertions in the parsers to ensure that each variant stops parsing at the same point. Would you find this acceptable?

2) Selectors in the device set (i.e. kind, isa, arch) resolve differently
depending on whether the program is running on a target or on the host.
Since we don't keep multiple versions of a function for each target on the
host compiler, resolving metadirectives with these selectors needs to be
delayed until after LTO streaming, at which point the host or offload
compiler can make the appropriate decision.

How is this different from declare variant?  For declare variant, it is true
I'm never trying to resolve it already during parsing of the call and that
probably should be changed, do a first attempt at that point.  Initially
I thought it typically will not be possible, but later clarification and
strong desire of LLVM/ICC etc. to do everything or almost everything already
during parsing suggests that it must be doable at least in some cases.
E.g. we have restrictions that requires directive on which some decision
could be dependent must appear only lexically before it or not at all, etc.
So, similarly to that, metadirective ideally should see if something is
impossible already during parsing (dunno if it should mean we wouldn't parse
the body in that case, that would mean worse diagnostics), then repeat the
checks during gimplification like declare variant is resolved there, then
repeat again after IPA.  Would be probably best if for metadirectives that
resolve to executable directives we represent it by something like a magic
IFN that is told everything needed to decide and can share as much code as
possible with the declare variant decisions.

It is true other compilers implement offloading quite differently from GCC,
by repeating all of preprocessing, parsing etc. for the offloading target,
so they can decide some metadirective/declare variant decisions earlier than
we can.  On the other side that approach has also quite some disadvantages,
it is much harder to ensure ABI compatibility between the host and offload
code if one can use #ifdefs and whatever to change layout of everything in
between.

For the checks during parsing, we'll need a different way how to track which
directives are currently active (or defer anything with construct
selectors till gimplification).  It is true that resolving that during
parsing goes against the goal to parse as many bodies together as possible,
so we need to pick one or the other.  Parsing what follows for all
standalone directives isn't a problem of course, but if the metadirective
has one when with for and another with simd, then parsing the loop just once
would be a problem if there is metadirective in the body that wants to
decide whether it is in for or simd and wants that decision be done during
parsing.


In my current patch, I attempt to resolve metadirectives at three points - during parsing, during Gimplification, and just after LTO.

For Fortran only, I skipped the parser resolution for now - I originally wanted to reuse the code from the C/C++ front ends to resolve metadirectives when translating from the Fortran parse tree to tree form, but there are quite a few references to C-family only functions in it (it would need to be rewritten to be more frontend-neutral).

Thanks,

Kwok

Reply via email to