-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(wasmer): add helpers.go file with helper functions (#2749)
- 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
Showing
7 changed files
with
281 additions
and
220 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ¤tLength) | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
Oops, something went wrong.