Skip to content

Commit

Permalink
chore(wasmer): add helpers.go file with helper functions (#2749)
Browse files Browse the repository at this point in the history
- Split out (CGO related) helper functions from `imports.go` to `lib/runtime/wasmer/helpers.go`
- Move pointer size helper functions to `lib/runtime/wasmer/helpers.go`
- Change `toWasmMemorySized` to NOT take a size argument (unneeded)
- Clarify all comments for helper functions
- Update all error wrappings
- Review variable names
  - Use `ptr` instead of `out` for 32 bit pointers
  - Use `pointerSize` instead of `span` for 64 bit pointers size
  - Name return values
  - Other minor renamings such as `res` to `result`, `enc` to `encodedResult`
- Optimizations:
  - `storageAppend`: use slice capacity allocation, `copy` and remove unneeded `append`s
  - `toKillStorageResultEnum`: use `copy` instead of `append`
  - `toWasmMemoryOptional`: remove unneeded variable copy
  • Loading branch information
qdm12 authored Sep 9, 2022
1 parent 1edb86b commit 7fa9196
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 220 deletions.
14 changes: 0 additions & 14 deletions lib/runtime/common.go

This file was deleted.

216 changes: 216 additions & 0 deletions lib/runtime/wasmer/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Copyright 2022 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package wasmer

// #include <stdlib.h>
import "C" //skipcq: SCC-compile

import (
"fmt"
"math/big"

"github.com/ChainSafe/gossamer/lib/common/types"
"github.com/ChainSafe/gossamer/lib/runtime"
"github.com/ChainSafe/gossamer/pkg/scale"
"github.com/wasmerio/go-ext-wasm/wasmer"
)

// toPointerSize converts an uint32 pointer and uint32 size
// to an int64 pointer size.
func toPointerSize(ptr, size uint32) (pointerSize int64) {
return int64(ptr) | (int64(size) << 32)
}

// splitPointerSize converts an int64 pointer size to an
// uint32 pointer and an uint32 size.
func splitPointerSize(pointerSize int64) (ptr, size uint32) {
return uint32(pointerSize), uint32(pointerSize >> 32)
}

// asMemorySlice converts a 64 bit pointer size to a Go byte slice.
func asMemorySlice(context wasmer.InstanceContext, pointerSize C.int64_t) (data []byte) {
memory := context.Memory().Data()
ptr, size := splitPointerSize(int64(pointerSize))
return memory[ptr : ptr+size]
}

// toWasmMemory copies a Go byte slice to wasm memory and returns the corresponding
// 64 bit pointer size.
func toWasmMemory(context wasmer.InstanceContext, data []byte) (
pointerSize int64, err error) {
allocator := context.Data().(*runtime.Context).Allocator
size := uint32(len(data))

ptr, err := allocator.Allocate(size)
if err != nil {
return 0, fmt.Errorf("allocating: %w", err)
}

memory := context.Memory().Data()

if uint32(len(memory)) < ptr+size {
panic(fmt.Sprintf("length of memory is less than expected, want %d have %d", ptr+size, len(memory)))
}

copy(memory[ptr:ptr+size], data)
pointerSize = toPointerSize(ptr, size)
return pointerSize, nil
}

// toWasmMemorySized copies a Go byte slice to wasm memory and returns the corresponding
// 32 bit pointer. Note the data must have a well known fixed length in the runtime.
func toWasmMemorySized(context wasmer.InstanceContext, data []byte) (
pointer uint32, err error) {
allocator := context.Data().(*runtime.Context).Allocator

size := uint32(len(data))
pointer, err = allocator.Allocate(size)
if err != nil {
return 0, fmt.Errorf("allocating: %w", err)
}

memory := context.Memory().Data()
copy(memory[pointer:pointer+size], data)

return pointer, nil
}

// toWasmMemoryOptional scale encodes the byte slice `data`, writes it to wasm memory
// and returns the corresponding 64 bit pointer size.
func toWasmMemoryOptional(context wasmer.InstanceContext, data []byte) (
pointerSize int64, err error) {
var optionalSlice *[]byte
if data != nil {
optionalSlice = &data
}

encoded, err := scale.Marshal(optionalSlice)
if err != nil {
return 0, err
}

return toWasmMemory(context, encoded)
}

// toWasmMemoryResult wraps the data byte slice in a Result type, scale encodes it,
// copies it to wasm memory and returns the corresponding 64 bit pointer size.
func toWasmMemoryResult(context wasmer.InstanceContext, data []byte) (
pointerSize int64, err error) {
var result *types.Result
if len(data) == 0 {
result = types.NewResult(byte(1), nil)
} else {
result = types.NewResult(byte(0), data)
}

encodedResult, err := result.Encode()
if err != nil {
return 0, fmt.Errorf("encoding result: %w", err)
}

return toWasmMemory(context, encodedResult)
}

// toWasmMemoryOptional scale encodes the uint32 pointer `data`, writes it to wasm memory
// and returns the corresponding 64 bit pointer size.
func toWasmMemoryOptionalUint32(context wasmer.InstanceContext, data *uint32) (
pointerSize int64, err error) {
enc, err := scale.Marshal(data)
if err != nil {
return 0, fmt.Errorf("scale encoding: %w", err)
}
return toWasmMemory(context, enc)
}

// toKillStorageResultEnum encodes the `allRemoved` flag and
// the `numRemoved` uint32 to a byte slice and returns it.
// The format used is:
// Byte 0: 1 if allRemoved is false, 0 otherwise
// Byte 1-5: scale encoding of numRemoved (up to 4 bytes)
func toKillStorageResultEnum(allRemoved bool, numRemoved uint32) (
encodedEnumValue []byte, err error) {
encodedNumRemoved, err := scale.Marshal(numRemoved)
if err != nil {
return nil, fmt.Errorf("scale encoding: %w", err)
}

encodedEnumValue = make([]byte, len(encodedNumRemoved)+1)
if !allRemoved {
// At least one key resides in the child trie due to the supplied limit.
encodedEnumValue[0] = 1
}
copy(encodedEnumValue[1:], encodedNumRemoved)

return encodedEnumValue, nil
}

// toWasmMemoryFixedSizeOptional copies the `data` byte slice to a 64B array,
// scale encodes the pointer to the resulting array, writes it to wasm memory
// and returns the corresponding 64 bit pointer size.
func toWasmMemoryFixedSizeOptional(context wasmer.InstanceContext, data []byte) (
pointerSize int64, err error) {
var optionalFixedSize [64]byte
copy(optionalFixedSize[:], data)
encodedOptionalFixedSize, err := scale.Marshal(&optionalFixedSize)
if err != nil {
return 0, fmt.Errorf("scale encoding: %w", err)
}
return toWasmMemory(context, encodedOptionalFixedSize)
}

func storageAppend(storage runtime.Storage, key, valueToAppend []byte) error {
// this function assumes the item in storage is a SCALE encoded array of items
// the valueToAppend is a new item, so it appends the item and increases the length prefix by 1
currentValue := storage.Get(key)

var value []byte
if len(currentValue) == 0 {
nextLength := big.NewInt(1)
encodedLength, err := scale.Marshal(nextLength)
if err != nil {
return fmt.Errorf("scale encoding: %w", err)
}
value = make([]byte, len(encodedLength)+len(valueToAppend))
// append new length prefix to start of items array
copy(value, encodedLength)
copy(value[len(encodedLength):], valueToAppend)
} else {
var currentLength *big.Int
err := scale.Unmarshal(currentValue, &currentLength)
if err != nil {
logger.Tracef(
"item in storage is not SCALE encoded, overwriting at key 0x%x", key)
value = make([]byte, 1+len(valueToAppend))
value[0] = 4
copy(value[1:], valueToAppend)
} else {
lengthBytes, err := scale.Marshal(currentLength)
if err != nil {
return fmt.Errorf("scale encoding: %w", err)
}

// increase length by 1
nextLength := big.NewInt(0).Add(currentLength, big.NewInt(1))
nextLengthBytes, err := scale.Marshal(nextLength)
if err != nil {
return fmt.Errorf("scale encoding next length bytes: %w", err)
}

// append new item, pop off number of bytes required for length encoding,
// since we're not using old scale.Decoder
value = make([]byte, len(nextLengthBytes)+len(currentValue)-len(lengthBytes)+len(valueToAppend))
// append new length prefix to start of items array
i := 0
copy(value[i:], nextLengthBytes)
i += len(nextLengthBytes)
copy(value[i:], currentValue[len(lengthBytes):])
i += len(currentValue) - len(lengthBytes)
copy(value[i:], valueToAppend)
}
}

logger.Debugf("resulting value: 0x%x", value)
storage.Set(key, value)
return nil
}
49 changes: 49 additions & 0 deletions lib/runtime/wasmer/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2022 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package wasmer

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_pointerSize(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
ptr uint32
size uint32
pointerSize int64
}{
"0": {},
"ptr 8 size 32": {
ptr: 8,
size: 32,
pointerSize: int64(8) | (int64(32) << 32),
},
"ptr max uint32 and size max uint32": {
ptr: ^uint32(0),
size: ^uint32(0),
pointerSize: ^int64(0),
},
}

for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()

pointerSize := toPointerSize(testCase.ptr, testCase.size)

require.Equal(t, testCase.pointerSize, pointerSize)

ptr, size := splitPointerSize(pointerSize)

assert.Equal(t, testCase.ptr, ptr)
assert.Equal(t, testCase.size, size)
})
}
}
Loading

0 comments on commit 7fa9196

Please sign in to comment.