David: Thank you for re-introducing this as a new thread. I appreciate it.
On Mon, Jul 29, 2013 at 3:17 PM, David Jeske <[email protected]> wrote: > In my view, a successful systems programming environment must (a) rely on > an error-signaling mechanism with a backward-and-forward compatible > success/fail test, must (b) not change the error-generating profile of a > function in successive supposedly-compatible versions (for this is a break > of the ABI), and should (c) not mandate excessive performance overhead for > error-signaling. Further, if we wish to aid the programmer, we should > consider (zz) mechanisms which help assure exhaustive success/fail-handling > and/or exhaustive condition handling when desired. > Empirically, this is over-stating the requirements. As a counter-example, the UNIX ERRNO space has been expanded and updated many times over the years, and most programs were unaffected by any given change. What seems to be the case is that systems arrive at a mature set of "general" error codes that cover all common cases, and then find themselves adding error codes at the margin to deal with new issues, typically issues raised by hardware. Often the new codes turn out to be specializations of some existing code. Once that maturity is reached, the reality is that most programs just don't care about the new cases the vast majority of the time, and *intentionally* do not perform exhaustive error handling. My observation is that there are really only three patterns for error handling: 1. The error is handled *very* close to the source, and the recovery code has intimate knowledge of what errors may arise. In practice, caller and callee are almost always in the same module and often written by the same person. 2. An optimistically attempted computation is abandoned in favor of an alternative. The discipline is that the code which throws the error doesn't leave observable effects behind. We rarely care *what* error was raised in this case. 3. The program exits. A slight extension of [1] is I/O errors, which may propagate a few levels up before they hit the IO subsystem boundary and get handled. End-of-file is an interesting example of a place where an exception should *not* be used, because life works better if that is handled by an out-of-band value. All I'm trying to say is that I think we can get a practically useful result without going quite as far as you imply. In the end, there are only so many ground sources of errors, and these are fairly well enumerable. > While catch-all exception handlers might seem like a backward compatible > success/fail test, in the context-of stack-unroll and non-local exception > handling they practically are not. > I just don't agree. This comes down to resource release and finalization discipline. But pragmatically, a combination of catch-all and automatically introduced catch-blocks is the best we know how to do in the current state of the art. C++: Fails both (a), and (b) and does not provide (zz). Shap recently > quoted 20% as an overhead for it's exception mechanism, which is slower > than return values but nowhere near as bad as some exception unroll costs. > To be clearer: there's a philosophical question about whether that is overhead. In the case of the EROS and Coyotos kernels, we know that: - No exceptions are ever thrown, so there is never a need to catch them. - The system design is transactional, no observable mutations are made until we are past the point where all failure-generating checks are performed, and there are only three things to clean up when a transaction aborts or succeeds. So basically, exceptions weren't a good fit, and all of the code produced to manage them was pure overhead with exactly zero payback. If you have a system that might actually *throw* an exception - which includes any system that invokes operator NEW, then that code is reachable and potentially needed. It has a given cost, but I don't think that cost can reasonably be viewed as overhead. You can shrink the amount of code involved if you do a whole-program compile. I should also note that later compilers do a *much* better job of keeping exception unwind costs off of the main execution path than they did at the time. For something like a microkernel, the *placement* of the exception code remains important (you want to gather all of it into a separate section) because it's presence in the main code path creates shadows on the I-cache that reduce utilization very measurably. Gathering the exception code that way certainly isn't difficult, but I've yet to see a compiler that does it. > CLR: Fails both (a) and (b) and does not provide (zz). Further, because > the CLR exception unroll mechanism is *very* expensive, library functions > often provide exception and return-value versions. However, if the library > author did not choose to create a return-value version, the library becomes > unusable for high-performance use. > It's an interesting question why the CLR exception mechanism is so slow. Do we have any insight into the cause? Is it something as simple as a JIT issue? Does it have to do with the need to connect to the (completely busted) windows SEH model? Could it be because Win32 and Win64 callbacks can cause the exception stack to cross the kernel stack, which is a serious mess? > Google-Go: has no stack-unroll exception mechanism, instead relying on > error-return-values and the backward-compatible zero-as-success-value test. > Google-Go meets both criteria (a) and (b), though it is unfortunate that > so-far it offers no compiler assistance to assure exhaustive > error-condition checking as in (zz). It also carries all the > Can you finish that last sentence at your convenience? General comment: this is something we should come back to when we're looking at how exception handling works. shap
_______________________________________________ bitc-dev mailing list [email protected] http://www.coyotos.org/mailman/listinfo/bitc-dev
