If you see-code a colon definition in gforth, for the
primitives in the colon definition you see native code that is copied
from the code that you see for the primitive, without or with
the part of the NEXT at the end.
If you see-code colon definitions in gforth-fast, you
often see code coming from other variants of the primitive, often
without the IP update, or with a different IP update. This code is
copied from other variants of the primitive in the engine. There is
no simple way to see these variants, but starting the engine with
--print-prims prints out information about all primitive
variants when ending the session. If you are interested in a
particular primitive, you can grep for it in the output, e.g.:
$ gforth-fast --print-prims -e bye |& grep '^[+] ' + 1-1 0 67 0 77 0x5555555bc427 len= 4+ 7+ 3 send=0 + 2-1 0 802 0 189 0x5555555c24f9 len= 4+ 3+ 3 send=0 + 3-2 0 803 0 77 0x5555555c2505 len= 4+ 3+ 3 send=0
The first column contains the name of the primitive, the second the stack-state transition. Then comes the IP offset (discussed below), next the primitive index (just a number), then (if non-zero) the index of the branch-to-IP variant discussed below, then the number of instances of this primitive variant in the generated native code, then the code address of the native code followed by its length, and finally an indication of whether the primitive ends a dynamic superinstruction.
The length is split into three parts: first the IP update, then the payload, and finally the load of the code address; the actual jump is not included (that’s a workaround bad code generation in some gcc versions).
In the above you see that there are three variants of + with
different stack-state transitions (see Stack caching), that the
2-1 variant is generated more often than the other two
combined, and that the 1-1 variant has longer native code than
the others.
If you want to use the address and the length to disassemble the
primitive variant, you usually need to invoke Gforth on Linux with
setarch -R gforth-fast to disable ASLR (or equivalent
commands on other OSs), on both the --print-prims run and the
following disassembling session.
E.g., you can disassemble the payload of + 3-2 (index 803)
with:
Here the 4 is the length of the code before the payload, the 3 is the length of the payload.
Concerning the IP offset, consider the following:
$ gforth-fast --print-prims -e bye |& grep '^lit ' lit 1-1 0 66 0 0 0x5555555bc413 len= 4+ 11+ 3 send=0 lit 0-1 0 659 0 351 0x5555555c1a4b len= 4+ 4+ 3 send=0 lit 0-1 1 660 0 1 0x5555555c1a58 len= 4+ 3+ 3 send=0 lit 0-1 2 661 0 1841 0x5555555c1a64 len= 4+ 4+ 3 send=0 lit 0-1 3 662 0 340 0x5555555c1a71 len= 4+ 4+ 3 send=0 ... lit 1-2 0 683 0 165 0x5555555c1b97 len= 4+ 4+ 3 send=0 lit 1-2 1 684 0 6 0x5555555c1ba4 len= 4+ 3+ 3 send=0 lit 1-2 2 685 0 1708 0x5555555c1bb0 len= 4+ 4+ 3 send=0 lit 1-2 3 686 0 211 0x5555555c1bbd len= 4+ 4+ 3 send=0 ... lit 2-3 0 707 0 36 0x5555555c1ce3 len= 4+ 4+ 3 send=0 lit 2-3 1 708 0 0 0x5555555c1cf0 len= 4+ 3+ 3 send=0 lit 2-3 2 709 0 53 0x5555555c1cfc len= 4+ 4+ 3 send=0 lit 2-3 3 710 0 191 0x5555555c1d09 len= 4+ 4+ 3 send=0 ...
Here you see variants of lit with different stack-state
transitions and different IP offsets (with the offset given as number
of cells). The IP offset avoids the need to update the IP before the
code for the primitive (see IP-update optimization); instead, the
immediate argument is accessed using the offset. E.g, here we see the
payload of the 0-1 variant with offsets of 0 and 3 cells
(indexes 659 and 662):
0x5555555c1a4b 4 + 4 discode $5555555C1A4F: mov -0x8(%rbx),%r13 ok 0x5555555c1a71 4 + 4 discode $5555555C1A75: mov 0x10(%rbx),%r13
For the branch-to-IP variants, let’s look at ?branch:
gforth-fast --print-prims -e bye |& grep '^?branch ' ?branch 1-1 0 21 1505 78 0x5555555bbc70 len= 4+ 24+ 3 send=0 ?branch 2-1 0 757 1506 0 0x5555555c20f4 len= 4+ 17+ 3 send=0 ?branch 2-1 1 758 0 0 0x5555555c210e len= 4+ 16+ 3 send=0 ... ?branch 2-1 23 780 0 2 0x5555555c235b len= 4+ 20+ 3 send=0 ?branch 1-1 0 1505 0 325 0x5555555c50b0 len= 0+ 17+ 3 send=0 ?branch 2-1 0 1506 0 677 0x5555555c50c8 len= 0+ 10+ 3 send=0
Here we see that the 2-1 variant of ?branch (index 757)
has the branch-to-IP variant 1506. The code for the payload of these
two variants is:
0x5555555c20f4 4 + 17 discode $5555555C20F8: mov -0x8(%rbx),%rsi $5555555C20FC: test %r15,%r15 $5555555C20FF: jne 0x5555555c2109 $5555555C2101: mov (%rsi),%rax $5555555C2104: mov %rsi,%rbx $5555555C2107: jmp *%rax ok 0x5555555c50c8 0 + 10 discode $5555555C50C8: mov (%rbx),%rax $5555555C50CB: test %r15,%r15 $5555555C50CE: jne 0x5555555c50d2 $5555555C50D0: jmp *%rax
The second variant is used for optimizing short branches in the IP-update optimization by setting the IP to the branch target before the branch (see IP-update optimization).
The primitive noop has variants for several purposes:
gforth-fast --print-prims -e bye |& grep '^noop ' noop 1-1 0 10 0 3 0x5555555bbb92 len= 4+ 0+ 3 send=0 noop 1-1 -24 1445 0 0 0x5555555c4dee len= 7+ 0+ 3 send=0 noop 1-1 -23 1446 0 0 0x5555555c4dfa len= 7+ 0+ 3 send=0 ... noop 1-1 22 1491 0 0 0x5555555c4fb1 len= 7+ 0+ 3 send=0 noop 1-1 23 1492 0 0 0x5555555c4fbd len= 7+ 0+ 3 send=0 noop 1-0 0 1493 0 2957 0x5555555c4fcb len= 0+ 7+ 3 send=0 noop 2-0 0 1494 0 982 0x5555555c4fd9 len= 0+ 12+ 3 send=0 noop 3-0 0 1495 0 137 0x5555555c4fec len= 0+ 16+ 3 send=0 noop 0-1 0 1496 0 158 0x5555555c5003 len= 0+ 8+ 3 send=0 noop 2-1 0 1497 0 89 0x5555555c5012 len= 0+ 10+ 3 send=0 noop 3-1 0 1498 0 35 0x5555555c5023 len= 0+ 14+ 3 send=0 noop 0-2 0 1499 0 40 0x5555555c5038 len= 0+ 12+ 3 send=0 noop 1-2 0 1500 0 150 0x5555555c504b len= 0+ 10+ 3 send=0 noop 3-2 0 1501 0 0 0x5555555c505c len= 0+ 13+ 3 send=0 noop 0-3 0 1502 0 15 0x5555555c5070 len= 0+ 15+ 3 send=0 noop 1-3 0 1503 0 48 0x5555555c5086 len= 0+ 15+ 3 send=0 noop 2-3 0 1504 0 5 0x5555555c509c len= 0+ 13+ 3 send=0
The variants 1445...1492 contain the IP updates that are inserted in various places when necessary, e.g., for 1445:
0x5555555c4dee 7 discode $5555555C4DEE: sub $0xc0,%rbx
These uses are not counted, so these variants are shown as having count 0.
The variants 1492...1504 contain stack-state transitions from every state to every other state that do nothing else; they are inserted between primitive variants in order to get to a stack state when there is no primitive variant for that (see Stack caching).
The shortest-path algorithm has the option of inserting any
stack-state transition between two primitive variants, and selects the
stack states in a way that results in a shortest code. You can see
this in the counts for lit with IP offset 0: the 1-1
variant (payload length 11) is never selected, because the
shortest-path algorithm apparently prefers to select a 1-0
transition (payload length 7) followed by a lit 0-1 (payload
length 4) with the same total length.
We also see static superinstructions in the output of --print-prims:
$ gforth-fast --print-prims -e bye |& grep "^< [?]branch" < ?branch 1-1 0 611 633 1 0x5555555c142a len= 4+ 28+ 3 send=0 < ?branch 1-1 0 633 0 1 0x5555555c1762 len= 4+ 21+ 3 send=0
The first variant is the regular one that branches to the address given in the immediate argument, the second is the branch-to-IP variant. We can see that both are only selected once for the code in the Gforth image.