Expand opcode documentation (0-9): refined descriptions, clarified stack effects...
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 22 Feb 2026 23:36:34 +0000 (01:36 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 22 Feb 2026 23:36:34 +0000 (01:36 +0200)
emulator/opcodes_00_09.asm

index eadc940..bb59fb4 100644 (file)
-; Opcodes 0-9: nop, halt, kbd@, num, jmp, call, inc, dec, dup, drop
-; Opcode 0 (xnop) is handled inline as the emu loop entry point.
-; Opcode 1 (xhalt/quit) is handled in the main emulator file.
+; =============================================================================
+; Virtual CPU Opcodes 0-9: nop, halt, kbd@, num, jmp, call, 1+, 1-, dup, drop
+; =============================================================================
+;
+; This file contains the implementation of virtual machine opcodes 0 through 9.
+; Each opcode handler is responsible for:
+;   1. Performing its operation
+;   2. Jumping back to the emulation loop (emu label in emulator.asm)
+;
+; Register conventions used throughout:
+;   esi    - Virtual CPU instruction pointer (points to next bytecode)
+;   edi    - Virtual CPU data stack pointer (grows downward, pre-decrement)
+;   [resp] - Virtual CPU return stack pointer
+;   es  - Set to 0 for flat memory access
+;   [xms_addr] - Base address of virtual RAM in host memory
+;
+; Data Stack Memory Layout:
+;   The data stack grows DOWNWARD from high memory toward low memory.
+;   edi points to the TOP element (most recently pushed).
+;   To push: decrement edi by 4, then store at [es:edi]
+;   To pop: read from [es:edi], then increment edi by 4
+;
+; Note: Opcode 0 (nop) and Opcode 1 (halt) are handled inline in emulator.asm.
+;   - nop  simply falls through to the next instruction fetch
+;   - halt jumps to the quit routine to exit the emulator
 
-op_02_kbd@:                     ; Read Keyboard: ( -- scancode )
+; =============================================================================
+; Opcode 2: kbd@ (Keyboard Read)
+; =============================================================================
+; Stack effect: ( -- scancode )
+;
+; Reads a keyboard scancode from the internal ring buffer and pushes it onto
+; the data stack. If no key is available, pushes 0.
+;
+; When to use:
+;   - Reading raw keyboard input for games, text editors, or interactive programs
+;   - Building custom keyboard drivers or input handlers
+;   - Detecting specific key presses or releases (each key generates two
+;     scan codes: one for press, one for release)
+;
+; Scancode behavior:
+;   - Each key press generates a make code (scancode 1-127 typically)
+;   - Each key release generates a break code (make code + 128, i.e., high bit set)
+;   - Example: Pressing 'A' might give scancode 1E, releasing gives 9E
+;
+; Returns:
+;   0 if no key is pending
+;   Otherwise, the scancode byte (1-255)
+;
+op_02_kbd@:
        call    KB_read             ; read scancode from keyboard ring buffer into dl
        sub         edi, 4              ; make room on data stack for one element
-       mov         [es:edi], dl        ; push scancode onto data stack
+       mov         [es:edi], dl        ; push scancode onto data stack (as 32-bit value, zero-extended)
 
-    mov     ah, 0bh             ; DOS: check if key waiting in keyboard buffer
+    ; Drain the DOS keyboard buffer to prevent key bleeding into the emulator.
+    ; Without this, keys pressed during emulation would appear in the DOS
+    ; command prompt after exit.
+       mov     ah, 0bh             ; DOS: check if key waiting in keyboard buffer
        int         21h                 ; call DOS interrupt
        cmp         al, 0h              ; al=0 means no key waiting
     je      emu                 ; if no key waiting, return to emulation loop
-    mov     ah, 0               ; BIOS: read key from keyboard buffer
-       int         16h                 ; call BIOS interrupt (consume the key)
+       mov     ah, 0               ; BIOS: read key from keyboard buffer (consume it)
+       int         16h                 ; call BIOS interrupt
        jmp     emu                 ; return to emulation loop
 
-op_03_xnum:                         ; Push Literal: ( -- n )
+; =============================================================================
+; Opcode 3: num (Push Literal Number)
+; =============================================================================
+; Stack effect: ( -- n )
+;
+; Pushes a 32-bit literal value onto the data stack. The value is read from
+; the instruction stream immediately following the opcode.
+;
+; Instruction format: [opcode 03] [32-bit value, little-endian]
+;
+; When to use:
+;   - Pushing constant values for calculations
+;   - Loading addresses into the stack
+;   - Setting up parameters for other operations
+;
+; Example bytecode sequence:
+;   03              ; num opcode
+;   78 56 34 12     ; 32-bit value 0x12345678 (little-endian)
+;   ; Result: stack contains 0x12345678
+;
+
+op_03_xnum:
        mov         edx, dword [es:esi] ; read 32-bit literal from instruction stream
        sub         edi, 4              ; make room on data stack for one element
        mov         [es:edi], edx       ; push the literal value onto data stack
-       add         esi, 4              ; advance instruction pointer past the 32-bit literal
+       add         esi, 4              ; advance instruction pointer past the literal
        jmp         emu                 ; return to emulation loop
 
-op_04_xjmp:                     ; Unconditional Jump: ( -- )
+; =============================================================================
+; Opcode 4: jmp (Unconditional Jump)
+; =============================================================================
+; Stack effect: ( -- )
+;
+; Jumps unconditionally to the specified address. The target address is read
+; from the instruction stream immediately following the opcode.
+;
+; Instruction format: [opcode 04] [32-bit target address, little-endian]
+;
+; When to use:
+;   - Creating infinite loops
+;   - Skipping over code blocks
+;   - Implementing control flow structures (loops, conditionals with if)
+;
+; Example bytecode sequence:
+;   04              ; jmp opcode
+;   00 10 00 00     ; target address 0x1000
+;   ; Result: instruction pointer set to 0x1000
+;
+; Note: The address is a VIRTUAL address in the virtual CPU's address space.
+; The emulator converts it to a physical address internally.
+;
+op_04_xjmp:
        mov         esi, dword [es:esi] ; read 32-bit target address from instruction stream
        add         esi, [xms_addr]     ; convert virtual address to physical address
        jmp         emu                 ; return to emulation loop
 
-op_05_xcall:                    ; Call Subroutine: ( -- ) R:( -- ret-addr )
+; =============================================================================
+; Opcode 5: call (Call Subroutine)
+; =============================================================================
+; Stack effect: ( -- )  Return stack: ( -- ret-addr )
+;
+; Calls a subroutine at the specified address. The return address (address of
+; the instruction after the call) is pushed onto the return stack.
+;
+; Instruction format: [opcode 05] [32-bit target address, little-endian]
+;
+; When to use:
+;   - Calling reusable code blocks (functions, procedures)
+;   - Implementing Forth words that call other words
+;   - Building modular programs with subroutines
+;
+; Example bytecode sequence:
+;   05              ; call opcode
+;   00 20 00 00     ; target address 0x2000
+;   ...             ; code here is skipped, return address points here
+;
+; After execution:
+;   - Return stack has the return address pushed
+;   - Instruction pointer is at the target address
+;   - Use 'ret' (opcode 11) to return
+;
+; Note: The return stack is separate from the data stack and is used
+; exclusively for return addresses and loop control variables.
+;
+op_05_xcall:
        mov         edx, dword [es:esi] ; read 32-bit target address from instruction stream
        mov         eax, [resp]         ; load return stack pointer
        sub         eax, 4              ; make room on return stack for one element
@@ -40,50 +160,99 @@ op_05_xcall:                    ; Call Subroutine: ( -- ) R:( -- ret-addr )
        add         esi, [xms_addr]     ; convert virtual address to physical address
        jmp         emu                 ; return to emulation loop
 
-op_06_xinc:                     ; Increment: ( n -- n+1 )
-; Increments the top of the data stack by 1.
-
+; =============================================================================
+; Opcode 6: 1+ (Increment)
+; =============================================================================
+; Stack effect: ( n -- n+1 )
+;
+; Adds 1 to the value on top of the data stack, modifying it in place.
+;
+; When to use:
+;   - Incrementing counters and loop indices
+;   - Advancing pointers through arrays or buffers
+;   - Computing successor values
+;
+; Example:
+;   Stack before:  [ ... 5 ]
+;   After 1+:      [ ... 6 ]
+;
+; Note: This is an efficient single-increment operation. For adding larger
+; values, use the '+' (add) opcode instead.
+;
+op_06_xinc:
        inc         dword [es:edi]      ; increment the top of the data stack in place
        jmp         emu                 ; return to emulation loop
 
-op_07_xdec:                     ; Decrement: ( n -- n-1 )
+; =============================================================================
+; Opcode 7: 1- (Decrement)
+; =============================================================================
+; Stack effect: ( n -- n-1 )
 ;
-; Decrements the top of the data stack by 1.
+; Subtracts 1 from the value on top of the data stack, modifying it in place.
 ;
-; Memory layout before execution:
-;   [es:edi] = n
+; When to use:
+;   - Decrementing counters and loop indices
+;   - Moving pointers backward through arrays or buffers
+;   - Computing predecessor values
 ;
-; Memory layout after execution:
-;   [es:edi] = n-1
-
+; Example:
+;   Stack before:  [ ... 10 ]
+;   After 1-:      [ ... 9 ]
+;
+; Note: This is an efficient single-decrement operation. For subtracting
+; larger values, use the '-' (subtract) opcode instead.
+;
+op_07_xdec:
        dec         dword [es:edi]      ; decrement the top of the data stack in place
        jmp         emu                 ; return to emulation loop
 
-op_08_xdup:                     ; Duplicate: ( n -- n n )
+; =============================================================================
+; Opcode 8: dup (Duplicate)
+; =============================================================================
+; Stack effect: ( n -- n n )
 ;
-; Duplicates the top element of the data stack.
+; Duplicates the value on top of the data stack, pushing a copy.
 ;
-; Memory layout before execution:
-;   [es:edi] = n
+; When to use:
+;   - Preserving a value before modifying it
+;   - Using the same value multiple times in a calculation
+;   - Displaying or logging values without removing them
 ;
-; Memory layout after execution:
-;   [es:edi]   = n (new top)
-;   [es:edi+4] = n (original top)
-
+; Example:
+;   Stack before:  [ ... 42 ]
+;   After dup:     [ ... 42 42 ]
+;
+; Common patterns:
+;   - dup @ : read from an address while keeping the address on stack
+;   - dup . : display a value while keeping it for further use
+;   - dup 1+ : create a copy and increment the copy
+;
+op_08_xdup:
        mov         eax, [es:edi]       ; copy top element to eax
        sub         edi, 4              ; make room on data stack for one element
        mov         [es:edi], eax       ; push copy of top element onto stack
        jmp         emu                 ; return to emulation loop
 
-op_09_xdrop:                    ; Drop: ( n -- )
+; =============================================================================
+; Opcode 9: drop (Discard)
+; =============================================================================
+; Stack effect: ( n -- )
 ;
-; Removes the top element from the data stack.
+; Removes the value from the top of the data stack, discarding it.
 ;
-; Memory layout before execution:
-;   [es:edi] = n
+; When to use:
+;   - Discarding values that are no longer needed
+;   - Cleaning up the stack after a calculation
+;   - Consuming values from operations whose results aren't needed
 ;
-; Memory layout after execution:
-;   Stack pointer is adjusted; n is no longer on stack
-
-       add         edi, 4              ; pop top element off the stack
+; Example:
+;   Stack before:  [ ... 100 42 ]
+;   After drop:    [ ... 100 ]
+;
+; Common patterns:
+;   - addr @ drop : read from memory, discard result (side effect only)
+;   - Used after conditionals when the test result is no longer needed
+;
+op_09_xdrop:
+       add         edi, 4              ; pop top element off the data stack
        jmp         emu                 ; return to emulation loop