-
Notifications
You must be signed in to change notification settings - Fork 10.5k
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
Conversation
@swift-ci please smoke test |
@eeckstein Can you please review? |
There was a problem hiding this 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; | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
@shajrawi Joe, do we also need the same optimization for witness tables? |
@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' |
4569ffe
to
24d5ff6
Compare
@swift-ci please smoke test |
@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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
24d5ff6
to
e0f0081
Compare
@swift-ci please smoke test |
Very nice @shajrawi ! |
There was a problem hiding this 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
e0f0081
to
ccf987e
Compare
@swift-ci please smoke test |
Looks good! = D |
There was a problem hiding this 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)); |
There was a problem hiding this comment.
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}.
There was a problem hiding this comment.
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 {}
ccf987e
to
684f04a
Compare
684f04a
to
9b2c0cd
Compare
@swift-ci please smoke test |
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.