diff --git a/go.mod b/go.mod index 4ae5a76238..ecc617943f 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/stretchr/testify v1.8.2 github.com/urfave/cli/v2 v2.25.1 github.com/wasmerio/go-ext-wasm v0.3.2-0.20200326095750-0a32be6068ec + github.com/wasmerio/wasmer-go v1.0.4 github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9 golang.org/x/crypto v0.7.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 diff --git a/go.sum b/go.sum index 3a6631ca2c..9ad1e2a273 100644 --- a/go.sum +++ b/go.sum @@ -612,6 +612,8 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w= github.com/wasmerio/go-ext-wasm v0.3.2-0.20200326095750-0a32be6068ec h1:VElCeVyfCWNmCv6UisKQrr+P2/JRG0uf4/FIdCB4pL0= github.com/wasmerio/go-ext-wasm v0.3.2-0.20200326095750-0a32be6068ec/go.mod h1:VGyarTzasuS7k5KhSIGpM3tciSZlkP31Mp9VJTHMMeI= +github.com/wasmerio/wasmer-go v1.0.4 h1:MnqHoOGfiQ8MMq2RF6wyCeebKOe84G88h5yv+vmxJgs= +github.com/wasmerio/wasmer-go v1.0.4/go.mod h1:0gzVdSfg6pysA6QVp6iVRPTagC6Wq9pOE8J86WKb2Fk= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9 h1:Y1/FEOpaCpD21WxrmfeIYCFPuVPRCY2XZTWzTNHGw30= diff --git a/lib/runtime/newWasmer/memory.go b/lib/runtime/newWasmer/memory.go new file mode 100644 index 0000000000..e18f8856ee --- /dev/null +++ b/lib/runtime/newWasmer/memory.go @@ -0,0 +1,53 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package latestwasmer + +import ( + "errors" + "fmt" + "math" + + "github.com/wasmerio/wasmer-go/wasmer" +) + +var ( + errCantGrowMemory = errors.New("failed to grow memory") + errMemoryValueOutOfBounds = errors.New("memory value is out of bounds") +) + +// Memory is a thin wrapper around Wasmer memory to support +// Gossamer runtime.Memory interface +type Memory struct { + memory *wasmer.Memory +} + +func checkBounds(value uint64) (uint32, error) { + if value > math.MaxUint32 { + return 0, fmt.Errorf("%w", errMemoryValueOutOfBounds) + } + return uint32(value), nil +} + +// Data returns the memory's data +func (m Memory) Data() []byte { + return m.memory.Data() +} + +// Length returns the memory's length +func (m Memory) Length() uint32 { + value, err := checkBounds(uint64(m.memory.DataSize())) + if err != nil { + panic(err) + } + return value +} + +// Grow grows the memory by the given number of pages +func (m Memory) Grow(numPages uint32) error { + ok := m.memory.Grow(wasmer.Pages(numPages)) + if !ok { + return fmt.Errorf("%w: by %d pages", errCantGrowMemory, numPages) + } + return nil +} diff --git a/lib/runtime/newWasmer/memory_test.go b/lib/runtime/newWasmer/memory_test.go new file mode 100644 index 0000000000..6e3bfe94ec --- /dev/null +++ b/lib/runtime/newWasmer/memory_test.go @@ -0,0 +1,175 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package latestwasmer + +import ( + "math" + "testing" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wasmerio/wasmer-go/wasmer" +) + +func createInstance(t *testing.T) (*wasmer.Instance, error) { + t.Helper() + // We are using the text representation of the module here. Taken from: + // https://github.com/wasmerio/wasmer-go/blob/23d786b6b81ad93e2b974d2f4510bea374f0f37c/examples/example_memory_test.go#L28 + wasmBytes := []byte(` + (module + (type $mem_size_t (func (result i32))) + (type $get_at_t (func (param i32) (result i32))) + (type $set_at_t (func (param i32) (param i32))) + (memory $mem 1) + (func $get_at (type $get_at_t) (param $idx i32) (result i32) + (i32.load (local.get $idx))) + (func $set_at (type $set_at_t) (param $idx i32) (param $val i32) + (i32.store (local.get $idx) (local.get $val))) + (func $mem_size (type $mem_size_t) (result i32) + (memory.size)) + (export "get_at" (func $get_at)) + (export "set_at" (func $set_at)) + (export "mem_size" (func $mem_size)) + (export "memory" (memory $mem))) + `) + + engine := wasmer.NewEngine() + store := wasmer.NewStore(engine) + + // Compile module + module, err := wasmer.NewModule(store, wasmBytes) + require.NoError(t, err) + + importObject := wasmer.NewImportObject() + + // Instantiate the Wasm module. + return wasmer.NewInstance(module, importObject) +} + +func TestMemory_Length(t *testing.T) { + t.Parallel() + const pageLength uint32 = 65536 + instance, err := createInstance(t) + require.NoError(t, err) + + wasmerMemory, err := instance.Exports.GetMemory("memory") + require.NoError(t, err) + + memory := Memory{ + memory: wasmerMemory, + } + + memLength := memory.Length() + require.Equal(t, pageLength, memLength) +} + +func TestMemory_Grow(t *testing.T) { + t.Parallel() + const pageLength uint32 = 65536 + instance, err := createInstance(t) + require.NoError(t, err) + + wasmerMemory, err := instance.Exports.GetMemory("memory") + require.NoError(t, err) + + memory := Memory{ + memory: wasmerMemory, + } + + memLength := memory.Length() + require.Equal(t, pageLength, memLength) + + err = memory.Grow(1) + require.NoError(t, err) + + memLength = memory.Length() + require.Equal(t, pageLength*2, memLength) +} + +func TestMemory_Data(t *testing.T) { + t.Parallel() + instance, err := createInstance(t) + require.NoError(t, err) + + // Grab exported utility functions from the module + getAt, err := instance.Exports.GetFunction("get_at") + require.NoError(t, err) + + setAt, err := instance.Exports.GetFunction("set_at") + require.NoError(t, err) + + wasmerMemory, err := instance.Exports.GetMemory("memory") + require.NoError(t, err) + + memory := Memory{ + memory: wasmerMemory, + } + + memAddr := 0x0 + const val int32 = 0xFEFEFFE + _, err = setAt(memAddr, val) + require.NoError(t, err) + + // Compare bytes at address 0x0 + expectedFirstBytes := []byte{254, 239, 239, 15} + memData := memory.Data() + require.Equal(t, expectedFirstBytes, memData[:4]) + + result, err := getAt(memAddr) + require.NoError(t, err) + require.Equal(t, val, result) + + // Write value at end of page + pageSize := 0x1_0000 + memAddr = (pageSize) - int(unsafe.Sizeof(val)) + const val2 int32 = 0xFEA09 + _, err = setAt(memAddr, val2) + require.NoError(t, err) + + result, err = getAt(memAddr) + require.NoError(t, err) + require.Equal(t, val2, result) +} + +func TestMemory_CheckBounds(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + value uint64 + exp uint32 + expErr error + expErrMsg string + }{ + { + name: "valid cast", + value: uint64(0), + exp: uint32(0), + }, + { + name: "max uint32", + value: uint64(math.MaxUint32), + exp: math.MaxUint32, + }, + { + name: "out of bounds", + value: uint64(math.MaxUint32 + 1), + expErr: errMemoryValueOutOfBounds, + expErrMsg: errMemoryValueOutOfBounds.Error(), + }, + } + for _, test := range testCases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + res, err := checkBounds(test.value) + assert.ErrorIs(t, err, test.expErr) + if test.expErr != nil { + assert.EqualError(t, err, test.expErrMsg) + } + assert.Equal(t, test.exp, res) + }) + } +}