There is two general types of Forth code in Collapse OS: regular
code and code designed for cross-compilation. Both types are
very similar in form, but they're not always interchangeable.
All blocks before B200 contain regular code. It's regular stuff,
nothing special about it. The rest, however, is "xcomp" code and
things can get tricky in there. Let's dive in...
What is xcomp for?
When Forth words are compiled, they are compiled for the system
currently running. Those compiled words are tricky to relocate
because their wordrefs reference addresses within the running
If you want to deploy to a new system, you need tricks, and
those tricks are located at B200, the cross-compilation toolset.
The mechanism is simple: override defining words (:, CREATE,
VALUE, etc.) so that it adds an offset to every wordrefs it
What should that offset be? the beginning of the binary being
built. That offset is the value in the ORG variable, supplied
by the assembler. It's logical: every binary begins with a bit
of assembler, which makes every following Forth word aligned
with this value.
Although the principle behind cross-compilation is simple, the
devil's in the details. While building our new binary, we still
need access to a full-fledged Forth interpreter. To allow this,
we'll maintain two CURRENT: the regular one and XCURRENT, the
CURRENT value of the cross-compiled binary.
XCURRENT's value is a *host* address, not a cross one. For
example, if our cross binary begins at offset $1000 and the
last word added to it was at offset $234, then XCURRENT is
During cross compilation, we *define* in XCURRENT and we
*execute* in CURRENT.
When we encounter an IMMEDIATE during compilation, we execute
the *host* version of that word. The reason for this is simple:
any word freshly cross-compiled is utterly un-runable because
its wordrefs are misaligned under the current host.
Cross-compilation is achieved through the writing of a cross-
compilation unit of code, xcomp unit for short.
The xcomp toolset at B200 alters core words in a deep way, so
ordering is important. First, we load our tools. Assembler,
xcomp toolset, etc.
We also define some support words that will not be part of our
resulting binary, but will be used during xcomp, for example,
declarations units and macros.
Then, it's time to apply xcomp overrides. From this point on.
every defining word is messed up and will produce offsetted
At this point, it's critical to set ORG before spitting any-
thing. Boot binaries will usually take care of this, so you
don't have to do it yourself. You just have to make sure that
you load the boot binary right after loading B200.
If your binary is not located at 0 on the target machine, you
have to set BIN(. For example, if your target OS is designed to
run from offset $3000, you'll add "$3000 TO BIN(" to your
Then, you spit your content following instructions in
After you're done, you can run "FORGET PS_ADDR" (or whatever
is the first word declared by your xcomp unit) to go back to a
Immediate compiling words trickyness
When using an immediate compiling word such as "IF" during
xcomp, things are a bit tricky for two reasons:
1. Immediates used during xcomp are from the host system.
2. The reference of the word(s) they compile is for the host
Therefore, unless the compiled word (for example (?br) compiled
by IF) has exactly the same address in both the host and guest,
the resulting binary will be broken.
For this reason, we re-implement many of those compiling words
in xcomp overrides, hacking our way through, so that those
compiling words compile proper guest references. We don't do
this for all compiling words though. This means that some words
can't be used in core and drivers, for example, ABORT" and .".
How to know whether a word can be used?
1. If it's not an immediate compiling word, it's fine.
2. If its overriden in B200-B205, it's fine.
3. Otherwise, you can't cross-compile it.
List of words that are known to *not* work in code having to be
* DOES>: words with DOES> in it can be cross-compiled, but not
used. ": FOO CREATE DOES> ;" is fine, but *not* "FOO BAR"
16 bit numbers you write when cross-compiling will often need to
follow your target's endian-ness, which might not be the same as
your host's. To this end, all assemblers define these words:
|T: Split word into 2 bytes, using Target's endian-ness. Calls
|L or |M
T!: Like "!", but uses Target's endian-ness.
T,: Like ",", but uses Target's endian-ness.
T@: Read a word using Target's endian-ness. Used, for example,
in XFIND to read prev to traverse a cross-compiled dict.
Constants and IMMEDIATE-ness, oh my!
One thing that is particularly tricky with xcomp code is the
management of constants. VALUEs declared before B200 is loaded
are *only* accessible outside of compilation mode. For example,
PS_ADDR will not be a word in the target system. When writing
assembly, you can reference it just fine because you're in
runtime mode. However, if you're inside a ":", you can't
reference PS_ADDR. You have to add a literal of its value with
"[ PS_ADDR LITN ]" (or by creating a VALUE inside the target,
but this will take precious binary space!).
This page generated at 2021-09-19 21:05:03 from documentation in CollapseOS snapshot 20210917.