Direct-threaded code is a fast interpretation technique, where primitives are represented as code addresses in the interpreted code, and each code address points to the code that implements the primitive.
We will use
as example. The threaded code for this source code looks as follows:
address content $7F6208AA7FE0 $5640443C831C \ at $5640443C831C the code of DUP starts $7F6208AA7FE8 $5640443C74DF \ at $5640443C74DF the code of * starts $7F6208AA7FF0 $5640443C6BE6 \ at $5640443C6BE6 the code of ;s starts
The exact numbers vary between builds and between runs of the same build. Looking at these primitives:
see dup Code dup $5640443C831C: add $0x8,%rbx $5640443C8320: mov %r13,(%r10) $5640443C8323: sub $0x8,%r10 $5640443C8327: mov (%rbx),%rax $5640443C832A: jmp *%rax end-code ok see * Code * $5640443C74DF: add $0x8,%rbx $5640443C74E3: imul 0x8(%r10),%r13 $5640443C74E8: add $0x8,%r10 $5640443C74EC: mov (%rbx),%rax $5640443C74EF: jmp *%rax end-code ok see ;s Code ;s $5640443C6BE6: mov (%r14),%rbx $5640443C6BE9: add $0x8,%r14 $5640443C6BED: mov (%rbx),%rax $5640443C6BF0: jmp *%rax end-code
We see that they start at the addresses given in the threaded code.
We also see the following instructions (not consecutive) in dup
and *, and the last two of these instructions in ;s (the
primitive compiled by ;).
add $0x8,%rbx \ update threaded-code instruction pointer (IP) mov (%rbx),%rax \ get code address from IP jmp *%rax \ jump to code address
This sequence ensures that the execution continues with the next
primitive and is called NEXT. %rbx is the threaded-code
instruction pointer (IP), %rax is just used as a temporary
register (the actual registers can be different in different builds,
even on the same architecture). The NEXT that entered the word
squared left %rbx at $7F6208AA7FE0 and jumped to
$5640443C831C (dup). The NEXT in dup advances
%rbx to the next threaded-code address, then loads the code
address into %rax and finally jumps there.
In this way one primitive after another is executed, and for
threaded-code control flow, %rbx is set differently (as in
;s, which returns to the caller of squared).
The remaining instructions in dup and * perform the work
we are actually interested in; you find the top-of-data-stack (TOS) in
%r13, and the data stack pointer (SP) in %r10. The data
stack grows towards smaller addresses.
Gforth compiles most non-primitives into a primitive followed by an
inline argument (primitive-centric code). E.g., a variable ist
compiled to the primitive lit with the body address of the
variable as inline argument. A word X defined with
create... ( xt ) set-does> is compiled as lit
with the body address of X as inline argument, followed by
compile,ing xt.
That’s all nice and well for code coming out of compile,, but
execute expects an xt that fits in one cell. How do we
transport the code address and the data address (for a variable) in
one cell? Here’s (a slightly simplified) execute:
add $0x8,%r10 \ adjust SP mov -0x10(%r13),%rax \ load code address from code field of xt mov %r13,%rdx \ store xt in W (for use by the executed code) mov (%r10),%r13 \ load the new TOS from SP jmp *%rax \ jump to the code address
The code addresses that execute jumps to are not normal
primitives, they are doers: routines like that at dovar: (all
these routines have names starting with “do”, that’s why they are
called “doers”). The routine at dovar: looks as follows:
add $0x8,%rbx \ NEXT (adjust IP) mov %r13,(%r10) \ store TOS (make room for new TOS) sub $0x8,%r10 \ adjust SP mov %rdx,%r13 \ move W to TOS mov (%rbx),%rax \ NEXT jmp *%rax \ NEXT
Note that the indirection through a code field as execute is
doing is what indirect-threaded code does, so we actually have a
hybrid of direct-threaded and indirect-threaded code: direct-threaded
code for compile,d code, indirect-threaded code for
execute (deferred words also use this).
Is there any chance of doing direct-threaded dispatch when indirect-threaded dispatch is called for, or vice versa? No, because IP always points to direct-threaded code, while every dispatch through an xt is indirect-threaded.
For a word X defined with xt1 set-does>, executing
X through its xt (xt2) has another twist: The code address
involved is dodoes:, and it gets xt2 in W (rdx). It
loads the xt1 from the >hmextra slot of the header methods
of X (see Header methods). Here’s the code of dodoes
(slightly simplified):
mov %r13,(%r10) \ store TOS (make room for new TOS) mov -0x8(%rdx),%rax \ load header methods of X, using W mov %rdx,%r13 \ move W to TOS, i.e. push X's body address sub $0x8,%r10 \ update SP mov 0x18(%rax),%rdx \ load xt1 from >hmextra into W mov -0x10(%rdx),%rax \ load code address from code field of xt jmp *%rax \ jump to the code address
Note that dodoes is not just jumped to by a dispatch through an xt (xt2), it performs a dispatch through an xt (xt1) itself, so it also loads from a code field and sets up W.
Primitive-centric hybrid direct/indirect threaded code is described in: M. Anton Ertl, Threaded code variations and optimizations (extended version), Forth-Tagung ’02.
You can invoke gforth-fast to execute just threaded code
without additional optimizations with
gforth-fast --no-dynamic --ss-number=0