Collapse OS Documentation Browser


asm/ code/ hw/ avr.txt blk.txt bootstrap.txt cross.txt dict.txt ed.txt faq.txt grid.txt hal.txt impl.txt intro.txt me.txt primer.txt protocol.txt rsh.txt sega.txt selfhost.txt usage.txt

Harmonized Assembler Layer

The HAL is a set of macros whose goal is to make it possible to
make some parts of high-level code faster without having to
write code for each platform. The secondary goal of it is to
reduce the size of the native part of each port by moving words
that aren't critical into HAL code.

The idea is that each supported CPU has a set of code-emitting
macros that all follow the same API.

The HAL is *not* for general purposes. It is specially designed
for Collapse OS itself. Trying to generate another kind of
binary with it is likely a bad idea.

Ops targets

This API revolves around PS, RS, IP, as with the rest of Forth,
but also give more control over what goes to W, the Working
register and how the stacks are managed.

Operation names always specify the elements they affect with a
list of suffixes, which are:

w: Working Register. The most important element. Most
   computation happens there.
p: Top Of PS. It is used, of course, for push/pop/transfer
   operations, but also for arithmetics. It is always the
   "secondary" element of the op. The result goes in w.
r: Top of RS. Rarely used for things other than push/pop/
   transfer. The RS is often assigned to a more "awkward"
   register in the CPU, making operations with it more
f: (for "far") Second element of PS. Often used for complex
   stack juggling.
i: immediate number which will be written next to the op. When
   an op has this signature, it means you have to supply it
   with a number at compile time.

Operations that work on a single element will work on W, not PS
or RS. Therefore, you have to POP or copy from PS or RS, do your
thing, push/copy back.

Some operations work on more than one elements at once. They
will then work on W and PS/RS/IP explicitly. Their name and
description will tell it.

There are also some exceptions, such as INCp, which works
directly on PS, without W. The idea is to take advantage of the
TOS register on CPUs that have it, or CPUs that have more
flexible access to their stacks in memory.

Ops costs

The HAL hides the cost of underlying operations, which is
different from CPU to CPU. The HAL has been designed to work
well with Collapse OS' darling CPU, the z80, but is also
designed to avoid ridiculously expensive operations.

It is useful to be aware of costs saving operations however.
For example, POPp, ( do stuff ) PUSHp, can work very well and
might be fast on some CPUs, but on the z80 which uses BC as its
TOS register, it's faster to copy BC to HL and back again. For
this reason, there are the p>w, and w>p, ops (which mean "copy
from p to w" and "copy from p to w") and if you use them instead
of POPp,/PUSHp, when you don't need them, your code can be
significantly faster.

Macro arguments

HAL macros generally require no argument. It's only when they
have a "i" signature (immediate) that they need to be supplied
with a number to hardcode in their instructions.


Jump words all require a numerical argument which will be
written next to them. For absolute jumps, it's easy: You use
labels. For example, you use "LSET mylabel" to mark, then
"mylabel JMPi," to jump.

Relative jump offsets are computed with "BR" and "FMARK". You
can use either labels or BEGIN, markers. Examples:

LSET L1 INCw, L1 BR JRi, \ backward jump with label
BEGIN, INCw, BR JRi, \ backward jump with BEGIN,
FJR JRi, INCw, FMARK \ forward jump with FJR
FJR JRi, TO L1 INCw, L1 FMARK \ forward jump with labels

The HAL convenience layer also has structures jumps helpers:

42 i>w, BEGIN, DECw, Z?w, BR JRNZi, \ loop 42 times

Jump arguments

Wrapping one's head around jumping can be a challenge,
especially with a cross-CPU API. For JMPw and JMPi, it's rather
easy: the immediate or w are expected to contain an absolute
address to jump to.

For relative jump words (JRi, JRNZi, etc.), things must be
clarified: The expected argument is a CPU-specific offset. You
will seldom determine that offset yourself, but will instead use
BR and FMARK, which will use the CPU-specific JROFF and JROPLEN
constants to compute a proper offset.

Conditional jumps

The HAL only allows relative conditional jumps through the ?JRi,
word. The jump can be conditional to the value of two flags:
Zero and Carry. A condition must be selected before ?JRi, is
used. The words to select the condition are Z? (jump if Z is
set) C? (jump if C is set) and ^? (invert jump condition).

On "good old" register based CPUs, these words don't emit
anything, they simply select the opcode that ?JRi, will emit.
On stack-based CPUs, things are different because the
conditional jump is generally based on a flag placed on the
stack, so these words will emit the code that will place the
proper value on the stack.

The flag selection word has to be called before BR/FJR. The
proper form looks like this:

Z? L1 BR ?JRi,
C^? FJR ?JRi,

Convenience words like IFZ, IFNC, etc. take care of selecting
the proper flag.

Important assumptions

Some things are considered the same across all supported CPUs:

* JMPi always yield 3 bytes.
* i>w yields a single opcode, followed by a 2-byte number.
* (i)>w yields a single opcode, followed by a 2-byte address.


The "," suffix is omitted below, but it's always there to
indicate that the effect of these word is to write (,) stuff to

Affected elements: as a general rule, w's value is always
modified after an op. It's generally affected to the accumulator
register of the CPU which is pretty much always needed to do
meaningful stuff.

Except of course simple copy ops such as "w>p" and friends. They
never change w.

Some ops explicitely save w though. Those are mentioned in the
description with "Saves w".

In the same way, p and r are generally preserved by ops. Some
ops, however, can't really achieve this feat without making the
op expensive, so it destroys p and/or r. When that happens, the
description mentions it with "Destroys p/r".

On many CPUs, Z and C flags are set after a large part of the
arithmetic ops, but that can't be assumed in the HAL. C and Z
flags are set only after an op that specifies it, and it can't
be assumed anymore after running another op.

POPp      Pop PS TOS to w
POPr      Pop RS TOS to w
POPf      Pop PS 2nd element to w
PUSHp     Push w to PS TOS 
PUSHr     Push w to RS TOS 
PUSHf     PUSH w to PS 2nd element
DUPp      Push PS TOS to PS. Saves w
DROPp     Pop PS TOS. Saves w
SWAPwp    Swap w and PS TOS
SWAPwf    Swap w and PS 2nd element

p>w       Copy PS TOS to w
w>p       Copy w to PS TOS
i>w       Load immediate value to w
C@w       w being an address, load its byte in w
@w        w being an address, load its word in w, native endian
C!wp      w being an address, write p's LSB to memory. Saves w
!wp       w being an address, write p to memory, native endian
w>IP      Copy w to IP (Interpreter Pointer)
IP>w      Copy IP to w
IP+       Increase IP by 1. Saves w
IP+off    IP pointing to a signed byte, increase IP by this byte
          Saves w.

CPU flags
w>Z       Set CPU's Z flag according to w's value. Saves w
p>Z       Set CPU's Z flag according to p's value. Saves w
Z>w       Set w to 1 if Z is set, 0 otherwise
C>w       Set w to 1 if C is set, 0 otherwise


Jumps never affect w.

JMPw      Inconditional jump to PC w
JMPi      Inconditional jump to PC i
CALLi     Push PC+X to PS, then JMPi. X=length of CALL op
JRi       Inconditional jump with offset i
JRZi      Jump with offset i if Z is set
JRNZi     Jump with offset i if Z is unset
JRCi      Jump with offset i if C is set
JRNCi     Jump with offset i if C is unset


INCw      Increase w by 1
INCp      Increase p by 1. Saves w
DECw      Decrease w by 1
DECp      Decrease p by 1. Saves w
CMPwp     Set Z if p==w. Set C if w<p. Saves w
ANDwp     Make w the bitwise AND of w and p
ORwp      Make w the bitwise OR of w and p
XORwp     Make w the bitwise XOR of w and p
XORwi     Make w the bitwise XOR of w and i
+wp       Add p to w. Sets Z/C flags.
-wp       Subtract p from w. Sets Z/C flags.
>>w       Shift w right by 1 bit. Sets C flag.
<<w       Shift w left by 1 bit. Sets C flag.
>>8w      Shift w right by 1 bit.
<<8w      Shift w left by 1 bit.

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 2021-09-19 21:05:03 from documentation in CollapseOS snapshot 20210917.