And finally, see and friends show compiled code. Some of the
things in the source code are not present in the compiled code (e.g.,
formatting and comments), but this is useful to see what threaded code
or native code is produced by macros and Gforth’s optimization
features.
Locate name using the current search order. Display the definition of name. Since this is achieved by decompiling the definition, the formatting is mechanised and some source information (comments, interpreted sequences within definitions etc.) is lost.
Decompile the definition represented by xt.
Decompile the colon definition name, showing a line for each cell, and try to guess a meaning for the cell, and show that.
Decompile the colon definition xt like
simple-see
Decompile code in [addr1,addr2) like simple-see
Like simple-see, but also shows the dynamic native code for
the inlined primitives. For static superinstructions, it shows the
primitive sequence instead of the first primitive (the other
primitives of the superinstruction are shown, too). For primitives
for which native code is generated, it shows the number of stack
items in registers at the beginning and at the end (e.g.,
1->1 means 1 stack item is in a register at the start and at
the end). For each primitive or superinstruction with native code,
the inline arguments and component primitives are shown first, then
the native code.
Decompile the colon definition xt like see-code.
Decompile code in [addr1,addr2) like see-code.
As an example, consider the following contrived definition:
This is not particularly useful, but it demonstrates the various
variants in Gforth’s code generation. Compiling this on
gforth-fast on AMD64 and then using see-code foo
outputs:
<foo> dup >r 1->1 <foo+$8> >r 1->1 <foo+$10> drop 1->0 $7FBC47873933: mov %r13,-0x8(%r14) $7FBC47873937: sub $0x8,%r14 <foo+$18> lit 0->1 <foo+$20> #5 <foo+$28> fsin 1->1 $7FBC4787393B: mov 0x20(%rbx),%r13 $7FBC4787393F: add $0x28,%rbx $7FBC47873943: mov (%rbx),%rax $7FBC47873946: jmp *%rax <foo+$30> ;s 1->1 $7FBC47873948: mov (%r14),%rbx $7FBC4787394B: add $0x8,%r14 $7FBC4787394F: mov (%rbx),%rax $7FBC47873952: jmp *%rax
This shows two sequences: The threaded code at
<foo>...<foo+$30>, and the dynamically-generated native code at
$7FBC47873933...$7FBC47873952. These two sequences are
shown in an interleaved way, ideally such that we see the threaded
code of a primitive or static superinstruction (including immediate
arguments and slots for the remaining primitives of a static
superinstruction) is followed by the native code for the primitive or
static superinstruction. In some cases, Gforth deviates from this
ideal, as we will see. But the important thing to remember is that
the two sequences are at different places in memory and are not
interleaved there. You can easily see that by noticing that there is
no room for the native code between the threaded-code addresses, nor
is there room for the threaded code between the native-code addresses.
In the example, you first see a threaded-code cell for a static
superinstruction with the components dup and >r,
starting and ending with one data stack item in a register
(1->1); this is followed by the the cell for the >r
component of the superinstruction; the latter cell is not used, but is
there, because static superinstructions are formed only after the
threaded-code layout has been determined. Next, you see the threaded
code for drop, which starts with one data stack item in
registers and ends with no data stack items in registers
(1->0), i.e., all data stack items are in memory.
Next, the dynamically generated native code for the superinstruction
dup >r is shown.
There is no native code executed for drop in the 1->0
case, so no native code is shown for drop, and that’s also the
reason why the threaded code for drop is shown before the
native code for dup >r; see-code is not clever enough to
show the drop behind that native-code fragment.
If you want to understand the native code shown here: the return-stack
pointer is in %r14, and the first data stack register is
%r13 (i.e., the top of stack resides there if there is one data
stack item in a register). The threaded-code instruction pointer (IP)
is in %rbx (used by lit and ;s in this example).
Note that the register assignments vary between builds, so you may see
a different register assignment for this code.
Next, the threaded code for lit is shown as well as its stack
states (0->1), followed by the cell of the immediate argument
5 of lit.
Then the threaded code for fsin is shown. There is no
dynamically-generated native code for fsin, and see-code
does not show the static native code for this word (you can look at it
with see fsin), that’s why no native code is shown for
fsin, and its threaded code is shown adjacent to the previous
threaded code.
The next line is the native code for the lit, which is adjacent
to the last shown native code. The code for lit loads the 5
residing at <foo+$20> with an access that uses IP (which still
points to <foo> at this point) with an offset of $20
(shown as 0x20).
Because the (static) native code for fsin is elsewhere, code
for jumping to that native code has to be generated: First, IP is
brought up-to-date with the instruction at $7FBC4787393F and
after that IP points to <foo+$28>. The instruction at
$7FBC47873943 loads the code address of fsin from IP,
and the next instruction jumps to that code address, executing
fsin. These two instructions constitute a direct-threaded code
dispatch (NEXT1).
Next, we see the threaded code of ;s (the primitive compiled
for ; in foo), and then the dynamically generated native
code for ;s: It loads IP from the return stack and updates the
return stack pointer. Because it performs control flow, ;s has
to end in a direct-threaded code dispatch.