WIP opcodes

This commit is contained in:
zongor 2026-01-03 19:03:50 -08:00
parent e71d687db8
commit 39d088bd9c
2 changed files with 282 additions and 7 deletions

134
' Normal file
View File

@ -0,0 +1,134 @@
#+TITLE Project Specification
* Binary interface
The VM does not use floating point numbers, it instead uses fixed point numbers.
This is for portability reasons as some devices might not have a FPU in them
especially microcontrollers and some retro game systems like the PS1.
** Numbers
| type | size (bytes) | description |
|------+--------------+---------------------------------------|
| u8 | 1 | unsigned 8bit, alias =char= and =byte= |
| bool | 1 | unsigned 8bit, =false= or =true= |
| i8 | 1 | signed 8bit for interop |
| u16 | 2 | unsigned 16bit for interop |
| i16 | 2 | signed 16bit for interop |
| u32 | 4 | unsigned 32bit, alias =nat= |
| i32 | 4 | signed 32bit, alias =int= |
| f32 | 4 | signed 32bit fixed number, alias =real= |
* Memory
Uses a harvard style archecture, meaning the code and ram memory
are split up into two seperate blocks.
In the C version you can see these are two seperate arrays 'code' and 'mem'.
During compilation constants and local variables are put onto 'mem'
* Opcodes
| 2^n | count |
|----+-------|
| 2^3 | 8 |
| 2^4 | 16 |
| 2^5 | 32 |
| 2^6 | 64 |
| 2^8 | 128 |
** could be encoded
- op type [this is to maximize jump immidate and load immidate size]
- memory location
- local value / register
- local value type
*** Simplest
[opcode][dest][src1][src2]
[8][8][8][8]
*** Maximize inline jump and load-immidate
[0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0]
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 | 0 0 ] noop
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 1 | 0 0 ] call
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 1 0 | 0 0 ] return
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 1 1 | 0 0 ] syscall?
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 1][0 0 0 | 1 0 0 | 0 0 ] exit
[0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 | 0 1 ] jump ~1GB range
[0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 | 1 0 ] load-immidate 2^30 max
*** multibyte ops
- ones that would be easier if they were multibyte
- jump
- load immidate
- syscall
- call
0 0 - system, lowest because lower opcodes are faster
0 1 - memory
1 0 - math
1 1 - jump
[0 0][0 0 0 0 0 0] = [system][no op]
[0 0][1 0 0 0 0 0] = [system][loadimm]
[0 0][0 0 0 0 0 0] = [system][return]
[0 0 0 | r r r r r][0 0 0 | r r r r r][0 0 0 | r r r r r][b b | t | o o o | 1 1] [math][add][f][8]
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 | 1 | 0 0 1 | 1 1] [math][add][f][8]
[0 1][0 0 1][0][0 0] = [math][sub][f][16]
[0 1][0 1 0][0][0 0] = [math][mul][f][32]
[0 1][0 1 1][0][0 0] = [math][div][f][?]
[0 1][1 0 0][0][0 0] = [math][and][f][?]
[1 1][0][0 0][0 0] = [jmp][u][eq]
[1 1][0][0 0][0 0] = [jmp][u][ne]
[1 1][0][0 0][0 0] = [jmp][u][lt]
[1 1][0][0 0][0 0] = [jmp][u][gt]
[1 1][1][0 0 0][0 0] = [jmp][s][le]
[1 1][1][0 0 0][0 0] = [jmp][s][ge]
[1 1][1][0 0 0][0 0] = [jmp][s][]
[1 1][1][0 0 0][0 0] = [jmp][s][]
3 2 2 1
[...] i 32 0
3 1 3 1
[...] u rl j
[...] u ab j
[...] u eq j
[...] u nq j
[...] u lt j
[...] u le j
[...] u gt j
[...] u ge j
[jmp][dest][18]
[lli][dest][2]
int 3
add
sub
mul
div
jeq
jne
jlt
jle
jgt
jge

View File

@ -1,4 +1,4 @@
#+TITLE Project Specification
#TITLE Project Specification
* Binary interface
@ -31,15 +31,156 @@ During compilation constants and local variables are put onto 'mem'
* Opcodes
Most opcodes are 4 bytes
| 2^n | count |
|----+-------|
| 2^3 | 8 |
| 2^4 | 16 |
| 2^5 | 32 |
| 2^6 | 64 |
| 2^8 | 128 |
** could be encoded
- op type [this is to maximize jump immidate and load immidate size]
- memory location
- local value / register
- local value type
*** Simplest
[opcode][dest][src1][src2]
[8][8][8][8]
A small number are multibyte like load-long-immidiate
*** Maximize inline jump and load-immidate
[0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0]
[multibyte-opcode][0u8][0u8][opcode] . [u32]
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 | 0 0 ] noop
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 1 | 0 0 ] call
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 1 0 | 0 0 ] return
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 1 1 | 0 0 ] syscall?
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 1][0 0 0 | 1 0 0 | 0 0 ] exit
These are decoded during runtime and selected.
[0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 | 0 1 ] jump ~1GB range
[0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 | 1 0 ] load-immidate 2^30 max
In theory, bitshift decoding is faster than
accessing an unknown n bytes of memory in the 'code' array.
*** multibyte ops
- ones that would be easier if they were multibyte
- jump
- load immidate
- syscall
- call
0 0 - system, lowest because lower opcodes are faster
0 1 - memory
1 0 - math
1 1 - jump
[0 0][0 0 0 0 0 0] = [system][no op]
[0 0][1 0 0 0 0 0] = [system][loadimm]
[0 0][0 0 0 0 0 0] = [system][return]
J [0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 | 0 1]
L [0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 0 0 0 0 0 0 0][0 | o o o o o | 1 0]
R [r r r r r r | r r][r r r r | r r r r][r r | b b | 0 0 0 0][o o o o o o | 1 1]
[0 0 0 0 0 | r r r][r r r r | r r r r][r r | r r r r r r][b b | t | o o o | 1 1]
[0 0 0 | r r r r r][0 0 0 | r r r r r][0 0 0 | r r r r r][b b | t | o o o | 1 1]
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 | 1 | 0 0 1 | 1 1] [math][add][f][8]
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 | 1 | 0 1 0 | 1 1] [math][sub][f][8]
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 | 1 | 0 1 1 | 1 1] [math][mul][f][8]
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 | 1 | 1 0 0 | 1 1] [math][div][f][8]
[0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 0 | 0 0 0 0 0][0 0 | 1 | 1 0 1 | 1 1] [math][mod][f][8]
[0 1][0 0 1][0][0 0] = [math][sub][f][16]
[0 1][0 1 0][0][0 0] = [math][mul][f][32]
[0 1][0 1 1][0][0 0] = [math][div][f][?]
[0 1][1 0 0][0][0 0] = [math][and][f][?]
[1 1][0][0 0][0 0] = [jmp][u][eq]
[1 1][0][0 0][0 0] = [jmp][u][ne]
[1 1][0][0 0][0 0] = [jmp][u][lt]
[1 1][0][0 0][0 0] = [jmp][u][gt]
[1 1][1][0 0 0][0 0] = [jmp][s][le]
[1 1][1][0 0 0][0 0] = [jmp][s][ge]
[1 1][1][0 0 0][0 0] = [jmp][s][]
[1 1][1][0 0 0][0 0] = [jmp][s][]
3 2 2 1
[...] i 32 0
3 1 3 1
[...] u rl j
[...] u ab j
[...] u eq j
[...] u nq j
[...] u lt j
[...] u le j
[...] u gt j
[...] u ge j
[jmp][dest][18]
[lli][dest][2]
int 3
add
sub
mul
div
jeq
jne
jlt
jle
jgt
jge
Maybe opcodes could be Huffman encoded? That way the smaller opcodes get more operand data within the u32
At compile time each function gets N number of locals (up to 255). These are allocated onto memory along with everything else, but they come before the heap values.
Maybe instead of push pushing onto a stack it instead pushes onto a child frame? Pops would just mark what locals need to be replaced by the child function? Maybe we can get rid of call/return and just use jumps?
U8 u8 u8 u8
Push/pop parent_local child_local metadata
** Maybe more flexible calling convention?
Memory-to-memory with register characteristics?
Passed in values
Copy each argument from the callers local to the callees local. This includes pointers.
child modifies the heap
If a child modifies a value in the parents heap do nothing, this is expected behavior.
If a child changes the size of a parents heap then copy the heap value to the childs frame.
Returned values
If a primitive value just copy from child local to parent local
If a heap value is returned but placed in a new local in the parent then copy the child to the parent and update the frames memory pointer
If a heap value is replaced (i.e. the return sets a heap value with its modified version) then
Sort each returned value by its pointers location in memory, lowest first
Move to position of returned values lowest ptr position.
Read fat ptr size of the earliest value.
Take the current size of heap.
Move to just after the end of the size + ptr.
Copy all values from that location through current end of heap to the old start location of that value.
Subtract the old size of the value from the mp.
Copy the new sized value and put it at the current end of the heap.
Update the new pointers local position.
Add the new size to the mp.
Repeat for each returned value that is a replaced heap value.