Collapse OS Documentation Browser


asm/ code/ hw/ algo.txt arch.txt avr.txt blk.txt blksrv.txt bootstrap.txt cross.txt design.txt dict.txt dis.txt drivers.txt ed.txt emul.txt faq.txt grid.txt grok.txt impl.txt intro.txt me.txt mspan.txt ps2.txt rxtx.txt sdcard.txt sega.txt selfhost.txt spi.txt usage.txt wordtbl.txt


When you naively compile binary (see doc/asm) or forth code, the
resulting binary will only run on the machine it was compiled

If you want to compile for another machine, you need to cross-
compile. Collapse OS includes tools to do so.

There are two distinct tasks that require distinct tools: binary
xcomp and forth xcomp.

1. Binary xcomp

Assemblers, when encoding absolute addresses, do so naively. If
you write "$1234 JMPi,", $1234 will always be the encoded
address. So far, so good.

Where there's a problem is when labels are involved. For exam-
ple, the result of "LSET L1 L1 JMPi," depends on where in memory
this opcode was created. As long as the code runs on the machine
that compiled it at the address it was compiled, this will
always do the right thing. Otherwise, we need to stop being
naive. We do so with the XCOMP loader word (it loads B200).

This loader introduces 2 new VALUEs that determine how labels
work: XORG and BIN(.

XORG is the address at which our binary starts on the host
machine. When you're ready to spit your cross-compiled binary,
you'll want to do "HERE TO XORG".

BIN( is the address at which our binary is expected to live in
the target machine. By default, it's 0.

There's a convenience "XSTART ( bin( -- )" word which sets BIN(
and XORG in one fell swoop.

Together, these words give you control over what the assembler
considers it's current "PC" (program counter) at any given
moment. For example, right after a "XSTART", "LSET L1" will give
L1 the value of BIN(. $100 bytes later, PC will be BIN(+$100.

To do binary xcomp, you will want to load XCOMP before you load
your assembler and only call XSTART when you're ready to spit

Example of xcomp from fresh Z80 Collapse OS boot:


This produces a binary that is designed to run an infinite loop
from address 0. Without XCOMP, the jump would be incorrect and
jump somewhere in the middle of the memory, where HERE was
during compilation.

Cross-compiling for another CPU architecture is the same thing,
all you need to do is to load the proper assembler. You just
have to be extra careful if compiling for a different endian-
ness. See below.

2. Forth xcomp

Binary xcomp is relatively straightforward. Forth xcomp is a bit
hairy. Because forth words are nothing but references to other
words all the way until we "hit rock" (hit native code), that
code is tricky to relocate.

Collapse OS has tools (which builds upon the tools explained in
section 1) to produce a Collapse OS forth dictionary designed to
run on another machine at another address.

These tools are loaded with the "XCOMPC" (XCOMP for Collapse OS)
which requires "XCOMP" to be loaded first.

As with binary xcomp, XCOMPC requires XORG and BIN( to be pro-
perly set before you begin spitting cross forth words.

XCOMPC overrides defining words (:, CREATE, CONSTANT, etc.) so
that it adds an offset to every wordref it compiles. With this
override, you end up with a dictionary that is separate from the
host dictionary and is internally consistent.

What should that offset be? XORG, of course! As with binary
xcomp, XORG corresponds to the beginning of the binary being
built. In fact, every Collapse OS binary begins with purely
binary code, and thus, "regular binary xcomp" code.

It's only when the first word is created (the first CODE word
of the arch's port) that XCOMPC mechanism kicks in.


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 XORG is $1000 and the last word added to it was at
offset $234, then XCURRENT is $1234.

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.

xcomp unit

Cross-compilation of a Collapse OS binary is achieved through
the writing of a cross-compilation unit of code, xcomp unit for

The xcomp toolset at XCOMPC alters core words in a deep way, so
ordering is important. First, we load our tools. XCOMP,

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 XCOMPC overrides. From this point on.
every defining word is messed up and will produce offsetted

The XCOMPC loader implicitly calls "0 XSTART", so if your BIN(
is 0, you can start spitting right away. Otherwise, call XSTART
with a proper BIN( value before you spit.

What to spit? See doc/bootstrap for details, but in short it's:

1. Arch-specific port
3. Drivers
4. XWRAP (which loads COREH and wraps it up)

Once XWRAP is called, and if you did things the right way, what
is between XORG and HERE is your fancy new Collapse OS binary!

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
usable system.

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 target,
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 target 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 .".

DOES words

DOES> can't work in an xcomp environment. It's too tricky be-
cause the word that DOES> compiles has to work in both the host
and the target system at the same time. The hoops to jump
through to make this kind of word work are horrific.

However, DOES words do allow for some juicy space saving, so the
xcomp program has an alternate way to compile does words: ~DOER.

Instead of creating a "generator" word as you do with DOER, you
first create an anonymous word with ":~", which is the equi-
valent of the code following the "DOES>" word. And then, you
create your DOES words with ~DOER at runtime. Example:

:~ ( n 'n ) @ + . ;
~DOER foo 42 T,
~DOER bar 54 T,

In the compiled system, "1 foo" will print 43 and "1 bar" will
print 55. "T," is for endian-ness. See below.


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, XCOMP defines these words:

|T: Split word into 2 bytes, using Target's endian-ness.
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 XCOMPC 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!).

Extra words

xcomp tools define a couple of extra words that are specific to

OALLOT   oa --   ALLOT0 n bytes where n = oa-PC. In other words,
                 make current binary oa bytes, filling with 0.
*VALUE   --      A read-only, indirect VALUE
*ALIAS   --      An indirect ALIAS
~DOER x  --      Create a xcomp-compatible DOES word. See above.
PC2A     pc -- a Transforms a target's PC into a host's addr.

Collapse OS and its documentation are created by Virgil Dupras and licensed under the GNU GPL v3.

This documentation browser by James Stanley. Please report bugs on github or to

This page generated at 2023-12-05 21:05:04 from documentation in CollapseOS snapshot 20230427.