Reid Kleckner
2015-May-15 22:37 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
After a long tale of sorrow and woe, my colleagues and I stand here before you defeated. The Itanium EH representation is not amenable to implementing MSVC-compatible exceptions. We need a new representation that preserves information about how try-catch blocks are nested. WinEH background ------------------------------- Skip this if you already know a lot about Windows exceptions. On Windows, every exceptional action that you can imagine is a function call. Throwing an exception is a call. Destructor cleanups and finally blocks are calls to outlined functions that run the cleanup code. Even catching an exception is implemented as an outlined catch handler function which returns the address of the basic block at which normal execution should continue. This is *not* how Itanium landingpads work, where cleanups and catches are executed after unwinding and clearing old function frames off the stack. The transition to a landingpad is *not* like a function call, and this is the only special control transfer used for Itanium EH. In retrospect, having exactly one kind of control transfer turns out to be a great design simplification. Go Itanium! Instead, all MSVC EH personality functions (x86, x64, ARM) cross (C++, SEH) are implemented with interval tables that express the nesting levels of various source constructs like destructors, try ranges, catch ranges, etc. When you rinse your program through LLVM IR today, this structure is what gets lost. New information ------------------------- Recently, we have discovered that the tables for __CxxFrameHandler3 have the additional constraint that the EH states assigned to a catch body must immediately follow the state numbers assigned to the try body. The natural scoping rules of C++ make it so that doing this numbering at the source level is trivial, but once we go to LLVM IR CFG soup, scopes are gone. If you want to know exactly what corner cases break down, search the bug database and mailing lists. The explanations are too long for this RFC. New representation ------------------------------ I propose adding the following new instructions, all of which (except for resume) are glued to the top of their basic blocks, just like landingpads. They all have an optional ‘unwind’ label operand, which provides the IR with a tree-like structure of what EH action to take after this EH action completes. The unwind label only participates in the formation of the CFG when used in a catch block, and in other blocks it is considered opaque, personality-specific information. If the unwind label is missing, then control leaves the function after the EH action is completed. If a function is inlined, EH blocks with missing unwind labels are wired up to the unwind label used by the inlined call site. The new representation is designed to be able to represent Itanium EH in case we want to converge on a single EH representation in LLVM and Clang. An IR pass can convert these actions to landingpads, typeid selector comparisons, and branches, which means we can phase this representation in on Windows at first and experiment with it slowly on other platforms. Over time, we can move the landingpad conversion lower and lower in the stack until it’s moved into DwarfEHPrepare. We’ll need to support landingpads at least until LLVM 4.0, but we may want to keep them because they are the natural representation for Itanium-style EH, and have a relatively low support burden. resume ------------- ; Old form still works, still means control is leaving the function. resume <valty> %val ; New form overloaded for intra-frame unwinding or resuming normal execution resume <valty> %val, label %nextaction ; New form for EH personalities that produce no value resume void Now resume takes an optional label operand which is the next EH action to run. The label must point to a block starting with an EH action. The various EH action blocks impose personality-specific rules about what the targets of the resume can be. catchblock --------------- %val = catchblock <valty> [i8* @typeid.int, i32 7, i32* %e.addr] to label %catch.int unwind label %nextaction The catchblock is a terminator that conditionally selects which block to execute based on the opaque operands interpreted by the personality function. If the exception is caught, the ‘to’ block is executed. If unwinding should continue, the ‘unwind’ block is executed. Because the catchblock is a terminator, no instructions can be inserted into a catchblock. The MSVC personality function requires more than just a pointer to RTTI data, so a variable list of operands is accepted. For an Itanium personality, only one RTTI operand is needed. The ‘unwind’ label of a catchblock must point to a catchend. catchendblock ---------------- catchend unwind label %nextaction The catchend is a terminator that unconditionally unwinds to the next action. It is merely a placeholder to help reconstruct which invokes were part of the catch blocks of a try. Invokes that are reached after a catchblock without following any unwind edges must transitively unwind to the first catchend block that the catchblock unwinds to. Executing such an invoke that does not transitively unwind to the correct catchend block has undefined behavior. cleanupblock -------------------- %val = cleanupblock <valty> unwind label %nextaction This is not a terminator, and control is expected to flow into a resume instruction which indicates which EH block runs next. If the resume instruction and the unwind label disagree, behavior is undefined. terminateblock ---------------------- ; for noexcept terminateblock [void ()* @std.terminate] unwind label %nextaction ; for exception specifications, throw(int) terminateblock [void ()* @__cxa_unexpected, @typeid.int, ...] unwind label %nextaction This is a terminator, and the unwind label is where execution will continue if the program continues execution. It also has an opaque, personality-specific list of constant operands interpreted by the backend of LLVM. The convention is that the first operand is the function to call to end the program, and the rest determine if the program should end. sehfilterblock? ------------------ One big hole in the new representation is SEH filter expressions. They present a major complication because they do not follow a stack discipline. Any EH action is reachable after an SEH filter runs. Because the CFG is so useless for optimization purposes, it’s better to outline the filter in the frontend and assume the filter can run during any potentially throwing function call. MSVC EH implementation strategy ---------------------------------------------- Skim this if you just need the semantics of the representation above, and not the implementation details. The new EH block representation allows WinEHPrepare to get a lot simpler. EH blocks should now look a lot more familiar, they are single entry, multi-exit regions of code. This is exactly equivalent to a function, and we can call them funclets. The plan is to generate code for the parent function first, skipping all exceptional blocks, and then generate separate MachineFunctions for each subfunction in turn. I repeat, we can stop doing outlining in IR. This was just a mistake, because I was afraid of grappling with CodeGen. WinEHPrepare will have two jobs now: 1. Mark down which basic blocks are reachable from which handler. Duplicate any blocks that are reachable from two handlers until each block belongs to exactly one funclet, pruning as many unreachable CFG edges as possible. 2. Demote SSA values that are defined in a funclet and used in another funclet. The instruction selection pass is the pass that builds MachineFunctions from IR Functions. This is the pass that will be responsible for the split. It will maintain information about the offsets of static allocas in FunctionLoweringInfo, and will throw it away when all funclets have been generated for this function. This means we don’t need to insert framerecover calls anymore. Generating EH state numbers for the TryBlockMap and StateUnwindTable is a matter of building a tree of EH blocks and invokes. Every unwind edge from an invoke or an EH block represents that the instruction is a child of the target block. If the unwind edge is empty, it is a child of the parent function, which is the root node of the tree. State numbers can be assigned by doing a DFS traversal where invokes are visited before EH blocks, and EH blocks can be visited in an arbitrary-but-deterministic order that vaguely corresponds to source order. Invokes are immediately assigned the current state number. Upon visiting an EH block, the state number is recorded as the “low” state of the block. All invokes are assigned this state number. The state number is incremented, and each child EH block is visited, passing in the state number and producing a new state number. The final state number is returned to the parent node. Example IR from Clang ---------------------------------------- The C++: struct Obj { ~Obj(); }; void f(int); void foo() noexcept { try { f(1); Obj o; f(2); } catch (int e) { f(3); try { f(4); } catch (...) { f(5); } } } The IR for __CxxFrameHandler3: define void @foo() personality i32 (...)* @__CxxFrameHandler3 { %e.addr = alloca i32 invoke void @f(i32 1) to label %cont1 unwind label %maycatch.int cont1: invoke void @f(i32 2) to label %cont2 unwind label %cleanup.Obj cont2: call void @~Obj() br label %return return: ret void cleanup.Obj: cleanupblock unwind label %maycatch.int call void @~Obj() resume label %maycatch.int maycatch.int: catchblock void [i8* @typeid.int, i32 7, i32* %e.addr] to label %catch.int unwind label %catchend1 catch.int: invoke void @f(i32 3) to label %cont3 unwind label %catchend1 cont3: invoke void @f(i32 4) to label %cont4 unwind label %maycatch.all cont4: resume label %return maycatch.all: catchblock void [i8* null, i32 0, i8* null] to label %catch.all unwind label %catchend2 catch.all: invoke void @f(i32 5) to label %cont5 unwind label %catchend2 cont5: resume label %cont4 catchend2: catchendblock unwind label %catchend1 catchend1: catchendblock unwind label %callterminate callterminate: terminateblock [void ()* @std.terminate] }>From this IR, we can recover the original scoped nesting form that thetable formation requires. I think that covers it. Feedback welcome. :) -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150515/52a05a58/attachment.html>
Kaylor, Andrew
2015-May-16 00:27 UTC
[LLVMdev] New EH representation for MSVC compatibility
I like the way this sorts out with regard to funclet code generation. It feels very natural for Windows EH, though obviously not as natural for non-Windows targets and I think it is likely to block some optimizations that are currently possible with those targets.> If the unwind label is missing, then control leaves the function after the EH action is completed. If a function is inlined, EH blocks with missing unwind labels are wired up to the unwind label used by the inlined call site.Is this saying that a “missing” unwind label corresponds to telling the runtime to continue the search at the next frame? Your example looks wrong in this regard, unless I’m misunderstanding it. It looks like any exceptions that aren’t caught in that function will lead to a terminate call.> Invokes that are reached after a catchblock without following any unwind edges must transitively unwind to the first catchend block that the catchblock unwinds to.I’m not sure I understand this correctly. In particular, I’m confused about the roles of resume and catchend.> %val = cleanupblock <valty> unwind label %nextactionWhy isn’t this a terminator? It seems like it performs the same sort of role as catchblock, except presumably it is always entered. I suppose that’s probably the answer to my question, but it strikes me as an ambiguity in the scheme. The catchblock instruction is more or less a conditional branch whereas the cleanupblock is more like a label with a hint as to an unconditional branch that will happen later. And I guess that’s another thing that bothers me -- a resume instruction at the end of a catch implementation means something subtly different than a resume instruction at the end of a cleanup implementation. From: Reid Kleckner [mailto:rnk at google.com] Sent: Friday, May 15, 2015 3:38 PM To: LLVM Developers Mailing List; Bill Wendling; Nick Lewycky; Kaylor, Andrew Subject: RFC: New EH representation for MSVC compatibility After a long tale of sorrow and woe, my colleagues and I stand here before you defeated. The Itanium EH representation is not amenable to implementing MSVC-compatible exceptions. We need a new representation that preserves information about how try-catch blocks are nested. WinEH background ------------------------------- Skip this if you already know a lot about Windows exceptions. On Windows, every exceptional action that you can imagine is a function call. Throwing an exception is a call. Destructor cleanups and finally blocks are calls to outlined functions that run the cleanup code. Even catching an exception is implemented as an outlined catch handler function which returns the address of the basic block at which normal execution should continue. This is *not* how Itanium landingpads work, where cleanups and catches are executed after unwinding and clearing old function frames off the stack. The transition to a landingpad is *not* like a function call, and this is the only special control transfer used for Itanium EH. In retrospect, having exactly one kind of control transfer turns out to be a great design simplification. Go Itanium! Instead, all MSVC EH personality functions (x86, x64, ARM) cross (C++, SEH) are implemented with interval tables that express the nesting levels of various source constructs like destructors, try ranges, catch ranges, etc. When you rinse your program through LLVM IR today, this structure is what gets lost. New information ------------------------- Recently, we have discovered that the tables for __CxxFrameHandler3 have the additional constraint that the EH states assigned to a catch body must immediately follow the state numbers assigned to the try body. The natural scoping rules of C++ make it so that doing this numbering at the source level is trivial, but once we go to LLVM IR CFG soup, scopes are gone. If you want to know exactly what corner cases break down, search the bug database and mailing lists. The explanations are too long for this RFC. New representation ------------------------------ I propose adding the following new instructions, all of which (except for resume) are glued to the top of their basic blocks, just like landingpads. They all have an optional ‘unwind’ label operand, which provides the IR with a tree-like structure of what EH action to take after this EH action completes. The unwind label only participates in the formation of the CFG when used in a catch block, and in other blocks it is considered opaque, personality-specific information. If the unwind label is missing, then control leaves the function after the EH action is completed. If a function is inlined, EH blocks with missing unwind labels are wired up to the unwind label used by the inlined call site. The new representation is designed to be able to represent Itanium EH in case we want to converge on a single EH representation in LLVM and Clang. An IR pass can convert these actions to landingpads, typeid selector comparisons, and branches, which means we can phase this representation in on Windows at first and experiment with it slowly on other platforms. Over time, we can move the landingpad conversion lower and lower in the stack until it’s moved into DwarfEHPrepare. We’ll need to support landingpads at least until LLVM 4.0, but we may want to keep them because they are the natural representation for Itanium-style EH, and have a relatively low support burden. resume ------------- ; Old form still works, still means control is leaving the function. resume <valty> %val ; New form overloaded for intra-frame unwinding or resuming normal execution resume <valty> %val, label %nextaction ; New form for EH personalities that produce no value resume void Now resume takes an optional label operand which is the next EH action to run. The label must point to a block starting with an EH action. The various EH action blocks impose personality-specific rules about what the targets of the resume can be. catchblock --------------- %val = catchblock <valty> [i8* @typeid.int<http://typeid.int>, i32 7, i32* %e.addr] to label %catch.int<http://catch.int> unwind label %nextaction The catchblock is a terminator that conditionally selects which block to execute based on the opaque operands interpreted by the personality function. If the exception is caught, the ‘to’ block is executed. If unwinding should continue, the ‘unwind’ block is executed. Because the catchblock is a terminator, no instructions can be inserted into a catchblock. The MSVC personality function requires more than just a pointer to RTTI data, so a variable list of operands is accepted. For an Itanium personality, only one RTTI operand is needed. The ‘unwind’ label of a catchblock must point to a catchend. catchendblock ---------------- catchend unwind label %nextaction The catchend is a terminator that unconditionally unwinds to the next action. It is merely a placeholder to help reconstruct which invokes were part of the catch blocks of a try. Invokes that are reached after a catchblock without following any unwind edges must transitively unwind to the first catchend block that the catchblock unwinds to. Executing such an invoke that does not transitively unwind to the correct catchend block has undefined behavior. cleanupblock -------------------- %val = cleanupblock <valty> unwind label %nextaction This is not a terminator, and control is expected to flow into a resume instruction which indicates which EH block runs next. If the resume instruction and the unwind label disagree, behavior is undefined. terminateblock ---------------------- ; for noexcept terminateblock [void ()* @std.terminate] unwind label %nextaction ; for exception specifications, throw(int) terminateblock [void ()* @__cxa_unexpected, @typeid.int<http://typeid.int>, ...] unwind label %nextaction This is a terminator, and the unwind label is where execution will continue if the program continues execution. It also has an opaque, personality-specific list of constant operands interpreted by the backend of LLVM. The convention is that the first operand is the function to call to end the program, and the rest determine if the program should end. sehfilterblock? ------------------ One big hole in the new representation is SEH filter expressions. They present a major complication because they do not follow a stack discipline. Any EH action is reachable after an SEH filter runs. Because the CFG is so useless for optimization purposes, it’s better to outline the filter in the frontend and assume the filter can run during any potentially throwing function call. MSVC EH implementation strategy ---------------------------------------------- Skim this if you just need the semantics of the representation above, and not the implementation details. The new EH block representation allows WinEHPrepare to get a lot simpler. EH blocks should now look a lot more familiar, they are single entry, multi-exit regions of code. This is exactly equivalent to a function, and we can call them funclets. The plan is to generate code for the parent function first, skipping all exceptional blocks, and then generate separate MachineFunctions for each subfunction in turn. I repeat, we can stop doing outlining in IR. This was just a mistake, because I was afraid of grappling with CodeGen. WinEHPrepare will have two jobs now: 1. Mark down which basic blocks are reachable from which handler. Duplicate any blocks that are reachable from two handlers until each block belongs to exactly one funclet, pruning as many unreachable CFG edges as possible. 2. Demote SSA values that are defined in a funclet and used in another funclet. The instruction selection pass is the pass that builds MachineFunctions from IR Functions. This is the pass that will be responsible for the split. It will maintain information about the offsets of static allocas in FunctionLoweringInfo, and will throw it away when all funclets have been generated for this function. This means we don’t need to insert framerecover calls anymore. Generating EH state numbers for the TryBlockMap and StateUnwindTable is a matter of building a tree of EH blocks and invokes. Every unwind edge from an invoke or an EH block represents that the instruction is a child of the target block. If the unwind edge is empty, it is a child of the parent function, which is the root node of the tree. State numbers can be assigned by doing a DFS traversal where invokes are visited before EH blocks, and EH blocks can be visited in an arbitrary-but-deterministic order that vaguely corresponds to source order. Invokes are immediately assigned the current state number. Upon visiting an EH block, the state number is recorded as the “low” state of the block. All invokes are assigned this state number. The state number is incremented, and each child EH block is visited, passing in the state number and producing a new state number. The final state number is returned to the parent node. Example IR from Clang ---------------------------------------- The C++: struct Obj { ~Obj(); }; void f(int); void foo() noexcept { try { f(1); Obj o; f(2); } catch (int e) { f(3); try { f(4); } catch (...) { f(5); } } } The IR for __CxxFrameHandler3: define void @foo() personality i32 (...)* @__CxxFrameHandler3 { %e.addr = alloca i32 invoke void @f(i32 1) to label %cont1 unwind label %maycatch.int<http://maycatch.int> cont1: invoke void @f(i32 2) to label %cont2 unwind label %cleanup.Obj cont2: call void @~Obj() br label %return return: ret void cleanup.Obj: cleanupblock unwind label %maycatch.int<http://maycatch.int> call void @~Obj() resume label %maycatch.int<http://maycatch.int> maycatch.int<http://maycatch.int>: catchblock void [i8* @typeid.int<http://typeid.int>, i32 7, i32* %e.addr] to label %catch.int<http://catch.int> unwind label %catchend1 catch.int<http://catch.int>: invoke void @f(i32 3) to label %cont3 unwind label %catchend1 cont3: invoke void @f(i32 4) to label %cont4 unwind label %maycatch.all cont4: resume label %return maycatch.all: catchblock void [i8* null, i32 0, i8* null] to label %catch.all unwind label %catchend2 catch.all: invoke void @f(i32 5) to label %cont5 unwind label %catchend2 cont5: resume label %cont4 catchend2: catchendblock unwind label %catchend1 catchend1: catchendblock unwind label %callterminate callterminate: terminateblock [void ()* @std.terminate] } From this IR, we can recover the original scoped nesting form that the table formation requires. I think that covers it. Feedback welcome. :) -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150516/a5f94411/attachment.html>
Steve Cheng
2015-May-16 14:29 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
On 2015-05-15 18:37:58 -0400, Reid Kleckner said:> After a long tale of sorrow and woe, my colleagues and I stand here > before you defeated. The Itanium EH representation is not amenable to > implementing MSVC-compatible exceptions. We need a new representation > that preserves information about how try-catch blocks are nested.Hi, Newbie here. This must be a dumb question, but it's not something I can understand from reading the design documents and RFCs. Why don't we write and use our own personality function, and then we avoid all these restrictions on the interval tables? On Windows, we would still have to catch exceptions with SEH, of course. But SEH should be language-independent, in the sense that it concerns only unwinding for the "low level" parts of the ABI, like restoring non-volatile registers. It doesn't seem to make sense that LLVM, being a language-independent IR, should concern itself with personality functions specific to Microsoft's C++ run-time library. I understand we want to link compatibility with object code from Visual Studio, but I didn't think that the personality-specific unwind tables were actually an ABI "artifact". I mean, let's say you compile a function in Visual Studio, the result is a function with some mangled symbol that other object code can make references through the linker. But the other object code never incestuously refers to the unwind tables of the function it calls, right? I'm speaking from the point of view of an implementor of a new programming language who wants to interoperate with C++. I've already got code that can decode the Microsoft RTTI info coming from C++ exceptions, and the pointers to those can be extracted with SEH GetExceptionPointers, etc. To support catching exceptions in my language, or at least calling cleanups while unwinding, I really don't care about the C++ personality function. After all my language might not even have the concept of (nested) try/catch blocks. The custom personality function does have to be supplied as part of the run-time, but as a frontend implementor I'm prepared to have to write a run-time anyway. Steve
Reid Kleckner
2015-May-18 17:32 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
On Sat, May 16, 2015 at 7:29 AM, Steve Cheng <steve.ckp at gmail.com> wrote:> On 2015-05-15 18:37:58 -0400, Reid Kleckner said: > > After a long tale of sorrow and woe, my colleagues and I stand here >> before you defeated. The Itanium EH representation is not amenable to >> implementing MSVC-compatible exceptions. We need a new representation that >> preserves information about how try-catch blocks are nested. >> > > Hi, > > Newbie here. This must be a dumb question, but it's not something I can > understand from reading the design documents and RFCs. > > Why don't we write and use our own personality function, and then we avoid > all these restrictions on the interval tables? On Windows, we would still > have to catch exceptions with SEH, of course. But SEH should be > language-independent, in the sense that it concerns only unwinding for the > "low level" parts of the ABI, like restoring non-volatile registers. It > doesn't seem to make sense that LLVM, being a language-independent IR, > should concern itself with personality functions specific to Microsoft's > C++ run-time library. > > I understand we want to link compatibility with object code from Visual > Studio, but I didn't think that the personality-specific unwind tables were > actually an ABI "artifact". I mean, let's say you compile a function in > Visual Studio, the result is a function with some mangled symbol that other > object code can make references through the linker. But the other object > code never incestuously refers to the unwind tables of the function it > calls, right? > > I'm speaking from the point of view of an implementor of a new programming > language who wants to interoperate with C++. I've already got code that can > decode the Microsoft RTTI info coming from C++ exceptions, and the pointers > to those can be extracted with SEH GetExceptionPointers, etc. To support > catching exceptions in my language, or at least calling cleanups while > unwinding, I really don't care about the C++ personality function. After > all my language might not even have the concept of (nested) try/catch > blocks. The custom personality function does have to be supplied as part of > the run-time, but as a frontend implementor I'm prepared to have to write a > run-time anyway. >Right, doing our own personality function is possible, but still has half the challenge of using __CxxFrameHandler3, and introduces a new runtime dependency that isn't there currently. Given that it won't save that much work, I'd rather not introduce a dependency that wasn't there before. The reason it's still hard is that you have to split the main function up into more than one subfunction. The exception object is allocated in the frame of the function calling __CxxThrow, and it has to stay alive until control leaves the catch block receiving the exception. This is different from Itanium, where the exception object is constructed in heap memory and the pointer is saved in TLS. If this were not the case, we'd use the __gxx_personaltity_v0-style landingpad approach and make a new personality variant that understands MS RTTI. We could try to do all this outlining in Clang, but that blocks a lot of LLVM optimizations. Any object with a destructor (std::string) is now escaped into the funclet that calls the destructor, and simple transformations (SROA) require interprocedural analysis. This affects the code on the normal code path and not just the exceptional path. While EH constructs like try / catch are fairly rare in C++, destructor cleanups are very, very common, and I'd rather not pessimize so much code. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150518/cd2adcd8/attachment.html>
Kaylor, Andrew
2015-May-18 18:02 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
We already have something like what you describe in the form of mingw support. It runs on Windows and handles exceptions using a DWARF-style personality function and (I think) an LLVM-provided implementation of the libc++abi library. What this doesn't do is provide interoperability with MSVC-compiled objects. For instance, you can't throw an exception from MSVC-compiled code and catch it with clang/LLVM-compiled code or vice versa. With the (too fragile) implementation we have in place right now you can do that (at least in cases that don't break for other reasons), and we want to be able to continue that capability with a new, more robust, solution. -Andy -----Original Message----- From: llvmdev-bounces at cs.uiuc.edu [mailto:llvmdev-bounces at cs.uiuc.edu] On Behalf Of Steve Cheng Sent: Saturday, May 16, 2015 7:30 AM To: llvmdev at cs.uiuc.edu Subject: Re: [LLVMdev] RFC: New EH representation for MSVC compatibility On 2015-05-15 18:37:58 -0400, Reid Kleckner said:> After a long tale of sorrow and woe, my colleagues and I stand here > before you defeated. The Itanium EH representation is not amenable to > implementing MSVC-compatible exceptions. We need a new representation > that preserves information about how try-catch blocks are nested.Hi, Newbie here. This must be a dumb question, but it's not something I can understand from reading the design documents and RFCs. Why don't we write and use our own personality function, and then we avoid all these restrictions on the interval tables? On Windows, we would still have to catch exceptions with SEH, of course. But SEH should be language-independent, in the sense that it concerns only unwinding for the "low level" parts of the ABI, like restoring non-volatile registers. It doesn't seem to make sense that LLVM, being a language-independent IR, should concern itself with personality functions specific to Microsoft's C++ run-time library. I understand we want to link compatibility with object code from Visual Studio, but I didn't think that the personality-specific unwind tables were actually an ABI "artifact". I mean, let's say you compile a function in Visual Studio, the result is a function with some mangled symbol that other object code can make references through the linker. But the other object code never incestuously refers to the unwind tables of the function it calls, right? I'm speaking from the point of view of an implementor of a new programming language who wants to interoperate with C++. I've already got code that can decode the Microsoft RTTI info coming from C++ exceptions, and the pointers to those can be extracted with SEH GetExceptionPointers, etc. To support catching exceptions in my language, or at least calling cleanups while unwinding, I really don't care about the C++ personality function. After all my language might not even have the concept of (nested) try/catch blocks. The custom personality function does have to be supplied as part of the run-time, but as a frontend implementor I'm prepared to have to write a run-time anyway. Steve _______________________________________________ LLVM Developers mailing list LLVMdev at cs.uiuc.edu http://llvm.cs.uiuc.edu http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev
Reid Kleckner
2015-May-18 18:53 UTC
[LLVMdev] New EH representation for MSVC compatibility
On Fri, May 15, 2015 at 5:27 PM, Kaylor, Andrew <andrew.kaylor at intel.com> wrote:> I like the way this sorts out with regard to funclet code generation. > It feels very natural for Windows EH, though obviously not as natural for > non-Windows targets and I think it is likely to block some optimizations > that are currently possible with those targets. >Right, it will block some of today's optimizations by default. I'm OK with this because we can add those optimizations back by checking if the personality is Itanium-family (sjlj, arm, or dwarf), and optimizing EH codepaths is not usually performance critical.> > If the unwind label is missing, then control leaves the function after > the EH action is completed. If a function is inlined, EH blocks with > missing unwind labels are wired up to the unwind label used by the inlined > call site. > > > > Is this saying that a “missing” unwind label corresponds to telling the > runtime to continue the search at the next frame? >Yep. For the C++ data structure it would simply be a missing or null operand.> Your example looks wrong in this regard, unless I’m misunderstanding it. > It looks like any exceptions that aren’t caught in that function will lead > to a terminate call. >Well, those are the intended semantics of noexcept, unless I'm mistaken. And the inliner *should* wire up the unwind edge of the terminateblock to the unwind edge of the inlined invoke instruction, because it's natural to lower terminateblock to a catch-all plus termination call block. I wanted to express that as data, though, so that in the common case that the noexcept function is not inlined, we can simply flip the "noexcept" bit in the EH info. There's a similar optimization we can do for Itanium that we miss today.> Invokes that are reached after a catchblock without following any unwind > edges must transitively unwind to the first catchend block that the > catchblock unwinds to. > > > > I’m not sure I understand this correctly. In particular, I’m confused > about the roles of resume and catchend. >catchendblock is really there to support figuring out which calls were inside the catch scope. resume has two roles: moving to the next EH action after a cleanup, and transitioning from the catch block back to normal control flow. Some of my coworkers said it should be split into two instructions for each purpose, and I could go either way.> > %val = cleanupblock <valty> unwind label %nextaction > > > > Why isn’t this a terminator? It seems like it performs the same sort of > role as catchblock, except presumably it is always entered. I suppose > that’s probably the answer to my question, but it strikes me as an > ambiguity in the scheme. The catchblock instruction is more or less a > conditional branch whereas the cleanupblock is more like a label with a > hint as to an unconditional branch that will happen later. And I guess > that’s another thing that bothers me -- a resume instruction at the end of > a catch implementation means something subtly different than a resume > instruction at the end of a cleanup implementation. >Yeah, reusing the resume instruction for both these things might not be good. I liked not having to add more terminator instructions, though. I think most optimizations will not care about the differences between the two kinds of resume. For CFG formation purposes, it either has one successor or none, and that's enough for most users. I felt that cleanupblock should not be a terminator because it keeps the IR more concise. The smaller an IR construct is, the more people seem to understand it, so I tried to go with that. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150518/04fb58fd/attachment.html>
Joseph Tremoulet
2015-May-18 19:03 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
Hi, Thanks for sending this out. We're looking forward to seeing this come about, since we need funclet separation for LLILC as well (and I have cycles to spend on it, if that would be helpful). Some questions about the new proposal: - Do the new forms of resume have any implied read/write side-effects, or do they work just like a branch? In particular, I'm wondering what prevents reordering a call across a resume. Is this just something that code motion optimizations are expected to check for explicitly to avoid introducing UB per the "Executing such an invoke [or call] that does not transitively unwind to the correct catchend block has undefined behavior" rule? - Does LLVM already have other examples of terminators that are glued to the top of their basic blocks, or will these be the first? I ask because it's a somewhat nonstandard thing (a block in the CFG that can't have instructions added to it) that any code placement algorithms (PRE, PGO probe insertion, Phi elimination, RA spill/copy placement, etc.) may need to be adjusted for. The adjustments aren't terrible (conceptually it's no worse than having unsplittable edges from each of the block's preds to each of its succs), but it's something to be aware of. - Since this will require auditing any code with special processing of resume instructions to make sure it handles the new resume forms correctly, I wonder if it might be helpful to give resume (or the new forms of it) a different name, since then it would be immediately clear which code has/hasn't been updated to the new model. - Is the idea that a resume (of the sort that resumes normal execution) ends only one catch/cleanup, or that it can end any number of them? Did you consider having it end a single one, and giving it a source that references (in a non-flow-edge-inducing way) the related catchend? If you did that, then: + The code to find a funclet region could terminate with confidence when it reaches this sort of resume, and + Resumes which exit different catches would have different sources and thus couldn't be merged, reducing the need to undo tail-merging with code duplication in EH preparation (by blocking the tail-merging in the first place) - What is the plan for cleanup/__finally code that may be executed on either normal paths or EH paths? One could imagine a number of options here: + require the IR producer to duplicate code for EH vs non-EH paths + duplicate code for EH vs non-EH paths during EH preparation + use resume to exit these even on the non-EH paths; code doesn't need to be duplicated (but could and often would be as an optimization for hot/non-EH paths), and normal paths could call the funclet at the end of the day and it isn't clear to me which you're suggesting. Requiring duplication can worst-case quadratically expand the code (in that if you have n levels of cleanup-inside-cleanup-inside-cleanup-…, and each cleanup has k code bytes outside the next-inner cleanup, after duplication you'll have k*n + k*(n-1) + … or O(k*n^2) bytes total [compared to k*n before duplication]), which I'd think could potentially be a problem in pathological inputs. Thanks -Joseph From: llvmdev-bounces at cs.uiuc.edu [mailto:llvmdev-bounces at cs.uiuc.edu] On Behalf Of Reid Kleckner Sent: Friday, May 15, 2015 6:38 PM To: LLVM Developers Mailing List; Bill Wendling; Nick Lewycky; Kaylor, Andrew Subject: [LLVMdev] RFC: New EH representation for MSVC compatibility After a long tale of sorrow and woe, my colleagues and I stand here before you defeated. The Itanium EH representation is not amenable to implementing MSVC-compatible exceptions. We need a new representation that preserves information about how try-catch blocks are nested. WinEH background ------------------------------- Skip this if you already know a lot about Windows exceptions. On Windows, every exceptional action that you can imagine is a function call. Throwing an exception is a call. Destructor cleanups and finally blocks are calls to outlined functions that run the cleanup code. Even catching an exception is implemented as an outlined catch handler function which returns the address of the basic block at which normal execution should continue. This is *not* how Itanium landingpads work, where cleanups and catches are executed after unwinding and clearing old function frames off the stack. The transition to a landingpad is *not* like a function call, and this is the only special control transfer used for Itanium EH. In retrospect, having exactly one kind of control transfer turns out to be a great design simplification. Go Itanium! Instead, all MSVC EH personality functions (x86, x64, ARM) cross (C++, SEH) are implemented with interval tables that express the nesting levels of various source constructs like destructors, try ranges, catch ranges, etc. When you rinse your program through LLVM IR today, this structure is what gets lost. New information ------------------------- Recently, we have discovered that the tables for __CxxFrameHandler3 have the additional constraint that the EH states assigned to a catch body must immediately follow the state numbers assigned to the try body. The natural scoping rules of C++ make it so that doing this numbering at the source level is trivial, but once we go to LLVM IR CFG soup, scopes are gone. If you want to know exactly what corner cases break down, search the bug database and mailing lists. The explanations are too long for this RFC. New representation ------------------------------ I propose adding the following new instructions, all of which (except for resume) are glued to the top of their basic blocks, just like landingpads. They all have an optional ‘unwind’ label operand, which provides the IR with a tree-like structure of what EH action to take after this EH action completes. The unwind label only participates in the formation of the CFG when used in a catch block, and in other blocks it is considered opaque, personality-specific information. If the unwind label is missing, then control leaves the function after the EH action is completed. If a function is inlined, EH blocks with missing unwind labels are wired up to the unwind label used by the inlined call site. The new representation is designed to be able to represent Itanium EH in case we want to converge on a single EH representation in LLVM and Clang. An IR pass can convert these actions to landingpads, typeid selector comparisons, and branches, which means we can phase this representation in on Windows at first and experiment with it slowly on other platforms. Over time, we can move the landingpad conversion lower and lower in the stack until it’s moved into DwarfEHPrepare. We’ll need to support landingpads at least until LLVM 4.0, but we may want to keep them because they are the natural representation for Itanium-style EH, and have a relatively low support burden. resume ------------- ; Old form still works, still means control is leaving the function. resume <valty> %val ; New form overloaded for intra-frame unwinding or resuming normal execution resume <valty> %val, label %nextaction ; New form for EH personalities that produce no value resume void Now resume takes an optional label operand which is the next EH action to run. The label must point to a block starting with an EH action. The various EH action blocks impose personality-specific rules about what the targets of the resume can be. catchblock --------------- %val = catchblock <valty> [i8* @typeid.int<http://typeid.int>, i32 7, i32* %e.addr] to label %catch.int<http://catch.int> unwind label %nextaction The catchblock is a terminator that conditionally selects which block to execute based on the opaque operands interpreted by the personality function. If the exception is caught, the ‘to’ block is executed. If unwinding should continue, the ‘unwind’ block is executed. Because the catchblock is a terminator, no instructions can be inserted into a catchblock. The MSVC personality function requires more than just a pointer to RTTI data, so a variable list of operands is accepted. For an Itanium personality, only one RTTI operand is needed. The ‘unwind’ label of a catchblock must point to a catchend. catchendblock ---------------- catchend unwind label %nextaction The catchend is a terminator that unconditionally unwinds to the next action. It is merely a placeholder to help reconstruct which invokes were part of the catch blocks of a try. Invokes that are reached after a catchblock without following any unwind edges must transitively unwind to the first catchend block that the catchblock unwinds to. Executing such an invoke that does not transitively unwind to the correct catchend block has undefined behavior. cleanupblock -------------------- %val = cleanupblock <valty> unwind label %nextaction This is not a terminator, and control is expected to flow into a resume instruction which indicates which EH block runs next. If the resume instruction and the unwind label disagree, behavior is undefined. terminateblock ---------------------- ; for noexcept terminateblock [void ()* @std.terminate] unwind label %nextaction ; for exception specifications, throw(int) terminateblock [void ()* @__cxa_unexpected, @typeid.int<http://typeid.int>, ...] unwind label %nextaction This is a terminator, and the unwind label is where execution will continue if the program continues execution. It also has an opaque, personality-specific list of constant operands interpreted by the backend of LLVM. The convention is that the first operand is the function to call to end the program, and the rest determine if the program should end. sehfilterblock? ------------------ One big hole in the new representation is SEH filter expressions. They present a major complication because they do not follow a stack discipline. Any EH action is reachable after an SEH filter runs. Because the CFG is so useless for optimization purposes, it’s better to outline the filter in the frontend and assume the filter can run during any potentially throwing function call. MSVC EH implementation strategy ---------------------------------------------- Skim this if you just need the semantics of the representation above, and not the implementation details. The new EH block representation allows WinEHPrepare to get a lot simpler. EH blocks should now look a lot more familiar, they are single entry, multi-exit regions of code. This is exactly equivalent to a function, and we can call them funclets. The plan is to generate code for the parent function first, skipping all exceptional blocks, and then generate separate MachineFunctions for each subfunction in turn. I repeat, we can stop doing outlining in IR. This was just a mistake, because I was afraid of grappling with CodeGen. WinEHPrepare will have two jobs now: 1. Mark down which basic blocks are reachable from which handler. Duplicate any blocks that are reachable from two handlers until each block belongs to exactly one funclet, pruning as many unreachable CFG edges as possible. 2. Demote SSA values that are defined in a funclet and used in another funclet. The instruction selection pass is the pass that builds MachineFunctions from IR Functions. This is the pass that will be responsible for the split. It will maintain information about the offsets of static allocas in FunctionLoweringInfo, and will throw it away when all funclets have been generated for this function. This means we don’t need to insert framerecover calls anymore. Generating EH state numbers for the TryBlockMap and StateUnwindTable is a matter of building a tree of EH blocks and invokes. Every unwind edge from an invoke or an EH block represents that the instruction is a child of the target block. If the unwind edge is empty, it is a child of the parent function, which is the root node of the tree. State numbers can be assigned by doing a DFS traversal where invokes are visited before EH blocks, and EH blocks can be visited in an arbitrary-but-deterministic order that vaguely corresponds to source order. Invokes are immediately assigned the current state number. Upon visiting an EH block, the state number is recorded as the “low” state of the block. All invokes are assigned this state number. The state number is incremented, and each child EH block is visited, passing in the state number and producing a new state number. The final state number is returned to the parent node. Example IR from Clang ---------------------------------------- The C++: struct Obj { ~Obj(); }; void f(int); void foo() noexcept { try { f(1); Obj o; f(2); } catch (int e) { f(3); try { f(4); } catch (...) { f(5); } } } The IR for __CxxFrameHandler3: define void @foo() personality i32 (...)* @__CxxFrameHandler3 { %e.addr = alloca i32 invoke void @f(i32 1) to label %cont1 unwind label %maycatch.int<http://maycatch.int> cont1: invoke void @f(i32 2) to label %cont2 unwind label %cleanup.Obj cont2: call void @~Obj() br label %return return: ret void cleanup.Obj: cleanupblock unwind label %maycatch.int<http://maycatch.int> call void @~Obj() resume label %maycatch.int<http://maycatch.int> maycatch.int<http://maycatch.int>: catchblock void [i8* @typeid.int<http://typeid.int>, i32 7, i32* %e.addr] to label %catch.int<http://catch.int> unwind label %catchend1 catch.int<http://catch.int>: invoke void @f(i32 3) to label %cont3 unwind label %catchend1 cont3: invoke void @f(i32 4) to label %cont4 unwind label %maycatch.all cont4: resume label %return maycatch.all: catchblock void [i8* null, i32 0, i8* null] to label %catch.all unwind label %catchend2 catch.all: invoke void @f(i32 5) to label %cont5 unwind label %catchend2 cont5: resume label %cont4 catchend2: catchendblock unwind label %catchend1 catchend1: catchendblock unwind label %callterminate callterminate: terminateblock [void ()* @std.terminate] } From this IR, we can recover the original scoped nesting form that the table formation requires. I think that covers it. Feedback welcome. :) -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150518/923bd46a/attachment.html>
Reid Kleckner
2015-May-18 21:35 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
On Mon, May 18, 2015 at 12:03 PM, Joseph Tremoulet <jotrem at microsoft.com> wrote:> Hi, > > > > Thanks for sending this out. We're looking forward to seeing this come > about, since we need funclet separation for LLILC as well (and I have > cycles to spend on it, if that would be helpful). > > > > Some questions about the new proposal: > > > > - Do the new forms of resume have any implied read/write side-effects, or > do they work just like a branch? In particular, I'm wondering what > prevents reordering a call across a resume. Is this just something that > code motion optimizations are expected to check for explicitly to avoid > introducing UB per the "Executing such an invoke [or call] that does not > transitively unwind to the correct catchend block has undefined behavior" > rule? >Yes, crossing a resume from a catchblock ends the lifetime of the exception object, so I'd say that's a "writes escaped memory" constraint. That said, a resume after a cleanupblock doesn't, but I'm not sure it's worth having this kind of fine-grained analysis. I'm OK teaching SimplifyCFG to combine cleanupblocks and leaving it at that.> - Does LLVM already have other examples of terminators that are glued to > the top of their basic blocks, or will these be the first? I ask because > it's a somewhat nonstandard thing (a block in the CFG that can't have > instructions added to it) that any code placement algorithms (PRE, PGO > probe insertion, Phi elimination, RA spill/copy placement, etc.) may need > to be adjusted for. The adjustments aren't terrible (conceptually it's no > worse than having unsplittable edges from each of the block's preds to each > of its succs), but it's something to be aware of. >No, LLVM doesn't have anything like this yet. It does have unsplittable critical edges, which can come from indirectbr and the unwind edge of an invoke. I don't think it'll be too hard to teach transforms how to deal with one more, but maybe that's unrealistic youthful optimism. :) - Since this will require auditing any code with special processing of> resume instructions to make sure it handles the new resume forms correctly, > I wonder if it might be helpful to give resume (or the new forms of it) a > different name, since then it would be immediately clear which code > has/hasn't been updated to the new model. >There aren't that many references to ResumeInst across LLVM, so I'm not too scared. I'm not married to reusing 'resume', other candidate names include 'unwind' and 'continue', and I'd like more ideas.> - Is the idea that a resume (of the sort that resumes normal execution) > ends only one catch/cleanup, or that it can end any number of them? Did > you consider having it end a single one, and giving it a source that > references (in a non-flow-edge-inducing way) the related catchend? If you > did that, then: > > + The code to find a funclet region could terminate with confidence when > it reaches this sort of resume, and > > + Resumes which exit different catches would have different sources and > thus couldn't be merged, reducing the need to undo tail-merging with code > duplication in EH preparation (by blocking the tail-merging in the first > place) >We already have something like this for cleanupblocks because the resume target and unwind label of the cleanupblock must match. It isn't as strong as having a reference to the catchblock itself, because tail merging could kick in like you mention. Undoing this would be and currently is the job of WinEHPrepare. I guess I felt like the extra representational complexity wasn't worth the confidence that it would buy us.> - What is the plan for cleanup/__finally code that may be executed on > either normal paths or EH paths? One could imagine a number of options > here: > > + require the IR producer to duplicate code for EH vs non-EH paths > > + duplicate code for EH vs non-EH paths during EH preparation > > + use resume to exit these even on the non-EH paths; code doesn't need to > be duplicated (but could and often would be as an optimization for > hot/non-EH paths), and normal paths could call the funclet at the end of > the day > > and it isn't clear to me which you're suggesting. Requiring duplication > can worst-case quadratically expand the code (in that if you have n levels > of cleanup-inside-cleanup-inside-cleanup-…, and each cleanup has k code > bytes outside the next-inner cleanup, after duplication you'll have k*n + > k*(n-1) + … or O(k*n^2) bytes total [compared to k*n before duplication]), > which I'd think could potentially be a problem in pathological inputs. >I want to have separate normal and exceptional codepaths, but at -O0 all the cleanup work should be bundled up in a function that gets called from both those paths. Today, for C++ destructors, we emit two calls to the destructor: one on the normal path and one on the EH path. For __finally, we outline the finally body early in clang and emit two calls to it as before, but passing in the frameaddress as an argument. I think this is a great place to be. It keeps our -O0 code size small, simplifies the implementation, and allows us to inline one or both call sites if we think it's profitable. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150518/07c33350/attachment.html>
John McCall
2015-Jun-04 18:37 UTC
[LLVMdev] RFC: New EH representation for MSVC compatibility
> On May 15, 2015, at 3:37 PM, Reid Kleckner <rnk at google.com> wrote: > After a long tale of sorrow and woe, my colleagues and I stand here before you defeated. The Itanium EH representation is not amenable to implementing MSVC-compatible exceptions. We need a new representation that preserves information about how try-catch blocks are nested.A couple quick apologies: this response is pretty late, and for the same reasons I’ve only been able to skim the rest of the thread. I think the basic ideas in this proposal seem reasonable, although it seems they may have evolved a bit over the course of the thread. A few points do stand out to me:> Instead, all MSVC EH personality functions (x86, x64, ARM) cross (C++, SEH) are implemented with interval tables that express the nesting levels of various source constructs like destructors, try ranges, catch ranges, etc. When you rinse your program through LLVM IR today, this structure is what gets lost.Yes. It seems to me that the main additional thing that your proposed IR preserves is the ability to very easily reconstruct the tree of control flow. Correct?> New information > ------------------------- > > Recently, we have discovered that the tables for __CxxFrameHandler3 have the additional constraint that the EH states assigned to a catch body must immediately follow the state numbers assigned to the try body. The natural scoping rules of C++ make it so that doing this numbering at the source level is trivial, but once we go to LLVM IR CFG soup, scopes are gone. If you want to know exactly what corner cases break down, search the bug database and mailing lists. The explanations are too long for this RFC.I don’t quite understand this constraint (aren’t the EH states within the catch body in a different function and therefore numbered separately?), and I don’t really understand why it makes anything about EH harder (aren’t you still going to have exactly the same numbering problems with cleanups/catches being shared between invoke sites?) as opposed to simply being something that you didn’t design your current implementation around, but it doesn’t really matter. If the new representation is better, it’s better.> New representation > ------------------------------ > > I propose adding the following new instructions, all of which (except for resume) are glued to the top of their basic blocks, just like landingpads.The fact that most of these are terminators makes pinning to the absolutely beginning really problematic. An edge that can’t support arbitrary code is one thing (although usually they’re at least splittable!), but at the very least, we need to be able to drop phis and debug instructions in basic blocks, or you’re going to completely wreck basic optimizability. You should consider giving these a consistent prefix, like “eh_” or “eh.”, just to clearly distinguish them. You can rename “resume” and “landingpad”, too.> They all have an optional ‘unwind’ label operand, which provides the IR with a tree-like structure of what EH action to take after this EH action completes. The unwind label only participates in the formation of the CFG when used in a catch block, and in other blocks it is considered opaque, personality-specific information. If the unwind label is missing, then control leaves the function after the EH action is completed. If a function is inlined, EH blocks with missing unwind labels are wired up to the unwind label used by the inlined call site.For the terminators, this unwind label makes sense. For the non-terminators (just cleanupblock, I think), you’re going to need to define what it means, its strength of reference, etc. For example, if I have a cleanup that doesn’t terminate (imagine a destructor that calls abort()), the IR will contain no CFG links from the basic block with the cleanupblock to the basic block with the resume. The basic block with the resume, and all the downstream EH blocks, may even be unreachable; and so the natural tendency would be to remove them. Does a reference from a cleanupblock keep its target alive?> The new representation is designed to be able to represent Itanium EH in case we want to converge on a single EH representation in LLVM and Clang. An IR pass can convert these actions to landingpads, typeid selector comparisons, and branches, which means we can phase this representation in on Windows at first and experiment with it slowly on other platforms. Over time, we can move the landingpad conversion lower and lower in the stack until it’s moved into DwarfEHPrepare. We’ll need to support landingpads at least until LLVM 4.0, but we may want to keep them because they are the natural representation for Itanium-style EH, and have a relatively low support burden.I agree that we could migrate Itanium to this pattern fairly successfully, as long as we’re agreed that we’re not setting of goal of eventually emitting identical IR for different personalities.> resume > ------------- > > ; Old form still works, still means control is leaving the function. > resume <valty> %val > ; New form overloaded for intra-frame unwinding or resuming normal execution > resume <valty> %val, label %nextaction > ; New form for EH personalities that produce no value > resume void > > Now resume takes an optional label operand which is the next EH action to run. The label must point to a block starting with an EH action. The various EH action blocks impose personality-specific rules about what the targets of the resume can be.I agree with the feedback elsewhere that you should separate these instructions.> catchendblock> ---------------- > > catchend unwind label %nextaction > > The catchend is a terminator that unconditionally unwinds to the next action. It is merely a placeholder to help reconstruct which invokes were part of the catch blocks of a try. Invokes that are reached after a catchblock without following any unwind edges must transitively unwind to the first catchend block that the catchblock unwinds to. Executing such an invoke that does not transitively unwind to the correct catchend block has undefined behavior.I think the rule you’re looking for here is that it’s undefined behavior if control flow from a catchblock doesn’t eventually reach the corresponding catchendblock (or reaches the catchblock again before that point). It’s not unwind-specific.> cleanupblock > -------------------- > > %val = cleanupblock <valty> unwind label %nextaction > > This is not a terminator, and control is expected to flow into a resume instruction which indicates which EH block runs next. If the resume instruction and the unwind label disagree, behavior is undefined.What’s the expectation here? Is each cleanupblock conceptually an independent cleanup, or is it a legal transformation to combine successive cleanupblocks? Is it okay for the code within a cleanupblock to be reachable from multiple cleanupblock instructions? Is there an expectation about how many resumes are reachable? Does the cleanupblock instruction itself prevent reordering in any way?> terminateblock > ---------------------- > > ; for noexcept > terminateblock [void ()* @std.terminate] unwind label %nextaction > ; for exception specifications, throw(int) > terminateblock [void ()* @__cxa_unexpected, @typeid.int <http://typeid.int/>, ...] unwind label %nextaction > > This is a terminator, and the unwind label is where execution will continue if the program continues execution. It also has an opaque, personality-specific list of constant operands interpreted by the backend of LLVM. The convention is that the first operand is the function to call to end the program, and the rest determine if the program should end.It looks like you’re expecting this be useful for things like EH filters; that's cool, but it suggests the name might not be appropriate. Maybe the instruction should be called “eh.filter” and it should take a string label to tell the personality the kind of filtering to do? And then the function pointer is just another thing in the list of constant parameters. Also, std::unexpected doesn’t necessarily terminate the program; it gets a chance to remap the exception. I don’t know that that changes anything — it just means that control continues to the unwind label — but you should keep that in mind in your examples.> sehfilterblock? > ------------------ > > One big hole in the new representation is SEH filter expressions. They present a major complication because they do not follow a stack discipline. Any EH action is reachable after an SEH filter runs. Because the CFG is so useless for optimization purposes, it’s better to outline the filter in the frontend and assume the filter can run during any potentially throwing function call.It’s not that it doesn’t follow a stack discipline, it’s that it’s not quite part of the same stack as other EH actions. It’s similar in spirit to a catch block in Smalltalk, which runs before the stack is unwound because it has the ability to resume control at the throw point. One way to model this would be to have a list of such blocks handing off the invoke (or a landingpad-like instruction that’s easily found from the invoke), where inlining would pull them from outer invokes to inner calls. But the control flow is inherently strange because it can essentially loop within the invoke, and that’s really hard to model. John. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150604/31b080b4/attachment.html>
Reasonably Related Threads
- [LLVMdev] RFC: New EH representation for MSVC compatibility
- [LLVMdev] Windows EH support
- [LLVMdev] RFC: New EH representation for MSVC compatibility
- [LLVMdev] RFC: How to represent SEH (__try / __except) in LLVM IR
- [RFC] [Windows SEH] Local_Unwind (Jumping out of a _finally) and -EHa (Hardware Exception Handling)