Repository: tetratelabs/wazero
Pull request #543 (2022-05-11 08:14:18)

Fixes coverage on externref/uintptr
thx @anuraaga for noticing the bug


Patch:
diff --git a/api/wasm.go b/api/wasm.go
index 76e04fef..f366ef70 100644
--- a/api/wasm.go
+++ b/api/wasm.go
@@ -327,6 +327,18 @@ type Memory interface {
 	Write(ctx context.Context, offset uint32, v []byte) bool
 }
 
+// EncodeExternref encodes the input as a ValueTypeExternref.
+// See DecodeExternref
+func EncodeExternref(input uintptr) uint64 {
+	return uint64(input)
+}
+
+// DecodeExternref decodes the input as a ValueTypeExternref.
+// See EncodeExternref
+func DecodeExternref(input uint64) uintptr {
+	return uintptr(input)
+}
+
 // EncodeI32 encodes the input as a ValueTypeI32.
 func EncodeI32(input int32) uint64 {
 	return uint64(uint32(input))
@@ -344,13 +356,13 @@ func EncodeF32(input float32) uint64 {
 }
 
 // DecodeF32 decodes the input as a ValueTypeF32.
-// See DecodeF32
+// See EncodeF32
 func DecodeF32(input uint64) float32 {
 	return math.Float32frombits(uint32(input))
 }
 
 // EncodeF64 encodes the input as a ValueTypeF64.
-// See DecodeF64
+// See EncodeF32
 func EncodeF64(input float64) uint64 {
 	return math.Float64bits(input)
 }
diff --git a/internal/wasm/gofunc.go b/internal/wasm/gofunc.go
index 4f21eff5..ccd48c52 100644
--- a/internal/wasm/gofunc.go
+++ b/internal/wasm/gofunc.go
@@ -96,15 +96,18 @@ func CallGoFunc(ctx context.Context, callCtx *CallContext, f *FunctionInstance,
 
 		for _, raw := range params {
 			val := reflect.New(tp.In(i)).Elem()
-			switch tp.In(i).Kind() {
+			k := tp.In(i).Kind()
+			switch k {
 			case reflect.Float32:
 				val.SetFloat(float64(math.Float32frombits(uint32(raw))))
 			case reflect.Float64:
 				val.SetFloat(math.Float64frombits(raw))
-			case reflect.Uint32, reflect.Uint64:
+			case reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 				val.SetUint(raw)
 			case reflect.Int32, reflect.Int64:
 				val.SetInt(int64(raw))
+			default:
+				panic(fmt.Errorf("BUG: param[%d] has an invalid type: %v", i, k))
 			}
 			in[i] = val
 			i++
@@ -116,20 +119,18 @@ func CallGoFunc(ctx context.Context, callCtx *CallContext, f *FunctionInstance,
 	if tp.NumOut() > 0 {
 		results = make([]uint64, 0, tp.NumOut())
 	}
-	for _, ret := range f.GoFunc.Call(in) {
+	for i, ret := range f.GoFunc.Call(in) {
 		switch ret.Kind() {
 		case reflect.Float32:
 			results = append(results, uint64(math.Float32bits(float32(ret.Float()))))
 		case reflect.Float64:
 			results = append(results, math.Float64bits(ret.Float()))
-		case reflect.Uint32, reflect.Uint64:
+		case reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 			results = append(results, ret.Uint())
 		case reflect.Int32, reflect.Int64:
 			results = append(results, uint64(ret.Int()))
-		case reflect.Uintptr:
-			results = append(results, ret.Uint())
 		default:
-			panic("BUG: invalid return type")
+			panic(fmt.Errorf("BUG: result[%d] has an invalid type: %v", i, ret.Kind()))
 		}
 	}
 	return results


Test patch:
diff --git a/api/wasm_test.go b/api/wasm_test.go
index 8f409566..0f154f38 100644
--- a/api/wasm_test.go
+++ b/api/wasm_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"math"
 	"testing"
+	"unsafe"
 
 	"github.com/tetratelabs/wazero/internal/testing/require"
 )
@@ -40,6 +41,7 @@ func TestValueTypeName(t *testing.T) {
 		{"i64", ValueTypeI64, "i64"},
 		{"f32", ValueTypeF32, "f32"},
 		{"f64", ValueTypeF64, "f64"},
+		{"externref", ValueTypeExternref, "externref"},
 		{"unknown", 100, "unknown"},
 	}
 
@@ -52,6 +54,18 @@ func TestValueTypeName(t *testing.T) {
 	}
 }
 
+func TestEncodeDecodeExternRef(t *testing.T) {
+	for _, v := range []uintptr{
+		0, uintptr(unsafe.Pointer(t)),
+	} {
+		t.Run(fmt.Sprintf("%x", v), func(t *testing.T) {
+			encoded := EncodeExternref(v)
+			binary := DecodeExternref(encoded)
+			require.Equal(t, v, binary)
+		})
+	}
+}
+
 func TestEncodeDecodeF32(t *testing.T) {
 	for _, v := range []float32{
 		0, 100, -100, 1, -1,
diff --git a/internal/wasm/gofunc_test.go b/internal/wasm/gofunc_test.go
index b3852aa2..386564b8 100644
--- a/internal/wasm/gofunc_test.go
+++ b/internal/wasm/gofunc_test.go
@@ -5,6 +5,7 @@ import (
 	"math"
 	"reflect"
 	"testing"
+	"unsafe"
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/testing/require"
@@ -46,27 +47,32 @@ func TestGetFunctionType(t *testing.T) {
 		},
 		{
 			name:         "all supported params and i32 result",
-			inputFunc:    func(uint32, uint64, float32, float64) uint32 { return 0 },
+			inputFunc:    func(uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
 			expectedKind: FunctionKindGoNoContext,
-			expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, Results: []ValueType{i32}},
+			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) (uint32, uint64, float32, float64) { return 0, 0, 0, 0 },
+			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}, Results: []ValueType{i32, i64, f32, f64}},
+			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) uint32 { return 0 },
+			inputFunc:    func(api.Module, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
 			expectedKind: FunctionKindGoModule,
-			expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, Results: []ValueType{i32}},
+			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) uint32 { return 0 },
+			inputFunc:    func(context.Context, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
 			expectedKind: FunctionKindGoContext,
-			expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, Results: []ValueType{i32}},
+			expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}},
 		},
 		{
 			name:         "all supported params and i32 result - context.Context and api.Module",
@@ -222,23 +228,23 @@ func TestPopGoFuncParams(t *testing.T) {
 		},
 		{
 			name:      "all supported params",
-			inputFunc: func(uint32, uint64, float32, float64) {},
-			expected:  []uint64{4, 5, 6, 7},
+			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) {},
-			expected:  []uint64{4, 5, 6, 7},
+			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) {},
-			expected:  []uint64{4, 5, 6, 7},
+			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) {},
-			expected:  []uint64{4, 5, 6, 7},
+			inputFunc: func(context.Context, api.Module, uint32, uint64, float32, float64, uintptr) {},
+			expected:  []uint64{3, 4, 5, 6, 7},
 		},
 	}
 
@@ -257,7 +263,9 @@ func TestPopGoFuncParams(t *testing.T) {
 }
 
 func TestCallGoFunc(t *testing.T) {
+	tPtr := uintptr(unsafe.Pointer(t))
 	callCtx := &CallContext{}
+	callCtxPtr := uintptr(unsafe.Pointer(callCtx))
 
 	var tests = []struct {
 		name                         string
@@ -289,7 +297,8 @@ func TestCallGoFunc(t *testing.T) {
 		},
 		{
 			name: "all supported params and i32 result",
-			inputFunc: func(w uint32, x uint64, y float32, z float64) uint32 {
+			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)
@@ -297,6 +306,7 @@ func TestCallGoFunc(t *testing.T) {
 				return 100
 			},
 			inputParams: []uint64{
+				api.EncodeExternref(tPtr),
 				math.MaxUint32,
 				math.MaxUint64,
 				api.EncodeF32(math.MaxFloat32),
@@ -306,20 +316,23 @@ func TestCallGoFunc(t *testing.T) {
 		},
 		{
 			name: "all supported params and all supported results",
-			inputFunc: func(w uint32, x uint64, y float32, z float64) (uint32, uint64, float32, float64) {
+			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 100, 200, 300, 400
+				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),
@@ -328,8 +341,9 @@ func TestCallGoFunc(t *testing.T) {
 		},
 		{
 			name: "all supported params and i32 result - wasm.Module",
-			inputFunc: func(m api.Module, w uint32, x uint64, y float32, z float64) uint32 {
+			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)
@@ -337,6 +351,7 @@ func TestCallGoFunc(t *testing.T) {
 				return 100
 			},
 			inputParams: []uint64{
+				api.EncodeExternref(tPtr),
 				math.MaxUint32,
 				math.MaxUint64,
 				api.EncodeF32(math.MaxFloat32),
@@ -346,8 +361,9 @@ func TestCallGoFunc(t *testing.T) {
 		},
 		{
 			name: "all supported params and i32 result - context.Context",
-			inputFunc: func(ctx context.Context, w uint32, x uint64, y float32, z float64) uint32 {
+			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)
@@ -355,6 +371,7 @@ func TestCallGoFunc(t *testing.T) {
 				return 100
 			},
 			inputParams: []uint64{
+				api.EncodeExternref(tPtr),
 				math.MaxUint32,
 				math.MaxUint64,
 				api.EncodeF32(math.MaxFloat32),
@@ -364,9 +381,10 @@ func TestCallGoFunc(t *testing.T) {
 		},
 		{
 			name: "all supported params and i32 result - context.Context and api.Module",
-			inputFunc: func(ctx context.Context, m api.Module, w uint32, x uint64, y float32, z float64) uint32 {
+			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)
@@ -374,6 +392,7 @@ func TestCallGoFunc(t *testing.T) {
 				return 100
 			},
 			inputParams: []uint64{
+				api.EncodeExternref(tPtr),
 				math.MaxUint32,
 				math.MaxUint64,
 				api.EncodeF32(math.MaxFloat32),