Files
wazero/internal/wasm/gofunc_test.go
Takeshi Yoneda 064bcdddc6 Implements v128.const and adds support for vector value type. (#556)
This commit implements the v128.const, i32x4.add and i64x2.add in
interpreter mode and this adds support for the vector value types in the
locals and globals.

Notably, the vector type values can be passed and returned by exported functions
as well as host functions via two-uint64 encodings as described in #484 (comment).

Note: implementation of these instructions on JIT will be done in subsequent PR.

part of #484

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
2022-05-16 13:17:26 +09:00

418 lines
12 KiB
Go

package wasm
import (
"context"
"math"
"reflect"
"testing"
"unsafe"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/testing/require"
)
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
func TestGetFunctionType(t *testing.T) {
var tests = []struct {
name string
inputFunc interface{}
expectedKind FunctionKind
expectedType *FunctionType
}{
{
name: "nullary",
inputFunc: func() {},
expectedKind: FunctionKindGoNoContext,
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
},
{
name: "wasm.Module void return",
inputFunc: func(api.Module) {},
expectedKind: FunctionKindGoModule,
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
},
{
name: "context.Context void return",
inputFunc: func(context.Context) {},
expectedKind: FunctionKindGoContext,
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
},
{
name: "context.Context and api.Module void return",
inputFunc: func(context.Context, api.Module) {},
expectedKind: FunctionKindGoContextModule,
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
},
{
name: "all supported params and i32 result",
inputFunc: func(uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedKind: FunctionKindGoNoContext,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1},
},
{
name: "all supported params and all supported results",
inputFunc: func(uint32, uint64, float32, float64, uintptr) (uint32, uint64, float32, float64, uintptr) {
return 0, 0, 0, 0, 0
},
expectedKind: FunctionKindGoNoContext,
expectedType: &FunctionType{
Params: []ValueType{i32, i64, f32, f64, externref},
Results: []ValueType{i32, i64, f32, f64, externref},
ParamNumInUint64: 5, ResultNumInUint64: 5,
},
},
{
name: "all supported params and i32 result - wasm.Module",
inputFunc: func(api.Module, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedKind: FunctionKindGoModule,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1},
},
{
name: "all supported params and i32 result - context.Context",
inputFunc: func(context.Context, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedKind: FunctionKindGoContext,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1},
},
{
name: "all supported params and i32 result - context.Context and api.Module",
inputFunc: func(context.Context, api.Module, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedKind: FunctionKindGoContextModule,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
rVal := reflect.ValueOf(tc.inputFunc)
fk, ft, err := getFunctionType(&rVal, Features20191205|FeatureMultiValue)
require.NoError(t, err)
require.Equal(t, tc.expectedKind, fk)
require.Equal(t, tc.expectedType, ft)
})
}
}
func TestGetFunctionTypeErrors(t *testing.T) {
tests := []struct {
name string
input interface{}
allowErrorResult bool
expectedErr string
}{
{
name: "not a func",
input: struct{}{},
expectedErr: "kind != func: struct",
},
{
name: "unsupported param",
input: func(uint32, string) {},
expectedErr: "param[1] is unsupported: string",
},
{
name: "unsupported result",
input: func() string { return "" },
expectedErr: "result[0] is unsupported: string",
},
{
name: "error result",
input: func() error { return nil },
expectedErr: "result[0] is an error, which is unsupported",
},
{
name: "multiple results - multi-value not enabled",
input: func() (uint64, uint32) { return 0, 0 },
expectedErr: "multiple result types invalid as feature \"multi-value\" is disabled",
},
{
name: "multiple context types",
input: func(api.Module, context.Context) error { return nil },
expectedErr: "param[1] is a context.Context, which may be defined only once as param[0]",
},
{
name: "multiple context.Context",
input: func(context.Context, uint64, context.Context) error { return nil },
expectedErr: "param[2] is a context.Context, which may be defined only once as param[0]",
},
{
name: "multiple wasm.Module",
input: func(api.Module, uint64, api.Module) error { return nil },
expectedErr: "param[2] is a api.Module, which may be defined only once as param[0]",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
rVal := reflect.ValueOf(tc.input)
_, _, err := getFunctionType(&rVal, Features20191205)
require.EqualError(t, err, tc.expectedErr)
})
}
}
// stack simulates the value stack in a way easy to be tested.
type stack struct {
vals []uint64
}
func (s *stack) pop() (result uint64) {
stackTopIndex := len(s.vals) - 1
result = s.vals[stackTopIndex]
s.vals = s.vals[:stackTopIndex]
return
}
func TestPopValues(t *testing.T) {
stackVals := []uint64{1, 2, 3, 4, 5, 6, 7}
var tests = []struct {
name string
count int
expected []uint64
}{
{
name: "pop zero doesn't allocate a slice ",
},
{
name: "pop 1",
count: 1,
expected: []uint64{7},
},
{
name: "pop 2",
count: 2,
expected: []uint64{6, 7},
},
{
name: "pop 3",
count: 3,
expected: []uint64{5, 6, 7},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
vals := PopValues(tc.count, (&stack{stackVals}).pop)
require.Equal(t, tc.expected, vals)
})
}
}
func TestPopGoFuncParams(t *testing.T) {
stackVals := []uint64{1, 2, 3, 4, 5, 6, 7}
var tests = []struct {
name string
inputFunc interface{}
expected []uint64
}{
{
name: "nullary",
inputFunc: func() {},
},
{
name: "wasm.Module",
inputFunc: func(api.Module) {},
},
{
name: "context.Context",
inputFunc: func(context.Context) {},
},
{
name: "context.Context and api.Module",
inputFunc: func(context.Context, api.Module) {},
},
{
name: "all supported params",
inputFunc: func(uint32, uint64, float32, float64, uintptr) {},
expected: []uint64{3, 4, 5, 6, 7},
},
{
name: "all supported params - wasm.Module",
inputFunc: func(api.Module, uint32, uint64, float32, float64, uintptr) {},
expected: []uint64{3, 4, 5, 6, 7},
},
{
name: "all supported params - context.Context",
inputFunc: func(context.Context, uint32, uint64, float32, float64, uintptr) {},
expected: []uint64{3, 4, 5, 6, 7},
},
{
name: "all supported params - context.Context and api.Module",
inputFunc: func(context.Context, api.Module, uint32, uint64, float32, float64, uintptr) {},
expected: []uint64{3, 4, 5, 6, 7},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
goFunc := reflect.ValueOf(tc.inputFunc)
fk, _, err := getFunctionType(&goFunc, Features20220419)
require.NoError(t, err)
vals := PopGoFuncParams(&FunctionInstance{Kind: fk, GoFunc: &goFunc}, (&stack{stackVals}).pop)
require.Equal(t, tc.expected, vals)
})
}
}
func TestCallGoFunc(t *testing.T) {
tPtr := uintptr(unsafe.Pointer(t))
callCtx := &CallContext{}
callCtxPtr := uintptr(unsafe.Pointer(callCtx))
var tests = []struct {
name string
inputFunc interface{}
inputParams, expectedResults []uint64
}{
{
name: "nullary",
inputFunc: func() {},
},
{
name: "wasm.Module void return",
inputFunc: func(m api.Module) {
require.Equal(t, callCtx, m)
},
},
{
name: "context.Context void return",
inputFunc: func(ctx context.Context) {
require.Equal(t, testCtx, ctx)
},
},
{
name: "context.Context and api.Module void return",
inputFunc: func(ctx context.Context, m api.Module) {
require.Equal(t, testCtx, ctx)
require.Equal(t, callCtx, m)
},
},
{
name: "all supported params and i32 result",
inputFunc: func(v uintptr, w uint32, x uint64, y float32, z float64) uint32 {
require.Equal(t, tPtr, v)
require.Equal(t, uint32(math.MaxUint32), w)
require.Equal(t, uint64(math.MaxUint64), x)
require.Equal(t, float32(math.MaxFloat32), y)
require.Equal(t, math.MaxFloat64, z)
return 100
},
inputParams: []uint64{
api.EncodeExternref(tPtr),
math.MaxUint32,
math.MaxUint64,
api.EncodeF32(math.MaxFloat32),
api.EncodeF64(math.MaxFloat64),
},
expectedResults: []uint64{100},
},
{
name: "all supported params and all supported results",
inputFunc: func(v uintptr, w uint32, x uint64, y float32, z float64) (uintptr, uint32, uint64, float32, float64) {
require.Equal(t, tPtr, v)
require.Equal(t, uint32(math.MaxUint32), w)
require.Equal(t, uint64(math.MaxUint64), x)
require.Equal(t, float32(math.MaxFloat32), y)
require.Equal(t, math.MaxFloat64, z)
return uintptr(unsafe.Pointer(callCtx)), 100, 200, 300, 400
},
inputParams: []uint64{
api.EncodeExternref(tPtr),
math.MaxUint32,
math.MaxUint64,
api.EncodeF32(math.MaxFloat32),
api.EncodeF64(math.MaxFloat64),
},
expectedResults: []uint64{
api.EncodeExternref(callCtxPtr),
api.EncodeI32(100),
200,
api.EncodeF32(300),
api.EncodeF64(400),
},
},
{
name: "all supported params and i32 result - wasm.Module",
inputFunc: func(m api.Module, v uintptr, w uint32, x uint64, y float32, z float64) uint32 {
require.Equal(t, callCtx, m)
require.Equal(t, tPtr, v)
require.Equal(t, uint32(math.MaxUint32), w)
require.Equal(t, uint64(math.MaxUint64), x)
require.Equal(t, float32(math.MaxFloat32), y)
require.Equal(t, math.MaxFloat64, z)
return 100
},
inputParams: []uint64{
api.EncodeExternref(tPtr),
math.MaxUint32,
math.MaxUint64,
api.EncodeF32(math.MaxFloat32),
api.EncodeF64(math.MaxFloat64),
},
expectedResults: []uint64{100},
},
{
name: "all supported params and i32 result - context.Context",
inputFunc: func(ctx context.Context, v uintptr, w uint32, x uint64, y float32, z float64) uint32 {
require.Equal(t, testCtx, ctx)
require.Equal(t, tPtr, v)
require.Equal(t, uint32(math.MaxUint32), w)
require.Equal(t, uint64(math.MaxUint64), x)
require.Equal(t, float32(math.MaxFloat32), y)
require.Equal(t, math.MaxFloat64, z)
return 100
},
inputParams: []uint64{
api.EncodeExternref(tPtr),
math.MaxUint32,
math.MaxUint64,
api.EncodeF32(math.MaxFloat32),
api.EncodeF64(math.MaxFloat64),
},
expectedResults: []uint64{100},
},
{
name: "all supported params and i32 result - context.Context and api.Module",
inputFunc: func(ctx context.Context, m api.Module, v uintptr, w uint32, x uint64, y float32, z float64) uint32 {
require.Equal(t, testCtx, ctx)
require.Equal(t, callCtx, m)
require.Equal(t, tPtr, v)
require.Equal(t, uint32(math.MaxUint32), w)
require.Equal(t, uint64(math.MaxUint64), x)
require.Equal(t, float32(math.MaxFloat32), y)
require.Equal(t, math.MaxFloat64, z)
return 100
},
inputParams: []uint64{
api.EncodeExternref(tPtr),
math.MaxUint32,
math.MaxUint64,
api.EncodeF32(math.MaxFloat32),
api.EncodeF64(math.MaxFloat64),
},
expectedResults: []uint64{100},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
goFunc := reflect.ValueOf(tc.inputFunc)
fk, _, err := getFunctionType(&goFunc, Features20220419)
require.NoError(t, err)
results := CallGoFunc(testCtx, callCtx, &FunctionInstance{Kind: fk, GoFunc: &goFunc}, tc.inputParams)
require.Equal(t, tc.expectedResults, results)
})
}
}