# Cross-compilation When you naively compile binary (see doc/asm) or forth code, the resulting binary will only run on the machine it was compiled on. 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 binary. Example of xcomp from fresh Z80 Collapse OS boot: ARCHM XCOMP Z80A 0 XSTART LSET L1 NOP, L1 JP, 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. # Dual-CURRENT 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 short. The xcomp toolset at XCOMPC alters core words in a deep way, so ordering is important. First, we load our tools. XCOMP, assembler. 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 binaries. 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 deails, but in short it's: 1. Arch-specific port 2. COREL 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 system. 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 .". 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 XCOMPC, 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 cross-compiled: * DOES>: words with DOES> in it can be cross-compiled, but not used. ": FOO CREATE DOES> ;" is fine, but *not* "FOO BAR" afterwards! * ABORT" * ." # Endian-ness 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 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 it: 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