For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP
jumping to an xt that is supplied on the data stack.
Is there any reasonably portable Forth way to do a tail call or tail
jump?
If not, is there a way to do it in gforth?
Is there any
likelihood of standardizing such a mechanism?
Purpose is to jump from one word to another, or to the beginning of the >current word, without pushing the stack. It might have to do something
like UNLOOP if invoked in a loop.
It does NOT have to do tail call optimization in the compiler, which
could require making the compiler smarter.
Another example: actual implementation of tail recursive algorithms, e.g.:
: (factorial) {: n a -- n :}
n 0= IF a ELSE n 1- a n * TAILREC THEN ;
: factorial ( n -- n ) 1 (factorial) ;
For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP jumping
to an xt that is supplied on the data stack.
Paul Rubin <no.email@nospam.invalid> writes:
For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP
jumping to an xt that is supplied on the data stack.
I guess TAILJUMP could also be called -EXECUTE ("minus execute") or >(EXECUTE). It would be like EXECUTE except it wouldn't push a return >address.
There could also be something for coroutine jump, that would manipulate
the implementation's return address in the case where the return address >doesn't live on the return stack. Traditionally with the return stack,
CO just swaps the program counter with TOR.
Paul Rubin <no.email@nospam.invalid> writes:
Is there any reasonably portable Forth way to do a tail call or tail
jump?
A call followed by an EXIT or ";" should do it on systems that perform automatic tail-call optimization (e.g., SwiftForth).
If not, is there a way to do it in gforth?
For now, Gforth does not perform automatic tail-call optimization.
In development Gforth, you can perform an optimized tail-call (or
actually a tail-execute) explicitly with EXECUTE-EXIT.
For direct calls, you can do it as follows:
: foo ." foo" ;
: bar [ exit-like ] branch [ ' foo >body , ] ;
How long this method will continue to work in the future is unclear.
Is there any
likelihood of standardizing such a mechanism?
The current practice is that some Forth systems (e.g., SwiftForth)
perform automatic tail-call optimization (SwiftForth does it for
direct calls, including RECURSE, for EXECUTE, and for deferred words),
so if anything in that direction would be standardized at all, a
guarantee for that would be standardized. Maybe also a mechanism for
turning it off.
However, given that there is little use for recursion in Forth, and
little use of tail-recursion for loops, I doubt that there is any
consensus on requiring such a guarantee from all Forth systems. But
if you see the need, don't let my doubts stop you.
Purpose is to jump from one word to another, or to the beginning of the >current word, without pushing the stack. It might have to do something >like UNLOOP if invoked in a loop.
If you put an EXIT in a counted loop, you have to insert UNLOOP
yourself. That does not change with tail-call optimization. It may
be a good idea to do the UNLOOP(s) before the tail call, however, in
order to get the optimization.
It does NOT have to do tail call optimization in the compiler, which
could require making the compiler smarter.
Only slightly. Even Chuck Moore, who has argued (and implemented) for letting the programmer, rather than the computer do work, has
implemented automatic tail-call optimization in cmForth, machine
Forth, and AFAIK colorForth.
Another example: actual implementation of tail recursive algorithms, e.g.:
: (factorial) {: n a -- n :}
n 0= IF a ELSE n 1- a n * TAILREC THEN ;
: factorial ( n -- n ) 1 (factorial) ;
That's perverse. The natural recursive formulation of the factorial
is not tail-recursive, so one has to transform it into "(factorial)"
to get tail recursion. But what for? Forth has loops. No need to
use tail recursion where it does not fit.
For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP jumping
to an xt that is supplied on the data stack.
For deferred words and EXECUTE, we have two kinds of systems:
1) Systems based on threaded code (or following the principles used in threaded code; I think the decisive issue here is having a Forth
instruction pointer (IP) separate from the machine's program counter
(PC)), where EXECUTE and deferred words jump to a word-specific
run-time routine (such as docon, docol, dodefer, dodoes, etc.), and
where docol and dodoes push IP on the return stack, but other run-time routines do not. In these systems tail-call optimization for EXECUTE
and deferred words can be performed by popping the return address from
the return stack and storing it into IP, and then doing whatever
EXECUTE or the deferred word does normally.
2) Native-code systems usually have no separate IP, so they tend to
call all EXECUTEd and deferred words, whether they are colon
definitions or constants, and at the end of the called word, there is
a return. For these systems, it is sufficient to convert the
(possibly indirect) call into a jump.
- anton
Paul Rubin <no.email@nospam.invalid> writes:
Paul Rubin <no.email@nospam.invalid> writes:
For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP
jumping to an xt that is supplied on the data stack.
I guess TAILJUMP could also be called -EXECUTE ("minus execute") or >>(EXECUTE). It would be like EXECUTE except it wouldn't push a return >>address.
In Gforth, EXECUTE does not push a return address. If a return
address is pushed, it is pushed by docol or dodoes.
EXECUTE-EXIT pops the return address into IP and then does whatMost implementation allows return-address manipulation in a
EXECUTE does; if EXECUTE invokes docol, it pushes IP to the return
stack.
In the case of EXECUTE-EXITing a colon definition, this results in
more work, but when EXECUTEing a constant, variable, primitive, or
other word that does not push IP, it results in less work.
There could also be something for coroutine jump, that would manipulate
the implementation's return address in the case where the return address >>doesn't live on the return stack. Traditionally with the return stack,
CO just swaps the program counter with TOR.
Much of the stuff that has traditionally been does with return-address >manipulation can be done in a designated-standard way with quotations.
The de-standardization of return-address manipulation has made it easy
enough to implement tail-call optimizations and inlining that Forth
systems have actually performed them. I doubt we will see any
consensus on reverting that.
- anton
However, given that there is little use for recursion in Forth, and
little use of tail-recursion for loops, I doubt that there is any
consensus on requiring such a guarantee from all Forth systems. But
if you see the need, don't let my doubts stop you.
- anton
Is there any reasonably portable Forth way to do a tail call or tail
jump? If not, is there a way to do it in gforth? Is there any
likelihood of standardizing such a mechanism?
Any simple mechanim has to make assumptions about the target
hardware, e.g. that a call pushes onto a return stack.
TCE also complcates tools such as the disassembler.
IMHO TCE is another micro-optimisation for threaded-code
systems.
: (factorial) {: n a -- n :}
n 0= IF a ELSE n 1- a n * TAILREC THEN ;
: factorial ( n -- n ) 1 (factorial) ;
Stephen Pelc <stephen@vfxforth.com> writes:
Any simple mechanim has to make assumptions about the target
hardware, e.g. that a call pushes onto a return stack.
It's sufficient to replace a call followed by a return with a jump.
Depending on how locals cleanup is implemented, and whether you want
to tail-call optimize cases where that is in play, it needs some
additional considerations.
TCE also complcates tools such as the disassembler.
How?
IMHO TCE is another micro-optimisation for threaded-code
systems.
What makes you think so? The only systems that come to my mind that
use it are native-code systems (SwiftForth, cmForth, machine Forth);
why would they use a "micro-optimisation for threaded-code systems"?
And can you name any threaded-code systems that use it?
In development Gforth, you can perform an optimized tail-call (or
actually a tail-execute) explicitly with EXECUTE-EXIT.
That's perverse. The natural recursive formulation of the factorial
is not tail-recursive, so one has to transform it into "(factorial)"
to get tail recursion.
But what for? Forth has loops. No need to use tail recursion where
it does not fit.
The attraction (if it's something that appeals to you) is to program
without using any mutable variables such as a loop index.
I agree with
you that it's not idiomatic Forth by any stretch.
On 21-09-2026 01:04, Paul Rubin wrote:
: (factorial) {: n a -- n :}
n 0= IF a ELSE n 1- a n * TAILREC THEN ;
: factorial ( n -- n ) 1 (factorial) ;
First, lemme translate that to actual Forth:
: (factorial) over if over 1- spin * recurse ;then nip ;
: (factorial) over if over 1- spin * recurse ;then nip ;
: factorial ( n -- n ) 1 (factorial) ;
Hans Bezemer <the.beez.speaks@gmail.com> writes:
On 21-09-2026 01:04, Paul Rubin wrote:
: (factorial) {: n a -- n :}
n 0= IF a ELSE n 1- a n * TAILREC THEN ;
: factorial ( n -- n ) 1 (factorial) ;
First, lemme translate that to actual Forth:
: (factorial) over if over 1- spin * recurse ;then nip ;
"Actual Forth"?
desperately trying to mimic C behavior.
Also CO doesn't get rid of return stack items. Instead it
saves them for later use.
albert@spenarnc.xs4all.nl writes:
Also CO doesn't get rid of return stack items. Instead it
saves them for later use.
I don't understand what you mean by that. Is there a description
somewhere of exactly what it does? On the GA144 and relatives, the
coroutine switch ("EX") simply swaps the PC with the top of the R stack.
I had thought your CO word did something similar.
CO swaps the interpreter pointer with the top of the R stack
so the interpreter pointer is available later.
albert@spenarnc.xs4all.nl writes:
CO swaps the interpreter pointer with the top of the R stack
so the interpreter pointer is available later.
Ok, that is what I thought. I must have gotten confused by one of your >earlier posts that seemed to say something different.
Do you run into hazards if you have something like DO loops with loop
indexes on the return stack? It occurs to me that Chuck's
implementations bypass this problem by having FOR...NEXT instead of DO
loops. There's a similar issue if the implementation supports locals.
FOR-WORDS hides the chaining mechanism of the vocabulary, returns
dictionary address ("name tokens") and ends with a NULL.
\ Loop over a wordlist starting with dea .
: FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
\ ISO
: WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;
albert@spenarnc.xs4all.nl writes:
FOR-WORDS hides the chaining mechanism of the vocabulary, returns >>dictionary address ("name tokens") and ends with a NULL.
\ Loop over a wordlist starting with dea .
: FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
\ ISO
: WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;
This is exactly the kind of usage pattern where quotations result in
clearer code and do not need return-address manipulation:
: context@ ( -- wid ) get-order over >r set-order r> ;
: words ( -- )
[: name>string type space true ;] context@ traverse-wordlist ;
- anton--
--
M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html >comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
New standard: https://forth-standard.org/
EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
In article <2026Sep27.183911@mips.complang.tuwien.ac.at>,
Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote: >>albert@spenarnc.xs4all.nl writes:
FOR-WORDS hides the chaining mechanism of the vocabulary, returns >>>dictionary address ("name tokens") and ends with a NULL.
\ Loop over a wordlist starting with dea .
: FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
\ ISO
: WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;
This is exactly the kind of usage pattern where quotations result in >>clearer code and do not need return-address manipulation:
I object that CO is called "return address manipulation".
It is a documented control structure.
: context@ ( -- wid ) get-order over >r set-order r> ;
: words ( -- )
[: name>string type space true ;] context@ traverse-wordlist ;
No. The comparison is between the implementation of traverse-wordlist
and FOR-WORDS.
In ciforth without using the CO words it is similar
: WORDS 'ID. CONTEXT @ FOR-WORDS ;
This is however an uglier FOR-WORDS.
albert@spenarnc.xs4all.nl writes:
In article <2026Sep27.183911@mips.complang.tuwien.ac.at>,
Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote: >>>albert@spenarnc.xs4all.nl writes:
FOR-WORDS hides the chaining mechanism of the vocabulary, returns >>>>dictionary address ("name tokens") and ends with a NULL.
\ Loop over a wordlist starting with dea .
: FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
\ ISO
: WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;
This is exactly the kind of usage pattern where quotations result in >>>clearer code and do not need return-address manipulation:
I object that CO is called "return address manipulation".
It takes the return address off the return stack and does something
with it. If you do "0 >R CO R> DROP" instead of just CO, it does not
work.
It is a documented control structure.
What is it's documentation?
: context@ ( -- wid ) get-order over >r set-order r> ;
: words ( -- )
[: name>string type space true ;] context@ traverse-wordlist ;
No. The comparison is between the implementation of traverse-wordlist
and FOR-WORDS.
Why should that be more relevant than the implementation of WORDS?
But anyway, here's Gforth's implementation of TRAVERSE-WORDLIST.
: traverse-wordlist ( ... xt wid -- ... ) \ tools-ext
\G perform @i{xt} ( ... nt -- f ... ) once for every word @i{nt}
\G in the wordlist @i{wid}, until @i{f} is false or the wordlist
\G is exhausted. @i{xt} is free to use the stack underneath.
swap >r wordlist-id @
BEGIN
dup
WHILE
r@ over >r execute WHILE r> name>link
REPEAT r>
THEN drop rdrop ;
- anton
| Sysop: | Amessyroom |
|---|---|
| Location: | Fayetteville, NC |
| Users: | 74 |
| Nodes: | 6 (0 / 6) |
| Uptime: | 122:20:41 |
| Calls: | 1,194 |
| Files: | 1,352 |
| Messages: | 290,355 |