13.1 Threaded code

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

: squared dup * ;

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