Removes requirement to pass a HostFunctionCallContext (#260)

This allows users to decouple from wazero code when authoring host
functions. Notably, this allows them to opt out of using a context, or
only using a Go context instead of HostFunctionCallContext.

This backfills docs on how to write host functions (in simple terms).

Finally, this does not optimize engines to avoid propagating context or
looking up memory if it would never be used. That could be done later.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-02-18 16:54:52 +08:00
committed by GitHub
parent fbe153bb2c
commit 3d25f48b4a
16 changed files with 441 additions and 148 deletions

View File

@@ -1,10 +1,14 @@
package internalwasm
import (
"context"
"math"
"reflect"
"testing"
"github.com/stretchr/testify/require"
publicwasm "github.com/tetratelabs/wazero/wasm"
)
func TestMemoryInstance_HasLen(t *testing.T) {
@@ -448,3 +452,122 @@ func TestMemoryInstance_WriteFloat64Le(t *testing.T) {
})
}
}
func TestGetFunctionType(t *testing.T) {
i32, i64, f32, f64 := ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64
tests := []struct {
name string
inputFunc interface{}
expectedKind FunctionKind
expectedType *FunctionType
}{
{
name: "nullary",
inputFunc: func() {},
expectedKind: FunctionKindHostNoContext,
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
},
{
name: "wasm.HostFunctionCallContext void return",
inputFunc: func(publicwasm.HostFunctionCallContext) {},
expectedKind: FunctionKindHostFunctionCallContext,
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
},
{
name: "context.Context void return",
inputFunc: func(context.Context) {},
expectedKind: FunctionKindHostGoContext,
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
},
{
name: "all supported params and i32 result",
inputFunc: func(uint32, uint64, float32, float64) uint32 { return 0 },
expectedKind: FunctionKindHostNoContext,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, Results: []ValueType{i32}},
},
{
name: "all supported params and i32 result - wasm.HostFunctionCallContext",
inputFunc: func(publicwasm.HostFunctionCallContext, uint32, uint64, float32, float64) uint32 { return 0 },
expectedKind: FunctionKindHostFunctionCallContext,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, Results: []ValueType{i32}},
},
{
name: "all supported params and i32 result - context.Context",
inputFunc: func(context.Context, uint32, uint64, float32, float64) uint32 { return 0 },
expectedKind: FunctionKindHostGoContext,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, 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("fn", &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{}
expectedErr string
}{
{
name: "not a func",
input: struct{}{},
expectedErr: "fn is a struct, but should be a Func",
},
{
name: "unsupported param",
input: func(uint32, string) {},
expectedErr: "fn param[1] is unsupported: string",
},
{
name: "unsupported result",
input: func() string { return "" },
expectedErr: "fn result[0] is unsupported: string",
},
{
name: "error result",
input: func() error { return nil },
expectedErr: "fn result[0] is an error, which is unsupported",
},
{
name: "multiple results",
input: func() (uint64, uint32) { return 0, 0 },
expectedErr: "fn has more than one result",
},
{
name: "multiple context types",
input: func(publicwasm.HostFunctionCallContext, context.Context) error { return nil },
expectedErr: "fn 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: "fn param[2] is a context.Context, which may be defined only once as param[0]",
},
{
name: "multiple wasm.HostFunctionCallContext",
input: func(publicwasm.HostFunctionCallContext, uint64, publicwasm.HostFunctionCallContext) error { return nil },
expectedErr: "fn param[2] is a wasm.HostFunctionCallContext, 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("fn", &rVal)
require.EqualError(t, err, tc.expectedErr)
})
}
}