Skip to content

Commit

Permalink
feat(runtime/wasmer): create wrapper around wasmer.Memory (#3160)
Browse files Browse the repository at this point in the history
  • Loading branch information
jimjbrettj authored Mar 28, 2023
1 parent 46c0ef7 commit fc1055d
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
53 changes: 53 additions & 0 deletions lib/runtime/newWasmer/memory.go
Original file line number Diff line number Diff line change
@@ -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
}
175 changes: 175 additions & 0 deletions lib/runtime/newWasmer/memory_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}

0 comments on commit fc1055d

Please sign in to comment.