Skip to content

Work with calls and state

Instance.InvokeValues checks typed arguments against the export signature and uses the supplied context for cancellation.

Wasm typeGo argumentRead a result
i32wago.ValueI32(v)result.I32()
i64wago.ValueI64(v)result.I64()
f32wago.ValueF32(v)result.F32()
f64wago.ValueF64(v)result.F64()

Use Invoke for raw ABI slots without a caller-supplied context, or InvokeContext when raw-slot execution also needs cancellation. Resolve repeated calls once with WasmFunc; its Invoke method accepts every arity and performs lifecycle admission for each call.

See instance state survive

Create counter.wat:

wat
(module
  (global $count (export "count") (mut i32) (i32.const 0))
  (func (export "inc") (result i32)
    global.get $count
    i32.const 1
    i32.add
    global.set $count
    global.get $count))

Compile it with WABT:

sh
wat2wasm counter.wat -o counter.wasm

Replace main.go with:

go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/wago-org/wago"
)

func run(ctx context.Context) error {
	wasm, err := os.ReadFile("counter.wasm")
	if err != nil { return err }

	runtime := wago.NewRuntime()
	defer runtime.Close()
	module, err := runtime.Compile(wasm)
	if err != nil { return err }; defer module.Close()

	instance, err := runtime.Instantiate(ctx, module)
	if err != nil { return err }; defer instance.Close()

	for range 3 {
		results, err := instance.InvokeValues(ctx, "inc")
		if err != nil { return err }; fmt.Println(results[0].I32())
	}

	count, err := instance.GlobalValue("count")
	if err != nil { return err }
	fmt.Println("count:", count.I32())

	if err := instance.SetGlobalValue("count", wago.ValueI32(40)); err != nil { return err }
	results, err := instance.InvokeValues(ctx, "inc")
	if err != nil { return err }; fmt.Println("after set:", results[0].I32())

	fresh, err := runtime.Instantiate(ctx, module)
	if err != nil { return err }; defer fresh.Close()
	results, err = fresh.InvokeValues(ctx, "inc")
	if err != nil { return err }; fmt.Println("fresh instance:", results[0].I32())
	return nil
}

func main() {
	if err := run(context.Background()); err != nil {
		log.Fatal(err)
	}
}
sh
go run .
text
1
2
3
count: 3
after set: 41
fresh instance: 1

One instance preserves state and accepts one call at a time. A fresh instance starts with the module's initial state while reusing the same compiled code.

Read and write memory safely

Use checked copies for ordinary host access:

go
data, ok := instance.Read(offset, length)
if !ok {
	return fmt.Errorf("guest memory range is out of bounds")
}

if ok := instance.Write(offset, replacement); !ok {
	return fmt.Errorf("guest memory range is out of bounds")
}

Read returns a copy. Write either copies the whole slice or changes nothing. Typed little-endian helpers such as ReadUint32Le and WriteUint32Le are available for scalar fields.

instance.Memory().UnsafeBytes() is a zero-copy mutable view. Use it only when you can keep the view inside a tightly controlled operation; never retain it after closing the instance.

Released under the Apache 2.0 License.