flat assembler
Message board for the users of flat assembler.

Index > High Level Languages > FreeForth

Author
Thread Post new topic Reply to topic
Christophe Lavarenne



Joined: 25 Aug 2009
Posts: 5
Location: Burgundy - France
Christophe Lavarenne 25 Aug 2009, 12:16
FreeForth is a public domain, small and fast, extendable interactive incremental compiler generating compact i386 native code, including floating-point instructions, with more than 100K of online help and a Primer documentation.

It has been developped and used for everyday work since 2006, as interactive development basis for real-time industrial applications embedded into microcontrollers, such as the TI-MSP430 and the STM32, and for PC-controlled manufacturing test benches.

Fasm (thanks Grysztar!) is used to assemble the compiler seed (less than 3K of binary code and 1K of symbols table, for Linux or Windows), and embed in the compiler executable the compiler core source (less than 8K of text, documented in the online help) that the compiler seed compiles on startup; then the compiler core compiles an user-editable startup file, then the shell command line, and finally enters its top loop prompting the user to edit/enter lines to compile.

Unlike other Forth dialects (which either compile, or interpret i.e. execute each source word in turn and therefore are unable to manage control structures forward references in interpret mode), FreeForth has no interpreter, it simply always compiles, i.e. looks for each word in its symbol table, and compiles a call assembly instruction to the code pointed to by the symbol; but words defined by a symbol with a final backquote (aka "macros"), when used without their final backquote, are instead executed: macros are the compiler components (they are the equivalent of "immediate" words in other Forth dialects), they are used mainly to inline i386 binary code. You can easily extend the compiler by defining new macros.

The macro ;` (semicolon) is the heart of FreeForth interactivity; it terminates a "definition" (i.e. subroutine) by compiling a subroutine return assembly instruction; then if the definition is anonymous (i.e. its entry point was not explicitely declared with a symbol by the macro :` but instead implicitely declared by the previous ;), as ; can't reference the adef (anonymous definition) later by a symbol, it recovers its memory space by bringing back the compilation pointer to the adef entry point, and issues a call to it; i.e. anonymous definitions are executed by their terminating ;

More sweeties and implementation details are described on the FreeForth home page.

Christophe Lavarenne
http://christophe.lavarenne.free.fr
Post 25 Aug 2009, 12:16
View user's profile Send private message Visit poster's website Reply with quote
Christophe Lavarenne



Joined: 25 Aug 2009
Posts: 5
Location: Burgundy - France
Christophe Lavarenne 25 Aug 2009, 20:44
Here is an example of code generated by FreeForth for an iterative (i.e. non-recursive) implementation of the factorial function (note text between \ and end-of-line is FreeForth comment):
Code:
            \ cSP = call Stack Pointer (normal = esp, alternate = eax)
            \ dSP = data Stack Pointer (normal = eax, alternate = esp)
            \ TOS = Top  Of dataStack (normal = ebx, alternate = edx)
            \ NOS = Next Of dataStack (normal = edx, alternate = ebx)
            \                                     esp eax ebx edx
: factorial \ n -- n! ;                           cSP dSP TOS NOS  initial=normal
  1         \           0: 94       xchg eax,esp; dSP cSP
            \           1: 52       push edx;             NOS TOS  under swap
            \           2: 6A 01    push 1;        (push 1; pop edx; takes 3 bytes)
            \ n 1     ; 4: 5A       pop  edx;      (mov edx,1; would take 5 bytes)
  BEGIN     \ i f     ; 5: 90 90 90 nop; nop; nop; (loop aligned on multiple of 4)
    over*   \ i f*i   ; 8: 0F AF D3 imul edx,ebx;
    swap    \ f*i i   ;                                   TOS NOS  renaming
    1-      \ f*i i-1 ; B: 4B       dec  ebx;      (modifies i386 flags)
    swap    \ i-1 f*i ;                                   NOS TOS  renaming
  0= UNTIL  \ 0   n!  ; C: 75 FA    jnz  8;        (no drop)
  nip       \ n!      ; E: 5B       pop  ebx;
;           \           F: 87 DA    xchg ebx,edx;         TOS NOS  final=normal
            \          11: 94       xchg eax,esp; cSP dSP          final=normal
            \          12: C3       ret
    

To push the literal 1 onto the dataStack, [0: xchg eax,esp] first allows to use short push/pop instructions to access the dataStack within the definition body; then [1: push edx] saves NOS into memory (as does "under"), then we exchange the roles of ebx and edx ("register renaming", as does "swap"), then we can load the constant 1 into TOS=edx.

The "BEGIN" generates 3 "nop" to align the loop start address on a multiple of 4 bytes: this will reduce the number of clock cycles for the loop conditional jump [C: jnz 8].
The "over*" macro generates the push-pop-less single instruction [8: imul edx,ebx].
The two "swap" generate no code, they only exchange the roles of ebx and edx at compile time, then the sequence "swap 1- swap" compiles the single instruction [B: dec ebx].
FreeForth conditional jumps don't modify the dataStack, they only test the i386 flags modified by the last arithmetic or logic instruction; then the "0= UNTIL" generates the single instruction [C: jnz 8].

The following "nip" generates a "pop NOS", i.e. here [E: pop ebx].
The final ";" must restore the normal registers before compiling the final [12:ret]:
the [F: xchg ebx,edx] restores the normal use of ebx=TOS and edx=NOS,
and the [11: xchg eax,esp] restores the normal use of esp=cSP and eax=dSP.

This takes a total of 19 bytes, of which 6 for the 3 instructions in the loop.
For a compiler, as simple as FreeForth is, it isn't so bad.

But I'm sure some of you, assembler artists, think you can do smaller and faster in direct assembler. Ok: use fasm to obtain the binary code, and use FreeForth literal strings to inline it (note ^ prefix subtracts 64 from the next ASCII, and ~ adds 128 to the previous ASCII):
Code:
: factorial  \ n -- n!
  ,"^I~Y~"      \ 0: 89 D9           mov ecx,ebx
  ,";~^A^@^@^@" \ 2: BB 01 00 00 00  mov ebx,1
  ,"^P~"        \ 7: 90              nop ; align loop on multiple of 4
  ,"^O/~Y~"     \ 8: 0F AF D9        imul ebx,ecx
  ,"b~{~"       \ B: E2 FB           loop 8
;               \ D: C3              ret
    

This takes 14 bytes, of which 5 for 2 instructions in the loop: that's indeed better if you need the very best performance from your x86.

Then you can use FreeForth interactivity to test your code snippet: isn't it cool?
Code:
  5 factorial . ;  \ this is your command ("." displays the top of dataStack)
  120  0;          \ this is the result of "." followed by FreeForth prompt
  6 factorial . ;
  720  0;
    
Post 25 Aug 2009, 20:44
View user's profile Send private message Visit poster's website Reply with quote
shoorick



Joined: 25 Feb 2005
Posts: 1614
Location: Ukraine
shoorick 26 Aug 2009, 04:50
amazing listing!
Post 26 Aug 2009, 04:50
View user's profile Send private message Visit poster's website Reply with quote
rugxulo



Joined: 09 Aug 2005
Posts: 2341
Location: Usono (aka, USA)
rugxulo 29 Aug 2009, 04:31
Looks very cool, but it's (still) a bit over my head even though I've briefly looked at Forth before. Wink I personally wonder how much work it would be to port to FreeDOS (probably not much from what I can tell by looking).
Post 29 Aug 2009, 04:31
View user's profile Send private message Visit poster's website Reply with quote
Christophe Lavarenne



Joined: 25 Aug 2009
Posts: 5
Location: Burgundy - France
Christophe Lavarenne 29 Aug 2009, 08:45
Saluton Rugxulo, FreeForth is designed for a 32-bits flat model, whereas DOS is designed for a 16-bits segmented model. Kind of problem: the ff.help file is more than 100K, it couldn't fit into a single segment.
Post 29 Aug 2009, 08:45
View user's profile Send private message Visit poster's website Reply with quote
rugxulo



Joined: 09 Aug 2005
Posts: 2341
Location: Usono (aka, USA)
rugxulo 29 Aug 2009, 22:16
Christophe, ever heard of "flat real" mode? Wink (Hint: FASM uses it or DPMI.) So I'm sure it's possible. Now, I'm probably not skilled enough to do it myself, and honestly I almost consider it wrong to bring it up here. But I also felt you should have some feedback for your excellent work, so I risked it anyways. Smile

P.S. Technically, I have enough (old) DOS Forths lying around to play with. And like I said, I don't know Forth much (that's an understatement!). I just hate the idea that *nix and Win32 are all that's left in the world. Sad
Post 29 Aug 2009, 22:16
View user's profile Send private message Visit poster's website Reply with quote
shoorick



Joined: 25 Feb 2005
Posts: 1614
Location: Ukraine
shoorick 31 Aug 2009, 05:54
agree with rugxulo
in my view, forth application area is mostly autonomic systems, as with forth it is possible to make complex and flexible system in strict conditions for the size of memory etc., diskless systems and so.

usual modern systems give us an "ocean" of ram and disks space, so profit of using forth become unsignificant, while it gives more strict requirements for the programmer.
Post 31 Aug 2009, 05:54
View user's profile Send private message Visit poster's website Reply with quote
rugxulo



Joined: 09 Aug 2005
Posts: 2341
Location: Usono (aka, USA)
rugxulo 31 Aug 2009, 07:41
I'll be honest, though, messing with the recent Befunge interpreter (written in FASM) makes me more curious about Forth again. Wink
Post 31 Aug 2009, 07:41
View user's profile Send private message Visit poster's website Reply with quote
Christophe Lavarenne



Joined: 25 Aug 2009
Posts: 5
Location: Burgundy - France
Christophe Lavarenne 02 Sep 2009, 19:30
FreeForth has been designed with multi-OS support in mind.
As described on FF home page, most source files are OS-independent, and 3 are OS-dependent (in the following filenames, substitue "lin" for Linux by "win" for Windows, say "dos" for DOS, and say "osx" when fasm supports OS-X):
+ fflinio.asm for file-io (and DLL, although maybe not for DOS) assembly interface (around 9K bytes)
+ fflin.boot for environment-variable dependent location of FF home directory (around 300 bytes)
+ fflin.asm defining 3 small OS-dependent macros before including the OS-independent source ff.asm

If any fasm artist wants to port FF under DOS in "flat real" mode, his questions for help on FF will be welcome.
Post 02 Sep 2009, 19:30
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.