This is an archive of the discontinued LLVM Phabricator instance.

Fix SectionMemoryManager deconstruction error with MSVC
AcceptedPublic

Authored by justice_adams on Jul 29 2021, 10:05 AM.

Details

Summary

This commit fixes an issue with deconstructing the SectionMemoryManager where the _vfptr on the MMapper instance variable would be wiped out by the time the ~SectionMemoryManager()
was called, thus causing a method call to _purecall() (MSVC specific) which then would trigger an abort()

This is most evident when running the LLVM Kaleidoscope example on windows.

Destructors of objects with static storage duration are called in reverse order of completion of their constructors or the completion of their dynamic initialization, and the functions passed to std::atexit are called in reverse order they are registered (last one first)

See https://en.cppreference.com/w/cpp/utility/program/exit

Previously, If a user created an object of static storage duration containing a SectionMemoryManager before the DefaultMMapperInstance was instantiated, this will cause an error. For example, consider the following construction order in a program
1.) static std::unique_ptr<KaleidoscopeJIT> (which holds an ObjectLayer which has a SectionMemoryManager)
2.) DefaultMMapperInstance

Thus, at deconstruction time the order is
1.) DefaultMMapperInstance (This wipes out the Vtable)
2.) static std::unique_ptr<KaleidoscopeJIT> (which holds an ObjectLayer which has a SectionMemoryManager)

when the second destructor is called, it calls the virtual method on a wiped out table. Thus the explosion.

The solution here is to make SectionMemoryManager not rely on a static variable which can be wiped out before an instance of the class is ready to be destroyed, and thus ensuring each SectionMemoryManager has it's own reliable instance of DefaultMMapperInstance to be used at deconstruction time.

Note this bug error is specific to MSVC.

I have confirmed all tests pass on both Windows and Linux

Update: I have updated the diff to use ManagedStatic per recommendation and confirmed it fixes the issue

Diff Detail

Event Timeline

justice_adams created this revision.Jul 29 2021, 10:05 AM
justice_adams requested review of this revision.Jul 29 2021, 10:05 AM
justice_adams edited the summary of this revision. (Show Details)

Hi Justice!

Thanks very much for identifying the problem here.

I think it feels odd to make DefaultMMapper a member of SectionMemoryManager. Does it fix your issue if you wrap the static global in a ManagedStatic instead? (see https://llvm.org/docs/ProgrammersManual.html#lazy-initialization-with-managedstatic).

  • Lang.

@lhames You are correct! I wasn't familiar with ManagedStatic but that does fix it. Do you prefer that approach to what's here? If so, I'll go ahead and update the diff. Originally I thought it made sense to have DefaultMMapperInstance be an instance variable of the SectionMemoryManager since it's the only place it's used, but perhaps not since I would have to move the corresponding class definition into the SectionMemoryManager as I did here

lhames added a comment.Sep 1 2021, 9:55 PM

Hi Justice,

Thanks for your patience with this review.

@lhames You are correct! I wasn't familiar with ManagedStatic but that does fix it. Do you prefer that approach to what's here? If so, I'll go ahead and update the diff.

Yes -- I think ManagedStatic should be preferred approach here.

  • Lang.
justice_adams edited the summary of this revision. (Show Details)

@lhames update made. Thank you for the feedback/insight. If this looks good, could you go ahead and commit this for me (and possibly https://reviews.llvm.org/D107084 as well)

lhames accepted this revision.Sep 16 2021, 11:16 PM

LGTM. Thanks Justice!

This revision is now accepted and ready to land.Sep 16 2021, 11:16 PM
This revision was landed with ongoing or failed builds.Sep 16 2021, 11:58 PM
This revision was automatically updated to reflect the committed changes.

Thanks for this again Justice.

Looking again, I think we're arguably masking the issue here by leaking the default mapper (the tutorial doesn't call llvm_shutdown, which would free the managed statics). I think that's ok for now in a tutorial context, but I'll see if we can come up with a nicer long term solution too.

Looks like this broke some of the bots. E.g. https://lab.llvm.org/buildbot#builders/109/builds/22561.

A better solution to this problem might be to document that global SectionMemoryManagers are not allowed, then update the tutorial to avoid using them. I'll look in to that.

@lhames Sorry for the broken build, I didn't realize clang tests would be affected here.

I'm happy to continue working towards the correct approach here. Also thanks for the help so far!

A better solution to this problem might be to document that global SectionMemoryManagers are not allowed, then update the tutorial to avoid using them

Is the problem with SectionMemoryManager being global, or is it with the fact that the class internally relies on a variable of static storage duration (DefaultMMapperInstance) which may be wiped out before the SectionMemoryManagers own deconstruction?

If the solution is to enforce that no SectionMemoryManager are global, how would we enforce that programmatically?

In the tutorial case, the RTDyldObjectLinkingLayer holds onto the SectionMemoryManager instance, so it's not necessarily globally defined
https://reviews.llvm.org/source/llvm-github/browse/main/llvm/examples/Kaleidoscope/include/KaleidoscopeJIT.h$49
But the object it's contained in (The KaleidascopeJIT) is of static storage duration which causes the original error.

@lhames Sorry for the broken build, I didn't realize clang tests would be affected here.

Not a problem at all, I did not realize either. :)

This is what bots are for.

I'm happy to continue working towards the correct approach here. Also thanks for the help so far!

A better solution to this problem might be to document that global SectionMemoryManagers are not allowed, then update the tutorial to avoid using them

Is the problem with SectionMemoryManager being global, or is it with the fact that the class internally relies on a variable of static storage duration (DefaultMMapperInstance) which may be wiped out before the SectionMemoryManagers own deconstruction?

If the solution is to enforce that no SectionMemoryManager are global, how would we enforce that programmatically?

In the tutorial case, the RTDyldObjectLinkingLayer holds onto the SectionMemoryManager instance, so it's not necessarily globally defined
https://reviews.llvm.org/source/llvm-github/browse/main/llvm/examples/Kaleidoscope/include/KaleidoscopeJIT.h$49
But the object it's contained in (The KaleidascopeJIT) is of static storage duration which causes the original error.

Agreed. Enforcing this programmatically would be impossible. We've also seen this bug pop up in the wild since you filed this review, so I think it's likely to keep coming up even if we document that JIT's shouldn't be global.

I've reconsidered by position on this bug: SectionMemoryManager is a legacy API at this point, and it doesn't make sense to disallow global JIT objects just to keep the implementation of a legacy API clean and tidy.

Let's go with your original fix. Sorry for the run-around! :)

lhames reopened this revision.Sep 27 2021, 1:08 PM
This revision is now accepted and ready to land.Sep 27 2021, 1:08 PM

@lhames When you say "original fix" do you mean the fix where I moved DefaultMMapper to be a member of SectionMemoryManager?

I played around with the ManagedStatic approach some more, and see why the buildbots failed originally. It's because llvm_shutdown() is called by clang-repl.exe before the global JIT is destroyed when the program exits. Calling llvm_shutdown() makes sense, as this is the correct way to use the API.

But, if llvm_shutdown() is called, then when the program exits, the JIT is deconstructed (since it's global) and the SectionMemoryManager within the JIT is deconstructed as well, causing the original error to occur (a function called on a destroyed object since the DefaultMMapperInstance was already destroyed with llvm_shutdown())

Ultimately, we need to ensure that the MemoryMapper being held by the SectionMemoryManager is not destroyed prior to the destruction of the SectionMemoryManager instance. I came up with an approach using shared_ptrs which I think is the cleanest way I've found.

This ensures that
a.) if a MemoryMapper is allocated elsewhere and passed into the SectionMemoryManager constructor, then it can live on past the lifetime of the SectionMemoryManager instance due to the nature of shared_ptr's

and

b.) the SectionMemoryManager will instantiate a DefaultMMapper instance if needed, which will then deconstruct after the SectionMemoryManager is destroyed and the ref count is 0

This also allows us to keep DefaultMMapper where it's defined, which I agree makes the most sense.

I've updated the patch and ensured all LLVM + clang tests pass this time. Any thoughts?

use shared_ptrs

Sorry for the delayed reply.

@lhames When you say "original fix" do you mean the fix where I moved DefaultMMapper to be a member of SectionMemoryManager?

Yep. But I think anything that fixes this issue without unreasonable overhead is fine. As I said -- this is more-or-less legacy at this point.

The new approach looks ok too, but we don't allow SectionMemoryManagers to be copied so I think this could just as easily be a unique_ptr, couldn't it?

  • Lang.

@justice_adams Is the anything blocking this from being committed?

@rgal no I don't think there is anything stopping this. Feel free to commit this for me (sorry for the delayed response)

@lhames Do you have any more feedback?

Ping for further feedback (or commit)

Herald added a project: Restricted Project. · View Herald TranscriptMar 7 2022, 1:25 PM
sgraenitz requested changes to this revision.May 17 2023, 5:35 AM

This issues popped up with GCC 12 as well after https://github.com/llvm/llvm-project/commit/47f5c54f997a59bb2c65abe6b8b811f6e7553456

However, I don't think this patch is ready yet. I see two options:
(a) Keep the current approach to break the interface, fix all users of SectionMemoryManager (in MLIR for example, but I am not sure that's all) and maybe add a release note
(b) Distinguish between owned and non-owned MemoryMapper and fix only the DefaultMMapperInstance case that causes the Kaleidoscope issue

IMHO option (b) is the better solution, because it doesn't break the API and clients can get away without the shared_ptr. What do you think?

llvm/lib/ExecutionEngine/SectionMemoryManager.cpp
16

We don't need this anymore

This revision now requires changes to proceed.May 17 2023, 5:35 AM

@sgraenitz Option (b) sounds good to me.

Hi all, any news on this patch?

@justice_adams The patch linked above fixes the issue for GCC. Does it work for you as well? Also, there is a ticket here that looks very similar: https://github.com/llvm/llvm-project/issues/63204

@sgraenitz, could we move forward with the patch as is? It fixes a whole bunch of issues for us.

@sgraenitz, could we move forward with the patch as is?

Well, it cannot land be cause it breaks the interface of SectionMemoryManager. If that gets fixed and the patch gets rebased, sure I will withdraw my veto.

It fixes a whole bunch of issues for us.

In terms of the original issue, however, my fix should cover everything from this patch. And given that there has been no progress in this review for 2 years, I am not sure this will move forward anymore. I guess it's the std::shared_ptr<MemoryMapper> that your are referring to? That's not a bug fix but rather a feature. I guess you can implement it independent from this review.

sgraenitz resigned from this revision.Jul 28 2023, 7:03 AM

This review should be closed

This revision is now accepted and ready to land.Jul 28 2023, 7:03 AM