Virtual Machine
The Devlish VM (devlish_vm) is a register-based bytecode interpreter that executes compiled .dvlc.json packages. It delegates all I/O to a host through the HostEffects trait.
Execution Model
Section titled “Execution Model”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
devlish run program.dvlc.json --input '{"customer_tier": "priority"}'HostEffects Trait
Section titled “HostEffects Trait”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 accessThe native CLI implements all effects. The WASM host implements only safe effects (emit_event, respond). Unimplemented effects return an error string.
Instruction Limit
Section titled “Instruction Limit”The VM enforces a maximum instruction count to prevent infinite loops:
Default: 10,000,000 instructionsWhen the limit is reached, execution stops with an error. The limit is configurable:
// In devlish-runtimeconst tool = await loadTool({ bytecode: compiled, instructionLimit: 5000000});Event Emission
Section titled “Event Emission”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.
Try/Catch Frames
Section titled “Try/Catch Frames”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 occurredWhen an error occurs inside a try block, the VM:
- Pops the try frame
- Stores the error message in
last_error - Jumps to the handler address
- Continues execution in the Otherwise block
Try frames nest. An error propagates to the nearest enclosing frame.
Checkpoint Handling
Section titled “Checkpoint Handling”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.
Permission Enforcement
Section titled “Permission Enforcement”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.
Result Structure
Section titled “Result Structure”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.