Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use a pre-processor directive for a 'case' statement?

Tags:

c++

I was browsing through the SpiderMonkey engine source and saw some code in the interpreter that intrigued me.

// Portable switch-based dispatch.
# define INTERPRETER_LOOP()       the_switch: switch (switchOp)
# define CASE(OP)                 case OP:
# define DEFAULT()                default:

(source: https://dxr.mozilla.org/mozilla-b2g44_v2_5/source/js/src/vm/Interpreter.cpp#1579)

Is there any non-stylistic benefit for defining something like case OP: as CASE(OP)?

like image 310
learner0000 Avatar asked Aug 09 '16 22:08

learner0000


People also ask

Why preprocessing directives are used?

Preprocessor directives, such as #define and #ifdef , are typically used to make source programs easy to change and easy to compile in different execution environments. Directives in the source file tell the preprocessor to take specific actions.

What is preprocessor directives and why?

Preprocessor directives are lines of the source file where the first non-whitespace character is # , which distinguishes them from other lines of text. The effect of each preprocessor directive is a change to the text and the result is a transformation of the text that does not contain the directives nor comments.

What is a pre processor statement?

Preprocessor statements are handled by the compiler (or preprocessor) before the program is actually compiled. All # statements are processed first, and the symbols (like TRUE) which occur in the C program are replaced by their value (like 1).

Are preprocessor directives case sensitive?

A preprocessor directive statement must occupy a single line only, and there cannot be more than one statement in any one line. Preprocessor directive names are not case sensitive, but are typically written in lower case.


2 Answers

Look up half a screen:

#if (defined(__GNUC__) ||                                                         \
     (__IBMC__ >= 700 && defined __IBM_COMPUTED_GOTO) ||                      \
     __SUNPRO_C >= 0x570)
// Non-standard but faster indirect-goto-based dispatch.
# define INTERPRETER_LOOP()
# define CASE(OP)                 label_##OP:
// ... <snip>
#else
// Portable switch-based dispatch.
# define INTERPRETER_LOOP()       the_switch: switch (switchOp)
# define CASE(OP)                 case OP:
// ... <snip>
#endif

GCC and some other compilers support "computed goto", which is faster than a loop-switch for an interpreter loop, but is non-standard and hence non-portable.

If the compiler supports computed goto, the first branch of this #if defines INTERPRETER_LOOP, CASE(OP) etc. to use computed goto; otherwise, the #else branch defines them in terms of standard facilities.

like image 126
ecatmur Avatar answered Oct 15 '22 15:10

ecatmur


If you look higher up in the same source, there are different definitions for those same macros for different compiler syntaxes:

/*
 * Define macros for an interpreter loop. Opcode dispatch may be either by a
 * switch statement or by indirect goto (aka a threaded interpreter), depending
 * on compiler support.
 *
 * Threaded interpretation appears to be well-supported by GCC 3 and higher.
 * IBM's C compiler when run with the right options (e.g., -qlanglvl=extended)
 * also supports threading. Ditto the SunPro C compiler.
 */
#if (defined(__GNUC__) ||                                                         \
     (__IBMC__ >= 700 && defined __IBM_COMPUTED_GOTO) ||                      \
     __SUNPRO_C >= 0x570)
// Non-standard but faster indirect-goto-based dispatch.
# define INTERPRETER_LOOP()
# define CASE(OP)                 label_##OP:
# define DEFAULT()                label_default:
# define DISPATCH_TO(OP)          goto* addresses[(OP)]

//...

#else
// Portable switch-based dispatch.
# define INTERPRETER_LOOP()       the_switch: switch (switchOp)
# define CASE(OP)                 case OP:
# define DEFAULT()                default:
# define DISPATCH_TO(OP)                                                      \
    JS_BEGIN_MACRO                                                            \
        switchOp = (OP);                                                      \
        goto the_switch;                                                      \
    JS_END_MACRO

// ...

#endif

If you look further down in the same source, you will see these macros actually being used:

INTERPRETER_LOOP() {

CASE(EnableInterruptsPseudoOpcode)
{
    //...
    DISPATCH_TO(op);
}

* Various 1-byte no-ops. */
CASE(JSOP_NOP)
CASE(JSOP_UNUSED14)
CASE(JSOP_BACKPATCH)
//...
{
    //...
    ADVANCE_AND_DISPATCH(1);
}

CASE(JSOP_LOOPHEAD)
END_CASE(JSOP_LOOPHEAD)

//...

DEFAULT()
{
    //...
    goto error;
}

} /* interpreter loop */

Depending on the compiler, that code would compile to either this:

static const void* const addresses[EnableInterruptsPseudoOpcode + 1] = {
    ...
};

...

{

label_EnableInterruptsPseudoOpcode:
{
    //...
    goto* addresses[op];
}

* Various 1-byte no-ops. */
label_JSOP_NOP:
label_JSOP_UNUSED14:
label_JSOP_BACKPATCH:
//...
{
    //...
    REGS.pc += 1;
    SANITY_CHECKS();
    goto* addresses[*REGS.pc | activation.opMask()];
}

label_JSOP_LOOPHEAD:
    goto* addresses[JSOP_LOOPHEAD_LENGTH];

//...

label_default:
{
    //...
    goto error;
}

} /* interpreter loop */

Or to this:

jsbytecode switchOp;

...

the_switch:
switch (switchOp) {

case EnableInterruptsPseudoOpcode:
{
    //...
    switchOp = op;
    goto the_switch;
}

* Various 1-byte no-ops. */
case JSOP_NOP:
case JSOP_UNUSED14:
case JSOP_BACKPATCH:
//...
{
    //...
    REGS.pc += 1;
    SANITY_CHECKS();
    switchOp = *REGS.pc | activation.opMask;
    goto the_switch;
}

case JSOP_LOOPHEAD:
    REGS.pc += JSOP_LOOPHEAD_LENGTH;
    SANITY_CHECKS();
    switchOp = *REGS.pc | activation.opMask();
    goto the_switch;  

//...

default:
{
    //...
    goto error;
}

} /* interpreter loop */
like image 33
Remy Lebeau Avatar answered Oct 15 '22 16:10

Remy Lebeau