Supports functions with multiple results (multi-value) (#446)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-04-13 09:22:39 +08:00
committed by GitHub
parent 927914ffb0
commit c3ff16d596
68 changed files with 5555 additions and 1155 deletions

View File

@@ -11,7 +11,7 @@ import (
// DecodeModule implements wasm.DecodeModule for the WebAssembly 1.0 (20191205) Binary Format
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
func DecodeModule(binary []byte, _ wasm.Features, memoryMaxPages uint32) (*wasm.Module, error) {
func DecodeModule(binary []byte, enabledFeatures wasm.Features, memoryMaxPages uint32) (*wasm.Module, error) {
r := bytes.NewReader(binary)
// Magic number.
@@ -69,7 +69,7 @@ func DecodeModule(binary []byte, _ wasm.Features, memoryMaxPages uint32) (*wasm.
}
case wasm.SectionIDType:
m.TypeSection, err = decodeTypeSection(r)
m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
case wasm.SectionIDImport:
if m.ImportSection, err = decodeImportSection(r, memoryMaxPages); err != nil {
return nil, err // avoid re-wrapping the error.

View File

@@ -1,6 +1,10 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
@@ -46,7 +50,50 @@ func encodeFunctionType(t *wasm.FunctionType) []byte {
}
return append(append([]byte{0x60}, encodeValTypes(t.Params)...), 1, t.Results[0])
}
// This branch should never be reaches as WebAssembly 1.0 (20191205) supports at most 1 result
// Only reached when "multi-value" is enabled because WebAssembly 1.0 (20191205) supports at most 1 result.
data := append([]byte{0x60}, encodeValTypes(t.Params)...)
return append(data, encodeValTypes(t.Results)...)
}
func decodeFunctionType(enabledFeatures wasm.Features, r *bytes.Reader) (*wasm.FunctionType, error) {
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read leading byte: %w", err)
}
if b != 0x60 {
return nil, fmt.Errorf("%w: %#x != 0x60", ErrInvalidByte, b)
}
paramCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("could not read parameter count: %w", err)
}
paramTypes, err := decodeValueTypes(r, paramCount)
if err != nil {
return nil, fmt.Errorf("could not read parameter types: %w", err)
}
resultCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("could not read result count: %w", err)
}
// Guard >1.0 feature multi-value
if resultCount > 1 {
if err = enabledFeatures.Require(wasm.FeatureMultiValue); err != nil {
return nil, fmt.Errorf("multiple result types invalid as %v", err)
}
}
resultTypes, err := decodeValueTypes(r, resultCount)
if err != nil {
return nil, fmt.Errorf("could not read result types: %w", err)
}
return &wasm.FunctionType{
Params: paramTypes,
Results: resultTypes,
}, nil
}

View File

@@ -1,6 +1,8 @@
package binary
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/require"
@@ -8,7 +10,7 @@ import (
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestEncodeFunctionType(t *testing.T) {
func TestFunctionType(t *testing.T) {
i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
tests := []struct {
name string
@@ -25,53 +27,38 @@ func TestEncodeFunctionType(t *testing.T) {
input: &wasm.FunctionType{Params: []wasm.ValueType{i32}},
expected: []byte{0x60, 1, i32, 0},
},
{
name: "undefined param no result", // ensure future spec changes don't panic
input: &wasm.FunctionType{Params: []wasm.ValueType{0x6f}},
expected: []byte{0x60, 1, 0x6f, 0},
},
{
name: "no param one result",
input: &wasm.FunctionType{Results: []wasm.ValueType{i32}},
expected: []byte{0x60, 0, 1, i32},
},
{
name: "no param undefined result", // ensure future spec changes don't panic
input: &wasm.FunctionType{Results: []wasm.ValueType{0x6f}},
expected: []byte{0x60, 0, 1, 0x6f},
},
{
name: "one param one result",
input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}},
expected: []byte{0x60, 1, i64, 1, i32},
},
{
name: "undefined param undefined result", // ensure future spec changes don't panic
input: &wasm.FunctionType{Params: []wasm.ValueType{0x6f}, Results: []wasm.ValueType{0x6f}},
expected: []byte{0x60, 1, 0x6f, 1, 0x6f},
},
{
name: "two params no result",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 2, i32, i64, 0},
},
{
name: "no param two results", // this is just for coverage as WebAssembly 1.0 (20191205) does not allow it!
input: &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 0, 2, i32, i64},
},
{
name: "one param two results", // this is just for coverage as WebAssembly 1.0 (20191205) does not allow it!
input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 1, i64, 2, i32, i64},
},
{
name: "two param one result",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32}},
expected: []byte{0x60, 2, i32, i64, 1, i32},
},
{
name: "two param two results", // this is just for coverage as WebAssembly 1.0 (20191205) does not allow it!
name: "no param two results",
input: &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 0, 2, i32, i64},
},
{
name: "one param two results",
input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 1, i64, 2, i32, i64},
},
{
name: "two param two results",
input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32, i64}},
expected: []byte{0x60, 2, i32, i64, 2, i32, i64},
},
@@ -80,9 +67,65 @@ func TestEncodeFunctionType(t *testing.T) {
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
bytes := encodeFunctionType(tc.input)
require.Equal(t, tc.expected, bytes)
b := encodeFunctionType(tc.input)
t.Run(fmt.Sprintf("encode - %s", tc.name), func(t *testing.T) {
require.Equal(t, tc.expected, b)
})
t.Run(fmt.Sprintf("decode - %s", tc.name), func(t *testing.T) {
binary, err := decodeFunctionType(wasm.FeaturesFinished, bytes.NewReader(b))
require.NoError(t, err)
require.Equal(t, binary, tc.input)
})
}
}
func TestDecodeFunctionType_Errors(t *testing.T) {
i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
tests := []struct {
name string
input []byte
enabledFeatures wasm.Features
expectedErr string
}{
{
name: "undefined param no result",
input: []byte{0x60, 1, 0x6f, 0},
expectedErr: "could not read parameter types: invalid value type: 111",
},
{
name: "no param undefined result",
input: []byte{0x60, 0, 1, 0x6f},
expectedErr: "could not read result types: invalid value type: 111",
},
{
name: "undefined param undefined result",
input: []byte{0x60, 1, 0x6f, 1, 0x6f},
expectedErr: "could not read parameter types: invalid value type: 111",
},
{
name: "no param two results - multi-value not enabled",
input: []byte{0x60, 0, 2, i32, i64},
expectedErr: "multiple result types invalid as feature \"multi-value\" is disabled",
},
{
name: "one param two results - multi-value not enabled",
input: []byte{0x60, 1, i64, 2, i32, i64},
expectedErr: "multiple result types invalid as feature \"multi-value\" is disabled",
},
{
name: "two param two results - multi-value not enabled",
input: []byte{0x60, 2, i32, i64, 2, i32, i64},
expectedErr: "multiple result types invalid as feature \"multi-value\" is disabled",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
_, err := decodeFunctionType(wasm.Features20191205, bytes.NewReader(tc.input))
require.EqualError(t, err, tc.expectedErr)
})
}
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeTypeSection(r *bytes.Reader) ([]*wasm.FunctionType, error) {
func decodeTypeSection(enabledFeatures wasm.Features, r *bytes.Reader) ([]*wasm.FunctionType, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
@@ -16,51 +16,13 @@ func decodeTypeSection(r *bytes.Reader) ([]*wasm.FunctionType, error) {
result := make([]*wasm.FunctionType, vs)
for i := uint32(0); i < vs; i++ {
if result[i], err = decodeFunctionType(r); err != nil {
if result[i], err = decodeFunctionType(enabledFeatures, r); err != nil {
return nil, fmt.Errorf("read %d-th type: %v", i, err)
}
}
return result, nil
}
func decodeFunctionType(r *bytes.Reader) (*wasm.FunctionType, error) {
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read leading byte: %w", err)
}
if b != 0x60 {
return nil, fmt.Errorf("%w: %#x != 0x60", ErrInvalidByte, b)
}
s, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("could not read parameter count: %w", err)
}
paramTypes, err := decodeValueTypes(r, s)
if err != nil {
return nil, fmt.Errorf("could not read parameter types: %w", err)
}
s, _, err = leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("could not read result count: %w", err)
} else if s > 1 {
return nil, fmt.Errorf("multi value results not supported")
}
resultTypes, err := decodeValueTypes(r, s)
if err != nil {
return nil, fmt.Errorf("could not read result types: %w", err)
}
return &wasm.FunctionType{
Params: paramTypes,
Results: resultTypes,
}, nil
}
func decodeImportSection(r *bytes.Reader, memoryMaxPages uint32) ([]*wasm.Import, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {