-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[DebugInfo] Add a verifier pass to find holes in lexical scopes. #13491
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
lib/SIL/SILVerifier.cpp
Outdated
AlreadySeenScopes.insert(LastSeenScope); | ||
for (SILInstruction &SI : *BB) { | ||
auto *DS = SI.getDebugScope(); | ||
if (!AlreadySeenScopes.count(DS)) { |
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.
nit: if (AlreadySeenScopes.insert(DS).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.
Yes, definitely. I'll change it :)
I don't know much (~nothing) about the |
@JDevlieghere From what I see this is not possible at |
@JDevlieghere is correct. We need to add an additional check: if the "re-entered" scope is a (non-inlined) parent of the previous instruction, then this is okay. |
For example:
|
Updated after your comments. Thanks for your review! It looks like this has found a case where
Here we have something like:
Is this a valid scenario? From what I see |
lib/SIL/SILVerifier.cpp
Outdated
|
||
// Visit the instructions in the BB, to find holes in the lexical scopes. | ||
// We use a fairly simple heuristic here (Explain heuristic). | ||
llvm::DenseSet<const SILDebugScope *> AlreadySeenScopes; |
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.
This probably is growing enough that can be split in its own function. I've been doing this when I was at 30k feet and I didn't want to lose this snapshot :)
lib/SIL/SILVerifier.cpp
Outdated
} | ||
if (DS != LastSeenScope) { | ||
llvm::dbgs() << "Broken instruction!\n"; SI.dump(); | ||
require(DS == LastSeenScope, "Found an hole in debug lexical scopes"); |
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.
require
is printing the wrong instruction (always the last one in the block). We probably need an overload where we pass the instruction to print.
tools/sil-opt/SILOpt.cpp
Outdated
@@ -314,7 +314,7 @@ int main(int argc, char **argv) { | |||
// Setup the SIL Options. | |||
SILOptions &SILOpts = Invocation.getSILOptions(); | |||
SILOpts.InlineThreshold = SILInlineThreshold; | |||
SILOpts.VerifyAll = EnableSILVerifyAll; | |||
SILOpts.VerifyAll = true; |
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.
This (and the other changes to LLVMOpt
/CompilerInvocation
seem to be required to trigger issues, otherwise we don't run the verifier after each pass). Of course this can't go in as is, but it seems to be valuable to keep this hack around in the PR for testing purposes.
lib/SIL/SILVerifier.cpp
Outdated
|
||
// Visit the instructions in the BB, to find holes in the lexical scopes. | ||
// We use a fairly simple heuristic here (Explain heuristic). | ||
llvm::DenseSet<const SILDebugScope *> AlreadySeenScopes; |
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.
(nit) I'd move this down until after the additional checks, closer to the for loop.
if (!AlreadySeenScopes.count(DS)) { | ||
AlreadySeenScopes.insert(DS); | ||
LastSeenScope = DS; | ||
continue; |
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 always continue
in the if
-branch you might as well omit the else
.
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
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.
Two minor nits, but the heuristic/algorithm looks sound to me.
fe2b6e2
to
61097f5
Compare
After fixing the issues that this verifier check exposes, this is ready for review/commit. |
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 to me.
lib/SIL/SILVerifier.cpp
Outdated
return; | ||
SILInstruction &First = *BB->begin(); | ||
const SILDebugScope *LastSeenScope = First.getDebugScope(); | ||
AlreadySeenScopes.insert(LastSeenScope); |
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.
This doesn't yet handle inlined scopes. I think the right way to handle them here is to insert the scope of the inlined call site into the map. This way all inlined instructions belong to the same scope (the scope of the apply instruction that was inlined).
We also need to to restrict this to only run at -ONone. Non-mandatory optimizations may legitimately hoist sink or merge instructions.
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 the inlined scope be handled as a follow up I assume?
About -Onone
, I think this code should take care of it:
// This check only makes sense at -Onone. Optimizations,
// e.g. inlining, can move scopes around.
llvm::DenseSet<const SILDebugScope *> AlreadySeenScopes;
if (BB->getParent()->getEffectiveOptimizationMode() !=
OptimizationMode::NoOptimization)
return;
lib/SIL/SILVerifier.cpp
Outdated
LastSeenScope = DS; | ||
continue; | ||
} | ||
require(DS == LastSeenScope, "Found an hole in debug lexical scopes"); |
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.
The check looks good!
perhaps: "Basic block contains a non-contiguous lexical scope at -Onone"
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.
Good idea, done!
include/swift/SIL/SILDebugScope.h
Outdated
@@ -64,6 +64,10 @@ class SILDebugScope : public SILAllocated<SILDebugScope> { | |||
/// into. | |||
SILFunction *getParentFunction() const; | |||
|
|||
PointerUnion<const SILDebugScope *, SILFunction *> getParent() const { |
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 we should either have an accessor, or make the field public, but not both.
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.
lib/SIL/SILVerifier.cpp
Outdated
@@ -4338,6 +4342,76 @@ class SILVerifier : public SILVerifierBase<SILVerifier> { | |||
} | |||
} | |||
|
|||
// This pass verifies that there are no hole in debug scopes at -Onone. |
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.
///
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.
@@ -56,6 +56,10 @@ static llvm::cl::opt<bool> AbortOnFailure( | |||
"verify-abort-on-failure", | |||
llvm::cl::init(true)); | |||
|
|||
static llvm::cl::opt<bool> VerifyDIHoles( |
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.
Is this option really necessary, shouldn't it just always be on?
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 is good while we're transitioning, so that if something breaks, we can just turn the option off and investigate rather than reverting the whole commit.
Addressed comments & replied. Thanks for your reviews, @adrian-prantl/ @vedantk ! |
@swift-ci please test |
Build failed |
Build failed |
@swift-ci please test |
Build failed |
Build failed |
Apparently this found a bug in swiftpm :)
|
Just a heads-up that you'll want to run "AT swift-ci Please test source compatibility" before checking this in. That contains a large suite of swift projects which we'll want to run the verifier over. |
In fact |
This found a bunch of issues where we didn't properly preserve debug infos which have been fixed over the course of the past two weeks. The basic idea is we want to make sure that within a BB we never have `holes` in debug scopes, i.e. if we enter a scope, one of the two holds 1) We never visited that scope before 2) We're re-entering a scope that is an ancestor of the scope currently leaving. This is needed to correctly model nested scopes, e.g. { let i = 0; // scope 0 if (cond) { let j = i // scope 1 } let k = i + 1 // scope 0 and okay. } I also checked in a `cl::opt`, so that if this breaks something, it can be conveniently disabled. This is currently not enabled as we flesh out the last violations. <rdar://problem/35816212>
@swift-ci please test and merge |
@swift-ci Please test source compatibility |
@swift-ci test source compatibility |
I'm currently merging this with the support disabled, as this is breaks a lot of code in the wild probably (and unfortunately, I'd add). I plan to write an RFC to send to swift-dev so that people can check & report bugs, and then I'll enable this after a grace period. |
@swift-ci please smoke test and merge |
@swift-ci test and merge |
WIP, this can't be merged yet, it's just an experiment I and Adrian discussed yesterday.
OTOH, it apparently already caught a violation I'm investigating. It can't be enabled by default until at least we self-host with this on, but it seems like there's some value in being more strict here.