This is an archive of the discontinued LLVM Phabricator instance.

Fix initialization problems with PassRegistry
AbandonedPublic

Authored by zturner on Jun 2 2014, 5:41 PM.

Details

Summary

(Not sure who the best reviewer is, so I've put chandlerc based on CODE_OWNERS and dblaikie based on llvm-dev discussion).

The following trace is taken from Visual Studio by putting Watchpoints in ManagedStaticBase::RegisterManagedStatic, ManagedStaticBase::destroy, and llvm_shutdown

Allocating ManagedStatic 0x0x04ab19d0 {ManagedStatic<llvm::PassRegistry> PassRegistryObj}
Allocating ManagedStatic 0x0x04ab19dc {ManagedStatic<llvm::sys::SmartRWMutex<1> > Lock}
Allocating ManagedStatic 0x0x04ab2d34 {ManagedStatic<llvm::SmallPtrSet<llvm::cl::OptionCategory *,16> > RegisteredOptionCategories}
Allocating ManagedStatic 0x0x04ab2f08 {ManagedStatic<std::string> LibSupportInfoOutputFilename}
Allocating ManagedStatic 0x0x04ab2dd8 {ManagedStatic<llvm::sys::ThreadLocal<llvm::PrettyStackTraceEntry const > > PrettyStackTraceHead}
Allocating ManagedStatic 0x0x04ab2f14 {ManagedStatic<llvm::sys::SmartMutex<1> > TimerLock}
llvm_shutdown
Destroying ManagedStatic 0x0x04ab2f14 {ManagedStatic<llvm::sys::SmartMutex<1> > TimerLock}
Destroying ManagedStatic 0x0x04ab2dd8 {ManagedStatic<llvm::sys::ThreadLocal<llvm::PrettyStackTraceEntry const > > PrettyStackTraceHead} {...}
Destroying ManagedStatic 0x0x04ab2f08 {ManagedStatic<std::string> LibSupportInfoOutputFilename}
Destroying ManagedStatic 0x0x04ab2d34 {ManagedStatic<llvm::SmallPtrSet<llvm::cl::OptionCategory *,16> > RegisteredOptionCategories}
Destroying ManagedStatic 0x0x04ab19dc {ManagedStatic<llvm::sys::SmartRWMutex<1> > Lock}
Destroying ManagedStatic 0x0x04ab19d0 {ManagedStatic<llvm::PassRegistry> PassRegistryObj}
Allocating ManagedStatic 0x0x04ab19dc {ManagedStatic<llvm::sys::SmartRWMutex<1> > Lock}
Destroying ManagedStatic 0x0x04ab19dc {ManagedStatic<llvm::sys::SmartRWMutex<1> > Lock}
Allocating ManagedStatic 0x0x04ab19d0 {ManagedStatic<llvm::PassRegistry> PassRegistryObj}
Allocating ManagedStatic 0x0x04ab19dc {ManagedStatic<llvm::sys::SmartRWMutex<1> > Lock}

From this you can see that ManagedStatics are allocated during llvm_shutdown, and that one of them is the PassRegistry, which has already been allocated and destroyed.

This patch addresses this by introducing a StaticPassRegistry, and having RegisterPass<> write its entries to the StaticPassRegistry. Once main is entered, StaticPassRegistry becomes immutable and its entries are transferred over to the PassRegistry. By doing this, we can guarantee that PassRegistry won't be access during static initialization, and thus we can change PassRegistry's mutex to be a raw static instead of a ManagedStatic.

Eventually, the goal here is to get to a point where ManagedStatic can enforce true once-only initialization of ManagedStatics using std::call_once, guaranteeing that once a ManagedStatic is destroyed it is never allocated again, and also fixing existing race conditions in the implementation of ManagedStatic.

Diff Detail

Event Timeline

zturner updated this revision to Diff 10031.Jun 2 2014, 5:41 PM
zturner retitled this revision from to Fix initialization problems with PassRegistry.
zturner updated this object.
zturner edited the test plan for this revision. (Show Details)
zturner added reviewers: chandlerc, dblaikie.
zturner added a subscriber: Unknown Object (MLST).
rnk added a reviewer: resistor.Jun 3 2014, 5:28 PM
rnk added a subscriber: rnk.

I think Owen was the original author for this code back in 2010. Maybe he has thoughts on how this should work. In particular, the commit logs of r114130 and r114131 look suspicious.

chandlerc edited edge metadata.Jun 3 2014, 6:00 PM

(Just as an FYI, this is in my queue, but requires more thinking.)

One unfortunate thing about the patch is that it mixes both refactorings and the bug fix which makes it more noisy. I don't think we should call it "PassCallback" either way, and I would just avoid doing the renaming and such in the same patch unless strictly necessary to implement it.

zturner updated this revision to Diff 10107.Jun 4 2014, 2:19 PM
zturner edited edge metadata.

Renamed PassCallback back to PassRegistrationListener, and removed some other superfluous changes to reduce the size of the review.

dblaikie edited edge metadata.Jun 11 2014, 10:29 AM

This still seems to be doing multiple things for possibly distinct reasons & might be clearer (to me - whether that matters is a fair question) in separate patches.

The original problem you demonstrate with the traces is due to the use of global PassRegistrationListener's that have self-deregistration, right?

So just removing self PassRegistrationListener's automatic registration/deregistration would be sufficient to fix that issue, right? No need to change the Pass registration itself, or introduce the StaticPassregistry.

We could even maintain the ability to automatically register PassRegistrationListeners if we just had a separate wrapper type that handled their registration in global contexts and wouldn't try to deregister them (since the PassRegistry is going to cleanup itself, there would be no need to deregister). That way we could preserve the existing automatic attachment/removal of PassRegistrationListeners (or not... they might need some other wrapper type to do local scope attachment and detachment)

Does any of that rambling make sense?

There are a couple of things that lead to the original issue. Another one is the use of the Mutex. Mutex is locked in the destructor of the PassRegistry, and the PassRegistry itself is a ManagedStatic. So the PassRegistry is allocated lazily, then the first time a method is called the Mutex is allocated. Then during shutdown Mutex is freed, then PassRegistry is freed, calling PassRegistry's destructor, locking the mutex again.

Also, I'm not sure if it's as easy as removing PassRegistrationListener's automatic registration / deregistration. I tried that and ran into some problems somewhere else, although the details escape me. If needed I can go back and do some digging to figure out what it was. That being said, I don't want to spend too much additional time on this, as it was mostly intended to be a drive-by cleanup task tangential to my original goal.

Ah, right, the PassRegistry's dtor accesses the lock. Why? It's running as a global dtor - do they ever run concurrently? (I know at Google we have a rule of no global dtors since you might be in the middle of a thread, holding a lock, when the process gets shutdown - at which point you'll deadlock in your global ctor. I'm not sure if we want to go that far & not sure how that'd work with llvm_shutdown)

It still seems a bit error prone to have a global object (PassRegistry) access another global (its lock) in a dtor without any guarantee that PassRegistry is initialized after the lock...

If the PassRegistry's dtor actually needs to lock, then it sounds like the program will have a race on the lock (since it's handling the lock in some other thread during destruction and the lock will be destroyed at some point here) - so I'm not sure if your change actually makes any of this actually safe?

I might be able to solve the lock problem by just deleting the lock access from the destructor, as you mention, or making the lock an instance variable of the PassRegistry class. But I still think the way I've done is the only real way to solve the problem with the PassRegistrationListener.

Ultimately, the root of the problem is that ManagedStatics are being accessed during static initialization and shutdown. This needs to be prevented, and I think we need to try to get to a point where we can actually enforce, by way of asserts, that you cannot touch a ManagedStatic before main is entered or after it returns.

zturner abandoned this revision.Jun 11 2014, 5:43 PM