Page MenuHomePhabricator

[WIP] Add support for return from an SEH __finally block.
Needs ReviewPublic

Authored by efriedma on Apr 28 2022, 3:57 PM.



MSVC has a warning C4532 for jumping out of a finally block. However, that only specifically covers "break", "continue", and "goto". There's no warning for "__leave" and "return".

It's not completely clear to me why MSVC considers "__leave" and "return" special, compared to other sorts of control flow. My best guess is that return-in-finally is so pervasive across drivers that the warning would be way too noisy. An alternative theory is that MSVC generates a call to _local_unwind in the normal path for certain uses of break/continue/goto, and that's somehow a hazard for unwinding.

In any case, the end result is that "return" shows up somewhat frequently in __finally blocks in Windows drivers, but I haven't seen any of the others.

This patch generates code that's similar to what MSVC generates: it branches directly to the return block for normal code, and uses _local_unwind to break out of exception handling.

Currently, this only handles "return". Adding support for other ways of exiting a __finally blocks should be straightforward on top of this; it's mostly a question of managing the different possible destinations.

To represent the _local_unwind, this patch includes a new intrinsic llvm.seh_localunwind. This generates the necessary arguments to _local_unwind to the nearest catchpad automatically.

Some basic testing shows this seems to do the right thing in simple cases.

Handling try/finally inside the try of a try/__finally depends on the change to WinException.cpp. Still a bit ugly, but not sure how to make it cleaner.

__try/__finally inside a __finally block is broken because recoverAddrOfEscapedLocal can't recover the address of a variable allocated inside a __finally block. (Here specifically, we compute the wrong value for SEHRetNowParent, but it's a general issue affecting any variable declared inside a __finally block.) We probably need to store the frame pointer in an alloca in the parent function, and then load it later so we can recover the pointer we want, or something like that.

Based on the patch by Ten Tzen, particularly the code to call _local_unwind. I rewrote a bunch of the code to simplify it, and avoid using _local_unwind in cases where it isn't necessary.

Diff Detail

Event Timeline

efriedma created this revision.Apr 28 2022, 3:57 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 28 2022, 3:57 PM
efriedma requested review of this revision.Apr 28 2022, 3:57 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptApr 28 2022, 3:57 PM
Herald added a subscriber: jdoerfert. · View Herald Transcript
efriedma edited the summary of this revision. (Show Details)Apr 28 2022, 3:59 PM
dpaoliello resigned from this revision.Apr 28 2022, 4:08 PM
dpaoliello removed a reviewer: dpaoliello.
efriedma updated this revision to Diff 426486.May 2 2022, 12:30 PM
efriedma edited the summary of this revision. (Show Details)

Figured out the __finally nesting thing; we were missing unwind table entries. (This is a consequence of the fact that our unwind tables don't cover the entire function, just invokes.) Need to think about a cleaner solution.

I'm currently thinking it makes sense to introduce an intrinsic "void @llvm.localunwind() noreturn". No arguments. Only valid with SEH personality; must unwind to a catchpad with a filter named __IsLocalUnwind, or something like that. It would basically just lower to a call to _local_unwind, but it would compute the arguments in the backend. Primary downside is that it wouldn't be possible to unwind to anything other than the closest catchpad. But not sure if unwinding further is actually useful in practice, given the ways we need to use it.

The other thing I'm thinking about is how to restructure the code to make the catchpad hack in WinException.cpp a bit less hacky.

efriedma updated this revision to Diff 426814.May 3 2022, 1:25 PM
efriedma edited the summary of this revision. (Show Details)

Switch to use llvm.seh_localunwind intrinsic.

I'd appreciate any feedback at this point.