Skip to content

Virtual Machine

The virtual machine (VM) executes the instructions produced by the Devlish compiler. It holds program values, follows branches and loops, and asks the surrounding application to perform operations such as reading a file. That surrounding application is called the host.

This separation lets the same bytecode be used in native and browser environments while each host controls the external operations it supports. This page explains execution state, host effects, and limits; read the Architecture Overview for how compilation and execution fit together.

The VM keeps the values and bookkeeping needed to execute a program one instruction at a time. These internal structures explain how it follows branches, recovers from errors, and reports results.

The VM maintains:

  • Constant pool: Immutable values referenced by index
  • Registers: Named temporary storage for intermediate values
  • Context: Input variables and program state (a JSON object)
  • Program counter: Current instruction index
  • Try stack: Nested error recovery frames
  • Events: Execution trace for debugging and observability
Terminal window
devlish run program.dvlc.json --input '{"customer_tier": "priority"}'

All side effects are delegated to the host through a trait interface:

HostEffects:
emit_event(event) - Execution tracing
write_file(request) - File output
read_file(request) - File input (JSON, CSV, text)
http_request(method, url, body, headers) - HTTP calls
call_service(request) - External service integration
respond(value) - Structured output
file_copy(src, dst) - Filesystem copy
file_move(src, dst) - Filesystem move
file_mkdir(path) - Create directory
file_delete(path) - Remove file/directory
file_exists(path) - Check existence
file_stat(path) - Get file metadata
file_list(path) - List directory
file_glob(pattern, dir) - Glob search
resolve_credential(key) - Secure credential access

The native CLI implements all effects. The WASM host implements only safe effects (emit_event, respond). Unimplemented effects return an error string.

The VM enforces a maximum instruction count to prevent infinite loops:

Default: 10,000,000 instructions

When the limit is reached, execution stops with an error. The limit is configurable:

// In devlish-runtime
const tool = await loadTool({
bytecode: compiled,
instructionLimit: 5000000
});

The VM emits structured events during execution for tracing and debugging:

Event Fired When
run_started Execution begins
variable_assigned A variable receives a value
effect_requested A host effect is about to execute
effect_completed A host effect finished
output_emitted Print or Show produces output
run_finished Execution completes successfully
run_failed Execution terminates with an error

Events can be suppressed for production use or consumed by debugging tools.

The VM uses a frame stack for error recovery. When a TRY_PUSH instruction executes, it registers a handler address:

TRY_PUSH handler=15 ; push frame pointing to instruction 15
... risky operations ...
TRY_POP ; pop frame on success, skip to after handler
; instruction 15: ; handler runs if error occurred

When an error occurs inside a try block, the VM:

  1. Pops the try frame
  2. Stores the error message in last_error
  3. Jumps to the handler address
  4. Continues execution in the Otherwise block

Try frames nest. An error propagates to the nearest enclosing frame.

When the VM encounters a CHECKPOINT instruction, it pauses execution and returns the checkpoint data to the caller:

{
"success": true,
"checkpoint": {
"message": "Review before continuing",
"context": {"amount": 50000}
}
}

The program can be resumed later with new input merged into context.

If the bytecode package includes a manifest with permissions, the VM checks every effect against the declared list before dispatching to the host. A mismatch produces a “Permission denied” error that can be caught by Try blocks.

On completion, the VM returns:

{
"success": true,
"context": {"all": "variables"},
"results": {"validations": "..."},
"events": [{"type": "run_started"}, "..."]
}

Failed runs include the error message and any events emitted before failure.