api: adds Encode and Decode functions for int32 and uint32 (#871)

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Takeshi Yoneda
2022-11-30 14:29:00 +09:00
committed by GitHub
parent 721327decc
commit 0a52a732f0
2 changed files with 81 additions and 10 deletions

View File

@@ -53,7 +53,7 @@ func ExternTypeName(et ExternType) string {
//
// The following describes how to convert between Wasm and Golang types:
//
// - ValueTypeI32 - uint64(uint32,int32)
// - ValueTypeI32 - EncodeU32 DecodeU32 for uint32 / EncodeI32 DecodeI32 for int32
// - ValueTypeI64 - uint64(int64)
// - ValueTypeF32 - EncodeF32 DecodeF32 from float32
// - ValueTypeF64 - EncodeF64 DecodeF64 from float64
@@ -300,6 +300,9 @@ type Function interface {
// Call is not goroutine-safe, therefore it is recommended to create
// another Function if you want to invoke the same function concurrently.
// On the other hand, sequential invocations of Call is allowed.
//
// To safely encode/decode params/results expressed as uint64, users are encouraged to
// use api.EncodeXXX or DecodeXXX functions. See the docs on api.ValueType.
Call(ctx context.Context, params ...uint64) ([]uint64, error)
}
@@ -315,10 +318,10 @@ type Function interface {
// Here's a typical way to read three parameters and write back one.
//
// // read parameters off the stack in index order
// argv, argvBuf := uint32(stack[0]), uint32(stack[1])
// argv, argvBuf := api.DecodeU32(stack[0]), api.DecodeU32(stack[1])
//
// // write results back to the stack in index order
// stack[0] = uint64(ErrnoSuccess)
// stack[0] = api.EncodeU32(ErrnoSuccess)
//
// This function can be non-deterministic or cause side effects. It also
// has special properties not defined in the WebAssembly Core specification.
@@ -329,23 +332,26 @@ type Function interface {
// use reflection or code generators instead. These approaches are more
// idiomatic as they can map go types to ValueType. This type is exposed for
// those willing to trade usability and safety for performance.
//
// To safely decode/encode values from/to the uint64 stack, users are encouraged to use
// api.EncodeXXX or api.DecodeXXX functions. See the docs on api.ValueType.
type GoModuleFunction interface {
Call(ctx context.Context, mod Module, stack []uint64)
}
// GoModuleFunc is a convenience for defining an inlined function.
//
// For example, the following returns a uint32 value read from parameter zero:
// For example, the following returns an uint32 value read from parameter zero:
//
// api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) {
// offset := uint32(params[0]) // read the parameter from the stack
// offset := api.DecodeU32(params[0]) // read the parameter from the stack
//
// ret, ok := mod.Memory().ReadUint32Le(ctx, offset)
// if !ok {
// panic("out of memory")
// }
//
// results[0] = uint64(ret) // add the result back to the stack.
// results[0] = api.EncodeU32(ret) // add the result back to the stack.
// })
type GoModuleFunc func(ctx context.Context, mod Module, stack []uint64)
@@ -368,8 +374,8 @@ type GoFunction interface {
// For example, the following returns the sum of two uint32 parameters:
//
// api.GoFunc(func(ctx context.Context, stack []uint64) {
// x, y := uint32(params[0]), uint32(params[1])
// results[0] = uint64(x + y)
// x, y := api.DecodeU32(params[0]), api.DecodeU32(params[1])
// results[0] = api.EncodeU32(x + y)
// })
type GoFunc func(ctx context.Context, stack []uint64)
@@ -560,6 +566,21 @@ func EncodeI32(input int32) uint64 {
return uint64(uint32(input))
}
// DecodeI32 decodes the input as a ValueTypeI32.
func DecodeI32(input uint64) int32 {
return int32(input)
}
// EncodeU32 encodes the input as a ValueTypeI32.
func EncodeU32(input uint32) uint64 {
return uint64(input)
}
// DecodeU32 decodes the input as a ValueTypeI32.
func DecodeU32(input uint64) uint32 {
return uint32(input)
}
// EncodeI64 encodes the input as a ValueTypeI64.
func EncodeI64(input int64) uint64 {
return uint64(input)

View File

@@ -111,7 +111,7 @@ func TestEncodeDecodeF64(t *testing.T) {
}
}
func TestEncodeCastI32(t *testing.T) {
func TestEncodeI32(t *testing.T) {
for _, v := range []int32{
0, 100, -100, 1, -1,
math.MaxInt32,
@@ -126,7 +126,57 @@ func TestEncodeCastI32(t *testing.T) {
}
}
func TestEncodeCastI64(t *testing.T) {
func TestDecodeI32(t *testing.T) {
mini32 := math.MinInt32
for _, tc := range []struct {
in uint64
exp int32
}{
{in: 0, exp: 0},
{in: 1 << 60, exp: 0},
{in: 1 << 30, exp: 1 << 30},
{in: 1<<30 | 1<<60, exp: 1 << 30},
{in: uint64(uint32(mini32)) | 1<<59, exp: math.MinInt32},
{in: uint64(uint32(math.MaxInt32)) | 1<<50, exp: math.MaxInt32},
} {
decoded := DecodeI32(tc.in)
require.Equal(t, tc.exp, decoded)
}
}
func TestEncodeU32(t *testing.T) {
for _, v := range []uint32{
0, 100, 1, 1 << 31,
math.MaxInt32,
math.MaxUint32,
} {
t.Run(fmt.Sprintf("%d", v), func(t *testing.T) {
encoded := EncodeU32(v)
require.Zero(t, encoded>>32) // Ensures high bits aren't set
require.Equal(t, v, uint32(encoded))
})
}
}
func TestDecodeU32(t *testing.T) {
mini32 := math.MinInt32
for _, tc := range []struct {
in uint64
exp uint32
}{
{in: 0, exp: 0},
{in: 1 << 60, exp: 0},
{in: 1 << 30, exp: 1 << 30},
{in: 1<<30 | 1<<60, exp: 1 << 30},
{in: uint64(uint32(mini32)) | 1<<59, exp: uint32(mini32)},
{in: uint64(uint32(math.MaxInt32)) | 1<<50, exp: math.MaxInt32},
} {
decoded := DecodeU32(tc.in)
require.Equal(t, tc.exp, decoded)
}
}
func TestEncodeI64(t *testing.T) {
for _, v := range []int64{
0, 100, -100, 1, -1,
math.MaxInt64,