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:

  1. Restores text video mode (mode 3)
  2. Closes the virtual disk file handle
  3. Restores the original keyboard interrupt handler
  4. 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 0 if 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_addr to convert to a physical memory address
  • No return address is saved; use call for 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 push and pop opcodes
  • Always balance call with ret to 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 drop to 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 dup for testing values without consuming them
  • For discarding two values, use 2drop (defined in high-level Fifth)

Created: 2026-02-24 Tue 23:35

Validate