Skip to content

Add a sidetable cache in the module for VTable entries, replacing linear lookup #5053

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 1 commit into from
Sep 29, 2016

Conversation

shajrawi
Copy link

radar rdar://problem/28311199

This commits resolves a FIXME in SILVTable::getImplementation: Building a sidetable cache in the module for function lookup.

That, in turn, drastically reduces Swift's compilation time in programs with large VTables.

@shajrawi
Copy link
Author

@swift-ci please smoke test

@shajrawi
Copy link
Author

@eeckstein Can you please review?

Copy link
Contributor

@eeckstein eeckstein left a comment

Choose a reason for hiding this comment

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

One additional comment:
As the cache is always up-to-date it would be a good idea to add a check in SILVerifier if the cache is consistent with the actual vtable entries. This means:
-) iterate over all vtables
-) check if there is a cache entry for each vtable entry
-) check if the number of total vtable entries == the size of the cache

/// This is a cache of vtable entries for quick look-up
llvm::DenseMap<const SILVTable *, llvm::DenseMap<SILDeclRef, SILFunction *>>
VTableEntryCache;

Copy link
Contributor

@eeckstein eeckstein Sep 28, 2016

Choose a reason for hiding this comment

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

llvm::DenseMap<std::pair<const SILVTable *, SILDeclRef>, SILFunction *> would be much more efficient

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually ... I'm not sure about this. Probably it would be more efficient, but I don't know if this is significant.

Copy link
Contributor

Choose a reason for hiding this comment

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

Anyway, instead of this outer DenseMap you can directly place the inner DenseMap into SILVTable directly.

Copy link
Contributor

Choose a reason for hiding this comment

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

So either you do a combined cache in SILModule (DenseMap<std::pair<const SILVTable *, SILDeclRef>, SILFunction *>) or separate caches (DenseMap<SILDeclRef, SILFunction *>) in SILVTable.

Copy link
Author

Choose a reason for hiding this comment

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

Did a combined cache in SILModule (DenseMap<std::pair<const SILVTable *, SILDeclRef>, SILFunction *>), will update the PR after adding a check in SILVerifier

EntryCache.insert(entry);
}
}
}
Copy link
Contributor

@eeckstein eeckstein Sep 28, 2016

Choose a reason for hiding this comment

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

I think this function is not needed (See below)

Copy link
Author

Choose a reason for hiding this comment

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

Needed unless we want to add friend classes (See below)

@@ -374,6 +374,7 @@ class DeadFunctionElimination : FunctionLivenessComputation {
return false;
});
}
Module->recalculateVTableEntryCache();
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of recalculating the whole cache you could just erase the dead entries in the loop above

Copy link
Author

Choose a reason for hiding this comment

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

I can't without making the cache public and/or allow public mutability. either that or make the optimization (and any other future optimizations) friends with the Module

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 just add SILModule::removeFromVTableCache(SILVTable *, SILDeclRef)

Copy link
Author

Choose a reason for hiding this comment

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

Can do that, will create such a version, my rationale was mitigating the risk of leaving the table in an inconsistent state (By removing an entry from cache but keeping it in the vtable) - but, your solution is more efficient compilation-time wise.

@swiftix
Copy link
Contributor

swiftix commented Sep 28, 2016

@shajrawi Joe, do we also need the same optimization for witness tables?

@shajrawi
Copy link
Author

@swiftix As far as I can tell we do not: SIL Modules have DenseMap(s) for witness table lookups, Witness table itself doesn't do any linear lookups and I don't see this as an issue in 'instruments'

@shajrawi
Copy link
Author

@swift-ci please smoke test

@shajrawi
Copy link
Author

@eeckstein requested changes / SILVerifier add-on are in amended PR

if (!isAlive(entry.second)) {
DEBUG(llvm::dbgs() << " erase dead vtable method " <<
entry.second->getName() << "\n");
// Update cache:
Module->removeFromVTableCache(&vTable, entry.first);
Copy link
Contributor

Choose a reason for hiding this comment

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

Wait... it would be cleaner and simpler to remove the cache entry in SILVTable::removeEntries_if. Then you also don't need SILModule:: removeFromVTableCache.

Copy link
Author

Choose a reason for hiding this comment

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

SILVTable doesn't have the module as a class member and passing the module to remove_if would be a problem: the function is templated (typename being the Predicate), implemented inside the header file, the header file has a forward deceleration of the SIL Module (can't include it because SIL Module uses SILVTable)

Copy link
Author

Choose a reason for hiding this comment

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

And if SILVTable::removeEntries_if could have cleanly used the Module I wouldn't have written my previous solution in the first place nor added this public function now: SILVTable is friend class of SILModule :)

Copy link
Contributor

Choose a reason for hiding this comment

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

I still think it would be cleaner, because all the cache handling is done inside SILVTable.
You can get the module from the SILFunction of the vtable entry (which you are about to delete).
To solve the forward-declaration problem, you can define a private SILVtable helper function, which is defined in the cpp file.

Copy link
Author

Choose a reason for hiding this comment

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

Alright - Done the change you suggested - see amended PR

llvm::DenseSet<ClassDecl*> vtableClasses;
size_t EntriesSZ = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: you can move this line down to the loop where it's used.

Copy link
Author

Choose a reason for hiding this comment

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

No: the assert is outside of the loop, you need to sum-up all the entries from all the vtables then compare the overall sum to the cache size.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I was confused by the nested loop

@shajrawi
Copy link
Author

@swift-ci please smoke test

@lattner
Copy link
Contributor

lattner commented Sep 29, 2016

Very nice @shajrawi !

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.

Looks good! Some small nits that improve QoI verifier error feedback and style.

llvm::DenseSet<ClassDecl*> vtableClasses;
size_t EntriesSZ = 0;
Copy link
Contributor

@gottesmm gottesmm Sep 29, 2016

Choose a reason for hiding this comment

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

nitpick: We generally do not use size_t in these cases. Please use unsigned.

Copy link
Author

Choose a reason for hiding this comment

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

done

for (auto entry : vt.getEntries()) {
assert(VTableEntryCache.find(std::make_pair(&vt, entry.first)) !=
VTableEntryCache.end() &&
"VTable entry not in cache");
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 follow the code above and dump out the entry that is not in the cache?

Copy link
Author

Choose a reason for hiding this comment

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

done

@@ -3546,15 +3546,25 @@ void SILModule::verify() const {
g.verify();
}

// Check all vtables.
// Check all vtables and vtable cache.
Copy link
Contributor

Choose a reason for hiding this comment

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

Grammer nit: "Check all vtables and the vtable cache"

Copy link
Author

Choose a reason for hiding this comment

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

done

// Update the Module's cache with new vtable + vtable entries:
for (auto &entry : Entries) {
M.VTableEntryCache.insert(
std::make_pair(std::make_pair(vt, entry.first), entry.second));
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 you can replace this with:

M.VTableEntryCache.insert({{vt, entry.first}, entry.second});

But my memory might be wrong.

Copy link
Author

Choose a reason for hiding this comment

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

done

@shajrawi
Copy link
Author

@swift-ci please smoke test

@gottesmm
Copy link
Contributor

Looks good! = D

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.

Looking good. Just one last nit. I am not going to review this again, so no more nits after this. I promise = ).

return nullptr;
}

void SILVTable::removeFromVTableCache(Pair &entry) {
SILModule &M = entry.second->getModule();
M.VTableEntryCache.erase(std::make_pair(this, entry.first));
Copy link
Contributor

Choose a reason for hiding this comment

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

If you want, you can also remove the make_pair here and just use {this, entry.first}.

Copy link
Author

Choose a reason for hiding this comment

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

replaced all make_pair(s) with {}

@shajrawi
Copy link
Author

@swift-ci please smoke test

@shajrawi shajrawi merged commit 00d10c4 into swiftlang:master Sep 29, 2016
@shajrawi shajrawi deleted the vtable_entry_cache branch September 29, 2016 19:17
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.

5 participants