Collapse OS features many assemblers. Each of them have their
specificities, but they are very similar in the way they work.
This page describes common behavior. Some assemblers stray from
it. Refer to arch-specific documentation for details.
After you've loaded the assembler (for example, with "5 LOAD"
in the case of the Z80 assembler), you have to set it up before
spitting opcodes. More specifically, you might have to set
ORG and BIN( variables.
ORG, defaulting to 0, specifies where the binary begins in
memory. It allows the PC word to return the proper value.
Generally, when you're ready to spit upcodes, you run
"H@ ORG !" so that PC is set to 0.
BIN(, defaulting to 0, specifies where the resulting binary
lives in memory. If all you spit are relative jumps, it doesn't
matter, but if you need to jump to an absolute address, BIN(
needs to be correct. Note that ;CODE spits an absolute jump in
many arches, so BIN( often needs to be correct.
Wrapping native code
You will often want to wrap your native code in such a way that
it can be used from within forth. You have to main options.
CODE allows you to create a new word, but instead of compiling
references to other words, you write native code directly.
CODE 1+ BC INCd, ;CODE
This word can then be used like any other (and is of course
Unlike the regular compiling process, you don't go in "compile
mode" when you use CODE. You stay in regular INTERPRET mode.
All CODE does is spit the proper ENTRY head.
Be sure to read about your target platform in doc/code. These
documents specify which registers are assigned to what role.
Another option is "inline assembler". When you're in a tight
spot inside a word that you'd like to be faster, but that
creating a whole word is too much, you can use CODE[. Example:
; foo 42 CODE[ BC INCd, BC INCd, ]CODE . ; \ prints 44
This example above would be significantly faster than "2 +".
At runtime, the overhead, in terms of speed, is the same as a
regular CODE word, but in terms of binary size, it's better.
To spit binary code, use opcode words such as "LDrr," in the
Z80 assembler which spits LD in its "r1, r2" form. Unlike
typical assemblers, operation arguments go before the opcode
word, not after it. Therefore, the "LD A, B" you would write in
a regular assembler becomes "A B LDrr,"
Those opcode words, of which there is a complete list in each
arch-specific documentation, end with "," to indicate that their
effect is to write (,) the corresponding opcode.
The "argtype" suffix after each mnemonic is needed because the
assembler doesn't auto-detect the op's form based on arguments.
It has to be explicitly specified.
Although efforts are made to keep those argtypes consistent
across arches, there are differences. Arch-specific doc has
precise definitions for those argtypes.
For example, in Z80 asm, "r" is for 8-bit registers, "d" for
16-bit ones, "i" for immediate, "c" is for conditions.
Labels and flow
Labels are a convenient way of managing relative jump
calculations. Backward labels are easy. It is only a matter or
recording "HERE" and do subtractions. Forward labels record the
place where we should write the offset, and then when we get to
that point later on, the label records the offset there.
To avoid using dict memory in compilation targets, we pre-
declare label variables here, which means we have a limited
number of it. We have 3: L1, L2, L3.
You can define your own labels with a simple "0 VALUE lblname",
but you have to do so before you begin spitting opcodes.
You manipulate those labels using what we call flow words. These
words are the same across assemblers and are located in the HAL
convenience layer and therefor documented in doc/hal.txt.
As explained in cross.txt, all assembler supply words allowing
to write 16bit numbers in a target's endian-ness. Common words
at B2 already supply these words and they're all dependent on
the BIGEND? variable which defaults to 0. Assemblers for big-
endian architectures have to set this to 1.
This page generated at 2021-09-19 21:05:03 from documentation in CollapseOS snapshot 20210917.