Creation of CODE in metacompilation
published: 3 May 2021 / updated 11 May 2021
Compiling a header into the target
Here is the first word that interests us, header-t
. It saves a header
word FORTH in the target. Header creation is only possible if the content of the header is
variable WIDTH
is non-zero.
Here, we execute this header creation via the word compile-header
which is a word
deferred execution.
\ *** compilation of complete header word *************************** defer compile-header \ compile header into target --- @TODO : à adapter aux en-têtes FlashForth : header-t ( -- ) bl word dup c@ WIDTH @ MIN VALID @ AND if align compile-header \ compilation of complete word header else drop then ;
But creating a header in the target is insufficient. Remember that the target is a
inert memory space. To find a reference saved in the target, you must
keep this reference elsewhere. This is the role of the TARGET
vocabulary.
The word target-create
will create a definition in the TARGET
vocabulary
and in the target:
\ Creation in target and target reference with FORWARD auto-resolution : target-create ( -- ) ?backstep ?valid >IN @ dup header-t >IN ! in-target \ create word in target vocabulary create in-meta here-t , VALID @ , >IN ! ?resolves does> make-code ;
Creation of a CODE..END-CODE definition
Since the first versions of the FORTH language, a definition that uses the
assembly or binary code is defined by code
and end-code
.
We will keep this proven mechanism.
But gForth has its own versions of the code
words
and end-code
. To avoid redefining them, we create our own versions of these
words in the META
vocabulary:
\ definition of word in binary in target : code ( comp: --| exec: --) target-create Xassembler also ; \ end of binary in target : end-code in-meta ;
Meta-compilation of LFA and NFA fields in the target
The creation of the LFA and NFA fields in the target is deported to deferred execution words. This mechanism allows to adapt the meta-compiler to any type of target. Here, o, will generate LFA and NFA fields for a FORTH version inspired by FlashForth:
\ compilation of FlashForth lfa field \ lfa = Link Field Address : compileLFA ( --) LAST-T \ Latest nfa compiled @ ,-t here-t LAST-T ! ; \ compilation of FlashForth nfa field -- tested OK: 27apr2021 \ nfa = Name Field Address : compileNFA ( toIn --) dup c@ $80 or c,-t \ compile first byte of NFA count string,-t \ compile name of word ; \ compile header for FlashForth words : (compile-header) ( --) compileLFA \ first compile the LFA field compileNFA \ then compile the NFA field ; \ resolves header and end words in meta voc. ' (compile-header) is compile-header
Our first word compileLFA
will create the LFA field in the target:
- we get the last NFA address
LAST-T @
- we store this address in the target
,-t
which increases the content ofdp-t
(Dictionnary Pointer in Target) - we put the new value of
DP-T
inLAST-T
Our second word compiles compileNFA
saves the NFA field in
the target:
- we store the byte $ 8x where x is the length of the header
dup c@ $80 or c,-t
- we then store the name of the word
count string,-t
Let's see in a practical way how to go from a definition to a pure assembler to a code..endcode definition.
Definition in FORTH assembler
Here is how the word execute
is defined in the source code
FlashForth assembler:
fdw KEYQ_L EXECUTE_L: .db NFA|7,"execute" EXECUTE: movw zl, tosl sub_pflash_z poptos rampv_to_c ror zh ror zl mijmp fdw EXECUTE_L
Labels suffixed by "_L" are used to manage LFA fields, for example EXECUTE_L
With code..endcode it is no longer necessary to manage these labels, this is done automatically by the meta-compiler.
Similarly, it is no longer necessary to indicate the length of the word defined by code
.
Here is how execute
is rewritten with our FORTH assembler:
code execute ( cfa --) label EXECUTE zl tosl movw, sub_pflash_z poptos rampv_to_c zh ror, zl ror, mijmp end-code
In this definition, we kept the EXECUTE
label which is used to mark the
start of the CFA / PFA field of our word execute
. This definition is not necessary,
but the creation of this label does not take any place in the target.
With meta-compilation, it becomes much easier to insert, delete, move anywhere what definition code..endcode in the source code of our FORTH target.
Of course, it is highly recommended to replace a code-endcode definition by its equivalent ":" (colon) whenever possible.
Here, the word negate
written in FORTH assembler:
\ invert sign of n :: tested OK 05may2021 code negate ( n -- -n ) tosl com, tosh com, tosl 1 adiw, ret, end-code
and its rewriting in FORTH:
: negate invert 1+ ;
Here, our word negate
directly exploits the word invert
previously defined in FORTH assembler.
In conclusion
You are starting to see the benefits of meta-compilation:
- define as few definitions as possible in FORTH assembler
- reuse the same ":" (colon) definitions for any target...
The meta-compiler can also generate code that can be directly used in the target processor, limited to essential words only, without FORTH header. Meta-compilation can generate a very compact application. This is what we will have the opportunity to see in a future article.