blog post 2
This commit is contained in:
136
blog-post-2.md
Normal file
136
blog-post-2.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Blog Entry #2 — Registers, Addressing Modes & Type Size Modifiers
|
||||||
|
|
||||||
|
In this second entry I will cover three fundamental concepts of the
|
||||||
|
Spider Virtual Machine: the register table, addressing modes, and
|
||||||
|
type size modifiers. These three systems work together to define how
|
||||||
|
every single instruction in Spider operates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Registers
|
||||||
|
|
||||||
|
The Spider VM provides two categories of registers.
|
||||||
|
|
||||||
|
**General Purpose Registers (16 total)** are directly accessible from
|
||||||
|
instructions using addressing modes. They can hold either integers or
|
||||||
|
floats depending on the instruction being executed.
|
||||||
|
|
||||||
|
The 16 GP registers have defined roles by convention:
|
||||||
|
|
||||||
|
- `RA` — First argument and return value of a function
|
||||||
|
- `RB`, `RC`, `RD` — Second, third and fourth arguments
|
||||||
|
- `RX`, `RY` — Free auxiliary registers with no rules
|
||||||
|
- `R0`–`R3` — Caller-saved: the called function may overwrite them
|
||||||
|
- `R4`–`R7` — Callee-saved: the called function must restore them
|
||||||
|
- `R8`, `R9` — Fifth and sixth function arguments
|
||||||
|
|
||||||
|
**System Registers (8 total)** cannot be accessed directly by addressing
|
||||||
|
modes. They require dedicated instructions to read or modify them, and
|
||||||
|
they are always integers.
|
||||||
|
|
||||||
|
The most important system registers are:
|
||||||
|
|
||||||
|
- `RF` — Flag Register: holds the full state of the VM in 64 bits
|
||||||
|
- `RI` — Instruction Register: points to the current instruction (program counter)
|
||||||
|
- `RS` — Stack Register: tracks the current top of the stack
|
||||||
|
- `RZ` — Stack Base Register: base reference for the current function frame
|
||||||
|
- `RE` — Exception Register: holds the address of the active exception handler
|
||||||
|
- `RV` — Interrupt Vector Register: used for both internal and external interrupts
|
||||||
|
- `RM` — Memory Register: total RAM available to the VM
|
||||||
|
|
||||||
|
One of Spider's core design decisions is to prioritize registers over
|
||||||
|
the stack. Since registers are always available and fast to access,
|
||||||
|
a well-written Spider program can run with minimal stack usage.
|
||||||
|
This is especially important on constrained hardware like the ATmega328p,
|
||||||
|
which only has 2KB of RAM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Addressing Modes
|
||||||
|
|
||||||
|
Each instruction in Spider operates on parameters. An addressing mode
|
||||||
|
defines **how to interpret** those parameters. The same instruction can
|
||||||
|
behave very differently depending on the mode.
|
||||||
|
|
||||||
|
Spider has 8 addressing modes:
|
||||||
|
|
||||||
|
| Mode | Syntax | Meaning |
|
||||||
|
|-----------|-------------------|------------------------------------------------------|
|
||||||
|
| Implied | (none) | No parameter needed, operand is implicit |
|
||||||
|
| Immediate | `42i` | The value is a literal constant |
|
||||||
|
| Absolute | `0x1000i` | The value is a fixed memory address |
|
||||||
|
| Register | `RA` | The value is stored in a register |
|
||||||
|
| Indirect | `[0x1000i]` | Go to that address and use what's stored there |
|
||||||
|
| Pointer | `[RA]` | The register holds a memory address, follow it |
|
||||||
|
| Indexed | `[RA + 8i]` | Base register plus a constant offset |
|
||||||
|
| Scaled | `[RA + RB * 4i]` | Base register plus scaled index register |
|
||||||
|
| Displaced | `[RA + RB*4i+2i]` | Full address calculation with two registers |
|
||||||
|
|
||||||
|
Addressing modes are encoded in 5 bits inside each 2-byte instruction.
|
||||||
|
When an instruction has two parameters, those 5 bits are split: 2 bits
|
||||||
|
for the first parameter and 3 bits for the second.
|
||||||
|
|
||||||
|
A modifier suffix is used in assembly syntax to specify the mode explicitly:
|
||||||
|
`.imp`, `.imm`, `.abs`, `.reg`, `.ind`, `.ptr`, `.idx`, `.sca`, `.dis`
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
MOV.reg RA, RB ; move value from RB into RA using registers
|
||||||
|
MOV.ptr RA, [RB] ; move value from address stored in RB into RA
|
||||||
|
MOV.idx RA, [RB + 8i] ; move value from address RB+8 into RA
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Type Size Modifiers
|
||||||
|
|
||||||
|
Every instruction in Spider also carries a **type size modifier** encoded
|
||||||
|
in the last 2 bits of the 2-byte instruction header. This tells the VM
|
||||||
|
how many bytes to read or write when executing the instruction.
|
||||||
|
|
||||||
|
| Type | Modifier | Size |
|
||||||
|
|--------|----------|--------|
|
||||||
|
| Byte | `.B` | 1 byte |
|
||||||
|
| Short | `.S` | 2 bytes|
|
||||||
|
| Int | `.I` | 4 bytes|
|
||||||
|
| Long | `.L` | 8 bytes|
|
||||||
|
| Float | `.F` | 4 bytes|
|
||||||
|
| Double | `.D` | 8 bytes|
|
||||||
|
|
||||||
|
The type modifier applies to the **entire instruction**, meaning both
|
||||||
|
operands must be congruent. You cannot mix sizes in a single instruction.
|
||||||
|
|
||||||
|
This system is directly tied to Spider's philosophy of **strong typing**:
|
||||||
|
the programmer always knows exactly how much memory an operation consumes.
|
||||||
|
There are no hidden conversions or surprises.
|
||||||
|
|
||||||
|
Two important behaviors to understand:
|
||||||
|
|
||||||
|
**Reading a smaller size** ignores the top bits of the value. For example,
|
||||||
|
reading a register containing `0x12345678` with `.B` gives you `0x78`.
|
||||||
|
|
||||||
|
**Writing a smaller size** only modifies the lower bytes and leaves the
|
||||||
|
top bits untouched. Writing `0xAB` with `.B` into that same register
|
||||||
|
gives you `0x123456AB`.
|
||||||
|
|
||||||
|
A complete instruction combining all three systems looks like this:
|
||||||
|
```
|
||||||
|
MOV.I.reg RA, RB ; move a 4-byte integer from RB into RA
|
||||||
|
MOV.B.ptr RA, [RB] ; move 1 byte from the address in RB into RA
|
||||||
|
ADD.L.idx RA, [RB + 8i] ; add 8-byte value at address RB+8 into RA
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How these three systems connect
|
||||||
|
|
||||||
|
These three concepts are not independent. Every instruction in Spider
|
||||||
|
uses all three simultaneously:
|
||||||
|
|
||||||
|
- The **op code** (9 bits) says what to do
|
||||||
|
- The **addressing mode** (5 bits) says where the data is
|
||||||
|
- The **type modifier** (2 bits) says how big the data is
|
||||||
|
|
||||||
|
Together they fit in exactly 2 bytes per instruction, followed by the
|
||||||
|
actual parameter data. This compact design is what makes Spider efficient
|
||||||
|
enough to run on microcontrollers with very limited memory.
|
||||||
@@ -37,7 +37,7 @@ and the result value `42` is successfully collected from `RA`.
|
|||||||
|
|
||||||
**Function signature:** `result = func(a, b, c, d, e, f, g, h)`
|
**Function signature:** `result = func(a, b, c, d, e, f, g, h)`
|
||||||
|
|
||||||
**Output**
|
**Output:**
|
||||||
```
|
```
|
||||||
===== TEST 2: 8 arguments, some on the stack =====
|
===== TEST 2: 8 arguments, some on the stack =====
|
||||||
=== State after do_function_call ===
|
=== State after do_function_call ===
|
||||||
@@ -67,7 +67,6 @@ all stack entries are cleaned and the result `99` is collected from `RA`.
|
|||||||
|
|
||||||
**Output:**
|
**Output:**
|
||||||
```
|
```
|
||||||
|
|
||||||
===== TEST 3: Boolean arguments =====
|
===== TEST 3: Boolean arguments =====
|
||||||
=== State after do_function_call ===
|
=== State after do_function_call ===
|
||||||
Registers used: ['RA', 'RB']
|
Registers used: ['RA', 'RB']
|
||||||
|
|||||||
Reference in New Issue
Block a user