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>
418 lines
12 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|