Fifth - virtual machine opcodes 10 – 19
Table of Contents
- 1. Overview
- 2. 10: if (Conditional Jump)
- 3. 11: ret (Return from Subroutine)
- 4. 12: c@ (Fetch Byte from Memory)
- 5. 13: c! (Store Byte to Memory)
- 6. 14: push (Move Data to Return Stack)
- 7. 15: pop (Move Data from Return Stack)
- 8. 16: (Unused/Reserved)
- 9. 17: rot (Rotate Three Stack Elements)
- 10. 18: disk@ (Read Disk Sector)
- 11. 19: disk! (Write Disk Sector)
1. Overview
This document describes virtual machine opcodes 10 through 19. These opcodes provide conditional branching, subroutine return, byte-level memory access, stack-to-return-stack operations, stack rotation, and disk I/O.
Quick Reference:
| Opcode | Name | Stack Effect | Bytecode Size | Purpose |
|---|---|---|---|---|
| 10 | if | flag -- | 5 bytes | Conditional jump (jump if zero) |
| 11 | ret | -- | 1 byte | Return from subroutine |
| 12 | c@ | addr – byte | 1 byte | Read byte from memory |
| 13 | c! | byte addr -- | 1 byte | Write byte to memory |
| 14 | push | n -- | 1 byte | Move data to return stack |
| 15 | pop | – n | 1 byte | Move data from return stack |
| 16 | unused | -- | 1 byte | Reserved for future use. Currently acts as nop. |
| 17 | rot | a b c – b c a | 1 byte | Rotate three stack elements |
| 18 | disk@ | sector addr -- | 1 byte | Read 1KB sector from disk |
| 19 | disk! | addr sector -- | 1 byte | Write 1KB sector to disk |
2. 10: if (Conditional Jump)
| Property | Value |
|---|---|
| Opcode | 10 (0x0A) |
| Name | if |
| Stack | ( flag – ) |
| Bytecode | 0A followed by 4-byte target address |
Description:
Pops the top value from the data stack. If the value is zero, jumps to the address embedded in the instruction stream. If the value is non-zero, skips the address and continues with the next instruction.
This "jump if false" semantics is exactly what is needed to jump/skip over code because comparison condition was not met:
#1: <comparison logic, leaves comparison result on the top of data stack> #2: <jmp instruction + pointer to #4 address> #3: <this code will be executed conditionally, if comparison was true> #4: <any other code that follows after IF ... THEN ... block.>
Bytecode Format:
0A aa aa aa aa │ └──────────┘ 32-bit target address (little-endian) └ opcode
When to Use
- Implementing conditional branches in control structures
- Building if-then-else constructs (the "else" branch)
- Loop exit conditions
Important Notes:
- The jump is taken when flag is ZERO (false condition)
- This is actually "jump if false" semantics
- High-level Fifth words like
ifthenelsecompile to this opcode - The address is a virtual address in the VM's address space
3. 11: ret (Return from Subroutine)
| Property | Value |
|---|---|
| Opcode | 11 (0x0B) |
| Name | ret |
| Stack | ( – ) |
| R. Stack | ( ret-addr – ) |
| Bytecode | Single byte: 0B |
Description:
Returns from a subroutine by popping the return address from the
return stack and jumping to it. This is the counterpart to the call
opcode (5).
When to Use:
- Ending subroutine/word definitions
- Early exit from subroutines
Example:
; Subroutine at address 0x2000: 03 2A 00 00 00 ; num 42 - push a value 0B ; ret - return to caller
In Fifth source code:
: myword ( -- n ) 2A \ push 42 ; \ compiles to ret
3.1. Important Notes
- Each
retmust have a matchingcallto maintain return stack balance - Unbalanced call/ret pairs will corrupt the return stack
- The return stack is also used by
pushandpopopcodes - Return addresses are virtual addresses (without xms_addr offset)
4. 12: c@ (Fetch Byte from Memory)
| Property | Value |
|---|---|
| Opcode | 12 (0x0C) |
| Name | c@ (c-fetch) |
| Stack | ( addr – byte ) |
| Bytecode | Single byte: 0C |
Description:
Reads a single byte from the specified memory address and pushes it onto the data stack. The byte value is zero-extended to 32 bits (padded with zeros in the upper 24 bits).
When to Use:
- Reading character data from strings
- Parsing binary data structures
- Reading individual pixels from image buffers
Example:
Read a byte from address 0x1000:
03 00 10 00 00 ; num 0x1000 - push address 0C ; c@ - read byte ; Stack now contains: byte-value (0-255, zero-extended)
Important Notes:
- The byte is zero-extended: reading 0xFF gives 0x000000FF on stack
- The address is a virtual address (xms_addr is added internally)
5. 13: c! (Store Byte to Memory)
| Property | Value |
|---|---|
| Opcode | 13 (0x0D) |
| Name | c! (c-store) |
| Stack | ( byte addr – ) |
| Bytecode | Single byte: 0D |
Description:
Stores a single byte to the specified memory address. The byte value is taken from the low 8 bits of the value on the stack. The upper 24 bits are ignored.
When to Use:
- Writing character data to strings
- Building binary data structures
- Drawing individual pixels to image buffers
Example:
Store byte 0x42 at address 0x1000:
03 42 00 00 00 ; num 0x42 - byte value to store 03 00 10 00 00 ; num 0x1000 - destination address 0D ; c! - store byte ; Memory at 0x1000 now contains 0x42
Important Notes:
- Upper 24 bits are ignored (no error if value > 255)
- The address is a virtual address
6. 14: push (Move Data to Return Stack)
| Property | Value |
|---|---|
| Opcode | 14 (0x0E) |
| Name | push |
| Stack | ( n – ) |
| R. Stack | ( – n ) |
| Bytecode | Single byte: 0E |
Description:
Moves the top value from the data stack to the return stack. This provides temporary storage that doesn't interfere with data stack operations.
When to Use:
- Temporarily saving values during complex stack manipulations
Example:
Save a value for later retrieval:
03 2A 00 00 00 ; num 42 - push value onto data stack 0E ; push - move 42 to return stack ; Data stack is now empty ; Return stack now contains 42 ; ... do other work ... 0F ; pop - retrieve 42 from return stack ; Data stack now contains 42
Important Notes:
- Must be balanced with
popto avoid return stack corruption - Do not mix carelessly with
call/=ret= pairs - The return stack grows downward (like the data stack)
- Popping too many times can corrupt return addresses
7. 15: pop (Move Data from Return Stack)
| Property | Value |
|---|---|
| Opcode | 15 (0x0F) |
| Name | pop |
| Stack | ( – n ) |
| R. Stack | ( n – ) |
| Bytecode | Single byte: 0F |
Description:
Moves the top value from the return stack to the data stack. This is
the inverse of the push opcode.
When to Use:
- Retrieving temporarily saved values
Example:
Retrieve a saved value:
; Assuming return stack contains 42 (from previous 'push') 0F ; pop - move 42 from return stack to data stack ; Data stack now contains 42 ; Return stack top item removed
Important Notes:
- Popping from an empty return stack causes undefined behavior
- Always ensure there's a value on the return stack before calling
pop - Balance every
pushwith a correspondingpop
7.1. Related Opcode: i
The i opcode (31) copies (rather than moves) the top of the return
stack to the data stack, leaving the return stack unchanged.
| Opcode | Action | R. Stack Effect |
|---|---|---|
| i | Copy top to data stack | <unchanged> |
| pop | Move top to data stack | n -- |
8. 16: (Unused/Reserved)
Opcode 16 (0x10) is reserved for future use. Opcode currently behaves as nop instruction.
9. 17: rot (Rotate Three Stack Elements)
| Property | Value |
|---|---|
| Opcode | 17 (0x11) |
| Name | rot |
| Stack | ( n1 n2 n3 – n2 n3 n1 ) |
| Bytecode | Single byte: 11 |
Description
Rotates the top three values on the data stack. The third item from the top moves to the top, while the top two items shift down.
Visual Representation:
Before: [ ... n1 n2 n3 ] (n3 is on top) After: [ ... n2 n3 n1 ] (n1 is now on top)
Rotation direction: the deepest of the three (n1) rotates to the top.
When to Use:
- Accessing values buried deeper on the stack
- Swapping values in multi-item calculations
Example:
03 01 00 00 00 ; num 1 - push n1 03 02 00 00 00 ; num 2 - push n2 03 03 00 00 00 ; num 3 - push n3 ; Stack: [ 1 2 3 ] with 3 on top 11 ; rot ; Stack: [ 2 3 1 ] with 1 on top
Important Notes:
- Requires at least three items on the data stack
- Rotating with fewer items causes undefined behavior
- This is a rotation, not a swap - all three elements move
10. 18: disk@ (Read Disk Sector)
| Property | Value |
|---|---|
| Opcode | 18 (0x12) |
| Name | disk@ (disk-fetch) |
| Stack | ( sector addr – ) |
| Bytecode | Single byte: 12 |
Description:
Reads one sector (1024 bytes) from the virtual disk into memory at the specified address.
Parameters:
- sector (top of stack): Sector number to read from (0-based)
- addr (second): Destination memory address for the data
When to Use:
- Loading program code from disk
- Reading data files
- Accessing filesystem structures
Example:
Read sector 16 into memory at 0x2000:
03 10 00 00 00 ; num 16 - sector number 03 00 20 00 00 ; num 0x2000 - destination address 12 ; disk@ - read sector ; 1024 bytes from sector 16 now at address 0x2000
10.1. Technical Details
- One sector = 1024 bytes (1 KB)
- Sector numbering starts at 0
- Uses DOS file I/O to access the virtual disk image
- The virtual disk is typically a file named
disk.imgor similar
10.2. Important Notes
- The address is a virtual address in the VM's address space
- Reading beyond the end of the disk file may return zeros or cause errors
- The disk is accessed through DOS file handles, not BIOS interrupts
11. 19: disk! (Write Disk Sector)
| Property | Value |
|---|---|
| Opcode | 19 (0x13) |
| Name | disk! (disk-store) |
| Stack | ( addr sector – ) |
| Bytecode | Single byte: 13 |
Description
Writes one sector (1024 bytes) from memory to the virtual disk at the specified sector number.
Parameters:
- addr (top of stack): Source memory address
- sector (second): Sector number to write to (0-based)
When to Use:
- Saving program data to disk
- Modifying filesystem structures
- Persisting state across emulator sessions
Example:
Write memory at 0x2000 to sector 16:
03 00 20 00 00 ; num 0x2000 - source address 03 10 00 00 00 ; num 16 - sector number 13 ; disk! - write sector ; 1024 bytes from address 0x2000 written to sector 16
Technical Details:
- One sector = 1024 bytes (1 KB)
- Data is first copied to a temporary buffer in conventional memory before being written to disk (required for XMS memory access)
- Writing to sector 0 may corrupt the bootloader/kernel
Stack Order Note:
The stack order is ( addr sector -- ), which is reversed from
disk@ ( sector addr -- ). This is because the data source (addr)
is "produced" first, then the destination (sector) is specified.
Important Notes
- The address is a virtual address
- Writing to the disk image is immediate (no buffering)
- Be careful not to overwrite critical sectors (kernel, filesystem)