Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to add an executable construct stack #686

Merged
merged 5 commits into from Sep 10, 2019
Merged

Conversation

psteinfeld
Copy link
Collaborator

I added a stack of ExecutableConstruct's to SemanticsContext along with
functions to push and pop constructs. I added code to the SemanticsVisitor
to use these new functions. I also added functions Pre and Post functions
for UnlabeledStatement's so that we could isolate the source positions for
statements embedded in "if" statements to improve error messages.

I also added code to check-do.[h,cc] to use this new infrastructure to check
for CYCLE and EXIT statements that are not contained within DO constructs
along with a test.

@ichoyjx
Copy link
Collaborator

ichoyjx commented Aug 26, 2019

Could you do a quick test of the following code:

  !$omp parallel do
  do i = 1, 10
     if (i == 4) cycle
  end do
end

@@ -120,6 +121,12 @@ class SemanticsContext {
const Scope &FindScope(parser::CharBlock) const;
Scope &FindScope(parser::CharBlock);

const std::vector<const parser::ExecutableConstruct *> executables() const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is would create a copy of executables_ if not for copy elision.
Question for other reviewers: As a general rule, do we depend on such optimizations or code for non existence of such optimizations ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there is a reason to return a copy I think it should return a const reference.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can depend on RVO in C++17. That said, this routine either needs to add a & or remove a const.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll make this a reference.

@@ -647,4 +647,21 @@ void DoChecker::Leave(const parser::DoConstruct &x) {
doContext.Check(x);
}

void DoChecker::CheckLoneStmt(const char *stmtString) const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckLoStmt doesn't really say what it's doing. How about CheckInsideDO() or something like that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion. I'll make that change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that, in my latest version of the code, I added the function InsideDoConstruct() to the classSemanticsContext which supersedes this code.

@@ -20,6 +20,7 @@
#include "../evaluate/intrinsics.h"
#include "../parser/features.h"
#include "../parser/message.h"
#include "../parser/parse-tree.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only need a forward declaration of ExecutableConstruct, not all of parse-tree.h.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll change that.

@psteinfeld
Copy link
Collaborator Author

Could you do a quick test of the following code:

  !$omp parallel do
  do i = 1, 10
     if (i == 4) cycle
  end do
end

Brian, I'm not sure how to construct a complete test. Here's the complete source code for a test file that I constructed:

subroutine s1()
!$omp parallel do
do i = 1, 10
if (i == 4) cycle
end do
!$omp end
end subroutine s1

It compiles without error.

}

void SemanticsContext::PopExecutable() {
CHECK(executables_.size() > 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!empty() is what you're trying to say, I think.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll change this.

@@ -647,4 +647,21 @@ void DoChecker::Leave(const parser::DoConstruct &x) {
doContext.Check(x);
}

void DoChecker::CheckLoneStmt(const char *stmtString) const {
for (auto &executable : context_.executables()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use const auto & and make sure that executables() returns a const &.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll make those changes.

@@ -136,6 +143,7 @@ class SemanticsContext {
evaluate::FoldingContext foldingContext_{defaultKinds_};

bool CheckError(bool);
std::vector<const parser::ExecutableConstruct *> executables_;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This vector represents a stack, but neither its name nor its type mention "stack".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion. I'll change the name of the data member and accessor to executableStack.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the type?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the use case is only to lookup top of the stack then std::stack should suffice. However, if one needs to peel the layers for more information then (either now or in the future), implementing a stack using std::vector would be better, just like it is right now with ompContext_

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure that we'll need the more general capabilities of std::vector as we add more semantic checks.


private:
void CheckLoneStmt(const char *const) const;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second const here doesn't do anything.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for spotting this. I'll fix it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this function has been eliminated in the latest version of the code.

@@ -168,6 +298,24 @@ Scope &SemanticsContext::FindScope(parser::CharBlock source) {
}
}

void SemanticsContext::PushConstruct(const ConstructNode &construct) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually surprised that C++ implicitly converts the pointers at the various call sites into a ConstructNode that can be passed by reference.

This function might be more clear if it used move semantics.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to make sure that I understand your suggestion. Are you saying that, for example, the code for the Pre function for a DoConstruct should look like this?

  bool Pre(const parser::DoConstruct &doConstruct) {
    ConstructNode doNode{std::move(&doConstruct)};
    context_.PushConstruct(doNode);
    Enter(doConstruct);
    return true;
  }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that doesn't work. I meant something like context_.PushConstruct(ConstructNode{doConstruct}); in Pre() with the definition of PushConstruct accepting a ConstructNode &&construct argument.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Peter. I'll change the Pre() functions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the version in PR #708

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template suggestion doesn't have anything to do with PushConstruct or constructStack_ being templated. This PR has roughly 10 Pre() and Post() routines manually defined, functionally they all do exactly the same thing. Instead you could use a templated routine each for Pre() and Post() .

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please disregard my earlier comment. I misunderstood things.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Tim. I'll change my code to match Brian's.

}

void SemanticsContext::PopConstruct() {
CHECK(!constructStack_.empty());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This CHECK may not add any value; the pop_back() will crash if the stack is empty.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHECK has the advantage that the message coming from the compiler contains that name of the file and source line of the failing check. When pop_back() fails, the compiler just produces a segmentation fault with no indication of the location of the failure.

}

bool SemanticsContext::InsideDoConstruct() const {
for (const ConstructNode construct : constructStack_) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than looping over the entries in the stack and looking for DO loops, you could maintain a count of active DO loops.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that was my original implementation. When I reviewed that with Tim, he suggested using a stack. The stack is heavier weight, but it has the promise of being useful in other kinds of semantic analysis.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still maintain a counter.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe a cumulative ancestor set for all ancestor types, not just do loops. That is, for top level constructs, this set is empty. For nested constructs, the set is the parent's set plus the parent's type. That way, queries of the form "this construct has at least one kind X ancestor" can be answered without searching. And queries of the form "this construct is immediately nested in a kind X parent' can also be directly answered by looking one up the stack. Those two queries probably cover a large majority of all potential queries.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Val's suggestion is more efficient compared to the stack approach. However one would still have to traverse up to get a count of active DO loops. So a cumulative ancestor set + depth record for DO (and any other construct that needs its depth saved) seems like the best idea.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestions. One of the checks I plan to implement (although not in this pull request) is to semantic checking for CYCLE do-construct-name. For that case, I'll need to walk up the currently active set of nested DO constructs looking for one with do-construct-name. For just checking a lone CYCLE statement, a counter is sufficient and more efficient. Since I don't think I can get rid of the stack, and since I believe that it's sufficient for the cases of CYCLE statements with and without do-construct-names, I plan to just go with a stack. I'll keep the notion of counters in mind as I add the rest of the checks, though.

@psteinfeld
Copy link
Collaborator Author

I changed the implementation of PushConstruct() to match Brian's -- to be
a template that accepts any kind of node and do all of the work of creating the
ConstructNode and putting it on the stack.

context_.set_location(std::nullopt);
}

bool Pre(const parser::AssociateConstruct &associateConstruct) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if you could combine all of these repetitive Pre overloads. It's hard to see the differences between them, if any, and it's hard to tell which ones are paired up with Post overloads and which are not.

Copy link
Collaborator

@klausler klausler Sep 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, you could augment the default Pre() and Post() member function templates above (ca. line #60) with something like

if constexpr (common::HasMember<const N *, ConstructNode>) {
  context_.PushConstruct(node);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peter, you're a genius! I wanted to combine those calls, but I couldn't figure out how to do it.

I added a stack of ExecutableConstruct's to SemanticsContext along with
functions to push and pop constructs.  I added code to the SemanticsVisitor
to use these new functions.  I also added functions Pre and Post functions
for UnlabeledStatement's so that we could isolate the source positions for
statements embedded in "if" statements to improve error messages.

I also added code to check-do.[h,cc] to use this new infrastructure to check
for CYCLE and EXIT statements that are not contained within DO constructs
along with a test.
The most significant change is that I replaced the stack of
ExecutableConstruct's with a stack composed of ConstructNode's, each of which
is a variant of the constructs that made up the type ExecutableConstruct.  This
change allows the nodes of the stack to be extended to include the types needed
for OMP semantic checking.

I also extended the existing test to include some correct DO loops with CYCLE
and EXIT statements to test the code more completely.
I changed the interface of ```PushConstruct()``` to take an rvalue reference as its only parameter and made the construction of the ```ConstructNode```s explicit in all of the ```Pre()``` functions for the various construct types.
creation of the ```ConstructNode``` into ```PushConstruct()```.
…s that

call `PushConstruct()``` and ```PopConstruct()``` following a genius
suggestion from Peter.
@psteinfeld
Copy link
Collaborator Author

I combined all of the functions that called PushConstruct() and PopConstruct() based on a suggestion from Peter.

@psteinfeld psteinfeld merged commit 2fc28c7 into master Sep 10, 2019
@psteinfeld psteinfeld deleted the ps-stmt-stack branch September 10, 2019 22:27
swift-ci pushed a commit to apple/llvm-project that referenced this pull request Apr 9, 2020
I added a stack of ExecutableConstruct's to SemanticsContext along with
functions to push and pop constructs.  I added code to the SemanticsVisitor
to use these new functions.  I also added functions Pre and Post functions
for UnlabeledStatement's so that we could isolate the source positions for
statements embedded in "if" statements to improve error messages.

I also added code to check-do.[h,cc] to use this new infrastructure to check
for CYCLE and EXIT statements that are not contained within DO constructs
along with a test.

Original-commit: flang-compiler/f18@b8370bd
Reviewed-on: flang-compiler/f18#686
Tree-same-pre-rewrite: false
swift-ci pushed a commit to apple/llvm-project that referenced this pull request Apr 9, 2020
The most significant change is that I replaced the stack of
ExecutableConstruct's with a stack composed of ConstructNode's, each of which
is a variant of the constructs that made up the type ExecutableConstruct.  This
change allows the nodes of the stack to be extended to include the types needed
for OMP semantic checking.

I also extended the existing test to include some correct DO loops with CYCLE
and EXIT statements to test the code more completely.

Original-commit: flang-compiler/f18@d26f34e
Reviewed-on: flang-compiler/f18#686
Tree-same-pre-rewrite: false
swift-ci pushed a commit to apple/llvm-project that referenced this pull request Apr 9, 2020
I changed the interface of ```PushConstruct()``` to take an rvalue reference as its only parameter and made the construction of the ```ConstructNode```s explicit in all of the ```Pre()``` functions for the various construct types.

Original-commit: flang-compiler/f18@f8be813
Reviewed-on: flang-compiler/f18#686
Tree-same-pre-rewrite: false
swift-ci pushed a commit to apple/llvm-project that referenced this pull request Apr 9, 2020
…he implicit

creation of the ```ConstructNode``` into ```PushConstruct()```.

Original-commit: flang-compiler/f18@3984566
Reviewed-on: flang-compiler/f18#686
Tree-same-pre-rewrite: false
swift-ci pushed a commit to apple/llvm-project that referenced this pull request Apr 9, 2020
…functions that

call `PushConstruct()``` and ```PopConstruct()``` following a genius
suggestion from Peter.

Original-commit: flang-compiler/f18@be2a03e
Reviewed-on: flang-compiler/f18#686
swift-ci pushed a commit to apple/llvm-project that referenced this pull request Apr 9, 2020
…/ps-stmt-stack

Changes to add an executable construct stack

Original-commit: flang-compiler/f18@2fc28c7
Reviewed-on: flang-compiler/f18#686
mem-frob pushed a commit to draperlaboratory/hope-llvm-project that referenced this pull request Oct 7, 2022
I added a stack of ExecutableConstruct's to SemanticsContext along with
functions to push and pop constructs.  I added code to the SemanticsVisitor
to use these new functions.  I also added functions Pre and Post functions
for UnlabeledStatement's so that we could isolate the source positions for
statements embedded in "if" statements to improve error messages.

I also added code to check-do.[h,cc] to use this new infrastructure to check
for CYCLE and EXIT statements that are not contained within DO constructs
along with a test.

Original-commit: flang-compiler/f18@b8370bd
Reviewed-on: flang-compiler/f18#686
Tree-same-pre-rewrite: false
mem-frob pushed a commit to draperlaboratory/hope-llvm-project that referenced this pull request Oct 7, 2022
The most significant change is that I replaced the stack of
ExecutableConstruct's with a stack composed of ConstructNode's, each of which
is a variant of the constructs that made up the type ExecutableConstruct.  This
change allows the nodes of the stack to be extended to include the types needed
for OMP semantic checking.

I also extended the existing test to include some correct DO loops with CYCLE
and EXIT statements to test the code more completely.

Original-commit: flang-compiler/f18@d26f34e
Reviewed-on: flang-compiler/f18#686
Tree-same-pre-rewrite: false
mem-frob pushed a commit to draperlaboratory/hope-llvm-project that referenced this pull request Oct 7, 2022
I changed the interface of ```PushConstruct()``` to take an rvalue reference as its only parameter and made the construction of the ```ConstructNode```s explicit in all of the ```Pre()``` functions for the various construct types.

Original-commit: flang-compiler/f18@f8be813
Reviewed-on: flang-compiler/f18#686
Tree-same-pre-rewrite: false
mem-frob pushed a commit to draperlaboratory/hope-llvm-project that referenced this pull request Oct 7, 2022
…he implicit

creation of the ```ConstructNode``` into ```PushConstruct()```.

Original-commit: flang-compiler/f18@3984566
Reviewed-on: flang-compiler/f18#686
Tree-same-pre-rewrite: false
mem-frob pushed a commit to draperlaboratory/hope-llvm-project that referenced this pull request Oct 7, 2022
…functions that

call `PushConstruct()``` and ```PopConstruct()``` following a genius
suggestion from Peter.

Original-commit: flang-compiler/f18@be2a03e
Reviewed-on: flang-compiler/f18#686
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants