Skip to content
siyul-parkPublic

About

Fast bytecode VM that embeds anywhere.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

minivm

CI codecov Go Reference License: MIT

English · 한국어

A compact, embeddable bytecode VM for Go

Run dynamic logic inside Go with explicit resource limits, typed host calls, and a threaded semantic baseline.

  • Bounded execution — stack, heap, frame depth, fuel, hooks, context.
  • Direct host calls — typed, reflection-free HostFunction path.
  • Native tier — ARM64 execution, on by default, with threaded fallback.
go get github.com/siyul-park/minivm

Requires Go 1.26.2+. The VM core uses only the Go standard library.

Quick Start

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.

Why minivm

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
Semantic baseline Threaded interpreter with explicit resource controls
Safe admission Static bytecode verification before execution

Built for

  • 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

Call Go from Bytecode

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.

Performance

Threaded execution is the semantic baseline. ARM64 native execution is on by default and tuned or disabled with interp.WithThreshold; measurements and reproduction live in Benchmarks.

Runtime Tooling

Verify untrusted bytecode

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.

Optimize ahead of execution

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.

Control execution

vm := interp.New(prog,
    interp.WithStack(4096),
    interp.WithHeap(512),
    interp.WithFrame(256),
    interp.WithFuel(10_000),
    interp.WithTick(128),
)

Use hooks for policy checks and debug.NewDebugger with WithHook(dbg.Hook) and WithTick(1) for instruction-accurate breakpoints and stepping (Debugging).

Architecture

Program → Verify → optimize? → threaded ⇄ native (ARM64)

The threaded interpreter is the semantic baseline and complete execution engine. On ARM64 the JIT adds native execution for hot functions once a program has run long enough to repay a compile (WithThreshold); unsupported paths return to threaded execution.

The instruction set is WebAssembly-inspired but intentionally custom. It uses one-byte opcodes with fixed-width or length-prefixed operands.

Status

Feature Status
Threaded interpreter ✅ Available
Static bytecode verifier ✅ Available
AOT optimizer (O1-O3) ✅ Available
ARM64 native tier (on by default, interp.WithThreshold) ✅ Available
Debugger and profiler ✅ Available
x86-64 native backend 🔲 Not implemented

The x86-64 assembler backend is not present. See the Roadmap for current priorities.

Documentation

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

License

MIT

About

Fast bytecode VM that embeds anywhere.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages