English · 한국어
Run dynamic logic inside your Go application without giving up control over performance, resources, or host integration.
- Bounded execution — limit stack, heap, call depth, fuel, hooks, and context.
- Direct host integration — call Go through typed, reflection-free host functions.
- Adaptive performance — start in a threaded interpreter and promote hot ARM64 functions and loops to native code.
go get github.com/siyul-park/minivmRequires Go 1.26.2+. The VM core uses only the Go standard library.
Build and run a bytecode program that calculates 6 × 7:
prog := program.New([]instr.Instruction{
instr.New(instr.I32_CONST, 6),
instr.New(instr.I32_CONST, 7),
instr.New(instr.I32_MUL),
})
vm := interp.New(prog)
defer vm.Close()
if err := vm.Run(context.Background()); err != nil {
log.Fatal(err)
}
result, _ := vm.Pop() // types.I32(42)minivm keeps the execution model explicit: bytecode in, controlled runtime, typed value out.
| Capability | What it gives you |
|---|---|
| Embeddable runtime | First-class functions, locals, globals, closures, refs, strings, arrays, structs, maps, coroutines, and structured errors |
| Host integration | Typed HostFunction calls plus Marshal and Unmarshal for ordinary Go values |
| Resource control | Stack, heap, frame, fuel, context, hook, and debugger controls |
| Fast baseline | Closure-threaded dispatch with low steady-state allocation on core workloads |
| Hot-path acceleration | Adaptive ARM64 trace JIT for supported functions and loops |
| Safe admission | Static bytecode verification before execution |
- Scripting engines that execute user-defined behavior under host policy
- Rule engines that change runtime decisions without redeployment
- DSL runtimes that need a compact execution layer
- Plugin systems that isolate extension logic from the host application
Expose Go behavior through a typed host function:
lookup := interp.NewHostFunction(
&types.FunctionType{
Params: []types.Type{types.TypeI32},
Returns: []types.Type{types.TypeI32},
},
func(vm *interp.Interpreter, params []types.Boxed) ([]types.Boxed, error) {
price := db.GetPrice(int(params[0].I32()))
return []types.Boxed{types.BoxI32(price)}, nil
},
)Parameters and results stay in typed []types.Boxed values. The direct path does
not require reflection or interface{} boxing.
See Host Integration for marshaling, host objects, and lifetime rules.
minivm is designed to be useful before JIT compilation and faster when repeated execution makes native traces worthwhile.
Representative medians measured July 15, 2026, on Apple M4 Pro,
darwin/arm64, Go 1.26.2 (ns/op, lower is better):
| Runtime | Iterative Fib (30) | Recursive Fib (35) | Sieve (256) | Branch Tree (96) |
|---|---|---|---|---|
| native Go | 8.337 | 20,957,448 | 247.8 | 77.39 |
| wazero | 49.84 | 46,785,131 | 645.4 | 156.9 |
| minivm/default | 71.83 | 48,426,669 | 5,052 | 228.0 |
| minivm/threaded | 730.9 | 512,675,498 | 15,385 | 986.4 |
minivm/default uses the adaptive ARM64 trace-JIT policy. Results vary by
workload: unsupported paths remain in the threaded interpreter, and some
workloads do not benefit from tracing yet.
See Benchmarks for the full matrix, memory results, measurement boundaries, and reproduction commands.
if err := program.Verify(prog); err != nil {
log.Fatal(err)
}The verifier rejects malformed control flow, invalid stack behavior, and type
mismatches before execution. The run CLI verifies loaded programs by default.
prog, err := optimize.New(optimize.O2).Optimize(prog)Optimization levels range from local constant folding and deduplication to dead-code elimination and cross-block global value numbering.
vm := interp.New(prog,
interp.WithStack(4096),
interp.WithHeap(512),
interp.WithFrame(256),
interp.WithFuel(10_000),
interp.WithThreshold(4096),
interp.WithTick(128),
)Use hooks for policy checks and NewDebugger with WithDebugger for
instruction-accurate breakpoints and stepping.
Program -> verifier / optimizer -> threaded interpreter -> ARM64 trace JIT
| |
+-- always valid ----+-- hot paths only
The threaded interpreter is the complete execution engine. The trace JIT is an adaptive acceleration layer: supported hot paths compile to native ARM64 code, while every unsupported or cold path continues in the interpreter.
The instruction set is WebAssembly-inspired but intentionally custom. It uses one-byte opcodes with fixed-width or length-prefixed operands.
| Feature | Status |
|---|---|
| Threaded interpreter | ✅ Available |
| Static bytecode verifier | ✅ Available |
AOT optimizer (O1-O3) |
✅ Available |
| ARM64 trace JIT | ✅ Available |
| Debugger and profiler | ✅ Available |
| x86-64 JIT | 🔲 Planned |
The x86-64 assembler package currently provides a non-emitting placeholder. See the Roadmap for current priorities.
| Guide | Use it for |
|---|---|
| Documentation Index | Browse all project documentation |
| Compatibility | Check Go, platform, CGO, and build-tag support |
| Host Integration | Connect bytecode with Go values and functions |
| Verification | Understand static admission checks and limits |
| Debugging | Use breakpoints, stepping, and inspection |
| Testing | Understand executable specifications and gates |
| Benchmarks | Reproduce performance and allocation measurements |