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.
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
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.
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
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:
IFZ, INCw, ELSE, DECw, THEN, \ also: IFNZ, IFC, IFNC,
42 i>w, BEGIN, DECw, Z?w, BR JRNZi, \ loop 42 times
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.
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.
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
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
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.
This page generated at 2021-09-19 21:05:03 from documentation in CollapseOS snapshot 20210917.