* Makes CacheNumInUint64 lazy and stops crashing in assemblyscript This makes CacheNumInUint64 lazy so that all tests for function types don't need to handle it. This also changes the assemblyscript special functions so they don't crash when attempting to log. Finally, this refactors `wasm.Func` so that it can enclose the parameter names as it is more sensible than defining them elsewhere. Signed-off-by: Adrian Cole <adrian@tetrate.io>
412 lines
12 KiB
Go
412 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}},
|
|
},
|
|
{
|
|
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},
|
|
},
|
|
},
|
|
{
|
|
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}},
|
|
},
|
|
{
|
|
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}},
|
|
},
|
|
{
|
|
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}},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
rVal := reflect.ValueOf(tc.inputFunc)
|
|
fk, ft, err := getFunctionType(&rVal)
|
|
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 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)
|
|
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)
|
|
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)
|
|
require.NoError(t, err)
|
|
|
|
results := CallGoFunc(testCtx, callCtx, &FunctionInstance{Kind: fk, GoFunc: &goFunc}, tc.inputParams)
|
|
require.Equal(t, tc.expectedResults, results)
|
|
})
|
|
}
|
|
}
|