JKForth: a Forth system for 16-bit Intel machines

In the summer of Y2K, I picked up my copy of Leo Brodie's charming book, Starting Forth, which is a gentle introduction to the Forth language. In fact it's more than an introduction - later chapters provide a good bit of detail on how a real Forth system is implemented. You can get a copy of the second edition of Starting Forth from Forth, Inc.

Forth is the grandparent of "threaded" languages. In this age of Java and multi-threaded applications, that statement can easily be misunderstood: Forth is not (or not primarily) a language designed for multiprocessing. The "threading" refers to the manner in which Forth routines, or "words", invoke one another: they do not do so via subroutine calls with argument passing, as in traditional procedural languages like C and Pascal. Instead, an extremely simple calling convention is used: the only piece of context saved during invocation of a word is the return address. However, Forth code has explicit access to a data stack (which is not the same stack that saves return addresses). One consequence of this simple calling convention is that refactoring is extremely simple in Forth: a given sequence of words usually has the same meaning whether it appears inline or within a subroutine call.

One of the points that Mr. Brodie makes is that Forth is more than just a language: it is often also the operating system of the machine on which it runs. Because of its very simple structure, an individual can hope to fully understand the interpreter, compiler, and runtime environment from the top (running Forth application code) to the bottom (the hardware/firmware interface). I found that prospect quite enticing.

Another feature of Forth is its extreme compactness and speed. Mr. Brodie states that Forth code is usually within a close order of being as fast as assembly code, and that Forth programs can sometimes actually be more compact than equivalent assembly programs, due to the language's aggressive re-use of code.

My experience bears this out: the JKForth compiler, interpreter, and runtime system (which is essentially an extremely primitive disk operating system) occupies well under 10K of RAM. And this is my first significant Intel assembly-language project: I'm sure an experienced x86 coder could do a lot better. And another thing: if you compile a 61-byte "Hello, world" program in C using GCC, the resulting executable is almost 11K (dynamically linked to libc.so). By contrast, the equivalent JKForth program is 27 bytes long and compiles to 39 bytes of object code. Even more astonishing, the 2K or so Forth source that implements the JKForth utility and editor package compiles into less than 1K of Forth object code. I find that rather funny. If all the PCs in the world ran a simple Forth-based OS, we'd have way more computational power than we knew what to do with! (OK, so we do anyway - that's not the point.)

I have been fiddling with robotics for a couple of years now, and until recently I've done all my robot programming on an extremely limited Motorola 6811-based microcontroller board, in 6811 assembly. Reading "Starting Forth" got me thinking that it would be nice to build robot firmware in Forth on one of the many Intel-based single-board machines that are available. It seemed like a fun way to build a system that I could understand intimately, and make of it exactly what I wanted. I did not have the monetary resources to purchase a computer for this project, but I did have, mouldering away in a closet, an ancient 8088-based NEC Multispeed laptop. And thus, JKForth was born.

Obtaining JKForth

You can get the source code, which must be compiled with NASM; or you can get a disk image that you can "dd" or "RAWRITE" to a floppy disk and boot on a PC.

Building JKForth

On a Linux machine you can just "make jkf"; on a Windows machine you will need to assemble flod.s and tfdict.s, and then concatenate flod.s, tfdict.s, and the various *.fth files in the order they appear in the "cat" command in the Makefile. It is important that each of the *.fth files be exactly 512 bytes long (which they should be already); if you change them, make sure you don't change their sizes. If you're building on a Linux system, the make procedure will pad the *.fth files to 512 bytes if necessary, but on Windows you will need to do it manually.

Installing JKForth

Once you've got the JKForth binary built, installation is simply a matter of writing it to a (720K) diskette in raw format. On a Linux system, this can be accomplished by the command

dd if=jkf of=/dev/fd0
(which you probably need to be root to execute). On Windows, you can use the RAWRITE.EXE program to do this.

Then, put the floppy in your A: drive, make sure your CMOS settings allow your machine to boot from the floppy, and reboot. In short order you will be staring at the JKForth interpreter prompt.

JKForth Internals

There are basically four pieces to the JKForth puzzle:

  1. The bootloader;
  2. Some native x86 assembly code to implement some very basic machinery necessary to make the Forth address interpreter work;
  3. A bunch of hand-compiled Forth code to implement all the important stuff (text interpreter, compiler, disk manager) (this is a lot easier than it sounds);
  4. Some genuine Forth source code that implements an extremely primitive text editor similar to "ed". (Actually this part is not quite finished yet.) Also, some utility words are defined that make debugging and development a bit easier.

To-Do List

  • Handle all interrupts at the Forth level.
  • Add multi-tasking.
  • Make a 32-bit version with virtual memory.

Last changed:
06-07-06 15:29:37

Questions and comments to Joseph Knapka.