13

g++ compiler has the feature of zero-cost exception handling. To my understanding, try does nothing, but when exception is thrown, a subroutine for the exception handler is executed. Like this:

void foo() {
    try {
        bar(); // throws.
    } catch (Type exc) {
        baz();
    }
}

In pseudocode (c-stylish) would look like this:

void foo() {
    bar();
    return;
catch1_Type:
    baz();
}

bar() throws. The exception routine does the following:

Ah, the return address is in function foo()! And the return address is in the first try-catch block, and we throw type Type, so the exception handler routine is at address foo+catch1_Type. So cleanup the stack so we end up there!

Now my question: is there any way to implement it in C? (Can be C99 or newer, although I'm interested in C dialect supported by gcc). I know that I can use e.g libunwind for stack examination and traversal, although I have no idea how to get the address of catch1_Type label. It may not be possible.

The exception handler may be a different function, that's equally OK, but then how to get addresses of local variables of stackframe foo in that other function? It also seem to be impossible.

So... is there any way to do it? I would like not to go into assembler with this, but it also is acceptable if everything else fails (although local variables - man, you never know where they are if using different optimization levels).

And to be clear - the purpose of this question is to avoid setjmp/longjmp approach.

EDIT: I've found a quite cool idea, but does not work entirely:

Nested functions in gcc. What they can do?

  • have access to local variables,
  • have possibility to goto local labels in the parent function!
  • can be called by callees of our function, provided that we pass a pointer to the nested function, so it's available by pointer in the callee.

Downside which prevents me from doing anything zero-cost:

  • they're optimized out even at -O0 level if they're unused. Can I do anything about this? If I could, I could get address by symbol name when exception is thrown and it would simply do the job of implementing exceptions which cost nohing when not thrown...
rkhb
  • 14,159
  • 7
  • 32
  • 60
Al W
  • 476
  • 3
  • 15
  • This seems like a slightly different take on this question: http://stackoverflow.com/questions/307610/how-do-exceptions-work-behind-the-scenes-in-c ... it asks how exceptions work in C++; once you know how C++ implements exceptions, you can decide if it's feasible to copy that implementation for your C program. – Martin Atkins Mar 17 '13 at 19:07
  • @MartinAtkins, I'm only slightly aware of how are exceptions implemented in g++, although I think I've read somewhere that the exception support in g++ is partially on the compiler and linker's side, so duplicating it in C would be a problem without modifying the tools. I will read the topic that you mentioned to get more mana though. – Al W Mar 17 '13 at 19:10
  • 1
    You are completely mistaken, `try`/`catch` construct come at a cost. First the `try` has to somehow mark the position of the stack and all registers, and the the `catch`/`throw` has to unwind the `try` context one-by-one. There is an "equivalent" feature in C `setjmp`/`longjmp`, which does exactly the same, only that it has less glue around it. – Jens Gustedt Mar 17 '13 at 19:56
  • @JensGustedt, no, I'm not mistaken. Unwind tables are generated at build time (link time I think?) and they're sufficient for the exception routine to unwind the stack and find addresses of the exception handlers. At runtime, no marking happens. http://www.systemcall.org/blog/2010/10/zero-cost-exception-handling-in-cpp/ – Al W Mar 17 '13 at 20:00
  • That article is at least misleading about the `setjmp`/`longjmp` part. A C centered approach would never have to go through "a list" of jump tables. – Jens Gustedt Mar 17 '13 at 20:32
  • 1
    Then their claim of "zero cost" is hard to believe. The table that they have already comes at a cost, and then they don't explain how different instances of the same function that might be piled on the stack are distinguished. They must have a way to "register" the last function context of a specific kind that has an active `catch`, and the function variables that are held registers must also have been saved somewhere. So there might be no explicit cost, but a lot of lost optimization potential everywhere. – Jens Gustedt Mar 17 '13 at 20:33
  • I can't speak for the gnu C++ implementation. It is possible to have zero-execution cost for exceptions, by having tables that identify for each block of instructions where the catch handler is for that block. The table has a *space* cost, true, but then so does all the manual code for inspecting returned error status if you implement exceptions with dynamic tracking. (I've built compilers with the zero execution cost. They work just fine, and no, the optimization potential isn't lost; the compiler just tracks an exception target for each piece of code, no matter where it ends up). – Ira Baxter Mar 17 '13 at 20:45
  • @AlW: It isn't a trivial task to build a compiler that implements zero-cost trys. I doubt you are going to write a few macros or modify the compiler you have easily. Why don't you just use the C++ compiler, since it offers you this? – Ira Baxter Mar 17 '13 at 20:48
  • @IraBaxter, I believe there's a lot of C vs C++ vs Java vs PHP flamewars, and I don't want to take part in one. ;) – Al W Mar 17 '13 at 20:51
  • @ALW: This isn't about flamewars; it appears to be about getting zero cost exception handlers, for which you appear to have an easy out. – Ira Baxter Mar 17 '13 at 20:55
  • @IraBaxter, that's exactly the reasoning that I do not accept. I'm a computer engineer at my work, but at home and on this forum I want to be computer scientist ;) Well, enough of this offtopic. My question is how to do this thing in C, not any other language. – Al W Mar 17 '13 at 21:29
  • Since 0 cost solutions don't exist, nowhere, go with a constant cost solution. `setjmp`/`longjmp` has that property: `setjmp` stores a finite number of registers on the stack, `longjmp` restores them. As simple as that, built into the language. – Jens Gustedt Mar 17 '13 at 23:03
  • @AlW: What I said was building these is a lot of work. You're welcome to give it a try if you like. You're not going to do it by modifying C code; you have no good way to track how blocks of code are tied to exception handlers. So, you need to do this from inside an existing compiler. Be prepared for a long slog if the compiler you start with doesn't have this concept already in place. If you insisted on proceeding, you should take GCC for C++, and modify it to work for C since the languages aren't very different and you say GCC C++ already does this, so it must have the right foundation. – Ira Baxter Mar 18 '13 at 01:31

1 Answers1

3

I've found some time to play with this idea, and I'm very close to finding the solution for my own question. Here are the details:

  • gcc allows nested functions, which have access to parent function's local variables, and can goto labels in the parent function!
  • gcc will not emit the code for the internal function if it's only defined but not referenced. You can define inline no-op function which obtains a pointer to the local function, and call it in the parent function. This will force generating code for the internal function, and it will be zero-cost (at higher optimization levels the inline no-op call will be removed). It is possible that in more recent gcc optimizing out the inline call will prevent generating the code for the internal function though..
  • bad thing is, I see no way to force nested (internal) functions to be global symbols. They're always local, so no possibility to get the address using dlsym.
  • another bad news, if the program is using such nested functions, valgrind is crashing ;) I was able to verify my simple test program, but I was unable to use valgrind to verify that there are no memory violations.

The code that I used for checking has obvious flaw: it's not 'zero-cost', as the global pointer to the exception handling routine must be set during function's execution. If only we could make this compile-time!

Well, after all if one wants to use the internal function properly, like passing a pointer to it into callees, so they can call it in case of exceptions thrown, we could probably have very fast exception handling, much, MUCH faster than setjmp/longjmp...

I'll keep on hacking, maybe I'll find a way (some assembler chunk of code to force GAS registering the function as a personality routine of the parent?).

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>

typedef void (*catch_routine)(void*);

catch_routine g_r = NULL;

void tostr_internal(char* str, int a)
{
    int result = a + 'a';
    if (result < 'a' || result > 'z')
    {
        // handle exception
        if(g_r)
        {
            g_r(&a);
        }
        else
        {
            fprintf(stderr, "Exception not caught!");
            abort();
        }
    }
    else
    {
        str[0] = result;
        str[1] = '\0';
    }
}

char* tostring(int a)
{
    __label__ exhandler;
    char* string = (char*)malloc(2*sizeof(char));

    void personality(void* exid) {
        fprintf(stderr, "Number %d is not a character!\n", *(int*)(exid));
        free(string);
        goto exhandler;
    }
    g_r = personality;

    tostr_internal(string, a);
    return string;

exhandler:
    return NULL;
}

int main(int a, char** b)
{
    int i = 0;

    for(i = 0; i < 10000; i++)
    {
        int trythisbastard = i % 95;
        char* result = tostring(trythisbastard);
        if (result)
        {
            fprintf(stderr, "Number %d is %s\n", trythisbastard, result);
            free(result);
        }
    }

    return 0;
}
Al W
  • 476
  • 3
  • 15
  • 1
    In the early days of C++, the C++ compiler (`cfront`) generated C code that was compiled to object code by the C compiler. Exception handling was one of the features that forced a break in that system; there wasn't a way to generate C code that could handle exceptions. See Stroustrup 'Design and Evolution of C++' (D&E) for more information. – Jonathan Leffler Mar 31 '13 at 17:46
  • Having a single global variable `g_r` to record exception handlers is going to run into problems with nested `try` blocks. You have to maintain a stack of exception handlers at minimum. – Jonathan Leffler Mar 31 '13 at 17:49
  • @JonathanLeffler, I know. This code is just a little experimentation, I've been trying to eg. dlopen self and get the inner function's address by symbol name... To be honest, I think that the global stack is also not a good option. If we have to store the pointer somewhere, the fastest code would be probably for stack, so a function argument (accessing stack is faster cache-wise than accessing global data I think, and on x86_64 chances are that the argument will be passed using register, not a stack memory). – Al W Mar 31 '13 at 17:57
  • @JonathanLeffler Is it really totally impossible to implement C++ exceptions by transpiling to C or only impractical? If it's somehow totally impossible, I'm surprised and I'd like to open a question about that. – Praxeolitic Jan 16 '18 at 22:47
  • @Praxeolitic: read D&E mentioned before. My understanding is based on what Stroustrup says in that. A truly reliable exception handling system was deemed “not feasible”. Maybe people have worked out a different approach and it is now possible. But I believe it ends up needing assembly level code that can’t be written in C. – Jonathan Leffler Jan 16 '18 at 23:06