I have troubles finding information on this subject, so I'll try to ask here:
What kind of unwinding mechanism implementation is used by VC8.0
Is it the same old and slow 'update structure on the stack while we go' (like it was up to VC7.1) Or MS finally decides to go after GCC & others by separating successfull execution path on compile stage (which is significantly more efficient)
Bye.
Sincerely yours, Michael.

implementaion of unwinding
Frank213
I am confused -- why SQL Server needs -EHa then You do not need to unwind C++ stack properly, if you will kill app anyway.
There is a misunderstanding here -- I am not arguing against C++ exceptions, I think "you should use C++ exceptions instead of..." -- i.e. I belong to even more strict "school" than one you have mentioned. C++ exception mechanism was invented to make error-handling more efficient (both in terms of writing and executing the code) than "check error code" approach. And the purpose of my posting here is to find whether VC finally became "good" in terms of C++ exceptions implementation.
As long as "do not pass invalid arguments" precondition is a part of your interface -- everything is ok -- developer shooted his own legs, but he was warned. Later you added error checking (based on SEH it seems) and removed "do not pass bad arguments" precondition from your interface (if it ever was documented at all ;) ) -- it is perfectly ok, as long as your internal state invariant is held in the face of OS exceptions. It is simply convenience traded for performance. I do not see how this will makes binding between OS exceptions and C++ exceptions look better.
AFAIK these optimizations are disabled regardles of 'table-' or 'not-table-' driven approach -- it is dictated by the nature of C++ exception. But gain in comparison with "check error code" approach still considerably outweights absence of these optimizations (AFAIK).
a) Being an application developer I am not convinced that complexity of building good C++ compiler should justify me having problems -- after all, good compiler needs to be written only once, but thousands of applications will benefit
b) I already told it before -- C++ exception implementation does not need to be bound in any way with OS exceptions. The only good reason I could see -- is the ability to implement -EHa -- i.e. by sacrificing performance achieve application state invariantness in face of OS exceptions, but this technique is somewhat "condemned" by C++ standard (and I think this is right). So -- particular demands of OS handler are not good reasons...
Bye.
Sincerely yours, Michael.
JoyK
Regarding the code separation comment, VC7.0 separated "cold" exception handling code. VC7.1 did it as well. X64 & IA64 both clone finally's and move the exception-handling version cold. All VC 8.0 (x86, x64, and IA64) have profile guided optimizations that will separate scenario-cold code at a basic block level (most error check failures will be moved off-page, for example). Search for -LTCG:PGO and -GL
Now, about the claim of 'better performance'. On Linux, most distros are full binary rebuilds of everything from source. Which is great, from a trusting developer's point of view. QA don't tend to be so trusting of their compiler :-). In this world, the ABI can be redesigned willy-nilly, and when everything is rebuilt, I'm sure that there are no problems with any of it. Because of Windows binary compatibility requirements, when you're changing or extending the ABI, the changes must be made in such a way that you can still invoke functions from a pre-ABI-change DLL on the same thread as a post-ABI-change DLL. So if you've changed your ABI too much, you must provide a fairly heavy-weight thunking mechanism to handle cross-ABI communication. This is, in fact, exactly what the CLR team did for the x86 .Net framework. If you think it's a good idea in general purpose code, go search for 'Double-thunk elimination' and read up. The .Net framework for x64 was authored against the same ABI as native code, and so they have a lighter weight mechanism for invoking native code from managed code. It's still costly (garbage collection ain't cheap), but it's definitely more performant than the x86 default thunking mechanism.
Hope that helps!
-Kev
Jose Fuentes
Hmm... thanks for links. As I understood it describes OS-provided unwinding mechanism (similar to one in Win32 on x86). I do not care much about OS-provided unwinding -- after all C++ unwinding does not necessary need to use it. (unfortunately long ago it was decided to catch access violations with catch(...)
, and thus OS-unwinding and C++-unwinding became bound)
I am interested in "how VC8.0 implements unwinding in case of exception ". I.e. if we speak in terms of this article:
http://msdn.microsoft.com/library/default.asp url=/library/en-us/kmarch/hh/kmarch/64bitAMD_001864bc-0ea9-4257-aaf5-18af50ed020a.xml.asp
1. it looks like in VC8.0 C++ exception unwinding based on OS-unwinding like before, right
2. How VC8.0 implements language-specific handler And what is done to the generated code to support this handler functionality Do we still update some structures on the stack while we go or we are searching tables using current IP as a key
3.
How
a) if I am right -- this functionality is executed by OS, how OS find static data, associated with specific function in specific module (PE format has some clues for OS ) How it finds the module, btw (binary search using IP something on stack )
b) which algorithm is used to find RUNTIME_FUNCTION structure (which complexity )
I have said nothing about code separation, I know that VC7.1 implements this. I was talking about execution path separation (i.e. successful execution path should not have anything related to failures, which is not the case in all previous VCs, where we need to update stack-based data after function calls) -- btw, this is maybe the strongest point in C++ error handling model.
Now about thunking and changing ABI in general:
I do not think that ABI change is a great obstacle for C++ unwinding, because:
- language-specific handler is provided by given function's module, which knows how given function should be unwound
- the main problem is the how to switch from one ABI to another while C++ unwinding driven by OS unwinding "passes through" functions of different modules. Maybe it is possible to solve it efficiently -- so far I do not see any reason why not ( but I may be wrong :) )
- since OS calls are all extern "C" -- no exceptions should come from OS-call (as long as there is no OS exception or user-provided callback throws, which violates extern "C" convention anyway)
Could you give me some more detailed insights why you think it is impossible to implement "right C++ unwinding" and yet retain binary compatibility on x86
Bye.
Sincerely yours, Michael.
78Spit1500Fed
(2) "I am not convinced that complexity of building good C++ compiler should justify me having problems" -- exactly what problems Any complex program such as compiler is result of some tradeoffs. It can happen that instead of forcing each and every optimization to correctly work in every possible case it's better to turn some optimization off for some corner cases and spend resources on something else in the compiler.
(3) "C++ exception implementation does not need to be bound in any way with OS exceptions" -- (a) trust me, you don't want to have 2 different implementations of stack walking, context restoring, etc. It's extremely hard to implement even one stack walk correctly. (b) As Kevin already wrote, it's important to unwind the stack and call destructors when recovering from hardware exception, especially if you are using some 3rd party library.
(4) "You added error checking ... and removed "do not pass bad arguments" precondition from your interface" -- not so. It's still illegal to pass bad arguments. We just found the hard way that by doing some arguments validation we can make both our and ISV developer life better. That resembles compiler warnings -- using your words "developer shooted his own legs", so it's absolutely legal do not emit any warnings, but every subsequent Visual C++ version emits more warnings, and customers are happy that we emit them.
Thanks,
Eugene
cybercoaster
That is not the case of C++ exceptions. There is large school that says "you can use exceptions instead of checking error codes". A lot of C++ code is written this way. I may not agree with the ways how our customers use exceptions, but that is what they want. They don't want Microsoft to dictate them how to code. They want tools that will simplify things for them. They would appreciate better tools, or tools that will find more bugs for them, or tools that do things in a simpler ways, but choice is theirs. We can suggest something new (e.g. managed code), but we always should give backward compatible tools as well.
Now returning to the original thread:
(1) There is at least one legal use of the (structured) exception handling I know about. SEH is widely used in the Windows kernel to check input arguments for correctness, and return error code "bad argument" to caller. That was not always so in the past; as a result programs AV'ed somewhere in the Windows when user's code passed bad arguments, and developers were saying "that is not my fault -- I believe I am doing everything properly, but Windows API function just crashed because Windows is sloppy". By returning reasonable error code we help developer to figure out his bug; everybody is happier this way.
(2) Even with table-driven approach there is some penalty you are paying for using exceptions in the non-exception-raising case. Sometimes we are disabling some optimizations. There are several reasons why -- e.g. optimization may be too hard to debug in the presence of exception, or OS hanler may want some things in the particular way, so we cannot generate more efficient instructions sequence.
Joseph Wee
I care because based on my knowledge of compiler behavior I make decisions. :)
Thanks a lot for your answers.
So for x64 compiler successfull execution path is almost not influenced by presence of exceptions mechanism. That is good! Also under -EHs C++ exception mechanism can not see OS exceptions. I hope that VC9.0 will finally break another side of this tie by forbidding C++ unwinding from __except. This will open road to propietary (and potentially faster) C++ unwinding implementations which is not "driven" by OS unwinding. By the way -- this mechanism could look into stack frames for OS exception handlers, so that SEH could "feel" presence of C++ exception.
Also I found that in current implementation as exception is processed each stack frame gives four binary lookups (amortized) -- which is not the best, I hope it will be improved in the future.
BTW, what did you mean when you said:
:) that means you, guys, have a great speed-up potential in this product! Maybe you'll again rewrite it's engine from scratch as you did not so long ago ;))
I spoke to my friend, who is more proficient with *nix-world stuff. OS-exception here are signals, and signal processing is absolutely unrelated to C++ unwinding -- you can't unwind C++ stack from signal's handler -- you can either fix&continue or abort (like it should be). And this handler could be even called on different thread (depending on number of conditions), while interrupted thread waits for handler's decision.
Anyway -- thanks for enlightenment. :)
Bye.
Sincerely yours, Michael.
nron
The language specific handler contains almost identical data to the VC7/7.1 (even for x64 & IA64). The additions for IA64 & X64 are, as you guessed, a small IP to state table that is, you guessed it, binary searched. Once you have the state, the handler data consists of a set of tables. One table is for how to destroy objects that are live, and the other table is for how to find the handler for a given exception. Going beyond that level of detail is really difficult, and probably unecessary. You can probably follow it yourself, if you look at the unwind data.
Now to address the other items in your post:
Yes, there are caching mechanisms to accelerate the stack walk, but honestly, why would you care If an exception occurs, it's rare (or should be), and you're already basically thrashing the processor's L1 cache by hitting all sorts of cold sections of OS code.
#1 - I'm not really defending the original decision, I'm just describing the legacy scenario we're stuck supporting. I started around here in 1997, so I was about 5 years too late...
#2 - With VC8.0, your example code produces very specific, very well defined behavior with -EHs: The program terminates with a divide by zero error. For EHs in VC8, catch(...) NEVER catches OS exceptions, and when an OS exception occurs, no destructors are invoked. If you want destructors called, you must compile -EHa. So it sounds like we do what you describe.
-Kev
BTW - SQL Server 2005 is one of the big applications I know of that use -EHa.
nullsmind
(1)(b) "user code failure" -- sorry, but if user code violates conventions (whatever they are, e.g. "no OS exceptions") -- that means your process is to be considered "dead". You could reduce possiblity of it by many means, but nonetheless. And I do not think it is a big trouble to put __try/__catch around calls to user code instead of compiling everything with -EHa.
(2)
(3)(a) "trust me, you don't want..." -- I do not care about compiler complexity (see (2)), all I need from it -- it should squeeze as much performance from my code as it could (this is its purpose) and to be compatible with standard
(3)(b) "As Kevin already wrote, it's important to unwind the stack and call destructors when recovering from hardware exception" -- no it is not. It is important if you used -EHa -- otherwise you won't unwind C++ stack properly anyway, terminate or fix-and-try-again should be your only options. That means that under -EHs there is no need to bind OS and C++ exceptions mechanisms. But it is convenient, I understand
Anyway -- thanks a lot, guys. I satisfied my curiosity about VC8.0 features I was interested in and even more.
Bye.
Sincerely yours, Michael.
RoyLane
My exposing hardware exceptions to the C++ exception model, I can use a C++ object that, say, closes file handles upon destruction, and I don't have to worry about leaving them open if an A/V occurs - my hardware exception recovery doesn't need to worry about cleaning up any resources. Specifically, consider the SQL Server scenario: If SQL has a network socket open, and an unexpected network error occurs (maybe the NIC driver pukes, who knows), my socket class will automatically free associated resources [maybe an input buffer, etc...] without needing to explicitly think about the exception class. It lets you use the 'destruction frees resources' in a server-hardening scenario...
-Kev
Sarath.
did you mean two 32bit writes (first -- write -2 before prolog, second -- write smth else after prolog)
Huh could you explain your idea
Bye.
Sincerely yours, Michael.
mandoman2
#2 ... that means that for x64 successfull execution path is not encumbered with anything related to possible failures (well, except some constraints for optimizer) if yes, this is really good.
#3b) sigh... three O(log2 N) lookups for every frame (amortized by some caching mechanism, I hope)
So, basically answer is:
- for x86 everything is like it was before
- for x64 we have some hope but not because compiler became better, but because x64 OS unwinding mechanism is more "suited" now
Still there is a question left:
How VC8.0 implements language-specific handler And what is done to the generated code to support this handler functionality
E.g. how handler "knows" which objects on stack to destroy making fourth binary search using IP through static structures, which describe given function
1. No problem -- but why you are making OS exceptions visible to C++ exception handling Behavior of OS exception is highly implementation-specific and they should be handled by implementation-specific constructs
2. Do you think it is right This example will still produce unexpected behavior (with -EHs):
volatile static int k = 0;
try {
S s1;
try {
S s2; // s2's destructor won't be called
volatile static int n = 1/k;
} catch(...) { throw; }
} catch(...) {}
So with -EHs this "visibility" won't solve anything but provides some problems, actually it will do more harm than good -- according to C++ standard this example has undefined behavior, but if you compile and run it -- it will run and hide DIV error, while it should stop execution (simply because application invariant is violated -- s2's destructor MUST be called). I think it would be a good idea not to catch OS exceptions with catch(...), but it is too late. BTW -- this is why it is unadvisable to use catch(...) anywhere except top-level functions (thread/app main()) -- and even there it is not very good, because s1's destructor might rely on work that
should be done by s2's destructor.
I think that, until developer explicitly asked for it (with -EHa for example) you should never unwind C++ stack after you caught C++ exception in __except block. All you could have to do -- is to report/fix and either try to restart from the same place or terminate program. All other scenarios leads to problems and confusions.
I do not know, I have rather limited experience working with Linux. But I have good deal of understanding C++ standard and significant experience with VC's up to VC7.1 :)
Bye.
Sincerely yours, Michael.
orbit
#1 - yes, VC8.0 C++ Exception Handling is basedon OS unwinding, just like before, although we do support C++-only exception handling (-EHs) which results in better code for x86.
#2 - For x86, the language specific handler hangs off of the FS:0 function linkage. For AMD64 & IA64, the language specific handler is described in unwind data, and is ...
#3a) ... disovered by searching a sorted table that exists in all .exe's & .dll's (.pdata) This is just a sorted list of RUNTIME_FUNCTION structures that the compiler/assembler emit, and the linker sorts & merges.
#3b) The OS first does a binary search of the images loaded in a process, then does a binary search of the correct image's .pdata section to find the pointer to the unwind data.
Now about your final comments: I do not believe it is impossible to implement proper C++ unwinding and still retain x86 binary compatibility, I just think it's A) very difficult & highly error prone, and B) difficult to get improved performance with a system-wide recompile, which we just can't do...
There's another reason for all of this: In order to properly handle both hardware & software exceptions (hardware exceptions: AV's, Div by zero, etc..; Software Exceptions: C++ objects), we really are forced to make C++ exceptions visible to the hardware exception handling API's. If you don't do this, when a hardware exception occurs, you'll probably leak resources (because the hardware handlers won't be able to clean up C++ objects). The primary hole here is that when you catch a C++ exception with an __except(), you won't call that object's destructor, so if you want to properly destroy both, you have to do something like this:
void wrapperCPP()
{
try {
func();
} catch (std::exception)
{}
}
void wrapperSEH()
{
__try {
wrapperCPP();
} __except(1)
{}
}
It's not pleasant, but it works. I'm curious, having not written any exception safe code for any other platform: How does Linux handle the scenario of writing fully hardware & software safe exceptions
-Kev
fraXis
And in this specific scenario, if NIC driver state is completely separated from SQL Server state, I'd wrap all calls to driver with __try/__except and compile everything with -EHs. Behavior in case of AV from driver should be ruled by SQL Server settings according to user's will.
Well... it seems that for x64 we are going to get good compiler with some (not all!) of our dreams implemented in it properly. ;)
Bye.
Sincerely yours, Michael.
Jim Sharkey
The only advantage to exposing hardware exceptions to the C++ exception model is that you can use C++ objects to force the compiler to release resources, rather than needing to keep track of it manually (a la standard C cleanup), for apps that want long up-times.
-Kev