This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Implement exception_ptr on Windows
ClosedPublic

Authored by EricWF on May 5 2017, 4:53 PM.

Details

Summary

This patch implements exception_ptr on Windows using the __ExceptionPtrFoo functions provided by MSVC.

The __ExceptionPtrFoo functions are defined inside the C++ standard library, msvcprt, which is unfortunate because it requires libc++ to link to the MSVC STL. However this doesn't seem to cause any immediate problems. However to be safe I kept all usages within the libc++ dylib so that user programs wouldn't have to link to MSVCPRT as well.

Note there are still 2 outstanding exception_ptr/nested_exception test failures.

  • current_exception.pass.cpp needs to be rewritten for the Windows exception_ptr semantics which copy the exception every time.
  • rethrow_if_nested.pass.cpp need investigation. It hits a stack overflow, likely from recursion.

This patch also gets most of the <future> tests passing as well.

Diff Detail

Repository
rL LLVM

Event Timeline

EricWF created this revision.May 5 2017, 4:53 PM
EricWF edited the summary of this revision. (Show Details)May 5 2017, 4:54 PM
BillyONeal added inline comments.May 5 2017, 5:14 PM
include/exception
192 ↗(On Diff #98039)

I hope you realize you are doing "evil" unsupported things :). (We won't go out of our way to break this, but if it does break we won't feel bad :P )

test/std/language.support/support.exception/except.nested/rethrow_if_nested.pass.cpp
12 ↗(On Diff #98039)

FWIW it does not pass for our implementation either.

Note that unlike the Itanium ABI, the stack is not popped back to the catch block when handling an exception (because SEH can act like a signal handler and say EXCEPTION_CONTINUE_EXECUTION).

test/std/language.support/support.exception/propagation/current_exception.pass.cpp
10–11 ↗(On Diff #98039)

We don't copy the exception each time the exception_ptr is copied -- exception_ptr models shared_ptr. See: "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.10.25017\crt\src\stl\excptptr.cpp"

We do make a copy when making the initial exception_ptr.

STL_MSFT resigned from this revision.May 5 2017, 5:41 PM
STL_MSFT added inline comments.
include/exception
192 ↗(On Diff #98039)

More specifically, while this will work with MSVC 2015/2017's msvcp140.dll, we've changed exception_ptr's separately compiled component in the next major binary-incompatible version of our STL.

EricWF added inline comments.May 5 2017, 5:49 PM
include/exception
192 ↗(On Diff #98039)

I'm well aware I'm being a bad boy, but I need to mimic the Microsoft ABI. Obviously I expect nothing less than breakage and pain.

separately compiled component in the next major binary-incompatible version of our STL.

I'll have to cross that bridge when I come to it. Hopefully I can still make libc++ work *somehow*.
Maybe the exception_ptr internals will live outside of the STL libraries next release too :-)

test/std/language.support/support.exception/except.nested/rethrow_if_nested.pass.cpp
12 ↗(On Diff #98039)

Thanks for pointing that out! Otherwise I may have gone down a rabbit hole trying to figure it out.

test/std/language.support/support.exception/propagation/current_exception.pass.cpp
10–11 ↗(On Diff #98039)

Ah thanks for the clarification. So std::current_exception() always returns a different copy.

EricWF added a comment.May 5 2017, 7:47 PM

I think the medium to long term goal should be to replace the __ExceptionPtrFoo functions we use with our own implementation, so we no longer rely on these MSVC implementation details.

However it appears that we don't have access to the internal headers that we might need, such as ehdata.h

test/std/language.support/support.exception/propagation/current_exception.pass.cpp
10–11 ↗(On Diff #98039)

exception_ptr models shared_ptr.

Oh fudge me, it not only models it but it actually uses MSVC's shared_ptr implementation. This seems so much sketchier now.

bcraig added inline comments.May 6 2017, 3:15 PM
test/std/language.support/support.exception/except.nested/rethrow_if_nested.pass.cpp
12 ↗(On Diff #98039)

Also means that MSABI doesn't need to allocate heap memory in a failure case. That always bugged me about the Itanium ABI. In "the real world", I don't know if stack overflows are any better, but at least, in theory, maximum stack usage could be calculated by a determined enough programmer.

test/std/language.support/support.exception/propagation/current_exception.pass.cpp
10–11 ↗(On Diff #98039)

You may want to pursue your long term plan instead.

Rather than use the msvcprt functions, dig into the underlying SEH structures, find the pointer to the exception, and work from there.

Headers in the older crt source have a lot of useful information, and that may be useful enough to get things going.

The blog mentions where to fine one of those useful headers (ehdata.h):
http://workblog.pilin.name/2014/03/decoding-parameters-of-thrown-c.html
"Welcome - ehdata.h. This header appears only in VS2012 and VS2013 (see "%Program Files (x86)%\Microsoft Visual Studio 12.0\VC\crt\src\ehdata.h"):"

The other useful header is mtdll.h. I found it in my local msvc 9.0 install, but didn't find it in my msvc-14.0 install. I don't have 10.0, 11.0, or 12.0 installed locally right now to see if mtdll.h is present in any of those. mtdll.h describes the layout of _tiddata, a thread local data store for the CRT. The interesting fields there are void *_curexception; and void *_curcontext;.

EricWF added inline comments.May 6 2017, 3:28 PM
test/std/language.support/support.exception/propagation/current_exception.pass.cpp
10–11 ↗(On Diff #98039)

You may want to pursue your long term plan instead.

I don't think this should block this initial progress. Libc++ has a history of linking to libstdc++ without major problems. This should be OK too, although I still don't love doing it.

bcraig edited edge metadata.May 6 2017, 6:20 PM

libstdc++ and the Visual Studio C++ runtime have very different compatibility expectations.

libstdc++ is generally bundled with the operating system. It maintains binary compatibility over very long stretches of time. In most systems, there can only be one libstdc++ loaded in a process at a time. Throwing a std::exception in one shared library and catching it in another generally works, even if the two libraries were built with different compilers. So pretty similar to libc++ on Mac. This is almost certainly not news to you.

The Visual Studio C++ runtime is not bundled with the operating system. It generally does not maintain binary compatibility over more than a major release. Having multiple versions of the Visual Studio C++ runtime loaded in the same process is pretty common. Throwing a std::exception in one DLL and catching it in another will generally won't work if the two libraries were built with different compilers.

When you code against libstdc++ internals, your code will likely last 5+ years before it breaks, and when it breaks it would likely be considered a bug. When you code against Visual Studio C++ internals, your code has a shelf life of 1-3 years, and then the code has a high probability of breaking.

I think the seh internals are more stable than the ExceptionPtr helper functions. One thing that works reasonably well is throwing an exception that doesn't derive from a CRT type in one DLL, and then catching it in another, even with different compiler versions. "catch(...)" also works reasonably well across DLL / compiler version boundaries.

Is there a particular reason to go with a shorter term solution now, rather than straight to the long term solution? Is there a deadline or a particular milestone we are trying to hit for clang-cl support? Or is there a suspicion that future changes will make a really good implementation possible, incentivizing us to do something inexpensive for now?

EricWF added a comment.May 7 2017, 2:02 PM

libstdc++ and the Visual Studio C++ runtime have very different compatibility expectations.

I only meant to imply that linking to another standard library implementation hasn't caused major issues
in the past.

Is there a particular reason to go with a shorter term solution now, rather than straight to the long term solution? Is there a deadline or a particular milestone we are trying to hit for clang-cl support? Or is there a suspicion that future changes will make a really good implementation possible, incentivizing us to do something inexpensive for now?

My main desire is to get the test suite green so we can start detecting regressions. This patch reduces the remaining test failures by half, and although not future proof, it is correct at the moment.
Plus I would really prefer writing the more complex implementation against a set of tests that are already passing.

I also think this patch is a step in the right direction, regardless of the final implementation. The bulk of this patch is just boilerplate, setting up the correct declarations and definitions for implementing exception_ptr targeting Microsoft's ABI as opposed to Itanium.
It would be nice to commit these changes apart from the future implementation, and this seems like an OK way to do that.

@bcraig Since this patch doesn't preclude improvements in the future are you OK to move forward with it? I agree this shouldn't be exception_ptr's final form, and I plan to continue working on the suggested implementation, but I don't want to block other Windows
progress while this is ongoing (and that's what having an un-implemented exception_ptr is doing).

bcraig added a comment.May 7 2017, 6:20 PM

Getting the test suite green sooner rather than later seems like a good reason to temporarily pick a 1-3 year solution rather than a 5+ year solution. Also, as you mention, this isn't all throw away work, so it's still progress.

So yeah, this is fine to go in as is. LGTM.

EricWF updated this revision to Diff 98119.May 7 2017, 6:30 PM
  • Remove some missed XFAILS
This revision was automatically updated to reflect the committed changes.