This is an archive of the discontinued LLVM Phabricator instance.

[OpenMP5.0] Support more kinds of lvalues in map clauses
Needs ReviewPublic

Authored by jacobdweightman on Nov 12 2020, 11:50 AM.

Details

Reviewers
ABataev
jdoerfert
Summary

This patch aims to fully support this change from OpenMP 4.5 to 5.0:

The to and from clauses on the target update construct (see Section 2.12.6 on page 176), the depend clause on task generating constructs (see Section 2.17.11 on page 255), and the map clause (see Section 2.19.7.1 on page 315) were extended to allow any lvalue expression as a list item for C/C++.

Specifically, support is added for:

  • calling functions which return references/constexprs
  • cast expressions as part of a larger lvalue expression
  • expressions with the ternary Elvis operators (A ? B : C and A ?: C)

This patch modifies Sema to accept such expressions. A few small changes in CodeGen were required to avoid asserts, but CodeGen appeared to already handle such expressions correctly.

Diff Detail

Event Timeline

Herald added a project: Restricted Project. · View Herald TranscriptNov 12 2020, 11:50 AM
jacobdweightman requested review of this revision.Nov 12 2020, 11:50 AM
clang/lib/Sema/SemaOpenMP.cpp
17141

It looks like this short-circuits and the false expression doesn't get visited if the true expression is "well-formed." We probably want to emit diagnostics and fail if either expression is bad (i.e. visit true expression && visit false expression), but that results in an incorrect component list.

I suspect this may indicate a deeper issue, since I'm not sure what the components should actually be. It seems like in general it should be a tree-like structure rather than a list-like structure, but that seems like it may require significant changes. Any thoughts on this or suggestions for improvement?

Ping

  1. Provide full diff, see https://www.llvm.org/docs/Phabricator.html for the guidance.
  2. Need some more test with ast printing and codegen.
  3. Would be good if you could run (at least locally) some tests to check that the changes really work.

Thanks for the initial feedback, @ABataev!

I've added one code gen test each for function calls, cast expressions, and ternaries. I haven't added any AST printing tests because I didn't think I changed anything about the AST, but please let me know what needs to be tested and I'll add it. Let me know if I need to be more thorough with the codegen tests as well!

I created this diff according to your instructions and now see that it includes the modified files in their entirety.

Prior to the first revision, I did run some test programs to verify that the changes work (these still work, since I haven't changed anything outside of tests for this revision). I had a test that looked like this for each of the three kinds of lvalue expressions this diff attempts to support:

#include <assert.h>

int main(void) {
    // test with map
    #pragma omp target data map(tofrom: <lvalue expression>)
    {
        <type of lvalue expression> xp = <lvalue expression>

        #pragma omp target
        <some mutation to lvalue expression>
    }

    assert(<mutation is reflected on host>);


    // test with update to/from
    int y;
    #pragma omp target data map(alloc: y) // we need a clause here, so do something useless
    {
        auto xp = <lvalue expression>;

        #pragma omp target update to(<lvalue expression>)

        #pragma omp target
        <some mutation to lvalue expression>

        #pragma omp target update from(<lvalue expression>)
        assert(<mutation is reflected on host>);
    }
}
ABataev added inline comments.Nov 26 2020, 9:52 AM
clang/lib/Sema/SemaOpenMP.cpp
16869

I don't think it should always return true. What about map(s.foo) where foo is a member function?

16939

Same, too general check, plus in some cases OVE->getSourceExpr() may return nullptr.

17065–17069

What is this for?

17110–17119
int a;
int &foo() {return a;}

...
#pragma omp target map(foo())
 foo() = 0;
...

How is this supposed to work?

17120–17139

Same questions here, how's the actual mapping is supposed to work? Need some more detailed description. I don't think this is going to be easy to implement it directly. We map the addresses of the base declarations but in your cases there is just no base declarations. SO, you need to create one. To me, this should look like this:

#pragma omp target map(<lvalue>)
{
  <lvalue> = xxx;
  yyy = <lvalue>;
}

V

auto &mapped = <lvalue>;
#pragma omp target map(mapped)
{
  mapped = xxx;
  yyy = mapped;
}
jacobdweightman added inline comments.Dec 2 2020, 2:16 PM
clang/lib/Sema/SemaOpenMP.cpp
16869

Hmm... I had previously added a test covering this on line 416 of target_map_messages.cpp which seemed to be passing, but not for the reason I thought. This program illustrates the difference:

struct Foo {
    int x;
    int &id() {
        return x;
    }
};

int x;
int &id() {
    return x;
}

int main(void) {
    Foo f;
    #pragma omp target map(tofrom: id, f.id)
    {}
}

The free function is parsed in bool Parser::ParseOpenMPVarList to this:

DeclRefExpr 0xfee3f8 'int &(void)' lvalue Function 0xfedc10 'id' 'int &(void)'

Whereas the method is parsed to this:

MemberExpr 0xfee438 '<bound member function type>' .id 0xfeda00
`-DeclRefExpr 0xfee418 'struct Foo' lvalue Var 0xfede98 'f' 'struct Foo'

Note that the former is an lvalue, whereas the latter is not. Therefore, the latter emits the error "early" inside of void checkMappableExpressionList due to the !RE->isLValue() check near SemaOpenMP.cpp:17668 rather than in the Visit* methods. I guess the current error message is misleading given that id is still an lvalue, though. Perhaps it would be good to add a new message specifically for free functions since they are lvalues, but that issue already exists in Clang today and feels out of scope for this change. If you disagree, I wouldn't mind adding it.

16939

I'm not exactly sure in what sense this check is too general, but perhaps it would be better to handle this together with the Elvis operator. For instance, I could have a VisitBinaryConditionalOperator which would "unwrap" the OpaqueValueExpression (OVA) directly rather than calling through to this method, since this is the only context in which we expect to handle OVAs. Let me know if this doesn't fully address your concern, though.

However, this usage of getSourceExpr appears to be consistent with other uses of OVAs that I see in Clang (I see a few similar examples in the static analyzer), but you make a good point. The Elvis operator's OVA should always have a source expression which refers to the condition without the implicit cast to bool. I'll add an assert that it isn't nullptr for now, but let me know if you have any other ideas.

17065–17069

This is a bit of a hack and definitely unclear to the reader. The Components.empty() check was added because an rvalue or a unary operator other than the de-referencing operator should be allowed in a sub-expression of the map clause list item, so long as the complete expression is an lvalue. As a minimal example, consider something like map(*(&x)). More usefully, one may cast pointer types so that a variable is mapped as a different type like map(*((int *) &x)). Without this check, the new casting tests which do this would fail.

A few ideas to make this more readable would be to use a temporary variable before the if statement like bool isFullExpression = Components.empty(); or add a comment explaining the above. Do you have any other ideas for how to improve this?

17110–17119

From my understanding of the spec, foo should be implicitly declared for both the host and the target. However, the user would be responsible for explicitly declaring a for the target if it isn't referenced in the target region. This test program seems to behave as I expect, with the result that a = 2:

#include <stdio.h>

int a;
#pragma omp declare target to(a)

int &foo() { return a; }

int main(void) {
    a = 1;

    #pragma omp target map(foo())
    foo() = 2;

    #pragma omp target update from(foo())

    printf("a = %d\n", a);
}
17120–17139

I think there's an issue with that source-level transformation when evaluating <lvalue> has side effects, since they would be performed 3 times in the first program but only once in the second program.

I might be off base here, but would there also be an issue if <lvalue> might share original storage with another list item? For example, shouldn't we issue an error when compiling the following program?

int a[10], b[10];
bool c;
#pragma omp target map(a, c ? a[1] : b[1])
{ ... }

If I'm missing something here, how would we go about implementing a transformation like that? Is it something that belongs in codegen? I think I need a bit more guidance here.

I separated the handling of ConditionalOperators and BinaryConditionalOperators in order to eliminate handling of OpaqueValueExpressions in general, and asserted that the OVE's SourceExpr is not null. I also made some minor changes to improve readability.

Restore original formatting in test cases that were not directly affected by the patch. Also, I would start with a single kind of expression rather than trying to cover as many kinds of expressions as possible. It makes it easier to understand and to review it.

clang/lib/Sema/SemaOpenMP.cpp
16869

The mapping of functions is meaningless here, if they should return references to the globals and these globals should be marked as declare target

17065–17069

I would say that something like map(*((int *) &x)) should not be allowed. Something like map(*p) - yes, but not something that changes the original type.

17110–17119

Just like I said before, then mapping of the function is absolutely meaningless, we can just ignore it.

17120–17139

I assume side effects are not allowed throughout the standard though it is not explicitly expressed for the map clauses.
Plus, the fact that something is not allowed does not mean that the compiler should diagnose it. In general, it is just impossible, just in some cases.

17150–17151

&&?

17343

NextComponentExpr

17346

ShouldCheckForOverlap

17350

What if MemberExpr is a base of array section, subscript etc.?

clang/test/OpenMP/target_map_messages.cpp
95–114

Restore original formatting

191–208

Same here, remove unrelated changes