Skip to content

[WIP] Visitor for AccessedStorage use-def chain walk #26896

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

Merged
merged 2 commits into from
Aug 30, 2019

Conversation

jckarter
Copy link
Contributor

@jckarter jckarter commented Aug 28, 2019

Factor a visitor out of findAccessedStorage to let other clients of the logic have more control over what's done with the information. Use the visitor in the isWrittenTo analysis to extract the exact begin_borrow that an access went through in order to do the linear lifetime analysis on let loads.

@jckarter jckarter requested a review from atrick August 28, 2019 19:10
@jckarter jckarter force-pushed the accessed-storage-chain-visitor branch from 08af45a to 127198b Compare August 29, 2019 17:44
@jckarter jckarter requested a review from gottesmm August 29, 2019 17:46
@jckarter jckarter marked this pull request as ready for review August 29, 2019 17:46
@jckarter
Copy link
Contributor Author

@swift-ci Please benchmark

@jckarter
Copy link
Contributor Author

@swift-ci Please test

Copy link
Contributor

@gottesmm gottesmm left a comment

Choose a reason for hiding this comment

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

Two quick comments. I want to go through this with a finer tooth comb again.

@@ -406,6 +402,40 @@ template <> struct DenseMapInfo<swift::AccessedStorage> {

namespace swift {

/// Abstract base class for a visitor passed to \c visitAccessUseDefChain.
class AccessUseDefChainVisitor {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use CRTP and make visitAccessUseDefChain a static method?

// TODO-NEXT: end_borrow [[INNER]]
// TODO-NEXT: end_borrow [[OUTER]]
// TODO-NEXT: destroy_value
// CHECK: [[OUTER:%.*]] = begin_borrow
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this expand the functionality of the pass in some way? If so, can you do that in a different PR? My hope is that this one can be just a simple refactoring so I can eye-ball it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The refactoring part is in 918282e6a80619e072be6b1d8f186f7ab38926e3, and the SemanticARCOpts bit in the next commit, if you want to review them separately.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah ok. I didn't notice it was in a separate commit.

Copy link
Contributor

@atrick atrick left a comment

Choose a reason for hiding this comment

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

It might be better to split AccessedStorage into an AccessedStorageKind interface with the uint_8 enum and all the kind-related queries.

SILValue operAddr = cast<LoadInst>(sourceAddr)->getOperand();
assert(isa<UncheckedTakeEnumDataAddrInst>(operAddr));
return AccessedStorageResult::incomplete(operAddr);
class AccessUseDefChainVisitor {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this needs to be a template parameter. findAccessedStorage[NonNested] is the critical path for several expensive passes. Every time I debugged a compile time issue with exclusivity enabled, all the time was spent in getAccessedStorageFromAddress. I wouldn't don't mind keeping it in the .cpp and explicitly specializing for the few visitors, but that could mean including SILOptimizer headers from within SIL.

The template could be instantiated once for getAccessedStorageFromAddress and a second time for a virtual visitor, but maybe it should just be fast everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we could make the implementation of visitAccessUseDefChain always_inline within MemAccessUtils.cpp, so that the visitor can be devirtualized.

phiArg->getIncomingPhiValues(addressWorklist);
continue;

class FindAccessedStorageVisitor : public AccessUseDefChainVisitor {
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need the worklist in the outer function anymore. If you move the class into an anonymous namespace, then it would still be possible to read the outer function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, I forgot to delete the now-unused vars from the outer scope. What do you mean by " it would still be possible to read the outer function". At top level, the class would still need to declare its own member variables for the state, unless I'm missing something.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is just my personal bias toward function scopes that fit on screen. I can read this:

namespace {
class Utility {
  State state;
  visitStuff1() {}
  visitStuff2() {}
};
} // namespace

void myAPI() {
  // has an implementation that fits on a screen
  return Utility().doIt()
}

I can't read this:

void myAPI() {
  class {
   // blah blah for more than a screen
  }
  // who does this code belong too a method of the above class or some outer function?
  return stuff
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I thought you mean "read" in a language semantics sense. OTOH, scoping the nested class helps indicate that there are no uses of the class outside of the one function, and there's only one line of code outside of the class in this case.

@swift-ci
Copy link
Contributor

Performance: -O

Code size: -O

Performance: -Osize

Improvement OLD NEW DELTA RATIO
PrefixCountableRangeLazy 17 15 -11.8% 1.13x (?)

Code size: -Osize

Performance: -Onone

Code size: -swiftlibs

How to read the data The tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.

If you see any unexpected regressions, you should consider fixing the
regressions before you merge the PR.

Noise: Sometimes the performance results (not code size!) contain false
alarms. Unexpected regressions which are marked with '(?)' are probably noise.
If you see regressions which you cannot explain you can try to run the
benchmarks again. If regressions still show up, please consult with the
performance team (@eeckstein).

Hardware Overview
  Model Name: Mac mini
  Model Identifier: Macmini8,1
  Processor Name: Intel Core i7
  Processor Speed: 3.2 GHz
  Number of Processors: 1
  Total Number of Cores: 6
  L2 Cache (per Core): 256 KB
  L3 Cache: 12 MB
  Memory: 64 GB

@jckarter
Copy link
Contributor Author

It might be better to split AccessedStorage into an AccessedStorageKind interface with the uint_8 enum and all the kind-related queries.

@atrick Could you clarify what you mean?

@atrick
Copy link
Contributor

atrick commented Aug 29, 2019

When I wrote the comment about AccessedStorageKind, you still had a visitIdentified. Which might actually be what we want most of the time. It's nice to be able to write a small covered switch rather than override a bunch of visitors--and only override a visitor when you need to specially handle borrows for example. Here's a quick strawman, not sure it makes sense without trying to implement it:

class AccessedStorage {
  enum Kind { ...}
  static classify(SILValue)
  Kind kind
  SILValue base
  bool isLocal()
  isUniquelyIdentified()
//etc.
}

class AccessedStorageKey {
  AccessedStorage::Kind kind
  int64 opaqueBits
  union { SILValue, Global }
  getArgument()
  getObject()
  bool hasIdenticalBase()
  ...
}

AccessedStorage findAccessedStorage<Visitor>(SILValue) {...}

AccessedStorageKey findAccessedStorageKey(SILValue V) {
  return AccessedStorageKey(findAccessedStorage<RecurseThroughPhisAndStrongAssertions>(V))
}

@jckarter
Copy link
Contributor Author

Hm. I split out the visitor methods because IMO it helped make clear the almost 1:1 type relationship between access kinds and the instruction types they correspond to—that was lost on me when I was first looking at this code—and it cleaned up a bunch of casts that were necessary in the SemanticARCOpts code before I broke out the methods. If we're going to CRTP this, I could make it so that the individual access methods all have default implementations that call a common visitIdentified method, which I think would get the result you want—in the common case, you only need to implement one method, and you only need to implement the more specific methods you actually need.

The same logic for looking through projection paths is useful elsewhere.
By intercepting the walk that the findAccessedStorage walker does, we can find the exact borrow
that appeared along a certain access, so we don't need to limit ourselves to a singly-borrowed
base.
@jckarter jckarter force-pushed the accessed-storage-chain-visitor branch from 127198b to 97d15f6 Compare August 29, 2019 23:08
@jckarter
Copy link
Contributor Author

@swift-ci Please test

@jckarter
Copy link
Contributor Author

@atrick @gottesmm OK, I CRTP-ized the visitor, and routed the methods through a visitBase common method by default.

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 127198b81132f3045fb859ab15d889a2114a8e3d

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 127198b81132f3045fb859ab15d889a2114a8e3d

@atrick
Copy link
Contributor

atrick commented Aug 30, 2019

@jckarter this looks really nice! Yes, visitors should have typed cases--that's the only time I like the visitor pattern. The user can just construct an AccessedStorage from the identified base to get all the benefits I was looking for. I was trying to micro-optimize by classifying AccessedStorage first but this is cleaner and probably just as fast.

@jckarter jckarter merged commit c0ec48c into swiftlang:master Aug 30, 2019
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