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:
10
api/wasm.go
10
api/wasm.go
@@ -248,6 +248,16 @@ type Memory interface {
|
||||
Write(offset uint32, v []byte) bool
|
||||
}
|
||||
|
||||
// EncodeI32 encodes the input as a ValueTypeI32.
|
||||
func EncodeI32(input int32) uint64 {
|
||||
return uint64(uint32(input))
|
||||
}
|
||||
|
||||
// EncodeI64 encodes the input as a ValueTypeI64.
|
||||
func EncodeI64(input int64) uint64 {
|
||||
return uint64(input)
|
||||
}
|
||||
|
||||
// EncodeF32 encodes the input as a ValueTypeF32.
|
||||
// See DecodeF32
|
||||
func EncodeF32(input float32) uint64 {
|
||||
|
||||
@@ -41,6 +41,7 @@ func TestEncodeDecodeF32(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
|
||||
encoded := EncodeF32(v)
|
||||
binary := DecodeF32(encoded)
|
||||
require.Zero(t, encoded>>32) // Ensures high bits aren't set
|
||||
if math.IsNaN(float64(binary)) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
||||
require.True(t, math.IsNaN(float64(binary)))
|
||||
} else {
|
||||
@@ -73,3 +74,32 @@ func TestEncodeDecodeF64(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeCastI32(t *testing.T) {
|
||||
for _, v := range []int32{
|
||||
0, 100, -100, 1, -1,
|
||||
math.MaxInt32,
|
||||
math.MinInt32,
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%d", v), func(t *testing.T) {
|
||||
encoded := EncodeI32(v)
|
||||
require.Zero(t, encoded>>32) // Ensures high bits aren't set
|
||||
binary := int32(encoded)
|
||||
require.Equal(t, v, binary)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeCastI64(t *testing.T) {
|
||||
for _, v := range []int64{
|
||||
0, 100, -100, 1, -1,
|
||||
math.MaxInt64,
|
||||
math.MinInt64,
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%d", v), func(t *testing.T) {
|
||||
encoded := EncodeI64(v)
|
||||
binary := int64(encoded)
|
||||
require.Equal(t, v, binary)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,8 +247,13 @@ func (b *moduleBuilder) Build() (*CompiledCode, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we can use r.enabledFeatures to fail early on things like mutable globals
|
||||
if module, err := wasm.NewHostModule(b.moduleName, b.nameToGoFunc, b.nameToMemory, b.nameToGlobal); err != nil {
|
||||
if module, err := wasm.NewHostModule(
|
||||
b.moduleName,
|
||||
b.nameToGoFunc,
|
||||
b.nameToMemory,
|
||||
b.nameToGlobal,
|
||||
b.r.enabledFeatures,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &CompiledCode{module: module}, nil
|
||||
|
||||
44
config.go
44
config.go
@@ -16,25 +16,25 @@ import (
|
||||
|
||||
// RuntimeConfig controls runtime behavior, with the default implementation as NewRuntimeConfig
|
||||
type RuntimeConfig struct {
|
||||
newEngine func() wasm.Engine
|
||||
ctx context.Context
|
||||
enabledFeatures wasm.Features
|
||||
newEngine func(wasm.Features) wasm.Engine
|
||||
ctx context.Context
|
||||
memoryMaxPages uint32
|
||||
}
|
||||
|
||||
// engineLessConfig helps avoid copy/pasting the wrong defaults.
|
||||
var engineLessConfig = &RuntimeConfig{
|
||||
ctx: context.Background(),
|
||||
enabledFeatures: wasm.Features20191205,
|
||||
ctx: context.Background(),
|
||||
memoryMaxPages: wasm.MemoryMaxPages,
|
||||
}
|
||||
|
||||
// clone ensures all fields are coped even if nil.
|
||||
func (c *RuntimeConfig) clone() *RuntimeConfig {
|
||||
return &RuntimeConfig{
|
||||
enabledFeatures: c.enabledFeatures,
|
||||
newEngine: c.newEngine,
|
||||
ctx: c.ctx,
|
||||
enabledFeatures: c.enabledFeatures,
|
||||
memoryMaxPages: c.memoryMaxPages,
|
||||
}
|
||||
}
|
||||
@@ -89,10 +89,23 @@ func (c *RuntimeConfig) WithMemoryMaxPages(memoryMaxPages uint32) *RuntimeConfig
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFinishedFeatures enables currently supported "finished" feature proposals. Use this to improve compatibility with
|
||||
// tools that enable all features by default.
|
||||
//
|
||||
// Note: The features implied can vary and can lead to unpredictable behavior during updates.
|
||||
// Note: This only includes "finished" features, but "finished" is not an official W3C term: it is possible that
|
||||
// "finished" features do not make the next W3C recommended WebAssembly core specification.
|
||||
// See https://github.com/WebAssembly/spec/tree/main/proposals
|
||||
func (c *RuntimeConfig) WithFinishedFeatures() *RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = wasm.FeaturesFinished
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureMutableGlobal allows globals to be mutable. This defaults to true as the feature was finished in
|
||||
// WebAssembly 1.0 (20191205).
|
||||
//
|
||||
// When false, a api.Global can never be cast to a api.MutableGlobal, and any source that includes global vars
|
||||
// When false, an api.Global can never be cast to an api.MutableGlobal, and any source that includes global vars
|
||||
// will fail to parse.
|
||||
func (c *RuntimeConfig) WithFeatureMutableGlobal(enabled bool) *RuntimeConfig {
|
||||
ret := c.clone()
|
||||
@@ -100,8 +113,11 @@ func (c *RuntimeConfig) WithFeatureMutableGlobal(enabled bool) *RuntimeConfig {
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureSignExtensionOps enables sign-extend operations. This defaults to false as the feature was not finished in
|
||||
// WebAssembly 1.0 (20191205).
|
||||
// WithFeatureSignExtensionOps enables sign extension instructions ("sign-extension-ops"). This defaults to false as the
|
||||
// feature was not finished in WebAssembly 1.0 (20191205).
|
||||
//
|
||||
// This has the following effects:
|
||||
// * Adds instructions `i32.extend8_s`, `i32.extend16_s`, `i64.extend8_s`, `i64.extend16_s` and `i64.extend32_s`
|
||||
//
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
func (c *RuntimeConfig) WithFeatureSignExtensionOps(enabled bool) *RuntimeConfig {
|
||||
@@ -110,6 +126,20 @@ func (c *RuntimeConfig) WithFeatureSignExtensionOps(enabled bool) *RuntimeConfig
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureMultiValue enables multiple values ("multi-value"). This defaults to false as the feature was not finished
|
||||
// in WebAssembly 1.0 (20191205).
|
||||
//
|
||||
// This has the following effects:
|
||||
// * Function (`func`) types allow more than one result
|
||||
// * Block types (`block`, `loop` and `if`) can be arbitrary function types
|
||||
//
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
func (c *RuntimeConfig) WithFeatureMultiValue(enabled bool) *RuntimeConfig {
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMultiValue, enabled)
|
||||
return ret
|
||||
}
|
||||
|
||||
// CompiledCode is a WebAssembly 1.0 (20191205) module ready to be instantiated (Runtime.InstantiateModule) as an\
|
||||
// api.Module.
|
||||
//
|
||||
|
||||
165
examples/results_test.go
Normal file
165
examples/results_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Test_MultiReturn_V1_0 implements functions with multiple returns values, using an approach portable with any
|
||||
// WebAssembly 1.0 (20191205) runtime.
|
||||
//
|
||||
// Note: This is the same approach used by WASI snapshot-01!
|
||||
// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
|
||||
func Test_MultipleResults(t *testing.T) {
|
||||
r := wazero.NewRuntime()
|
||||
|
||||
// Instantiate a module that illustrates multiple results using functions defined as WebAssembly instructions.
|
||||
wasm, err := resultOffsetWasmFunctions(r)
|
||||
require.NoError(t, err)
|
||||
defer wasm.Close()
|
||||
|
||||
// Instantiate a module that illustrates multiple results using functions defined in Go.
|
||||
host, err := resultOffsetHostFunctions(r)
|
||||
require.NoError(t, err)
|
||||
defer wasm.Close()
|
||||
|
||||
// Prove both implementations have the same effects!
|
||||
for _, mod := range []api.Module{wasm, host} {
|
||||
callGetNumber := mod.ExportedFunction("call_get_age")
|
||||
results, err := callGetNumber.Call(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{37}, results)
|
||||
}
|
||||
}
|
||||
|
||||
// resultOffsetWasmFunctions defines Wasm functions that illustrate multiple results using a technique compatible
|
||||
// with any WebAssembly 1.0 (20191205) runtime.
|
||||
//
|
||||
// To return a value in WASM written to a result parameter, you have to define memory and pass a location to write
|
||||
// the result. At the end of your function, you load that location.
|
||||
func resultOffsetWasmFunctions(r wazero.Runtime) (api.Module, error) {
|
||||
return r.InstantiateModuleFromCode([]byte(`(module $result-offset/wasm
|
||||
;; To use result parameters, we need scratch memory. Allocate the least possible: 1 page (64KB).
|
||||
(memory 1 1)
|
||||
|
||||
;; Define a function that returns a result, while a second result is written to memory.
|
||||
(func $get_age (param $result_offset.age i32) (result (;errno;) i32)
|
||||
local.get 0 ;; stack = [$result_offset.age]
|
||||
i64.const 37 ;; stack = [$result_offset.age, 37]
|
||||
i64.store ;; stack = []
|
||||
i32.const 0 ;; stack = [0]
|
||||
)
|
||||
|
||||
;; Now, define a function that shows the Wasm mechanics returning something written to a result parameter.
|
||||
;; The caller provides a memory offset to the callee, so that it knows where to write the second result.
|
||||
(func $call_get_age (result i64)
|
||||
i32.const 8 ;; stack = [8] $result_offset.age parameter to get_age (arbitrary memory offset 8)
|
||||
call $get_age ;; stack = [errno] result of get_age
|
||||
drop ;; stack = []
|
||||
|
||||
i32.const 8 ;; stack = [8] same value as the $result_offset.age parameter
|
||||
i64.load ;; stack = [age]
|
||||
)
|
||||
|
||||
;; Export the function, so that we can test it!
|
||||
(export "call_get_age" (func $call_get_age))
|
||||
)`))
|
||||
}
|
||||
|
||||
// resultOffsetHostFunctions defines host functions that illustrate multiple results using a technique compatible
|
||||
// with any WebAssembly 1.0 (20191205) runtime.
|
||||
//
|
||||
// To return a value in WASM written to a result parameter, you have to define memory and pass a location to write
|
||||
// the result. At the end of your function, you load that location.
|
||||
func resultOffsetHostFunctions(r wazero.Runtime) (api.Module, error) {
|
||||
return r.NewModuleBuilder("result-offset/host").
|
||||
// To use result parameters, we need scratch memory. Allocate the least possible: 1 page (64KB).
|
||||
ExportMemoryWithMax("mem", 1, 1).
|
||||
// Define a function that returns a result, while a second result is written to memory.
|
||||
ExportFunction("get_age", func(m api.Module, resultOffsetAge uint32) (errno uint32) {
|
||||
if m.Memory().WriteUint64Le(resultOffsetAge, 37) {
|
||||
return 0
|
||||
}
|
||||
return 1 // overflow
|
||||
}).
|
||||
// Now, define a function that shows the Wasm mechanics returning something written to a result parameter.
|
||||
// The caller provides a memory offset to the callee, so that it knows where to write the second result.
|
||||
ExportFunction("call_get_age", func(m api.Module) (age uint64) {
|
||||
resultOffsetAge := uint32(8) // arbitrary memory offset (in bytes)
|
||||
_, _ = m.ExportedFunction("get_age").Call(m, uint64(resultOffsetAge))
|
||||
age, _ = m.Memory().ReadUint64Le(resultOffsetAge)
|
||||
return
|
||||
}).Instantiate()
|
||||
}
|
||||
|
||||
// Test_MultipleResults_MultiValue implements functions with multiple returns values naturally, due to enabling the
|
||||
// "multi-value" feature.
|
||||
//
|
||||
// Note: While "multi-value" is not yet a W3C recommendation, most WebAssembly runtimes support it by default.
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
func Test_MultipleResults_MultiValue(t *testing.T) {
|
||||
// wazero enables only W3C recommended features by default. Opt-in to other features like so:
|
||||
r := wazero.NewRuntimeWithConfig(
|
||||
wazero.NewRuntimeConfig().WithFeatureMultiValue(true),
|
||||
// ^^ Note: You can enable all features via WithFinishedFeatures.
|
||||
)
|
||||
|
||||
// Instantiate a module that illustrates multi-value functions defined as WebAssembly instructions.
|
||||
wasm, err := multiValueWasmFunctions(r)
|
||||
require.NoError(t, err)
|
||||
defer wasm.Close()
|
||||
|
||||
// Instantiate a module that illustrates multi-value functions using functions defined in Go.
|
||||
host, err := multiValueHostFunctions(r)
|
||||
require.NoError(t, err)
|
||||
defer wasm.Close()
|
||||
|
||||
// Prove both implementations have the same effects!
|
||||
for _, mod := range []api.Module{wasm, host} {
|
||||
callGetNumber := mod.ExportedFunction("call_get_age")
|
||||
results, err := callGetNumber.Call(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{37}, results)
|
||||
}
|
||||
}
|
||||
|
||||
// multiValueWasmFunctions defines Wasm functions that illustrate multiple results using the "multi-value" feature.
|
||||
func multiValueWasmFunctions(r wazero.Runtime) (api.Module, error) {
|
||||
return r.InstantiateModuleFromCode([]byte(`(module $multi-value/wasm
|
||||
|
||||
;; Define a function that returns two results
|
||||
(func $get_age (result (;age;) i64 (;errno;) i32)
|
||||
i64.const 37 ;; stack = [37]
|
||||
i32.const 0 ;; stack = [37, 0]
|
||||
)
|
||||
|
||||
;; Now, define a function that returns only the first result.
|
||||
(func $call_get_age (result i64)
|
||||
call $get_age ;; stack = [37, errno] result of get_age
|
||||
drop ;; stack = [37]
|
||||
)
|
||||
|
||||
;; Export the function, so that we can test it!
|
||||
(export "call_get_age" (func $call_get_age))
|
||||
)`))
|
||||
}
|
||||
|
||||
// multiValueHostFunctions defines Wasm functions that illustrate multiple results using the "multi-value" feature.
|
||||
func multiValueHostFunctions(r wazero.Runtime) (api.Module, error) {
|
||||
return r.NewModuleBuilder("multi-value/host").
|
||||
// Define a function that returns two results
|
||||
ExportFunction("get_age", func() (age uint64, errno uint32) {
|
||||
age = 37
|
||||
errno = 0
|
||||
return
|
||||
}).
|
||||
// Now, define a function that returns only the first result.
|
||||
ExportFunction("call_get_age", func(m api.Module) (age uint64) {
|
||||
results, _ := m.ExportedFunction("get_age").Call(m)
|
||||
return results[0]
|
||||
}).Instantiate()
|
||||
}
|
||||
@@ -31,12 +31,12 @@ import (
|
||||
)
|
||||
|
||||
type EngineTester interface {
|
||||
NewEngine() wasm.Engine
|
||||
NewEngine(enabledFeatures wasm.Features) wasm.Engine
|
||||
InitTable(me wasm.ModuleEngine, initTableLen uint32, initTableIdxToFnIdx map[wasm.Index]wasm.Index) []interface{}
|
||||
}
|
||||
|
||||
func RunTestEngine_NewModuleEngine(t *testing.T, et EngineTester) {
|
||||
e := et.NewEngine()
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
|
||||
t.Run("sets module name", func(t *testing.T) {
|
||||
me, err := e.NewModuleEngine(t.Name(), nil, nil, nil, nil)
|
||||
@@ -47,7 +47,7 @@ func RunTestEngine_NewModuleEngine(t *testing.T, et EngineTester) {
|
||||
}
|
||||
|
||||
func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
|
||||
e := et.NewEngine()
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
|
||||
// Define a basic function which defines one parameter. This is used to test results when incorrect arity is used.
|
||||
i64 := wasm.ValueTypeI64
|
||||
@@ -84,7 +84,7 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) {
|
||||
}
|
||||
|
||||
func RunTestEngine_NewModuleEngine_InitTable(t *testing.T, et EngineTester) {
|
||||
e := et.NewEngine()
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
|
||||
t.Run("no table elements", func(t *testing.T) {
|
||||
table := &wasm.TableInstance{Min: 2, Table: make([]interface{}, 2)}
|
||||
@@ -185,9 +185,10 @@ func runTestModuleEngine_Call_HostFn_ModuleContext(t *testing.T, et EngineTester
|
||||
return v
|
||||
})
|
||||
|
||||
e := et.NewEngine()
|
||||
features := wasm.Features20191205
|
||||
e := et.NewEngine(features)
|
||||
module := &wasm.ModuleInstance{Memory: memory}
|
||||
modCtx := wasm.NewModuleContext(context.Background(), wasm.NewStore(e, wasm.Features20191205), module, nil)
|
||||
modCtx := wasm.NewModuleContext(context.Background(), wasm.NewStore(features, e), module, nil)
|
||||
|
||||
f := &wasm.FunctionInstance{
|
||||
GoFunc: &hostFn,
|
||||
@@ -218,7 +219,7 @@ func runTestModuleEngine_Call_HostFn_ModuleContext(t *testing.T, et EngineTester
|
||||
func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
|
||||
runTestModuleEngine_Call_HostFn_ModuleContext(t, et) // TODO: refactor to use the same test interface.
|
||||
|
||||
e := et.NewEngine()
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
|
||||
imported, importedMe, importing, importingMe := setupCallTests(t, e)
|
||||
defer importingMe.Close()
|
||||
@@ -265,7 +266,7 @@ func RunTestModuleEngine_Call_HostFn(t *testing.T, et EngineTester) {
|
||||
}
|
||||
|
||||
func RunTestModuleEngine_Call_Errors(t *testing.T, et EngineTester) {
|
||||
e := et.NewEngine()
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
|
||||
imported, importedMe, importing, importingMe := setupCallTests(t, e)
|
||||
defer importingMe.Close()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -2,6 +2,7 @@ package wasm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Features are the currently enabled features.
|
||||
@@ -15,14 +16,34 @@ type Features uint64
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205
|
||||
const Features20191205 = FeatureMutableGlobal
|
||||
|
||||
// FeaturesFinished include all supported finished features, regardless of W3C status.
|
||||
//
|
||||
// See https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md
|
||||
const FeaturesFinished = 0xffffffffffffffff
|
||||
|
||||
const (
|
||||
// FeatureMutableGlobal decides if global vars are allowed to be imported or exported (ExternTypeGlobal)
|
||||
// See https://github.com/WebAssembly/mutable-global
|
||||
FeatureMutableGlobal Features = 1 << iota
|
||||
|
||||
// FeatureSignExtensionOps decides if parsing should succeed on wasm.GlobalType Mutable
|
||||
// FeatureSignExtensionOps decides if parsing should succeed on the following instructions:
|
||||
//
|
||||
// * OpcodeI32Extend8S
|
||||
// * OpcodeI32Extend16S
|
||||
// * OpcodeI64Extend8S
|
||||
// * OpcodeI64Extend16S
|
||||
// * OpcodeI64Extend32S
|
||||
//
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
FeatureSignExtensionOps
|
||||
|
||||
// FeatureMultiValue decides if parsing should succeed on the following:
|
||||
//
|
||||
// * FunctionType.Results length greater than one.
|
||||
// * `block`, `loop` and `if` can be arbitrary function types.
|
||||
//
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
FeatureMultiValue
|
||||
)
|
||||
|
||||
// Set assigns the value for the given feature.
|
||||
@@ -41,23 +62,38 @@ func (f Features) Get(feature Features) bool {
|
||||
// Require fails with a configuration error if the given feature is not enabled
|
||||
func (f Features) Require(feature Features) error {
|
||||
if f&feature == 0 {
|
||||
return fmt.Errorf("feature %s is disabled", feature)
|
||||
return fmt.Errorf("feature %q is disabled", feature)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer by returning each enabled feature.
|
||||
func (f Features) String() string {
|
||||
var builder strings.Builder
|
||||
for i := Features(0); i < 63; i++ { // cycle through all bits to reduce code and maintenance
|
||||
if f.Get(i) {
|
||||
if name := featureName(i); name != "" {
|
||||
if builder.Len() > 0 {
|
||||
builder.WriteByte('|')
|
||||
}
|
||||
builder.WriteString(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func featureName(f Features) string {
|
||||
switch f {
|
||||
case 0:
|
||||
return ""
|
||||
case FeatureMutableGlobal:
|
||||
// match https://github.com/WebAssembly/mutable-global
|
||||
return "mutable-global"
|
||||
case FeatureSignExtensionOps:
|
||||
// match https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
return "sign-extension-ops"
|
||||
default:
|
||||
return "undefined" // TODO: when there are multiple features join known ones on pipe (|)
|
||||
case FeatureMultiValue:
|
||||
// match https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
return "multi-value"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -59,7 +59,10 @@ func TestFeatures_String(t *testing.T) {
|
||||
{name: "none", feature: 0, expected: ""},
|
||||
{name: "mutable-global", feature: FeatureMutableGlobal, expected: "mutable-global"},
|
||||
{name: "sign-extension-ops", feature: FeatureSignExtensionOps, expected: "sign-extension-ops"},
|
||||
{name: "undefined", feature: 1 << 63, expected: "undefined"},
|
||||
{name: "multi-value", feature: FeatureMultiValue, expected: "multi-value"},
|
||||
{name: "features", feature: FeatureMutableGlobal | FeatureMultiValue, expected: "mutable-global|multi-value"},
|
||||
{name: "undefined", feature: 1 << 63, expected: ""},
|
||||
{name: "all", feature: FeaturesFinished, expected: "mutable-global|sign-extension-ops|multi-value"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -76,10 +79,11 @@ func TestFeatures_Require(t *testing.T) {
|
||||
feature Features
|
||||
expectedErr string
|
||||
}{
|
||||
{name: "none", feature: 0, expectedErr: "feature mutable-global is disabled"},
|
||||
{name: "none", feature: 0, expectedErr: "feature \"mutable-global\" is disabled"},
|
||||
{name: "mutable-global", feature: FeatureMutableGlobal},
|
||||
{name: "sign-extension-ops", feature: FeatureSignExtensionOps, expectedErr: "feature mutable-global is disabled"},
|
||||
{name: "undefined", feature: 1 << 63, expectedErr: "feature mutable-global is disabled"},
|
||||
{name: "sign-extension-ops", feature: FeatureSignExtensionOps, expectedErr: "feature \"mutable-global\" is disabled"},
|
||||
{name: "multi-value", feature: FeatureMultiValue, expectedErr: "feature \"mutable-global\" is disabled"},
|
||||
{name: "undefined", feature: 1 << 63, expectedErr: "feature \"mutable-global\" is disabled"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -2,37 +2,50 @@ package wasm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
)
|
||||
|
||||
// The wazero specific limitation described at RATIONALE.md.
|
||||
const maximumValuesOnStack = 1 << 27
|
||||
|
||||
// validateFunction validates the instruction sequence of a function.
|
||||
// following the specification https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#instructions%E2%91%A2.
|
||||
//
|
||||
// f is the validation target function instance.
|
||||
// functions is the list of function indexes which are declared on a module from which the target is instantiated.
|
||||
// globals is the list of global types which are declared on a module from which the target is instantiated.
|
||||
// memories is the list of memory types which are declared on a module from which the target is instantiated.
|
||||
// tables is the list of table types which are declared on a module from which the target is instantiated.
|
||||
// types is the list of function types which are declared on a module from which the target is instantiated.
|
||||
// maxStackValues is the maximum height of values stack which the target is allowed to reach.
|
||||
// * idx is the index in the FunctionSection
|
||||
// * functions are the function index namespace, which is prefixed by imports. The value is the TypeSection index.
|
||||
// * globals are the global index namespace, which is prefixed by imports.
|
||||
// * memory is the potentially imported memory and can be nil.
|
||||
// * table is the potentially imported table and can be nil.
|
||||
//
|
||||
// Returns an error if the instruction sequence is not valid,
|
||||
// or potentially it can exceed the maximum number of values on the stack.
|
||||
func validateFunction(
|
||||
func (m *Module) validateFunction(enabledFeatures Features, idx Index, functions []Index, globals []*GlobalType, memory *Memory, table *Table) error {
|
||||
return m.validateFunctionWithMaxStackValues(enabledFeatures, idx, functions, globals, memory, table, maximumValuesOnStack)
|
||||
}
|
||||
|
||||
// validateFunctionWithMaxStackValues is like validateFunction, but allows overriding maxStackValues for testing.
|
||||
//
|
||||
// * maxStackValues is the maximum height of values stack which the target is allowed to reach.
|
||||
func (m *Module) validateFunctionWithMaxStackValues(
|
||||
enabledFeatures Features,
|
||||
functionType *FunctionType,
|
||||
body []byte,
|
||||
localTypes []ValueType,
|
||||
idx Index,
|
||||
functions []Index,
|
||||
globals []*GlobalType,
|
||||
memory *Memory,
|
||||
table *Table,
|
||||
types []*FunctionType,
|
||||
maxStackValues int,
|
||||
) error {
|
||||
functionType := m.TypeSection[m.FunctionSection[idx]]
|
||||
body := m.CodeSection[idx].Body
|
||||
localTypes := m.CodeSection[idx].LocalTypes
|
||||
types := m.TypeSection
|
||||
|
||||
// We start with the outermost control block which is for function return if the code branches into it.
|
||||
controlBlockStack := []*controlBlock{{blockType: functionType}}
|
||||
// Create the valueTypeStack to track the state of Wasm value stacks at anypoint of execution.
|
||||
@@ -287,7 +300,8 @@ func validateFunction(
|
||||
case OpcodeLocalGet:
|
||||
inputLen := uint32(len(functionType.Params))
|
||||
if l := uint32(len(localTypes)) + inputLen; index >= l {
|
||||
return fmt.Errorf("invalid local index for local.get %d >= %d(=len(locals)+len(parameters))", index, l)
|
||||
return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
|
||||
OpcodeLocalGetName, index, l)
|
||||
}
|
||||
if index < inputLen {
|
||||
valueTypeStack.push(functionType.Params[index])
|
||||
@@ -297,7 +311,8 @@ func validateFunction(
|
||||
case OpcodeLocalSet:
|
||||
inputLen := uint32(len(functionType.Params))
|
||||
if l := uint32(len(localTypes)) + inputLen; index >= l {
|
||||
return fmt.Errorf("invalid local index for local.set %d >= %d(=len(locals)+len(parameters))", index, l)
|
||||
return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
|
||||
OpcodeLocalSetName, index, l)
|
||||
}
|
||||
var expType ValueType
|
||||
if index < inputLen {
|
||||
@@ -311,7 +326,8 @@ func validateFunction(
|
||||
case OpcodeLocalTee:
|
||||
inputLen := uint32(len(functionType.Params))
|
||||
if l := uint32(len(localTypes)) + inputLen; index >= l {
|
||||
return fmt.Errorf("invalid local index for local.tee %d >= %d(=len(locals)+len(parameters))", index, l)
|
||||
return fmt.Errorf("invalid local index for %s %d >= %d(=len(locals)+len(parameters))",
|
||||
OpcodeLocalTeeName, index, l)
|
||||
}
|
||||
var expType ValueType
|
||||
if index < inputLen {
|
||||
@@ -325,14 +341,14 @@ func validateFunction(
|
||||
valueTypeStack.push(expType)
|
||||
case OpcodeGlobalGet:
|
||||
if index >= uint32(len(globals)) {
|
||||
return fmt.Errorf("invalid global index")
|
||||
return fmt.Errorf("invalid index for %s", OpcodeGlobalGetName)
|
||||
}
|
||||
valueTypeStack.push(globals[index].ValType)
|
||||
case OpcodeGlobalSet:
|
||||
if index >= uint32(len(globals)) {
|
||||
return fmt.Errorf("invalid global index")
|
||||
} else if !globals[index].Mutable {
|
||||
return fmt.Errorf("global.set when not mutable")
|
||||
return fmt.Errorf("%s when not mutable", OpcodeGlobalSetName)
|
||||
} else if err := valueTypeStack.popAndVerifyType(
|
||||
globals[index].ValType); err != nil {
|
||||
return err
|
||||
@@ -344,19 +360,19 @@ func validateFunction(
|
||||
if err != nil {
|
||||
return fmt.Errorf("read immediate: %v", err)
|
||||
} else if int(index) >= len(controlBlockStack) {
|
||||
return fmt.Errorf("invalid br operation: index out of range")
|
||||
return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrName)
|
||||
}
|
||||
pc += num - 1
|
||||
// Check type soundness.
|
||||
target := controlBlockStack[len(controlBlockStack)-int(index)-1]
|
||||
targetResultType := target.blockType.Results
|
||||
if target.isLoop {
|
||||
if target.op == OpcodeLoop {
|
||||
// Loop operation doesn't require results since the continuation is
|
||||
// the beginning of the loop.
|
||||
targetResultType = []ValueType{}
|
||||
}
|
||||
if err := valueTypeStack.popResults(targetResultType, false); err != nil {
|
||||
return fmt.Errorf("type mismatch on the br operation: %v", err)
|
||||
if err = valueTypeStack.popResults(op, targetResultType, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// br instruction is stack-polymorphic.
|
||||
valueTypeStack.unreachable()
|
||||
@@ -367,23 +383,23 @@ func validateFunction(
|
||||
return fmt.Errorf("read immediate: %v", err)
|
||||
} else if int(index) >= len(controlBlockStack) {
|
||||
return fmt.Errorf(
|
||||
"invalid ln param given for br_if: index=%d with %d for the current lable stack length",
|
||||
index, len(controlBlockStack))
|
||||
"invalid ln param given for %s: index=%d with %d for the current lable stack length",
|
||||
OpcodeBrIfName, index, len(controlBlockStack))
|
||||
}
|
||||
pc += num - 1
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the required operand for br_if")
|
||||
return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrIfName)
|
||||
}
|
||||
// Check type soundness.
|
||||
target := controlBlockStack[len(controlBlockStack)-int(index)-1]
|
||||
targetResultType := target.blockType.Results
|
||||
if target.isLoop {
|
||||
if target.op == OpcodeLoop {
|
||||
// Loop operation doesn't require results since the continuation is
|
||||
// the beginning of the loop.
|
||||
targetResultType = []ValueType{}
|
||||
}
|
||||
if err := valueTypeStack.popResults(targetResultType, false); err != nil {
|
||||
return fmt.Errorf("type mismatch on the br_if operation: %v", err)
|
||||
if err := valueTypeStack.popResults(op, targetResultType, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// Push back the result
|
||||
for _, t := range targetResultType {
|
||||
@@ -411,43 +427,43 @@ func validateFunction(
|
||||
return fmt.Errorf("read immediate: %w", err)
|
||||
} else if int(ln) >= len(controlBlockStack) {
|
||||
return fmt.Errorf(
|
||||
"invalid ln param given for br_table: ln=%d with %d for the current lable stack length",
|
||||
ln, len(controlBlockStack))
|
||||
"invalid ln param given for %s: ln=%d with %d for the current lable stack length",
|
||||
OpcodeBrTableName, ln, len(controlBlockStack))
|
||||
}
|
||||
pc += n + num - 1
|
||||
// Check type soundness.
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the required operand for br_table")
|
||||
return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrTableName)
|
||||
}
|
||||
lnLabel := controlBlockStack[len(controlBlockStack)-1-int(ln)]
|
||||
expType := lnLabel.blockType.Results
|
||||
if lnLabel.isLoop {
|
||||
if lnLabel.op == OpcodeLoop {
|
||||
// Loop operation doesn't require results since the continuation is
|
||||
// the beginning of the loop.
|
||||
expType = []ValueType{}
|
||||
}
|
||||
for _, l := range list {
|
||||
if int(l) >= len(controlBlockStack) {
|
||||
return fmt.Errorf("invalid l param given for br_table")
|
||||
return fmt.Errorf("invalid l param given for %s", OpcodeBrTableName)
|
||||
}
|
||||
label := controlBlockStack[len(controlBlockStack)-1-int(l)]
|
||||
expType2 := label.blockType.Results
|
||||
if label.isLoop {
|
||||
if label.op == OpcodeLoop {
|
||||
// Loop operation doesn't require results since the continuation is
|
||||
// the beginning of the loop.
|
||||
expType2 = []ValueType{}
|
||||
}
|
||||
if len(expType) != len(expType2) {
|
||||
return fmt.Errorf("incosistent block type length for br_table at %d; %v (ln=%d) != %v (l=%d)", l, expType, ln, expType2, l)
|
||||
return fmt.Errorf("incosistent block type length for %s at %d; %v (ln=%d) != %v (l=%d)", OpcodeBrTableName, l, expType, ln, expType2, l)
|
||||
}
|
||||
for i := range expType {
|
||||
if expType[i] != expType2[i] {
|
||||
return fmt.Errorf("incosistent block type for br_table at %d", l)
|
||||
return fmt.Errorf("incosistent block type for %s at %d", OpcodeBrTableName, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := valueTypeStack.popResults(expType, false); err != nil {
|
||||
return fmt.Errorf("type mismatch on the br_table operation: %v", err)
|
||||
if err = valueTypeStack.popResults(op, expType, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// br_table instruction is stack-polymorphic.
|
||||
valueTypeStack.unreachable()
|
||||
@@ -464,7 +480,7 @@ func validateFunction(
|
||||
funcType := types[functions[index]]
|
||||
for i := 0; i < len(funcType.Params); i++ {
|
||||
if err := valueTypeStack.popAndVerifyType(funcType.Params[len(funcType.Params)-1-i]); err != nil {
|
||||
return fmt.Errorf("type mismatch on call operation param type")
|
||||
return fmt.Errorf("type mismatch on %s operation param type", OpcodeCallName)
|
||||
}
|
||||
}
|
||||
for _, exp := range funcType.Results {
|
||||
@@ -479,21 +495,21 @@ func validateFunction(
|
||||
pc += num - 1
|
||||
pc++
|
||||
if body[pc] != 0x00 {
|
||||
return fmt.Errorf("call_indirect reserved bytes not zero but got %d", body[pc])
|
||||
return fmt.Errorf("%s reserved bytes not zero but got %d", OpcodeCallIndirectName, body[pc])
|
||||
}
|
||||
if table == nil {
|
||||
return fmt.Errorf("table not given while having call_indirect")
|
||||
return fmt.Errorf("table not given while having %s", OpcodeCallIndirectName)
|
||||
}
|
||||
if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the in table index's type for call_indirect")
|
||||
return fmt.Errorf("cannot pop the in table index's type for %s", OpcodeCallIndirectName)
|
||||
}
|
||||
if int(typeIndex) >= len(types) {
|
||||
return fmt.Errorf("invalid type index at call_indirect: %d", typeIndex)
|
||||
return fmt.Errorf("invalid type index at %s: %d", OpcodeCallIndirectName, typeIndex)
|
||||
}
|
||||
funcType := types[typeIndex]
|
||||
for i := 0; i < len(funcType.Params); i++ {
|
||||
if err = valueTypeStack.popAndVerifyType(funcType.Params[len(funcType.Params)-1-i]); err != nil {
|
||||
return fmt.Errorf("type mismatch on call_indirect operation input type")
|
||||
return fmt.Errorf("type mismatch on %s operation input type", OpcodeCallIndirectName)
|
||||
}
|
||||
}
|
||||
for _, exp := range funcType.Results {
|
||||
@@ -503,53 +519,53 @@ func validateFunction(
|
||||
switch op {
|
||||
case OpcodeI32Eqz:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for i32.eqz: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32EqzName, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI32Eq, OpcodeI32Ne, OpcodeI32LtS,
|
||||
OpcodeI32LtU, OpcodeI32GtS, OpcodeI32GtU, OpcodeI32LeS,
|
||||
OpcodeI32LeU, OpcodeI32GeS, OpcodeI32GeU:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st i32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st i32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 2nd i32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 2nd i32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI64Eqz:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for i64.eqz: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI64EqzName, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI64Eq, OpcodeI64Ne, OpcodeI64LtS,
|
||||
OpcodeI64LtU, OpcodeI64GtS, OpcodeI64GtU,
|
||||
OpcodeI64LeS, OpcodeI64LeU, OpcodeI64GeS, OpcodeI64GeU:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st i64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st i64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 2nd i64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 2nd i64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeF32Eq, OpcodeF32Ne, OpcodeF32Lt, OpcodeF32Gt, OpcodeF32Le, OpcodeF32Ge:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st f32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 2nd f32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 2nd f32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeF64Eq, OpcodeF64Ne, OpcodeF64Lt, OpcodeF64Gt, OpcodeF64Le, OpcodeF64Ge:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st f64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 2nd f64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 2nd f64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI32Clz, OpcodeI32Ctz, OpcodeI32Popcnt:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the i32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI32Add, OpcodeI32Sub, OpcodeI32Mul, OpcodeI32DivS,
|
||||
@@ -557,15 +573,15 @@ func validateFunction(
|
||||
OpcodeI32Or, OpcodeI32Xor, OpcodeI32Shl, OpcodeI32ShrS,
|
||||
OpcodeI32ShrU, OpcodeI32Rotl, OpcodeI32Rotr:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st i32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 2nd i32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 2nd operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI64Clz, OpcodeI64Ctz, OpcodeI64Popcnt:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the i64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI64)
|
||||
case OpcodeI64Add, OpcodeI64Sub, OpcodeI64Mul, OpcodeI64DivS,
|
||||
@@ -573,124 +589,124 @@ func validateFunction(
|
||||
OpcodeI64Or, OpcodeI64Xor, OpcodeI64Shl, OpcodeI64ShrS,
|
||||
OpcodeI64ShrU, OpcodeI64Rotl, OpcodeI64Rotr:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st i64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st i64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 2nd i64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 2nd i64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI64)
|
||||
case OpcodeF32Abs, OpcodeF32Neg, OpcodeF32Ceil,
|
||||
OpcodeF32Floor, OpcodeF32Trunc, OpcodeF32Nearest,
|
||||
OpcodeF32Sqrt:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st f32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF32)
|
||||
case OpcodeF32Add, OpcodeF32Sub, OpcodeF32Mul,
|
||||
OpcodeF32Div, OpcodeF32Min, OpcodeF32Max,
|
||||
OpcodeF32Copysign:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st f32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st f32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the 2nd f32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 2nd f32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF32)
|
||||
case OpcodeF64Abs, OpcodeF64Neg, OpcodeF64Ceil,
|
||||
OpcodeF64Floor, OpcodeF64Trunc, OpcodeF64Nearest,
|
||||
OpcodeF64Sqrt:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st f64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF64)
|
||||
case OpcodeF64Add, OpcodeF64Sub, OpcodeF64Mul,
|
||||
OpcodeF64Div, OpcodeF64Min, OpcodeF64Max,
|
||||
OpcodeF64Copysign:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 1st f64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 1st f64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the 2nd f64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the 2nd f64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF64)
|
||||
case OpcodeI32WrapI64:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for i32.wrap_i64: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32WrapI64Name, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI32TruncF32S, OpcodeI32TruncF32U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the f32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the f32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI32TruncF64S, OpcodeI32TruncF64U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the f64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the f64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI64ExtendI32S, OpcodeI64ExtendI32U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the i32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI64)
|
||||
case OpcodeI64TruncF32S, OpcodeI64TruncF32U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the f32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the f32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI64)
|
||||
case OpcodeI64TruncF64S, OpcodeI64TruncF64U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the f64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the f64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI64)
|
||||
case OpcodeF32ConvertI32s, OpcodeF32ConvertI32U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the i32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF32)
|
||||
case OpcodeF32ConvertI64S, OpcodeF32ConvertI64U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the i64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF32)
|
||||
case OpcodeF32DemoteF64:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for f32.demote_f64: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF32DemoteF64Name, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF32)
|
||||
case OpcodeF64ConvertI32S, OpcodeF64ConvertI32U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the i32 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the i32 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF64)
|
||||
case OpcodeF64ConvertI64S, OpcodeF64ConvertI64U:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the i64 operand for 0x%x: %v", op, err)
|
||||
return fmt.Errorf("cannot pop the i64 operand for %s: %v", InstructionName(op), err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF64)
|
||||
case OpcodeF64PromoteF32:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for f64.promote_f32: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF64PromoteF32Name, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF64)
|
||||
case OpcodeI32ReinterpretF32:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF32); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for i32.reinterpret_f32: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI32ReinterpretF32Name, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI32)
|
||||
case OpcodeI64ReinterpretF64:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeF64); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for i64.reinterpret_f64: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeI64ReinterpretF64Name, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeI64)
|
||||
case OpcodeF32ReinterpretI32:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for f32.reinterpret_i32: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF32ReinterpretI32Name, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF32)
|
||||
case OpcodeF64ReinterpretI64:
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for f64.reinterpret_i64: %v", err)
|
||||
return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeF64ReinterpretI64Name, err)
|
||||
}
|
||||
valueTypeStack.push(ValueTypeF64)
|
||||
case OpcodeI32Extend8S, OpcodeI32Extend16S:
|
||||
@@ -713,7 +729,7 @@ func validateFunction(
|
||||
return fmt.Errorf("invalid numeric instruction 0x%x", op)
|
||||
}
|
||||
} else if op == OpcodeBlock {
|
||||
bt, num, err := decodeBlockType(types, bytes.NewReader(body[pc+1:]))
|
||||
bt, num, err := decodeBlockType(types, bytes.NewReader(body[pc+1:]), enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read block: %w", err)
|
||||
}
|
||||
@@ -722,10 +738,10 @@ func validateFunction(
|
||||
blockType: bt,
|
||||
blockTypeBytes: num,
|
||||
})
|
||||
valueTypeStack.pushStackLimit()
|
||||
valueTypeStack.pushStackLimit(len(bt.Params))
|
||||
pc += num
|
||||
} else if op == OpcodeLoop {
|
||||
bt, num, err := decodeBlockType(types, bytes.NewReader(body[pc+1:]))
|
||||
bt, num, err := decodeBlockType(types, bytes.NewReader(body[pc+1:]), enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read block: %w", err)
|
||||
}
|
||||
@@ -733,12 +749,19 @@ func validateFunction(
|
||||
startAt: pc,
|
||||
blockType: bt,
|
||||
blockTypeBytes: num,
|
||||
isLoop: true,
|
||||
op: op,
|
||||
})
|
||||
valueTypeStack.pushStackLimit()
|
||||
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// Plus we have to push any block params again.
|
||||
for _, p := range bt.Params {
|
||||
valueTypeStack.push(p)
|
||||
}
|
||||
valueTypeStack.pushStackLimit(len(bt.Params))
|
||||
pc += num
|
||||
} else if op == OpcodeIf {
|
||||
bt, num, err := decodeBlockType(types, bytes.NewReader(body[pc+1:]))
|
||||
bt, num, err := decodeBlockType(types, bytes.NewReader(body[pc+1:]), enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read block: %w", err)
|
||||
}
|
||||
@@ -746,38 +769,64 @@ func validateFunction(
|
||||
startAt: pc,
|
||||
blockType: bt,
|
||||
blockTypeBytes: num,
|
||||
isIf: true,
|
||||
op: op,
|
||||
})
|
||||
if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
|
||||
return fmt.Errorf("cannot pop the operand for 'if': %v", err)
|
||||
}
|
||||
valueTypeStack.pushStackLimit()
|
||||
if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// Plus we have to push any block params again.
|
||||
for _, p := range bt.Params {
|
||||
valueTypeStack.push(p)
|
||||
}
|
||||
valueTypeStack.pushStackLimit(len(bt.Params))
|
||||
pc += num
|
||||
} else if op == OpcodeElse {
|
||||
bl := controlBlockStack[len(controlBlockStack)-1]
|
||||
bl.elseAt = pc
|
||||
// Check the type soundness of the instructions *before* entering this else Op.
|
||||
if err := valueTypeStack.popResults(bl.blockType.Results, true); err != nil {
|
||||
return fmt.Errorf("invalid instruction results in then instructions")
|
||||
if err := valueTypeStack.popResults(OpcodeIf, bl.blockType.Results, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Before entering instructions inside else, we pop all the values pushed by then block.
|
||||
valueTypeStack.resetAtStackLimit()
|
||||
// Plus we have to push any block params again.
|
||||
for _, p := range bl.blockType.Params {
|
||||
valueTypeStack.push(p)
|
||||
}
|
||||
} else if op == OpcodeEnd {
|
||||
bl := controlBlockStack[len(controlBlockStack)-1]
|
||||
bl.endAt = pc
|
||||
controlBlockStack = controlBlockStack[:len(controlBlockStack)-1]
|
||||
if bl.isIf && bl.elseAt <= bl.startAt {
|
||||
if len(bl.blockType.Results) > 0 {
|
||||
return fmt.Errorf("type mismatch between then and else blocks")
|
||||
|
||||
// OpcodeEnd can end a block or the function itself. Check to see what it is:
|
||||
|
||||
ifMissingElse := bl.op == OpcodeIf && bl.elseAt <= bl.startAt
|
||||
if ifMissingElse {
|
||||
// If this is the end of block without else, the number of block's results and params must be same.
|
||||
// Otherwise, the value stack would result in the inconsistent state at runtime.
|
||||
if !bytes.Equal(bl.blockType.Results, bl.blockType.Params) {
|
||||
return typeCountError(false, OpcodeElseName, bl.blockType.Params, bl.blockType.Results)
|
||||
}
|
||||
// To handle if block without else properly,
|
||||
// we set ElseAt to EndAt-1 so we can just skip else.
|
||||
// -1 skips else, to handle if block without else properly.
|
||||
bl.elseAt = bl.endAt - 1
|
||||
}
|
||||
// Check type soundness.
|
||||
if err := valueTypeStack.popResults(bl.blockType.Results, true); err != nil {
|
||||
return fmt.Errorf("invalid instruction results at end instruction; expected %v: %v", bl.blockType.Results, err)
|
||||
|
||||
// Determine the block context
|
||||
ctx := "" // the outer-most block: the function return
|
||||
if bl.op == OpcodeIf && !ifMissingElse && bl.elseAt > 0 {
|
||||
ctx = OpcodeElseName
|
||||
} else if bl.op != 0 {
|
||||
ctx = InstructionName(bl.op)
|
||||
}
|
||||
|
||||
// Check return types match
|
||||
if err := valueTypeStack.requireStackValues(false, ctx, bl.blockType.Results, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Put the result types at the end after resetting at the stack limit
|
||||
// since we might have Any type between the limit and the current top.
|
||||
valueTypeStack.resetAtStackLimit()
|
||||
@@ -788,11 +837,9 @@ func validateFunction(
|
||||
// on values previously pushed by outer blocks.
|
||||
valueTypeStack.popStackLimit()
|
||||
} else if op == OpcodeReturn {
|
||||
expTypes := functionType.Results
|
||||
for i := 0; i < len(expTypes); i++ {
|
||||
if err := valueTypeStack.popAndVerifyType(expTypes[len(expTypes)-1-i]); err != nil {
|
||||
return fmt.Errorf("return type mismatch on return: %v; want %v", err, expTypes)
|
||||
}
|
||||
// Same formatting as OpcodeEnd on the outer-most block
|
||||
if err := valueTypeStack.requireStackValues(false, "", functionType.Results, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// return instruction is stack-polymorphic.
|
||||
valueTypeStack.unreachable()
|
||||
@@ -850,30 +897,41 @@ const (
|
||||
valueTypeUnknown = ValueType(0xFF)
|
||||
)
|
||||
|
||||
func (s *valueTypeStack) pop() (ValueType, error) {
|
||||
limit := 0
|
||||
func (s *valueTypeStack) tryPop() (vt ValueType, limit int, ok bool) {
|
||||
if len(s.stackLimits) > 0 {
|
||||
limit = s.stackLimits[len(s.stackLimits)-1]
|
||||
}
|
||||
if len(s.stack) <= limit {
|
||||
return 0, fmt.Errorf("invalid operation: trying to pop at %d with limit %d",
|
||||
len(s.stack), limit)
|
||||
} else if len(s.stack) == limit+1 && s.stack[limit] == valueTypeUnknown {
|
||||
return valueTypeUnknown, nil
|
||||
stackLen := len(s.stack)
|
||||
if stackLen <= limit {
|
||||
return
|
||||
} else if stackLen == limit+1 && s.stack[limit] == valueTypeUnknown {
|
||||
vt = valueTypeUnknown
|
||||
ok = true
|
||||
return
|
||||
} else {
|
||||
ret := s.stack[len(s.stack)-1]
|
||||
s.stack = s.stack[:len(s.stack)-1]
|
||||
return ret, nil
|
||||
vt = s.stack[stackLen-1]
|
||||
s.stack = s.stack[:stackLen-1]
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *valueTypeStack) popAndVerifyType(expected ValueType) error {
|
||||
actual, err := s.pop()
|
||||
if err != nil {
|
||||
return err
|
||||
func (s *valueTypeStack) pop() (ValueType, error) {
|
||||
if vt, limit, ok := s.tryPop(); ok {
|
||||
return vt, nil
|
||||
} else {
|
||||
return 0, fmt.Errorf("invalid operation: trying to pop at %d with limit %d", len(s.stack), limit)
|
||||
}
|
||||
if actual != expected && actual != valueTypeUnknown && expected != valueTypeUnknown {
|
||||
return fmt.Errorf("type mismatch")
|
||||
}
|
||||
|
||||
// popAndVerifyType returns an error if the stack value is unexpected.
|
||||
func (s *valueTypeStack) popAndVerifyType(expected ValueType) error {
|
||||
have, _, ok := s.tryPop()
|
||||
if !ok {
|
||||
return fmt.Errorf("%s missing", ValueTypeName(expected))
|
||||
}
|
||||
if have != expected && have != valueTypeUnknown && expected != valueTypeUnknown {
|
||||
return fmt.Errorf("type mismatch: expected %s, but was %s", ValueTypeName(expected), ValueTypeName(have))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -904,28 +962,129 @@ func (s *valueTypeStack) popStackLimit() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *valueTypeStack) pushStackLimit() {
|
||||
s.stackLimits = append(s.stackLimits, len(s.stack))
|
||||
// pushStackLimit pushes the control frame's bottom of the stack.
|
||||
func (s *valueTypeStack) pushStackLimit(params int) {
|
||||
limit := len(s.stack) - params
|
||||
s.stackLimits = append(s.stackLimits, limit)
|
||||
}
|
||||
|
||||
func (s *valueTypeStack) popResults(expResults []ValueType, checkAboveLimit bool) error {
|
||||
func (s *valueTypeStack) popParams(oc Opcode, want []ValueType, checkAboveLimit bool) error {
|
||||
return s.requireStackValues(true, InstructionName(oc), want, checkAboveLimit)
|
||||
}
|
||||
|
||||
func (s *valueTypeStack) popResults(oc Opcode, want []ValueType, checkAboveLimit bool) error {
|
||||
return s.requireStackValues(false, InstructionName(oc), want, checkAboveLimit)
|
||||
}
|
||||
|
||||
func (s *valueTypeStack) requireStackValues(
|
||||
isParam bool,
|
||||
context string,
|
||||
want []ValueType,
|
||||
checkAboveLimit bool,
|
||||
) error {
|
||||
limit := 0
|
||||
if len(s.stackLimits) > 0 {
|
||||
limit = s.stackLimits[len(s.stackLimits)-1]
|
||||
}
|
||||
for _, exp := range expResults {
|
||||
if err := s.popAndVerifyType(exp); err != nil {
|
||||
return err
|
||||
// Iterate backwards as we are comparing the desired slice against stack value types.
|
||||
countWanted := len(want)
|
||||
|
||||
// First, check if there are enough values on the stack.
|
||||
have := make([]ValueType, 0, countWanted)
|
||||
for i := countWanted - 1; i >= 0; i-- {
|
||||
popped, _, ok := s.tryPop()
|
||||
if !ok {
|
||||
if len(have) > len(want) {
|
||||
return typeCountError(isParam, context, have, want)
|
||||
}
|
||||
return typeCountError(isParam, context, have, want)
|
||||
}
|
||||
have = append(have, popped)
|
||||
}
|
||||
|
||||
// Now, check if there are too many values.
|
||||
if checkAboveLimit {
|
||||
if !(limit == len(s.stack) || (limit+1 == len(s.stack) && s.stack[limit] == valueTypeUnknown)) {
|
||||
return fmt.Errorf("leftovers found in the stack")
|
||||
return typeCountError(isParam, context, append(s.stack, want...), want)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, check the types of the values:
|
||||
for i, v := range have {
|
||||
nextWant := want[countWanted-i-1] // have is in reverse order (stack)
|
||||
if v != nextWant && v != valueTypeUnknown && nextWant != valueTypeUnknown {
|
||||
return typeMismatchError(isParam, context, v, nextWant, i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// typeMismatchError returns an error similar to go compiler's error on type mismatch.
|
||||
func typeMismatchError(isParam bool, context string, have ValueType, want ValueType, i int) error {
|
||||
var ret strings.Builder
|
||||
ret.WriteString("cannot use ")
|
||||
ret.WriteString(ValueTypeName(have))
|
||||
if context != "" {
|
||||
ret.WriteString(" in ")
|
||||
ret.WriteString(context)
|
||||
ret.WriteString(" block")
|
||||
}
|
||||
if isParam {
|
||||
ret.WriteString(" as param")
|
||||
} else {
|
||||
ret.WriteString(" as result")
|
||||
}
|
||||
ret.WriteString("[")
|
||||
ret.WriteString(strconv.Itoa(i))
|
||||
ret.WriteString("] type ")
|
||||
ret.WriteString(ValueTypeName(want))
|
||||
return errors.New(ret.String())
|
||||
}
|
||||
|
||||
// typeCountError returns an error similar to go compiler's error on type count mismatch.
|
||||
func typeCountError(isParam bool, context string, have []ValueType, want []ValueType) error {
|
||||
var ret strings.Builder
|
||||
if len(have) > len(want) {
|
||||
ret.WriteString("too many ")
|
||||
} else {
|
||||
ret.WriteString("not enough ")
|
||||
}
|
||||
if isParam {
|
||||
ret.WriteString("params")
|
||||
} else {
|
||||
ret.WriteString("results")
|
||||
}
|
||||
if context != "" {
|
||||
if isParam {
|
||||
ret.WriteString(" for ")
|
||||
} else {
|
||||
ret.WriteString(" in ")
|
||||
}
|
||||
ret.WriteString(context)
|
||||
ret.WriteString(" block")
|
||||
}
|
||||
ret.WriteString("\n\thave (")
|
||||
writeValueTypes(have, &ret)
|
||||
ret.WriteString(")\n\twant (")
|
||||
writeValueTypes(want, &ret)
|
||||
ret.WriteByte(')')
|
||||
return errors.New(ret.String())
|
||||
}
|
||||
|
||||
func writeValueTypes(vts []ValueType, ret *strings.Builder) {
|
||||
switch len(vts) {
|
||||
case 0:
|
||||
case 1:
|
||||
ret.WriteString(api.ValueTypeName(vts[0]))
|
||||
default:
|
||||
ret.WriteString(api.ValueTypeName(vts[0]))
|
||||
for _, vt := range vts[1:] {
|
||||
ret.WriteString(", ")
|
||||
ret.WriteString(api.ValueTypeName(vt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *valueTypeStack) String() string {
|
||||
var typeStrs, limits []string
|
||||
for _, v := range s.stack {
|
||||
@@ -954,30 +1113,36 @@ type controlBlock struct {
|
||||
startAt, elseAt, endAt uint64
|
||||
blockType *FunctionType
|
||||
blockTypeBytes uint64
|
||||
isLoop bool
|
||||
isIf bool
|
||||
// op is zero when the outermost block
|
||||
op Opcode
|
||||
}
|
||||
|
||||
func decodeBlockType(types []*FunctionType, r *bytes.Reader) (*FunctionType, uint64, error) {
|
||||
func decodeBlockType(types []*FunctionType, r *bytes.Reader, enabledFeatures Features) (*FunctionType, uint64, error) {
|
||||
return decodeBlockTypeImpl(func(index int64) (*FunctionType, error) {
|
||||
if index < 0 || (index >= int64(len(types))) {
|
||||
return nil, fmt.Errorf("type index out of range: %d", index)
|
||||
}
|
||||
return types[index], nil
|
||||
}, r)
|
||||
}, r, enabledFeatures)
|
||||
}
|
||||
|
||||
// DecodeBlockType is exported for use in the compiler
|
||||
func DecodeBlockType(types []*TypeInstance, r *bytes.Reader) (*FunctionType, uint64, error) {
|
||||
func DecodeBlockType(types []*TypeInstance, r *bytes.Reader, enabledFeatures Features) (*FunctionType, uint64, error) {
|
||||
return decodeBlockTypeImpl(func(index int64) (*FunctionType, error) {
|
||||
if index < 0 || (index >= int64(len(types))) {
|
||||
return nil, fmt.Errorf("type index out of range: %d", index)
|
||||
}
|
||||
return types[index].Type, nil
|
||||
}, r)
|
||||
}, r, enabledFeatures)
|
||||
}
|
||||
|
||||
func decodeBlockTypeImpl(functionTypeResolver func(index int64) (*FunctionType, error), r *bytes.Reader) (*FunctionType, uint64, error) {
|
||||
// decodeBlockTypeImpl decodes the type index from a positive 33-bit signed integer. Negative numbers indicate up to one
|
||||
// WebAssembly 1.0 (20191205) compatible result type. Positive numbers are decoded when `enabledFeatures` include
|
||||
// FeatureMultiValue and include an index in the Module.TypeSection.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-blocktype
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
func decodeBlockTypeImpl(functionTypeResolver func(index int64) (*FunctionType, error), r *bytes.Reader, enabledFeatures Features) (*FunctionType, uint64, error) {
|
||||
raw, num, err := leb128.DecodeInt33AsInt64(r)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("decode int33: %w", err)
|
||||
@@ -996,6 +1161,9 @@ func decodeBlockTypeImpl(functionTypeResolver func(index int64) (*FunctionType,
|
||||
case -4: // 0x7c in original byte = f64
|
||||
ret = &FunctionType{Results: []ValueType{ValueTypeF64}}
|
||||
default:
|
||||
if err = enabledFeatures.Require(FeatureMultiValue); err != nil {
|
||||
return nil, num, fmt.Errorf("block with function type return invalid as %v", err)
|
||||
}
|
||||
ret, err = functionTypeResolver(raw)
|
||||
}
|
||||
return ret, num, err
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
@@ -48,7 +47,7 @@ func GetHostFunctionCallContextValue(fk FunctionKind, ctx *ModuleContext) *refle
|
||||
}
|
||||
|
||||
// getFunctionType returns the function type corresponding to the function signature or errs if invalid.
|
||||
func getFunctionType(fn *reflect.Value, allowErrorResult bool) (fk FunctionKind, ft *FunctionType, hasErrorResult bool, err error) {
|
||||
func getFunctionType(fn *reflect.Value, enabledFeatures Features) (fk FunctionKind, ft *FunctionType, err error) {
|
||||
p := fn.Type()
|
||||
|
||||
if fn.Kind() != reflect.Func {
|
||||
@@ -62,16 +61,12 @@ func getFunctionType(fn *reflect.Value, allowErrorResult bool) (fk FunctionKind,
|
||||
}
|
||||
|
||||
rCount := p.NumOut()
|
||||
if (allowErrorResult && rCount > 2) || (!allowErrorResult && rCount > 1) {
|
||||
err = errors.New("multiple results are unsupported")
|
||||
return
|
||||
}
|
||||
|
||||
if allowErrorResult && rCount > 0 {
|
||||
maybeErrIdx := rCount - 1
|
||||
if p.Out(maybeErrIdx).Implements(errorType) {
|
||||
hasErrorResult = true
|
||||
rCount--
|
||||
if rCount > 1 {
|
||||
// Guard >1.0 feature multi-value
|
||||
if err = enabledFeatures.Require(FeatureMultiValue); err != nil {
|
||||
err = fmt.Errorf("multiple result types invalid as %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,21 +95,21 @@ func getFunctionType(fn *reflect.Value, allowErrorResult bool) (fk FunctionKind,
|
||||
return
|
||||
}
|
||||
|
||||
if rCount == 0 {
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(ft.Results); i++ {
|
||||
rI := p.Out(i)
|
||||
if t, ok := getTypeOf(rI.Kind()); ok {
|
||||
ft.Results[i] = t
|
||||
continue
|
||||
}
|
||||
|
||||
result := p.Out(0)
|
||||
if t, ok := getTypeOf(result.Kind()); ok {
|
||||
ft.Results[0] = t
|
||||
// Now, we will definitely err, decide which message is best
|
||||
if rI.Implements(errorType) {
|
||||
err = fmt.Errorf("result[%d] is an error, which is unsupported", i)
|
||||
} else {
|
||||
err = fmt.Errorf("result[%d] is unsupported: %s", i, rI.Kind())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if result.Implements(errorType) {
|
||||
err = errors.New("result[0] is an error, which is unsupported")
|
||||
} else {
|
||||
err = fmt.Errorf("result[0] is unsupported: %s", result.Kind())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,14 @@ import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
type errno uint32
|
||||
|
||||
func TestGetFunctionType(t *testing.T) {
|
||||
i32, i64, f32, f64 := ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputFunc interface{}
|
||||
allowErrorResult bool
|
||||
expectedKind FunctionKind
|
||||
expectedType *FunctionType
|
||||
expectErrorResult bool
|
||||
var tests = []struct {
|
||||
name string
|
||||
inputFunc interface{}
|
||||
expectedKind FunctionKind
|
||||
expectedType *FunctionType
|
||||
}{
|
||||
{
|
||||
name: "nullary",
|
||||
@@ -29,43 +25,6 @@ func TestGetFunctionType(t *testing.T) {
|
||||
expectedKind: FunctionKindGoNoContext,
|
||||
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
|
||||
},
|
||||
{
|
||||
name: "nullary allowErrorResult",
|
||||
inputFunc: func() {},
|
||||
allowErrorResult: true,
|
||||
expectedKind: FunctionKindGoNoContext,
|
||||
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
|
||||
},
|
||||
{
|
||||
name: "void error result",
|
||||
inputFunc: func() error { return nil },
|
||||
allowErrorResult: true,
|
||||
expectedKind: FunctionKindGoNoContext,
|
||||
expectErrorResult: true,
|
||||
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{}},
|
||||
},
|
||||
{
|
||||
name: "void uint32 allowErrorResult",
|
||||
inputFunc: func() uint32 { return 0 },
|
||||
allowErrorResult: true,
|
||||
expectedKind: FunctionKindGoNoContext,
|
||||
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{i32}},
|
||||
},
|
||||
{
|
||||
name: "void type uint32 allowErrorResult",
|
||||
inputFunc: func() errno { return 0 },
|
||||
allowErrorResult: true,
|
||||
expectedKind: FunctionKindGoNoContext,
|
||||
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{i32}},
|
||||
},
|
||||
{
|
||||
name: "void (uint32,error) results",
|
||||
inputFunc: func() (uint32, error) { return 0, nil },
|
||||
allowErrorResult: true,
|
||||
expectedKind: FunctionKindGoNoContext,
|
||||
expectErrorResult: true,
|
||||
expectedType: &FunctionType{Params: []ValueType{}, Results: []ValueType{i32}},
|
||||
},
|
||||
{
|
||||
name: "wasm.Module void return",
|
||||
inputFunc: func(api.Module) {},
|
||||
@@ -84,6 +43,12 @@ func TestGetFunctionType(t *testing.T) {
|
||||
expectedKind: FunctionKindGoNoContext,
|
||||
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, 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 },
|
||||
expectedKind: FunctionKindGoNoContext,
|
||||
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64}, Results: []ValueType{i32, i64, f32, f64}},
|
||||
},
|
||||
{
|
||||
name: "all supported params and i32 result - wasm.Module",
|
||||
inputFunc: func(api.Module, uint32, uint64, float32, float64) uint32 { return 0 },
|
||||
@@ -97,17 +62,15 @@ func TestGetFunctionType(t *testing.T) {
|
||||
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, hasErrorResult, err := getFunctionType(&rVal, tc.allowErrorResult)
|
||||
fk, ft, err := getFunctionType(&rVal, Features20191205|FeatureMultiValue)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedKind, fk)
|
||||
require.Equal(t, tc.expectedType, ft)
|
||||
require.Equal(t, tc.expectErrorResult, hasErrorResult)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -140,9 +103,9 @@ func TestGetFunctionTypeErrors(t *testing.T) {
|
||||
expectedErr: "result[0] is an error, which is unsupported",
|
||||
},
|
||||
{
|
||||
name: "multiple results",
|
||||
name: "multiple results - multi-value not enabled",
|
||||
input: func() (uint64, uint32) { return 0, 0 },
|
||||
expectedErr: "multiple results are unsupported",
|
||||
expectedErr: "multiple result types invalid as feature \"multi-value\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "multiple context types",
|
||||
@@ -166,7 +129,7 @@ func TestGetFunctionTypeErrors(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rVal := reflect.ValueOf(tc.input)
|
||||
_, _, _, err := getFunctionType(&rVal, tc.allowErrorResult)
|
||||
_, _, err := getFunctionType(&rVal, Features20191205)
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,13 @@ import (
|
||||
)
|
||||
|
||||
// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
|
||||
func NewHostModule(moduleName string, nameToGoFunc map[string]interface{}, nameToMemory map[string]*Memory, nameToGlobal map[string]*Global) (m *Module, err error) {
|
||||
func NewHostModule(
|
||||
moduleName string,
|
||||
nameToGoFunc map[string]interface{},
|
||||
nameToMemory map[string]*Memory,
|
||||
nameToGlobal map[string]*Global,
|
||||
enabledFeatures Features,
|
||||
) (m *Module, err error) {
|
||||
if moduleName != "" {
|
||||
m = &Module{NameSection: &NameSection{ModuleName: moduleName}}
|
||||
} else {
|
||||
@@ -26,7 +32,7 @@ func NewHostModule(moduleName string, nameToGoFunc map[string]interface{}, nameT
|
||||
}
|
||||
|
||||
if funcCount > 0 {
|
||||
if err = addFuncs(m, nameToGoFunc); err != nil {
|
||||
if err = addFuncs(m, nameToGoFunc, enabledFeatures); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -37,6 +43,7 @@ func NewHostModule(moduleName string, nameToGoFunc map[string]interface{}, nameT
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we can use enabledFeatures to fail early on things like mutable globals (once supported)
|
||||
if globalCount > 0 {
|
||||
if err = addGlobals(m, nameToGlobal); err != nil {
|
||||
return
|
||||
@@ -45,7 +52,7 @@ func NewHostModule(moduleName string, nameToGoFunc map[string]interface{}, nameT
|
||||
return
|
||||
}
|
||||
|
||||
func addFuncs(m *Module, nameToGoFunc map[string]interface{}) error {
|
||||
func addFuncs(m *Module, nameToGoFunc map[string]interface{}, enabledFeatures Features) error {
|
||||
funcCount := uint32(len(nameToGoFunc))
|
||||
funcNames := make([]string, 0, funcCount)
|
||||
if m.NameSection == nil {
|
||||
@@ -64,7 +71,7 @@ func addFuncs(m *Module, nameToGoFunc map[string]interface{}) error {
|
||||
for idx := Index(0); idx < funcCount; idx++ {
|
||||
name := funcNames[idx]
|
||||
fn := reflect.ValueOf(nameToGoFunc[name])
|
||||
_, functionType, _, err := getFunctionType(&fn, false)
|
||||
_, functionType, err := getFunctionType(&fn, enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("func[%s] %w", name, err)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ func (a *wasiAPI) FdWrite(ctx api.Module, fd, iovs, iovsCount, resultSize uint32
|
||||
return 0
|
||||
}
|
||||
|
||||
func swap(x, y uint32) (uint32, uint32) {
|
||||
return y, x
|
||||
}
|
||||
|
||||
func TestNewHostModule(t *testing.T) {
|
||||
i32 := ValueTypeI32
|
||||
|
||||
@@ -34,6 +38,8 @@ func TestNewHostModule(t *testing.T) {
|
||||
fnArgsSizesGet := reflect.ValueOf(a.ArgsSizesGet)
|
||||
functionFdWrite := "fd_write"
|
||||
fnFdWrite := reflect.ValueOf(a.FdWrite)
|
||||
functionSwap := "swap"
|
||||
fnSwap := reflect.ValueOf(swap)
|
||||
|
||||
tests := []struct {
|
||||
name, moduleName string
|
||||
@@ -78,6 +84,20 @@ func TestNewHostModule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi-value",
|
||||
moduleName: "swapper",
|
||||
nameToGoFunc: map[string]interface{}{
|
||||
functionSwap: swap,
|
||||
},
|
||||
expected: &Module{
|
||||
TypeSection: []*FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}}},
|
||||
FunctionSection: []Index{0},
|
||||
HostFunctionSection: []*reflect.Value{&fnSwap},
|
||||
ExportSection: map[string]*Export{"swap": {Name: "swap", Type: ExternTypeFunc, Index: 0}},
|
||||
NameSection: &NameSection{ModuleName: "swapper", FunctionNames: NameMap{{Index: 0, Name: "swap"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "memory",
|
||||
nameToMemory: map[string]*Memory{"memory": {1, 2}},
|
||||
@@ -164,7 +184,13 @@ func TestNewHostModule(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, tc.nameToMemory, tc.nameToGlobal)
|
||||
m, e := NewHostModule(
|
||||
tc.moduleName,
|
||||
tc.nameToGoFunc,
|
||||
tc.nameToMemory,
|
||||
tc.nameToGlobal,
|
||||
Features20191205|FeatureMultiValue,
|
||||
)
|
||||
require.NoError(t, e)
|
||||
requireHostModuleEquals(t, tc.expected, m)
|
||||
})
|
||||
@@ -206,6 +232,12 @@ func TestNewHostModule_Errors(t *testing.T) {
|
||||
nameToGoFunc: map[string]interface{}{"fn": t},
|
||||
expectedErr: "func[fn] kind != func: ptr",
|
||||
},
|
||||
{
|
||||
name: "function has multiple results",
|
||||
nameToGoFunc: map[string]interface{}{"fn": func() (uint32, uint32) { return 0, 0 }},
|
||||
nameToMemory: map[string]*Memory{"fn": {1, 1}},
|
||||
expectedErr: "func[fn] multiple result types invalid as feature \"multi-value\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "memory collides on func name",
|
||||
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
|
||||
@@ -234,7 +266,7 @@ func TestNewHostModule_Errors(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, tc.nameToMemory, tc.nameToGlobal)
|
||||
_, e := NewHostModule(tc.moduleName, tc.nameToGoFunc, tc.nameToMemory, tc.nameToGlobal, Features20191205)
|
||||
require.EqualError(t, e, tc.expectedErr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,8 +21,22 @@ const (
|
||||
// breaks out to after the OpcodeEnd on the enclosing OpcodeIf.
|
||||
OpcodeElse Opcode = 0x05
|
||||
// OpcodeEnd terminates a control instruction OpcodeBlock, OpcodeLoop or OpcodeIf.
|
||||
OpcodeEnd Opcode = 0x0b
|
||||
OpcodeBr Opcode = 0x0c
|
||||
OpcodeEnd Opcode = 0x0b
|
||||
|
||||
// OpcodeBr is a stack-polymorphic opcode that performs an unconditional branch. How the stack is modified depends
|
||||
// on whether the "br" is enclosed by a loop, and if FeatureMultiValue is enabled.
|
||||
//
|
||||
// Here are the rules in pseudocode about how the stack is modified based on the "br" operand L (label):
|
||||
// if L is loop: append(L.originalStackWithoutInputs, N-values popped from the stack) where N == L.inputs
|
||||
// else: append(L.originalStackWithoutInputs, N-values popped from the stack) where N == L.results
|
||||
//
|
||||
// In WebAssembly 1.0 (20191205), N can be zero or one. When FeatureMultiValue is enabled, N can be more than one,
|
||||
// depending on the type use of the label L.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-controlmathsfbrl
|
||||
OpcodeBr Opcode = 0x0c
|
||||
// ^^ TODO: Add a diagram to help explain br l means that branch into AFTER l for non-loop labels
|
||||
|
||||
OpcodeBrIf Opcode = 0x0d
|
||||
OpcodeBrTable Opcode = 0x0e
|
||||
OpcodeReturn Opcode = 0x0f
|
||||
@@ -240,186 +254,369 @@ const (
|
||||
LastOpcode = OpcodeI64Extend32S
|
||||
)
|
||||
|
||||
var instructionNames = [256]string{
|
||||
OpcodeUnreachable: "unreachable",
|
||||
OpcodeNop: "nop",
|
||||
OpcodeBlock: "block",
|
||||
OpcodeLoop: "loop",
|
||||
OpcodeIf: "if",
|
||||
OpcodeElse: "else",
|
||||
OpcodeEnd: "end",
|
||||
OpcodeBr: "br",
|
||||
OpcodeBrIf: "br_if",
|
||||
OpcodeBrTable: "br_table",
|
||||
OpcodeReturn: "return",
|
||||
OpcodeCall: "call",
|
||||
OpcodeCallIndirect: "call_indirect",
|
||||
OpcodeDrop: "drop",
|
||||
OpcodeSelect: "select",
|
||||
OpcodeLocalGet: "local.get",
|
||||
OpcodeLocalSet: "local.set",
|
||||
OpcodeLocalTee: "local.tee",
|
||||
OpcodeGlobalGet: "global.get",
|
||||
OpcodeGlobalSet: "global.set",
|
||||
OpcodeI32Load: "i32.load",
|
||||
OpcodeI64Load: "i64.load",
|
||||
OpcodeF32Load: "f32.load",
|
||||
OpcodeF64Load: "f64.load",
|
||||
OpcodeI32Load8S: "i32.load8_s",
|
||||
OpcodeI32Load8U: "i32.load8_u",
|
||||
OpcodeI32Load16S: "i32.load16_s",
|
||||
OpcodeI32Load16U: "i32.load16_u",
|
||||
OpcodeI64Load8S: "i64.load8_s",
|
||||
OpcodeI64Load8U: "i64.load8_u",
|
||||
OpcodeI64Load16S: "i64.load16_s",
|
||||
OpcodeI64Load16U: "i64.load16_u",
|
||||
OpcodeI64Load32S: "i64.load32_s",
|
||||
OpcodeI64Load32U: "i64.load32_u",
|
||||
OpcodeI32Store: "i32.store",
|
||||
OpcodeI64Store: "i64.store",
|
||||
OpcodeF32Store: "f32.store",
|
||||
OpcodeF64Store: "f64.store",
|
||||
OpcodeI32Store8: "i32.store8",
|
||||
OpcodeI32Store16: "i32.store16",
|
||||
OpcodeI64Store8: "i64.store8",
|
||||
OpcodeI64Store16: "i64.store16",
|
||||
OpcodeI64Store32: "i64.store32",
|
||||
OpcodeMemorySize: "memory.size",
|
||||
OpcodeMemoryGrow: "memory.grow",
|
||||
OpcodeI32Const: "i32.const",
|
||||
OpcodeI64Const: "i64.const",
|
||||
OpcodeF32Const: "f32.const",
|
||||
OpcodeF64Const: "f64.const",
|
||||
OpcodeI32Eqz: "i32.eqz",
|
||||
OpcodeI32Eq: "i32.eq",
|
||||
OpcodeI32Ne: "i32.ne",
|
||||
OpcodeI32LtS: "i32.lt_s",
|
||||
OpcodeI32LtU: "i32.lt_u",
|
||||
OpcodeI32GtS: "i32.gt_s",
|
||||
OpcodeI32GtU: "i32.gt_u",
|
||||
OpcodeI32LeS: "i32.le_s",
|
||||
OpcodeI32LeU: "i32.le_u",
|
||||
OpcodeI32GeS: "i32.ge_s",
|
||||
OpcodeI32GeU: "i32.ge_u",
|
||||
OpcodeI64Eqz: "i64.eqz",
|
||||
OpcodeI64Eq: "i64.eq",
|
||||
OpcodeI64Ne: "i64.ne",
|
||||
OpcodeI64LtS: "i64.lt_s",
|
||||
OpcodeI64LtU: "i64.lt_u",
|
||||
OpcodeI64GtS: "i64.gt_s",
|
||||
OpcodeI64GtU: "i64.gt_u",
|
||||
OpcodeI64LeS: "i64.le_s",
|
||||
OpcodeI64LeU: "i64.le_u",
|
||||
OpcodeI64GeS: "i64.ge_s",
|
||||
OpcodeI64GeU: "i64.ge_u",
|
||||
OpcodeF32Eq: "f32.eq",
|
||||
OpcodeF32Ne: "f32.ne",
|
||||
OpcodeF32Lt: "f32.lt",
|
||||
OpcodeF32Gt: "f32.gt",
|
||||
OpcodeF32Le: "f32.le",
|
||||
OpcodeF32Ge: "f32.ge",
|
||||
OpcodeF64Eq: "f64.eq",
|
||||
OpcodeF64Ne: "f64.ne",
|
||||
OpcodeF64Lt: "f64.lt",
|
||||
OpcodeF64Gt: "f64.gt",
|
||||
OpcodeF64Le: "f64.le",
|
||||
OpcodeF64Ge: "f64.ge",
|
||||
OpcodeI32Clz: "i32.clz",
|
||||
OpcodeI32Ctz: "i32.ctz",
|
||||
OpcodeI32Popcnt: "i32.popcnt",
|
||||
OpcodeI32Add: "i32.add",
|
||||
OpcodeI32Sub: "i32.sub",
|
||||
OpcodeI32Mul: "i32.mul",
|
||||
OpcodeI32DivS: "i32.div_s",
|
||||
OpcodeI32DivU: "i32.div_u",
|
||||
OpcodeI32RemS: "i32.rem_s",
|
||||
OpcodeI32RemU: "i32.rem_u",
|
||||
OpcodeI32And: "i32.and",
|
||||
OpcodeI32Or: "i32.or",
|
||||
OpcodeI32Xor: "i32.xor",
|
||||
OpcodeI32Shl: "i32.shl",
|
||||
OpcodeI32ShrS: "i32.shr_s",
|
||||
OpcodeI32ShrU: "i32.shr_u",
|
||||
OpcodeI32Rotl: "i32.rotl",
|
||||
OpcodeI32Rotr: "i32.rotr",
|
||||
OpcodeI64Clz: "i64.clz",
|
||||
OpcodeI64Ctz: "i64.ctz",
|
||||
OpcodeI64Popcnt: "i64.popcnt",
|
||||
OpcodeI64Add: "i64.add",
|
||||
OpcodeI64Sub: "i64.sub",
|
||||
OpcodeI64Mul: "i64.mul",
|
||||
OpcodeI64DivS: "i64.div_s",
|
||||
OpcodeI64DivU: "i64.div_u",
|
||||
OpcodeI64RemS: "i64.rem_s",
|
||||
OpcodeI64RemU: "i64.rem_u",
|
||||
OpcodeI64And: "i64.and",
|
||||
OpcodeI64Or: "i64.or",
|
||||
OpcodeI64Xor: "i64.xor",
|
||||
OpcodeI64Shl: "i64.shl",
|
||||
OpcodeI64ShrS: "i64.shr_s",
|
||||
OpcodeI64ShrU: "i64.shr_u",
|
||||
OpcodeI64Rotl: "i64.rotl",
|
||||
OpcodeI64Rotr: "i64.rotr",
|
||||
OpcodeF32Abs: "f32.abs",
|
||||
OpcodeF32Neg: "f32.neg",
|
||||
OpcodeF32Ceil: "f32.ceil",
|
||||
OpcodeF32Floor: "f32.floor",
|
||||
OpcodeF32Trunc: "f32.trunc",
|
||||
OpcodeF32Nearest: "f32.nearest",
|
||||
OpcodeF32Sqrt: "f32.sqrt",
|
||||
OpcodeF32Add: "f32.add",
|
||||
OpcodeF32Sub: "f32.sub",
|
||||
OpcodeF32Mul: "f32.mul",
|
||||
OpcodeF32Div: "f32.div",
|
||||
OpcodeF32Min: "f32.min",
|
||||
OpcodeF32Max: "f32.max",
|
||||
OpcodeF32Copysign: "f32.copysign",
|
||||
OpcodeF64Abs: "f64.abs",
|
||||
OpcodeF64Neg: "f64.neg",
|
||||
OpcodeF64Ceil: "f64.ceil",
|
||||
OpcodeF64Floor: "f64.floor",
|
||||
OpcodeF64Trunc: "f64.trunc",
|
||||
OpcodeF64Nearest: "f64.nearest",
|
||||
OpcodeF64Sqrt: "f64.sqrt",
|
||||
OpcodeF64Add: "f64.add",
|
||||
OpcodeF64Sub: "f64.sub",
|
||||
OpcodeF64Mul: "f64.mul",
|
||||
OpcodeF64Div: "f64.div",
|
||||
OpcodeF64Min: "f64.min",
|
||||
OpcodeF64Max: "f64.max",
|
||||
OpcodeF64Copysign: "f64.copysign",
|
||||
OpcodeI32WrapI64: "i32.wrap_i64",
|
||||
OpcodeI32TruncF32S: "i32.trunc_f32_s",
|
||||
OpcodeI32TruncF32U: "i32.trunc_f32_u",
|
||||
OpcodeI32TruncF64S: "i32.trunc_f64_s",
|
||||
OpcodeI32TruncF64U: "i32.trunc_f64_u",
|
||||
OpcodeI64ExtendI32S: "i64.extend_i32_s",
|
||||
OpcodeI64ExtendI32U: "i64.extend_i32_u",
|
||||
OpcodeI64TruncF32S: "i64.trunc_f32_s",
|
||||
OpcodeI64TruncF32U: "i64.trunc_f32_u",
|
||||
OpcodeI64TruncF64S: "i64.trunc_f64_s",
|
||||
OpcodeI64TruncF64U: "i64.trunc_f64_u",
|
||||
OpcodeF32ConvertI32s: "f32.convert_i32_s",
|
||||
OpcodeF32ConvertI32U: "f32.convert_i32_u",
|
||||
OpcodeF32ConvertI64S: "f32.convert_i64_s",
|
||||
OpcodeF32ConvertI64U: "f32.convert_i64u",
|
||||
OpcodeF32DemoteF64: "f32.demote_f64",
|
||||
OpcodeF64ConvertI32S: "f64.convert_i32_s",
|
||||
OpcodeF64ConvertI32U: "f64.convert_i32_u",
|
||||
OpcodeF64ConvertI64S: "f64.convert_i64_s",
|
||||
OpcodeF64ConvertI64U: "f64.convert_i64_u",
|
||||
OpcodeF64PromoteF32: "f64.promote_f32",
|
||||
OpcodeI32ReinterpretF32: "i32.reinterpret_f32",
|
||||
OpcodeI64ReinterpretF64: "i64.reinterpret_f64",
|
||||
OpcodeF32ReinterpretI32: "f32.reinterpret_i32",
|
||||
OpcodeF64ReinterpretI64: "f64.reinterpret_i64",
|
||||
const (
|
||||
OpcodeUnreachableName = "unreachable"
|
||||
OpcodeNopName = "nop"
|
||||
OpcodeBlockName = "block"
|
||||
OpcodeLoopName = "loop"
|
||||
OpcodeIfName = "if"
|
||||
OpcodeElseName = "else"
|
||||
OpcodeEndName = "end"
|
||||
OpcodeBrName = "br"
|
||||
OpcodeBrIfName = "br_if"
|
||||
OpcodeBrTableName = "br_table"
|
||||
OpcodeReturnName = "return"
|
||||
OpcodeCallName = "call"
|
||||
OpcodeCallIndirectName = "call_indirect"
|
||||
OpcodeDropName = "drop"
|
||||
OpcodeSelectName = "select"
|
||||
OpcodeLocalGetName = "local.get"
|
||||
OpcodeLocalSetName = "local.set"
|
||||
OpcodeLocalTeeName = "local.tee"
|
||||
OpcodeGlobalGetName = "global.get"
|
||||
OpcodeGlobalSetName = "global.set"
|
||||
OpcodeI32LoadName = "i32.load"
|
||||
OpcodeI64LoadName = "i64.load"
|
||||
OpcodeF32LoadName = "f32.load"
|
||||
OpcodeF64LoadName = "f64.load"
|
||||
OpcodeI32Load8SName = "i32.load8_s"
|
||||
OpcodeI32Load8UName = "i32.load8_u"
|
||||
OpcodeI32Load16SName = "i32.load16_s"
|
||||
OpcodeI32Load16UName = "i32.load16_u"
|
||||
OpcodeI64Load8SName = "i64.load8_s"
|
||||
OpcodeI64Load8UName = "i64.load8_u"
|
||||
OpcodeI64Load16SName = "i64.load16_s"
|
||||
OpcodeI64Load16UName = "i64.load16_u"
|
||||
OpcodeI64Load32SName = "i64.load32_s"
|
||||
OpcodeI64Load32UName = "i64.load32_u"
|
||||
OpcodeI32StoreName = "i32.store"
|
||||
OpcodeI64StoreName = "i64.store"
|
||||
OpcodeF32StoreName = "f32.store"
|
||||
OpcodeF64StoreName = "f64.store"
|
||||
OpcodeI32Store8Name = "i32.store8"
|
||||
OpcodeI32Store16Name = "i32.store16"
|
||||
OpcodeI64Store8Name = "i64.store8"
|
||||
OpcodeI64Store16Name = "i64.store16"
|
||||
OpcodeI64Store32Name = "i64.store32"
|
||||
OpcodeMemorySizeName = "memory.size"
|
||||
OpcodeMemoryGrowName = "memory.grow"
|
||||
OpcodeI32ConstName = "i32.const"
|
||||
OpcodeI64ConstName = "i64.const"
|
||||
OpcodeF32ConstName = "f32.const"
|
||||
OpcodeF64ConstName = "f64.const"
|
||||
OpcodeI32EqzName = "i32.eqz"
|
||||
OpcodeI32EqName = "i32.eq"
|
||||
OpcodeI32NeName = "i32.ne"
|
||||
OpcodeI32LtSName = "i32.lt_s"
|
||||
OpcodeI32LtUName = "i32.lt_u"
|
||||
OpcodeI32GtSName = "i32.gt_s"
|
||||
OpcodeI32GtUName = "i32.gt_u"
|
||||
OpcodeI32LeSName = "i32.le_s"
|
||||
OpcodeI32LeUName = "i32.le_u"
|
||||
OpcodeI32GeSName = "i32.ge_s"
|
||||
OpcodeI32GeUName = "i32.ge_u"
|
||||
OpcodeI64EqzName = "i64.eqz"
|
||||
OpcodeI64EqName = "i64.eq"
|
||||
OpcodeI64NeName = "i64.ne"
|
||||
OpcodeI64LtSName = "i64.lt_s"
|
||||
OpcodeI64LtUName = "i64.lt_u"
|
||||
OpcodeI64GtSName = "i64.gt_s"
|
||||
OpcodeI64GtUName = "i64.gt_u"
|
||||
OpcodeI64LeSName = "i64.le_s"
|
||||
OpcodeI64LeUName = "i64.le_u"
|
||||
OpcodeI64GeSName = "i64.ge_s"
|
||||
OpcodeI64GeUName = "i64.ge_u"
|
||||
OpcodeF32EqName = "f32.eq"
|
||||
OpcodeF32NeName = "f32.ne"
|
||||
OpcodeF32LtName = "f32.lt"
|
||||
OpcodeF32GtName = "f32.gt"
|
||||
OpcodeF32LeName = "f32.le"
|
||||
OpcodeF32GeName = "f32.ge"
|
||||
OpcodeF64EqName = "f64.eq"
|
||||
OpcodeF64NeName = "f64.ne"
|
||||
OpcodeF64LtName = "f64.lt"
|
||||
OpcodeF64GtName = "f64.gt"
|
||||
OpcodeF64LeName = "f64.le"
|
||||
OpcodeF64GeName = "f64.ge"
|
||||
OpcodeI32ClzName = "i32.clz"
|
||||
OpcodeI32CtzName = "i32.ctz"
|
||||
OpcodeI32PopcntName = "i32.popcnt"
|
||||
OpcodeI32AddName = "i32.add"
|
||||
OpcodeI32SubName = "i32.sub"
|
||||
OpcodeI32MulName = "i32.mul"
|
||||
OpcodeI32DivSName = "i32.div_s"
|
||||
OpcodeI32DivUName = "i32.div_u"
|
||||
OpcodeI32RemSName = "i32.rem_s"
|
||||
OpcodeI32RemUName = "i32.rem_u"
|
||||
OpcodeI32AndName = "i32.and"
|
||||
OpcodeI32OrName = "i32.or"
|
||||
OpcodeI32XorName = "i32.xor"
|
||||
OpcodeI32ShlName = "i32.shl"
|
||||
OpcodeI32ShrSName = "i32.shr_s"
|
||||
OpcodeI32ShrUName = "i32.shr_u"
|
||||
OpcodeI32RotlName = "i32.rotl"
|
||||
OpcodeI32RotrName = "i32.rotr"
|
||||
OpcodeI64ClzName = "i64.clz"
|
||||
OpcodeI64CtzName = "i64.ctz"
|
||||
OpcodeI64PopcntName = "i64.popcnt"
|
||||
OpcodeI64AddName = "i64.add"
|
||||
OpcodeI64SubName = "i64.sub"
|
||||
OpcodeI64MulName = "i64.mul"
|
||||
OpcodeI64DivSName = "i64.div_s"
|
||||
OpcodeI64DivUName = "i64.div_u"
|
||||
OpcodeI64RemSName = "i64.rem_s"
|
||||
OpcodeI64RemUName = "i64.rem_u"
|
||||
OpcodeI64AndName = "i64.and"
|
||||
OpcodeI64OrName = "i64.or"
|
||||
OpcodeI64XorName = "i64.xor"
|
||||
OpcodeI64ShlName = "i64.shl"
|
||||
OpcodeI64ShrSName = "i64.shr_s"
|
||||
OpcodeI64ShrUName = "i64.shr_u"
|
||||
OpcodeI64RotlName = "i64.rotl"
|
||||
OpcodeI64RotrName = "i64.rotr"
|
||||
OpcodeF32AbsName = "f32.abs"
|
||||
OpcodeF32NegName = "f32.neg"
|
||||
OpcodeF32CeilName = "f32.ceil"
|
||||
OpcodeF32FloorName = "f32.floor"
|
||||
OpcodeF32TruncName = "f32.trunc"
|
||||
OpcodeF32NearestName = "f32.nearest"
|
||||
OpcodeF32SqrtName = "f32.sqrt"
|
||||
OpcodeF32AddName = "f32.add"
|
||||
OpcodeF32SubName = "f32.sub"
|
||||
OpcodeF32MulName = "f32.mul"
|
||||
OpcodeF32DivName = "f32.div"
|
||||
OpcodeF32MinName = "f32.min"
|
||||
OpcodeF32MaxName = "f32.max"
|
||||
OpcodeF32CopysignName = "f32.copysign"
|
||||
OpcodeF64AbsName = "f64.abs"
|
||||
OpcodeF64NegName = "f64.neg"
|
||||
OpcodeF64CeilName = "f64.ceil"
|
||||
OpcodeF64FloorName = "f64.floor"
|
||||
OpcodeF64TruncName = "f64.trunc"
|
||||
OpcodeF64NearestName = "f64.nearest"
|
||||
OpcodeF64SqrtName = "f64.sqrt"
|
||||
OpcodeF64AddName = "f64.add"
|
||||
OpcodeF64SubName = "f64.sub"
|
||||
OpcodeF64MulName = "f64.mul"
|
||||
OpcodeF64DivName = "f64.div"
|
||||
OpcodeF64MinName = "f64.min"
|
||||
OpcodeF64MaxName = "f64.max"
|
||||
OpcodeF64CopysignName = "f64.copysign"
|
||||
OpcodeI32WrapI64Name = "i32.wrap_i64"
|
||||
OpcodeI32TruncF32SName = "i32.trunc_f32_s"
|
||||
OpcodeI32TruncF32UName = "i32.trunc_f32_u"
|
||||
OpcodeI32TruncF64SName = "i32.trunc_f64_s"
|
||||
OpcodeI32TruncF64UName = "i32.trunc_f64_u"
|
||||
OpcodeI64ExtendI32SName = "i64.extend_i32_s"
|
||||
OpcodeI64ExtendI32UName = "i64.extend_i32_u"
|
||||
OpcodeI64TruncF32SName = "i64.trunc_f32_s"
|
||||
OpcodeI64TruncF32UName = "i64.trunc_f32_u"
|
||||
OpcodeI64TruncF64SName = "i64.trunc_f64_s"
|
||||
OpcodeI64TruncF64UName = "i64.trunc_f64_u"
|
||||
OpcodeF32ConvertI32sName = "f32.convert_i32_s"
|
||||
OpcodeF32ConvertI32UName = "f32.convert_i32_u"
|
||||
OpcodeF32ConvertI64SName = "f32.convert_i64_s"
|
||||
OpcodeF32ConvertI64UName = "f32.convert_i64u"
|
||||
OpcodeF32DemoteF64Name = "f32.demote_f64"
|
||||
OpcodeF64ConvertI32SName = "f64.convert_i32_s"
|
||||
OpcodeF64ConvertI32UName = "f64.convert_i32_u"
|
||||
OpcodeF64ConvertI64SName = "f64.convert_i64_s"
|
||||
OpcodeF64ConvertI64UName = "f64.convert_i64_u"
|
||||
OpcodeF64PromoteF32Name = "f64.promote_f32"
|
||||
OpcodeI32ReinterpretF32Name = "i32.reinterpret_f32"
|
||||
OpcodeI64ReinterpretF64Name = "i64.reinterpret_f64"
|
||||
OpcodeF32ReinterpretI32Name = "f32.reinterpret_i32"
|
||||
OpcodeF64ReinterpretI64Name = "f64.reinterpret_i64"
|
||||
|
||||
// Below are toggled with FeatureSignExtensionOps
|
||||
OpcodeI32Extend8S: "i32.extend8_s",
|
||||
OpcodeI32Extend16S: "i32.extend16_s",
|
||||
OpcodeI64Extend8S: "i64.extend8_s",
|
||||
OpcodeI64Extend16S: "i64.extend16_s",
|
||||
OpcodeI64Extend32S: "i64.extend32_s",
|
||||
|
||||
OpcodeI32Extend8SName = "i32.extend8_s"
|
||||
OpcodeI32Extend16SName = "i32.extend16_s"
|
||||
OpcodeI64Extend8SName = "i64.extend8_s"
|
||||
OpcodeI64Extend16SName = "i64.extend16_s"
|
||||
OpcodeI64Extend32SName = "i64.extend32_s"
|
||||
)
|
||||
|
||||
var instructionNames = [256]string{
|
||||
OpcodeUnreachable: OpcodeUnreachableName,
|
||||
OpcodeNop: OpcodeNopName,
|
||||
OpcodeBlock: OpcodeBlockName,
|
||||
OpcodeLoop: OpcodeLoopName,
|
||||
OpcodeIf: OpcodeIfName,
|
||||
OpcodeElse: OpcodeElseName,
|
||||
OpcodeEnd: OpcodeEndName,
|
||||
OpcodeBr: OpcodeBrName,
|
||||
OpcodeBrIf: OpcodeBrIfName,
|
||||
OpcodeBrTable: OpcodeBrTableName,
|
||||
OpcodeReturn: OpcodeReturnName,
|
||||
OpcodeCall: OpcodeCallName,
|
||||
OpcodeCallIndirect: OpcodeCallIndirectName,
|
||||
OpcodeDrop: OpcodeDropName,
|
||||
OpcodeSelect: OpcodeSelectName,
|
||||
OpcodeLocalGet: OpcodeLocalGetName,
|
||||
OpcodeLocalSet: OpcodeLocalSetName,
|
||||
OpcodeLocalTee: OpcodeLocalTeeName,
|
||||
OpcodeGlobalGet: OpcodeGlobalGetName,
|
||||
OpcodeGlobalSet: OpcodeGlobalSetName,
|
||||
OpcodeI32Load: OpcodeI32LoadName,
|
||||
OpcodeI64Load: OpcodeI64LoadName,
|
||||
OpcodeF32Load: OpcodeF32LoadName,
|
||||
OpcodeF64Load: OpcodeF64LoadName,
|
||||
OpcodeI32Load8S: OpcodeI32Load8SName,
|
||||
OpcodeI32Load8U: OpcodeI32Load8UName,
|
||||
OpcodeI32Load16S: OpcodeI32Load16SName,
|
||||
OpcodeI32Load16U: OpcodeI32Load16UName,
|
||||
OpcodeI64Load8S: OpcodeI64Load8SName,
|
||||
OpcodeI64Load8U: OpcodeI64Load8UName,
|
||||
OpcodeI64Load16S: OpcodeI64Load16SName,
|
||||
OpcodeI64Load16U: OpcodeI64Load16UName,
|
||||
OpcodeI64Load32S: OpcodeI64Load32SName,
|
||||
OpcodeI64Load32U: OpcodeI64Load32UName,
|
||||
OpcodeI32Store: OpcodeI32StoreName,
|
||||
OpcodeI64Store: OpcodeI64StoreName,
|
||||
OpcodeF32Store: OpcodeF32StoreName,
|
||||
OpcodeF64Store: OpcodeF64StoreName,
|
||||
OpcodeI32Store8: OpcodeI32Store8Name,
|
||||
OpcodeI32Store16: OpcodeI32Store16Name,
|
||||
OpcodeI64Store8: OpcodeI64Store8Name,
|
||||
OpcodeI64Store16: OpcodeI64Store16Name,
|
||||
OpcodeI64Store32: OpcodeI64Store32Name,
|
||||
OpcodeMemorySize: OpcodeMemorySizeName,
|
||||
OpcodeMemoryGrow: OpcodeMemoryGrowName,
|
||||
OpcodeI32Const: OpcodeI32ConstName,
|
||||
OpcodeI64Const: OpcodeI64ConstName,
|
||||
OpcodeF32Const: OpcodeF32ConstName,
|
||||
OpcodeF64Const: OpcodeF64ConstName,
|
||||
OpcodeI32Eqz: OpcodeI32EqzName,
|
||||
OpcodeI32Eq: OpcodeI32EqName,
|
||||
OpcodeI32Ne: OpcodeI32NeName,
|
||||
OpcodeI32LtS: OpcodeI32LtSName,
|
||||
OpcodeI32LtU: OpcodeI32LtUName,
|
||||
OpcodeI32GtS: OpcodeI32GtSName,
|
||||
OpcodeI32GtU: OpcodeI32GtUName,
|
||||
OpcodeI32LeS: OpcodeI32LeSName,
|
||||
OpcodeI32LeU: OpcodeI32LeUName,
|
||||
OpcodeI32GeS: OpcodeI32GeSName,
|
||||
OpcodeI32GeU: OpcodeI32GeUName,
|
||||
OpcodeI64Eqz: OpcodeI64EqzName,
|
||||
OpcodeI64Eq: OpcodeI64EqName,
|
||||
OpcodeI64Ne: OpcodeI64NeName,
|
||||
OpcodeI64LtS: OpcodeI64LtSName,
|
||||
OpcodeI64LtU: OpcodeI64LtUName,
|
||||
OpcodeI64GtS: OpcodeI64GtSName,
|
||||
OpcodeI64GtU: OpcodeI64GtUName,
|
||||
OpcodeI64LeS: OpcodeI64LeSName,
|
||||
OpcodeI64LeU: OpcodeI64LeUName,
|
||||
OpcodeI64GeS: OpcodeI64GeSName,
|
||||
OpcodeI64GeU: OpcodeI64GeUName,
|
||||
OpcodeF32Eq: OpcodeF32EqName,
|
||||
OpcodeF32Ne: OpcodeF32NeName,
|
||||
OpcodeF32Lt: OpcodeF32LtName,
|
||||
OpcodeF32Gt: OpcodeF32GtName,
|
||||
OpcodeF32Le: OpcodeF32LeName,
|
||||
OpcodeF32Ge: OpcodeF32GeName,
|
||||
OpcodeF64Eq: OpcodeF64EqName,
|
||||
OpcodeF64Ne: OpcodeF64NeName,
|
||||
OpcodeF64Lt: OpcodeF64LtName,
|
||||
OpcodeF64Gt: OpcodeF64GtName,
|
||||
OpcodeF64Le: OpcodeF64LeName,
|
||||
OpcodeF64Ge: OpcodeF64GeName,
|
||||
OpcodeI32Clz: OpcodeI32ClzName,
|
||||
OpcodeI32Ctz: OpcodeI32CtzName,
|
||||
OpcodeI32Popcnt: OpcodeI32PopcntName,
|
||||
OpcodeI32Add: OpcodeI32AddName,
|
||||
OpcodeI32Sub: OpcodeI32SubName,
|
||||
OpcodeI32Mul: OpcodeI32MulName,
|
||||
OpcodeI32DivS: OpcodeI32DivSName,
|
||||
OpcodeI32DivU: OpcodeI32DivUName,
|
||||
OpcodeI32RemS: OpcodeI32RemSName,
|
||||
OpcodeI32RemU: OpcodeI32RemUName,
|
||||
OpcodeI32And: OpcodeI32AndName,
|
||||
OpcodeI32Or: OpcodeI32OrName,
|
||||
OpcodeI32Xor: OpcodeI32XorName,
|
||||
OpcodeI32Shl: OpcodeI32ShlName,
|
||||
OpcodeI32ShrS: OpcodeI32ShrSName,
|
||||
OpcodeI32ShrU: OpcodeI32ShrUName,
|
||||
OpcodeI32Rotl: OpcodeI32RotlName,
|
||||
OpcodeI32Rotr: OpcodeI32RotrName,
|
||||
OpcodeI64Clz: OpcodeI64ClzName,
|
||||
OpcodeI64Ctz: OpcodeI64CtzName,
|
||||
OpcodeI64Popcnt: OpcodeI64PopcntName,
|
||||
OpcodeI64Add: OpcodeI64AddName,
|
||||
OpcodeI64Sub: OpcodeI64SubName,
|
||||
OpcodeI64Mul: OpcodeI64MulName,
|
||||
OpcodeI64DivS: OpcodeI64DivSName,
|
||||
OpcodeI64DivU: OpcodeI64DivUName,
|
||||
OpcodeI64RemS: OpcodeI64RemSName,
|
||||
OpcodeI64RemU: OpcodeI64RemUName,
|
||||
OpcodeI64And: OpcodeI64AndName,
|
||||
OpcodeI64Or: OpcodeI64OrName,
|
||||
OpcodeI64Xor: OpcodeI64XorName,
|
||||
OpcodeI64Shl: OpcodeI64ShlName,
|
||||
OpcodeI64ShrS: OpcodeI64ShrSName,
|
||||
OpcodeI64ShrU: OpcodeI64ShrUName,
|
||||
OpcodeI64Rotl: OpcodeI64RotlName,
|
||||
OpcodeI64Rotr: OpcodeI64RotrName,
|
||||
OpcodeF32Abs: OpcodeF32AbsName,
|
||||
OpcodeF32Neg: OpcodeF32NegName,
|
||||
OpcodeF32Ceil: OpcodeF32CeilName,
|
||||
OpcodeF32Floor: OpcodeF32FloorName,
|
||||
OpcodeF32Trunc: OpcodeF32TruncName,
|
||||
OpcodeF32Nearest: OpcodeF32NearestName,
|
||||
OpcodeF32Sqrt: OpcodeF32SqrtName,
|
||||
OpcodeF32Add: OpcodeF32AddName,
|
||||
OpcodeF32Sub: OpcodeF32SubName,
|
||||
OpcodeF32Mul: OpcodeF32MulName,
|
||||
OpcodeF32Div: OpcodeF32DivName,
|
||||
OpcodeF32Min: OpcodeF32MinName,
|
||||
OpcodeF32Max: OpcodeF32MaxName,
|
||||
OpcodeF32Copysign: OpcodeF32CopysignName,
|
||||
OpcodeF64Abs: OpcodeF64AbsName,
|
||||
OpcodeF64Neg: OpcodeF64NegName,
|
||||
OpcodeF64Ceil: OpcodeF64CeilName,
|
||||
OpcodeF64Floor: OpcodeF64FloorName,
|
||||
OpcodeF64Trunc: OpcodeF64TruncName,
|
||||
OpcodeF64Nearest: OpcodeF64NearestName,
|
||||
OpcodeF64Sqrt: OpcodeF64SqrtName,
|
||||
OpcodeF64Add: OpcodeF64AddName,
|
||||
OpcodeF64Sub: OpcodeF64SubName,
|
||||
OpcodeF64Mul: OpcodeF64MulName,
|
||||
OpcodeF64Div: OpcodeF64DivName,
|
||||
OpcodeF64Min: OpcodeF64MinName,
|
||||
OpcodeF64Max: OpcodeF64MaxName,
|
||||
OpcodeF64Copysign: OpcodeF64CopysignName,
|
||||
OpcodeI32WrapI64: OpcodeI32WrapI64Name,
|
||||
OpcodeI32TruncF32S: OpcodeI32TruncF32SName,
|
||||
OpcodeI32TruncF32U: OpcodeI32TruncF32UName,
|
||||
OpcodeI32TruncF64S: OpcodeI32TruncF64SName,
|
||||
OpcodeI32TruncF64U: OpcodeI32TruncF64UName,
|
||||
OpcodeI64ExtendI32S: OpcodeI64ExtendI32SName,
|
||||
OpcodeI64ExtendI32U: OpcodeI64ExtendI32UName,
|
||||
OpcodeI64TruncF32S: OpcodeI64TruncF32SName,
|
||||
OpcodeI64TruncF32U: OpcodeI64TruncF32UName,
|
||||
OpcodeI64TruncF64S: OpcodeI64TruncF64SName,
|
||||
OpcodeI64TruncF64U: OpcodeI64TruncF64UName,
|
||||
OpcodeF32ConvertI32s: OpcodeF32ConvertI32sName,
|
||||
OpcodeF32ConvertI32U: OpcodeF32ConvertI32UName,
|
||||
OpcodeF32ConvertI64S: OpcodeF32ConvertI64SName,
|
||||
OpcodeF32ConvertI64U: OpcodeF32ConvertI64UName,
|
||||
OpcodeF32DemoteF64: OpcodeF32DemoteF64Name,
|
||||
OpcodeF64ConvertI32S: OpcodeF64ConvertI32SName,
|
||||
OpcodeF64ConvertI32U: OpcodeF64ConvertI32UName,
|
||||
OpcodeF64ConvertI64S: OpcodeF64ConvertI64SName,
|
||||
OpcodeF64ConvertI64U: OpcodeF64ConvertI64UName,
|
||||
OpcodeF64PromoteF32: OpcodeF64PromoteF32Name,
|
||||
OpcodeI32ReinterpretF32: OpcodeI32ReinterpretF32Name,
|
||||
OpcodeI64ReinterpretF64: OpcodeI64ReinterpretF64Name,
|
||||
OpcodeF32ReinterpretI32: OpcodeF32ReinterpretI32Name,
|
||||
OpcodeF64ReinterpretI64: OpcodeF64ReinterpretI64Name,
|
||||
|
||||
// Below are toggled with FeatureSignExtensionOps
|
||||
OpcodeI32Extend8S: OpcodeI32Extend8SName,
|
||||
OpcodeI32Extend16S: OpcodeI32Extend16SName,
|
||||
OpcodeI64Extend8S: OpcodeI64Extend8SName,
|
||||
OpcodeI64Extend16S: OpcodeI64Extend16SName,
|
||||
OpcodeI64Extend32S: OpcodeI64Extend32SName,
|
||||
}
|
||||
|
||||
// InstructionName returns the instruction corresponding to this binary Opcode.
|
||||
|
||||
@@ -21,12 +21,14 @@ var callStackCeiling = buildoptions.CallStackCeiling
|
||||
|
||||
// engine is an interpreter implementation of wasm.Engine
|
||||
type engine struct {
|
||||
enabledFeatures wasm.Features
|
||||
compiledFunctions map[*wasm.FunctionInstance]*compiledFunction // guarded by mutex.
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
func NewEngine() wasm.Engine {
|
||||
func NewEngine(enabledFeatures wasm.Features) wasm.Engine {
|
||||
return &engine{
|
||||
enabledFeatures: enabledFeatures,
|
||||
compiledFunctions: make(map[*wasm.FunctionInstance]*compiledFunction),
|
||||
}
|
||||
}
|
||||
@@ -172,7 +174,7 @@ func (e *engine) NewModuleEngine(name string, importedFunctions, moduleFunctions
|
||||
for i, f := range moduleFunctions {
|
||||
var compiled *compiledFunction
|
||||
if f.Kind == wasm.FunctionKindWasm {
|
||||
ir, err := wazeroir.Compile(f)
|
||||
ir, err := wazeroir.Compile(e.enabledFeatures, f)
|
||||
if err != nil {
|
||||
me.Close()
|
||||
// TODO(Adrian): extract Module.funcDesc so that errors here have more context
|
||||
@@ -311,7 +313,7 @@ func (e *engine) lowerIROps(f *wasm.FunctionInstance,
|
||||
op.us[1] = uint64(f.Module.Types[o.TypeIndex].TypeID)
|
||||
case *wazeroir.OperationDrop:
|
||||
op.rs = make([]*wazeroir.InclusiveRange, 1)
|
||||
op.rs[0] = o.Range
|
||||
op.rs[0] = o.Depth
|
||||
case *wazeroir.OperationSelect:
|
||||
case *wazeroir.OperationPick:
|
||||
op.us = make([]uint64, 1)
|
||||
@@ -629,7 +631,7 @@ func (ce *callEngine) callNativeFunc(ctx *wasm.ModuleContext, f *compiledFunctio
|
||||
}
|
||||
case wazeroir.OperationKindBrTable:
|
||||
{
|
||||
if v := int(ce.pop()); v < len(op.us)-1 {
|
||||
if v := uint64(ce.pop()); v < uint64(len(op.us)-1) {
|
||||
ce.drop(op.rs[v+1])
|
||||
frame.pc = op.us[v+1]
|
||||
} else {
|
||||
|
||||
@@ -51,8 +51,8 @@ var et = &engineTester{}
|
||||
type engineTester struct {
|
||||
}
|
||||
|
||||
func (e engineTester) NewEngine() wasm.Engine {
|
||||
return NewEngine()
|
||||
func (e engineTester) NewEngine(enabledFeatures wasm.Features) wasm.Engine {
|
||||
return NewEngine(enabledFeatures)
|
||||
}
|
||||
|
||||
func (e engineTester) InitTable(me wasm.ModuleEngine, initTableLen uint32, initTableIdxToFnIdx map[wasm.Index]wasm.Index) []interface{} {
|
||||
@@ -198,7 +198,7 @@ func TestInterpreter_CallEngine_callNativeFunc_signExtend(t *testing.T) {
|
||||
|
||||
func TestInterpreter_EngineCompile_Errors(t *testing.T) {
|
||||
t.Run("invalid import", func(t *testing.T) {
|
||||
e := et.NewEngine().(*engine)
|
||||
e := et.NewEngine(wasm.Features20191205).(*engine)
|
||||
_, err := e.NewModuleEngine(t.Name(),
|
||||
[]*wasm.FunctionInstance{{Module: &wasm.ModuleInstance{Name: "uncompiled"}, DebugName: "uncompiled.fn"}},
|
||||
nil, // moduleFunctions
|
||||
@@ -209,7 +209,7 @@ func TestInterpreter_EngineCompile_Errors(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("release on compilation error", func(t *testing.T) {
|
||||
e := et.NewEngine().(*engine)
|
||||
e := et.NewEngine(wasm.Features20191205).(*engine)
|
||||
|
||||
importedFunctions := []*wasm.FunctionInstance{
|
||||
{DebugName: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
|
||||
@@ -269,7 +269,7 @@ func TestInterpreter_Close(t *testing.T) {
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
e := et.NewEngine().(*engine)
|
||||
e := et.NewEngine(wasm.Features20191205).(*engine)
|
||||
if len(tc.importedFunctions) > 0 {
|
||||
// initialize the module-engine containing imported functions
|
||||
me, err := e.NewModuleEngine(t.Name(), nil, tc.importedFunctions, nil, nil)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
type (
|
||||
// engine is an JIT implementation of wasm.Engine
|
||||
engine struct {
|
||||
enabledFeatures wasm.Features
|
||||
compiledFunctions map[*wasm.FunctionInstance]*compiledFunction // guarded by mutex.
|
||||
mux sync.RWMutex
|
||||
// setFinalizer defaults to runtime.SetFinalizer, but overridable for tests.
|
||||
@@ -367,7 +368,7 @@ func (e *engine) NewModuleEngine(name string, importedFunctions, moduleFunctions
|
||||
var compiled *compiledFunction
|
||||
var err error
|
||||
if f.Kind == wasm.FunctionKindWasm {
|
||||
compiled, err = compileWasmFunction(f)
|
||||
compiled, err = compileWasmFunction(e.enabledFeatures, f)
|
||||
} else {
|
||||
compiled, err = compileHostFunction(f)
|
||||
}
|
||||
@@ -513,12 +514,13 @@ func (me *moduleEngine) Call(m *wasm.ModuleContext, f *wasm.FunctionInstance, pa
|
||||
return
|
||||
}
|
||||
|
||||
func NewEngine() wasm.Engine {
|
||||
return newEngine()
|
||||
func NewEngine(enabledFeatures wasm.Features) wasm.Engine {
|
||||
return newEngine(enabledFeatures)
|
||||
}
|
||||
|
||||
func newEngine() *engine {
|
||||
func newEngine(enabledFeatures wasm.Features) *engine {
|
||||
return &engine{
|
||||
enabledFeatures: enabledFeatures,
|
||||
compiledFunctions: map[*wasm.FunctionInstance]*compiledFunction{},
|
||||
setFinalizer: runtime.SetFinalizer,
|
||||
}
|
||||
@@ -814,8 +816,8 @@ func compileHostFunction(f *wasm.FunctionInstance) (*compiledFunction, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func compileWasmFunction(f *wasm.FunctionInstance) (*compiledFunction, error) {
|
||||
ir, err := wazeroir.Compile(f)
|
||||
func compileWasmFunction(enabledFeatures wasm.Features, f *wasm.FunctionInstance) (*compiledFunction, error) {
|
||||
ir, err := wazeroir.Compile(enabledFeatures, f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to lower to wazeroir: %w", err)
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ var et = &engineTester{}
|
||||
|
||||
type engineTester struct{}
|
||||
|
||||
func (e *engineTester) NewEngine() wasm.Engine {
|
||||
return newEngine()
|
||||
func (e *engineTester) NewEngine(enabledFeatures wasm.Features) wasm.Engine {
|
||||
return newEngine(enabledFeatures)
|
||||
}
|
||||
|
||||
func (e *engineTester) InitTable(me wasm.ModuleEngine, initTableLen uint32, initTableIdxToFnIdx map[wasm.Index]wasm.Index) []interface{} {
|
||||
@@ -149,7 +149,7 @@ func requireSupportedOSArch(t *testing.T) {
|
||||
|
||||
func TestJIT_EngineCompile_Errors(t *testing.T) {
|
||||
t.Run("invalid import", func(t *testing.T) {
|
||||
e := et.NewEngine()
|
||||
e := et.NewEngine(wasm.Features20191205)
|
||||
_, err := e.NewModuleEngine(
|
||||
t.Name(),
|
||||
[]*wasm.FunctionInstance{{Module: &wasm.ModuleInstance{Name: "uncompiled"}, DebugName: "uncompiled.fn"}},
|
||||
@@ -161,7 +161,7 @@ func TestJIT_EngineCompile_Errors(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("release on compilation error", func(t *testing.T) {
|
||||
e := et.NewEngine().(*engine)
|
||||
e := et.NewEngine(wasm.Features20191205).(*engine)
|
||||
|
||||
importedFunctions := []*wasm.FunctionInstance{
|
||||
{DebugName: "1", Type: &wasm.FunctionType{}, Body: []byte{wasm.OpcodeEnd}, Module: &wasm.ModuleInstance{}},
|
||||
@@ -213,7 +213,7 @@ func TestJIT_NewModuleEngine_CompiledFunctions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
e := et.NewEngine().(*engine)
|
||||
e := et.NewEngine(wasm.Features20191205).(*engine)
|
||||
|
||||
importedFinalizer := fakeFinalizer{}
|
||||
e.setFinalizer = importedFinalizer.setFinalizer
|
||||
@@ -325,7 +325,7 @@ func TestJIT_ModuleEngine_Close(t *testing.T) {
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
e := et.NewEngine().(*engine)
|
||||
e := et.NewEngine(wasm.Features20191205).(*engine)
|
||||
var imported *moduleEngine
|
||||
if len(tc.importedFunctions) > 0 {
|
||||
// Instantiate the imported module
|
||||
@@ -387,8 +387,9 @@ func TestJIT_ModuleEngine_Close(t *testing.T) {
|
||||
// allows us to safely access to their data region from native code.
|
||||
// See comments on initialValueStackSize and initialCallFrameStackSize.
|
||||
func TestJIT_SliceAllocatedOnHeap(t *testing.T) {
|
||||
e := newEngine()
|
||||
store := wasm.NewStore(e, wasm.Features20191205)
|
||||
enabledFeatures := wasm.Features20191205
|
||||
e := newEngine(enabledFeatures)
|
||||
store := wasm.NewStore(enabledFeatures, e)
|
||||
|
||||
const hostModuleName = "env"
|
||||
const hostFnName = "grow_and_shrink_goroutine_stack"
|
||||
@@ -408,7 +409,7 @@ func TestJIT_SliceAllocatedOnHeap(t *testing.T) {
|
||||
// Trigger relocation of goroutine stack because at this point we have the majority of
|
||||
// goroutine stack unused after recursive call.
|
||||
runtime.GC()
|
||||
}}, map[string]*wasm.Memory{}, map[string]*wasm.Global{})
|
||||
}}, map[string]*wasm.Memory{}, map[string]*wasm.Global{}, enabledFeatures)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.Instantiate(context.Background(), hm, hostModuleName, nil)
|
||||
|
||||
@@ -836,7 +836,7 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
|
||||
|
||||
// compileDrop implements compiler.compileDrop for the amd64 architecture.
|
||||
func (c *amd64Compiler) compileDrop(o *wazeroir.OperationDrop) error {
|
||||
return c.emitDropRange(o.Range)
|
||||
return c.emitDropRange(o.Depth)
|
||||
}
|
||||
|
||||
func (c *amd64Compiler) emitDropRange(r *wazeroir.InclusiveRange) error {
|
||||
|
||||
@@ -1143,7 +1143,7 @@ func (c *arm64Compiler) compileCallIndirect(o *wazeroir.OperationCallIndirect) e
|
||||
|
||||
// compileDrop implements compiler.compileDrop for the arm64 architecture.
|
||||
func (c *arm64Compiler) compileDrop(o *wazeroir.OperationDrop) error {
|
||||
return c.compileDropRange(o.Range)
|
||||
return c.compileDropRange(o.Depth)
|
||||
}
|
||||
|
||||
// compileDropRange is the implementation of compileDrop. See compiler.compileDrop.
|
||||
|
||||
@@ -268,7 +268,7 @@ func TestCompiler_compileDrop(t *testing.T) {
|
||||
}
|
||||
require.Equal(t, uint64(liveNum), compiler.valueLocationStack().sp)
|
||||
|
||||
err = compiler.compileDrop(&wazeroir.OperationDrop{Range: nil})
|
||||
err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: nil})
|
||||
require.NoError(t, err)
|
||||
|
||||
// After the nil range drop, the stack must remain the same.
|
||||
@@ -306,7 +306,7 @@ func TestCompiler_compileDrop(t *testing.T) {
|
||||
}
|
||||
require.Equal(t, uint64(liveNum+dropTargetNum), compiler.valueLocationStack().sp)
|
||||
|
||||
err = compiler.compileDrop(&wazeroir.OperationDrop{Range: r})
|
||||
err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: r})
|
||||
require.NoError(t, err)
|
||||
|
||||
// After the drop operation, the stack contains only live contents.
|
||||
@@ -355,7 +355,7 @@ func TestCompiler_compileDrop(t *testing.T) {
|
||||
|
||||
require.Equal(t, uint64(total), compiler.valueLocationStack().sp)
|
||||
|
||||
err = compiler.compileDrop(&wazeroir.OperationDrop{Range: r})
|
||||
err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: r})
|
||||
require.NoError(t, err)
|
||||
|
||||
// After the drop operation, the stack contains only live contents.
|
||||
|
||||
@@ -292,20 +292,12 @@ func (m *Module) validateFunctions(enabledFeatures Features, functions []Index,
|
||||
return fmt.Errorf("code count (%d) != function count (%d)", codeCount, functionCount)
|
||||
}
|
||||
|
||||
// The wazero specific limitation described at RATIONALE.md.
|
||||
const maximumValuesOnStack = 1 << 27
|
||||
for idx, typeIndex := range m.FunctionSection {
|
||||
if typeIndex >= typeCount {
|
||||
return fmt.Errorf("invalid %s: type section index %d out of range", m.funcDesc(SectionIDFunction, Index(idx)), typeIndex)
|
||||
}
|
||||
|
||||
if err := validateFunction(
|
||||
enabledFeatures,
|
||||
m.TypeSection[typeIndex],
|
||||
m.CodeSection[idx].Body,
|
||||
m.CodeSection[idx].LocalTypes,
|
||||
functions, globals, memory, table, m.TypeSection, maximumValuesOnStack); err != nil {
|
||||
|
||||
if err := m.validateFunction(enabledFeatures, Index(idx), functions, globals, memory, table); err != nil {
|
||||
return fmt.Errorf("invalid %s: %w", m.funcDesc(SectionIDFunction, Index(idx)), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +530,7 @@ func TestModule_validateImports(t *testing.T) {
|
||||
Type: ExternTypeGlobal,
|
||||
DescGlobal: &GlobalType{ValType: ValueTypeI32, Mutable: true},
|
||||
},
|
||||
expectedErr: `invalid import["m"."n"] global: feature mutable-global is disabled`,
|
||||
expectedErr: `invalid import["m"."n"] global: feature "mutable-global" is disabled`,
|
||||
},
|
||||
{
|
||||
name: "table",
|
||||
@@ -611,7 +611,7 @@ func TestModule_validateExports(t *testing.T) {
|
||||
enabledFeatures: Features20191205.Set(FeatureMutableGlobal, false),
|
||||
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 0}},
|
||||
globals: []*GlobalType{{ValType: ValueTypeI32, Mutable: true}},
|
||||
expectedErr: `invalid export["e1"] global[0]: feature mutable-global is disabled`,
|
||||
expectedErr: `invalid export["e1"] global[0]: feature "mutable-global" is disabled`,
|
||||
},
|
||||
{
|
||||
name: "global out of range",
|
||||
|
||||
@@ -25,12 +25,12 @@ type (
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0
|
||||
Store struct {
|
||||
// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
|
||||
engine Engine
|
||||
|
||||
// EnabledFeatures are read-only to allow optimizations.
|
||||
EnabledFeatures Features
|
||||
|
||||
// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
|
||||
engine Engine
|
||||
|
||||
// moduleNames ensures no race conditions instantiating two modules of the same name
|
||||
moduleNames map[string]struct{} // guarded by mux
|
||||
|
||||
@@ -228,10 +228,10 @@ func (m *ModuleInstance) getExport(name string, et ExternType) (*ExportInstance,
|
||||
return exp, nil
|
||||
}
|
||||
|
||||
func NewStore(engine Engine, enabledFeatures Features) *Store {
|
||||
func NewStore(enabledFeatures Features, engine Engine) *Store {
|
||||
return &Store{
|
||||
engine: engine,
|
||||
EnabledFeatures: enabledFeatures,
|
||||
engine: engine,
|
||||
moduleNames: map[string]struct{}{},
|
||||
modules: map[string]*ModuleInstance{},
|
||||
typeIDs: map[string]FunctionTypeID{},
|
||||
|
||||
@@ -91,7 +91,13 @@ func TestModuleInstance_Memory(t *testing.T) {
|
||||
|
||||
func TestStore_Instantiate(t *testing.T) {
|
||||
s := newStore()
|
||||
m, err := NewHostModule("", map[string]interface{}{"fn": func(api.Module) {}}, map[string]*Memory{}, map[string]*Global{})
|
||||
m, err := NewHostModule(
|
||||
"",
|
||||
map[string]interface{}{"fn": func(api.Module) {}},
|
||||
map[string]*Memory{},
|
||||
map[string]*Global{},
|
||||
Features20191205,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
type key string
|
||||
@@ -121,7 +127,13 @@ func TestStore_CloseModule(t *testing.T) {
|
||||
{
|
||||
name: "Module imports HostModule",
|
||||
initializer: func(t *testing.T, s *Store) {
|
||||
m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, map[string]*Memory{}, map[string]*Global{})
|
||||
m, err := NewHostModule(
|
||||
importedModuleName,
|
||||
map[string]interface{}{"fn": func(api.Module) {}},
|
||||
map[string]*Memory{},
|
||||
map[string]*Global{},
|
||||
Features20191205,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = s.Instantiate(context.Background(), m, importedModuleName, nil)
|
||||
require.NoError(t, err)
|
||||
@@ -178,7 +190,13 @@ func TestStore_CloseModule(t *testing.T) {
|
||||
func TestStore_hammer(t *testing.T) {
|
||||
const importedModuleName = "imported"
|
||||
|
||||
m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, map[string]*Memory{}, map[string]*Global{})
|
||||
m, err := NewHostModule(
|
||||
importedModuleName,
|
||||
map[string]interface{}{"fn": func(api.Module) {}},
|
||||
map[string]*Memory{},
|
||||
map[string]*Global{},
|
||||
Features20191205,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := newStore()
|
||||
@@ -228,7 +246,13 @@ func TestStore_Instantiate_Errors(t *testing.T) {
|
||||
const importedModuleName = "imported"
|
||||
const importingModuleName = "test"
|
||||
|
||||
m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func(api.Module) {}}, map[string]*Memory{}, map[string]*Global{})
|
||||
m, err := NewHostModule(
|
||||
importedModuleName,
|
||||
map[string]interface{}{"fn": func(api.Module) {}},
|
||||
map[string]*Memory{},
|
||||
map[string]*Global{},
|
||||
Features20191205,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Fails if module name already in use", func(t *testing.T) {
|
||||
@@ -313,7 +337,13 @@ func TestStore_Instantiate_Errors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestModuleContext_ExportedFunction(t *testing.T) {
|
||||
host, err := NewHostModule("host", map[string]interface{}{"host_fn": func(api.Module) {}}, map[string]*Memory{}, map[string]*Global{})
|
||||
host, err := NewHostModule(
|
||||
"host",
|
||||
map[string]interface{}{"host_fn": func(api.Module) {}},
|
||||
map[string]*Memory{},
|
||||
map[string]*Global{},
|
||||
Features20191205,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := newStore()
|
||||
@@ -342,13 +372,19 @@ func TestModuleContext_ExportedFunction(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFunctionInstance_Call(t *testing.T) {
|
||||
store := NewStore(&mockEngine{shouldCompileFail: false, callFailIndex: -1}, Features20191205)
|
||||
store := NewStore(Features20191205, &mockEngine{shouldCompileFail: false, callFailIndex: -1})
|
||||
|
||||
// Add the host module
|
||||
functionName := "fn"
|
||||
|
||||
// This is a fake engine, so we don't capture inside the function body.
|
||||
m, err := NewHostModule("host", map[string]interface{}{functionName: func(api.Module) {}}, map[string]*Memory{}, map[string]*Global{})
|
||||
m, err := NewHostModule(
|
||||
"host",
|
||||
map[string]interface{}{functionName: func(api.Module) {}},
|
||||
map[string]*Memory{},
|
||||
map[string]*Global{},
|
||||
Features20191205,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add the host module
|
||||
@@ -399,7 +435,7 @@ type mockModuleEngine struct {
|
||||
}
|
||||
|
||||
func newStore() *Store {
|
||||
return NewStore(&mockEngine{shouldCompileFail: false, callFailIndex: -1}, Features20191205)
|
||||
return NewStore(Features20191205, &mockEngine{shouldCompileFail: false, callFailIndex: -1})
|
||||
}
|
||||
|
||||
// NewModuleEngine implements the same method as documented on wasm.Engine.
|
||||
|
||||
@@ -45,12 +45,12 @@ const (
|
||||
// are also enforced in module instantiation, they are also enforced here, to allow relevant source line/col in errors.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A3
|
||||
type moduleParser struct {
|
||||
// source is the entire WebAssembly text format source code being parsed.
|
||||
source []byte
|
||||
|
||||
// enabledFeatures ensure parsing errs at the correct line and column number when a feature is disabled.
|
||||
enabledFeatures wasm.Features
|
||||
|
||||
// source is the entire WebAssembly text format source code being parsed.
|
||||
source []byte
|
||||
|
||||
// module holds the fields incrementally parsed from tokens in the source.
|
||||
module *wasm.Module
|
||||
|
||||
@@ -147,10 +147,10 @@ func newModuleParser(module *wasm.Module, enabledFeatures wasm.Features, memoryM
|
||||
funcNamespace: newIndexNamespace(module.SectionElementCount),
|
||||
memoryNamespace: newIndexNamespace(module.SectionElementCount),
|
||||
}
|
||||
p.typeParser = newTypeParser(p.typeNamespace, p.onTypeEnd)
|
||||
p.typeUseParser = newTypeUseParser(module, p.typeNamespace)
|
||||
p.typeParser = newTypeParser(enabledFeatures, p.typeNamespace, p.onTypeEnd)
|
||||
p.typeUseParser = newTypeUseParser(enabledFeatures, module, p.typeNamespace)
|
||||
p.funcParser = newFuncParser(enabledFeatures, p.typeUseParser, p.funcNamespace, p.endFunc)
|
||||
p.memoryParser = newMemoryParser(uint32(memoryMaxPages), p.memoryNamespace, p.endMemory)
|
||||
p.memoryParser = newMemoryParser(memoryMaxPages, p.memoryNamespace, p.endMemory)
|
||||
return &p
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ func (p *moduleParser) beginModuleField(tok tokenType, tokenBytes []byte, _, _ u
|
||||
return p.parseExportName, nil
|
||||
case "start":
|
||||
if p.module.SectionElementCount(wasm.SectionIDStart) > 0 {
|
||||
return nil, moreThanOneInvalid("start")
|
||||
return nil, moreThanOneInvalidInSection(wasm.SectionIDStart)
|
||||
}
|
||||
p.pos = positionStart
|
||||
return p.parseStart, nil
|
||||
|
||||
@@ -14,9 +14,8 @@ func TestDecodeModule(t *testing.T) {
|
||||
localGet0End := []byte{wasm.OpcodeLocalGet, 0x00, wasm.OpcodeEnd}
|
||||
|
||||
tests := []struct {
|
||||
name, input string
|
||||
enabledFeatures wasm.Features
|
||||
expected *wasm.Module
|
||||
name, input string
|
||||
expected *wasm.Module
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
@@ -60,6 +59,24 @@ func TestDecodeModule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "type func multiple abbreviated results",
|
||||
input: "(module (type (func (param i32 i32) (result i32 i32))))",
|
||||
expected: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "type func multiple results",
|
||||
input: "(module (type (func (param i32) (param i32) (result i32) (result i32))))",
|
||||
expected: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "import func empty",
|
||||
input: "(module (import \"foo\" \"bar\" (func)))", // ok empty sig
|
||||
@@ -208,7 +225,6 @@ func TestDecodeModule(t *testing.T) {
|
||||
input: `(module
|
||||
(func (param i64) (result i64) local.get 0 i64.extend16_s)
|
||||
)`,
|
||||
enabledFeatures: wasm.FeatureSignExtensionOps,
|
||||
expected: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{i64_i64},
|
||||
FunctionSection: []wasm.Index{0},
|
||||
@@ -621,6 +637,21 @@ func TestDecodeModule(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "import func multiple abbreviated results",
|
||||
input: `(module (import "misc" "swap" (func $swap (param i32 i32) (result i32 i32))))`,
|
||||
expected: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}}},
|
||||
ImportSection: []*wasm.Import{{
|
||||
Module: "misc", Name: "swap",
|
||||
Type: wasm.ExternTypeFunc,
|
||||
DescFunc: 0,
|
||||
}},
|
||||
NameSection: &wasm.NameSection{
|
||||
FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: "swap"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "func empty",
|
||||
input: "(module (func))", // ok empty sig
|
||||
@@ -1099,6 +1130,18 @@ func TestDecodeModule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "func multiple abbreviated results",
|
||||
input: "(module (func $swap (param i32 i32) (result i32 i32) local.get 1 local.get 0))",
|
||||
expected: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}}},
|
||||
FunctionSection: []wasm.Index{0},
|
||||
CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeLocalGet, 0x01, wasm.OpcodeLocalGet, 0x00, wasm.OpcodeEnd}}},
|
||||
NameSection: &wasm.NameSection{
|
||||
FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: "swap"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "func call - index",
|
||||
input: `(module
|
||||
@@ -1507,10 +1550,7 @@ func TestDecodeModule(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.enabledFeatures == 0 {
|
||||
tc.enabledFeatures = wasm.Features20191205
|
||||
}
|
||||
m, err := DecodeModule([]byte(tc.input), tc.enabledFeatures, wasm.MemoryMaxPages)
|
||||
m, err := DecodeModule([]byte(tc.input), wasm.FeaturesFinished, wasm.MemoryMaxPages)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, m)
|
||||
})
|
||||
@@ -1519,10 +1559,9 @@ func TestDecodeModule(t *testing.T) {
|
||||
|
||||
func TestParseModule_Errors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, input string
|
||||
enabledFeatures wasm.Features
|
||||
memoryMaxPages uint32
|
||||
expectedErr string
|
||||
name, input string
|
||||
memoryMaxPages uint32
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "forgot parens",
|
||||
@@ -1580,10 +1619,20 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
expectedErr: "1:30: unexpected ID: $Math in module.import[0]",
|
||||
},
|
||||
{
|
||||
name: "type ID clash",
|
||||
name: "type func ID clash",
|
||||
input: "(module (type $1 (func)) (type $1 (func (param i32))))",
|
||||
expectedErr: "1:32: duplicate ID $1 in module.type[1]",
|
||||
},
|
||||
{
|
||||
name: "type func multiple abbreviated results - multi-value disabled",
|
||||
input: "(module (type (func (param i32 i32) (result i32 i32))))",
|
||||
expectedErr: "1:49: multiple result types invalid as feature \"multi-value\" is disabled in module.type[0].func.result[0]",
|
||||
},
|
||||
{
|
||||
name: "type func multiple results - multi-value disabled",
|
||||
input: "(module (type (func (param i32) (param i32) (result i32) (result i32))))",
|
||||
expectedErr: "1:59: multiple result types invalid as feature \"multi-value\" is disabled in module.type[0].func",
|
||||
},
|
||||
{
|
||||
name: "import missing module",
|
||||
input: "(module (import))",
|
||||
@@ -1684,16 +1733,6 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
input: "(module (import \"\" \"\" (func (param $x i32) (param i32) (param $x i32)))_",
|
||||
expectedErr: "1:63: duplicate ID $x in module.import[0].func.param[2]",
|
||||
},
|
||||
{
|
||||
name: "import func missing param0 type",
|
||||
input: "(module (import \"\" \"\" (func (param))))",
|
||||
expectedErr: "1:35: expected a type in module.import[0].func.param[0]",
|
||||
},
|
||||
{
|
||||
name: "import func missing param1 type",
|
||||
input: "(module (import \"\" \"\" (func (param i32) (param))))",
|
||||
expectedErr: "1:47: expected a type in module.import[0].func.param[1]",
|
||||
},
|
||||
{
|
||||
name: "import func wrong param0 type",
|
||||
input: "(module (import \"\" \"\" (func (param f65))))",
|
||||
@@ -1705,29 +1744,19 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
expectedErr: "1:48: unknown type: f65 in module.import[0].func.param[1]",
|
||||
},
|
||||
{
|
||||
name: "import func double result",
|
||||
input: "(module (import \"\" \"\" (func (param i32) (result i32) (result i32))))",
|
||||
expectedErr: "1:54: unexpected '(' in module.import[0].func",
|
||||
name: "import func multiple abbreviated results - multi-value disabled",
|
||||
input: `(module (import "misc" "swap" (func $swap (param i32 i32) (result i32 i32))))`,
|
||||
expectedErr: "1:71: multiple result types invalid as feature \"multi-value\" is disabled in module.import[0].func.result[0]",
|
||||
},
|
||||
{
|
||||
name: "import func double result type",
|
||||
input: "(module (import \"\" \"\" (func (param i32) (result i32 i32))))",
|
||||
expectedErr: "1:53: redundant type in module.import[0].func.result",
|
||||
name: "import func multiple results - multi-value disabled",
|
||||
input: `(module (import "misc" "swap" (func $swap (param i32) (param i32) (result i32) (result i32))))`,
|
||||
expectedErr: "1:81: multiple result types invalid as feature \"multi-value\" is disabled in module.import[0].func",
|
||||
},
|
||||
{
|
||||
name: "import func wrong result type",
|
||||
input: "(module (import \"\" \"\" (func (param i32) (result f65))))",
|
||||
expectedErr: "1:49: unknown type: f65 in module.import[0].func.result",
|
||||
},
|
||||
{
|
||||
name: "import func wrong no param type",
|
||||
input: "(module (import \"\" \"\" (func (param))))",
|
||||
expectedErr: "1:35: expected a type in module.import[0].func.param[0]",
|
||||
},
|
||||
{
|
||||
name: "import func no result type",
|
||||
input: "(module (import \"\" \"\" (func (param i32) (result))))",
|
||||
expectedErr: "1:48: expected a type in module.import[0].func.result",
|
||||
expectedErr: "1:49: unknown type: f65 in module.import[0].func.result[0]",
|
||||
},
|
||||
{
|
||||
name: "import func wrong param token",
|
||||
@@ -1737,7 +1766,7 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
{
|
||||
name: "import func wrong result token",
|
||||
input: "(module (import \"\" \"\" (func (result () ))))",
|
||||
expectedErr: "1:37: unexpected '(' in module.import[0].func.result",
|
||||
expectedErr: "1:37: unexpected '(' in module.import[0].func.result[0]",
|
||||
},
|
||||
{
|
||||
name: "import func ID after param",
|
||||
@@ -1777,7 +1806,7 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
{
|
||||
name: "import func param after result",
|
||||
input: "(module (import \"\" \"\" (func (result i32) (param i32))))",
|
||||
expectedErr: "1:42: unexpected '(' in module.import[0].func",
|
||||
expectedErr: "1:43: param after result in module.import[0].func",
|
||||
},
|
||||
{
|
||||
name: "import func double desc",
|
||||
@@ -1829,16 +1858,6 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
input: "(module (func (param $x i32 i64) ))",
|
||||
expectedErr: "1:29: cannot assign IDs to parameters in abbreviated form in module.func[0].param[0]",
|
||||
},
|
||||
{
|
||||
name: "func missing param0 type",
|
||||
input: "(module (func (param)))",
|
||||
expectedErr: "1:21: expected a type in module.func[0].param[0]",
|
||||
},
|
||||
{
|
||||
name: "func missing param1 type",
|
||||
input: "(module (func (param i32) (param)))",
|
||||
expectedErr: "1:33: expected a type in module.func[0].param[1]",
|
||||
},
|
||||
{
|
||||
name: "func wrong param0 type",
|
||||
input: "(module (func (param f65)))",
|
||||
@@ -1850,29 +1869,19 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
expectedErr: "1:34: unknown type: f65 in module.func[0].param[1]",
|
||||
},
|
||||
{
|
||||
name: "func duplicate result",
|
||||
input: "(module (func (param i32) (result i32) (result i32)))",
|
||||
expectedErr: "1:41: at most one result allowed in module.func[0]",
|
||||
name: "func multiple abbreviated results - multi-value disabled",
|
||||
input: "(module (func $swap (param i32 i32) (result i32 i32) local.get 1 local.get 0))",
|
||||
expectedErr: "1:49: multiple result types invalid as feature \"multi-value\" is disabled in module.func[0].result[0]",
|
||||
},
|
||||
{
|
||||
name: "func double result type",
|
||||
input: "(module (func (param i32) (result i32 i32)))",
|
||||
expectedErr: "1:39: redundant type in module.func[0].result",
|
||||
name: "func multiple results - multi-value disabled",
|
||||
input: "(module (func $swap (param i32) (param i32) (result i32) (result i32) local.get 1 local.get 0))",
|
||||
expectedErr: "1:59: multiple result types invalid as feature \"multi-value\" is disabled in module.func[0]",
|
||||
},
|
||||
{
|
||||
name: "func wrong result type",
|
||||
input: "(module (func (param i32) (result f65)))",
|
||||
expectedErr: "1:35: unknown type: f65 in module.func[0].result",
|
||||
},
|
||||
{
|
||||
name: "func wrong no param type",
|
||||
input: "(module (func (param)))",
|
||||
expectedErr: "1:21: expected a type in module.func[0].param[0]",
|
||||
},
|
||||
{
|
||||
name: "func no result type",
|
||||
input: "(module (func (param i32) (result)))",
|
||||
expectedErr: "1:34: expected a type in module.func[0].result",
|
||||
expectedErr: "1:35: unknown type: f65 in module.func[0].result[0]",
|
||||
},
|
||||
{
|
||||
name: "func wrong param token",
|
||||
@@ -1882,7 +1891,7 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
{
|
||||
name: "func wrong result token",
|
||||
input: "(module (func (result () )))",
|
||||
expectedErr: "1:23: unexpected '(' in module.func[0].result",
|
||||
expectedErr: "1:23: unexpected '(' in module.func[0].result[0]",
|
||||
},
|
||||
{
|
||||
name: "func ID after param",
|
||||
@@ -1965,7 +1974,7 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
input: `(module
|
||||
(func (param i64) (result i64) local.get 0 i64.extend16_s)
|
||||
)`,
|
||||
expectedErr: "2:47: i64.extend16_s invalid as feature sign-extension-ops is disabled in module.func[0]",
|
||||
expectedErr: "2:47: i64.extend16_s invalid as feature \"sign-extension-ops\" is disabled in module.func[0]",
|
||||
},
|
||||
{
|
||||
name: "memory over max",
|
||||
@@ -2115,13 +2124,10 @@ func TestParseModule_Errors(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.enabledFeatures == 0 {
|
||||
tc.enabledFeatures = wasm.Features20191205
|
||||
}
|
||||
if tc.memoryMaxPages == 0 {
|
||||
tc.memoryMaxPages = wasm.MemoryMaxPages
|
||||
}
|
||||
_, err := DecodeModule([]byte(tc.input), tc.enabledFeatures, tc.memoryMaxPages)
|
||||
_, err := DecodeModule([]byte(tc.input), wasm.Features20191205, tc.memoryMaxPages)
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -67,12 +67,7 @@ func importAfterModuleDefined(section wasm.SectionID) error {
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#tables%E2%91%A0
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memories%E2%91%A0
|
||||
func moreThanOneInvalidInSection(section wasm.SectionID) error {
|
||||
return moreThanOneInvalid(wasm.SectionIDName(section))
|
||||
}
|
||||
|
||||
// moreThanOneInvalid is the failure when a declaration that can result in more than one item.
|
||||
func moreThanOneInvalid(context string) error {
|
||||
return fmt.Errorf("at most one %s allowed", context)
|
||||
return fmt.Errorf("at most one %s allowed", wasm.SectionIDName(section))
|
||||
}
|
||||
|
||||
func unhandledSection(section wasm.SectionID) error {
|
||||
|
||||
@@ -121,8 +121,6 @@ func sExpressionsUnsupported(tok tokenType, tokenBytes []byte, _, _ uint32) (tok
|
||||
switch string(tokenBytes) {
|
||||
case "param":
|
||||
return nil, errors.New("param after result")
|
||||
case "result":
|
||||
return nil, moreThanOneInvalid("result")
|
||||
case "local":
|
||||
return nil, errors.New("TODO: local")
|
||||
}
|
||||
@@ -145,33 +143,47 @@ func (p *funcParser) beginFieldOrInstruction(tok tokenType, tokenBytes []byte, _
|
||||
func (p *funcParser) beginInstruction(tokenBytes []byte) (next tokenParser, err error) {
|
||||
var opCode wasm.Opcode
|
||||
switch string(tokenBytes) {
|
||||
case "call": // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-controlmathsfcallx
|
||||
case wasm.OpcodeCallName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-controlmathsfcallx
|
||||
opCode = wasm.OpcodeCall
|
||||
next = p.parseFuncIndex
|
||||
case "drop": // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-parametricmathsfdrop
|
||||
case wasm.OpcodeDropName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-parametricmathsfdrop
|
||||
opCode = wasm.OpcodeDrop
|
||||
next = p.beginFieldOrInstruction
|
||||
case "i32.add": // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-instr-numeric
|
||||
case wasm.OpcodeI32AddName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-instr-numeric
|
||||
opCode = wasm.OpcodeI32Add
|
||||
next = p.beginFieldOrInstruction
|
||||
case "i32.extend8_s": // See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
opCode = wasm.OpcodeI32Extend8S
|
||||
next = p.beginFieldOrInstruction
|
||||
case "i32.extend16_s": // See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
opCode = wasm.OpcodeI32Extend16S
|
||||
next = p.beginFieldOrInstruction
|
||||
case "i64.extend8_s": // See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
opCode = wasm.OpcodeI64Extend8S
|
||||
next = p.beginFieldOrInstruction
|
||||
case "i64.extend16_s": // See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
opCode = wasm.OpcodeI64Extend16S
|
||||
next = p.beginFieldOrInstruction
|
||||
case "i64.extend32_s": // See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
opCode = wasm.OpcodeI64Extend32S
|
||||
next = p.beginFieldOrInstruction
|
||||
case "local.get": // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-variablemathsflocalgetx%E2%91%A0
|
||||
case wasm.OpcodeI32ConstName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-instr-numeric
|
||||
opCode = wasm.OpcodeI32Const
|
||||
next = p.parseI32
|
||||
case wasm.OpcodeI64ConstName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-instr-numeric
|
||||
opCode = wasm.OpcodeI64Const
|
||||
next = p.parseI64
|
||||
case wasm.OpcodeI64LoadName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instructions%E2%91%A8
|
||||
return p.encodeI64Instruction(wasm.OpcodeI64Load)
|
||||
case wasm.OpcodeI64StoreName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instructions%E2%91%A8
|
||||
return p.encodeI64Instruction(wasm.OpcodeI64Store)
|
||||
case wasm.OpcodeLocalGetName: // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#variable-instructions%E2%91%A0
|
||||
opCode = wasm.OpcodeLocalGet
|
||||
next = p.parseLocalIndex
|
||||
|
||||
// Next are sign-extension-ops
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
|
||||
case wasm.OpcodeI32Extend8SName:
|
||||
opCode = wasm.OpcodeI32Extend8S
|
||||
next = p.beginFieldOrInstruction
|
||||
case wasm.OpcodeI32Extend16SName:
|
||||
opCode = wasm.OpcodeI32Extend16S
|
||||
next = p.beginFieldOrInstruction
|
||||
case wasm.OpcodeI64Extend8SName:
|
||||
opCode = wasm.OpcodeI64Extend8S
|
||||
next = p.beginFieldOrInstruction
|
||||
case wasm.OpcodeI64Extend16SName:
|
||||
opCode = wasm.OpcodeI64Extend16S
|
||||
next = p.beginFieldOrInstruction
|
||||
case wasm.OpcodeI64Extend32SName:
|
||||
opCode = wasm.OpcodeI64Extend32S
|
||||
next = p.beginFieldOrInstruction
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported instruction: %s", tokenBytes)
|
||||
}
|
||||
@@ -187,6 +199,16 @@ func (p *funcParser) beginInstruction(tokenBytes []byte) (next tokenParser, err
|
||||
return next, nil
|
||||
}
|
||||
|
||||
func (p *funcParser) encodeI64Instruction(oc wasm.Opcode) (tokenParser, error) {
|
||||
p.currentBody = append(
|
||||
p.currentBody,
|
||||
oc,
|
||||
3, // alignment=3 (natural alignment) because 2^3 = size of I64 (8 bytes)
|
||||
0, // offset=0 because that's the default
|
||||
)
|
||||
return p.beginFieldOrInstruction, nil
|
||||
}
|
||||
|
||||
// end invokes onFunc to continue parsing
|
||||
func (p *funcParser) end() (tokenParser, error) {
|
||||
var code *wasm.Code
|
||||
@@ -198,6 +220,32 @@ func (p *funcParser) end() (tokenParser, error) {
|
||||
return p.onFunc(p.currentTypeIdx, code, p.currentName, p.currentParamNames)
|
||||
}
|
||||
|
||||
// parseI32 parses a wasm.ValueTypeI32 and appends it to the currentBody.
|
||||
func (p *funcParser) parseI32(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
|
||||
if tok != tokenUN {
|
||||
return nil, unexpectedToken(tok, tokenBytes)
|
||||
}
|
||||
if i, overflow := decodeUint32(tokenBytes); overflow { // TODO: negative and hex
|
||||
return nil, fmt.Errorf("i32 outside range of uint32: %s", tokenBytes)
|
||||
} else { // See /RATIONALE.md we can't tell the signed interpretation of a constant, so default to signed.
|
||||
p.currentBody = append(p.currentBody, leb128.EncodeInt32(int32(i))...)
|
||||
}
|
||||
return p.beginFieldOrInstruction, nil
|
||||
}
|
||||
|
||||
// parseI64 parses a wasm.ValueTypeI64 and appends it to the currentBody.
|
||||
func (p *funcParser) parseI64(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
|
||||
if tok != tokenUN {
|
||||
return nil, unexpectedToken(tok, tokenBytes)
|
||||
}
|
||||
if i, overflow := decodeUint64(tokenBytes); overflow { // TODO: negative and hex
|
||||
return nil, fmt.Errorf("i64 outside range of uint64: %s", tokenBytes)
|
||||
} else { // See /RATIONALE.md we can't tell the signed interpretation of a constant, so default to signed.
|
||||
p.currentBody = append(p.currentBody, leb128.EncodeInt64(int64(i))...)
|
||||
}
|
||||
return p.beginFieldOrInstruction, nil
|
||||
}
|
||||
|
||||
// parseFuncIndex parses an index in the function namespace and appends it to the currentBody. If it was an ID, a
|
||||
// placeholder byte(0) is added instead and will be resolved later.
|
||||
func (p *funcParser) parseFuncIndex(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
|
||||
|
||||
@@ -11,9 +11,8 @@ import (
|
||||
|
||||
func TestFuncParser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, source string
|
||||
enabledFeatures wasm.Features
|
||||
expected *wasm.Code
|
||||
name, source string
|
||||
expected *wasm.Code
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
@@ -46,9 +45,41 @@ func TestFuncParser(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "i32.extend8_s",
|
||||
source: "(func (param i32) local.get 0 i32.extend8_s)",
|
||||
enabledFeatures: wasm.FeatureSignExtensionOps,
|
||||
name: "i32.const",
|
||||
source: "(func i32.const 306)",
|
||||
expected: &wasm.Code{Body: []byte{wasm.OpcodeI32Const, 0xb2, 0x02, wasm.OpcodeEnd}},
|
||||
},
|
||||
{
|
||||
name: "i64.const",
|
||||
source: "(func i64.const 356)",
|
||||
expected: &wasm.Code{Body: []byte{wasm.OpcodeI64Const, 0xe4, 0x02, wasm.OpcodeEnd}},
|
||||
},
|
||||
{
|
||||
name: "i64.load",
|
||||
source: "(func i32.const 8 i64.load)",
|
||||
expected: &wasm.Code{Body: []byte{
|
||||
wasm.OpcodeI32Const, 8, // dynamic memory offset to load
|
||||
wasm.OpcodeI64Load, 0x3, 0x0, // load alignment=3 (natural alignment) staticOffset=0
|
||||
wasm.OpcodeEnd,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "i64.store",
|
||||
source: "(func i32.const 8 i64.const 37 i64.store)",
|
||||
expected: &wasm.Code{Body: []byte{
|
||||
wasm.OpcodeI32Const, 8, // dynamic memory offset to store
|
||||
wasm.OpcodeI64Const, 37, // value to store
|
||||
wasm.OpcodeI64Store, 0x3, 0x0, // load alignment=3 (natural alignment) staticOffset=0
|
||||
wasm.OpcodeEnd,
|
||||
}},
|
||||
},
|
||||
|
||||
// Below are changes to test/core/i32 and i64.wast from the commit that added "sign-extension-ops" support.
|
||||
// See https://github.com/WebAssembly/spec/commit/e308ca2ae04d5083414782e842a81f931138cf2e
|
||||
|
||||
{
|
||||
name: "i32.extend8_s",
|
||||
source: "(func (param i32) local.get 0 i32.extend8_s)",
|
||||
expected: &wasm.Code{Body: []byte{
|
||||
wasm.OpcodeLocalGet, 0x00,
|
||||
wasm.OpcodeI32Extend8S,
|
||||
@@ -56,9 +87,8 @@ func TestFuncParser(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "i32.extend16_s",
|
||||
source: "(func (param i32) local.get 0 i32.extend16_s)",
|
||||
enabledFeatures: wasm.FeatureSignExtensionOps,
|
||||
name: "i32.extend16_s",
|
||||
source: "(func (param i32) local.get 0 i32.extend16_s)",
|
||||
expected: &wasm.Code{Body: []byte{
|
||||
wasm.OpcodeLocalGet, 0x00,
|
||||
wasm.OpcodeI32Extend16S,
|
||||
@@ -66,9 +96,8 @@ func TestFuncParser(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "i64.extend8_s",
|
||||
source: "(func (param i64) local.get 0 i64.extend8_s)",
|
||||
enabledFeatures: wasm.FeatureSignExtensionOps,
|
||||
name: "i64.extend8_s",
|
||||
source: "(func (param i64) local.get 0 i64.extend8_s)",
|
||||
expected: &wasm.Code{Body: []byte{
|
||||
wasm.OpcodeLocalGet, 0x00,
|
||||
wasm.OpcodeI64Extend8S,
|
||||
@@ -76,9 +105,8 @@ func TestFuncParser(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "i64.extend16_s",
|
||||
source: "(func (param i64) local.get 0 i64.extend16_s)",
|
||||
enabledFeatures: wasm.FeatureSignExtensionOps,
|
||||
name: "i64.extend16_s",
|
||||
source: "(func (param i64) local.get 0 i64.extend16_s)",
|
||||
expected: &wasm.Code{Body: []byte{
|
||||
wasm.OpcodeLocalGet, 0x00,
|
||||
wasm.OpcodeI64Extend16S,
|
||||
@@ -86,9 +114,8 @@ func TestFuncParser(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "i64.extend32_s",
|
||||
source: "(func (param i64) local.get 0 i64.extend32_s)",
|
||||
enabledFeatures: wasm.FeatureSignExtensionOps,
|
||||
name: "i64.extend32_s",
|
||||
source: "(func (param i64) local.get 0 i64.extend32_s)",
|
||||
expected: &wasm.Code{Body: []byte{
|
||||
wasm.OpcodeLocalGet, 0x00,
|
||||
wasm.OpcodeI64Extend32S,
|
||||
@@ -108,7 +135,7 @@ func TestFuncParser(t *testing.T) {
|
||||
}
|
||||
|
||||
module := &wasm.Module{}
|
||||
fp := newFuncParser(tc.enabledFeatures, &typeUseParser{module: module}, newIndexNamespace(module.SectionElementCount), setFunc)
|
||||
fp := newFuncParser(wasm.FeaturesFinished, &typeUseParser{module: module}, newIndexNamespace(module.SectionElementCount), setFunc)
|
||||
require.NoError(t, parseFunc(fp, tc.source))
|
||||
require.Equal(t, tc.expected, parsedCode)
|
||||
})
|
||||
@@ -118,7 +145,6 @@ func TestFuncParser(t *testing.T) {
|
||||
func TestFuncParser_Call_Unresolved(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, source string
|
||||
enabledFeatures wasm.Features
|
||||
expectedCode *wasm.Code
|
||||
expectedUnresolvedIndex *unresolvedIndex
|
||||
}{
|
||||
@@ -167,7 +193,7 @@ func TestFuncParser_Call_Unresolved(t *testing.T) {
|
||||
}
|
||||
|
||||
module := &wasm.Module{}
|
||||
fp := newFuncParser(tc.enabledFeatures, &typeUseParser{module: module}, newIndexNamespace(module.SectionElementCount), setFunc)
|
||||
fp := newFuncParser(wasm.Features20191205, &typeUseParser{module: module}, newIndexNamespace(module.SectionElementCount), setFunc)
|
||||
require.NoError(t, parseFunc(fp, tc.source))
|
||||
require.Equal(t, tc.expectedCode, parsedCode)
|
||||
require.Equal(t, []*unresolvedIndex{tc.expectedUnresolvedIndex}, fp.funcNamespace.unresolvedIndices)
|
||||
@@ -177,9 +203,8 @@ func TestFuncParser_Call_Unresolved(t *testing.T) {
|
||||
|
||||
func TestFuncParser_Call_Resolved(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, source string
|
||||
enabledFeatures wasm.Features
|
||||
expected *wasm.Code
|
||||
name, source string
|
||||
expected *wasm.Code
|
||||
}{
|
||||
{
|
||||
name: "index zero",
|
||||
@@ -228,7 +253,7 @@ func TestFuncParser_Call_Resolved(t *testing.T) {
|
||||
return parseErr, nil
|
||||
}
|
||||
|
||||
fp := newFuncParser(tc.enabledFeatures, &typeUseParser{module: &wasm.Module{}}, funcNamespace, setFunc)
|
||||
fp := newFuncParser(wasm.FeaturesFinished, &typeUseParser{module: &wasm.Module{}}, funcNamespace, setFunc)
|
||||
require.NoError(t, parseFunc(fp, tc.source))
|
||||
require.Equal(t, tc.expected, parsedCode)
|
||||
})
|
||||
@@ -237,9 +262,8 @@ func TestFuncParser_Call_Resolved(t *testing.T) {
|
||||
|
||||
func TestFuncParser_Errors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, source string
|
||||
enabledFeatures wasm.Features
|
||||
expectedErr string
|
||||
name, source string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "not field",
|
||||
@@ -261,6 +285,16 @@ func TestFuncParser_Errors(t *testing.T) {
|
||||
source: "(func local.get 4294967296)",
|
||||
expectedErr: "1:17: index outside range of uint32: 4294967296",
|
||||
},
|
||||
{
|
||||
name: "i32.const overflow",
|
||||
source: "(func i32.const 4294967296)",
|
||||
expectedErr: "1:17: i32 outside range of uint32: 4294967296",
|
||||
},
|
||||
{
|
||||
name: "i64.const overflow",
|
||||
source: "(func i64.const 18446744073709551616)",
|
||||
expectedErr: "1:17: i64 outside range of uint64: 18446744073709551616",
|
||||
},
|
||||
{
|
||||
name: "instruction not yet supported",
|
||||
source: "(func f32.const 1.1)",
|
||||
@@ -279,32 +313,32 @@ func TestFuncParser_Errors(t *testing.T) {
|
||||
{
|
||||
name: "duplicate result",
|
||||
source: "(func (result i32) (result i32))",
|
||||
expectedErr: "1:21: at most one result allowed",
|
||||
expectedErr: "1:21: multiple result types invalid as feature \"multi-value\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "i32.extend8_s disabled",
|
||||
source: "(func (param i32) local.get 0 i32.extend8_s)",
|
||||
expectedErr: "1:31: i32.extend8_s invalid as feature sign-extension-ops is disabled",
|
||||
expectedErr: "1:31: i32.extend8_s invalid as feature \"sign-extension-ops\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "i32.extend16_s disabled",
|
||||
source: "(func (param i32) local.get 0 i32.extend16_s)",
|
||||
expectedErr: "1:31: i32.extend16_s invalid as feature sign-extension-ops is disabled",
|
||||
expectedErr: "1:31: i32.extend16_s invalid as feature \"sign-extension-ops\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "i64.extend8_s disabled",
|
||||
source: "(func (param i64) local.get 0 i64.extend8_s)",
|
||||
expectedErr: "1:31: i64.extend8_s invalid as feature sign-extension-ops is disabled",
|
||||
expectedErr: "1:31: i64.extend8_s invalid as feature \"sign-extension-ops\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "i64.extend16_s disabled",
|
||||
source: "(func (param i64) local.get 0 i64.extend16_s)",
|
||||
expectedErr: "1:31: i64.extend16_s invalid as feature sign-extension-ops is disabled",
|
||||
expectedErr: "1:31: i64.extend16_s invalid as feature \"sign-extension-ops\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "i64.extend32_s disabled",
|
||||
source: "(func (param i64) local.get 0 i64.extend32_s)",
|
||||
expectedErr: "1:31: i64.extend32_s invalid as feature sign-extension-ops is disabled",
|
||||
expectedErr: "1:31: i64.extend32_s invalid as feature \"sign-extension-ops\" is disabled",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -313,7 +347,7 @@ func TestFuncParser_Errors(t *testing.T) {
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
module := &wasm.Module{}
|
||||
fp := newFuncParser(tc.enabledFeatures, &typeUseParser{module: module}, newIndexNamespace(module.SectionElementCount), failOnFunc)
|
||||
fp := newFuncParser(wasm.Features20191205, &typeUseParser{module: module}, newIndexNamespace(module.SectionElementCount), failOnFunc)
|
||||
require.EqualError(t, parseFunc(fp, tc.source), tc.expectedErr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func newTypeParser(typeNamespace *indexNamespace, onType onType) *typeParser {
|
||||
return &typeParser{typeNamespace: typeNamespace, onType: onType}
|
||||
func newTypeParser(enabledFeatures wasm.Features, typeNamespace *indexNamespace, onType onType) *typeParser {
|
||||
return &typeParser{enabledFeatures: enabledFeatures, typeNamespace: typeNamespace, onType: onType}
|
||||
}
|
||||
|
||||
type onType func(ft *wasm.FunctionType) tokenParser
|
||||
@@ -22,6 +21,9 @@ type onType func(ft *wasm.FunctionType) tokenParser
|
||||
//
|
||||
// Note: typeParser is reusable. The caller resets via begin.
|
||||
type typeParser struct {
|
||||
// enabledFeatures should be set to moduleParser.enabledFeatures
|
||||
enabledFeatures wasm.Features
|
||||
|
||||
typeNamespace *indexNamespace
|
||||
|
||||
// onType is invoked on end
|
||||
@@ -33,15 +35,16 @@ type typeParser struct {
|
||||
// currentType is reset on begin and complete onType
|
||||
currentType *wasm.FunctionType
|
||||
|
||||
// currentParamField is a field index and used to give an appropriate errorContext. Due to abbreviation it may be
|
||||
// unrelated to the length of currentParams
|
||||
currentParamField wasm.Index
|
||||
// currentField is a field index and used to give an appropriate errorContext.
|
||||
//
|
||||
// Note: Due to abbreviation, this may be less than to the length of params or results.
|
||||
currentField wasm.Index
|
||||
|
||||
// parsedParam allows us to check if we parsed a type in a "param" field. We can't use currentParamField because
|
||||
// when parameters are abbreviated, ex. (param i32 i32), the currentParamField will be less than the type count.
|
||||
parsedParam bool
|
||||
// parsedParamType allows us to check if we parsed a type in a "param" field. This is used to enforce param names
|
||||
// can't coexist with abbreviations.
|
||||
parsedParamType bool
|
||||
|
||||
// parsedParamID is true when the field at currentParamField had an ID. Ex. (param $x i32)
|
||||
// parsedParamID is true when the field at currentField had an ID. Ex. (param $x i32)
|
||||
//
|
||||
// Note: param IDs are allowed to be present on module types, but they serve no purpose. parsedParamID is only used
|
||||
// to validate the grammar rules: ID validation is not necessary.
|
||||
@@ -142,12 +145,15 @@ func (p *typeParser) beginParamOrResult(tok tokenType, tokenBytes []byte, _, _ u
|
||||
return nil, unexpectedToken(tok, tokenBytes)
|
||||
}
|
||||
|
||||
p.parsedParamType = false
|
||||
|
||||
switch string(tokenBytes) {
|
||||
case "param":
|
||||
p.pos = positionParam
|
||||
p.parsedParam, p.parsedParamID = false, false
|
||||
p.parsedParamID = false
|
||||
return p.parseParamID, nil
|
||||
case "result":
|
||||
p.currentField = 0 // reset
|
||||
p.pos = positionResult
|
||||
return p.parseResult, nil
|
||||
default:
|
||||
@@ -165,6 +171,39 @@ func (p *typeParser) parseMoreParamsOrResult(tok tokenType, tokenBytes []byte, l
|
||||
return p.parseFuncEnd(tok, tokenBytes, line, col) // end of params, but no result. Ex. (func (param i32)) or (func)
|
||||
}
|
||||
|
||||
// parseMoreResults looks for a '(', and if present returns beginResult to continue any additional results. Otherwise,
|
||||
// it calls onType.
|
||||
func (p *typeParser) parseMoreResults(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
|
||||
if tok == tokenLParen {
|
||||
p.pos = positionFunc
|
||||
return p.beginResult, nil
|
||||
}
|
||||
return p.parseFuncEnd(tok, tokenBytes, line, col) // end of results
|
||||
}
|
||||
|
||||
// beginResult attempts to begin a "result" field.
|
||||
func (p *typeParser) beginResult(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
|
||||
if tok != tokenKeyword {
|
||||
return nil, unexpectedToken(tok, tokenBytes)
|
||||
}
|
||||
|
||||
switch string(tokenBytes) {
|
||||
case "param":
|
||||
return nil, errors.New("param after result")
|
||||
case "result":
|
||||
// Guard >1.0 feature multi-value
|
||||
if err := p.enabledFeatures.Require(wasm.FeatureMultiValue); err != nil {
|
||||
err = fmt.Errorf("multiple result types invalid as %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.pos = positionResult
|
||||
return p.parseResult, nil
|
||||
default:
|
||||
return nil, unexpectedFieldName(tokenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// parseParamID ignores any ID if present and resumes with parseParam .
|
||||
//
|
||||
// Ex. A param ID is present `(param $x i32)`
|
||||
@@ -188,12 +227,10 @@ func (p *typeParser) parseParamID(tok tokenType, tokenBytes []byte, line, col ui
|
||||
// records i32 --^ ^
|
||||
// parseMoreParamsOrResult resumes here --+
|
||||
//
|
||||
// Ex. One param type is present `(param i32)`
|
||||
// records i32 --^ ^
|
||||
// parseMoreParamsOrResult resumes here --+
|
||||
//
|
||||
// Ex. type is missing `(param)`
|
||||
// errs here --^
|
||||
// Ex. Multiple param types are present `(param i32 i64)`
|
||||
// records i32 --^ ^ ^
|
||||
// records i32 --+ |
|
||||
// parseMoreParamsOrResult resumes here --+
|
||||
func (p *typeParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
|
||||
switch tok {
|
||||
case tokenID: // Ex. $len
|
||||
@@ -203,18 +240,15 @@ func (p *typeParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.parsedParam && p.parsedParamID {
|
||||
if p.parsedParamType && p.parsedParamID {
|
||||
return nil, errors.New("cannot assign IDs to parameters in abbreviated form")
|
||||
}
|
||||
p.currentType.Params = append(p.currentType.Params, vt)
|
||||
p.parsedParam = true
|
||||
p.parsedParamType = true
|
||||
return p.parseParam, nil
|
||||
case tokenRParen: // end of this field
|
||||
if !p.parsedParam {
|
||||
return nil, errors.New("expected a type")
|
||||
}
|
||||
// since multiple param fields are valid, ex `(func (param i32) (param i64))`, prepare for any next.
|
||||
p.currentParamField++
|
||||
p.currentField++
|
||||
p.pos = positionFunc
|
||||
return p.parseMoreParamsOrResult, nil
|
||||
default:
|
||||
@@ -222,44 +256,53 @@ func (p *typeParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32) (
|
||||
}
|
||||
}
|
||||
|
||||
// parseResult parses the api.ValueType in the "result" field and returns onType to finish the type.
|
||||
// parseResult records value type and continues if it is an abbreviated form with multiple value types. When complete,
|
||||
// this returns parseMoreResults.
|
||||
//
|
||||
// Ex. One result type is present `(result i32)`
|
||||
// records i32 --^ ^
|
||||
// parseMoreResults resumes here --+
|
||||
//
|
||||
// Ex. Multiple result types are present `(result i32 i64)`
|
||||
// records i32 --^ ^ ^
|
||||
// records i32 --+ |
|
||||
// parseMoreResults resumes here --+
|
||||
func (p *typeParser) parseResult(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
|
||||
switch tok {
|
||||
case tokenID: // Ex. $len
|
||||
return nil, fmt.Errorf("unexpected ID: %s", tokenBytes)
|
||||
case tokenKeyword: // Ex. i32
|
||||
if p.currentType.Results != nil {
|
||||
return nil, errors.New("redundant type")
|
||||
if len(p.currentType.Results) > 0 { // ex (result i32 i32)
|
||||
// Guard >1.0 feature multi-value
|
||||
if err := p.enabledFeatures.Require(wasm.FeatureMultiValue); err != nil {
|
||||
err = fmt.Errorf("multiple result types invalid as %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
p.currentType.Results, err = parseResultType(tokenBytes)
|
||||
return p.parseResult, err
|
||||
vt, err := parseValueType(tokenBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.currentType.Results = append(p.currentType.Results, vt)
|
||||
return p.parseResult, nil
|
||||
case tokenRParen: // end of this field
|
||||
if p.currentType.Results == nil {
|
||||
return nil, errors.New("expected a type")
|
||||
}
|
||||
// since multiple result fields are valid, ex `(func (result i32) (result i64))`, prepare for any next.
|
||||
p.currentField++
|
||||
p.pos = positionFunc
|
||||
return p.parseFuncEnd, nil // end of result, and only one is allowed
|
||||
return p.parseMoreResults, nil
|
||||
default:
|
||||
return nil, unexpectedToken(tok, tokenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func parseResultType(tokenBytes []byte) ([]wasm.ValueType, error) {
|
||||
vt, err := parseValueType(tokenBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return leb128.EncodeUint32(uint32(vt)), nil // reuse cache
|
||||
}
|
||||
|
||||
func (p *typeParser) errorContext() string {
|
||||
switch p.pos {
|
||||
case positionFunc:
|
||||
return ".func"
|
||||
case positionParam:
|
||||
return fmt.Sprintf(".func.param[%d]", p.currentParamField)
|
||||
return fmt.Sprintf(".func.param[%d]", p.currentField)
|
||||
case positionResult:
|
||||
return ".func.result"
|
||||
return fmt.Sprintf(".func.result[%d]", p.currentField)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
f32, i32, i64 = wasm.ValueTypeF32, wasm.ValueTypeI32, wasm.ValueTypeI64
|
||||
f32, f64, i32, i64 = wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeI32, wasm.ValueTypeI64
|
||||
i32_v = &wasm.FunctionType{Params: []wasm.ValueType{i32}}
|
||||
v_i32 = &wasm.FunctionType{Results: []wasm.ValueType{i32}}
|
||||
v_i32i64 = &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}}
|
||||
i64_i64 = &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}}
|
||||
i32i64_v = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}}
|
||||
i32i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}
|
||||
@@ -63,6 +64,11 @@ func TestTypeParser(t *testing.T) {
|
||||
expected: v_i32,
|
||||
expectedID: "v_i32",
|
||||
},
|
||||
{
|
||||
name: "results no param",
|
||||
input: "(type (func (result i32) (result i64)))",
|
||||
expected: v_i32i64,
|
||||
},
|
||||
{
|
||||
name: "mixed param no result",
|
||||
input: "(type (func (param i32) (param i64)))",
|
||||
@@ -95,6 +101,84 @@ func TestTypeParser(t *testing.T) {
|
||||
input: "(type (func (param i32 i32) (param i32) (param i64) (param f32)))",
|
||||
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}},
|
||||
},
|
||||
|
||||
// Below are changes to test/core/br.wast from the commit that added "multi-value" support.
|
||||
// See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
|
||||
{
|
||||
name: "multi-value - v_i64f32 abbreviated",
|
||||
input: "(type (func (result i64 f32)))",
|
||||
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i64_f32f64 abbreviated",
|
||||
input: "(type (func (param i32 i64) (result f32 f64)))",
|
||||
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - v_i64f32",
|
||||
input: "(type (func (result i64) (result f32)))",
|
||||
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i64_f32f64",
|
||||
input: "(type (func (param i32) (param i64) (result f32) (result f64)))",
|
||||
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i64_f32f64 named",
|
||||
input: "(type (func (param $x i32) (param $y i64) (result f32) (result f64)))",
|
||||
expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i64i64f32_f32i32 results abbreviated in groups",
|
||||
input: "(type (func (result i64 i64 f32) (result f32 i32)))",
|
||||
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32, f32, i32}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i32i64i32_f32f64f64i32 params and results abbreviated in groups",
|
||||
input: "(type (func (param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32)))",
|
||||
expected: &wasm.FunctionType{
|
||||
Params: []wasm.ValueType{i32, i32, i64, i32},
|
||||
Results: []wasm.ValueType{f32, f64, f64, i32},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i32i64i32_f32f64f64i32 abbreviated in groups",
|
||||
input: "(type (func (param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32)))",
|
||||
expected: &wasm.FunctionType{
|
||||
Params: []wasm.ValueType{i32, i32, i64, i32},
|
||||
Results: []wasm.ValueType{f32, f64, f64, i32},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i32i64i32_f32f64f64i32 abbreviated in groups",
|
||||
input: "(type (func (param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32)))",
|
||||
expected: &wasm.FunctionType{
|
||||
Params: []wasm.ValueType{i32, i32, i64, i32},
|
||||
Results: []wasm.ValueType{f32, f64, f64, i32},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi-value - empty abbreviated results",
|
||||
input: "(type (func (result) (result) (result i64 i64) (result) (result f32) (result)))",
|
||||
// Abbreviations have min length zero, which implies no-op results are ok.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A2
|
||||
expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - empty abbreviated params and results",
|
||||
input: `(type (func
|
||||
(param i32 i32) (param i64 i32) (param) (param $x i32) (param)
|
||||
(result) (result f32 f64) (result f64 i32) (result)
|
||||
))`,
|
||||
// Abbreviations have min length zero, which implies no-op results are ok.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A2
|
||||
expected: &wasm.FunctionType{
|
||||
Params: []wasm.ValueType{i32, i32, i64, i32, i32},
|
||||
Results: []wasm.ValueType{f32, f64, f64, i32},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -105,7 +189,7 @@ func TestTypeParser(t *testing.T) {
|
||||
require.Equal(t, wasm.SectionIDType, sectionID)
|
||||
return 0
|
||||
})
|
||||
parsed, tp, err := parseFunctionType(typeNamespace, tc.input)
|
||||
parsed, tp, err := parseFunctionType(wasm.FeaturesFinished, typeNamespace, tc.input)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, parsed)
|
||||
require.Equal(t, uint32(1), tp.typeNamespace.count)
|
||||
@@ -120,7 +204,10 @@ func TestTypeParser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTypeParser_Errors(t *testing.T) {
|
||||
tests := []struct{ name, input, expectedErr string }{
|
||||
tests := []struct {
|
||||
name, input, expectedErr string
|
||||
enabledFeatures wasm.Features
|
||||
}{
|
||||
{
|
||||
name: "invalid token",
|
||||
input: "(type \"0\")",
|
||||
@@ -161,14 +248,9 @@ func TestTypeParser_Errors(t *testing.T) {
|
||||
input: "(type (func (type 0))",
|
||||
expectedErr: "unexpected field: type",
|
||||
},
|
||||
{
|
||||
name: "param missing type",
|
||||
input: "(type (func (param))",
|
||||
expectedErr: "expected a type",
|
||||
},
|
||||
{
|
||||
name: "param wrong type",
|
||||
input: "(type (func (param i33))",
|
||||
input: "(type (func (param i33)))",
|
||||
expectedErr: "unknown type: i33",
|
||||
},
|
||||
{
|
||||
@@ -183,37 +265,59 @@ func TestTypeParser_Errors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "param wrong end",
|
||||
input: "(type (func (param i64 \"\"))",
|
||||
input: "(type (func (param i64 \"\")))",
|
||||
expectedErr: "unexpected string: \"\"",
|
||||
},
|
||||
{
|
||||
name: "result has no ID",
|
||||
input: "(type (func (result $x i64) )",
|
||||
input: "(type (func (result $x i64)))",
|
||||
expectedErr: "unexpected ID: $x",
|
||||
},
|
||||
{
|
||||
name: "result missing type",
|
||||
input: "(type (func (result))",
|
||||
expectedErr: "expected a type",
|
||||
},
|
||||
{
|
||||
name: "result wrong type",
|
||||
input: "(type (func (result i33))",
|
||||
input: "(type (func (result i33)))",
|
||||
expectedErr: "unknown type: i33",
|
||||
},
|
||||
{
|
||||
name: "result abbreviated",
|
||||
input: "(type (func (result i32 i64)))",
|
||||
expectedErr: "multiple result types invalid as feature \"multi-value\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "result twice",
|
||||
input: "(type (func (result i32) (result i32)))",
|
||||
expectedErr: "multiple result types invalid as feature \"multi-value\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "result second wrong",
|
||||
input: "(type (func (result i32) (result i33)))",
|
||||
enabledFeatures: wasm.FeaturesFinished,
|
||||
expectedErr: "unknown type: i33",
|
||||
},
|
||||
{
|
||||
name: "result second redundant type wrong",
|
||||
input: "(type (func (result i32) (result i32 i33)))",
|
||||
enabledFeatures: wasm.FeaturesFinished,
|
||||
expectedErr: "unknown type: i33",
|
||||
},
|
||||
{
|
||||
name: "param after result",
|
||||
input: "(type (func (result i32) (param i32)))",
|
||||
expectedErr: "param after result",
|
||||
},
|
||||
{
|
||||
name: "result wrong end",
|
||||
input: "(type (func (result i64 \"\"))",
|
||||
input: "(type (func (result i64 \"\")))",
|
||||
expectedErr: "unexpected string: \"\"",
|
||||
},
|
||||
{
|
||||
name: "func has no ID",
|
||||
input: "(type (func $v_v ))",
|
||||
input: "(type (func $v_v )))",
|
||||
expectedErr: "unexpected ID: $v_v",
|
||||
},
|
||||
{
|
||||
name: "func invalid token",
|
||||
input: "(type (func \"0\"))",
|
||||
input: "(type (func \"0\")))",
|
||||
expectedErr: "unexpected string: \"0\"",
|
||||
},
|
||||
{
|
||||
@@ -223,7 +327,7 @@ func TestTypeParser_Errors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "wrong end - after func",
|
||||
input: "(type (func (param i32) \"\"))",
|
||||
input: "(type (func (param i32) \"\")))",
|
||||
expectedErr: "unexpected string: \"\"",
|
||||
},
|
||||
}
|
||||
@@ -232,11 +336,15 @@ func TestTypeParser_Errors(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
enabledFeatures := tc.enabledFeatures
|
||||
if enabledFeatures == 0 {
|
||||
enabledFeatures = wasm.Features20191205
|
||||
}
|
||||
typeNamespace := newIndexNamespace(func(sectionID wasm.SectionID) uint32 {
|
||||
require.Equal(t, wasm.SectionIDType, sectionID)
|
||||
return 0
|
||||
})
|
||||
parsed, _, err := parseFunctionType(typeNamespace, tc.input)
|
||||
parsed, _, err := parseFunctionType(enabledFeatures, typeNamespace, tc.input)
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
require.Nil(t, parsed)
|
||||
})
|
||||
@@ -251,19 +359,23 @@ func TestTypeParser_Errors(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
typeNamespace.count++
|
||||
|
||||
parsed, _, err := parseFunctionType(typeNamespace, "(type $v_v (func))")
|
||||
parsed, _, err := parseFunctionType(wasm.Features20191205, typeNamespace, "(type $v_v (func))")
|
||||
require.EqualError(t, err, "duplicate ID $v_v")
|
||||
require.Nil(t, parsed)
|
||||
})
|
||||
}
|
||||
|
||||
func parseFunctionType(typeNamespace *indexNamespace, input string) (*wasm.FunctionType, *typeParser, error) {
|
||||
func parseFunctionType(
|
||||
enabledFeatures wasm.Features,
|
||||
typeNamespace *indexNamespace,
|
||||
input string,
|
||||
) (*wasm.FunctionType, *typeParser, error) {
|
||||
var parsed *wasm.FunctionType
|
||||
var setFunc onType = func(ft *wasm.FunctionType) tokenParser {
|
||||
parsed = ft
|
||||
return parseErr
|
||||
}
|
||||
tp := newTypeParser(typeNamespace, setFunc)
|
||||
tp := newTypeParser(enabledFeatures, typeNamespace, setFunc)
|
||||
// typeParser starts after the '(type', so we need to eat it first!
|
||||
_, _, err := lex(skipTokens(2, tp.begin), []byte(input))
|
||||
return parsed, tp, err
|
||||
@@ -295,42 +407,8 @@ func TestParseValueType(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseResultType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tokenBytes string
|
||||
expected []wasm.ValueType
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "no value",
|
||||
tokenBytes: "i32",
|
||||
expected: []wasm.ValueType{wasm.ValueTypeI32},
|
||||
},
|
||||
{
|
||||
name: "invalid token",
|
||||
tokenBytes: "i33",
|
||||
expectedErr: "unknown type: i33",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rt, err := parseResultType([]byte(tc.tokenBytes))
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, rt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeParser_ErrorContext(t *testing.T) {
|
||||
p := typeParser{currentParamField: 3}
|
||||
p := typeParser{currentField: 3, currentType: &wasm.FunctionType{}}
|
||||
tests := []struct {
|
||||
input string
|
||||
pos parserPosition
|
||||
@@ -339,7 +417,7 @@ func TestTypeParser_ErrorContext(t *testing.T) {
|
||||
{input: "initial", pos: positionInitial, expected: ""},
|
||||
{input: "func", pos: positionFunc, expected: ".func"},
|
||||
{input: "param", pos: positionParam, expected: ".func.param[3]"},
|
||||
{input: "result", pos: positionResult, expected: ".func.result"},
|
||||
{input: "result", pos: positionResult, expected: ".func.result[3]"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func newTypeUseParser(module *wasm.Module, typeNamespace *indexNamespace) *typeUseParser {
|
||||
return &typeUseParser{module: module, typeNamespace: typeNamespace}
|
||||
func newTypeUseParser(enabledFeatures wasm.Features, module *wasm.Module, typeNamespace *indexNamespace) *typeUseParser {
|
||||
return &typeUseParser{enabledFeatures: enabledFeatures, module: module, typeNamespace: typeNamespace}
|
||||
}
|
||||
|
||||
// onTypeUse is invoked when the grammar "(param)* (result)?" completes.
|
||||
// onTypeUse is invoked when the grammar "(param)* (result)*" completes.
|
||||
//
|
||||
// * typeIdx if unresolved, this is replaced in moduleParser.resolveTypeUses
|
||||
// * paramNames is nil unless IDs existed on at least one "param" field.
|
||||
@@ -31,6 +31,9 @@ type onTypeUse func(typeIdx wasm.Index, paramNames wasm.NameMap, pos callbackPos
|
||||
// "type", "param" and "result" inner fields in the correct order.
|
||||
// Note: typeUseParser is reusable. The caller resets via begin.
|
||||
type typeUseParser struct {
|
||||
// enabledFeatures should be set to moduleParser.enabledFeatures
|
||||
enabledFeatures wasm.Features
|
||||
|
||||
// module during parsing is a read-only pointer to the TypeSection and SectionElementCount
|
||||
module *wasm.Module
|
||||
|
||||
@@ -75,15 +78,16 @@ type typeUseParser struct {
|
||||
// paramNames are the paramIndex formatted for the wasm.NameSection LocalNames
|
||||
paramNames wasm.NameMap
|
||||
|
||||
// currentParamField is a field index and used to give an appropriate errorContext. Due to abbreviation it may be
|
||||
// unrelated to the length of currentParams
|
||||
currentParamField wasm.Index
|
||||
// currentField is a field index and used to give an appropriate errorContext.
|
||||
//
|
||||
// Note: Due to abbreviation, this may be less than to the length of params or results.
|
||||
currentField wasm.Index
|
||||
|
||||
// parsedParam allows us to check if we parsed a type in a "param" field. We can't use currentParamField because when
|
||||
// parameters are abbreviated, ex. (param i32 i32), the currentParamField will be less than the type count.
|
||||
parsedParam bool
|
||||
// parsedParamType allows us to check if we parsed a type in a "param" field. This is used to enforce param names
|
||||
// can't coexist with abbreviations.
|
||||
parsedParamType bool
|
||||
|
||||
// parsedParamID is true when the field at currentParamField had an ID. Ex. (param $x i32)
|
||||
// parsedParamID is true when the field at currentField had an ID. Ex. (param $x i32)
|
||||
parsedParamID bool
|
||||
}
|
||||
|
||||
@@ -152,7 +156,7 @@ func (p *typeUseParser) beginTypeParamOrResult(tok tokenType, tokenBytes []byte,
|
||||
p.paramIndex = nil
|
||||
p.paramNames = nil
|
||||
}
|
||||
p.currentParamField = 0
|
||||
p.currentField = 0
|
||||
p.parsedTypeField = false
|
||||
if tok == tokenKeyword && string(tokenBytes) == "type" {
|
||||
p.pos = positionType
|
||||
@@ -198,12 +202,14 @@ func (p *typeUseParser) beginParamOrResult(tok tokenType, tokenBytes []byte, lin
|
||||
return nil, unexpectedToken(tok, tokenBytes)
|
||||
}
|
||||
|
||||
p.parsedParamType = false
|
||||
switch string(tokenBytes) {
|
||||
case "param":
|
||||
p.pos = positionParam
|
||||
p.parsedParam, p.parsedParamID = false, false
|
||||
p.parsedParamID = false
|
||||
return p.parseParamID, nil
|
||||
case "result":
|
||||
p.currentField = 0 // reset
|
||||
p.pos = positionResult
|
||||
return p.parseResult, nil
|
||||
case "type":
|
||||
@@ -222,6 +228,41 @@ func (p *typeUseParser) parseMoreParamsOrResult(tok tokenType, tokenBytes []byte
|
||||
return p.parseEnd(tok, tokenBytes, line, col)
|
||||
}
|
||||
|
||||
// parseMoreResults looks for a '(', and if present returns beginResult to continue any additional results. Otherwise,
|
||||
// it calls onType.
|
||||
func (p *typeUseParser) parseMoreResults(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
|
||||
if tok == tokenLParen {
|
||||
p.pos = positionFunc
|
||||
return p.beginResult, nil
|
||||
}
|
||||
return p.parseEnd(tok, tokenBytes, line, col)
|
||||
}
|
||||
|
||||
// beginResult attempts to begin a "result" field.
|
||||
func (p *typeUseParser) beginResult(tok tokenType, tokenBytes []byte, line, col uint32) (tokenParser, error) {
|
||||
if tok != tokenKeyword {
|
||||
return nil, unexpectedToken(tok, tokenBytes)
|
||||
}
|
||||
|
||||
switch string(tokenBytes) {
|
||||
case "param":
|
||||
return nil, errors.New("param after result")
|
||||
case "result":
|
||||
// Guard >1.0 feature multi-value
|
||||
if err := p.enabledFeatures.Require(wasm.FeatureMultiValue); err != nil {
|
||||
err = fmt.Errorf("multiple result types invalid as %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.pos = positionResult
|
||||
return p.parseResult, nil
|
||||
case "type":
|
||||
return nil, errors.New("type after result")
|
||||
default:
|
||||
return p.end(callbackPositionUnhandledField, tok, tokenBytes, line, col)
|
||||
}
|
||||
}
|
||||
|
||||
// parseParamID sets any ID if present and resumes with parseParam .
|
||||
//
|
||||
// Ex. A param ID is present `(param $x i32)`
|
||||
@@ -243,7 +284,7 @@ func (p *typeUseParser) parseParamID(tok tokenType, tokenBytes []byte, line, col
|
||||
|
||||
// setParamID adds the normalized ('$' stripped) parameter ID to the paramIndex and the wasm.NameSection.
|
||||
func (p *typeUseParser) setParamID(idToken []byte) error {
|
||||
// Note: currentParamField is the index of the param field, but due to mixing and matching of abbreviated params
|
||||
// Note: currentField is the index of the param field, but due to mixing and matching of abbreviated params
|
||||
// it can be less than the param index. Ex. (param i32 i32) (param $v i32) is param field 2, but the 3rd param.
|
||||
var idx wasm.Index
|
||||
if p.currentInlinedType != nil {
|
||||
@@ -273,9 +314,6 @@ func (p *typeUseParser) setParamID(idToken []byte) error {
|
||||
// records i32 --^ ^ ^
|
||||
// records i32 --+ |
|
||||
// parseMoreParamsOrResult resumes here --+
|
||||
//
|
||||
// Ex. type is missing `(param)`
|
||||
// errs here --^
|
||||
func (p *typeUseParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
|
||||
switch tok {
|
||||
case tokenID: // Ex. $len
|
||||
@@ -285,7 +323,7 @@ func (p *typeUseParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.parsedParam && p.parsedParamID {
|
||||
if p.parsedParamType && p.parsedParamID {
|
||||
return nil, errors.New("cannot assign IDs to parameters in abbreviated form")
|
||||
}
|
||||
if p.currentInlinedType == nil {
|
||||
@@ -293,14 +331,11 @@ func (p *typeUseParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32
|
||||
} else {
|
||||
p.currentInlinedType.Params = append(p.currentInlinedType.Params, vt)
|
||||
}
|
||||
p.parsedParam = true
|
||||
p.parsedParamType = true
|
||||
return p.parseParam, nil
|
||||
case tokenRParen: // end of this field
|
||||
if !p.parsedParam {
|
||||
return nil, errors.New("expected a type")
|
||||
}
|
||||
// since multiple param fields are valid, ex `(func (param i32) (param i64))`, prepare for any next.
|
||||
p.currentParamField++
|
||||
p.currentField++
|
||||
p.pos = positionInitial
|
||||
return p.parseMoreParamsOrResult, nil
|
||||
default:
|
||||
@@ -308,31 +343,44 @@ func (p *typeUseParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32
|
||||
}
|
||||
}
|
||||
|
||||
// parseResult parses the api.ValueType in the "result" field and returns onType to finish the type.
|
||||
// parseResult records value type and continues if it is an abbreviated form with multiple value types. When complete,
|
||||
// this returns parseMoreResults.
|
||||
//
|
||||
// Ex. One result type is present `(result i32)`
|
||||
// records i32 --^ ^
|
||||
// parseMoreResults resumes here --+
|
||||
//
|
||||
// Ex. Multiple result types are present `(result i32 i64)`
|
||||
// records i32 --^ ^ ^
|
||||
// records i32 --+ |
|
||||
// parseMoreResults resumes here --+
|
||||
func (p *typeUseParser) parseResult(tok tokenType, tokenBytes []byte, _, _ uint32) (tokenParser, error) {
|
||||
switch tok {
|
||||
case tokenID: // Ex. $len
|
||||
return nil, fmt.Errorf("unexpected ID: %s", tokenBytes)
|
||||
case tokenKeyword: // Ex. i32
|
||||
if p.currentInlinedType != nil && p.currentInlinedType.Results != nil {
|
||||
return nil, errors.New("redundant type")
|
||||
if p.currentInlinedType != nil && len(p.currentInlinedType.Results) > 0 { // ex (result i32 i32)
|
||||
// Guard >1.0 feature multi-value
|
||||
if err := p.enabledFeatures.Require(wasm.FeatureMultiValue); err != nil {
|
||||
err = fmt.Errorf("multiple result types invalid as %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
results, err := parseResultType(tokenBytes)
|
||||
vt, err := parseValueType(tokenBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.currentInlinedType == nil {
|
||||
p.currentInlinedType = &wasm.FunctionType{Results: results}
|
||||
p.currentInlinedType = &wasm.FunctionType{Results: []wasm.ValueType{vt}}
|
||||
} else {
|
||||
p.currentInlinedType.Results = results
|
||||
p.currentInlinedType.Results = append(p.currentInlinedType.Results, vt)
|
||||
}
|
||||
return p.parseResult, err
|
||||
return p.parseResult, nil
|
||||
case tokenRParen: // end of this field
|
||||
if p.currentInlinedType == nil || p.currentInlinedType.Results == nil {
|
||||
return nil, errors.New("expected a type")
|
||||
}
|
||||
// since multiple result fields are valid, ex `(func (result i32) (result i64))`, prepare for any next.
|
||||
p.currentField++
|
||||
p.pos = positionInitial
|
||||
return p.parseEnd, nil
|
||||
return p.parseMoreResults, nil
|
||||
default:
|
||||
return nil, unexpectedToken(tok, tokenBytes)
|
||||
}
|
||||
@@ -348,12 +396,12 @@ func (p *typeUseParser) parseEnd(tok tokenType, tokenBytes []byte, line, col uin
|
||||
|
||||
func (p *typeUseParser) errorContext() string {
|
||||
switch p.pos {
|
||||
case positionParam:
|
||||
return fmt.Sprintf(".param[%d]", p.currentParamField)
|
||||
case positionResult:
|
||||
return ".result"
|
||||
case positionType:
|
||||
return ".type"
|
||||
case positionParam:
|
||||
return fmt.Sprintf(".param[%d]", p.currentField)
|
||||
case positionResult:
|
||||
return fmt.Sprintf(".result[%d]", p.currentField)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package text
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
|
||||
type typeUseParserTest struct {
|
||||
name string
|
||||
source string
|
||||
input string
|
||||
expectedInlinedType *wasm.FunctionType
|
||||
expectedTypeIdx wasm.Index
|
||||
expectedParamNames wasm.NameMap
|
||||
@@ -27,62 +26,142 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) {
|
||||
tests := []*typeUseParserTest{
|
||||
{
|
||||
name: "empty",
|
||||
source: "()",
|
||||
input: "()",
|
||||
expectedInlinedType: v_v,
|
||||
},
|
||||
{
|
||||
name: "param no result",
|
||||
source: "((param i32))",
|
||||
input: "((param i32))",
|
||||
expectedInlinedType: i32_v,
|
||||
},
|
||||
{
|
||||
name: "param no result - ID",
|
||||
source: "((param $x i32))",
|
||||
input: "((param $x i32))",
|
||||
expectedInlinedType: i32_v,
|
||||
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 0, Name: "x"}},
|
||||
},
|
||||
{
|
||||
name: "result no param",
|
||||
source: "((result i32))",
|
||||
input: "((result i32))",
|
||||
expectedInlinedType: v_i32,
|
||||
},
|
||||
{
|
||||
name: "mixed param no result",
|
||||
source: "((param i32) (param i64))",
|
||||
input: "((param i32) (param i64))",
|
||||
expectedInlinedType: i32i64_v,
|
||||
},
|
||||
{
|
||||
name: "mixed param no result - ID",
|
||||
source: "((param $x i32) (param $y i64))",
|
||||
input: "((param $x i32) (param $y i64))",
|
||||
expectedInlinedType: i32i64_v,
|
||||
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 0, Name: "x"}, &wasm.NameAssoc{Index: 1, Name: "y"}},
|
||||
},
|
||||
{
|
||||
name: "mixed param result",
|
||||
source: "((param i32) (param i64) (result i32))",
|
||||
input: "((param i32) (param i64) (result i32))",
|
||||
expectedInlinedType: i32i64_i32,
|
||||
},
|
||||
{
|
||||
name: "mixed param result - ID",
|
||||
source: "((param $x i32) (param $y i64) (result i32))",
|
||||
input: "((param $x i32) (param $y i64) (result i32))",
|
||||
expectedInlinedType: i32i64_i32,
|
||||
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 0, Name: "x"}, &wasm.NameAssoc{Index: 1, Name: "y"}},
|
||||
},
|
||||
{
|
||||
name: "abbreviated param result",
|
||||
source: "((param i32 i64) (result i32))",
|
||||
input: "((param i32 i64) (result i32))",
|
||||
expectedInlinedType: i32i64_i32,
|
||||
},
|
||||
{
|
||||
name: "mixed param abbreviation", // Verifies we can handle less param fields than param types
|
||||
source: "((param i32 i32) (param i32) (param i64) (param f32))",
|
||||
input: "((param i32 i32) (param i32) (param i64) (param f32))",
|
||||
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}},
|
||||
},
|
||||
|
||||
// Below are changes to test/core/br.wast from the commit that added "multi-value" support.
|
||||
// See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
|
||||
{
|
||||
name: "multi-value - v_i64f32 abbreviated",
|
||||
input: "((result i64 f32))",
|
||||
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i64_f32f64 abbreviated",
|
||||
input: "((param i32 i64) (result f32 f64))",
|
||||
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - v_i64f32",
|
||||
input: "((result i64) (result f32))",
|
||||
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i64_f32f64",
|
||||
input: "((param i32) (param i64) (result f32) (result f64))",
|
||||
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i64_f32f64 named",
|
||||
input: "((param $x i32) (param $y i64) (result f32) (result f64))",
|
||||
expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}},
|
||||
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 0, Name: "x"}, &wasm.NameAssoc{Index: 1, Name: "y"}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i64i64f32_f32i32 results abbreviated in groups",
|
||||
input: "((result i64 i64 f32) (result f32 i32))",
|
||||
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32, f32, i32}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i32i64i32_f32f64f64i32 params and results abbreviated in groups",
|
||||
input: "((param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32))",
|
||||
expectedInlinedType: &wasm.FunctionType{
|
||||
Params: []wasm.ValueType{i32, i32, i64, i32},
|
||||
Results: []wasm.ValueType{f32, f64, f64, i32},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i32i64i32_f32f64f64i32 abbreviated in groups",
|
||||
input: "((param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32))",
|
||||
expectedInlinedType: &wasm.FunctionType{
|
||||
Params: []wasm.ValueType{i32, i32, i64, i32},
|
||||
Results: []wasm.ValueType{f32, f64, f64, i32},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi-value - i32i32i64i32_f32f64f64i32 abbreviated in groups",
|
||||
input: "((param i32 i32) (param i64 i32) (result f32 f64) (result f64 i32))",
|
||||
expectedInlinedType: &wasm.FunctionType{
|
||||
Params: []wasm.ValueType{i32, i32, i64, i32},
|
||||
Results: []wasm.ValueType{f32, f64, f64, i32},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi-value - empty abbreviated results",
|
||||
input: "((result) (result) (result i64 i64) (result) (result f32) (result))",
|
||||
// Abbreviations have min length zero, which implies no-op results are ok.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A2
|
||||
expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32}},
|
||||
},
|
||||
{
|
||||
name: "multi-value - empty abbreviated params and results",
|
||||
input: `(
|
||||
(param i32 i32) (param i64 i32) (param) (param $x i32) (param)
|
||||
(result) (result f32 f64) (result f64 i32) (result)
|
||||
)`,
|
||||
// Abbreviations have min length zero, which implies no-op results are ok.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#abbreviations%E2%91%A2
|
||||
expectedInlinedType: &wasm.FunctionType{
|
||||
Params: []wasm.ValueType{i32, i32, i64, i32, i32},
|
||||
Results: []wasm.ValueType{f32, f64, f64, i32},
|
||||
},
|
||||
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 4, Name: "x"}},
|
||||
},
|
||||
}
|
||||
|
||||
runTypeUseParserTests(t, tests, func(tc *typeUseParserTest) (*typeUseParser, func(t *testing.T)) {
|
||||
module := &wasm.Module{}
|
||||
tp := newTypeUseParser(module, newIndexNamespace(module.SectionElementCount))
|
||||
tp := newTypeUseParser(wasm.FeaturesFinished, module, newIndexNamespace(module.SectionElementCount))
|
||||
return tp, func(t *testing.T) {
|
||||
// We should have inlined the type, and it is the first type use, which means the inlined index is zero
|
||||
require.Zero(t, tp.inlinedTypeIndices[0].inlinedIdx)
|
||||
@@ -95,30 +174,30 @@ func TestTypeUseParser_UnresolvedType(t *testing.T) {
|
||||
tests := []*typeUseParserTest{
|
||||
{
|
||||
name: "unresolved type - index",
|
||||
source: "((type 1))",
|
||||
input: "((type 1))",
|
||||
expectedTypeIdx: 1,
|
||||
},
|
||||
{
|
||||
name: "unresolved type - ID",
|
||||
source: "((type $v_v))",
|
||||
input: "((type $v_v))",
|
||||
expectedTypeIdx: 0,
|
||||
},
|
||||
{
|
||||
name: "unresolved type - index - match",
|
||||
source: "((type 3) (param i32 i64) (result i32))",
|
||||
input: "((type 3) (param i32 i64) (result i32))",
|
||||
expectedTypeIdx: 3,
|
||||
expectedInlinedType: i32i64_i32,
|
||||
},
|
||||
{
|
||||
name: "unresolved type - ID - match",
|
||||
source: "((type $i32i64_i32) (param i32 i64) (result i32))",
|
||||
input: "((type $i32i64_i32) (param i32 i64) (result i32))",
|
||||
expectedTypeIdx: 0,
|
||||
expectedInlinedType: i32i64_i32,
|
||||
},
|
||||
}
|
||||
runTypeUseParserTests(t, tests, func(tc *typeUseParserTest) (*typeUseParser, func(t *testing.T)) {
|
||||
module := &wasm.Module{}
|
||||
tp := newTypeUseParser(module, newIndexNamespace(module.SectionElementCount))
|
||||
tp := newTypeUseParser(wasm.FeaturesFinished, module, newIndexNamespace(module.SectionElementCount))
|
||||
return tp, func(t *testing.T) {
|
||||
require.NotNil(t, tp.typeNamespace.unresolvedIndices)
|
||||
if tc.expectedInlinedType == nil {
|
||||
@@ -134,77 +213,77 @@ func TestTypeUseParser_ReuseExistingType(t *testing.T) {
|
||||
tests := []*typeUseParserTest{
|
||||
{
|
||||
name: "match existing - result",
|
||||
source: "((result i32))",
|
||||
input: "((result i32))",
|
||||
expectedTypeIdx: 0,
|
||||
},
|
||||
{
|
||||
name: "match existing - nullary",
|
||||
source: "()",
|
||||
input: "()",
|
||||
expectedTypeIdx: 1,
|
||||
},
|
||||
{
|
||||
name: "match existing - param",
|
||||
source: "((param i32))",
|
||||
input: "((param i32))",
|
||||
expectedTypeIdx: 2,
|
||||
},
|
||||
{
|
||||
name: "match existing - param and result",
|
||||
source: "((param i32 i64) (result i32))",
|
||||
input: "((param i32 i64) (result i32))",
|
||||
expectedTypeIdx: 3,
|
||||
},
|
||||
{
|
||||
name: "type field index - result",
|
||||
source: "((type 0))",
|
||||
input: "((type 0))",
|
||||
expectedTypeIdx: 0,
|
||||
},
|
||||
{
|
||||
name: "type field ID - result",
|
||||
source: "((type $v_i32))",
|
||||
input: "((type $v_i32))",
|
||||
expectedTypeIdx: 0,
|
||||
},
|
||||
{
|
||||
name: "type field ID - result - match",
|
||||
source: "((type $v_i32) (result i32))",
|
||||
input: "((type $v_i32) (result i32))",
|
||||
expectedTypeIdx: 0,
|
||||
},
|
||||
{
|
||||
name: "type field index - nullary",
|
||||
source: "((type 1))",
|
||||
input: "((type 1))",
|
||||
expectedTypeIdx: 1,
|
||||
},
|
||||
{
|
||||
name: "type field ID - nullary",
|
||||
source: "((type $v_v))",
|
||||
input: "((type $v_v))",
|
||||
expectedTypeIdx: 1,
|
||||
},
|
||||
{
|
||||
name: "type field index - param",
|
||||
source: "((type 2))",
|
||||
input: "((type 2))",
|
||||
expectedTypeIdx: 2,
|
||||
},
|
||||
{
|
||||
name: "type field ID - param",
|
||||
source: "((type $i32_v))",
|
||||
input: "((type $i32_v))",
|
||||
expectedTypeIdx: 2,
|
||||
},
|
||||
{
|
||||
name: "type field ID - param - match",
|
||||
source: "((type $i32_v) (param i32))",
|
||||
input: "((type $i32_v) (param i32))",
|
||||
expectedTypeIdx: 2,
|
||||
},
|
||||
{
|
||||
name: "type field index - param and result",
|
||||
source: "((type 3))",
|
||||
input: "((type 3))",
|
||||
expectedTypeIdx: 3,
|
||||
},
|
||||
{
|
||||
name: "type field ID - param and result",
|
||||
source: "((type $i32i64_i32))",
|
||||
input: "((type $i32i64_i32))",
|
||||
expectedTypeIdx: 3,
|
||||
},
|
||||
{
|
||||
name: "type field ID - param and result - matched",
|
||||
source: "((type $i32i64_i32) (param i32 i64) (result i32))",
|
||||
input: "((type $i32i64_i32) (param i32 i64) (result i32))",
|
||||
expectedTypeIdx: 3,
|
||||
},
|
||||
}
|
||||
@@ -228,7 +307,7 @@ func TestTypeUseParser_ReuseExistingType(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
typeNamespace.count++
|
||||
|
||||
tp := newTypeUseParser(module, typeNamespace)
|
||||
tp := newTypeUseParser(wasm.FeaturesFinished, module, typeNamespace)
|
||||
return tp, func(t *testing.T) {
|
||||
require.Nil(t, tp.typeNamespace.unresolvedIndices)
|
||||
require.Nil(t, tp.inlinedTypes)
|
||||
@@ -241,32 +320,32 @@ func TestTypeUseParser_ReuseExistingInlinedType(t *testing.T) {
|
||||
tests := []*typeUseParserTest{
|
||||
{
|
||||
name: "match existing - result",
|
||||
source: "((result i32))",
|
||||
input: "((result i32))",
|
||||
expectedInlinedType: v_i32,
|
||||
},
|
||||
{
|
||||
name: "nullary",
|
||||
source: "()",
|
||||
input: "()",
|
||||
expectedInlinedType: v_v,
|
||||
},
|
||||
{
|
||||
name: "param",
|
||||
source: "((param i32))",
|
||||
input: "((param i32))",
|
||||
expectedInlinedType: i32_v,
|
||||
},
|
||||
{
|
||||
name: "param and result",
|
||||
source: "((param i32 i64) (result i32))",
|
||||
input: "((param i32 i64) (result i32))",
|
||||
expectedInlinedType: i32i64_i32,
|
||||
},
|
||||
}
|
||||
runTypeUseParserTests(t, tests, func(tc *typeUseParserTest) (*typeUseParser, func(t *testing.T)) {
|
||||
module := &wasm.Module{}
|
||||
tp := newTypeUseParser(module, newIndexNamespace(module.SectionElementCount))
|
||||
tp := newTypeUseParser(wasm.FeaturesFinished, module, newIndexNamespace(module.SectionElementCount))
|
||||
// inline a type that doesn't match the test
|
||||
require.NoError(t, parseTypeUse(tp, "((param i32 i64))", ignoreTypeUse))
|
||||
// inline the test type
|
||||
require.NoError(t, parseTypeUse(tp, tc.source, ignoreTypeUse))
|
||||
require.NoError(t, parseTypeUse(tp, tc.input, ignoreTypeUse))
|
||||
|
||||
return tp, func(t *testing.T) {
|
||||
// verify it wasn't duplicated
|
||||
@@ -281,37 +360,37 @@ func TestTypeUseParser_BeginResets(t *testing.T) {
|
||||
tests := []*typeUseParserTest{
|
||||
{
|
||||
name: "result",
|
||||
source: "((result i32))",
|
||||
input: "((result i32))",
|
||||
expectedInlinedType: v_i32,
|
||||
},
|
||||
{
|
||||
name: "nullary",
|
||||
source: "()",
|
||||
input: "()",
|
||||
expectedInlinedType: v_v,
|
||||
},
|
||||
{
|
||||
name: "param",
|
||||
source: "((param i32))",
|
||||
input: "((param i32))",
|
||||
expectedInlinedType: i32_v,
|
||||
},
|
||||
{
|
||||
name: "param and result",
|
||||
source: "((param i32 i32) (result i32))",
|
||||
input: "((param i32 i32) (result i32))",
|
||||
expectedInlinedType: i32i32_i32,
|
||||
},
|
||||
{
|
||||
name: "param and result - with IDs",
|
||||
source: "((param $l i32) (param $r i32) (result i32))",
|
||||
input: "((param $l i32) (param $r i32) (result i32))",
|
||||
expectedInlinedType: i32i32_i32,
|
||||
expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 0, Name: "l"}, &wasm.NameAssoc{Index: 1, Name: "r"}},
|
||||
},
|
||||
}
|
||||
runTypeUseParserTests(t, tests, func(tc *typeUseParserTest) (*typeUseParser, func(t *testing.T)) {
|
||||
module := &wasm.Module{}
|
||||
tp := newTypeUseParser(module, newIndexNamespace(module.SectionElementCount))
|
||||
tp := newTypeUseParser(wasm.FeaturesFinished, module, newIndexNamespace(module.SectionElementCount))
|
||||
// inline a type that uses all fields
|
||||
require.NoError(t, parseTypeUse(tp, "((type $i32i64_i32) (param $x i32) (param $y i64) (result i32))", ignoreTypeUse))
|
||||
require.NoError(t, parseTypeUse(tp, tc.source, ignoreTypeUse))
|
||||
require.NoError(t, parseTypeUse(tp, tc.input, ignoreTypeUse))
|
||||
|
||||
return tp, func(t *testing.T) {
|
||||
// this is the second inlined type
|
||||
@@ -334,7 +413,7 @@ func runTypeUseParserTests(t *testing.T, tests []*typeUseParserTest, tf typeUseT
|
||||
|
||||
kt := *tt // copy
|
||||
kt.name = fmt.Sprintf("%s - trailing keyword", tt.name)
|
||||
kt.source = fmt.Sprintf("%s nop)", tt.source[:len(tt.source)-1])
|
||||
kt.input = fmt.Sprintf("%s nop)", tt.input[:len(tt.input)-1])
|
||||
kt.expectedOnTypeUsePosition = callbackPositionUnhandledToken
|
||||
kt.expectedOnTypeUseToken = tokenKeyword // at 'nop' and ')' remains
|
||||
kt.expectedTrailingTokens = []tokenType{tokenRParen}
|
||||
@@ -342,17 +421,10 @@ func runTypeUseParserTests(t *testing.T, tests []*typeUseParserTest, tf typeUseT
|
||||
|
||||
ft := *tt // copy
|
||||
ft.name = fmt.Sprintf("%s - trailing field", tt.name)
|
||||
ft.source = fmt.Sprintf("%s (nop))", tt.source[:len(tt.source)-1])
|
||||
// Two outcomes, we've reached a field not named "type", "param" or "result" or we completed "result"
|
||||
if strings.Contains(tt.source, "result") {
|
||||
ft.expectedOnTypeUsePosition = callbackPositionUnhandledToken
|
||||
ft.expectedOnTypeUseToken = tokenLParen // at '(' and 'nop))' remain
|
||||
ft.expectedTrailingTokens = []tokenType{tokenKeyword, tokenRParen, tokenRParen}
|
||||
} else {
|
||||
ft.expectedOnTypeUsePosition = callbackPositionUnhandledField
|
||||
ft.expectedOnTypeUseToken = tokenKeyword // at 'nop' and '))' remain
|
||||
ft.expectedTrailingTokens = []tokenType{tokenRParen, tokenRParen}
|
||||
}
|
||||
ft.input = fmt.Sprintf("%s (nop))", tt.input[:len(tt.input)-1])
|
||||
ft.expectedOnTypeUsePosition = callbackPositionUnhandledField
|
||||
ft.expectedOnTypeUseToken = tokenKeyword // at 'nop' and '))' remain
|
||||
ft.expectedTrailingTokens = []tokenType{tokenRParen, tokenRParen}
|
||||
moreTests = append(moreTests, &ft)
|
||||
}
|
||||
|
||||
@@ -372,7 +444,7 @@ func runTypeUseParserTests(t *testing.T, tests []*typeUseParserTest, tf typeUseT
|
||||
}
|
||||
|
||||
tp, test := tf(tc)
|
||||
require.NoError(t, parseTypeUse(tp, tc.source, setTypeUse))
|
||||
require.NoError(t, parseTypeUse(tp, tc.input, setTypeUse))
|
||||
require.Equal(t, tc.expectedTrailingTokens, p.tokenTypes)
|
||||
require.Equal(t, tc.expectedTypeIdx, parsedTypeIdx)
|
||||
require.Equal(t, tc.expectedParamNames, parsedParamNames)
|
||||
@@ -382,101 +454,121 @@ func runTypeUseParserTests(t *testing.T, tests []*typeUseParserTest, tf typeUseT
|
||||
}
|
||||
|
||||
func TestTypeUseParser_Errors(t *testing.T) {
|
||||
tests := []struct{ name, source, expectedErr string }{
|
||||
tests := []struct {
|
||||
name, input, expectedErr string
|
||||
enabledFeatures wasm.Features
|
||||
}{
|
||||
{
|
||||
name: "not param",
|
||||
source: "((param i32) ($param i32))",
|
||||
input: "((param i32) ($param i32))",
|
||||
expectedErr: "1:15: unexpected ID: $param",
|
||||
},
|
||||
{
|
||||
name: "param missing type",
|
||||
source: "((param))",
|
||||
expectedErr: "1:8: expected a type",
|
||||
},
|
||||
{
|
||||
name: "param wrong type",
|
||||
source: "((param i33))",
|
||||
input: "((param i33))",
|
||||
expectedErr: "1:9: unknown type: i33",
|
||||
},
|
||||
{
|
||||
name: "param ID in abbreviation",
|
||||
source: "((param $x i32 i64) ",
|
||||
input: "((param $x i32 i64) ",
|
||||
expectedErr: "1:16: cannot assign IDs to parameters in abbreviated form",
|
||||
},
|
||||
{
|
||||
name: "param second ID",
|
||||
source: "((param $x $x i64) ",
|
||||
input: "((param $x $x i64) ",
|
||||
expectedErr: "1:12: redundant ID $x",
|
||||
},
|
||||
{
|
||||
name: "param duplicate ID",
|
||||
source: "((param $x i32) (param $x i64) ",
|
||||
input: "((param $x i32) (param $x i64) ",
|
||||
expectedErr: "1:24: duplicate ID $x",
|
||||
},
|
||||
{
|
||||
name: "param wrong end",
|
||||
source: `((param i64 ""))`,
|
||||
input: `((param i64 ""))`,
|
||||
expectedErr: "1:13: unexpected string: \"\"",
|
||||
},
|
||||
{
|
||||
name: "result has no ID",
|
||||
source: "((result $x i64) ",
|
||||
input: "((result $x i64) ",
|
||||
expectedErr: "1:10: unexpected ID: $x",
|
||||
},
|
||||
{
|
||||
name: "result missing type",
|
||||
source: "((result))",
|
||||
expectedErr: "1:9: expected a type",
|
||||
},
|
||||
{
|
||||
name: "result wrong type",
|
||||
source: "((result i33))",
|
||||
input: "((result i33))",
|
||||
expectedErr: "1:10: unknown type: i33",
|
||||
},
|
||||
{
|
||||
name: "result second type",
|
||||
source: "((result i32 i64))",
|
||||
expectedErr: "1:14: redundant type",
|
||||
name: "result abbreviated",
|
||||
input: "((result i32 i64))",
|
||||
expectedErr: "1:14: multiple result types invalid as feature \"multi-value\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "result twice",
|
||||
input: "((result i32) (result i32))",
|
||||
expectedErr: "1:16: multiple result types invalid as feature \"multi-value\" is disabled",
|
||||
},
|
||||
{
|
||||
name: "result second wrong",
|
||||
input: "((result i32) (result i33))",
|
||||
enabledFeatures: wasm.FeaturesFinished,
|
||||
expectedErr: "1:23: unknown type: i33",
|
||||
},
|
||||
{
|
||||
name: "result second redundant type wrong",
|
||||
input: "((result i32) (result i32 i33))",
|
||||
enabledFeatures: wasm.FeaturesFinished,
|
||||
expectedErr: "1:27: unknown type: i33",
|
||||
},
|
||||
{
|
||||
name: "param after result",
|
||||
input: "((result i32) (param i32))",
|
||||
expectedErr: "1:16: param after result",
|
||||
},
|
||||
{
|
||||
name: "type after result",
|
||||
input: "((result i32) (type i32))",
|
||||
expectedErr: "1:16: type after result",
|
||||
},
|
||||
{
|
||||
name: "result wrong end",
|
||||
source: `((result i64 ""))`,
|
||||
input: "((result i64 \"\"))",
|
||||
expectedErr: "1:14: unexpected string: \"\"",
|
||||
},
|
||||
{
|
||||
name: "type missing index",
|
||||
source: "((type))",
|
||||
input: "((type))",
|
||||
expectedErr: "1:7: missing index",
|
||||
},
|
||||
{
|
||||
name: "type wrong token",
|
||||
source: "((type v_v))",
|
||||
input: "((type v_v))",
|
||||
expectedErr: "1:8: unexpected keyword: v_v",
|
||||
},
|
||||
{
|
||||
name: "type redundant",
|
||||
source: "((type 0) (type 1))",
|
||||
input: "((type 0) (type 1))",
|
||||
expectedErr: "1:12: redundant type",
|
||||
},
|
||||
{
|
||||
name: "type second index",
|
||||
source: "((type 0 1))",
|
||||
input: "((type 0 1))",
|
||||
expectedErr: "1:10: redundant index",
|
||||
},
|
||||
{
|
||||
name: "type overflow index",
|
||||
source: "((type 4294967296))",
|
||||
input: "((type 4294967296))",
|
||||
expectedErr: "1:8: index outside range of uint32: 4294967296",
|
||||
},
|
||||
{
|
||||
name: "type second ID",
|
||||
source: "((type $v_v $v_v i64) ",
|
||||
input: "((type $v_v $v_v i64) ",
|
||||
expectedErr: "1:13: redundant index",
|
||||
},
|
||||
|
||||
{
|
||||
name: "type wrong end",
|
||||
source: `((type 0 ""))`,
|
||||
input: `((type 0 ""))`,
|
||||
expectedErr: "1:10: unexpected string: \"\"",
|
||||
},
|
||||
}
|
||||
@@ -485,9 +577,13 @@ func TestTypeUseParser_Errors(t *testing.T) {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
enabledFeatures := tc.enabledFeatures
|
||||
if enabledFeatures == 0 {
|
||||
enabledFeatures = wasm.Features20191205
|
||||
}
|
||||
module := &wasm.Module{}
|
||||
tp := newTypeUseParser(module, newIndexNamespace(module.SectionElementCount))
|
||||
err := parseTypeUse(tp, tc.source, failOnTypeUse)
|
||||
tp := newTypeUseParser(enabledFeatures, module, newIndexNamespace(module.SectionElementCount))
|
||||
err := parseTypeUse(tp, tc.input, failOnTypeUse)
|
||||
require.EqualError(t, err, tc.expectedErr)
|
||||
})
|
||||
}
|
||||
@@ -505,7 +601,7 @@ func TestTypeUseParser_FailsMatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
typeNamespace.count++
|
||||
|
||||
tp := newTypeUseParser(module, typeNamespace)
|
||||
tp := newTypeUseParser(wasm.FeaturesFinished, module, typeNamespace)
|
||||
tests := []struct{ name, source, expectedErr string }{
|
||||
{
|
||||
name: "nullary index",
|
||||
@@ -559,7 +655,7 @@ func parseTypeUse(tp *typeUseParser, source string, onTypeUse onTypeUse) error {
|
||||
}
|
||||
|
||||
func TestTypeUseParser_ErrorContext(t *testing.T) {
|
||||
p := typeUseParser{currentParamField: 3}
|
||||
p := typeUseParser{currentField: 3}
|
||||
tests := []struct {
|
||||
source string
|
||||
pos parserPosition
|
||||
@@ -567,7 +663,7 @@ func TestTypeUseParser_ErrorContext(t *testing.T) {
|
||||
}{
|
||||
{source: "initial", pos: positionInitial, expected: ""},
|
||||
{source: "param", pos: positionParam, expected: ".param[3]"},
|
||||
{source: "result", pos: positionResult, expected: ".result"},
|
||||
{source: "result", pos: positionResult, expected: ".result[3]"},
|
||||
{source: "type", pos: positionType, expected: ".type"},
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,12 @@ const (
|
||||
|
||||
type (
|
||||
controlFrame struct {
|
||||
frameID uint32
|
||||
originalStackLen int
|
||||
returns []UnsignedType
|
||||
kind controlFrameKind
|
||||
frameID uint32
|
||||
// originalStackLen holds the number of values on the stack
|
||||
// when start executing this control frame minus params for the block.
|
||||
originalStackLenWithoutParam int
|
||||
blockType *wasm.FunctionType
|
||||
kind controlFrameKind
|
||||
}
|
||||
controlFrames struct{ frames []*controlFrame }
|
||||
)
|
||||
@@ -103,6 +105,7 @@ func (c *controlFrames) push(frame *controlFrame) {
|
||||
}
|
||||
|
||||
type compiler struct {
|
||||
enabledFeatures wasm.Features
|
||||
stack []UnsignedType
|
||||
currentID uint32
|
||||
controlFrames *controlFrames
|
||||
@@ -154,8 +157,13 @@ type CompilationResult struct {
|
||||
// Compile lowers given function instance into wazeroir operations
|
||||
// so that the resulting operations can be consumed by the interpreter
|
||||
// or the JIT compilation engine.
|
||||
func Compile(f *wasm.FunctionInstance) (*CompilationResult, error) {
|
||||
c := compiler{controlFrames: &controlFrames{}, f: f, result: CompilationResult{LabelCallers: map[string]uint32{}}}
|
||||
func Compile(enabledFeatures wasm.Features, f *wasm.FunctionInstance) (*CompilationResult, error) {
|
||||
c := compiler{
|
||||
enabledFeatures: enabledFeatures,
|
||||
controlFrames: &controlFrames{},
|
||||
f: f,
|
||||
result: CompilationResult{LabelCallers: map[string]uint32{}},
|
||||
}
|
||||
|
||||
// Push function arguments.
|
||||
for _, t := range f.Type.Params {
|
||||
@@ -170,18 +178,13 @@ func Compile(f *wasm.FunctionInstance) (*CompilationResult, error) {
|
||||
}
|
||||
|
||||
// Insert the function control frame.
|
||||
returns := make([]UnsignedType, 0, len(f.Type.Results))
|
||||
for _, t := range f.Type.Results {
|
||||
returns = append(returns, wasmValueTypeToUnsignedType(t))
|
||||
}
|
||||
c.controlFrames.push(&controlFrame{
|
||||
frameID: c.nextID(),
|
||||
originalStackLen: len(f.Type.Params),
|
||||
returns: returns,
|
||||
kind: controlFrameKindFunction,
|
||||
frameID: c.nextID(),
|
||||
blockType: f.Type,
|
||||
kind: controlFrameKindFunction,
|
||||
})
|
||||
|
||||
// Now enter the function body.
|
||||
// Now, enter the function body.
|
||||
for !c.controlFrames.empty() {
|
||||
if err := c.handleInstruction(); err != nil {
|
||||
return nil, fmt.Errorf("handling instruction: %w", err)
|
||||
@@ -221,7 +224,7 @@ operatorSwitch:
|
||||
// Nop is noop!
|
||||
case wasm.OpcodeBlock:
|
||||
bt, num, err := wasm.DecodeBlockType(c.f.Module.Types,
|
||||
bytes.NewReader(c.f.Body[c.pc+1:]))
|
||||
bytes.NewReader(c.f.Body[c.pc+1:]), c.enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading block type for block instruction: %w", err)
|
||||
}
|
||||
@@ -236,18 +239,15 @@ operatorSwitch:
|
||||
|
||||
// Create a new frame -- entering this block.
|
||||
frame := &controlFrame{
|
||||
frameID: c.nextID(),
|
||||
originalStackLen: len(c.stack),
|
||||
kind: controlFrameKindBlockWithoutContinuationLabel,
|
||||
}
|
||||
for _, t := range bt.Results {
|
||||
frame.returns = append(frame.returns, wasmValueTypeToUnsignedType(t))
|
||||
frameID: c.nextID(),
|
||||
originalStackLenWithoutParam: len(c.stack) - len(bt.Params),
|
||||
kind: controlFrameKindBlockWithoutContinuationLabel,
|
||||
blockType: bt,
|
||||
}
|
||||
c.controlFrames.push(frame)
|
||||
|
||||
case wasm.OpcodeLoop:
|
||||
bt, num, err := wasm.DecodeBlockType(c.f.Module.Types,
|
||||
bytes.NewReader(c.f.Body[c.pc+1:]))
|
||||
bt, num, err := wasm.DecodeBlockType(c.f.Module.Types, bytes.NewReader(c.f.Body[c.pc+1:]), c.enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading block type for loop instruction: %w", err)
|
||||
}
|
||||
@@ -262,17 +262,15 @@ operatorSwitch:
|
||||
|
||||
// Create a new frame -- entering loop.
|
||||
frame := &controlFrame{
|
||||
frameID: c.nextID(),
|
||||
originalStackLen: len(c.stack),
|
||||
kind: controlFrameKindLoop,
|
||||
}
|
||||
for _, t := range bt.Results {
|
||||
frame.returns = append(frame.returns, wasmValueTypeToUnsignedType(t))
|
||||
frameID: c.nextID(),
|
||||
originalStackLenWithoutParam: len(c.stack) - len(bt.Params),
|
||||
kind: controlFrameKindLoop,
|
||||
blockType: bt,
|
||||
}
|
||||
c.controlFrames.push(frame)
|
||||
|
||||
// Prep labels for inside and the continuation of this loop.
|
||||
loopLabel := &Label{FrameID: frame.frameID, Kind: LabelKindHeader, OriginalStackLen: frame.originalStackLen}
|
||||
loopLabel := &Label{FrameID: frame.frameID, Kind: LabelKindHeader}
|
||||
c.result.LabelCallers[loopLabel.String()]++
|
||||
|
||||
// Emit the branch operation to enter inside the loop.
|
||||
@@ -284,8 +282,7 @@ operatorSwitch:
|
||||
)
|
||||
|
||||
case wasm.OpcodeIf:
|
||||
bt, num, err := wasm.DecodeBlockType(c.f.Module.Types,
|
||||
bytes.NewReader(c.f.Body[c.pc+1:]))
|
||||
bt, num, err := wasm.DecodeBlockType(c.f.Module.Types, bytes.NewReader(c.f.Body[c.pc+1:]), c.enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading block type for if instruction: %w", err)
|
||||
}
|
||||
@@ -300,20 +297,18 @@ operatorSwitch:
|
||||
|
||||
// Create a new frame -- entering if.
|
||||
frame := &controlFrame{
|
||||
frameID: c.nextID(),
|
||||
originalStackLen: len(c.stack),
|
||||
frameID: c.nextID(),
|
||||
originalStackLenWithoutParam: len(c.stack) - len(bt.Params),
|
||||
// Note this will be set to controlFrameKindIfWithElse
|
||||
// when else opcode found later.
|
||||
kind: controlFrameKindIfWithoutElse,
|
||||
}
|
||||
for _, t := range bt.Results {
|
||||
frame.returns = append(frame.returns, wasmValueTypeToUnsignedType(t))
|
||||
kind: controlFrameKindIfWithoutElse,
|
||||
blockType: bt,
|
||||
}
|
||||
c.controlFrames.push(frame)
|
||||
|
||||
// Prep labels for if and else of this if.
|
||||
thenLabel := &Label{Kind: LabelKindHeader, FrameID: frame.frameID, OriginalStackLen: frame.originalStackLen}
|
||||
elseLabel := &Label{Kind: LabelKindElse, FrameID: frame.frameID, OriginalStackLen: frame.originalStackLen}
|
||||
thenLabel := &Label{Kind: LabelKindHeader, FrameID: frame.frameID}
|
||||
elseLabel := &Label{Kind: LabelKindElse, FrameID: frame.frameID}
|
||||
c.result.LabelCallers[thenLabel.String()]++
|
||||
c.result.LabelCallers[elseLabel.String()]++
|
||||
|
||||
@@ -337,12 +332,17 @@ operatorSwitch:
|
||||
// If it is currently in unreachable, and the non-nested if,
|
||||
// reset the stack so we can correctly handle the else block.
|
||||
top := c.controlFrames.top()
|
||||
c.stack = c.stack[:top.originalStackLen]
|
||||
c.stack = c.stack[:top.originalStackLenWithoutParam]
|
||||
top.kind = controlFrameKindIfWithElse
|
||||
|
||||
// Re-push the parameters to the if block so that else block can use them.
|
||||
for _, t := range frame.blockType.Params {
|
||||
c.stackPush(wasmValueTypeToUnsignedType(t))
|
||||
}
|
||||
|
||||
// We are no longer unreachable in else frame,
|
||||
// so emit the correct label, and reset the unreachable state.
|
||||
elseLabel := &Label{FrameID: frame.frameID, Kind: LabelKindElse, OriginalStackLen: top.originalStackLen}
|
||||
elseLabel := &Label{FrameID: frame.frameID, Kind: LabelKindElse}
|
||||
c.resetUnreachable()
|
||||
c.emit(
|
||||
&OperationLabel{Label: elseLabel},
|
||||
@@ -357,11 +357,17 @@ operatorSwitch:
|
||||
// We need to reset the stack so that
|
||||
// the values pushed inside the then block
|
||||
// do not affect the else block.
|
||||
dropOp := &OperationDrop{Range: c.getFrameDropRange(frame)}
|
||||
c.stack = c.stack[:frame.originalStackLen]
|
||||
dropOp := &OperationDrop{Depth: c.getFrameDropRange(frame, false)}
|
||||
|
||||
// Reset the stack manipulated by the then block, and re-push the block param types to the stack.
|
||||
|
||||
c.stack = c.stack[:frame.originalStackLenWithoutParam]
|
||||
for _, t := range frame.blockType.Params {
|
||||
c.stackPush(wasmValueTypeToUnsignedType(t))
|
||||
}
|
||||
|
||||
// Prep labels for else and the continuation of this if block.
|
||||
elseLabel := &Label{FrameID: frame.frameID, Kind: LabelKindElse, OriginalStackLen: frame.originalStackLen}
|
||||
elseLabel := &Label{FrameID: frame.frameID, Kind: LabelKindElse}
|
||||
continuationLabel := &Label{FrameID: frame.frameID, Kind: LabelKindContinuation}
|
||||
c.result.LabelCallers[continuationLabel.String()]++
|
||||
|
||||
@@ -386,15 +392,15 @@ operatorSwitch:
|
||||
return nil
|
||||
}
|
||||
|
||||
c.stack = c.stack[:frame.originalStackLen]
|
||||
for _, t := range frame.returns {
|
||||
c.stackPush(t)
|
||||
c.stack = c.stack[:frame.originalStackLenWithoutParam]
|
||||
for _, t := range frame.blockType.Results {
|
||||
c.stackPush(wasmValueTypeToUnsignedType(t))
|
||||
}
|
||||
|
||||
continuationLabel := &Label{FrameID: frame.frameID, Kind: LabelKindContinuation, OriginalStackLen: len(c.stack)}
|
||||
continuationLabel := &Label{FrameID: frame.frameID, Kind: LabelKindContinuation}
|
||||
if frame.kind == controlFrameKindIfWithoutElse {
|
||||
// Emit the else label.
|
||||
elseLabel := &Label{Kind: LabelKindElse, FrameID: frame.frameID, OriginalStackLen: frame.originalStackLen}
|
||||
elseLabel := &Label{Kind: LabelKindElse, FrameID: frame.frameID}
|
||||
c.result.LabelCallers[continuationLabel.String()]++
|
||||
c.emit(
|
||||
&OperationLabel{Label: elseLabel},
|
||||
@@ -414,12 +420,12 @@ operatorSwitch:
|
||||
|
||||
// We need to reset the stack so that
|
||||
// the values pushed inside the block.
|
||||
dropOp := &OperationDrop{Range: c.getFrameDropRange(frame)}
|
||||
c.stack = c.stack[:frame.originalStackLen]
|
||||
dropOp := &OperationDrop{Depth: c.getFrameDropRange(frame, true)}
|
||||
c.stack = c.stack[:frame.originalStackLenWithoutParam]
|
||||
|
||||
// Push the result types onto the stack.
|
||||
for _, t := range frame.returns {
|
||||
c.stackPush(t)
|
||||
for _, t := range frame.blockType.Results {
|
||||
c.stackPush(wasmValueTypeToUnsignedType(t))
|
||||
}
|
||||
|
||||
// Emit the instructions according to the kind of the current control frame.
|
||||
@@ -437,8 +443,8 @@ operatorSwitch:
|
||||
)
|
||||
case controlFrameKindIfWithoutElse:
|
||||
// This case we have to emit "empty" else label.
|
||||
elseLabel := &Label{Kind: LabelKindElse, FrameID: frame.frameID, OriginalStackLen: frame.originalStackLen}
|
||||
continuationLabel := &Label{Kind: LabelKindContinuation, FrameID: frame.frameID, OriginalStackLen: len(c.stack)}
|
||||
elseLabel := &Label{Kind: LabelKindElse, FrameID: frame.frameID}
|
||||
continuationLabel := &Label{Kind: LabelKindContinuation, FrameID: frame.frameID}
|
||||
c.result.LabelCallers[continuationLabel.String()] += 2
|
||||
c.emit(
|
||||
dropOp,
|
||||
@@ -451,7 +457,7 @@ operatorSwitch:
|
||||
)
|
||||
case controlFrameKindBlockWithContinuationLabel,
|
||||
controlFrameKindIfWithElse:
|
||||
continuationLabel := &Label{Kind: LabelKindContinuation, FrameID: frame.frameID, OriginalStackLen: len(c.stack)}
|
||||
continuationLabel := &Label{Kind: LabelKindContinuation, FrameID: frame.frameID}
|
||||
c.result.LabelCallers[continuationLabel.String()]++
|
||||
c.emit(
|
||||
dropOp,
|
||||
@@ -476,7 +482,7 @@ operatorSwitch:
|
||||
|
||||
targetFrame := c.controlFrames.get(int(targetIndex))
|
||||
targetFrame.ensureContinuation()
|
||||
dropOp := &OperationDrop{Range: c.getFrameDropRange(targetFrame)}
|
||||
dropOp := &OperationDrop{Depth: c.getFrameDropRange(targetFrame, false)}
|
||||
target := targetFrame.asBranchTarget()
|
||||
c.result.LabelCallers[target.Label.String()]++
|
||||
c.emit(
|
||||
@@ -496,7 +502,7 @@ operatorSwitch:
|
||||
|
||||
targetFrame := c.controlFrames.get(int(targetIndex))
|
||||
targetFrame.ensureContinuation()
|
||||
drop := c.getFrameDropRange(targetFrame)
|
||||
drop := c.getFrameDropRange(targetFrame, false)
|
||||
target := targetFrame.asBranchTarget()
|
||||
c.result.LabelCallers[target.Label.String()]++
|
||||
|
||||
@@ -530,7 +536,7 @@ operatorSwitch:
|
||||
c.pc += n
|
||||
targetFrame := c.controlFrames.get(int(l))
|
||||
targetFrame.ensureContinuation()
|
||||
drop := c.getFrameDropRange(targetFrame)
|
||||
drop := c.getFrameDropRange(targetFrame, false)
|
||||
target := &BranchTargetDrop{ToDrop: drop, Target: targetFrame.asBranchTarget()}
|
||||
targets[i] = target
|
||||
c.result.LabelCallers[target.Target.Label.String()]++
|
||||
@@ -544,7 +550,7 @@ operatorSwitch:
|
||||
c.pc += n
|
||||
defaultTargetFrame := c.controlFrames.get(int(l))
|
||||
defaultTargetFrame.ensureContinuation()
|
||||
defaultTargetDrop := c.getFrameDropRange(defaultTargetFrame)
|
||||
defaultTargetDrop := c.getFrameDropRange(defaultTargetFrame, false)
|
||||
defaultTarget := defaultTargetFrame.asBranchTarget()
|
||||
c.result.LabelCallers[defaultTarget.Label.String()]++
|
||||
|
||||
@@ -562,7 +568,7 @@ operatorSwitch:
|
||||
c.markUnreachable()
|
||||
case wasm.OpcodeReturn:
|
||||
functionFrame := c.controlFrames.functionFrame()
|
||||
dropOp := &OperationDrop{Range: c.getFrameDropRange(functionFrame)}
|
||||
dropOp := &OperationDrop{Depth: c.getFrameDropRange(functionFrame, false)}
|
||||
|
||||
// Cleanup the stack and then jmp to function frame's continuation (meaning return).
|
||||
c.emit(
|
||||
@@ -595,7 +601,7 @@ operatorSwitch:
|
||||
)
|
||||
case wasm.OpcodeDrop:
|
||||
c.emit(
|
||||
&OperationDrop{Range: &InclusiveRange{Start: 0, End: 0}},
|
||||
&OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
|
||||
)
|
||||
case wasm.OpcodeSelect:
|
||||
c.emit(
|
||||
@@ -620,7 +626,7 @@ operatorSwitch:
|
||||
// +1 because we already manipulated the stack before
|
||||
// called localDepth ^^.
|
||||
&OperationSwap{Depth: depth + 1},
|
||||
&OperationDrop{Range: &InclusiveRange{Start: 0, End: 0}},
|
||||
&OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
|
||||
)
|
||||
case wasm.OpcodeLocalTee:
|
||||
if index == nil {
|
||||
@@ -630,7 +636,7 @@ operatorSwitch:
|
||||
c.emit(
|
||||
&OperationPick{Depth: 0},
|
||||
&OperationSwap{Depth: depth + 1},
|
||||
&OperationDrop{Range: &InclusiveRange{Start: 0, End: 0}},
|
||||
&OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
|
||||
)
|
||||
case wasm.OpcodeGlobalGet:
|
||||
if index == nil {
|
||||
@@ -647,7 +653,7 @@ operatorSwitch:
|
||||
&OperationGlobalSet{Index: *index},
|
||||
)
|
||||
case wasm.OpcodeI32Load:
|
||||
imm, err := c.readMemoryImmediate("i32.load")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI32LoadName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -655,7 +661,7 @@ operatorSwitch:
|
||||
&OperationLoad{Type: UnsignedTypeI32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Load:
|
||||
imm, err := c.readMemoryImmediate("i64.load")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64LoadName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -663,7 +669,7 @@ operatorSwitch:
|
||||
&OperationLoad{Type: UnsignedTypeI64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeF32Load:
|
||||
imm, err := c.readMemoryImmediate("f32.load")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeF32LoadName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -671,7 +677,7 @@ operatorSwitch:
|
||||
&OperationLoad{Type: UnsignedTypeF32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeF64Load:
|
||||
imm, err := c.readMemoryImmediate("f64.load")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeF64LoadName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -679,7 +685,7 @@ operatorSwitch:
|
||||
&OperationLoad{Type: UnsignedTypeF64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI32Load8S:
|
||||
imm, err := c.readMemoryImmediate("i32.load8_s")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI32Load8SName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -687,7 +693,7 @@ operatorSwitch:
|
||||
&OperationLoad8{Type: SignedInt32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI32Load8U:
|
||||
imm, err := c.readMemoryImmediate("i32.load8_u")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI32Load8UName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -695,7 +701,7 @@ operatorSwitch:
|
||||
&OperationLoad8{Type: SignedUint32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI32Load16S:
|
||||
imm, err := c.readMemoryImmediate("i32.load16_s")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI32Load16SName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -703,7 +709,7 @@ operatorSwitch:
|
||||
&OperationLoad16{Type: SignedInt32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI32Load16U:
|
||||
imm, err := c.readMemoryImmediate("i32.load16_u")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI32Load16UName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -711,7 +717,7 @@ operatorSwitch:
|
||||
&OperationLoad16{Type: SignedUint32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Load8S:
|
||||
imm, err := c.readMemoryImmediate("i64.load8_s")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Load8SName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -719,7 +725,7 @@ operatorSwitch:
|
||||
&OperationLoad8{Type: SignedInt64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Load8U:
|
||||
imm, err := c.readMemoryImmediate("i64.load8_u")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Load8UName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -727,7 +733,7 @@ operatorSwitch:
|
||||
&OperationLoad8{Type: SignedUint64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Load16S:
|
||||
imm, err := c.readMemoryImmediate("i64.load16_s")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Load16SName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -735,7 +741,7 @@ operatorSwitch:
|
||||
&OperationLoad16{Type: SignedInt64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Load16U:
|
||||
imm, err := c.readMemoryImmediate("i64.load16_u")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Load16UName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -743,7 +749,7 @@ operatorSwitch:
|
||||
&OperationLoad16{Type: SignedUint64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Load32S:
|
||||
imm, err := c.readMemoryImmediate("i64.load32_s")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Load32SName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -751,7 +757,7 @@ operatorSwitch:
|
||||
&OperationLoad32{Signed: true, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Load32U:
|
||||
imm, err := c.readMemoryImmediate("i64.load32_s")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Load32UName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -759,7 +765,7 @@ operatorSwitch:
|
||||
&OperationLoad32{Signed: false, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI32Store:
|
||||
imm, err := c.readMemoryImmediate("i32.store")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI32StoreName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -767,7 +773,7 @@ operatorSwitch:
|
||||
&OperationStore{Type: UnsignedTypeI32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Store:
|
||||
imm, err := c.readMemoryImmediate("i64.store")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64StoreName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -775,7 +781,7 @@ operatorSwitch:
|
||||
&OperationStore{Type: UnsignedTypeI64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeF32Store:
|
||||
imm, err := c.readMemoryImmediate("f32.store")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeF32StoreName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -783,7 +789,7 @@ operatorSwitch:
|
||||
&OperationStore{Type: UnsignedTypeF32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeF64Store:
|
||||
imm, err := c.readMemoryImmediate("f64.store")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeF64StoreName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -791,7 +797,7 @@ operatorSwitch:
|
||||
&OperationStore{Type: UnsignedTypeF64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI32Store8:
|
||||
imm, err := c.readMemoryImmediate("i32.store8")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI32Store8Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -799,7 +805,7 @@ operatorSwitch:
|
||||
&OperationStore8{Type: UnsignedInt32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI32Store16:
|
||||
imm, err := c.readMemoryImmediate("i32.store16")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI32Store16Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -807,7 +813,7 @@ operatorSwitch:
|
||||
&OperationStore16{Type: UnsignedInt32, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Store8:
|
||||
imm, err := c.readMemoryImmediate("i64.store8")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Store8Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -815,7 +821,7 @@ operatorSwitch:
|
||||
&OperationStore8{Type: UnsignedInt64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Store16:
|
||||
imm, err := c.readMemoryImmediate("i64.store16")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Store16Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -823,7 +829,7 @@ operatorSwitch:
|
||||
&OperationStore16{Type: UnsignedInt64, Arg: imm},
|
||||
)
|
||||
case wasm.OpcodeI64Store32:
|
||||
imm, err := c.readMemoryImmediate("i64.store32")
|
||||
imm, err := c.readMemoryImmediate(wasm.OpcodeI64Store32Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1493,7 +1499,7 @@ func (c *compiler) emit(ops ...Operation) {
|
||||
// we could remove such operations.
|
||||
// That happens when drop operation is unnecessary.
|
||||
// i.e. when there's no need to adjust stack before jmp.
|
||||
if o.Range == nil {
|
||||
if o.Depth == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1530,18 +1536,28 @@ func (c *compiler) localDepth(n uint32) int {
|
||||
return int(len(c.stack)) - 1 - int(n)
|
||||
}
|
||||
|
||||
// Returns the range (starting from top of the stack) that spans across
|
||||
// the stack. The range is supposed to be dropped from the stack when
|
||||
// the given frame exists.
|
||||
func (c *compiler) getFrameDropRange(frame *controlFrame) *InclusiveRange {
|
||||
start := len(frame.returns)
|
||||
// getFrameDropRange returns the range (starting from top of the stack) that spans across the stack. The range is
|
||||
// supposed to be dropped from the stack when the given frame exists or branch into it.
|
||||
//
|
||||
// * frame is the control frame which the call-site is trying to branch into or exit.
|
||||
// * isEnd true if the call-site is handling wasm.OpcodeEnd.
|
||||
func (c *compiler) getFrameDropRange(frame *controlFrame, isEnd bool) *InclusiveRange {
|
||||
var start int
|
||||
if !isEnd && frame.kind == controlFrameKindLoop {
|
||||
// If this is not End and the call-site is trying to branch into the Loop control frame,
|
||||
// we have to start executing from the beginning of the loop block.
|
||||
// Therefore, we have to pass the inputs to the frame.
|
||||
start = len(frame.blockType.Params)
|
||||
} else {
|
||||
start = len(frame.blockType.Results)
|
||||
}
|
||||
var end int
|
||||
if frame.kind == controlFrameKindFunction {
|
||||
// On the function return, we eliminate all the contents on the stack
|
||||
// including locals (existing below of frame.originalStackLen)
|
||||
end = len(c.stack) - 1
|
||||
} else {
|
||||
end = len(c.stack) - 1 - frame.originalStackLen
|
||||
end = len(c.stack) - 1 - frame.originalStackLenWithoutParam
|
||||
}
|
||||
if start <= end {
|
||||
return &InclusiveRange{Start: start, End: end}
|
||||
|
||||
428
internal/wazeroir/compiler_test.go
Normal file
428
internal/wazeroir/compiler_test.go
Normal file
@@ -0,0 +1,428 @@
|
||||
package wazeroir
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/internal/wasm/text"
|
||||
)
|
||||
|
||||
var (
|
||||
f64, i32 = wasm.ValueTypeF64, wasm.ValueTypeI32
|
||||
i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}
|
||||
i32i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}
|
||||
v_v = &wasm.FunctionType{}
|
||||
v_f64f64 = &wasm.FunctionType{Results: []wasm.ValueType{f64, f64}}
|
||||
)
|
||||
|
||||
func TestCompile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
module *wasm.Module
|
||||
expected *CompilationResult
|
||||
enabledFeatures wasm.Features
|
||||
}{
|
||||
{
|
||||
name: "nullary",
|
||||
module: requireModuleText(t, `(module (func))`),
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: []
|
||||
&OperationBr{Target: &BranchTarget{}}, // return!
|
||||
},
|
||||
LabelCallers: map[string]uint32{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "identity",
|
||||
module: requireModuleText(t, `(module
|
||||
(func (param $x i32) (result i32) local.get 0)
|
||||
)`),
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: [$x]
|
||||
&OperationPick{Depth: 0}, // [$x, $x]
|
||||
&OperationDrop{Depth: &InclusiveRange{Start: 1, End: 1}}, // [$x]
|
||||
&OperationBr{Target: &BranchTarget{}}, // return!
|
||||
},
|
||||
LabelCallers: map[string]uint32{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
enabledFeatures := tc.enabledFeatures
|
||||
if enabledFeatures == 0 {
|
||||
enabledFeatures = wasm.FeaturesFinished
|
||||
}
|
||||
functions, err := compileFunctions(enabledFeatures, tc.module)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, functions, 1)
|
||||
|
||||
res, err := Compile(enabledFeatures, functions[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompile_Block(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
module *wasm.Module
|
||||
expected *CompilationResult
|
||||
enabledFeatures wasm.Features
|
||||
}{
|
||||
{
|
||||
name: "type-i32-i32",
|
||||
module: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{v_v},
|
||||
FunctionSection: []wasm.Index{0},
|
||||
CodeSection: []*wasm.Code{{Body: []byte{
|
||||
wasm.OpcodeBlock, 0x40,
|
||||
wasm.OpcodeBr, 0,
|
||||
wasm.OpcodeI32Add,
|
||||
wasm.OpcodeDrop,
|
||||
wasm.OpcodeEnd,
|
||||
wasm.OpcodeEnd,
|
||||
}}},
|
||||
},
|
||||
// Above set manually until the text compiler supports this:
|
||||
// (func (export "type-i32-i32") (block (drop (i32.add (br 0)))))
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: []
|
||||
&OperationBr{
|
||||
Target: &BranchTarget{
|
||||
Label: &Label{FrameID: 2, Kind: LabelKindContinuation}, // arbitrary FrameID
|
||||
},
|
||||
},
|
||||
&OperationLabel{
|
||||
Label: &Label{FrameID: 2, Kind: LabelKindContinuation}, // arbitrary FrameID
|
||||
},
|
||||
&OperationBr{Target: &BranchTarget{}}, // return!
|
||||
},
|
||||
// Note: i32.add comes after br 0 so is unreachable. Compilation succeeds when it feels like it
|
||||
// shouldn't because the br instruction is stack-polymorphic. In other words, (br 0) substitutes for the
|
||||
// two i32 parameters to add.
|
||||
LabelCallers: map[string]uint32{".L2_cont": 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
requireCompilationResult(t, tc.enabledFeatures, tc.expected, tc.module)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompile_MultiValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
module *wasm.Module
|
||||
expected *CompilationResult
|
||||
enabledFeatures wasm.Features
|
||||
}{
|
||||
{
|
||||
name: "swap",
|
||||
module: requireModuleText(t, `(module
|
||||
(func (param $x i32) (param $y i32) (result i32 i32) local.get 1 local.get 0)
|
||||
)`),
|
||||
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: [$x, $y]
|
||||
&OperationPick{Depth: 0}, // [$x, $y, $y]
|
||||
&OperationPick{Depth: 2}, // [$x, $y, $y, $x]
|
||||
&OperationDrop{Depth: &InclusiveRange{Start: 2, End: 3}}, // [$y, $x]
|
||||
&OperationBr{Target: &BranchTarget{}}, // return!
|
||||
},
|
||||
LabelCallers: map[string]uint32{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "br.wast - type-f64-f64-value",
|
||||
module: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{v_f64f64},
|
||||
FunctionSection: []wasm.Index{0},
|
||||
CodeSection: []*wasm.Code{{Body: []byte{
|
||||
wasm.OpcodeBlock, 0, // (block (result f64 f64)
|
||||
wasm.OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x10, 0x40, // (f64.const 4)
|
||||
wasm.OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x14, 0x40, // (f64.const 5)
|
||||
wasm.OpcodeBr, 0,
|
||||
wasm.OpcodeF64Add,
|
||||
wasm.OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0x18, 0x40, // (f64.const 6)
|
||||
wasm.OpcodeEnd,
|
||||
wasm.OpcodeEnd,
|
||||
}}},
|
||||
},
|
||||
// Above set manually until the text compiler supports this:
|
||||
// (func $type-f64-f64-value (result f64 f64)
|
||||
// (block (result f64 f64)
|
||||
// (f64.add (br 0 (f64.const 4) (f64.const 5))) (f64.const 6)
|
||||
// )
|
||||
// )
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: []
|
||||
&OperationConstF64{Value: 4}, // [4]
|
||||
&OperationConstF64{Value: 5}, // [4, 5]
|
||||
&OperationBr{
|
||||
Target: &BranchTarget{
|
||||
Label: &Label{FrameID: 2, Kind: LabelKindContinuation}, // arbitrary FrameID
|
||||
},
|
||||
},
|
||||
&OperationLabel{
|
||||
Label: &Label{FrameID: 2, Kind: LabelKindContinuation}, // arbitrary FrameID
|
||||
},
|
||||
&OperationBr{Target: &BranchTarget{}}, // return!
|
||||
},
|
||||
// Note: f64.add comes after br 0 so is unreachable. This is why neither the add, nor its other operand
|
||||
// are in the above compilation result.
|
||||
LabelCallers: map[string]uint32{".L2_cont": 1}, // arbitrary label
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "call.wast - $const-i32-i64",
|
||||
module: requireModuleText(t, `(module
|
||||
(func $const-i32-i64 (result i32 i64) i32.const 306 i64.const 356)
|
||||
)`),
|
||||
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: []
|
||||
&OperationConstI32{Value: 306}, // [306]
|
||||
&OperationConstI64{Value: 356}, // [306, 356]
|
||||
&OperationBr{Target: &BranchTarget{}}, // return!
|
||||
},
|
||||
LabelCallers: map[string]uint32{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "if.wast - param",
|
||||
module: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{i32_i32}, // (func (param i32) (result i32)
|
||||
FunctionSection: []wasm.Index{0},
|
||||
CodeSection: []*wasm.Code{{Body: []byte{
|
||||
wasm.OpcodeI32Const, 1, // (i32.const 1)
|
||||
wasm.OpcodeLocalGet, 0, wasm.OpcodeIf, 0, // (if (param i32) (result i32) (local.get 0)
|
||||
wasm.OpcodeI32Const, 2, wasm.OpcodeI32Add, // (then (i32.const 2) (i32.add))
|
||||
wasm.OpcodeElse, wasm.OpcodeI32Const, 0x7e, wasm.OpcodeI32Add, // (else (i32.const -2) (i32.add))
|
||||
wasm.OpcodeEnd, // )
|
||||
wasm.OpcodeEnd, // )
|
||||
}}},
|
||||
},
|
||||
// Above set manually until the text compiler supports this:
|
||||
// (func (export "param") (param i32) (result i32)
|
||||
// (i32.const 1)
|
||||
// (if (param i32) (result i32) (local.get 0)
|
||||
// (then (i32.const 2) (i32.add))
|
||||
// (else (i32.const -2) (i32.add))
|
||||
// )
|
||||
// )
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: [$0]
|
||||
&OperationConstI32{Value: 1}, // [$0, 1]
|
||||
&OperationPick{Depth: 1}, // [$0, 1, $0]
|
||||
&OperationBrIf{ // [$0, 1]
|
||||
Then: &BranchTargetDrop{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindHeader}}},
|
||||
Else: &BranchTargetDrop{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindElse}}},
|
||||
},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindHeader}},
|
||||
&OperationConstI32{Value: 2}, // [$0, 1, 2]
|
||||
&OperationAdd{Type: UnsignedTypeI32}, // [$0, 3]
|
||||
&OperationBr{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}}},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindElse}},
|
||||
&OperationConstI32{Value: uint32(api.EncodeI32(-2))}, // [$0, 1, -2]
|
||||
&OperationAdd{Type: UnsignedTypeI32}, // [$0, -1]
|
||||
&OperationBr{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}}},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}},
|
||||
&OperationDrop{Depth: &InclusiveRange{Start: 1, End: 1}}, // .L2 = [3], .L2_else = [-1]
|
||||
&OperationBr{Target: &BranchTarget{}},
|
||||
},
|
||||
LabelCallers: map[string]uint32{
|
||||
".L2": 1,
|
||||
".L2_cont": 2,
|
||||
".L2_else": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "if.wast - params",
|
||||
module: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
i32_i32, // (func (param i32) (result i32)
|
||||
i32i32_i32, // (if (param i32 i32) (result i32)
|
||||
},
|
||||
FunctionSection: []wasm.Index{0},
|
||||
CodeSection: []*wasm.Code{{Body: []byte{
|
||||
wasm.OpcodeI32Const, 1, // (i32.const 1)
|
||||
wasm.OpcodeI32Const, 2, // (i32.const 2)
|
||||
wasm.OpcodeLocalGet, 0, wasm.OpcodeIf, 1, // (if (param i32) (result i32) (local.get 0)
|
||||
wasm.OpcodeI32Add, // (then (i32.add))
|
||||
wasm.OpcodeElse, wasm.OpcodeI32Sub, // (else (i32.sub))
|
||||
wasm.OpcodeEnd, // )
|
||||
wasm.OpcodeEnd, // )
|
||||
}}},
|
||||
},
|
||||
// Above set manually until the text compiler supports this:
|
||||
// (func (export "params") (param i32) (result i32)
|
||||
// (i32.const 1)
|
||||
// (i32.const 2)
|
||||
// (if (param i32 i32) (result i32) (local.get 0)
|
||||
// (then (i32.add))
|
||||
// (else (i32.sub))
|
||||
// )
|
||||
// )
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: [$0]
|
||||
&OperationConstI32{Value: 1}, // [$0, 1]
|
||||
&OperationConstI32{Value: 2}, // [$0, 1, 2]
|
||||
&OperationPick{Depth: 2}, // [$0, 1, 2, $0]
|
||||
&OperationBrIf{ // [$0, 1, 2]
|
||||
Then: &BranchTargetDrop{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindHeader}}},
|
||||
Else: &BranchTargetDrop{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindElse}}},
|
||||
},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindHeader}},
|
||||
&OperationAdd{Type: UnsignedTypeI32}, // [$0, 3]
|
||||
&OperationBr{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}}},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindElse}},
|
||||
&OperationSub{Type: UnsignedTypeI32}, // [$0, -1]
|
||||
&OperationBr{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}}},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}},
|
||||
&OperationDrop{Depth: &InclusiveRange{Start: 1, End: 1}}, // .L2 = [3], .L2_else = [-1]
|
||||
&OperationBr{Target: &BranchTarget{}},
|
||||
},
|
||||
LabelCallers: map[string]uint32{
|
||||
".L2": 1,
|
||||
".L2_cont": 2,
|
||||
".L2_else": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "if.wast - params-break",
|
||||
module: &wasm.Module{
|
||||
TypeSection: []*wasm.FunctionType{
|
||||
i32_i32, // (func (param i32) (result i32)
|
||||
i32i32_i32, // (if (param i32 i32) (result i32)
|
||||
},
|
||||
FunctionSection: []wasm.Index{0},
|
||||
CodeSection: []*wasm.Code{{Body: []byte{
|
||||
wasm.OpcodeI32Const, 1, // (i32.const 1)
|
||||
wasm.OpcodeI32Const, 2, // (i32.const 2)
|
||||
wasm.OpcodeLocalGet, 0, wasm.OpcodeIf, 1, // (if (param i32) (result i32) (local.get 0)
|
||||
wasm.OpcodeI32Add, wasm.OpcodeBr, 0, // (then (i32.add) (br 0))
|
||||
wasm.OpcodeElse, wasm.OpcodeI32Sub, wasm.OpcodeBr, 0, // (else (i32.sub) (br 0))
|
||||
wasm.OpcodeEnd, // )
|
||||
wasm.OpcodeEnd, // )
|
||||
}}},
|
||||
},
|
||||
// Above set manually until the text compiler supports this:
|
||||
// (func (export "params-break") (param i32) (result i32)
|
||||
// (i32.const 1)
|
||||
// (i32.const 2)
|
||||
// (if (param i32 i32) (result i32) (local.get 0)
|
||||
// (then (i32.add) (br 0))
|
||||
// (else (i32.sub) (br 0))
|
||||
// )
|
||||
// )
|
||||
expected: &CompilationResult{
|
||||
Operations: []Operation{ // begin with params: [$0]
|
||||
&OperationConstI32{Value: 1}, // [$0, 1]
|
||||
&OperationConstI32{Value: 2}, // [$0, 1, 2]
|
||||
&OperationPick{Depth: 2}, // [$0, 1, 2, $0]
|
||||
&OperationBrIf{ // [$0, 1, 2]
|
||||
Then: &BranchTargetDrop{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindHeader}}},
|
||||
Else: &BranchTargetDrop{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindElse}}},
|
||||
},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindHeader}},
|
||||
&OperationAdd{Type: UnsignedTypeI32}, // [$0, 3]
|
||||
&OperationBr{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}}},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindElse}},
|
||||
&OperationSub{Type: UnsignedTypeI32}, // [$0, -1]
|
||||
&OperationBr{Target: &BranchTarget{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}}},
|
||||
&OperationLabel{Label: &Label{FrameID: 2, Kind: LabelKindContinuation}},
|
||||
&OperationDrop{Depth: &InclusiveRange{Start: 1, End: 1}}, // .L2 = [3], .L2_else = [-1]
|
||||
&OperationBr{Target: &BranchTarget{}},
|
||||
},
|
||||
LabelCallers: map[string]uint32{
|
||||
".L2": 1,
|
||||
".L2_cont": 2,
|
||||
".L2_else": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
enabledFeatures := tc.enabledFeatures
|
||||
if enabledFeatures == 0 {
|
||||
enabledFeatures = wasm.FeaturesFinished
|
||||
}
|
||||
functions, err := compileFunctions(enabledFeatures, tc.module)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, functions, 1)
|
||||
|
||||
res, err := Compile(enabledFeatures, functions[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func requireCompilationResult(t *testing.T, enabledFeatures wasm.Features, expected *CompilationResult, module *wasm.Module) {
|
||||
if enabledFeatures == 0 {
|
||||
enabledFeatures = wasm.FeaturesFinished
|
||||
}
|
||||
functions, err := compileFunctions(enabledFeatures, module)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, functions, 1)
|
||||
|
||||
res, err := Compile(enabledFeatures, functions[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, res)
|
||||
}
|
||||
|
||||
func requireModuleText(t *testing.T, source string) *wasm.Module {
|
||||
m, err := text.DecodeModule([]byte(source), wasm.FeaturesFinished, wasm.MemoryMaxPages)
|
||||
require.NoError(t, err)
|
||||
return m
|
||||
}
|
||||
|
||||
func compileFunctions(enabledFeatures wasm.Features, module *wasm.Module) ([]*wasm.FunctionInstance, error) {
|
||||
cf := &catchFunctions{}
|
||||
_, err := wasm.NewStore(enabledFeatures, cf).Instantiate(context.Background(), module, "", wasm.DefaultSysContext())
|
||||
return cf.functions, err
|
||||
}
|
||||
|
||||
type catchFunctions struct {
|
||||
functions []*wasm.FunctionInstance
|
||||
}
|
||||
|
||||
// NewModuleEngine implements the same method as documented on wasm.Engine.
|
||||
func (e *catchFunctions) NewModuleEngine(_ string, _, functions []*wasm.FunctionInstance, _ *wasm.TableInstance, _ map[wasm.Index]wasm.Index) (wasm.ModuleEngine, error) {
|
||||
e.functions = functions
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Name implements the same method as documented on wasm.ModuleEngine.
|
||||
func (e *catchFunctions) Name() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Call implements the same method as documented on wasm.ModuleEngine.
|
||||
func (e *catchFunctions) Call(_ *wasm.ModuleContext, _ *wasm.FunctionInstance, _ ...uint64) ([]uint64, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Close implements the same method as documented on wasm.ModuleEngine.
|
||||
func (e *catchFunctions) Close() {
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func formatOperation(w io.StringWriter, b Operation) {
|
||||
case *OperationCallIndirect:
|
||||
str = fmt.Sprintf("call_indirect: type=%d, table=%d", o.TypeIndex, o.TableIndex)
|
||||
case *OperationDrop:
|
||||
str = fmt.Sprintf("drop %d..%d", o.Range.Start, o.Range.End)
|
||||
str = fmt.Sprintf("drop %d..%d", o.Depth.Start, o.Depth.End)
|
||||
case *OperationSelect:
|
||||
str = "select"
|
||||
case *OperationPick:
|
||||
|
||||
@@ -343,9 +343,8 @@ const (
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
FrameID uint32
|
||||
OriginalStackLen int
|
||||
Kind LabelKind
|
||||
FrameID uint32
|
||||
Kind LabelKind
|
||||
}
|
||||
|
||||
func (l *Label) String() (ret string) {
|
||||
@@ -471,7 +470,7 @@ func (o *OperationCallIndirect) Kind() OperationKind {
|
||||
return OperationKindCallIndirect
|
||||
}
|
||||
|
||||
type OperationDrop struct{ Range *InclusiveRange }
|
||||
type OperationDrop struct{ Depth *InclusiveRange }
|
||||
|
||||
func (o *OperationDrop) Kind() OperationKind {
|
||||
return OperationKindDrop
|
||||
@@ -507,8 +506,19 @@ func (o *OperationGlobalSet) Kind() OperationKind {
|
||||
return OperationKindGlobalSet
|
||||
}
|
||||
|
||||
// MemoryImmediate is the "memarg" to all memory instructions.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instructions%E2%91%A0
|
||||
type MemoryImmediate struct {
|
||||
Alignment, Offset uint32
|
||||
// Alignment the expected alignment (expressed as the exponent of a power of 2). Default to the natural alignment.
|
||||
//
|
||||
// "Natural alignment" is defined here as the smallest power of two that can hold the size of the value type. Ex
|
||||
// wasm.ValueTypeI64 is encoded in 8 little-endian bytes. 2^3 = 8, so the natural alignment is three.
|
||||
Alignment uint32
|
||||
|
||||
// Offset is the address offset added to the instruction's dynamic address operand, yielding a 33-bit effective
|
||||
// address that is the zero-based index at which the memory is accessed. Default to zero.
|
||||
Offset uint32
|
||||
}
|
||||
|
||||
type OperationLoad struct {
|
||||
|
||||
309
tests/post1_0/multi-value/multi_value_test.go
Normal file
309
tests/post1_0/multi-value/multi_value_test.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package multi_value
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
func TestMultiValue_JIT(t *testing.T) {
|
||||
if !wazero.JITSupported {
|
||||
t.Skip()
|
||||
}
|
||||
testMultiValue(t, wazero.NewRuntimeConfigJIT)
|
||||
}
|
||||
|
||||
func TestMultiValue_Interpreter(t *testing.T) {
|
||||
testMultiValue(t, wazero.NewRuntimeConfigInterpreter)
|
||||
}
|
||||
|
||||
// multiValueWasm was compiled from testdata/multi_value.wat
|
||||
//go:embed testdata/multi_value.wasm
|
||||
var multiValueWasm []byte
|
||||
|
||||
func testMultiValue(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
|
||||
t.Run("disabled", func(t *testing.T) {
|
||||
// multi-value is disabled by default.
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig())
|
||||
_, err := r.InstantiateModuleFromCode(multiValueWasm)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("enabled", func(t *testing.T) {
|
||||
r := wazero.NewRuntimeWithConfig(newRuntimeConfig().WithFeatureMultiValue(true))
|
||||
module, err := r.InstantiateModuleFromCode(multiValueWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
swap := module.ExportedFunction("swap")
|
||||
results, err := swap.Call(nil, 100, 200)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{200, 100}, results)
|
||||
|
||||
add64UWithCarry := module.ExportedFunction("add64_u_with_carry")
|
||||
results, err = add64UWithCarry.Call(nil, 0x8000000000000000, 0x8000000000000000, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{0, 1}, results)
|
||||
|
||||
add64USaturated := module.ExportedFunction("add64_u_saturated")
|
||||
results, err = add64USaturated.Call(nil, 1230, 23)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{1253}, results)
|
||||
|
||||
fac := module.ExportedFunction("fac")
|
||||
results, err = fac.Call(nil, 25)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{7034535277573963776}, results)
|
||||
|
||||
t.Run("br.wast", func(t *testing.T) {
|
||||
testBr(t, r)
|
||||
})
|
||||
t.Run("call.wast", func(t *testing.T) {
|
||||
testCall(t, r)
|
||||
})
|
||||
t.Run("call_indirect.wast", func(t *testing.T) {
|
||||
testCallIndirect(t, r)
|
||||
})
|
||||
t.Run("fac.wast", func(t *testing.T) {
|
||||
testFac(t, r)
|
||||
})
|
||||
t.Run("func.wast", func(t *testing.T) {
|
||||
testFunc(t, r)
|
||||
})
|
||||
t.Run("if.wast", func(t *testing.T) {
|
||||
testIf(t, r)
|
||||
})
|
||||
t.Run("loop.wast", func(t *testing.T) {
|
||||
testLoop(t, r)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// brWasm was compiled from testdata/br.wat
|
||||
//go:embed testdata/br.wasm
|
||||
var brWasm []byte
|
||||
|
||||
func testBr(t *testing.T, r wazero.Runtime) {
|
||||
module, err := r.InstantiateModuleFromCode(brWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
testFunctions(t, module, []funcTest{
|
||||
{name: "type-i32-i32"}, {name: "type-i64-i64"}, {name: "type-f32-f32"}, {name: "type-f64-f64"},
|
||||
{name: "type-f64-f64-value", expected: []uint64{api.EncodeF64(4), api.EncodeF64(5)}},
|
||||
{name: "as-return-values", expected: []uint64{2, 7}},
|
||||
{name: "as-select-all", expected: []uint64{8}},
|
||||
{name: "as-call-all", expected: []uint64{15}},
|
||||
{name: "as-call_indirect-all", expected: []uint64{24}},
|
||||
{name: "as-store-both", expected: []uint64{32}},
|
||||
{name: "as-storeN-both", expected: []uint64{34}},
|
||||
{name: "as-binary-both", expected: []uint64{46}},
|
||||
{name: "as-compare-both", expected: []uint64{44}},
|
||||
})
|
||||
}
|
||||
|
||||
// callWasm was compiled from testdata/call.wat
|
||||
//go:embed testdata/call.wasm
|
||||
var callWasm []byte
|
||||
|
||||
func testCall(t *testing.T, r wazero.Runtime) {
|
||||
module, err := r.InstantiateModuleFromCode(callWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
testFunctions(t, module, []funcTest{
|
||||
{name: "type-i32-i64", expected: []uint64{0x132, 0x164}},
|
||||
{name: "type-all-i32-f64", expected: []uint64{32, api.EncodeF64(1.64)}},
|
||||
{name: "type-all-i32-i32", expected: []uint64{2, 1}},
|
||||
{name: "type-all-f32-f64", expected: []uint64{api.EncodeF64(2), api.EncodeF32(1)}},
|
||||
{name: "type-all-f64-i32", expected: []uint64{2, api.EncodeF64(1)}},
|
||||
{name: "as-binary-all-operands", expected: []uint64{7}},
|
||||
{name: "as-mixed-operands", expected: []uint64{32}},
|
||||
{name: "as-call-all-operands", expected: []uint64{3, 4}},
|
||||
})
|
||||
}
|
||||
|
||||
// callIndirectWasm was compiled from testdata/call_indirect.wat
|
||||
//go:embed testdata/call_indirect.wasm
|
||||
var callIndirectWasm []byte
|
||||
|
||||
func testCallIndirect(t *testing.T, r wazero.Runtime) {
|
||||
module, err := r.InstantiateModuleFromCode(callIndirectWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
testFunctions(t, module, []funcTest{
|
||||
{name: "type-f64-i32", expected: []uint64{api.EncodeF64(0xf64), 32}},
|
||||
{name: "type-all-f64-i32", expected: []uint64{api.EncodeF64(0xf64), 32}},
|
||||
{name: "type-all-i32-f64", expected: []uint64{1, api.EncodeF64(2)}},
|
||||
{name: "type-all-i32-i64", expected: []uint64{2, 1}},
|
||||
})
|
||||
|
||||
_, err = module.ExportedFunction("dispatch").Call(nil, 32, 2)
|
||||
require.EqualError(t, err, `wasm error: invalid table access
|
||||
wasm stack trace:
|
||||
call_indirect.wast.[16](i32,i64) i64`)
|
||||
}
|
||||
|
||||
// facWasm was compiled from testdata/fac.wat
|
||||
//go:embed testdata/fac.wasm
|
||||
var facWasm []byte
|
||||
|
||||
func testFac(t *testing.T, r wazero.Runtime) {
|
||||
module, err := r.InstantiateModuleFromCode(facWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
fac := module.ExportedFunction("fac-ssa")
|
||||
results, err := fac.Call(nil, 25)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{7034535277573963776}, results)
|
||||
}
|
||||
|
||||
// funcWasm was compiled from testdata/func.wat
|
||||
//go:embed testdata/func.wasm
|
||||
var funcWasm []byte
|
||||
|
||||
func testFunc(t *testing.T, r wazero.Runtime) {
|
||||
module, err := r.InstantiateModuleFromCode(funcWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
testFunctions(t, module, []funcTest{
|
||||
{name: "value-i32-f64", expected: []uint64{77, api.EncodeF64(7)}},
|
||||
{name: "value-i32-i32-i32", expected: []uint64{1, 2, 3}},
|
||||
{name: "value-block-i32-i64", expected: []uint64{1, 2}},
|
||||
|
||||
{name: "return-i32-f64", expected: []uint64{78, api.EncodeF64(78.78)}},
|
||||
{name: "return-i32-i32-i32", expected: []uint64{1, 2, 3}},
|
||||
{name: "return-block-i32-i64", expected: []uint64{1, 2}},
|
||||
{name: "break-i32-f64", expected: []uint64{79, api.EncodeF64(79.79)}},
|
||||
{name: "break-i32-i32-i32", expected: []uint64{1, 2, 3}},
|
||||
{name: "break-block-i32-i64", expected: []uint64{1, 2}},
|
||||
|
||||
{name: "break-br_if-num-num", params: []uint64{0}, expected: []uint64{51, 52}},
|
||||
{name: "break-br_if-num-num", params: []uint64{1}, expected: []uint64{50, 51}},
|
||||
{name: "break-br_table-num-num", params: []uint64{0}, expected: []uint64{50, 51}},
|
||||
{name: "break-br_table-num-num", params: []uint64{1}, expected: []uint64{50, 51}},
|
||||
{name: "break-br_table-num-num", params: []uint64{10}, expected: []uint64{50, 51}},
|
||||
{name: "break-br_table-num-num", params: []uint64{api.EncodeI32(-100)}, expected: []uint64{50, 51}},
|
||||
{name: "break-br_table-nested-num-num", params: []uint64{0}, expected: []uint64{101, 52}},
|
||||
{name: "break-br_table-nested-num-num", params: []uint64{1}, expected: []uint64{50, 51}},
|
||||
{name: "break-br_table-nested-num-num", params: []uint64{2}, expected: []uint64{101, 52}},
|
||||
{name: "break-br_table-nested-num-num", params: []uint64{api.EncodeI32(-3)}, expected: []uint64{101, 52}},
|
||||
})
|
||||
|
||||
fac := module.ExportedFunction("large-sig")
|
||||
results, err := fac.Call(nil,
|
||||
0, 1, api.EncodeF32(2), api.EncodeF32(3),
|
||||
4, api.EncodeF64(5), api.EncodeF32(6), 7,
|
||||
8, 9, api.EncodeF32(10), api.EncodeF64(11),
|
||||
api.EncodeF64(12), api.EncodeF64(13), 14, 15,
|
||||
api.EncodeF32(16))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint64{api.EncodeF64(5), api.EncodeF32(2), 0, 8,
|
||||
7, 1, api.EncodeF32(3), 9,
|
||||
4, api.EncodeF32(6), api.EncodeF64(13), api.EncodeF64(11),
|
||||
15, api.EncodeF32(16), 14, api.EncodeF64(12),
|
||||
}, results)
|
||||
}
|
||||
|
||||
// ifWasm was compiled from testdata/if.wat
|
||||
//go:embed testdata/if.wasm
|
||||
var ifWasm []byte
|
||||
|
||||
func testIf(t *testing.T, r wazero.Runtime) {
|
||||
module, err := r.InstantiateModuleFromCode(ifWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
testFunctions(t, module, []funcTest{
|
||||
{name: "multi", params: []uint64{0}, expected: []uint64{9, api.EncodeI32(-1)}},
|
||||
{name: "multi", params: []uint64{1}, expected: []uint64{8, 1}},
|
||||
{name: "multi", params: []uint64{13}, expected: []uint64{8, 1}},
|
||||
{name: "multi", params: []uint64{api.EncodeI32(-5)}, expected: []uint64{8, 1}},
|
||||
{name: "as-binary-operands", params: []uint64{0}, expected: []uint64{api.EncodeI32(-12)}},
|
||||
{name: "as-binary-operands", params: []uint64{1}, expected: []uint64{api.EncodeI32(12)}},
|
||||
{name: "as-compare-operands", params: []uint64{0}, expected: []uint64{1}},
|
||||
{name: "as-compare-operands", params: []uint64{1}, expected: []uint64{0}},
|
||||
{name: "as-mixed-operands", params: []uint64{0}, expected: []uint64{api.EncodeI32(-3)}},
|
||||
{name: "as-mixed-operands", params: []uint64{1}, expected: []uint64{27}},
|
||||
{name: "break-multi-value", params: []uint64{0}, expected: []uint64{api.EncodeI32(-18), 18, api.EncodeI64(-18)}},
|
||||
{name: "break-multi-value", params: []uint64{1}, expected: []uint64{18, api.EncodeI32(-18), 18}},
|
||||
{name: "param", params: []uint64{0}, expected: []uint64{api.EncodeI32(-1)}},
|
||||
{name: "param", params: []uint64{1}, expected: []uint64{3}},
|
||||
{name: "params", params: []uint64{0}, expected: []uint64{api.EncodeI32(-1)}},
|
||||
{name: "params", params: []uint64{1}, expected: []uint64{3}},
|
||||
{name: "params-id", params: []uint64{0}, expected: []uint64{3}},
|
||||
{name: "params-id", params: []uint64{1}, expected: []uint64{3}},
|
||||
{name: "param-break", params: []uint64{0}, expected: []uint64{api.EncodeI32(-1)}},
|
||||
{name: "param-break", params: []uint64{1}, expected: []uint64{3}},
|
||||
{name: "params-break", params: []uint64{0}, expected: []uint64{api.EncodeI32(-1)}},
|
||||
{name: "params-break", params: []uint64{1}, expected: []uint64{3}},
|
||||
{name: "params-id-break", params: []uint64{0}, expected: []uint64{3}},
|
||||
{name: "params-id-break", params: []uint64{1}, expected: []uint64{3}},
|
||||
{name: "add64_u_with_carry", params: []uint64{0, 0, 0}, expected: []uint64{0, 0}},
|
||||
{name: "add64_u_with_carry", params: []uint64{100, 124, 0}, expected: []uint64{224, 0}},
|
||||
{name: "add64_u_with_carry", params: []uint64{api.EncodeI64(-1), 0, 0}, expected: []uint64{api.EncodeI64(-1), 0}},
|
||||
{name: "add64_u_with_carry", params: []uint64{api.EncodeI64(-1), 1, 0}, expected: []uint64{0, 1}},
|
||||
{name: "add64_u_with_carry", params: []uint64{api.EncodeI64(-1), api.EncodeI64(-1), 0}, expected: []uint64{api.EncodeI64(-2), 1}},
|
||||
{name: "add64_u_with_carry", params: []uint64{api.EncodeI64(-1), 0, 1}, expected: []uint64{0, 1}},
|
||||
{name: "add64_u_with_carry", params: []uint64{api.EncodeI64(-1), 1, 1}, expected: []uint64{1, 1}},
|
||||
{name: "add64_u_with_carry", params: []uint64{0x8000000000000000, 0x8000000000000000, 0}, expected: []uint64{0, 1}},
|
||||
{name: "add64_u_saturated", params: []uint64{0, 0}, expected: []uint64{0}},
|
||||
{name: "add64_u_saturated", params: []uint64{1230, 23}, expected: []uint64{1253}},
|
||||
{name: "add64_u_saturated", params: []uint64{api.EncodeI64(-1), 0}, expected: []uint64{api.EncodeI64(-1)}},
|
||||
{name: "add64_u_saturated", params: []uint64{api.EncodeI64(-1), 1}, expected: []uint64{api.EncodeI64(-1)}},
|
||||
{name: "add64_u_saturated", params: []uint64{api.EncodeI64(-1), api.EncodeI64(-1)}, expected: []uint64{api.EncodeI64(-1)}},
|
||||
{name: "add64_u_saturated", params: []uint64{0x8000000000000000, 0x8000000000000000}, expected: []uint64{api.EncodeI64(-1)}},
|
||||
{name: "type-use"},
|
||||
})
|
||||
}
|
||||
|
||||
// loopWasm was compiled from testdata/loop.wat
|
||||
//go:embed testdata/loop.wasm
|
||||
var loopWasm []byte
|
||||
|
||||
func testLoop(t *testing.T, r wazero.Runtime) {
|
||||
module, err := r.InstantiateModuleFromCode(loopWasm)
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
testFunctions(t, module, []funcTest{
|
||||
{name: "as-binary-operands", expected: []uint64{12}},
|
||||
{name: "as-compare-operands", expected: []uint64{0}},
|
||||
{name: "as-mixed-operands", expected: []uint64{27}},
|
||||
{name: "break-multi-value", expected: []uint64{18, api.EncodeI32(-18), 18}},
|
||||
{name: "param", expected: []uint64{3}},
|
||||
{name: "params", expected: []uint64{3}},
|
||||
{name: "params-id", expected: []uint64{3}},
|
||||
{name: "param-break", expected: []uint64{13}},
|
||||
{name: "params-break", expected: []uint64{12}},
|
||||
{name: "params-id-break", expected: []uint64{3}},
|
||||
{name: "type-use"},
|
||||
})
|
||||
}
|
||||
|
||||
type funcTest struct {
|
||||
name string
|
||||
params []uint64
|
||||
expected []uint64
|
||||
}
|
||||
|
||||
func testFunctions(t *testing.T, module api.Module, tests []funcTest) {
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
results, err := module.ExportedFunction(tc.name).Call(nil, tc.params...)
|
||||
require.NoError(t, err)
|
||||
if tc.expected == nil {
|
||||
require.Empty(t, results)
|
||||
} else {
|
||||
require.Equal(t, tc.expected, results)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
BIN
tests/post1_0/multi-value/testdata/br.wasm
vendored
Normal file
BIN
tests/post1_0/multi-value/testdata/br.wasm
vendored
Normal file
Binary file not shown.
69
tests/post1_0/multi-value/testdata/br.wat
vendored
Normal file
69
tests/post1_0/multi-value/testdata/br.wat
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
;; This file includes changes to test/core/br.wast from the commit that added "multi-value" support.
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names br.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
(module $br.wast
|
||||
|
||||
;; preconditions
|
||||
(func $f (param i32 i32 i32) (result i32) (i32.const -1))
|
||||
(type $sig (func (param i32 i32 i32) (result i32)))
|
||||
(table funcref (elem $f))
|
||||
(memory 1)
|
||||
|
||||
;; changes
|
||||
(func (export "type-i32-i32") (block (drop (i32.add (br 0)))))
|
||||
(func (export "type-i64-i64") (block (drop (i64.add (br 0)))))
|
||||
(func (export "type-f32-f32") (block (drop (f32.add (br 0)))))
|
||||
(func (export "type-f64-f64") (block (drop (f64.add (br 0)))))
|
||||
|
||||
(func (export "type-f64-f64-value") (result f64 f64)
|
||||
(block (result f64 f64)
|
||||
(f64.add (br 0 (f64.const 4) (f64.const 5))) (f64.const 6)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "as-return-values") (result i32 i64)
|
||||
(i32.const 2)
|
||||
(block (result i64) (return (br 0 (i32.const 1) (i64.const 7))))
|
||||
)
|
||||
|
||||
(func (export "as-select-all") (result i32)
|
||||
(block (result i32) (select (br 0 (i32.const 8))))
|
||||
)
|
||||
|
||||
(func (export "as-call-all") (result i32)
|
||||
(block (result i32) (call $f (br 0 (i32.const 15))))
|
||||
)
|
||||
|
||||
(func (export "as-call_indirect-all") (result i32)
|
||||
(block (result i32) (call_indirect (type $sig) (br 0 (i32.const 24))))
|
||||
)
|
||||
|
||||
(func (export "as-store-both") (result i32)
|
||||
(block (result i32)
|
||||
(i64.store (br 0 (i32.const 32))) (i32.const -1)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "as-storeN-both") (result i32)
|
||||
(block (result i32)
|
||||
(i64.store16 (br 0 (i32.const 34))) (i32.const -1)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "as-binary-both") (result i32)
|
||||
(block (result i32) (i32.add (br 0 (i32.const 46))))
|
||||
)
|
||||
|
||||
(func (export "as-compare-both") (result i32)
|
||||
(block (result i32) (f64.le (br 0 (i32.const 44))))
|
||||
)
|
||||
)
|
||||
BIN
tests/post1_0/multi-value/testdata/call.wasm
vendored
Normal file
BIN
tests/post1_0/multi-value/testdata/call.wasm
vendored
Normal file
Binary file not shown.
65
tests/post1_0/multi-value/testdata/call.wat
vendored
Normal file
65
tests/post1_0/multi-value/testdata/call.wat
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
;; This file includes changes to test/core/call.wast from the commit that added "multi-value" support.
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names call.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
(module $call.wast
|
||||
|
||||
(func $const-i32-i64 (result i32 i64) (i32.const 0x132) (i64.const 0x164))
|
||||
|
||||
(func $id-i32-f64 (param i32 f64) (result i32 f64)
|
||||
(local.get 0) (local.get 1)
|
||||
)
|
||||
|
||||
(func $swap-i32-i32 (param i32 i32) (result i32 i32)
|
||||
(local.get 1) (local.get 0)
|
||||
)
|
||||
|
||||
(func $swap-f32-f64 (param f32 f64) (result f64 f32)
|
||||
(local.get 1) (local.get 0)
|
||||
)
|
||||
|
||||
(func $swap-f64-i32 (param f64 i32) (result i32 f64)
|
||||
(local.get 1) (local.get 0)
|
||||
)
|
||||
|
||||
(func (export "type-i32-i64") (result i32 i64) (call $const-i32-i64))
|
||||
|
||||
(func (export "type-all-i32-f64") (result i32 f64)
|
||||
(call $id-i32-f64 (i32.const 32) (f64.const 1.64))
|
||||
)
|
||||
|
||||
(func (export "type-all-i32-i32") (result i32 i32)
|
||||
(call $swap-i32-i32 (i32.const 1) (i32.const 2))
|
||||
)
|
||||
|
||||
(func (export "type-all-f32-f64") (result f64 f32)
|
||||
(call $swap-f32-f64 (f32.const 1) (f64.const 2))
|
||||
)
|
||||
|
||||
(func (export "type-all-f64-i32") (result i32 f64)
|
||||
(call $swap-f64-i32 (f64.const 1) (i32.const 2))
|
||||
)
|
||||
|
||||
(func (export "as-binary-all-operands") (result i32)
|
||||
(i32.add (call $swap-i32-i32 (i32.const 3) (i32.const 4)))
|
||||
)
|
||||
|
||||
(func (export "as-mixed-operands") (result i32)
|
||||
(call $swap-i32-i32 (i32.const 3) (i32.const 4))
|
||||
(i32.const 5)
|
||||
(i32.add)
|
||||
(i32.mul)
|
||||
)
|
||||
|
||||
(func (export "as-call-all-operands") (result i32 i32)
|
||||
(call $swap-i32-i32 (call $swap-i32-i32 (i32.const 3) (i32.const 4)))
|
||||
)
|
||||
)
|
||||
BIN
tests/post1_0/multi-value/testdata/call_indirect.wasm
vendored
Normal file
BIN
tests/post1_0/multi-value/testdata/call_indirect.wasm
vendored
Normal file
Binary file not shown.
260
tests/post1_0/multi-value/testdata/call_indirect.wat
vendored
Normal file
260
tests/post1_0/multi-value/testdata/call_indirect.wat
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
;; This file includes changes to test/core/call_indirect.wast from the commit that added "multi-value" support.
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names call_indirect.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
(module $call_indirect.wast
|
||||
|
||||
;; preconditions
|
||||
(type $proc (func))
|
||||
(type $out-i32 (func (result i32)))
|
||||
(type $out-i64 (func (result i64)))
|
||||
(type $out-f32 (func (result f32)))
|
||||
(type $out-f64 (func (result f64)))
|
||||
(type $f32-i32 (func (param f32 i32) (result i32)))
|
||||
(type $i32-i64 (func (param i32 i64) (result i64)))
|
||||
(type $f64-f32 (func (param f64 f32) (result f32)))
|
||||
(type $i64-f64 (func (param i64 f64) (result f64)))
|
||||
(type $over-i32 (func (param i32) (result i32)))
|
||||
(type $over-i64 (func (param i64) (result i64)))
|
||||
(type $over-f32 (func (param f32) (result f32)))
|
||||
(type $over-f64 (func (param f64) (result f64)))
|
||||
(type $over-i32-duplicate (func (param i32) (result i32)))
|
||||
(type $over-i64-duplicate (func (param i64) (result i64)))
|
||||
(type $over-f32-duplicate (func (param f32) (result f32)))
|
||||
(type $over-f64-duplicate (func (param f64) (result f64)))
|
||||
|
||||
(func $const-i32 (type $out-i32) (i32.const 0x132))
|
||||
(func $const-i64 (type $out-i64) (i64.const 0x164))
|
||||
(func $const-f32 (type $out-f32) (f32.const 0xf32))
|
||||
(func $const-f64 (type $out-f64) (f64.const 0xf64))
|
||||
(func $id-i32 (type $over-i32) (local.get 0))
|
||||
(func $id-i64 (type $over-i64) (local.get 0))
|
||||
(func $id-f32 (type $over-f32) (local.get 0))
|
||||
(func $id-f64 (type $over-f64) (local.get 0))
|
||||
(func $i32-i64 (type $i32-i64) (local.get 1))
|
||||
(func $i64-f64 (type $i64-f64) (local.get 1))
|
||||
(func $f32-i32 (type $f32-i32) (local.get 1))
|
||||
(func $f64-f32 (type $f64-f32) (local.get 1))
|
||||
(func $over-i32-duplicate (type $over-i32-duplicate) (local.get 0))
|
||||
(func $over-i64-duplicate (type $over-i64-duplicate) (local.get 0))
|
||||
(func $over-f32-duplicate (type $over-f32-duplicate) (local.get 0))
|
||||
(func $over-f64-duplicate (type $over-f64-duplicate) (local.get 0))
|
||||
(func (export "dispatch") (param i32 i64) (result i64)
|
||||
(call_indirect (type $over-i64) (local.get 1) (local.get 0))
|
||||
)
|
||||
(func $fac-i64 (export "fac-i64") (type $over-i64)
|
||||
(if (result i64) (i64.eqz (local.get 0))
|
||||
(then (i64.const 1))
|
||||
(else
|
||||
(i64.mul
|
||||
(local.get 0)
|
||||
(call_indirect (type $over-i64)
|
||||
(i64.sub (local.get 0) (i64.const 1))
|
||||
(i32.const 12)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $fib-i64 (export "fib-i64") (type $over-i64)
|
||||
(if (result i64) (i64.le_u (local.get 0) (i64.const 1))
|
||||
(then (i64.const 1))
|
||||
(else
|
||||
(i64.add
|
||||
(call_indirect (type $over-i64)
|
||||
(i64.sub (local.get 0) (i64.const 2))
|
||||
(i32.const 13)
|
||||
)
|
||||
(call_indirect (type $over-i64)
|
||||
(i64.sub (local.get 0) (i64.const 1))
|
||||
(i32.const 13)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $fac-i32 (export "fac-i32") (type $over-i32)
|
||||
(if (result i32) (i32.eqz (local.get 0))
|
||||
(then (i32.const 1))
|
||||
(else
|
||||
(i32.mul
|
||||
(local.get 0)
|
||||
(call_indirect (type $over-i32)
|
||||
(i32.sub (local.get 0) (i32.const 1))
|
||||
(i32.const 23)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $fac-f32 (export "fac-f32") (type $over-f32)
|
||||
(if (result f32) (f32.eq (local.get 0) (f32.const 0.0))
|
||||
(then (f32.const 1.0))
|
||||
(else
|
||||
(f32.mul
|
||||
(local.get 0)
|
||||
(call_indirect (type $over-f32)
|
||||
(f32.sub (local.get 0) (f32.const 1.0))
|
||||
(i32.const 24)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $fac-f64 (export "fac-f64") (type $over-f64)
|
||||
(if (result f64) (f64.eq (local.get 0) (f64.const 0.0))
|
||||
(then (f64.const 1.0))
|
||||
(else
|
||||
(f64.mul
|
||||
(local.get 0)
|
||||
(call_indirect (type $over-f64)
|
||||
(f64.sub (local.get 0) (f64.const 1.0))
|
||||
(i32.const 25)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $fib-i32 (export "fib-i32") (type $over-i32)
|
||||
(if (result i32) (i32.le_u (local.get 0) (i32.const 1))
|
||||
(then (i32.const 1))
|
||||
(else
|
||||
(i32.add
|
||||
(call_indirect (type $over-i32)
|
||||
(i32.sub (local.get 0) (i32.const 2))
|
||||
(i32.const 26)
|
||||
)
|
||||
(call_indirect (type $over-i32)
|
||||
(i32.sub (local.get 0) (i32.const 1))
|
||||
(i32.const 26)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $fib-f32 (export "fib-f32") (type $over-f32)
|
||||
(if (result f32) (f32.le (local.get 0) (f32.const 1.0))
|
||||
(then (f32.const 1.0))
|
||||
(else
|
||||
(f32.add
|
||||
(call_indirect (type $over-f32)
|
||||
(f32.sub (local.get 0) (f32.const 2.0))
|
||||
(i32.const 27)
|
||||
)
|
||||
(call_indirect (type $over-f32)
|
||||
(f32.sub (local.get 0) (f32.const 1.0))
|
||||
(i32.const 27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $fib-f64 (export "fib-f64") (type $over-f64)
|
||||
(if (result f64) (f64.le (local.get 0) (f64.const 1.0))
|
||||
(then (f64.const 1.0))
|
||||
(else
|
||||
(f64.add
|
||||
(call_indirect (type $over-f64)
|
||||
(f64.sub (local.get 0) (f64.const 2.0))
|
||||
(i32.const 28)
|
||||
)
|
||||
(call_indirect (type $over-f64)
|
||||
(f64.sub (local.get 0) (f64.const 1.0))
|
||||
(i32.const 28)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $even (export "even") (param i32) (result i32)
|
||||
(if (result i32) (i32.eqz (local.get 0))
|
||||
(then (i32.const 44))
|
||||
(else
|
||||
(call_indirect (type $over-i32)
|
||||
(i32.sub (local.get 0) (i32.const 1))
|
||||
(i32.const 15)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $odd (export "odd") (param i32) (result i32)
|
||||
(if (result i32) (i32.eqz (local.get 0))
|
||||
(then (i32.const 99))
|
||||
(else
|
||||
(call_indirect (type $over-i32)
|
||||
(i32.sub (local.get 0) (i32.const 1))
|
||||
(i32.const 14)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func $runaway (export "runaway") (call_indirect (type $proc) (i32.const 16)))
|
||||
|
||||
(func $mutual-runaway1 (export "mutual-runaway") (call_indirect (type $proc) (i32.const 18)))
|
||||
|
||||
(func $mutual-runaway2 (call_indirect (type $proc) (i32.const 17)))
|
||||
|
||||
;; changes
|
||||
(type $out-f64-i32 (func (result f64 i32)))
|
||||
(type $over-i32-f64 (func (param i32 f64) (result i32 f64)))
|
||||
(type $swap-i32-i64 (func (param i32 i64) (result i64 i32)))
|
||||
|
||||
(table funcref
|
||||
(elem
|
||||
$const-i32 $const-i64 $const-f32 $const-f64 ;; 0..3
|
||||
$id-i32 $id-i64 $id-f32 $id-f64 ;; 4..7
|
||||
$f32-i32 $i32-i64 $f64-f32 $i64-f64 ;; 9..11
|
||||
$fac-i64 $fib-i64 $even $odd ;; 12..15
|
||||
$runaway $mutual-runaway1 $mutual-runaway2 ;; 16..18
|
||||
$over-i32-duplicate $over-i64-duplicate ;; 19..20
|
||||
$over-f32-duplicate $over-f64-duplicate ;; 21..22
|
||||
$fac-i32 $fac-f32 $fac-f64 ;; 23..25
|
||||
$fib-i32 $fib-f32 $fib-f64 ;; 26..28
|
||||
$const-f64-i32 $id-i32-f64 $swap-i32-i64 ;; 29..31
|
||||
)
|
||||
)
|
||||
|
||||
(func $const-f64-i32 (type $out-f64-i32) (f64.const 0xf64) (i32.const 32))
|
||||
|
||||
(func $id-i32-f64 (type $over-i32-f64) (local.get 0) (local.get 1))
|
||||
|
||||
(func $swap-i32-i64 (type $swap-i32-i64) (local.get 1) (local.get 0))
|
||||
|
||||
(func (export "type-f64-i32") (result f64 i32)
|
||||
(call_indirect (type $out-f64-i32) (i32.const 29))
|
||||
)
|
||||
|
||||
(func (export "type-all-f64-i32") (result f64 i32)
|
||||
(call_indirect (type $out-f64-i32) (i32.const 29))
|
||||
)
|
||||
|
||||
(func (export "type-all-i32-f64") (result i32 f64)
|
||||
(call_indirect (type $over-i32-f64)
|
||||
(i32.const 1) (f64.const 2) (i32.const 30)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "type-all-i32-i64") (result i64 i32)
|
||||
(call_indirect (type $swap-i32-i64)
|
||||
(i32.const 1) (i64.const 2) (i32.const 31)
|
||||
)
|
||||
)
|
||||
)
|
||||
BIN
tests/post1_0/multi-value/testdata/fac.wasm
vendored
Normal file
BIN
tests/post1_0/multi-value/testdata/fac.wasm
vendored
Normal file
Binary file not shown.
30
tests/post1_0/multi-value/testdata/fac.wat
vendored
Normal file
30
tests/post1_0/multi-value/testdata/fac.wat
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
;; This file includes changes to test/core/fac.wast from the commit that added "multi-value" support.
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names fac.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
(module $fac.wast
|
||||
(func $pick0 (param i64) (result i64 i64)
|
||||
(local.get 0) (local.get 0)
|
||||
)
|
||||
(func $pick1 (param i64 i64) (result i64 i64 i64)
|
||||
(local.get 0) (local.get 1) (local.get 0)
|
||||
)
|
||||
(func (export "fac-ssa") (param i64) (result i64)
|
||||
(i64.const 1) (local.get 0)
|
||||
(loop $l (param i64 i64) (result i64)
|
||||
(call $pick1) (call $pick1) (i64.mul)
|
||||
(call $pick1) (i64.const 1) (i64.sub)
|
||||
(call $pick0) (i64.const 0) (i64.gt_u)
|
||||
(br_if $l)
|
||||
(drop) (return)
|
||||
)
|
||||
)
|
||||
)
|
||||
BIN
tests/post1_0/multi-value/testdata/func.wasm
vendored
Normal file
BIN
tests/post1_0/multi-value/testdata/func.wasm
vendored
Normal file
Binary file not shown.
104
tests/post1_0/multi-value/testdata/func.wat
vendored
Normal file
104
tests/post1_0/multi-value/testdata/func.wat
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
;; This file includes changes to test/core/func.wast from the commit that added "multi-value" support.
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names func.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
(module $func.wast
|
||||
;; preconditions
|
||||
(func $dummy)
|
||||
|
||||
;; changes
|
||||
(func (result))
|
||||
(func (result) (result))
|
||||
(func (result i32 f64 f32) (unreachable))
|
||||
(func (result i32) (result f64) (unreachable))
|
||||
(func (result i32 f32) (result i64) (result) (result i32 f64) (unreachable))
|
||||
|
||||
(func $complex
|
||||
(param i32 f32) (param $x i64) (param) (param i32)
|
||||
(result) (result i32) (result) (result i64 i32)
|
||||
(local f32) (local $y i32) (local i64 i32) (local) (local f64 i32)
|
||||
(unreachable) (unreachable)
|
||||
)
|
||||
|
||||
(func (export "value-i32-f64") (result i32 f64) (i32.const 77) (f64.const 7))
|
||||
(func (export "value-i32-i32-i32") (result i32 i32 i32)
|
||||
(i32.const 1) (i32.const 2) (i32.const 3)
|
||||
)
|
||||
|
||||
(func (export "value-block-i32-i64") (result i32 i64)
|
||||
(block (result i32 i64) (call $dummy) (i32.const 1) (i64.const 2))
|
||||
)
|
||||
|
||||
(func (export "return-i32-f64") (result i32 f64)
|
||||
(return (i32.const 78) (f64.const 78.78))
|
||||
)
|
||||
|
||||
(func (export "return-i32-i32-i32") (result i32 i32 i32)
|
||||
(return (i32.const 1) (i32.const 2) (i32.const 3))
|
||||
)
|
||||
|
||||
(func (export "return-block-i32-i64") (result i32 i64)
|
||||
(return (block (result i32 i64) (call $dummy) (i32.const 1) (i64.const 2)))
|
||||
)
|
||||
|
||||
(func (export "break-i32-f64") (result i32 f64)
|
||||
(br 0 (i32.const 79) (f64.const 79.79))
|
||||
)
|
||||
|
||||
(func (export "break-i32-i32-i32") (result i32 i32 i32)
|
||||
(br 0 (i32.const 1) (i32.const 2) (i32.const 3))
|
||||
)
|
||||
|
||||
(func (export "break-block-i32-i64") (result i32 i64)
|
||||
(br 0 (block (result i32 i64) (call $dummy) (i32.const 1) (i64.const 2)))
|
||||
)
|
||||
|
||||
(func (export "break-br_if-num-num") (param i32) (result i32 i64)
|
||||
(drop (drop (br_if 0 (i32.const 50) (i64.const 51) (local.get 0))))
|
||||
(i32.const 51) (i64.const 52)
|
||||
)
|
||||
|
||||
(func (export "break-br_table-num-num") (param i32) (result i32 i64)
|
||||
(br_table 0 0 (i32.const 50) (i64.const 51) (local.get 0))
|
||||
(i32.const 51) (i64.const 52)
|
||||
)
|
||||
|
||||
(func (export "break-br_table-nested-num-num") (param i32) (result i32 i32)
|
||||
(i32.add
|
||||
(block (result i32 i32)
|
||||
(br_table 0 1 0 (i32.const 50) (i32.const 51) (local.get 0))
|
||||
(i32.const 51) (i32.const -3)
|
||||
)
|
||||
)
|
||||
(i32.const 52)
|
||||
)
|
||||
|
||||
(func (export "large-sig")
|
||||
(param i32 i64 f32 f32 i32 f64 f32 i32 i32 i32 f32 f64 f64 f64 i32 i32 f32)
|
||||
(result f64 f32 i32 i32 i32 i64 f32 i32 i32 f32 f64 f64 i32 f32 i32 f64)
|
||||
(local.get 5)
|
||||
(local.get 2)
|
||||
(local.get 0)
|
||||
(local.get 8)
|
||||
(local.get 7)
|
||||
(local.get 1)
|
||||
(local.get 3)
|
||||
(local.get 9)
|
||||
(local.get 4)
|
||||
(local.get 6)
|
||||
(local.get 13)
|
||||
(local.get 11)
|
||||
(local.get 15)
|
||||
(local.get 16)
|
||||
(local.get 14)
|
||||
(local.get 12)
|
||||
)
|
||||
)
|
||||
BIN
tests/post1_0/multi-value/testdata/if.wasm
vendored
Normal file
BIN
tests/post1_0/multi-value/testdata/if.wasm
vendored
Normal file
Binary file not shown.
176
tests/post1_0/multi-value/testdata/if.wat
vendored
Normal file
176
tests/post1_0/multi-value/testdata/if.wat
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
;; This file includes changes to test/core/if.wast from the commit that added "multi-value" support.
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names if.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
(module $if.wast
|
||||
;; preconditions
|
||||
(func $dummy)
|
||||
|
||||
;; changes
|
||||
(func (export "multi") (param i32) (result i32 i32)
|
||||
(if (local.get 0) (then (call $dummy) (call $dummy) (call $dummy)))
|
||||
(if (local.get 0) (then) (else (call $dummy) (call $dummy) (call $dummy)))
|
||||
(if (result i32) (local.get 0)
|
||||
(then (call $dummy) (call $dummy) (i32.const 8) (call $dummy))
|
||||
(else (call $dummy) (call $dummy) (i32.const 9) (call $dummy))
|
||||
)
|
||||
(if (result i32 i64 i32) (local.get 0)
|
||||
(then
|
||||
(call $dummy) (call $dummy) (i32.const 1) (call $dummy)
|
||||
(call $dummy) (call $dummy) (i64.const 2) (call $dummy)
|
||||
(call $dummy) (call $dummy) (i32.const 3) (call $dummy)
|
||||
)
|
||||
(else
|
||||
(call $dummy) (call $dummy) (i32.const -1) (call $dummy)
|
||||
(call $dummy) (call $dummy) (i64.const -2) (call $dummy)
|
||||
(call $dummy) (call $dummy) (i32.const -3) (call $dummy)
|
||||
)
|
||||
)
|
||||
(drop) (drop)
|
||||
)
|
||||
|
||||
(func (export "as-binary-operands") (param i32) (result i32)
|
||||
(i32.mul
|
||||
(if (result i32 i32) (local.get 0)
|
||||
(then (call $dummy) (i32.const 3) (call $dummy) (i32.const 4))
|
||||
(else (call $dummy) (i32.const 3) (call $dummy) (i32.const -4))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "as-compare-operands") (param i32) (result i32)
|
||||
(f32.gt
|
||||
(if (result f32 f32) (local.get 0)
|
||||
(then (call $dummy) (f32.const 3) (call $dummy) (f32.const 3))
|
||||
(else (call $dummy) (f32.const -2) (call $dummy) (f32.const -3))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "as-mixed-operands") (param i32) (result i32)
|
||||
(if (result i32 i32) (local.get 0)
|
||||
(then (call $dummy) (i32.const 3) (call $dummy) (i32.const 4))
|
||||
(else (call $dummy) (i32.const -3) (call $dummy) (i32.const -4))
|
||||
)
|
||||
(i32.const 5)
|
||||
(i32.add)
|
||||
(i32.mul)
|
||||
)
|
||||
|
||||
(func (export "break-multi-value") (param i32) (result i32 i32 i64)
|
||||
(if (result i32 i32 i64) (local.get 0)
|
||||
(then
|
||||
(br 0 (i32.const 18) (i32.const -18) (i64.const 18))
|
||||
(i32.const 19) (i32.const -19) (i64.const 19)
|
||||
)
|
||||
(else
|
||||
(br 0 (i32.const -18) (i32.const 18) (i64.const -18))
|
||||
(i32.const -19) (i32.const 19) (i64.const -19)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "param") (param i32) (result i32)
|
||||
(i32.const 1)
|
||||
(if (param i32) (result i32) (local.get 0)
|
||||
(then (i32.const 2) (i32.add))
|
||||
(else (i32.const -2) (i32.add))
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "params") (param i32) (result i32)
|
||||
(i32.const 1)
|
||||
(i32.const 2)
|
||||
(if (param i32 i32) (result i32) (local.get 0)
|
||||
(then (i32.add))
|
||||
(else (i32.sub))
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "params-id") (param i32) (result i32)
|
||||
(i32.const 1)
|
||||
(i32.const 2)
|
||||
(if (param i32 i32) (result i32 i32) (local.get 0) (then))
|
||||
(i32.add)
|
||||
)
|
||||
|
||||
(func (export "param-break") (param i32) (result i32)
|
||||
(i32.const 1)
|
||||
(if (param i32) (result i32) (local.get 0)
|
||||
(then (i32.const 2) (i32.add) (br 0))
|
||||
(else (i32.const -2) (i32.add) (br 0))
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "params-break") (param i32) (result i32)
|
||||
(i32.const 1)
|
||||
(i32.const 2)
|
||||
(if (param i32 i32) (result i32) (local.get 0)
|
||||
(then (i32.add) (br 0))
|
||||
(else (i32.sub) (br 0))
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "params-id-break") (param i32) (result i32)
|
||||
(i32.const 1)
|
||||
(i32.const 2)
|
||||
(if (param i32 i32) (result i32 i32) (local.get 0) (then (br 0)))
|
||||
(i32.add)
|
||||
)
|
||||
|
||||
(func $add64_u_with_carry (export "add64_u_with_carry")
|
||||
(param $i i64) (param $j i64) (param $c i32) (result i64 i32)
|
||||
(local $k i64)
|
||||
(local.set $k
|
||||
(i64.add
|
||||
(i64.add (local.get $i) (local.get $j))
|
||||
(i64.extend_i32_u (local.get $c))
|
||||
)
|
||||
)
|
||||
(return (local.get $k) (i64.lt_u (local.get $k) (local.get $i)))
|
||||
)
|
||||
|
||||
(func $add64_u_saturated (export "add64_u_saturated")
|
||||
(param i64 i64) (result i64)
|
||||
(call $add64_u_with_carry (local.get 0) (local.get 1) (i32.const 0))
|
||||
(if (param i64) (result i64)
|
||||
(then (drop) (i64.const -1))
|
||||
)
|
||||
)
|
||||
|
||||
(type $block-sig-1 (func))
|
||||
(type $block-sig-2 (func (result i32)))
|
||||
(type $block-sig-3 (func (param $x i32)))
|
||||
(type $block-sig-4 (func (param i32 f64 i32) (result i32 f64 i32)))
|
||||
|
||||
(func (export "type-use")
|
||||
(if (type $block-sig-1) (i32.const 1) (then))
|
||||
(if (type $block-sig-2) (i32.const 1)
|
||||
(then (i32.const 0)) (else (i32.const 2))
|
||||
)
|
||||
(if (type $block-sig-3) (i32.const 1) (then (drop)) (else (drop)))
|
||||
(i32.const 0) (f64.const 0) (i32.const 0)
|
||||
(if (type $block-sig-4) (i32.const 1) (then))
|
||||
(drop) (drop) (drop)
|
||||
(if (type $block-sig-2) (result i32) (i32.const 1)
|
||||
(then (i32.const 0)) (else (i32.const 2))
|
||||
)
|
||||
(if (type $block-sig-3) (param i32) (i32.const 1)
|
||||
(then (drop)) (else (drop))
|
||||
)
|
||||
(i32.const 0) (f64.const 0) (i32.const 0)
|
||||
(if (type $block-sig-4)
|
||||
(param i32) (param f64 i32) (result i32 f64) (result i32)
|
||||
(i32.const 1) (then)
|
||||
)
|
||||
(drop) (drop) (drop)
|
||||
)
|
||||
)
|
||||
BIN
tests/post1_0/multi-value/testdata/loop.wasm
vendored
Normal file
BIN
tests/post1_0/multi-value/testdata/loop.wasm
vendored
Normal file
Binary file not shown.
163
tests/post1_0/multi-value/testdata/loop.wat
vendored
Normal file
163
tests/post1_0/multi-value/testdata/loop.wat
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
;; This file includes changes to test/core/loop.wast from the commit that added "multi-value" support.
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names loop.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
|
||||
(module $loop.wast
|
||||
|
||||
;; preconditions
|
||||
(func $dummy)
|
||||
|
||||
;; changes
|
||||
(func (export "multi") (result i32)
|
||||
(loop (call $dummy) (call $dummy) (call $dummy) (call $dummy))
|
||||
(loop (result i32) (call $dummy) (call $dummy) (i32.const 8) (call $dummy))
|
||||
(drop)
|
||||
(loop (result i32 i64 i32)
|
||||
(call $dummy) (call $dummy) (call $dummy) (i32.const 8) (call $dummy)
|
||||
(call $dummy) (call $dummy) (call $dummy) (i64.const 7) (call $dummy)
|
||||
(call $dummy) (call $dummy) (call $dummy) (i32.const 9) (call $dummy)
|
||||
)
|
||||
(drop) (drop)
|
||||
)
|
||||
|
||||
(func (export "as-binary-operands") (result i32)
|
||||
(i32.mul
|
||||
(loop (result i32 i32)
|
||||
(call $dummy) (i32.const 3) (call $dummy) (i32.const 4)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "as-compare-operands") (result i32)
|
||||
(f32.gt
|
||||
(loop (result f32 f32)
|
||||
(call $dummy) (f32.const 3) (call $dummy) (f32.const 3)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "as-mixed-operands") (result i32)
|
||||
(loop (result i32 i32)
|
||||
(call $dummy) (i32.const 3) (call $dummy) (i32.const 4)
|
||||
)
|
||||
(i32.const 5)
|
||||
(i32.add)
|
||||
(i32.mul)
|
||||
)
|
||||
|
||||
(func (export "break-value") (result i32)
|
||||
(block (result i32)
|
||||
(i32.const 0)
|
||||
(loop (param i32)
|
||||
(block (br 2 (i32.const 18)))
|
||||
(br 0 (i32.const 20))
|
||||
)
|
||||
(i32.const 19)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "break-multi-value") (result i32 i32 i64)
|
||||
(block (result i32 i32 i64)
|
||||
(i32.const 0) (i32.const 0) (i64.const 0)
|
||||
(loop (param i32 i32 i64)
|
||||
(block (br 2 (i32.const 18) (i32.const -18) (i64.const 18)))
|
||||
(br 0 (i32.const 20) (i32.const -20) (i64.const 20))
|
||||
)
|
||||
(i32.const 19) (i32.const -19) (i64.const 19)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "param") (result i32)
|
||||
(i32.const 1)
|
||||
(loop (param i32) (result i32)
|
||||
(i32.const 2)
|
||||
(i32.add)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "params") (result i32)
|
||||
(i32.const 1)
|
||||
(i32.const 2)
|
||||
(loop (param i32 i32) (result i32)
|
||||
(i32.add)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "params-id") (result i32)
|
||||
(i32.const 1)
|
||||
(i32.const 2)
|
||||
(loop (param i32 i32) (result i32 i32))
|
||||
(i32.add)
|
||||
)
|
||||
|
||||
(func (export "param-break") (result i32)
|
||||
(local $x i32)
|
||||
(i32.const 1)
|
||||
(loop (param i32) (result i32)
|
||||
(i32.const 4)
|
||||
(i32.add)
|
||||
(local.tee $x)
|
||||
(local.get $x)
|
||||
(i32.const 10)
|
||||
(i32.lt_u)
|
||||
(br_if 0)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "params-break") (result i32)
|
||||
(local $x i32)
|
||||
(i32.const 1)
|
||||
(i32.const 2)
|
||||
(loop (param i32 i32) (result i32)
|
||||
(i32.add)
|
||||
(local.tee $x)
|
||||
(i32.const 3)
|
||||
(local.get $x)
|
||||
(i32.const 10)
|
||||
(i32.lt_u)
|
||||
(br_if 0)
|
||||
(drop)
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "params-id-break") (result i32)
|
||||
(local $x i32)
|
||||
(local.set $x (i32.const 0))
|
||||
(i32.const 1)
|
||||
(i32.const 2)
|
||||
(loop (param i32 i32) (result i32 i32)
|
||||
(local.set $x (i32.add (local.get $x) (i32.const 1)))
|
||||
(br_if 0 (i32.lt_u (local.get $x) (i32.const 10)))
|
||||
)
|
||||
(i32.add)
|
||||
)
|
||||
|
||||
(type $block-sig-1 (func))
|
||||
(type $block-sig-2 (func (result i32)))
|
||||
(type $block-sig-3 (func (param $x i32)))
|
||||
(type $block-sig-4 (func (param i32 f64 i32) (result i32 f64 i32)))
|
||||
|
||||
(func (export "type-use")
|
||||
(loop (type $block-sig-1))
|
||||
(loop (type $block-sig-2) (i32.const 0))
|
||||
(loop (type $block-sig-3) (drop))
|
||||
(i32.const 0) (f64.const 0) (i32.const 0)
|
||||
(loop (type $block-sig-4))
|
||||
(drop) (drop) (drop)
|
||||
(loop (type $block-sig-2) (result i32) (i32.const 0))
|
||||
(loop (type $block-sig-3) (param i32) (drop))
|
||||
(i32.const 0) (f64.const 0) (i32.const 0)
|
||||
(loop (type $block-sig-4)
|
||||
(param i32) (param f64 i32) (result i32 f64) (result i32)
|
||||
)
|
||||
(drop) (drop) (drop)
|
||||
)
|
||||
)
|
||||
BIN
tests/post1_0/multi-value/testdata/multi_value.wasm
vendored
Normal file
BIN
tests/post1_0/multi-value/testdata/multi_value.wasm
vendored
Normal file
Binary file not shown.
58
tests/post1_0/multi-value/testdata/multi_value.wat
vendored
Normal file
58
tests/post1_0/multi-value/testdata/multi_value.wat
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
;; multiValue is a WebAssembly 1.0 (20191205) Text Format source, plus the "multi-value" feature.
|
||||
;; * allows multiple return values from `func`, `block`, `loop` and `if`
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names multi_value.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
(module $multi-value
|
||||
(func $swap (param i32 i32) (result i32 i32)
|
||||
(local.get 1) (local.get 0)
|
||||
)
|
||||
(export "swap" (func $swap))
|
||||
|
||||
(func $add64_u_with_carry (param $i i64) (param $j i64) (param $c i32) (result i64 i32)
|
||||
(local $k i64)
|
||||
(local.set $k
|
||||
(i64.add (i64.add (local.get $i) (local.get $j)) (i64.extend_i32_u (local.get $c)))
|
||||
)
|
||||
(return (local.get $k) (i64.lt_u (local.get $k) (local.get $i)))
|
||||
)
|
||||
(export "add64_u_with_carry" (func $add64_u_with_carry))
|
||||
|
||||
(func $add64_u_saturated (param i64 i64) (result i64)
|
||||
(call $add64_u_with_carry (local.get 0) (local.get 1) (i32.const 0))
|
||||
(if (param i64) (result i64)
|
||||
(then (drop) (i64.const 0xffff_ffff_ffff_ffff))
|
||||
)
|
||||
)
|
||||
(export "add64_u_saturated" (func $add64_u_saturated))
|
||||
|
||||
(func $pick0 (param i64) (result i64 i64)
|
||||
(local.get 0) (local.get 0)
|
||||
)
|
||||
|
||||
(func $pick1 (param i64 i64) (result i64 i64 i64)
|
||||
(local.get 0) (local.get 1) (local.get 0)
|
||||
)
|
||||
|
||||
;; Note: This implementation loops forever if the input is zero.
|
||||
(func $fac (param i64) (result i64)
|
||||
(i64.const 1) (local.get 0)
|
||||
|
||||
(loop $l (param i64 i64) (result i64)
|
||||
(call $pick1) (call $pick1) (i64.mul)
|
||||
(call $pick1) (i64.const 1) (i64.sub)
|
||||
(call $pick0) (i64.const 0) (i64.gt_u)
|
||||
(br_if $l)
|
||||
(drop) (return)
|
||||
)
|
||||
)
|
||||
(export "fac" (func $fac))
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package post1_0
|
||||
package sign_extension_ops
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
@@ -10,25 +10,15 @@ import (
|
||||
"github.com/tetratelabs/wazero"
|
||||
)
|
||||
|
||||
func TestJIT(t *testing.T) {
|
||||
func TestSignExtensionOps_JIT(t *testing.T) {
|
||||
if !wazero.JITSupported {
|
||||
t.Skip()
|
||||
}
|
||||
runOptionalFeatureTests(t, wazero.NewRuntimeConfigJIT)
|
||||
testSignExtensionOps(t, wazero.NewRuntimeConfigJIT)
|
||||
}
|
||||
|
||||
func TestInterpreter(t *testing.T) {
|
||||
runOptionalFeatureTests(t, wazero.NewRuntimeConfigInterpreter)
|
||||
}
|
||||
|
||||
// runOptionalFeatureTests tests features enabled by feature flags (wasm.Features) as they were unfinished when
|
||||
// WebAssembly 1.0 (20191205) was released.
|
||||
//
|
||||
// See https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md
|
||||
func runOptionalFeatureTests(t *testing.T, newRuntimeConfig func() *wazero.RuntimeConfig) {
|
||||
t.Run("sign-extension-ops", func(t *testing.T) {
|
||||
testSignExtensionOps(t, newRuntimeConfig)
|
||||
})
|
||||
func TestSignExtensionOps_Interpreter(t *testing.T) {
|
||||
testSignExtensionOps(t, wazero.NewRuntimeConfigInterpreter)
|
||||
}
|
||||
|
||||
// signExtend is a WebAssembly 1.0 (20191205) Text Format source, except that it uses opcodes from 'sign-extension-ops'.
|
||||
@@ -283,7 +283,7 @@ func TestInterpreter(t *testing.T) {
|
||||
runTest(t, interpreter.NewEngine)
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, newEngine func() wasm.Engine) {
|
||||
func runTest(t *testing.T, newEngine func(wasm.Features) wasm.Engine) {
|
||||
files, err := testcases.ReadDir("testdata")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -309,7 +309,8 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
|
||||
wastName := basename(base.SourceFile)
|
||||
|
||||
t.Run(wastName, func(t *testing.T) {
|
||||
store := wasm.NewStore(newEngine(), wasm.Features20191205)
|
||||
enabledFeatures := wasm.Features20191205
|
||||
store := wasm.NewStore(enabledFeatures, newEngine(enabledFeatures))
|
||||
addSpectestModule(t, store)
|
||||
|
||||
var lastInstantiatedModuleName string
|
||||
@@ -320,9 +321,9 @@ func runTest(t *testing.T, newEngine func() wasm.Engine) {
|
||||
case "module":
|
||||
buf, err := testcases.ReadFile(testdataPath(c.Filename))
|
||||
require.NoError(t, err, msg)
|
||||
mod, err := binary.DecodeModule(buf, wasm.Features20191205, wasm.MemoryMaxPages)
|
||||
mod, err := binary.DecodeModule(buf, enabledFeatures, wasm.MemoryMaxPages)
|
||||
require.NoError(t, err, msg)
|
||||
require.NoError(t, mod.Validate(wasm.Features20191205))
|
||||
require.NoError(t, mod.Validate(enabledFeatures))
|
||||
|
||||
moduleName := c.Name
|
||||
if moduleName == "" { // When "(module ...) directive doesn't have name.
|
||||
|
||||
@@ -27,13 +27,13 @@ var ensureJITFastest = "false"
|
||||
//go:embed testdata/fac.wasm
|
||||
var facWasm []byte
|
||||
|
||||
// TestFacIter ensures that the code in BenchmarkFacIter works as expected.
|
||||
func TestFacIter(t *testing.T) {
|
||||
// TestFac ensures that the code in BenchmarkFac works as expected.
|
||||
func TestFac(t *testing.T) {
|
||||
const in = 30
|
||||
expValue := uint64(0x865df5dd54000000)
|
||||
|
||||
t.Run("Interpreter", func(t *testing.T) {
|
||||
mod, fn, err := newWazeroFacIterBench(wazero.NewRuntimeConfigInterpreter())
|
||||
mod, fn, err := newWazeroFacBench(wazero.NewRuntimeConfigInterpreter().WithFinishedFeatures())
|
||||
require.NoError(t, err)
|
||||
defer mod.Close()
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestFacIter(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("JIT", func(t *testing.T) {
|
||||
mod, fn, err := newWazeroFacIterBench(wazero.NewRuntimeConfigJIT())
|
||||
mod, fn, err := newWazeroFacBench(wazero.NewRuntimeConfigJIT().WithFinishedFeatures())
|
||||
require.NoError(t, err)
|
||||
defer mod.Close()
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestFacIter(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("wasmer-go", func(t *testing.T) {
|
||||
store, instance, fn, err := newWasmerForFacIterBench()
|
||||
store, instance, fn, err := newWasmerForFacBench()
|
||||
require.NoError(t, err)
|
||||
defer store.Close()
|
||||
defer instance.Close()
|
||||
@@ -70,7 +70,7 @@ func TestFacIter(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("wasmtime-go", func(t *testing.T) {
|
||||
store, run, err := newWasmtimeForFacIterBench()
|
||||
store, run, err := newWasmtimeForFacBench()
|
||||
require.NoError(t, err)
|
||||
for i := 0; i < 10000; i++ {
|
||||
res, err := run.Call(store, in)
|
||||
@@ -82,7 +82,7 @@ func TestFacIter(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("go-wasm3", func(t *testing.T) {
|
||||
env, runtime, run, err := newGoWasm3ForFacIterBench()
|
||||
env, runtime, run, err := newGoWasm3ForFacBench()
|
||||
require.NoError(t, err)
|
||||
defer env.Destroy()
|
||||
defer runtime.Destroy()
|
||||
@@ -97,12 +97,12 @@ func TestFacIter(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkFacIter_Init tracks the time spent readying a function for use
|
||||
func BenchmarkFacIter_Init(b *testing.B) {
|
||||
// BenchmarkFac_Init tracks the time spent readying a function for use
|
||||
func BenchmarkFac_Init(b *testing.B) {
|
||||
b.Run("Interpreter", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod, _, err := newWazeroFacIterBench(wazero.NewRuntimeConfigInterpreter())
|
||||
mod, _, err := newWazeroFacBench(wazero.NewRuntimeConfigInterpreter().WithFinishedFeatures())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func BenchmarkFacIter_Init(b *testing.B) {
|
||||
b.Run("JIT", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod, _, err := newWazeroFacIterBench(wazero.NewRuntimeConfigJIT())
|
||||
mod, _, err := newWazeroFacBench(wazero.NewRuntimeConfigJIT().WithFinishedFeatures())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -124,7 +124,7 @@ func BenchmarkFacIter_Init(b *testing.B) {
|
||||
b.Run("wasmer-go", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
store, instance, _, err := newWasmerForFacIterBench()
|
||||
store, instance, _, err := newWasmerForFacBench()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -136,7 +136,7 @@ func BenchmarkFacIter_Init(b *testing.B) {
|
||||
b.Run("wasmtime-go", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, _, err := newWasmtimeForFacIterBench(); err != nil {
|
||||
if _, _, err := newWasmtimeForFacBench(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ func BenchmarkFacIter_Init(b *testing.B) {
|
||||
b.Run("go-wasm3", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
env, runtime, _, err := newGoWasm3ForFacIterBench()
|
||||
env, runtime, _, err := newGoWasm3ForFacBench()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -155,17 +155,17 @@ func BenchmarkFacIter_Init(b *testing.B) {
|
||||
})
|
||||
}
|
||||
|
||||
var facIterArgumentU64 = uint64(30)
|
||||
var facIterArgumentI64 = int64(facIterArgumentU64)
|
||||
var facArgumentU64 = uint64(30)
|
||||
var facArgumentI64 = int64(facArgumentU64)
|
||||
|
||||
// TestFacIter_JIT_Fastest ensures that JIT is the fastest engine for function invocations.
|
||||
// TestFac_JIT_Fastest ensures that JIT is the fastest engine for function invocations.
|
||||
// This is disabled by default, and can be run with -ldflags '-X github.com/tetratelabs/wazero/vs.ensureJITFastest=true'.
|
||||
func TestFacIter_JIT_Fastest(t *testing.T) {
|
||||
func TestFac_JIT_Fastest(t *testing.T) {
|
||||
if ensureJITFastest != "true" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
jitResult := testing.Benchmark(jitFacIterInvoke)
|
||||
jitResult := testing.Benchmark(jitFacInvoke)
|
||||
|
||||
cases := []struct {
|
||||
runtimeName string
|
||||
@@ -173,19 +173,19 @@ func TestFacIter_JIT_Fastest(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
runtimeName: "interpreter",
|
||||
result: testing.Benchmark(interpreterFacIterInvoke),
|
||||
result: testing.Benchmark(interpreterFacInvoke),
|
||||
},
|
||||
{
|
||||
runtimeName: "wasmer-go",
|
||||
result: testing.Benchmark(wasmerGoFacIterInvoke),
|
||||
result: testing.Benchmark(wasmerGoFacInvoke),
|
||||
},
|
||||
{
|
||||
runtimeName: "wasmtime-go",
|
||||
result: testing.Benchmark(wasmtimeGoFacIterInvoke),
|
||||
result: testing.Benchmark(wasmtimeGoFacInvoke),
|
||||
},
|
||||
{
|
||||
runtimeName: "go-wasm3",
|
||||
result: testing.Benchmark(goWasm3FacIterInvoke),
|
||||
result: testing.Benchmark(goWasm3FacInvoke),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -202,57 +202,57 @@ func TestFacIter_JIT_Fastest(t *testing.T) {
|
||||
// https://github.com/golang/go/blob/fd09e88722e0af150bf8960e95e8da500ad91001/src/testing/benchmark.go#L428-L432
|
||||
nanoPerOp := float64(tc.result.T.Nanoseconds()) / float64(tc.result.N)
|
||||
msg := fmt.Sprintf("JIT engine must be faster than %s. "+
|
||||
"Run BenchmarkFacIter_Invoke with ensureJITFastest=false instead to see the detailed result",
|
||||
"Run BenchmarkFac_Invoke with ensureJITFastest=false instead to see the detailed result",
|
||||
tc.runtimeName)
|
||||
require.Lessf(t, jitNanoPerOp, nanoPerOp, msg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkFacIter_Invoke benchmarks the time spent invoking a factorial calculation.
|
||||
func BenchmarkFacIter_Invoke(b *testing.B) {
|
||||
// BenchmarkFac_Invoke benchmarks the time spent invoking a factorial calculation.
|
||||
func BenchmarkFac_Invoke(b *testing.B) {
|
||||
if ensureJITFastest == "true" {
|
||||
// If ensureJITFastest == "true", the benchmark for invocation will be run by
|
||||
// TestFacIter_JIT_Fastest so skip here.
|
||||
// TestFac_JIT_Fastest so skip here.
|
||||
b.Skip()
|
||||
}
|
||||
b.Run("Interpreter", interpreterFacIterInvoke)
|
||||
b.Run("JIT", jitFacIterInvoke)
|
||||
b.Run("wasmer-go", wasmerGoFacIterInvoke)
|
||||
b.Run("wasmtime-go", wasmtimeGoFacIterInvoke)
|
||||
b.Run("go-wasm3", goWasm3FacIterInvoke)
|
||||
b.Run("Interpreter", interpreterFacInvoke)
|
||||
b.Run("JIT", jitFacInvoke)
|
||||
b.Run("wasmer-go", wasmerGoFacInvoke)
|
||||
b.Run("wasmtime-go", wasmtimeGoFacInvoke)
|
||||
b.Run("go-wasm3", goWasm3FacInvoke)
|
||||
}
|
||||
|
||||
func interpreterFacIterInvoke(b *testing.B) {
|
||||
mod, fn, err := newWazeroFacIterBench(wazero.NewRuntimeConfigInterpreter())
|
||||
func interpreterFacInvoke(b *testing.B) {
|
||||
mod, fn, err := newWazeroFacBench(wazero.NewRuntimeConfigInterpreter().WithFinishedFeatures())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer mod.Close()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = fn.Call(nil, facIterArgumentU64); err != nil {
|
||||
if _, err = fn.Call(nil, facArgumentU64); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func jitFacIterInvoke(b *testing.B) {
|
||||
mod, fn, err := newWazeroFacIterBench(wazero.NewRuntimeConfigJIT())
|
||||
func jitFacInvoke(b *testing.B) {
|
||||
mod, fn, err := newWazeroFacBench(wazero.NewRuntimeConfigJIT().WithFinishedFeatures())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer mod.Close()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = fn.Call(nil, facIterArgumentU64); err != nil {
|
||||
if _, err = fn.Call(nil, facArgumentU64); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func wasmerGoFacIterInvoke(b *testing.B) {
|
||||
store, instance, fn, err := newWasmerForFacIterBench()
|
||||
func wasmerGoFacInvoke(b *testing.B) {
|
||||
store, instance, fn, err := newWasmerForFacBench()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -260,35 +260,35 @@ func wasmerGoFacIterInvoke(b *testing.B) {
|
||||
defer instance.Close()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = fn(facIterArgumentI64); err != nil {
|
||||
if _, err = fn(facArgumentI64); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func wasmtimeGoFacIterInvoke(b *testing.B) {
|
||||
store, run, err := newWasmtimeForFacIterBench()
|
||||
func wasmtimeGoFacInvoke(b *testing.B) {
|
||||
store, run, err := newWasmtimeForFacBench()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// go-wasm3 only maps the int type
|
||||
if _, err = run.Call(store, int(facIterArgumentI64)); err != nil {
|
||||
if _, err = run.Call(store, int(facArgumentI64)); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func goWasm3FacIterInvoke(b *testing.B) {
|
||||
env, runtime, run, err := newGoWasm3ForFacIterBench()
|
||||
func goWasm3FacInvoke(b *testing.B) {
|
||||
env, runtime, run, err := newGoWasm3ForFacBench()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// go-wasm3 only maps the int type
|
||||
if _, err = run(int(facIterArgumentI64)); err != nil {
|
||||
if _, err = run(int(facArgumentI64)); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ func goWasm3FacIterInvoke(b *testing.B) {
|
||||
env.Destroy()
|
||||
}
|
||||
|
||||
func newWazeroFacIterBench(config *wazero.RuntimeConfig) (api.Module, api.Function, error) {
|
||||
func newWazeroFacBench(config *wazero.RuntimeConfig) (api.Module, api.Function, error) {
|
||||
r := wazero.NewRuntimeWithConfig(config)
|
||||
|
||||
m, err := r.InstantiateModuleFromCode(facWasm)
|
||||
@@ -304,12 +304,12 @@ func newWazeroFacIterBench(config *wazero.RuntimeConfig) (api.Module, api.Functi
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return m, m.ExportedFunction("fac-iter"), nil
|
||||
return m, m.ExportedFunction("fac"), nil
|
||||
}
|
||||
|
||||
// newWasmerForFacIterBench returns the store and instance that scope the factorial function.
|
||||
// newWasmerForFacBench returns the store and instance that scope the factorial function.
|
||||
// Note: these should be closed
|
||||
func newWasmerForFacIterBench() (*wasmer.Store, *wasmer.Instance, wasmer.NativeFunction, error) {
|
||||
func newWasmerForFacBench() (*wasmer.Store, *wasmer.Instance, wasmer.NativeFunction, error) {
|
||||
store := wasmer.NewStore(wasmer.NewEngine())
|
||||
importObject := wasmer.NewImportObject()
|
||||
module, err := wasmer.NewModule(store, facWasm)
|
||||
@@ -320,7 +320,7 @@ func newWasmerForFacIterBench() (*wasmer.Store, *wasmer.Instance, wasmer.NativeF
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
f, err := instance.Exports.GetFunction("fac-iter")
|
||||
f, err := instance.Exports.GetFunction("fac")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -330,7 +330,7 @@ func newWasmerForFacIterBench() (*wasmer.Store, *wasmer.Instance, wasmer.NativeF
|
||||
return store, instance, f, nil
|
||||
}
|
||||
|
||||
func newWasmtimeForFacIterBench() (*wasmtime.Store, *wasmtime.Func, error) {
|
||||
func newWasmtimeForFacBench() (*wasmtime.Store, *wasmtime.Func, error) {
|
||||
store := wasmtime.NewStore(wasmtime.NewEngine())
|
||||
module, err := wasmtime.NewModule(store.Engine, facWasm)
|
||||
if err != nil {
|
||||
@@ -342,14 +342,14 @@ func newWasmtimeForFacIterBench() (*wasmtime.Store, *wasmtime.Func, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
run := instance.GetFunc(store, "fac-iter")
|
||||
run := instance.GetFunc(store, "fac")
|
||||
if run == nil {
|
||||
return nil, nil, errors.New("not a function")
|
||||
}
|
||||
return store, run, nil
|
||||
}
|
||||
|
||||
func newGoWasm3ForFacIterBench() (*wasm3.Environment, *wasm3.Runtime, wasm3.FunctionWrapper, error) {
|
||||
func newGoWasm3ForFacBench() (*wasm3.Environment, *wasm3.Runtime, wasm3.FunctionWrapper, error) {
|
||||
env := wasm3.NewEnvironment()
|
||||
runtime := wasm3.NewRuntime(&wasm3.Config{
|
||||
Environment: wasm3.NewEnvironment(),
|
||||
@@ -366,7 +366,7 @@ func newGoWasm3ForFacIterBench() (*wasm3.Environment, *wasm3.Runtime, wasm3.Func
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
run, err := runtime.FindFunction("fac-iter")
|
||||
run, err := runtime.FindFunction("fac")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -29,8 +29,6 @@ var exampleText []byte
|
||||
// exampleBinary is the exampleText encoded in the WebAssembly 1.0 binary format.
|
||||
var exampleBinary = binary.EncodeModule(example)
|
||||
|
||||
var enabledFeatures = wasm.Features20191205.Set(wasm.FeatureSignExtensionOps, true)
|
||||
|
||||
func newExample() *wasm.Module {
|
||||
three := wasm.Index(3)
|
||||
i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
|
||||
@@ -40,6 +38,7 @@ func newExample() *wasm.Module {
|
||||
{},
|
||||
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
|
||||
{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}},
|
||||
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}},
|
||||
},
|
||||
ImportSection: []*wasm.Import{
|
||||
{
|
||||
@@ -52,17 +51,19 @@ func newExample() *wasm.Module {
|
||||
DescFunc: 2,
|
||||
},
|
||||
},
|
||||
FunctionSection: []wasm.Index{wasm.Index(1), wasm.Index(1), wasm.Index(0), wasm.Index(3)},
|
||||
FunctionSection: []wasm.Index{wasm.Index(1), wasm.Index(1), wasm.Index(0), wasm.Index(3), wasm.Index(4)},
|
||||
CodeSection: []*wasm.Code{
|
||||
{Body: []byte{wasm.OpcodeCall, 3, wasm.OpcodeEnd}},
|
||||
{Body: []byte{wasm.OpcodeEnd}},
|
||||
{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeI32Add, wasm.OpcodeEnd}},
|
||||
{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeI64Extend16S, wasm.OpcodeEnd}},
|
||||
{Body: []byte{wasm.OpcodeLocalGet, 1, wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}},
|
||||
},
|
||||
MemorySection: &wasm.Memory{Min: 1, Max: three},
|
||||
ExportSection: map[string]*wasm.Export{
|
||||
"AddInt": {Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(4)},
|
||||
"": {Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(3)},
|
||||
"AddInt": {Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(4)},
|
||||
"swap": {Name: "swap", Type: wasm.ExternTypeFunc, Index: wasm.Index(6)},
|
||||
"mem": {Name: "mem", Type: wasm.ExternTypeMemory, Index: wasm.Index(0)},
|
||||
},
|
||||
StartSection: &three,
|
||||
@@ -74,6 +75,7 @@ func newExample() *wasm.Module {
|
||||
{Index: wasm.Index(2), Name: "call_hello"},
|
||||
{Index: wasm.Index(3), Name: "hello"},
|
||||
{Index: wasm.Index(4), Name: "addInt"},
|
||||
{Index: wasm.Index(6), Name: "swap"},
|
||||
},
|
||||
LocalNames: wasm.IndirectNameMap{
|
||||
{Index: wasm.Index(1), NameMap: wasm.NameMap{
|
||||
@@ -93,19 +95,19 @@ func newExample() *wasm.Module {
|
||||
|
||||
func TestExampleUpToDate(t *testing.T) {
|
||||
t.Run("binary.DecodeModule", func(t *testing.T) {
|
||||
m, err := binary.DecodeModule(exampleBinary, enabledFeatures, wasm.MemoryMaxPages)
|
||||
m, err := binary.DecodeModule(exampleBinary, wasm.FeaturesFinished, wasm.MemoryMaxPages)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, example, m)
|
||||
})
|
||||
|
||||
t.Run("text.DecodeModule", func(t *testing.T) {
|
||||
m, err := text.DecodeModule(exampleText, enabledFeatures, wasm.MemoryMaxPages)
|
||||
m, err := text.DecodeModule(exampleText, wasm.FeaturesFinished, wasm.MemoryMaxPages)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, example, m)
|
||||
})
|
||||
|
||||
t.Run("Executable", func(t *testing.T) {
|
||||
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithFeatureSignExtensionOps(true))
|
||||
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithFinishedFeatures())
|
||||
|
||||
// Add WASI to satisfy import tests
|
||||
wm, err := wasi.InstantiateSnapshotPreview1(r)
|
||||
@@ -117,10 +119,10 @@ func TestExampleUpToDate(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer module.Close()
|
||||
|
||||
// Call the add function as a smoke test
|
||||
results, err := module.ExportedFunction("AddInt").Call(nil, 1, 2)
|
||||
// Call the swap function as a smoke test
|
||||
results, err := module.ExportedFunction("swap").Call(nil, 1, 2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(3), results[0])
|
||||
require.Equal(t, []uint64{2, 1}, results)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -128,7 +130,7 @@ func BenchmarkCodecExample(b *testing.B) {
|
||||
b.Run("binary.DecodeModule", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := binary.DecodeModule(exampleBinary, enabledFeatures, wasm.MemoryMaxPages); err != nil {
|
||||
if _, err := binary.DecodeModule(exampleBinary, wasm.FeaturesFinished, wasm.MemoryMaxPages); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -142,7 +144,7 @@ func BenchmarkCodecExample(b *testing.B) {
|
||||
b.Run("text.DecodeModule", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := text.DecodeModule(exampleText, enabledFeatures, wasm.MemoryMaxPages); err != nil {
|
||||
if _, err := text.DecodeModule(exampleText, wasm.FeaturesFinished, wasm.MemoryMaxPages); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -150,7 +152,7 @@ func BenchmarkCodecExample(b *testing.B) {
|
||||
b.Run("wat2wasm via text.DecodeModule->binary.EncodeModule", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if m, err := text.DecodeModule(exampleText, enabledFeatures, wasm.MemoryMaxPages); err != nil {
|
||||
if m, err := text.DecodeModule(exampleText, wasm.FeaturesFinished, wasm.MemoryMaxPages); err != nil {
|
||||
b.Fatal(err)
|
||||
} else {
|
||||
_ = binary.EncodeModule(m)
|
||||
|
||||
8
vs/testdata/example.wat
vendored
8
vs/testdata/example.wat
vendored
@@ -38,6 +38,12 @@
|
||||
(export "mem" (memory $mem))
|
||||
(memory $mem 1 3)
|
||||
|
||||
;; add >1.0 feature from https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
;; add function using "sign-extension-ops"
|
||||
;; https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
(func (param i64) (result i64) local.get 0 i64.extend16_s)
|
||||
|
||||
;; add function using "multi-value"
|
||||
;; https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
(func $swap (param i32 i32) (result i32 i32) local.get 1 local.get 0)
|
||||
(export "swap" (func $swap))
|
||||
)
|
||||
|
||||
BIN
vs/testdata/fac.wasm
vendored
BIN
vs/testdata/fac.wasm
vendored
Binary file not shown.
50
vs/testdata/fac.wat
vendored
50
vs/testdata/fac.wat
vendored
@@ -1,21 +1,35 @@
|
||||
;; This includes a factorial function that uses the "multi-value" feature
|
||||
;;
|
||||
;; Compile like so, in order to not add any other post 1.0 features to the resulting wasm.
|
||||
;; wat2wasm \
|
||||
;; --disable-saturating-float-to-int \
|
||||
;; --disable-sign-extension \
|
||||
;; --disable-simd \
|
||||
;; --disable-bulk-memory \
|
||||
;; --disable-reference-types \
|
||||
;; --debug-names fac.wat
|
||||
;;
|
||||
;; See https://github.com/WebAssembly/spec/blob/main/proposals/multi-value/Overview.md
|
||||
(module
|
||||
(func (export "fac-iter") (param i64) (result i64)
|
||||
(local i64 i64)
|
||||
(local.set 1 (local.get 0))
|
||||
(local.set 2 (i64.const 1))
|
||||
(block
|
||||
(loop
|
||||
(if
|
||||
(i64.eq (local.get 1) (i64.const 0))
|
||||
(then (br 2))
|
||||
(else
|
||||
(local.set 2 (i64.mul (local.get 1) (local.get 2)))
|
||||
(local.set 1 (i64.sub (local.get 1) (i64.const 1)))
|
||||
)
|
||||
)
|
||||
(br 0)
|
||||
)
|
||||
)
|
||||
(local.get 2)
|
||||
(func $pick0 (param i64) (result i64 i64)
|
||||
(local.get 0) (local.get 0)
|
||||
)
|
||||
|
||||
(func $pick1 (param i64 i64) (result i64 i64 i64)
|
||||
(local.get 0) (local.get 1) (local.get 0)
|
||||
)
|
||||
|
||||
;; Note: This implementation loops forever if the input is zero.
|
||||
(func $fac (param i64) (result i64)
|
||||
(i64.const 1) (local.get 0)
|
||||
|
||||
(loop $l (param i64 i64) (result i64)
|
||||
(call $pick1) (call $pick1) (i64.mul)
|
||||
(call $pick1) (i64.const 1) (i64.sub)
|
||||
(call $pick0) (i64.const 0) (i64.gt_u)
|
||||
(br_if $l)
|
||||
(drop) (return)
|
||||
)
|
||||
)
|
||||
(export "fac" (func $fac))
|
||||
)
|
||||
|
||||
4
wasm.go
4
wasm.go
@@ -110,7 +110,7 @@ func NewRuntime() Runtime {
|
||||
func NewRuntimeWithConfig(config *RuntimeConfig) Runtime {
|
||||
return &runtime{
|
||||
ctx: config.ctx,
|
||||
store: wasm.NewStore(config.newEngine(), config.enabledFeatures),
|
||||
store: wasm.NewStore(config.enabledFeatures, config.newEngine(config.enabledFeatures)),
|
||||
enabledFeatures: config.enabledFeatures,
|
||||
memoryMaxPages: config.memoryMaxPages,
|
||||
}
|
||||
@@ -118,9 +118,9 @@ func NewRuntimeWithConfig(config *RuntimeConfig) Runtime {
|
||||
|
||||
// runtime allows decoupling of public interfaces from internal representation.
|
||||
type runtime struct {
|
||||
enabledFeatures wasm.Features
|
||||
ctx context.Context
|
||||
store *wasm.Store
|
||||
enabledFeatures wasm.Features
|
||||
memoryMaxPages uint32
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user