# Ideas This is to document some of my crazy ideas for this project. ## Threaded interpreter The normal ways (computed gotos, tail-calls) to optimize VM opcode execution [do not work in Rust](https://pliniker.github.io/post/dispatchers/). Crazy ideas: 1. Each opcode as a closure/function. Still using a loop to call each opcode. 2. Each opcode as a closure that also has a reference to the next opcode. 3. Compile opcodes to nested closures. All opcodes have a static number of inputs and outputs. ### Compile opcodes into closures A closure can be made for each opcode of a function to capture the opcode's parameters (jump target, local/global idx, mem offset, etc..). It could also allow merging multiple opcodes into a single closure. Convert (2 push, 2 pop, 1 push): * GetLocal(0) - push local onto stack. * I64Const(1234) - push constant '1234' onto stack. * I64Add - pop two i64 values, push results (a + b) ```rust /// opcode functions for i64 opcodes. mod i64_ops { fn op_add_local_const(ctx: &Frame, local: u32, const_val: i64) -> Trap<()> { let left = ctx.get_local(local); let right = const_val; let res = left.wrapping_add(right); ctx.push(res) Ok(()) } /// many more "merged" opcode functions..... } ``` The compiler would make a closure: ```rust let local_idx = 0; // decoded from 'GetLocal(0)' op let const_val = 1234; // decoded from 'I64Const(1234)' op let merged_op = move |ctx: &Frame| -> Trap<()> { i64_ops::op_add_local_const(ctx, local_idx, const_val) }; ``` ## Structure * Module - immutable * Function - immutable * CodeBlock - immutable * Instruction - immutable * VM - mutable ### Module An Module defines the static code for a program/script. It contains a list of Functions. Can have an optional `start` Function that is executed before any exported functions can be called. Layout: * Meta data{version, total size, # globals, # functions, min size required to start execution} * Export/globals * One or more Functions. Allow stream execution. ### Function * Type * Locals * Body - Intructions. ### CodeBlock * Meta{# locals, # instructions} ### Instruction * ... many standard ops: Add,Sub,Mul,Load,Store,etc... * Call - Call another Function inside the Module. * TailCall - Optimized version of `Call`. * RustCall - Call non-async Rust function. Easy to use for API bindings. * AsyncCall - Async call. #### Call/TailCall These are optimizations to avoid creating a boxed (heap allocated) future. #### RustCall For simple library functions. Don't allow it to call functions in the Module. #### AsyncCall Need to used a `BoxFuture` to avoid recursive async calls. ```rust use futures::future::{BoxFuture, FutureExt}; fn recursive() -> BoxFuture<'static, ()> { async move { recursive().await; recursive().await; }.boxed() } ``` ### CallStack Rust's async support could be used to make the VM stackless without the need for an internal callstack or C-style coroutine (task/stacklet/etc...). Stack of: * FunctionCtx - VM Function context(function idx, pc, locals) * Future - Rust Future ### VM The execution context for a Module. The VM holds an immutable reference to the Module. ### Stack free execution Loading/executing the Module in a VM should be async. Load: * Create a Module by compiling/loading it from a file. Should support async I/O. * Create a VM from the Module. (VM is mutable, Module is immutable). doesn't block. Call Function: Rust -> VM * VM.call(name). async. Can yield from