hephv2.0 22/10/26 512b.dev/mjm
Heph is a platform-agnostic format for serialising parts of
or entire Forth dictionaries.
this document is intended as a user's reference for the
format. while it may be used as a reference for the
implementation of Heph translators, it is recommended that
implementors await a formal reference document.
heph
this document is a work in progress!
heph packs
header
elements
00 nn: single octet
01 nnnn: single doublet
02 nn..: multiple octets
03 nn..: multiple doublets
04 nn..: define native
05 nn..: define codeword
06 cccc nn..: composite by offset
07 cc.. nn..: composite by name
08 nnnn: execution token by offset
09 nn..: execution token by name
0A nnnn: executable token by offset
0B nn..: executable token by name
xt vs. xxt tokens
0C: mark forwards
0D: mark backwards
0E: resolve forwards
0F: resolve backwards
10 nn: roll
11 nn: align
12 nnnn: uninitialised
13: next
14: end-of-field
the heph machine
registers
data registers
stack registers
representation
addressing modes
r: direct access
@r: indirection
@r+: post-increment indirection
@-r: pre-decrement indirection
representation
addressing modes and stack registers
s: pointer access
@s: peek
@s+: pop
@-s: push
ir format
unary format
binary format
immediate operands
ir operations
unary operations
0: lsh
1: rsh
2: not
3: neg
binary operations
0: mov
1: and
2: or
3: xor
4: add
5: sub
6: adc
7: sbc
8: ads
9: sbs
A: jmp
B: jnz
C..F: implementation defined operations
acknowledgements
this document is a work in progress!while this version of the document is public, it is
published for demonstration purposes; some wording needs
clarification, and some important details have yet to be
written. do not rely on it yet!
heph packsdictionaries, partial or otherwise, are stored in Heph files
called packs. a pack consists of a header followed by a
sequence of elements.
the header consists of a thirty-two bit magic number
obtained by taking the lowest seven bits of each character
of the string "HEPH", and placing them back to back, with
the first character placed at the most significant position.
this magic number is stored with its bytes in most-to-least
significant order. the number can be generated with the
following C program:
#include <stdio.h>
int main(void)
{
char string[] = "HEPH";
unsigned long magic = 0;
int i;
for (i = 0; i < 4; i++) {
magic |= string[i] << 7 * (3 - i);
}
for (i = 3; i >= 0; i--) {
printf("%02lX%c",
0xFF & magic >> i * 8,
i? ' ' : '\n');
}
return 0;
}
the resulting hex string is "09 11 68 48".
elementselements are discreet parts of the dictionary, and can
either define the dictionary's structure or place translated
IR instructions. this distinction is represented by the top
bit of the leading character of an element; if it is set, it
is the first character of an IR instruction, and otherwise,
it is the first character of a structural element. said
structural elements are listed below.
00 nn: single octetplaces the character n at the write head.
01 nnnn: single doubletplaces the word n at the write head.
02 nn..: multiple octetsplaces n octets at the write head, read from the stream.
03 nn..: multiple doubletsplaces n words at the write head, read from the stream.
04 nn..: define nativeplaces a new word definition, with a name of length n taken
from the stream, such that subsequent IR operations are
executed when the created word is called.
05 nn..: define codewordcreates a new native word, as above, but additionally places
a code snippet such that, when this word is referenced as a
codeword, the IR operations that follow will be executed
with a pointer to the referencing word's data stored in the
W register.
06 cccc nn..: composite by offset07 cc.. nn..: composite by namecreates a new composite word, with a name of length n taken
from the stream. subsequent data will be stored within its
data area, a pointer to which is passed to its codeword
whenever it is called. in the former form, the codeword is
found by offset, and in the latter, it is found by name.
08 nnnn: execution token by offset09 nn..: execution token by nameplaces a pointer to the part of a dictionary entry directly
following its name. in the former form, the entry is found
by offset, and in the latter, it is found by name.
0A nnnn: executable token by offset0B nn..: executable token by nameplaces a token that, when pointed to by the I register
before execution, will invoke a particular word. in the
former form, the word is found by offset, and in the latter,
it is found by name.
xt vs. xxt tokensin execution models like DTC and ITC, XT and XXT elements
may be exactly identical to each other. this is not a fact
you should ever rely on; always use XXT elements when you
want something that will be executed directly by the
machine, and always use an XT when you want to reference a
word as a piece of data.
0C: mark forwardsplaces a placeholder word, and pushes its address to the
control-flow stack.
0D: mark backwardspushes the location of the write head to the control-flow
stack.
0E: resolve forwardspops the address on the top of the control-flow stack, and
stores the location of the write head to it.
0F: resolve backwardspops the address on the top of the control-flow stack, and
places it at the write head.
10 nn: rollpulls the nth address from the top of the control stack up
to the top.
11 nn: alignaligns the write head upwards to the nearest unit of 2^n.
12 nnnn: uninitialisedadds n characters to the write head.
13: nextplaces an IR snippet corresponding to the system's `NEXT`
operation, terminating a code- or native word.
14: end-of-fieldterminates the heph stream.
the heph machinethe Heph format includes an IR which represents instructions
on an imagined 16-bit Heph machine. this machine is designed
to provide the minimum viable set of facilities for
reasonably implementing a Forth system.
registersbesides the machine instruction pointer, which is seldom
accessed directly, the Heph machine contains four machine
registers. these can be further classified as data registers
and stack registers.
data registersthese two registers, named W and I, are both guaranteed to
be 16 bits in width, also known as a word. the W register is
intended to be used as a general purpose working register,
while the I register is used to store the Forth instruction
pointer. in other words, at any given time, I points to the
next execution token.
stack registersthese two registers, named S and R, point to the two stacks
of the Heph machine. the S register points to the data stack
used by most operations in a Forth system, while the R
register points to the return stack. while these registers
can be 16 bits in width, like the data registers, they are
only guaranteed to have a minimum width of 8 bits. in other
words, both stacks are only guaranteed to hold 128 words of
data.
representationin mode-register pairs, the register is represented by two
bits. the corresponding values are below:
+0 +1
0 | W | I |
2 | S | R |
addressing modeswherever a register is used, it is accompanied by an
addressing mode, allowing optional indirection in accessing
data.
r: direct accessthis mode represents direct access of the register. it will
return the value contained in the register in question.
@r: indirectionthis mode represents a dereferencing of the register. it
will return the value pointed to by the value in the
register in question.
@r+: post-increment indirectionthis mode behaves identical to @r, but results in the
register in question being incremented after the access.
when accessing a word, the register is incremented by 2, and
when accessing a character, the register is incremented by
1.
@-r: pre-decrement indirectionthis mode behaves identical to @r, except the register in
question gets decremented before the access. when accessing
a word, the register is decremented by 2, and when accessing
a character, the register is decremented by 1.
representationin mode-register pairs, the mode is represented by two bits.
the corresponding values are below:
+0 +1
0 | r | @r |
2 | @r+ | @-r |
addressing modes and stack registerswhile conceptually addressing modes affect stack registers
the same as data registers, in practice, each mode
represents a distinct operation on the stack.
s: pointer accessusing this mode for fetching data will return a portable
value representing the current location of the given stack
pointer. storing such a value back into this mode will
restore the stack pointer to where it was at time of fetch.
storing this value in a data register, performing arithmetic
on it, and storing it back into a stack register has an
undefined result, as does storing the value of one stack
register in another.
@s: peekwhen fetching, this mode will return the value on top of the
given stack without popping it. when storing, this mode will
set the value on top of the given stack.
@s+: popwhen fetching, this mode will pop the value on top of the
given stack and return it. using this mode for storing
invokes undefined behaviour.
@-s: pushwhen storing, this mode will push the given value to the
given stack. using this mode for fetching invokes undefined
behaviour.
ir formatall Heph IR elements begin with a character that has its top
bit set. the layout of the rest of the character depends on
whether the instruction is unary or binary.
unary formatthe bits of a unary instruction are laid out like so, with
bit 0 being the least significant:
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
value | 1 | 0 | O | M | R |
where:
- O: the opcode of the instruction.
- MR: a mode-register pair representing the destination.
binary formatthe bits of a binary instruction are laid out like so, with
bit 0 being the least significant, and the most significant
character coming first in the stream:
hi bit | F | E | D | C | B | A | 9 | 8 |
value | 1 | 1 | I | S | O |
+---+---+---+---+---+---+---+---+
value | M-D | R-D | M-S | R-S |
lo bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
where:
- I: a flag signifying immediacy. see immediate operands
- S: a flag signifying operand size; 1=char, 0=word
(char-size operations on stack registers are
undefined)
- O: the opcode of the instruction.
- MR-D: a mode-register pair representing the destination.
- MR-S: a mode-register pair representing the source.
immediate operandswhen the immediacy flag of a binary instruction is set, the
source register is not used. instead, the relevant data is
sourced from the stream. the lsb of the instruction looks
like this:
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
value | M-D | R-D | M-S | | E |
when E is cleared, the source operand is taken directly from
the stream. a big-endian word is always taken in this case,
even if the instruction operates on characters. when that is
the case, the most significant character of the operand is
ignored.
when E is set, the source operand is instead taken from the
result of the next element in the stream. if the next
element produces more than two characters of data, behaviour
is undefined.
in either of these cases, the addressing mode of the operand
still applies.
ir operationsunary operations +0 +1
0 | lsh | rsh |
2 | not | neg |
0: lshshifts the data in the destination to the left by one bit.
1: rshshifts the data in the destination to the right by one bit.
2: notperforms bitwise inversion on the data in the destination.
3: negperforms two's complement negation on the data in the
destination.
binary operations +0 +1 +2 +3
0 | mov | and | or | xor |
4 | add | sub | adc | sbc |
8 | ads | sbs | jmp | jnz |
C | free use |
0: movmoves the data in the source to the destination.
1: andtakes the result of a bitwise AND between the data in the
source and destination, and puts the result in the
destination.
2: ortakes the result of a bitwise OR between the data in the
source and destination, and puts the result in the
destination.
3: xortakes the result of a bitwise XOR between the data in the
source and destination, and puts the result in the
destination.
4: addtakes the sum of the data in the source and destination, and
puts the result in the destination. if the operation
overflows, the carry flag is set.
5: subsubtracts the data in the source from the data in the
destination, and puts the result in the destination. if the
operation overflows, the carry flag is set in such a way
that a subsequent use of sbc may be used to perform an
extended subtraction.
6: adcidentical to add, but the carry flag is included in the sum.
7: sbcmuch like sub, but relies on the carry flag set by either a
sub or another sbc to perform extended subtraction. an sbc
preceded directly by an instruction other than a sub or an
sbc invokes undefined behaviour.
8: adsadds or subtracts the source from the destination, such
that if performed on a stack register, it would be moved
downwards, where "downwards" is defined as the direction
from the top to the bottom of the stack.
9: sbsadds or subtracts the source from the destination, such
that if performed on a stack register, it would be moved
upwards, where "upwards" is defined as the direction from
the bottom to the top of the stack.
A: jmpsets the machine instruction pointer to the address stored
in the source unconditionally.
B: jnzidentical to jmp, but the jump is only performed if the data
in the destination is equal to any integer other than 0.
immediate operands are consumed regardless.
C..F: implementation defined operationsthese opcodes are free to be used in whatever way the
implementation necessitates. they may represent i/o, or
practically anything else. using these opcodes at all
outside of platform-specific contexts is unwise.
acknowledgementsmuch of the IR's design is parroted almost directly from
that of otesunki's MISA architecture, and otesunki herself
provided a lot of much-needed help in the design process of
the format.