diff --git a/.gitignore b/.gitignore index b8bff339e..0c71e4711 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ a.out # direnv Nix stuff .direnv/ + +reference-code/ diff --git a/go.mod b/go.mod index 9dffc99b7..068aa1ac7 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,19 @@ module github.com/CosmWasm/wasmvm/v3 -go 1.22 +go 1.22.0 + +toolchain go1.23.8 require ( github.com/google/btree v1.0.0 github.com/shamaton/msgpack/v2 v2.2.0 github.com/stretchr/testify v1.8.1 + github.com/tetratelabs/wazero v0.0.0 golang.org/x/sys v0.16.0 ) +replace github.com/tetratelabs/wazero => ./reference-code/wazero + require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect diff --git a/internal/wazeroimpl/runtime.go b/internal/wazeroimpl/runtime.go new file mode 100644 index 000000000..ed5b09d02 --- /dev/null +++ b/internal/wazeroimpl/runtime.go @@ -0,0 +1,145 @@ +package wazeroimpl + +import ( + "context" + "encoding/hex" + "fmt" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + + "github.com/CosmWasm/wasmvm/v3/types" +) + +// Cache manages a wazero runtime and compiled modules. +type Cache struct { + runtime wazero.Runtime + modules map[string]wazero.CompiledModule + memLimit uint32 // in wasm pages +} + +// InitCache creates a new wazero Runtime with memory limits similar to api.InitCache. +func InitCache(config types.VMConfig) (*Cache, error) { + ctx := context.Background() + r := wazero.NewRuntime(ctx) + limitPages := config.Cache.InstanceMemoryLimitBytes.Uint32() / 65536 + return &Cache{ + runtime: r, + modules: make(map[string]wazero.CompiledModule), + memLimit: limitPages, + }, nil +} + +// Close releases all resources of the runtime. +func (c *Cache) Close(ctx context.Context) error { + if c.runtime != nil { + return c.runtime.Close(ctx) + } + return nil +} + +// Compile stores a compiled module under the given checksum. +func (c *Cache) Compile(ctx context.Context, checksum types.Checksum, wasm []byte) error { + mod, err := c.runtime.CompileModule(ctx, wasm) + if err != nil { + return err + } + c.modules[hex.EncodeToString(checksum)] = mod + return nil +} + +// getModule returns the compiled module for the checksum. +func (c *Cache) getModule(checksum types.Checksum) (wazero.CompiledModule, bool) { + mod, ok := c.modules[hex.EncodeToString(checksum)] + return mod, ok +} + +// registerHost builds an env module with callbacks for the given state. +func (c *Cache) registerHost(ctx context.Context, store types.KVStore, apiImpl *types.GoAPI, q *types.Querier, gm types.GasMeter) (api.Module, error) { + builder := c.runtime.NewHostModuleBuilder("env") + + // db_read + builder.NewFunctionBuilder().WithGoModuleFunction(func(ctx context.Context, m api.Module, stack []uint64) { + keyPtr := uint32(stack[0]) + keyLen := uint32(stack[1]) + outPtr := uint32(stack[2]) + mem := m.Memory() + key, _ := mem.Read(keyPtr, keyLen) + value := store.Get(key) + if value == nil { + _ = mem.WriteUint32Le(outPtr, 0) + return + } + _ = mem.WriteUint32Le(outPtr, uint32(len(value))) + mem.Write(outPtr+4, value) + }, []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("db_read") + + // db_write + builder.NewFunctionBuilder().WithGoModuleFunction(func(ctx context.Context, m api.Module, stack []uint64) { + keyPtr := uint32(stack[0]) + keyLen := uint32(stack[1]) + valPtr := uint32(stack[2]) + valLen := uint32(stack[3]) + mem := m.Memory() + key, _ := mem.Read(keyPtr, keyLen) + val, _ := mem.Read(valPtr, valLen) + store.Set(key, val) + }, []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("db_write") + + // db_remove + builder.NewFunctionBuilder().WithGoModuleFunction(func(ctx context.Context, m api.Module, stack []uint64) { + keyPtr := uint32(stack[0]) + keyLen := uint32(stack[1]) + mem := m.Memory() + key, _ := mem.Read(keyPtr, keyLen) + store.Delete(key) + }, []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("db_remove") + + // query_external - simplified: returns 0 length + builder.NewFunctionBuilder().WithGoModuleFunction(func(ctx context.Context, m api.Module, stack []uint64) { + resPtr := uint32(stack[2]) + _ = m.Memory().WriteUint32Le(resPtr, 0) + }, []api.ValueType{api.ValueTypeI64, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("query_external") + + return builder.Instantiate(ctx) +} + +// Instantiate loads and runs the contract's instantiate function. +func (c *Cache) Instantiate(ctx context.Context, checksum types.Checksum, env, info, msg []byte, store types.KVStore, apiImpl *types.GoAPI, q *types.Querier, gm types.GasMeter) error { + compiled, ok := c.getModule(checksum) + if !ok { + return fmt.Errorf("module not found") + } + _, err := c.registerHost(ctx, store, apiImpl, q, gm) + if err != nil { + return err + } + mod, err := c.runtime.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithMemoryLimitPages(c.memLimit)) + if err != nil { + return err + } + if fn := mod.ExportedFunction("instantiate"); fn != nil { + _, err = fn.Call(ctx) + } + return err +} + +// Execute runs the contract's execute function. +func (c *Cache) Execute(ctx context.Context, checksum types.Checksum, env, info, msg []byte, store types.KVStore, apiImpl *types.GoAPI, q *types.Querier, gm types.GasMeter) error { + compiled, ok := c.getModule(checksum) + if !ok { + return fmt.Errorf("module not found") + } + _, err := c.registerHost(ctx, store, apiImpl, q, gm) + if err != nil { + return err + } + mod, err := c.runtime.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithMemoryLimitPages(c.memLimit)) + if err != nil { + return err + } + if fn := mod.ExportedFunction("execute"); fn != nil { + _, err = fn.Call(ctx) + } + return err +}