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)
?
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.
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.
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).
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.
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.
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 */
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With