-
Notifications
You must be signed in to change notification settings - Fork 9
Tagha Instruction Set Architecture
Tagha is a little-endian, 64-bit stack & register based VM. Tagha employs both an operand stack and a callstack. Tagha utilizes 3 registers: osp, csp, and lr.
osp - Operand Stack Pointer.
csp - Call Stack Pointer.
lr - Link Register.
Through the opcodes & operand stack, the instruction set can allocate up to 256 registers per alloc opcode, access up to 256 registers per register-based opcodes, and address up to 32,000+ register addresses.
Stops execution of a script/function.
does jack.
reduces the op-stack pointer by n * 8 bytes to create room for data.
increases the op-stack pointer by n * 8 bytes to destroy data. Inverse of alloc.
copies an 8 byte immediate value to a register.
copies a source register's contents to a destination register.
"Load Register Address", gets the address of the op-stack pointer, adds an unsigned 2-byte offset multiplied by 8 (word size of Tagha) to it, and puts the resulting address in a register.
"Load Effective Address", performs address calculation of a source register and signed 2-byte offset to a destination register.
loads the address of a global variable into a register.
loads a function pointer to a register.
loads a byte value from a memory address (added with a signed 2-byte offset) into a register.
loads a short (2 byte) value from a memory address (added with a signed 2-byte offset) into a register.
loads a long (4 byte) value from a memory address (added with a signed 2-byte offset) into a register.
loads a long long (8 byte) value from a memory address (added with a signed 2-byte offset) into a register.
loads an unsigned byte value from a memory address (added with a signed 2-byte offset) into a register.
loads an unsigned short (2 byte) value from a memory address (added with a signed 2-byte offset) into a register.
loads an unsigned long (4 byte) value from a memory address (added with a signed 2-byte offset) into a register.
stores a byte value from a register into a memory address (added with a signed 2-byte offset).
stores a short (2 byte) value from a register into a memory address (added with a signed 2-byte offset).
stores a long (4 byte) value from a register into a memory address (added with a signed 2-byte offset).
stores a long long (8 byte) value from a register into a memory address (added with a signed 2-byte offset).
Adds the integer value of a source register to a destination register.
Subtracts the integer value of a source register to a destination register.
multiplies the integer value of a source register to a destination register.
divides the integer value of a source register to a destination register.
modulos the integer value of a source register to a destination register.
negates the integer value of a register.
same as add but for floating point values. Math is done for the largest data width that's enabled (meaning if both doubles and floats are used, fadd will perform on doubles, regardless whether floats are defined or not).
same as sub but for floating point values. Math is done for the largest data width that's enabled (meaning if both doubles and floats are used, fsub will perform on doubles, regardless whether floats are defined or not).
same as mul but for floating point values. Math is done for the largest data width that's enabled (meaning if both doubles and floats are used, fmul will perform on doubles, regardless whether floats are defined or not).
same as idiv but for floating point values. Math is done for the largest data width that's enabled (meaning if both doubles and floats are used, fdiv will perform on doubles, regardless whether floats are defined or not).
same as neg but for floating point values. Math is done for the largest data width that's enabled (meaning if both doubles and floats are used, fneg will perform on doubles, regardless whether floats are defined or not).
peforms bitwise AND of the integer value of a source register to a destination register.
Assembler can use and & bit_and naming of opcode.
peforms bitwise OR of the integer value of a source register to a destination register.
Assembler can use or & bit_or naming of opcode.
peforms bitwise XOR of the integer value of a source register to a destination register.
Assembler can use xor & bit_xor naming of opcode.
peforms logical leftward bit shift of the integer value of a source register to a destination register.
peforms logical rightward bit shift of the integer value of a source register to a destination register.
peforms arithmetic rightward bit shift of the integer value of a source register to a destination register.
peforms bitwise NOT to a register.
Assembler can use not & bit_not naming of opcode.
signed LESS-THAN comparison between two registers.
signed LESS-EQUAL comparison between two registers.
unsigned LESS-THAN comparison between two registers.
unsigned LESS-EQUAL comparison between two registers.
EQUALITY comparison between two registers.
same as ilt but for floating point values. Math is done for the largest data width that's enabled (meaning if both doubles and floats are used, flt will perform on doubles, regardless whether floats are defined or not).
same as ile but for floating point values. Math is done for the largest data width that's enabled (meaning if both doubles and floats are used, fle will perform on doubles, regardless whether floats are defined or not).
stores the conditional flag to a register.
converts a register's float32 value to float64 value. Does nothing if floats or doubles values aren't defined for tagha registers or floating point support isn't used at all.
converts a register's float64 value to float32 value. Does nothing if floats or doubles values aren't defined for tagha registers or floating point support isn't used at all.
converts a register's integer value to a float64, does nothing if doubles values aren't defined for registers or floating point support isn't used at all.
converts a register's integer value to a float32, does nothing if floats values aren't defined for registers or floating point support isn't used at all.
converts a register's float64 value to an int, does nothing if floats values aren't defined for registers or floating point support isn't used at all.
converts a register's float32 value to a int, does nothing if floats values aren't defined for registers or floating point support isn't used at all.
local instruction jump using a signed 8 byte immediate value.
Jump if Zero by a comparison result using a signed 8 byte immediate value.
Jump if Not Zero by a comparison result using a signed 8 byte immediate value.
pushes the link register to the call stack. Necessary if a function calls another.
pops a return address from the call stack to the link register.
jumps to a function using an unsigned 2 byte value as an index. can also call a native function.
jumps to a function using a function pointer. can also call a native function pointer.
loads the value of the link register to the program counter and resumes execution.
Vectors are basically packed registers that can be larger than 8 bytes but can be any width multiplied by an element size. For any vector opcode that takes a source and destination register, the destination register must be the same vector size or larger than the source register. In terms of technical operation, a vector register is basically an array.
When using a register as a vector, any element size less than word (64-bits) must be packed. For float-based vector operations, both float32_t and float64_t MUST BE DEFINED AND USEABLE, otherwise it's entirely a nop.
sets the operational width of vectors that are used for the vector opcodes.
sets the element size of vectors. Valid sizes are byte, half, long, and word. For float-based vector operations, only long and word are valid. If the element size is invalid, word size is used.
copies a source register's contents as a vector (element size multiplied by vector width) to a destination register, destination registers is also used as a vector.
same as add but source + destination registers are vectors.
same as sub but source + destination registers are vectors.
same as mul but source + destination registers are vectors.
same as div but source + destination registers are vectors.
same as mod but source + destination registers are vectors.
same as neg but source register is a vector.
Same as vadd but with floats.
Same as vsub but with floats.
Same as vmul but with floats.
Same as vdiv but with floats.
Same as vneg but with floats.
same as bit_and but source + destination registers are vectors.
same as bit_or but source + destination registers are vectors.
same as bit_xor but source + destination registers are vectors.
same as shl but source + destination registers are vectors.
same as shr but source + destination registers are vectors.
same as shar but source + destination registers are vectors.
same as bit_not but source + destination registers are vectors.
same as cmp but source + destination registers are vectors.
same as ilt but source + destination registers are vectors.
same as ile but source + destination registers are vectors.
same as ult but source + destination registers are vectors. Not a "deus vult" joke.
same as ule but source + destination registers are vectors.
same as flt but source + destination registers are vectors.
same as fle but source + destination registers are vectors.
All opcodes take up a single byte and additional bytes depending on whether they operate on registers, immediate values, or doing memory operations.
- | byte: opcode | 1 byte
- | byte: opcode | byte: register id / argument | 2 bytes
- | byte: opcode | byte: dest reg | byte: src reg | 3 bytes
- | byte: opcode | byte: dest reg | 2 bytes: imm | 4 bytes
- | byte: opcode | byte: dest reg | byte: src reg | 2 bytes: offset | 5 bytes
- | byte: opcode | 4 bytes: imm (immediate) value | 5 bytes
- | byte: opcode | byte: register id | 8 bytes: imm value | 10 bytes
If a bytecode function calls another bytecode function (even itself) and that bytecode function is not tail-call optimized, then it's REQUIRED to preserve the link register (using pushlr) and restore it (using poplr) at the end of the function's context. External function calls ALWAYS preserves and restores the link register so pushlr and poplr is not necessary for external calls.
All arguments must be placed in registers from r1 to rN as needed for the amount of arguments.
It's suggested that r0 stores the number of arguments (or perhaps total byte size sum of all arguments) but not required.
In a different calling convention (called "Clobber Call"), a bytecode function may use r0 to hold the first argument and clobber r0 with the final return result.
For va_list, it's required to use a register to store a pointer that will point to two values, a pointer to the array of arguments and a number of the arguments.
Here's an example in pseudo-ASM:
r6 = 10
r5 = 15
r4 = 20
r3 = 3
r2 = &r4
r1 = &r2In a native function, r1 will be used as like union TaghaVal[2] where [0] will hold the array of arguments and [1] holds the argument count.
All functions, bytecode & native, must allocate one extra register as r0 (top of stack) will be the return value register. r0 may be used as pleased by the compiler if the function returns nothing (void).
Native functions clobber r0 regardless as all native wrappers must return data, even if the C(++) function returns void.
r0 may optionally hold a copy of any of the other arguments passed to the function for simplifying code size.
Realistically, though Tagha allows functions to allocate & reduce the amount of available registers, it's also permissible to allocate a large amount of registers and use those same registers throughout the entire program.