i've been fiddling with a series of prototype interpreters for a simple stack machine over the last year or so, and i've come to a few tentative conclusions:

1. it's probably a win to implicitly stick the arguments to a subroutine into local variables on subroutine entry, as smalltalk-80 and elisp and java do, rather than letting the subroutine do that itself if it wants to, as forth and postscript and hp calculators do. this is mostly for convenience and readability, but it also affects efficiency and safety. in the case of a straightforward stack interpreter, each stack operation takes about the time of 10 machine instructions. for example, in http://canonical.org/~kragen/sw/dev3/hadixmv.S, the addition bytecode instruction is implemented by the following arm assembly code

ldmdb r4!, {r3} @ cargar segundo item de la pila r4 en r3 temporario
add r5, r5, r3 @ sumar número cargado al primero en pila
dispatch

here `dispatch` is an assembly macro that expands (in my un-checked-in version) to

ldrb r3, [r6], #1 @ cargar próxima instrucción, post-index
ldr pc, [r7, r3, lsl #2] @ indexar tabla de bytecodes apuntada por r7

so this is four instructions, but three of them access memory, making them slower (on my target platform), and the last one usually causes a pipeline bubble, so this takes typically 10 clocks on my target chip

a few bytecode instructions are a bit faster than this, a few are much slower, and the average seems to come out to about 15 clocks, while an average instruction is maybe closer to 1.2 clocks, depending on your code. so there's a strong temptation to do anything that will allow you to waste less time in bytecode dispatch

i think glenn is strawmanning a bit here with his arg5. i think you'd write something like

/settext [/text /x /y /size /font] {
font findfont size scalefont setfont
x y moveto text show
} fn

that's still 10 calls inside the subroutine instead of 6, but glenn only got down to 6 because he put the arguments in almost the most convenient order

2. probably a super dumb jit compiler, if well optimized, will never be much slower than a straightforward bytecode interpreter, and in common cases will be about three times faster, but the overall system performance will still be about three times slower than well-optimized assembly. and i can write the compiler in the language i'm implementing (though i can't optimize it that way) which is a lot more convenient than writing it in assembly. even arm assembly, which is super sweet. for this reason, for many years now, the jvm 'interpreter' (what it uses for interpretation of everything that hasn't yet been found to be a hotspot) is a super dumb jit compiler and not actually an interpreter

this tends to undermine the performance argument for automatically creating local variables like this, but i think not completely

3. stack machines have more compact code than register machines, but because you need about twice as many operations to do a given job, the interpreter dispatch overhead is twice as expensive. my primary objective is code density, and although i haven't compiled anything big yet, i think i can get about 2/3 the code size of thumb-2 or rv32c. but a register-based code is probably better when code density isn't the primary objective