Creation of CODE in metacompilation

published: 3 May 2021 / updated 11 May 2021

Lire cette page en français

 

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:

Our second word compiles compileNFA saves the NFA field in the target:

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:

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.