In this post I would like to talk about Fleet Forth's DO LOOPs. Just a reminder, Fleet Forth is an ITC Forth for the C64.
Based on an idea from Garth Wilson, Fleet Forth's DO LOOP's place three cells on the return stack. There is no primitive (LEAVE) or (?LEAVE) . LEAVE and ?LEAVE are the primitives. LEAVE and ?LEAVE do not have an inline branch address. They use one of the DO LOOP parameters.
The three parameters placed on the return stack by (DO) and (?DO) are:
The address LEAVE uses to branch out of the loop is placed there first. This address is copied from an inline cell following (DO) or (?DO) .
The loop's limit.
The index.
LEAVE removes the index and limit from the return stack and discards them, it removes the address saved as one of the parameters and stores it in IP, effecting a branch out of the loop. In the event the loop runs to completion, (LOOP) ( or (+LOOP) ) exits the same way LEAVE does.
This short word:
Code: Select all
: TEST
DO
CR I .
LEAVE
LOOP
;
compiles to this:
Code: Select all
1000 (DO)
1002 1010 \ Address saved on return stack used to branch out of loop
1004 CR
1006 I
1008 .
100A LEAVE
100C (LOOP)
100E 1004 \ Address used to branch back to beginning of loop
1010 EXIT
Note that there is no branch address after LEAVE .
Why have (LOOP) behave so much like LEAVE instead of stepping IP over the branch address? With this version of DO LOOP's there are three reasons:
1. It is faster. Since all three DO LOOP parameters have to be pulled from the return stack anyway, storing the final parameter in IP is faster than adding the value 2 to IP.
2. It is smaller. LEAVE (LOOP) and (+LOOP) use the same code sequence to exit a DO LOOP. (+LOOP) branches into (LOOP) . LEAVE has no body, it's code field points into the appropriate location in the body of (LOOP) .
But wait! there is even more memory to be saved. EXIT also has no body and a code field that points at a later point in (LOOP) since EXIT pulls one cell from the return stack and stores it in IP.
3. Consistency. LEAVE ( and ?LEAVE ) always branch to the same address that (LOOP) and (+LOOP) go to when the loop terminates.
This behavior of LEAVE ( using an address saved as one of the DO LOOP parameters) allows some interesting possibilities. LEAVE normally can only exit the DO LOOP it is a part of. For example.
Code: Select all
DO
<high level stuff>
LEAVE
<high level stuff>
LOOP
<LEAVE branches to here>
In this code fragment, LEAVE branches to the code right after LOOP's branch address as expected.
In this fragment:
Code: Select all
DO
<high level stuff>
DO
<high level stuff>
LEAVE
<high level stuff>
LOOP
<LEAVE branches to here>
<high level stuff>
LOOP
LEAVE once again branches to the code right after the first LOOP's branch address, also as expected.
However, in this code fragment I introduce UNLOOP , a primitive that discards the loop parameters. It only discards the parameters. This makes it possible to leave nested loops by having an UNLOOP for each level of DO LOOP nesting, then the word EXIT to exit that word from within one or more DO LOOPS.
Code: Select all
DO
<high level stuff>
DO
<high level stuff>
UNLOOP UNLOOP EXIT \ Exit this word from within a nested DO LOOP.
<high level stuff>
LOOP
<high level stuff>
LOOP
In this next example, UNLOOP is not used to allow EXITing a word with a DO LOOP, but to make it possible for LEAVE in the inner loop to branch out of the outer loop.
Code: Select all
DO
<high level stuff>
DO
<high level stuff>
UNLOOP LEAVE
<high level stuff>
LOOP
<high level stuff>
LOOP
<LEAVE branches to here>
UNLOOP discards the inner DO LOOP's parameters and LEAVE uses the address from the outer DO LOOP's parameters to branch to right after the second LOOP's branch address.