13 KiB
HoleyBytes ISA Specification
Bytecode format
- Image format is not specified, though ELF is recommended
- All numbers are encoded little-endian
- There is 256 registers, they are represented by a byte
- Immediate values are 8, 16, 32 or 64 bit
Instruction encoding
- Instruction operands are packed (no alignment)
- [opcode, operand 0, operand 1, …]
Instruction parameter types
R
: Register (8 bits)- Relative program-counter offset immediates:
O
: 32 bit (Si32)P
: 16 bit (Si16)
- Immediates:
B
: Byte, 8 bit (Xi8)H
: Half-word, 16 bit (Xi16)W
: Word, 32 bit (Xi32)D
: Double-word, 64 bit (Xi64)
A
: Absolute address immediate, 64 bit (Ui64)
Types
- Sin: Signed integer of size n bits (Si8, Si16, Si32, Si64)
- Uin: Unsigned integer of size n bits (Ui8, Ui16, Ui32, Ui64)
- Xin: Sign-agnostic integer of size n bits (Xi8, Xi16, Xi32, Xi64)
- Fln: Floating point number of size n bits (Fl32, Fl64)
Behaviour
- There is only one type of register, a general-purpose one. Used for both integers and floats.
- Integer operations are wrapping, including signed numbers
- Bitshifts are truncating
- Two's complement
- Floats as specified by IEEE 754
- Execution model is implementation defined as long all observable effects are performed in correct order
Relative addressing
Relative addresses are computed from address of the first byte of offset in the code. Not from the beginning of current or following instruction.
Zero register
- Register 0
- Cannot be clobbered
- Write is no-op
- Load always yields 0
Rounding modes
| Rounding mode | Value | fn():------| | To nearest, ties to even | 0b00 | | Towards 0 (truncate) | 0b01 | | Towards +∞ (up) | 0b10 | | Towards -∞ (down) | 0b11 |
- Remaining values in the byte traps with invalid operand exception
Memory
- Memory implementation is implementation-defined
- Zero address (
0x0
) is considered invalid
Traps
- Environment call
- Environment breakpoint
Program counter goes to the following instruction
Exceptions
- Memory access fault
- Invalid operand
- Unknown opcode
Program counter stays on the currently executed instruction
Instructions
#n
: register in parameter n$n
: for immediate in parameter n#P ← V
: Set register P to value V[x]
: Address xXY
: X bytes from location Ypc
: Program counter<XYZ>
: PlaceholderType(X)
: Cast
Program execution control
- Type
N
| Opcode | Mnemonic | Action | fn():--------------------------------------------| | 0x00 | UN | Throw unreachable code exception | | 0x01 | TX | Terminate execution (eg. on end of program) | | 0x02 | NOP | Do nothing |
Binary register-register ops
- Type
RRR
- Action:
#0 ← #1 <OP> #2
Addition (+
)
| Opcode | Mnemonic | Type | fn():-----| | 0x03 | ADD8 | Xi8 | | 0x04 | ADD16 | Xi16 | | 0x05 | ADD32 | Xi32 | | 0x06 | ADD64 | Xi64 |
Subtraction (-
)
| Opcode | Mnemonic | Type | fn():-----| | 0x07 | SUB8 | Xi8 | | 0x08 | SUB16 | Xi16 | | 0x09 | SUB32 | Xi32 | | 0x0A | SUB64 | Xi64 |
Multiplication (*
)
| Opcode | Mnemonic | Type | fn():-----| | 0x0B | MUL8 | Xi8 | | 0x0C | MUL16 | Xi16 | | 0x0D | MUL32 | Xi32 | | 0x0E | MUL64 | Xi64 |
Bitwise ops (type: Xi64)
| Opcode | Mnemonic | Operation | fn():--------------------| | 0x0F | AND | Conjunction (&) | | 0x10 | OR | Disjunction (|) | | 0x11 | XOR | Non-equivalence (^) |
Unsigned left bitshift (<<
)
| Opcode | Mnemonic | Type | fn():-----| | 0x12 | SLU8 | Ui8 | | 0x13 | SLU16 | Ui16 | | 0x14 | SLU32 | Ui32 | | 0x15 | SLU64 | Ui64 |
Unsigned right bitshift (>>
)
| Opcode | Mnemonic | Type | fn():-----| | 0x16 | SRU8 | Ui8 | | 0x17 | SRU16 | Ui16 | | 0x18 | SRU32 | Ui32 | | 0x19 | SRU64 | Ui64 |
Signed right bitshift (>>
)
| Opcode | Mnemonic | Type | fn():-----| | 0x1A | SRS8 | Si8 | | 0x1B | SRS16 | Si16 | | 0x1C | SRS32 | Si32 | | 0x1D | SRS64 | Si64 |
Comparsion
- Compares two numbers, saves result to register
- Operation:
#0 ← #1 <=> #2
| Ordering | Number | fn():-------| | < | -1 | | = | 0 | | > | 1 |
| Opcode | Mnemonic | Type | fn():-----| | 0x1E | CMPU | Ui64 | | 0x1F | CMPS | Si64 |
Merged divide-remainder
-
Type
RRRR
-
Operation:
#0 ← #2 / #3
#1 ← #2 % #3
-
If dividing by zero:
#0 ← Ui64(-1)
#1 ← #2
| Opcode | Mnemonic | Type | fn():-----| | 0x20 | DIRU8 | Ui8 | | 0x21 | DIRU16 | Ui16 | | 0x22 | DIRU32 | Ui32 | | 0x23 | DIRU64 | Ui64 | | 0x24 | DIRS8 | Si8 | | 0x25 | DIRS16 | Si16 | | 0x26 | DIRS32 | Si32 | | 0x27 | DIRS64 | Si64 |
Unary register operations (type: Xi64)
- Type:
RR
- Operation:
#0 ← <OP> #1
| Opcode | Mnemonic | Operation |
fn():-------------------------|
| 0x28 | NEG | Bitwise complement (~
) |
| 0x29 | NOT | Logical negation (!
) |
Sign extensions
- Operation:
#0 ← Si64(#1)
| Opcode | Mnemonic | Source type | fn():------------| | 0x2A | SXT8 | Si8 | | 0x2B | SXT16 | Si16 | | 0x2C | SXT32 | Si32 |
Binary register-immediate operations
- Type:
RR<IMM>
- Operation:
#0 ← #1 <OP> $2
Addition (+
)
| Opcode | Mnemonic | Type | fn():-----| | 0x2D | ADDI8 | Xi8 | | 0x2E | ADDI16 | Xi16 | | 0x2F | ADDI32 | Xi32 | | 0x30 | ADDI64 | Xi64 |
Multiplication (*
)
| Opcode | Mnemonic | Type | fn():-----| | 0x31 | MULI8 | Xi8 | | 0x32 | MULI16 | Xi16 | | 0x33 | MULI32 | Xi32 | | 0x34 | MULI64 | Xi64 |
Bitwise ops (type: Xi64)
| Opcode | Mnemonic | Operation | fn():--------------------| | 0x35 | ANDI | Conjunction (&) | | 0x36 | ORI | Disjunction (|) | | 0x37 | XORI | Non-equivalence (^) |
Register-immediate bitshifts
- Type:
RRB
- Operation:
#0 ← #1 <OP> $2
Unsigned left bitshift (<<
)
| Opcode | Mnemonic | Type | fn():-----| | 0x38 | SLUI8 | Ui8 | | 0x39 | SLUI16 | Ui16 | | 0x3A | SLUI32 | Ui32 | | 0x3B | SLUI64 | Ui64 |
Unsigned right bitshift (>>
)
| Opcode | Mnemonic | Type | fn():-----| | 0x3C | SRUI8 | Ui8 | | 0x3D | SRUI16 | Ui16 | | 0x3E | SRUI32 | Ui32 | | 0x3F | SRUI64 | Ui64 |
Signed right bitshift (>>
)
| Opcode | Mnemonic | Type | fn():-----| | 0x40 | SRSI8 | Si8 | | 0x41 | SRSI16 | Si16 | | 0x42 | SRSI32 | Si32 | | 0x43 | SRSI64 | Si64 |
Comparsion
- Compares two numbers, saves result to register
- Operation:
#0 ← #1 <=> $2
- Comparsion table same for register-register one
| Opcode | Mnemonic | Type | fn():-----| | 0x44 | CMPUI | Ui64 | | 0x45 | CMPSI | Si64 |
Register copies
- Type:
RR
| Opcode | Mnemonic | Operation |
fn():---------------------------------|
| 0x46 | CP | Copy register value (#0 ← #1
) |
| 0x47 | SWA | Swap register values (#0 ⇆ #1
) |
Load immediate
- Load immediate value from code to register
- Type:
R<IMM>
- Operation:
#0 ← $1
| Opcode | Mnemonic | Type | fn():-----| | 0x48 | LI8 | Xi8 | | 0x49 | LI16 | Xi16 | | 0x4A | Li32 | Xi32 | | 0x4B | Li64 | Xi64 |
Load relative address
- Compute value from program counter, register value and offset
- Type:
RRO
- Operation:
#0 ← pc + #1 + $2
| Opcode | Mnemonic | fn():---------| | 0x4C | LRA |
Memory access operations
- Immediate
$3
specifies size - If size is greater than register size,
it overflows to adjecent register
(eg. copying 16 bytes to register
r1
copies first 8 bytes to it and the remaining tor2
)
Absolute addressing
- Type:
RRAH
- Computes address from base register and absolute offset
| Opcode | Mnemonic | Operation |
fn():-------------------|
| 0x4D | LD | #0 ← $3[#1 + $2]
|
| 0x4E | ST | $3[#1 + $2] ← #0
|
Relative addressing
- Type:
RROH
- Computes address from register and offset from program counter
| Opcode | Mnemonic | Operation |
fn():------------------------|
| 0x4F | LDR | #0 ← $3[pc + #1 + $2]
|
| 0x50 | STR | $3[pc + #1 + $2] ← #0
|
Block memory copy
- Type:
RRH
- Copies block of
$3
bytes from memory location on address on#0
to#1
| Opcode | Mnemonic | Operation |
fn():------------------|
| 0x51 | BMC | $3[#1] ← $3[x0]
|
Block register copy
- Type:
RRB
- Copy block of
$3
registers starting with#0
to#1
- Copying over the 256 registers causes an exception
| Opcode | Mnemonic | Operation |
fn():--------------|
| 0x52 | BRC | $3#1 ← $3#0
|
Relative jump
- Type:
O
| Opcode | Mnemonic | Operation |
fn():---------------|
| 0x53 | JMP | pc ← pc + $0
|
Linking jump
- Operation:
- Save address of following instruction to
#0
#0 ← pc+<instruction size>
- Jump to specified address
- Save address of following instruction to
| Opcode | Mnemonic | Instruction type | Address |
fn():-------------------------|
| 0x54 | JAL | RRO (size = 6 B) | Relative, pc + #1 + $2
|
| 0x55 | JALA | RRA (size = 10 B) | Absolute, #1 + $2
|
Conditional jump
- Perform comparsion, if operation met, jump to relative address
- Type:
RRP
- Operation:
if #0 <CMP> #1 { pc ← pc + $2 }
| Opcode | Mnemonic | Condition | Type |
fn():-----|
| 0x56 | JEQ | Equals (=
) | Xi64 |
| 0x57 | JNE | Not-equals (≠
) | Xi64 |
| 0x58 | JLTU | Less-than (<
) | Ui64 |
| 0x59 | JGTU | Greater-than (>
) | Ui64 |
| 0x5A | JLTS | Less-than (<
) | Si64 |
| 0x5B | JGTS | Greater-than (>
) | Si64 |
Environment traps
- Traps to the environment
- Type:
N
| Opcode | Mnemonic | Trap type | fn():-----------------| | 0x5C | ECA | Environment call | | 0x5D | EBP | Breakpoint |
Floating point binary operations
- Type:
RRR
- Operation:
#0 ← #1 <OP> #2
| Opcode | Mnemonic | Operation | Type |
fn():-----|
| 0x5E | FADD32 | Addition (+
) | Fl32 |
| 0x5F | FADD64 | Addition (+
) | Fl64 |
| 0x60 | FSUB32 | Subtraction (-
) | Fl32 |
| 0x61 | FSUB64 | Subtraction (-
) | Fl64 |
| 0x62 | FMUL32 | Multiplication (*
) | Fl32 |
| 0x63 | FMUL64 | Multiplication (*
) | Fl64 |
| 0x64 | FDIV32 | Division (/
) | Fl32 |
| 0x65 | FDIV64 | Division (/
) | Fl64 |
Fused multiply-add
- Type:
RRRR
- Operation:
#0 ← (#1 * #2) + #3
| Opcode | Mnemonic | Type | fn():-----| | 0x66 | FMA32 | Fl32 | | 0x67 | FMA64 | Fl64 |
Comparsions
- Type:
RRR
- Operation:
#0 ← #1 <=> #2
- Comparsion table same as for
CMPx
/CMPxI
- NaN is less-than/greater-than depends on variant
| Opcode | Mnemonic | Type | NaN is | fn():-------| | 0x6A | FCMPLT32 | Fl32 | < | | 0x6B | FCMPLT64 | Fl64 | < | | 0x6C | FCMPGT32 | Fl32 | > | | 0x6D | FCMPGT64 | Fl64 | > |
Int to float
- Type:
RR
- Converts from
Si64
- Operation:
#0 ← Fl<SIZE>(#1)
| Opcode | Mnemonic | Type | fn():-----| | 0x6E | ITF32 | Fl32 | | 0x6F | ITF64 | Fl64 |
Float to int
- Type:
RRB
- Operation:
#0 ← Si64(#1)
- Immediate
$2
specifies rounding mode
| Opcode | Mnemonic | Type | fn():-----| | 0x70 | FTI32 | Fl32 | | 0x71 | FTI64 | Fl64 |
Fl32 to Fl64
- Type:
RR
- Operation:
#0 ← Fl64(#1)
| Opcode | Mnemonic | fn():---------| | 0x72 | FC32T64 |
Fl64 to Fl32
- Type:
RRB
- Operation:
#0 ← Fl32(#1)
- Immediate
$2
specified rounding mode
| Opcode | Mnemonic | fn():---------| | 0x73 | FC64T32 |
16-bit relative address instruction variants
| Opcode | Mnemonic | Type | Variant of | fn():-----------| | 0x74 | LRA16 | RRP | LRA | | 0x75 | LDR16 | RRPH | LDR | | 0x76 | STR16 | RRPH | STR | | 0x77 | JMP16 | P | JMP |
psABI
C datatypes and alignment
- One byte is 8 bits
| C Type | Description | Byte sizes | fn():-----------| | char | Character / byte | 1 | | short | Short integer | 2 | | int | Integer | 4 | | long | Long integer | 8 | | long long | Long long integer | 8 | | float | Single-precision float | 4 | | double | Double-precision float | 8 | | long double | Extended-precision float | 8 |
- Bikeshedding note:
long double
is now 8 bytes as the base ISA does not supportf128
. an extension for that should be made.
Call convention
- Registers r1 – r31 are caller saved
- Registers r32 – r255 are callee saved
| Register | Description | Saver | fn():-------| | r0 | Hard-wired zero | N/A | | r1 - r2 | Return values | Caller | | r2 - r11 | Function parameters | Caller | | r12 - r30 | General purpose | Caller | | r31 | Return address | Caller | | r32 - r253 | General purpose | Callee | | r254 | Stack pointer | Callee | | r255 | Thread pointer | N/A |
- If return value is too big to fit r1, r2 is also used.
- Values larger than two double-words are passed by reference