In which I create a real return stack
So far, the return stack has been a hybrid of the Swift stack for returning from code and a stack implemented within the code for temporary values and loop control. It’s time to implement the whole stack in the code. I’m not sure if this will be beneficial or detrimental to performance. If it has a really bad effect, I may back the change out. For this reason, I am working on a new branch in git.
As preparation, I have tightened up the requirements for the collection passed into the
interpret function. It now has to be a random access collection and the index has to be an
Int. This is because I need to store the index on the return stack. The code is tagged blog-1229-1.With this change in, the performance is as follows:
Mean time = 2.2451142259999997) Mean mips = 2.41133254482349 Time standard deviation = 0.05344836229779769 words executed: 5413717
The initial attempt at storing return addresses on the return stack is tagged blog-1229-2. Some points to note about it:
- The mechanism for returning the loop indexes
Jis broken because the they are defined as ordinary words and when they are called, they put a return address on the return stack above the loop index.
- There’s a failing unit test in
- We have lost some performance. It’s approximately 12% slower.
Mean time = 2.7090813193999996) Mean mips = 1.998358986580372 Time standard deviation = 0.11162057920377298
The first problem is easily fixed. The definitions for
I goes from
: i r.loop> ( -- e i ) 2dup ( e i -- e i e i ) >r.loop ( e i e i -- e i ) swap drop ( e i -- i ) ;
: i postpone r.loop> ( -- e i ) postpone 2dup ( e i -- e i e i ) postpone >r.loop ( e i e i -- e i ) postpone swap postpone drop ( e i -- i ) ; immediate compile-only
which means that,
I now compiles the instructions directly into the current compiling word instead of a call to
I. A similar change can be made to
J to fix that too.
The failing unit test in
testQuote is because the
EXECUTE primitive always tries to execute the execution token on the stack as if it was a primitive.
EXECUTE needs to be restructured to call non primitives rather than execute them. This bug wasn’t picked up because
execute takes a
CompiledWord as its first argument, not a primitive and just ignores the word if it isn’t a primitive. So, we’ll make it so it has to be a primitive and save ourselves a check in
The code that implements the two fixes is tagged blog-1229-3. Note that, as part of the fix for the quote issue, I have moved
index out of the interpret loop to be properties of the Forth machine. This has had no serious effect on performance. On the other hand, I can probably remove the
ipAdjustment fix and change
index directly for branches etc.
Mean time = 2.6311988872) Mean mips = 2.0575096114307905 Time standard deviation = 0.02964115736226729
Having made the above changes, stack performance is now critical, so I took the decision to reimplement the Toolbox stack directly in the MyForth module. This opens the code up to more optimisations and the performance effect is dramatic:
Mean time = 1.06199634395) Mean mips = 5.097679507882454 Time standard deviation = 0.030844099259880086