bquarkz 565fc0e451 dev/pbs (#8)
Co-authored-by: Nilton Constantino <nilton.constantino@visma.com>
Reviewed-on: #8
2026-02-03 15:28:30 +00:00

12 KiB

< Back | Summary | Next >

⚙️ ** PVM (PROMETEU VM) — Instruction Set**

1. Overview

The PROMETEU VM is a mandatory virtual machine always present in the logical hardware:

  • stack-based
  • deterministic
  • cycle-oriented
  • designed for teaching and inspection

It exists to:

  • map high-level language concepts
  • make computational cost visible
  • allow execution analysis
  • serve as the foundation of the PROMETEU cartridge

The PROMETEU VM is simple by choice. Simplicity is a pedagogical tool.


2. Execution Model

2.1 Main Components

The VM has:

  • PC (Program Counter) — next instruction
  • Operand Stack — value stack
  • Call Stack — stores execution frames for function calls
  • Scope Stack — stores frames for blocks within a function
  • Heap — dynamic memory
  • Globals — global variables
  • Constant Pool — literals and references
  • ROM — cartridge bytecode
  • RAM — mutable data

2.2 Execution Cycle

Each instruction executes:

FETCH → DECODE → EXECUTE → ADVANCE PC

Properties:

  • every instruction has a fixed cost in cycles
  • there is no invisible implicit work
  • execution is fully deterministic

3. Fundamental Types

Type Description
int32 32-bit signed integer
int64 64-bit signed integer
float 64-bit floating point
boolean true/false
string immutable UTF-8
null absence of value
ref heap reference

3.1 Numeric Promotion

The VM promotes types automatically during operations:

  • int32 + int32int32
  • int32 + int64int64
  • int + floatfloat
  • Bitwise operations promote int32 to int64 if any operand is int64.

Do not exist:

  • magic coercions
  • implicit casts
  • silent overflows

4. Stack Conventions & Calling ABI

  • Operations use the top of the stack.
  • Results always return to the stack.
  • LIFO Order: Last pushed = first consumed.
  • Mandatory Return: Every function (Call) and Syscall MUST leave exactly one value on the stack upon completion. If there is no meaningful value to return, Null must be pushed.

4.1 Calling Convention (Call / Ret)

  1. Arguments: The caller pushes arguments in order (arg0, arg1, ..., argN-1).
  2. Execution: The Call instruction specifies args_count. These N values become the locals of the new frame (local 0 = arg0, local 1 = arg1, etc.).
  3. Return Value: Before executing Ret, the callee MUST push its return value.
  4. Cleanup: The Ret instruction is responsible for:
    • Popping the return value.
    • Removing all locals (the arguments) from the operand stack.
    • Re-pushing the return value.
    • Restoring the previous frame and PC.

4.2 Syscall Convention

  1. Arguments: The caller pushes arguments in order.
  2. Execution: The native implementation pops arguments as needed. Since it's a stack, it will pop them in reverse order (argN-1 first, then argN-2, ..., arg0).
  3. Return Value: The native implementation MUST push exactly one value onto the stack before returning to the VM.
  4. Cleanup: The native implementation is responsible for popping all arguments it expects.

Example:

PUSH_CONST 3
PUSH_CONST 4
ADD

State:

[3]
[3, 4]
[7]

5. Instruction Categories

  1. Flow control
  2. Stack
  3. Arithmetic and logic
  4. Variables
  5. Functions
  6. Heap and structures
  7. Peripherals (syscalls)
  8. System

6. Instructions — VM Set 1

6.1 Execution Control

Instruction Cycles Description
NOP 1 Does nothing
HALT 1 Terminates execution
JMP addr 2 Unconditional jump
JMP_IF_FALSE addr 3 Jumps if top is false
JMP_IF_TRUE addr 3 Jumps if top is true

6.2 Stack

Instruction Cycles Description
PUSH_CONST k 2 Pushes constant
POP 1 Removes top
DUP 1 Duplicates top
SWAP 1 Swaps two tops
PUSH_I32 v 2 Pushes 32-bit int
PUSH_I64 v 2 Pushes 64-bit int
PUSH_F64 v 2 Pushes 64-bit float
PUSH_BOOL v 2 Pushes boolean

6.3 Arithmetic

Instruction Cycles
ADD 2
SUB 2
MUL 4
DIV 6

6.4 Comparison and Logic

Instruction Cycles
EQ 2
NEQ 2
LT 2
GT 2
LTE 2
GTE 2
AND 2
OR 2
NOT 1
BIT_AND 2
BIT_OR 2
BIT_XOR 2
SHL 2
SHR 2
NEG 1

6.5 Variables

Instruction Cycles Description
GET_GLOBAL i 3 Reads global
SET_GLOBAL i 3 Writes global
GET_LOCAL i 2 Reads local
SET_LOCAL i 2 Writes local

6.6 Functions

Instruction Cycles Description
CALL <u32 func_id> 5 Saves PC and creates a new call frame
RET 4 Returns from function, restoring PC
PUSH_SCOPE 3 Creates a scope within the current function
POP_SCOPE 3 Removes current scope and its local variables

ABI Rules for Functions:

  • func_id: A 32-bit index into the final FunctionTable, assigned by the compiler linker at build time.
  • Mandatory Return Value: Every function MUST leave exactly one value on the stack before RET. If the function logic doesn't return a value, it must push null.
  • Stack Cleanup: RET automatically clears all local variables (based on stack_base) and re-pushes the return value.

6.7 Heap

Instruction Cycles Description
ALLOC size 10 Allocates on heap
LOAD_REF off 3 Reads field
STORE_REF off 3 Writes field

Heap is:

  • finite
  • monitored
  • accounted for in the CAP

6.8 Peripherals (Syscalls)

Instruction Cycles Description
SYSCALL id variable Call to hardware

ABI Rules for Syscalls:

  • Argument Order: Arguments must be pushed in the order they appear in the call (LIFO stack behavior).
    • Example: gfx.draw_rect(x, y, w, h, color) means:
      1. PUSH x
      2. PUSH y
      3. PUSH w
      4. PUSH h
      5. PUSH color
      6. SYSCALL 0x1002
  • Consumption: The native function MUST pop all its arguments from the stack.
  • Return Value: If the syscall returns a value, it will be pushed onto the stack by the native implementation. If not, the stack state for the caller remains as it was before pushing arguments.

Implemented Syscalls (v0.1)

ID Name Arguments (Stack) Return
0x0001 system.has_cart - bool
0x0002 system.run_cart - -
0x1001 gfx.clear color_idx -
0x1002 gfx.draw_rect x, y, w, h, color_idx -
0x1003 gfx.draw_line x1, y1, x2, y2, color_idx -
0x1004 gfx.draw_circle xc, yc, r, color_idx -
0x1005 gfx.draw_disc xc, yc, r, b_col, f_col -
0x1006 gfx.draw_square x, y, w, h, b_col, f_col -
0x2001 input.get_pad button_id bool
0x3001 audio.play s_id, v_id, vol, pan, pitch -

Button IDs:

  • 0: Up, 1: Down, 2: Left, 3: Right
  • 4: A, 5: B, 6: X, 7: Y
  • 8: L, 9: R
  • 10: Start, 11: Select

7. Execution Errors

Errors are:

  • explicit
  • fatal
  • never silent

Types:

  • stack underflow
  • invalid type
  • invalid heap
  • invalid frame

Generate:

  • clear message
  • state dump
  • stack trace

8. Determinism

Guarantees:

  • same input → same result
  • same sequence → same cycles
  • no speculative execution
  • no invisible optimizations

If you see the instruction, you pay for it.


9. Relationship with Languages

Java, TypeScript, Lua etc:

  • are source languages
  • compiled to this bytecode
  • never executed directly

All run on the same VM.


10. Example

Source:

x = 3 + 4;

Bytecode:

PUSH_CONST 3
PUSH_CONST 4
ADD
SET_GLOBAL 0

Cost:

2 + 2 + 2 + 3 = 9 cycles

11. Execution per Tick

The VM does not run infinitely.

It executes:

  • until consuming the logical frame budget
  • or until HALT

The budget is defined by the PROMETEU logical hardware (e.g., CYCLES_PER_FRAME).

Example:

vm.step_budget(10_000)

This feeds:

  • CAP
  • profiling
  • certification

12. Logical Frame and FRAME_SYNC

PROMETEU defines logical frame as the minimum unit of consistent game update.

  • Input is latched per logical frame (does not change until the logical frame is completed).
  • The cycle budget is applied to the logical frame.
  • A new logical frame only starts when the current frame ends.

12.1 System Instruction: FRAME_SYNC

The FRAME_SYNC instruction marks the end of the logical frame.

Instruction Cycles Description
FRAME_SYNC 1 Finalizes the current logical frame

Properties:

  • FRAME_SYNC is a system instruction.
  • It should not be exposed as a "manual" API to the user.
  • Tooling/compiler can inject FRAME_SYNC automatically at the end of the main loop.

12.2 Semantics (what happens when it executes)

When executing FRAME_SYNC, the core:

  1. Finalizes the current logical frame.
  2. Presents the frame (gfx.present() or gfx.compose_and_present() depending on the GFX model).
  3. Releases the input latch.
  4. Resets the budget for the next logical frame.

12.3 Overbudget (when the frame doesn't finish on time)

If the logical frame budget runs out before the VM reaches FRAME_SYNC:

  • the VM pauses (PC and stacks remain at the exact point)
  • there is no present
  • the input latch is maintained
  • on the next host tick, the VM continues from where it left off, still in the same logical frame

Practical effect:

  • if the code needs 2 budgets to reach FRAME_SYNC, the game updates at ~30 FPS (logical frame takes 2 ticks)
  • this is deterministic and reportable in the CAP

15. Extensibility

The Instruction Set is versioned.

Future:

  • DMA
  • streaming
  • vectors
  • fictitious coprocessors

No existing instruction changes its meaning.


16. Summary

  • stack-based VM
  • explicit cost
  • deterministic execution
  • integrated with CAP
  • foundation of every PROMETEU cartridge

< Back | Summary | Next >