An active-object file is used to contain
a relocatable Forth program. The file is created from a set of
compiling rules which have been defined for the Translator (as
described in the paper “Translator Frameworks” from the
Rochester Conference ‘91). These rules process the source
code, strip out useless information (comments), transform the text into
a form usable by the host computer, collect relocation information,
keep track of compiler decisions and store all this information as an
active-object file. Most of the time, the file will consist of a
user dictionary image with collections of relocation data. There
are six types of relocation collections:
- inlinks are pointers from existing code into the loaded code
- outlinks are pointers from loaded code to existing code
- internal references are pointers from loaded code to loaded code
- external references are pointers from existing code to existing code which are required after loading some new code
- internal values are values which need to be set in the loaded code
- external values are values which need to be set in the existing code
Additionally, an active-object file may contain a collection of
commands which may be executed to assist in relocating the program.
When reconstructing data spaces which contain a mix of data unit sizes,
such as cells and strings, we must be careful to make sure that the
memory alignment rules are followed. For instance, a header for a
word contains a cell for link-listing the headers, a string of bytes
for the name and another cell pointing to the word’s inner
The second cell must start at a cell boundary in memory. This
means padding the string with some number of bytes. For memory
which is 16 bits wide, the string padding might be 0 or 1 byte. For 32
bit memory, the padding is 0-3 bytes. In an active-object file,
access is byte wide so there are no padding bytes. When
transferring the header from an active-object file to a user dictionary
in memory, one must allow for alignment in one of the following ways:
- Align when loading. This requires knowing when a cell is
being transferred from the file to memory so that alignment may be
- Use worst case alignment always. Align the cells in the active-object file to 32 bit boundaries.
- Stick to one processor. Alignment of cells in the active-object file becomes the same as the processor.
- Separate data space for strings and cells. Modify the
header to be a pointer, which points to a string in the string
space. This accumulates a cell of overhead for each string but it
is offset by the uniqueness of strings longer than a cell in bytes.
Byte storage in a cell is different for different platforms. For
instance, on an Intel ?P, a byte-fetch from a stored cell, will return
the least significant byte, while on a Motorola ?P, it would return the
most significant byte. Since data is read from the active-object
file serially, the most significant bytes of values are read
first. To read a cell from the file, one cannot simply do:
HERE DUP CELL READ-BYTES @ ( THIS IS NOT PORTABLE! )
We can, however, use the data stack to reconstruct a cell value from
its bytes. The no-brainer, simple-straight-forward,
let-the-compiler-speed-it-up approach is:
0 CELL FOR 256 * READ OR NEXT ( read a cell from a file )
Now we can store the cell in memory and not care about its orientation.
Numbers or Words
Without a dictionary to look up words, we must somehow make a decision
whether a word is a word or a number. In a regular Forth
compiler, this is done by assuming that it is a word and looking it up
in a dictionary. If it is not found then it is assumed to be a
number. If it fails to be a number then an error occurs.
When compiling relocatable Forth, we assume that it is a number first,
since we have no dictionary to check to see if it is a word. If
it is not a number, then we assume it is a word and compile relocation
information for it. This precludes defining numbers as words
which usually isn’t a problem except for maybe hex numbers.
I also found out that my number checking algorithm had a bug in
it. A minus sign was accepted as a 0 (try “ -” NUMBER
on your system and see if it interprets it as a zero!).
Words which add new words to the dictionary must have translation rules
defined for them. This includes CONSTANT, VARIABLE, : and
CREATE. Any word which uses CREATE must have a translator rule
defined for it. These rules produce relocation information for
the link and inner interpreter.
Immediate words are a mechanism used to extend the compiler.
Since the compiler is now a set of rules defined for the Translator,
immediate words must be defined as rules. If there is no rule for
the immediate word, it is assumed to be a non-immediate word and
compiled as such. When it is loaded, if it is an immediate word,
then it is displayed as an error.
Probably the trickiest part about creating active objects is when there
are decisions about how to compile a program which must be made on the
machine which will run the program. What this usually means, is
the source code has parts which will only be included on certain
Some associated syntax guides the compiler through the proper path in the source code when compiled on the target machine.
An active object file contains all the conditional parts. The
decisions about which parts to use on the host computer are made by
active objects in the file at load time.