4

I'm trying to make a macro-based jump table in C.

Here's some example code:

#include "stdio.h"

#define GOTO(X) static void* caseArg[] = {&&I0, &&R0, &&S0, &&F0, &&G0, &&H0}; \
    goto *caseArg[X];

#define FINISH() goto caseEnd;

int main(int argc, char** argv) {

    GOTO(1);

    I0: printf("in I0\n"); FINISH();
    R0: printf("in R0\n"); FINISH();
    S0: printf("in R0\n"); FINISH();
    F0: printf("in R0\n"); FINISH();
    G0: printf("in R0\n"); FINISH();
    H0: printf("in R0\n"); FINISH();

    caseEnd:;

}

The possible labels (I0, R0, etc) have to be the same.

The problem is: I want to be able to use the same macro in different scoped parts of the same source file. However, the compiler complains that the labels are defined.

What I want to achieve:

int main(int argc, char** argv) {

     { // scope 1 

        GOTO(1);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in R0\n"); FINISH();
        F0: printf("in R0\n"); FINISH();
        G0: printf("in R0\n"); FINISH();
        H0: printf("in R0\n"); FINISH();

        caseEnd:;

    }

    { // scope 2

        GOTO(4);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in R0\n"); FINISH();
        F0: printf("in R0\n"); FINISH();
        G0: printf("in R0\n"); FINISH();
        H0: printf("in R0\n"); FINISH();

        caseEnd:;

    }

}

Any ideas? Any possible workaround?

Dr.Kameleon
  • 22,532
  • 20
  • 115
  • 223
  • The same macro, or a different macro with the same name? – Robert Harvey Jan 21 '20 at 15:37
  • 2
    What is the meaning of `&&` as in `&&IO`? – Fiddling Bits Jan 21 '20 at 15:40
  • Preferably, it would be the same macro. I had though of using some arg, and pasting it that as a unique identifiers to the jump table *and* the labels, inside the macro definitions. But this would result is some rather bloated code, nope? – Dr.Kameleon Jan 21 '20 at 15:40
  • 1
    For anyone else confused by this: https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html – jamieguinan Jan 21 '20 at 15:40
  • @FiddlingBits Please, have a look at this. – Dr.Kameleon Jan 21 '20 at 15:41
  • @jamieguinan and Dr.Kameleon Thanks for the link. – Fiddling Bits Jan 21 '20 at 15:43
  • 7
    Why not just use a normal `switch` statement? – Ian Abbott Jan 21 '20 at 15:44
  • @IanAbbott I'm experimenting. Some Switch statements *are* converted to jumptables internally, some array not. I want to make that explicitly and measure the performance difference. – Dr.Kameleon Jan 21 '20 at 15:45
  • 2
    @Dr.Kameleon A `switch` with contiguous cases should spit out equivalent code, especially if you add `default: __builtin_unreachable();` to try and coax the compiler not to bounds-check the jump table. One thing the compiler might do better on a switch is it could store the jump table in `const` storage and as offsets rather than absolutely labels if your compiling for a shared library. Storing offsets is friendlier to PIC code (See Drepper's How To Write Shared Libraries). – Petr Skocik Jan 21 '20 at 16:14

4 Answers4

8

You need the __label__ extension (at least in gcc, clang, and tinycc), which allows you to scope labels to a block.

The labels need to be declared at the very start of a block with

__label__ I0, R0, S0, F0, G0, H0;

(Contiguous __label__ I0; __label__ R0; ... or a mix of the two forms works as well.).

Unless declared scope-local with __label__, C labels are scoped to their enclosing function.

Your example with __label__:

#include "stdio.h"

#define GOTO(X) static void* const caseArg[] = {&&I0, &&R0, &&S0, &&F0, &&G0, &&H0}; \
    goto *caseArg[X];

#define FINISH() goto caseEnd;

#define DECL_LBLS() __label__ I0, R0, S0, F0, G0, H0, caseEnd

int main(int argc, char** argv) {

    {   DECL_LBLS();

        GOTO(2);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in S0\n"); FINISH();
        F0: printf("in F0\n"); FINISH();
        G0: printf("in G0\n"); FINISH();
        H0: printf("in H0\n"); FINISH();
        caseEnd:;
    }

    {   DECL_LBLS();

        GOTO(1);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in S0\n"); FINISH();
        F0: printf("in F0\n"); FINISH();
        G0: printf("in G0\n"); FINISH();
        H0: printf("in H0\n"); FINISH();
        caseEnd:;
    } 
}

https://gcc.godbolt.org/z/63YSkG

In this particular case, such a local-label based jumptable seems to buy little over a plain old switch.

exebook
  • 32,014
  • 33
  • 141
  • 226
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • That's a good catch. I know about `__label__`. The thing is: can I declare an array and just jump to some label using an index? (pretty much like in my example) – Dr.Kameleon Jan 21 '20 at 15:54
  • @Dr.Kameleon Yes. I've added a `__label__`-based implementation of your specific example. – Petr Skocik Jan 21 '20 at 16:00
  • I have a slight doubt: what about the GOTO macro? (this *has* to take a number value and not a label value) – Dr.Kameleon Jan 21 '20 at 16:05
  • @Dr.Kameleon I've done nothing to your definition or use of the `GOTO` macro. It still takes a number. Your example with the `__label__`-declared labels is basically a simple `switch` with contiguous `case`s implemented with a lower level primitive (namely, labels as values). – Petr Skocik Jan 21 '20 at 16:09
1

This is pretty ugly solution, but if you are willing to add extra prefix definitions to the scopes, you can do this with concatenation

#include "stdio.h"

// Helpers
#define CONCAT(a, b) CONCAT2(a, b)
#define CONCAT2(a, b) a ## b

// Label redirection
#define I0 CONCAT(PREFIX, I0)
#define R0 CONCAT(PREFIX, R0)
#define S0 CONCAT(PREFIX, S0)
#define F0 CONCAT(PREFIX, F0)
#define G0 CONCAT(PREFIX, G0)
#define H0 CONCAT(PREFIX, H0)
#define caseEnd CONCAT(PREFIX, caseEnd)

#define GOTO(X) static void* caseArg[] = {&&I0, &&R0, &&S0, &&F0, &&G0, &&H0}; \
    goto *caseArg[X];

#define FINISH() goto caseEnd;

int main(int argc, char** argv) {

     { // scope 1 
        #define PREFIX SCOPE1
        GOTO(1);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in R0\n"); FINISH();
        F0: printf("in R0\n"); FINISH();
        G0: printf("in R0\n"); FINISH();
        H0: printf("in R0\n"); FINISH();

        caseEnd:;
        #undef PREFIX

    }

    { // scope 2
        #define PREFIX SCOPE2
        GOTO(4);

        I0: printf("in I0\n"); FINISH();
        R0: printf("in R0\n"); FINISH();
        S0: printf("in R0\n"); FINISH();
        F0: printf("in R0\n"); FINISH();
        G0: printf("in R0\n"); FINISH();
        H0: printf("in R0\n"); FINISH();

        caseEnd:;
        #undef PREFIX
    }

}
user694733
  • 15,208
  • 2
  • 42
  • 68
0

Consider adding a scope parameter to your macro.

It would result in something like this:

#include "stdio.h"

#define GOTO(scope,X) static void* caseArg[] = {&&scope##_I0, &&scope##_R0, &&scope##_S0, &&scope##_F0, &&scope##_G0, &&scope##_H0}; \
    goto *caseArg[X];

#define FINISH(scope) goto scope##_caseEnd;

int main(int argc, char** argv)
{
  {
    GOTO(SCOPE_1, 1);

    SCOPE_1_I0: printf("in I0\n"); FINISH(SCOPE_1);
    SCOPE_1_R0: printf("in R0\n"); FINISH(SCOPE_1);
    SCOPE_1_S0: printf("in R0\n"); FINISH(SCOPE_1);
    SCOPE_1_F0: printf("in R0\n"); FINISH(SCOPE_1);
    SCOPE_1_G0: printf("in R0\n"); FINISH(SCOPE_1);
    SCOPE_1_H0: printf("in R0\n"); FINISH(SCOPE_1);

    SCOPE_1_caseEnd:;
  }
  {
    GOTO(SCOPE_2, 3);

    SCOPE_2_I0: printf("in I0\n"); FINISH(SCOPE_2);
    SCOPE_2_R0: printf("in R0\n"); FINISH(SCOPE_2);
    SCOPE_2_S0: printf("in R0\n"); FINISH(SCOPE_2);
    SCOPE_2_F0: printf("in R0\n"); FINISH(SCOPE_2);
    SCOPE_2_G0: printf("in R0\n"); FINISH(SCOPE_2);
    SCOPE_2_H0: printf("in R0\n"); FINISH(SCOPE_2);

    SCOPE_2_caseEnd:;
  }
}

That's not optimal, but it would work in every compiler.

Please note that in case the blocks to be called have all the same pattern as in the example shown in the question, you could even define a further macro to be called as FULL_MACRO(scope,X), since all GOTO and FINISH calls will be able to be parameterized.

Roberto Caboni
  • 7,252
  • 10
  • 25
  • 39
  • not needed as it works fine in different functions https://godbolt.org/z/aoA3XQ – 0___________ Jan 21 '20 at 15:50
  • @EricPostpischil Thank you. Anyway this answer has its reason to exist, since is 1) more "universal" than the accepted solution, 2) adding a second parameter is not more "ugly" than adding defines/#undefs within the blocks. – Roberto Caboni Jan 21 '20 at 17:22
-1

Labels as values can be used only if compiled using gcc as it is the gcc extension.

So it works 100% fine.

https://godbolt.org/z/jQvssU

enter image description here

Answering the second question - yes you can use it different functions: https://godbolt.org/z/aoA3XQ

0___________
  • 60,014
  • 4
  • 34
  • 74
  • I know it's working. But what if I use the same thing another time, just below my code in a separate scope? (Btw, I'm currently trying labels-as-values with Apple's Clang and it is working fine too) – Dr.Kameleon Jan 21 '20 at 15:42
  • Please have a look at my edit at the original post. – Dr.Kameleon Jan 21 '20 at 15:44