Forth Language Interpreter in C

This one is an interesting implementation detail. I'm going to go over how the Forth language interpreter works, specifically how a given Forth word becomes a C++ function. Forth word implementations are denoted by the label "prim" everywhere. Looking in vm_min.h we have code such as:

vm_min.h
prim_proto(int32_t, AND, (const int32_t, const int32_t))
which defines the Forth word "AND" and creates the C function prototype. This is later expanded in interpreter.cpp to:
interpreter.cpp
#define prim_proto(ret,nm,args) PRIM(ret,nm) args ;
And after looking at the macro definition of Prim:
interpreter.cpp
#define PRIM(ret,x) AVMPLUS_FORCE_INLINE static ret prim_##x
The AVMPLUS_FORCE_INLINE forces an inline of all the method calls. When fully expanded, the C function prototype that is generated by the C preprocessor is:
interpreter.cpp
INLINE static int32_t prim_AND(const int32_t, const int32_t);
The actual implementation is then supplied later in the file:
interpreter.cpp
PRIM(int32_t, AND)(const int32_t a, const int32_t b)
{
return a & b;
}
Again, these are only for Forth words. Now we have the function definitions for the Forth words being used in "vm.fs". However, to actually implement the Forth semantics, a few more things need to happen, like mimicking the stack. Located in vm_min_interp.h is a long series of generated code from the Python forth compiler. Continuing our example of using the Forth word "AND":

vm_min_interp.cpp
INTERP_PRIMCASE(AND)
INTERP_CALLPRIM(int32_t, i_1_0, AND, (sp[-1].i, sp[0].i))
INTERP_COPY(sp[-1].i, i_1_0)
INTERP_INVALBOXTYPE(sp[-1])
INTERP_ADJUSTSP(-1)
INTERP_NEXT(AND)

The function calls here are actually macro calls which get expanded in Interpreter.cpp as shown here:

interpreter.cpp
#define INTERP_PRIMCASE(x) case x: { TIMING_START(t_interp)

#define INTERP_CALLPRIM(rtype,ret,x,args) INTERP_CALLFUNCBYNAME(rtype, ret, prim_##x, args)
#define INTERP_CALLFUNCBYNAME(_rettype,_result,_funcname,_args) const _rettype _result = _funcname _args;
#define INTERP_COPY(dst,src) dst = src;

#define INTERP_INVALBOXTYPE(nm) INVALIDATE_BOX_TYPE(nm);
#define INTERP_ADJUSTSP(d) sp += (d);
#define INTERP_NEXT(x) } goto top_of_loop;


There are two more macro calls within this which are:
interpreter.cpp
#if defined(_DEBUG) || defined(AVMPLUS_VERBOSE)
#define INTERP_PRE inner_pre_interp(f, ip, sp, rp);
#else
#define INTERP_PRE
#endif

So unless the debug flag is set, the INTERP_PRE does nothing. Heres the macro for INVALIDATE_BOX_TYPE:

Box.h
#ifdef INVALIDATE_BOXTYPES
#define INVALIDATE_BOX_TYPE(x) do { (x).parts.type = BoxedInvalid; } while (0)
#else
#define INVALIDATE_BOX_TYPE(x) do { } while (0)
#endif
So after all the macro expansion, the final hand typed could would look like:
case AND: {
const int32_t i_1_0 = prim_AND(sp[-1].i, sp[0].i);
sp[-1].i = i_1_0;
do { (sp[-1]).parts.type = BoxedInvalid; } while (0);
sp += -1;
} goto top_of_loop;


Finally this is all wrapped around a switch statement:
#define NEXTIP        (*ip++)

switch (NEXTIP) {
#include "vm_min_interp.h"
}