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. headerthe 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. places 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.