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:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user