Skip to content

SILOptimizer: use BasicBlockSet instead of SmallPtrSet in various transformations. #35583

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 8 commits into from
Jan 27, 2021

Conversation

eeckstein
Copy link
Contributor

BasicBlockSet is more efficient than a SmallPtrSet<SILBasicBlock *>. This change reduces the number of memory allocations and therefore compile time.

@eeckstein
Copy link
Contributor Author

@swift-ci test

@eeckstein eeckstein requested a review from gottesmm January 25, 2021 12:45
@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 663821dc4088f495e75b21c8393460ad5f71c4bf

@eeckstein
Copy link
Contributor Author

@swift-ci smoke test macOS

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 663821dc4088f495e75b21c8393460ad5f71c4bf

@eeckstein
Copy link
Contributor Author

@swift-ci test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 663821dc4088f495e75b21c8393460ad5f71c4bf

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 663821dc4088f495e75b21c8393460ad5f71c4bf

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.

Some questions/feedback. Once this compiles, I'll do another look though.

///
/// Note: This class does not provide a `remove` method intentinally, because
/// it would have a O(n) complexity.
template <unsigned N> class BasicBlockSetVector {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you change the name of this to reflect that one can only add and can never remove? The name suggests it is a general utility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I like this name. It describes best what this class is.
I even think that there shouldn't be a remove method in llvm:SetVector. It is O(n), but people use it as if it was O(0).

Copy link
Contributor

Choose a reason for hiding this comment

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

@eeckstein hmmm... maybe the right thing to do is to make this a blot set vector then? Blot set vectors do not have this issue and are a generic algorithm.

/// The set of blocks where we begin our walk.
BasicBlockSet initialBlocks(function);

/// The set of blocks where we begin our walk.
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 I typed this. Can you fix the comment?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, why is isLeakingBlock a BasicBlockFlag? I would assume that this would be a BasicBlockSet? Can you elaborate on when I should use one or the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's just about naming. BasicBlockFlag is the same as BasicBlockSet, just with a different API. For something like isLeakingBlock it's natural to call it a "flag". For something like visitedBlocks, it's natural to call it a "set".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you fix the comment?

sure

unsigned Inv = InvalidationKind::Instructions;
if (dce.mustInvalidateCalls())
Inv |= (unsigned)InvalidationKind::Calls;
if (dce.mustInavlidateBranches()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo.

SmallVector<SILBasicBlock *, 16> ExitingBlocks;
Loop->getExitingBlocks(ExitingBlocks);
CachedExitingBlocks.insert(ExitingBlocks.begin(), ExitingBlocks.end());
Loop->getExitingBlocks(CachedExitingBlocks);
Copy link
Contributor

Choose a reason for hiding this comment

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

Question. Do you know why we used a SmallPtrSet here? Also, I imagine that this can be returned as an ArrayRef.

Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't thought about it, I am just double checking, I don't think our loop can have multiple of the same blocks passed here, right? Maybe we need an assert to that effect? Your thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you know why we used a SmallPtrSet here?

I think this was just a bug.

I imagine that this can be returned as an ArrayRef.

true. I'll change it.

I don't think our loop can have multiple of the same blocks passed here, right?

yes

Maybe we need an assert to that effect? Your thoughts?

I think an assert is not needed (it would be not trivial).

Copy link
Contributor

Choose a reason for hiding this comment

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

I actually think the assert wouldn't be too difficult (consider a copy + sortUnique + ensure nothing removed), that being said I think such an assert would be something for a lower part of the API.


/// The set of blocks in which we reported unreachable code errors.
/// These are used to ensure that we don't issue duplicate reports.
///
/// Note, this set is different from the PossiblyUnreachableBlocks as these
/// are the blocks that do contain user code and they might not be immediate
/// successors of a folded branch.
llvm::SmallPtrSet<const SILBasicBlock*, 2> BlocksWithErrors;
llvm::SmallPtrSet<SILBasicBlock*, 2> BlocksWithErrors;
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like maybe you forgot to finish fixing this part? Shouldn't this be a BasicBlockSet?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. I changed it

@@ -1402,7 +1403,7 @@ static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
// formatting and privacy options are literals, all candidate instructions
// must be in the same basic block. But, this code doesn't rely on that
// assumption.
SmallPtrSet<SILBasicBlock *, 4> candidateBBs;
BasicBlockSetVector<4> candidateBBs(oslogInit->getFunction());
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this specific one a BasicBlockSetVector instead of just a BasicBlockSet?

Copy link
Contributor Author

@eeckstein eeckstein Jan 26, 2021

Choose a reason for hiding this comment

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

Because size() and begin() is used. But I could rewrite the code a bit so that BasicBlockSet can be used.

worklist.push_back(predBlock);
}
}

// After our worklist has emptied, any blocks left in
// blocksThatLeakIfNeverVisited are "leaking blocks".
for (auto *leakingBlock : blocksThatLeakIfNeverVisited)
foundJointPostDomSetCompletionBlocks(leakingBlock);
for (auto *leakingBlock : blocksThatLeakIfNeverVisited) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is sufficient. How do you handle if we have the same successor block that must be visited for two different blocks. Then we would call foundJointPostDomSetCompletion with the same block twice which breaks an API guarantee.

Copy link
Contributor Author

@eeckstein eeckstein Jan 26, 2021

Choose a reason for hiding this comment

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

you are right, good catch!
I fixed it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you fix the comment here? I think the comment needs to be updated given functional changes.

@eeckstein
Copy link
Contributor Author

@swift-ci test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 6d12a35209881883d03254aadbe4c52c0404cb64

@eeckstein
Copy link
Contributor Author

@swift-ci smoke test macOS

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.

Closer.

DeadEndBlocks &deadEndBlocks) const {
// Make sure that we clear our scratch space/utilities before we exit.
SWIFT_DEFER {
scratchSpace.clear();
visitedBlocks.clear();
};
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Extra whitespace.

///
/// Note: This class does not provide a `remove` method intentinally, because
/// it would have a O(n) complexity.
template <unsigned N> class BasicBlockSetVector {
Copy link
Contributor

Choose a reason for hiding this comment

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

@eeckstein hmmm... maybe the right thing to do is to make this a blot set vector then? Blot set vectors do not have this issue and are a generic algorithm.

@@ -199,7 +199,7 @@ class SILFunction

/// A monotonically increasing ID which is incremented whenever a
/// BasicBlockBitfield is constructed.
/// Usually this stays below 1000, so a 32-bit unsigned is more than
/// Usually this stays below 100000, so a 32-bit unsigned is more than
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have an assert so in asserts builds we catch this if anyone breaks it? Or maybe you already have one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is already an assert in the BasicBlockBitfield constructor

worklist.push_back(predBlock);
}
}

// After our worklist has emptied, any blocks left in
// blocksThatLeakIfNeverVisited are "leaking blocks".
for (auto *leakingBlock : blocksThatLeakIfNeverVisited)
foundJointPostDomSetCompletionBlocks(leakingBlock);
for (auto *leakingBlock : blocksThatLeakIfNeverVisited) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you fix the comment here? I think the comment needs to be updated given functional changes.

BasicBlockSet initialBlocks(function);

/// True for blocks which are in blocksThatLeakIfNeverVisited.
BasicBlockFlag isLeakingBlock(function);
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that we are no longer removing elements from blocksThatLeakIfNeverVisited, why not change that to be a BasicBlockSetVector? Then we can eliminate this set and can just use blocksThatLeakIfNeverVisited, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Erik and I spoke about this and he mentioned that the reason why he didn't do that is he was worried about algorithmic changes in how the algorithm worked in this case. I am going to think about that and if we change this it will be in a follow on commit.

@@ -450,7 +415,8 @@ void DCE::propagateLiveness(SILInstruction *I) {
SILBasicBlock *DCE::nearestUsefulPostDominator(SILBasicBlock *Block) {
// Find the nearest post-dominator that has useful instructions.
auto *PostDomNode = PDT->getNode(Block)->getIDom();
while (PostDomNode && !LiveBlocks.count(PostDomNode->getBlock()))
while (PostDomNode && (!PostDomNode->getBlock() ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this changed? I am not saying it is incorrect. It just needs a comment at least explaining what the change is.

for (auto *predBB :
llvm::breadth_first<llvm::Inverse<SILBasicBlock *>>(yieldPred)) {
if (visitedBBs.count(predBB)) {
if (visitedBBs[predBB].isVisited()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

One thing that I am noticing in this change is that a bunch of const code is being replaced with non-const code like this. My worry is that we are relying on BasicBlockData to initialize data for all blocks even if we are only going to visit a few.

That could change the algorithmic behavior to change, no? Is it possible to make BasicBlockData initialize by default in a lazy way? That might help. But I am still worried about mallocing for all blocks in a function. @eeckstein your thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

I talked with eeckstein offline and he said that he only changed to use BasicBlockData for cases where we have to visit the whole CFG, so I'm cool with this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In case of YieldOnceCheck the "visitedBBs" data is only created once a run of the pass. And BasicBlockData should only be used for such cases.
Yes, we could make an extended version of BasicBlockData which lazily constructs data (by using it together with BasicBlockFlag). That's future work.

SmallVector<SILBasicBlock *, 16> ExitingBlocks;
Loop->getExitingBlocks(ExitingBlocks);
CachedExitingBlocks.insert(ExitingBlocks.begin(), ExitingBlocks.end());
Loop->getExitingBlocks(CachedExitingBlocks);
Copy link
Contributor

Choose a reason for hiding this comment

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

I actually think the assert wouldn't be too difficult (consider a copy + sortUnique + ensure nothing removed), that being said I think such an assert would be something for a lower part of the API.

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.

Just make the rest of the changes/etc. This LGTM!

…rLifetimeChecker

This makes it unnecessary to share a single instance of visited blocks across multiple calls the the checker.
* add a BasicBlockSetVector class
* add a second argument to BasicBlockFlag::set, for the set value.
* rename BasicBlockSet::remove -> BasicBlockSet::erase.
* add a MaxBitfieldID statistics value in SILFunction.cpp
…ingSet

This allows putting the set as local variables into the function itself (where they are used) and removing them from JointPostDominanceSetComputer.
…n().

Instead of manually managing the internal state of the pass.
Also, use BasicBlockSet instead of SmallPtrSet.
@eeckstein
Copy link
Contributor Author

@swift-ci smoke test and merge

@swift-ci swift-ci merged commit c4cfcc7 into swiftlang:main Jan 27, 2021
@eeckstein eeckstein deleted the use-basicblockflag branch January 27, 2021 11:56
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.

3 participants