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

Use workaround to enable lifetime checking for lists of borrowed classes #15575

Merged

Conversation

dlongnecke-cray
Copy link
Contributor

@dlongnecke-cray dlongnecke-cray commented Apr 23, 2020

This PR adds a small workaround to the List module that "encourages" the compiler to perform lifetime checking for methods such as list.append().

This workaround is required because lists use _ddata for their backing store instead of an array. When it comes time for the compiler to evaluate lifetime clauses, a lifetime clause is only considered for evaluation if both formals in the clause are or refer to a borrowed class (the relevant function in compiler/resolution/lifetime.cpp is isOrRefersBorrowedClass). The _ddata type is marked as unsafe by the compiler, and is skipped. Because of this the compiler is unable to infer that a list(borrowed C) contains a borrowed class, and lifetime clauses with the list as a receiver such as this < x are not evaluated.

To resolve this issue, I added a dummy field to the list type. If the eltType of a list is a borrowed class, this field will have the type eltType? (a nilable eltType) and is default initialized to nil. Otherwise this field has the type nothing and is folded away.


Testing:

  • ALL on linux64 when CHPL_COMM=none
  • ALL on linux64 when CHPL_COMM=gasnet

@dlongnecke-cray dlongnecke-cray self-assigned this Apr 23, 2020
bool bothFormalsContainBorrowedOrAreBorrowed = false;

if (isOrRefersBorrowedClass(formal1->getValType())) {
bothFormalsContainBorrowedOrAreBorrowed = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nothing's going to set this to false when formal2 isn't borrowed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point!

@dlongnecke-cray dlongnecke-cray requested a review from mppf April 27, 2020 23:42
@dlongnecke-cray
Copy link
Contributor Author

Hi @mppf, would you mind reviewing this? I added debugging info I found helpful but let me know if that is too much for you.

Copy link
Member

@mppf mppf left a comment

Choose a reason for hiding this comment

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

It looks good but I'd like you to remove the debug code. Most of that information is stuff I would gather from the debugger and tracing the checking of a particular call. It's enough code that I worry about it making the control flow of the program logic harder to see.

if (isResolved() && _this != NULL) {
return toAggregateType(_this->getValType());
} else {
if (numFormals() >= 2) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's fold else { if { into else if {

@dlongnecke-cray
Copy link
Contributor Author

Thought so. I'll go ahead and remove the debugging code. I'm not sure how to step through in the way you do with a debugger yet, I'll have to practice.

@dlongnecke-cray dlongnecke-cray merged commit fd8a023 into chapel-lang:master Apr 28, 2020
dlongnecke-cray added a commit that referenced this pull request Apr 29, 2020
Adjust list futures affected by merge of 15575 (#15608)

This PR adjusts list lifetime checker futures that were not addressed
by #15575. Some have been changed to normals tests. The test
`listAppendBorrow` was broken up into two smaller, more concise
tests.

---

Testing:

- [x] Adjusted tests on `linux64` when `CHPL_COMM=none`

---

Reviewed by @ben-albrecht.
for x in iterable do
append(x);

for x in iterable do {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ronawho So after profiling this PR this is trivially the reason comm counts changed in test/library/packages/Sort/performance/dist-performance.chpl. The only thing I don't understand is why. Do you think it's worth trying to figure out why this diff caused comm counts to change so drastically? Or should I just revert it and call it a day?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you remember the motivation for this change? I don't think I understand what's going on here well enough to have an opinion.

Copy link
Contributor Author

@dlongnecke-cray dlongnecke-cray Jun 25, 2020

Choose a reason for hiding this comment

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

My rationale for this change was that since _commonInitFromIterable is only called in an initializer, I might as well write the loop using internal methods to avoid taking a lock (list.append may take a lock if parSafe=true).

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. That makes sense, though I still don't grok the "no auto destroy"s. Can we make an internal version of append that doesn't take the lock?

    proc ref _appendNoLock(pragma "no auto destroy" in x: this.eltType) lifetime this < x {
      _appendByRef(x);
    }

Or does that still have the comm issue?

If that still has the problem, I'd probably just revert and keep the lock. That lock has very little overhead when used uncontested/serially

Copy link
Contributor Author

@dlongnecke-cray dlongnecke-cray Jun 25, 2020

Choose a reason for hiding this comment

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

The pragma "no auto destroy" exist because the list assumes ownership of the lifetime of the element. It controls when the element is destroyed as long as it contains it. (The element is moved into place instead of assigned, so only one deep copy is made.)

I can try _appendNoLock but honestly it's probably not worth adding a whole new method, as you say the overhead is not much when uncontested.

I was just curious about whether or not this is worth investigating (as a separate issue or something), as to me it doesn't really seem like the changes should cause such a massive increase in GETs. But maybe it is trivially obvious to you why GETs would increase?

Copy link
Member

Choose a reason for hiding this comment

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

I suspect that the pragma code confuses the compile enough that it doesn't remove a copy for the case that the iterator is returning by value. You could check the AST logs around after pass 20 callDestructors for differences to see if that's the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Awesome! I'll give that a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No luck, seems more or less the same sans an initCopy for the formal tmp in the append version vs an init= for the variable in the _appendByRef version. Iterator was returning refs.

dlongnecke-cray added a commit to dlongnecke-cray/chapel that referenced this pull request Jun 25, 2020
dlongnecke-cray added a commit that referenced this pull request Jun 26, 2020
Revert changes to `list._commonInitFromIterable` (#15943)

In #15575 I decided to modify `list._commonInitFromIterable` to
append elements without taking a lock. Such a small optimization
seemed reasonable since that method is only called by initializers.

The change ended up causing a fairly large increase (30270 -> 34410)
in GETs for `library/packages/Sort/performance/dist-performance`,
so revert it.

Add a lifetime constraint to `_commonInitFromIterable` so that old
code can function correctly.

---

Testing:

- [x] ALL on linux64 when CHPL_COMM=none

---

Reviewed by @ronawho. Thanks!
rahulghangas pushed a commit to rahulghangas/chapel that referenced this pull request Jul 22, 2020
Revert changes to `list._commonInitFromIterable` (chapel-lang#15943)

In chapel-lang#15575 I decided to modify `list._commonInitFromIterable` to
append elements without taking a lock. Such a small optimization
seemed reasonable since that method is only called by initializers.

The change ended up causing a fairly large increase (30270 -> 34410)
in GETs for `library/packages/Sort/performance/dist-performance`,
so revert it.

Add a lifetime constraint to `_commonInitFromIterable` so that old
code can function correctly.

---

Testing:

- [x] ALL on linux64 when CHPL_COMM=none

---

Reviewed by @ronawho. Thanks!
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.

4 participants