Fifth - virtual machine opcodes 0 – 9
Table of Contents
1. Overview
This document describes virtual machine opcodes 0 through 9. These are the fundamental instructions for program flow control, stack manipulation, and basic I/O operations.
Quick Reference:
| Opcode | Name | Stack Effect | Bytecode Size | Purpose |
|---|---|---|---|---|
| 0 | nop | -- | 1 byte | Do nothing (placeholder) |
| 1 | halt | -- | 1 byte | Exit emulator |
| 2 | kbd@ | – scancode | 1 byte | Read keyboard input |
| 3 | num | – n | 5 bytes | Push literal 32-bit number |
| 4 | jmp | -- | 5 bytes | Unconditional jump |
| 5 | call | – (R: – ret) | 5 bytes | Call subroutine |
| 6 | 1+ | n – n+1 | 1 byte | Increment by 1 |
| 7 | 1- | n – n-1 | 1 byte | Decrement by 1 |
| 8 | dup | n – n n | 1 byte | Duplicate top of stack |
| 9 | drop | n -- | 1 byte | Discard top of stack |
2. 0: nop (No Operation)
| Property | Value |
|---|---|
| Opcode | 0 |
| Name | nop |
| Stack | ( – ) |
| Bytecode | Single byte: 00 |
Description
Does nothing. Execution continues immediately to the next instruction. This opcode is handled as the entry point to the emulator's instruction fetch loop, meaning it effectively "falls through" to the next opcode.
When to Use:
- Padding bytecode for alignment purposes
- Placeholder during development or debugging
- Safe target for jump instructions when you need a "do nothing" branch
- Filling unused entries in jump tables
Example:
00 ; nop - do nothing 03 05 00 00 00 ; num 5 - push 5 onto stack
No observable effect. The instruction pointer simply advances to the next opcode.
3. 1: halt (Stop Execution)
| Property | Value |
|---|---|
| Opcode | 1 |
| Name | halt |
| Stack | ( – ) |
| Bytecode | Single byte: 01 |
3.1. Description
Terminates the emulator process cleanly. This is the only proper way to exit the Fifth environment. When executed, the emulator:
- Restores text video mode (mode 3)
- Closes the virtual disk file handle
- Restores the original keyboard interrupt handler
- Returns control to DOS
3.2. When to Use
- Terminating the program cleanly and exiting the Fifth environment
- Implementing a "quit" or "bye" command in REPL
- Shutting down after fatal errors
3.3. Example
01 ; halt - exit emulator immediately
In Fifth source code, this is typically invoked via the bye word,
which compiles to the halt opcode.
4. 2: kbd@ (Keyboard Input)
| Property | Value |
|---|---|
| Opcode | 2 |
| Name | kbd@ |
| Stack | ( – scancode ) |
| Bytecode | Single byte: 02 |
4.1. Description
Reads a keyboard scancode from the internal ring buffer and pushes it onto the data stack. This is a non-blocking operation: if no key is pending, it pushes 0 immediately.
4.2. Scan Code Behavior
The keyboard hardware generates scan codes for every key event:
- Make code: Generated when a key is pressed (values 1-127)
- Break code: Generated when a key is released (make code + 128)
For example:
- Pressing 'A' produces scan code
1E - Releasing 'A' produces scan code
9E(1E + 80 hex)
4.3. Common Scan Codes (US QWERTY)
| Key | Make Code | Break Code |
|---|---|---|
| Escape | 01 | 81 |
| 1-9 | 02-0A | 82-8A |
| 0 | 0B | 8B |
| Enter | 1C | 9C |
| Space | 39 | B9 |
| Left Arrow | 4B | CB |
| Right Arrow | 4D | CD |
| Up Arrow | 48 | C8 |
| Down Arrow | 50 | D0 |
| A | 1E | 9E |
| Z | 2C | AC |
4.4. When to Use
- Building interactive programs (games, text editors)
- Reading raw keyboard input without translation to characters
- Detecting key press/release events separately
- Implementing custom keyboard drivers or input handlers
4.5. Example
Basic key reading:
02 ; kbd@ - read scancode
; data stack now contains: scancode (or 0 if no key)
4.6. Return Values
- Returns
0if no key is available in the buffer - Returns scancode (1-255) when a key event is pending
- Each call consumes one scancode from the buffer
4.7. Important Notes
- This returns raw scan codes, not ASCII characters
- Use a keyboard layout file (like
5TH_KBD_US) to translate scan codes to FSCII characters - The buffer holds 128 scan codes; older codes are overwritten if full
5. 3: num (Push Literal Number)
| Property | Value |
|---|---|
| Opcode | 3 |
| Name | num |
| Stack | ( – n ) |
| Bytecode | 03 followed by 4 bytes (little-endian) |
5.1. Description
Pushes a 32-bit literal value onto the data stack. The value is encoded directly in the instruction stream immediately after the opcode byte. After execution, the instruction pointer advances by 4 bytes.
5.2. Bytecode Format
03 xx xx xx xx │ └──────────┘ 32-bit value (little-endian) └ opcode
5.3. When to Use
- Loading constant values for calculations
- Pushing memory addresses onto the stack
- Setting up parameters for function calls
- Initializing counters, pointers, and loop variables
5.4. Example
Push the value 0x12345678:
03 ; num opcode
78 56 34 12 ; 32-bit value 0x12345678 (little-endian)
; stack now contains: 0x12345678
In Fifth source code, simply write a number:
12345678 ; Compiles to: 03 78 56 34 12 FF ; Compiles to: 03 FF 00 00 00 1000 ; Compiles to: 03 00 10 00 00
5.5. Important Notes
- Numbers in Fifth source code are hexadecimal by default
- Use
d.to display numbers in decimal for debugging - All 32 bits are pushed; high bytes are zero-filled for small values
- The value is stored in little-endian byte order in bytecode
6. 4: jmp (Unconditional Jump)
| Property | Value |
|---|---|
| Opcode | 4 |
| Name | jmp |
| Stack | ( – ) |
| Bytecode | 04 followed by 4-byte target address |
6.1. Description
Transfers execution unconditionally to the specified address. The target address is embedded in the instruction stream immediately after the opcode. No return address is saved.
6.2. Bytecode Format
04 aa aa aa aa │ └──────────┘ 32-bit target address (little-endian) └ opcode
6.3. When to Use
- Creating infinite loops (combined with conditional exits)
- Skipping over code blocks
- Implementing forward jumps in control structures
- Jump tables and dispatch mechanisms
6.4. Example
Jump to address 0x1000:
04 ; jmp opcode
00 10 00 00 ; target address 0x1000 (little-endian)
; execution continues at 0x1000
Create an infinite loop:
; At address 0x100: 04 00 01 00 00 ; jmp 0x100 - infinite loop
Skip over code:
04 ... ; jmp to skip_target ... ; code to skip ; skip_target:
6.5. Important Notes
- The address is a virtual address in the VM's address space
- The emulator adds
xms_addrto convert to a physical memory address - No return address is saved; use
callfor subroutines
7. 5: call (Call Subroutine)
| Property | Value |
|---|---|
| Opcode | 5 |
| Name | call |
| Stack | ( – ) |
| Return Stack | ( – ret-addr ) |
| Bytecode | 05 followed by 4-byte target address |
7.1. Description
Calls a subroutine at the specified address. The return address (the
address of the instruction immediately following this call) is saved
on the return stack. Use ret (opcode 11) to return to the caller.
7.2. Bytecode Format
05 aa aa aa aa │ └──────────┘ 32-bit target address (little-endian) └ opcode
7.3. When to Use
- Calling reusable code blocks (functions, procedures)
- Implementing Forth word definitions
- Building modular programs with separate subroutines
- Recursion (with proper stack management)
7.4. Example
; Main code at 0x100: 05 00 20 00 00 ; call 0x2000 03 01 00 00 00 ; num 1 (this is where we return to) ; Subroutine at 0x2000: ... ; subroutine code 0B ; ret - return to caller
7.5. Stack Effects
| Stack | Before | After |
|---|---|---|
| Data Stack | … | … |
| Return Stack | … | ret-addr |
The return address is the virtual address immediately after the 4-byte argument (i.e., the address of the next instruction).
7.6. Important Notes
- The return stack is separate from the data stack
- The return stack is also used by
pushandpopopcodes - Always balance
callwithretto avoid return stack corruption - Nesting depth is limited by return stack size
8. 6: 1+ (Increment)
| Property | Value |
|---|---|
| Opcode | 6 |
| Name | 1+ |
| Stack | ( n – n+1 ) |
| Bytecode | Single byte: 06 |
8.1. Description
Adds 1 to the value on top of the data stack, modifying it in place. This is a highly efficient single-byte operation.
8.2. When to Use
- Incrementing loop counters
- Advancing pointers through arrays or buffers
- Computing successor values
- Any situation where you need to add exactly 1
8.3. Example
03 05 00 00 00 ; num 5 - stack: [ 5 ] 06 ; 1+ - stack: [ 6 ] 06 ; 1+ - stack: [ 7 ]
8.4. Efficiency Comparison
| Operation | Bytecode | Size |
|---|---|---|
| 1+ | 06 |
1 byte |
| 1 + | 03 01 00 00 00 18 |
6 bytes |
Use 1+ instead of 1 + for better code density.
8.5. Important Notes
- Modifies the value in place (no additional stack space needed)
- Works identically on signed and unsigned 32-bit values
- For adding larger values, use
+(opcode 24)
9. 7: 1- (Decrement)
| Property | Value |
|---|---|
| Opcode | 7 |
| Name | 1- |
| Stack | ( n – n-1 ) |
| Bytecode | Single byte: 07 |
9.1. Description
Subtracts 1 from the value on top of the data stack, modifying it in place. This is a highly efficient single-byte operation.
9.2. When to Use
- Decrementing loop counters
- Moving pointers backward through arrays or buffers
- Computing predecessor values
- Counting down to zero
9.3. Example
03 0A 00 00 00 ; num 10 - stack: [ 10 ] 07 ; 1- - stack: [ 9 ] 07 ; 1- - stack: [ 8 ]
9.4. Common Pattern: Countdown Loop
: loop ( n -- )
begin
dup . ; display counter
1- dup ; decrement and copy for test
0= until ; loop until zero
drop ; clean up
;
A loop ; prints A, 9, 8, ... 1
9.5. Important Notes
- Modifies the value in place (no additional stack space needed)
- Works identically on signed and unsigned 32-bit values
- For subtracting larger values, use
-(opcode 25)
10. 8: dup (Duplicate)
| Property | Value |
|---|---|
| Opcode | 8 |
| Name | dup |
| Stack | ( n – n n ) |
| Bytecode | Single byte: 08 |
10.1. Description
Duplicates the value on top of the data stack, pushing a copy. The original value remains, and an identical copy is placed on top.
10.2. When to Use
- Preserving a value before consuming operations
- Using the same value multiple times in a calculation
- Displaying values without losing them
- Setting up parameters for operations that consume values
10.3. Example
03 2A 00 00 00 ; num 42 - stack: [ 42 ] 08 ; dup - stack: [ 42 42 ]
10.4. Common Patterns
10.4.1. Read and Keep Address
addr dup @ ; stack: [ addr value ]
; read from address while keeping the address
10.4.2. Display and Keep
value dup . ; display value while keeping it on stack
10.4.3. Test Without Consuming
value dup 0= ; test if zero, keep original value if ... ; handle zero case (value still on stack) then drop ; now consume it
10.5. Important Notes
- Increases stack depth by 1
- Often paired with
dropto test values without consuming them - For duplicating the second stack item, use
over(opcode 22)
11. 9: drop (Discard)
| Property | Value |
|---|---|
| Opcode | 9 |
| Name | drop |
| Stack | ( n – ) |
| Bytecode | Single byte: 09 |
11.1. Description
Removes and discards the value from the top of the data stack. The value is completely lost.
11.2. When to Use
- Cleaning up the stack after calculations
- Discarding values that are no longer needed
- Consuming function results you don't need
- Balancing stack operations
11.3. Example
03 2A 00 00 00 ; num 42 - stack: [ 42 ] 03 64 00 00 00 ; num 100 - stack: [ 42 100 ] 09 ; drop - stack: [ 42 ] 09 ; drop - stack: [ ]
11.4. Common Patterns
11.4.1. Discard Unused Return Values
some-operation drop ; call operation, ignore result
11.4.2. Execute for Side Effect Only
addr @ drop ; read from memory, discard value
; useful for prefetching or triggering hardware
11.4.3. Clean Up After Conditional
condition if ... ; condition was true (consumed by if) else ... ; condition was false then
11.5. Important Notes
- Reduces stack depth by 1
- The value is completely lost; ensure you don't need it
- Often paired with
dupfor testing values without consuming them - For discarding two values, use
2drop(defined in high-level Fifth)