6


I'm making some kind of interpreter and I'm computing a static const jump table thanks to local label addresses.
You know the drill, static const int JUMP_TABLE[] = { &&case0 - &&case0, &&case1 - &&case0 and so on.
For various reasons, mostly performance, I'd like to copy/compress this table in an object during init.
I'm hurting my head against the wall because I can't figure how to escape the lexical scoping of the function !

How can I somehow reference &&case0 from another function ?

Does somebody have a good trick for this ?
Thanks in advance

Tramboi
  • 151
  • 5
  • I don't see how this wouldn't destroy the stack unless the compiler preserves it in such a way that you might as well be calling actual functions. – chris May 16 '20 at 08:12
  • 2
    @chris: The question does not say they want to jump to a target from outside the function in which it is defined, just that they want to compute with it. Presumably the computation results would be used inside the function. – Eric Postpischil May 16 '20 at 08:21
  • I don't want to jump from another function, I want to read the offset in a table that is not static const.To do some checking, to compress it, and to access it with live registers, not by loading constants in .text. – Tramboi May 16 '20 at 08:27
  • 1
    Sorry, I vastly misunderstood the question. – chris May 16 '20 at 15:57
  • No problem @chris, the issue is that I don't know what the solution looks like so I can't really show code. And I'm not a native speaker so my description might be... a bit unclear :) – Tramboi May 16 '20 at 18:29

2 Answers2

3

I'm not aware of ways to achieve this within pure GNU C so approaches below use other mechanisms.

Double compilation

You can compile your object file twice, collecting offsets on the first run and using them on the second. For example

int foo(int x) {
#ifdef GENERATE_ADDRESSES
    static __attribute__((section(".foo_offsets"))) unsigned offsets[] = { &&case0 - &&case0, &&case1 - &&case0 };
#endif
    switch (x) {
case0:
        case 0:
            return 1;
case1:
        case 1:
            return 2;
    }
    return 0;
}

Now you can compile, extract bytes from .foo_offsets section and embed them to you app on second run

$ gcc tmp.c -c -DGENERATE_ADDRESSES
$ objcopy -j .foo_offsets -O binary tmp.o
$ xxd -i tmp.o | tee offsets.inc
unsigned char tmp_o[] = {
  0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00
};
unsigned int tmp_o_len = 8;

Inline assembly

You can use inline assembly to globalize labels:

extern char foo_case0[];
extern char foo_case1[];
const void *foo_addresses[] = { &foo_case0[0], &foo_case1[0] };

int foo(int x) {
    switch (x) {
        case 0:
asm("foo_case0:");
            return 1;
        case 1:
asm("foo_case1:");
            return 2;
    }
    return 0;
}

Unfortunately in this case you can only collect addresses (not offsets) so you'll need to manually compute offsets at startup.

yugr
  • 19,769
  • 3
  • 51
  • 96
  • Thank you so much, the second way is exactly what I need ! It's self-contained, and I can pay the footprint of computing my table at runtime. – Tramboi May 17 '20 at 08:48
0

Sometimes Goto is simply the best solution. Tho very rare. Its still part of c++ after many years for a good reason

So what i did was create a global bool and set it after I initialize my address array. So the first time my interpreter function is called, it loads a structure of addresses so everything is within the same function.

Then with a little study of the assemblers output i was able to save a few ticks by arranging my code as follows.

if(is_initialized) .. ex command else ... initialize stuff. Goto ex command

use a goto to jump back to the top and execute the command. My interpreter is using almost 200 commands.

With a switch statement it was taking 5-1100 ticks. Depending on how far down the list the command was

Using goto functions[command] has it down to 14 regardless of where the command is in the list

This provides a method that is purely c++ but not supported on all compilers

ron
  • 186
  • 7