Sets up spectest infra for wazevo (#1647)

Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
Takeshi Yoneda
2023-08-22 13:50:40 +09:00
committed by GitHub
parent de23cd4dff
commit 967d8df56d
5 changed files with 176 additions and 161 deletions

View File

@@ -3,7 +3,6 @@ package wazevo
import (
"context"
"encoding/binary"
"fmt"
"reflect"
"unsafe"
@@ -118,8 +117,6 @@ func (c *callEngine) CallWithStack(ctx context.Context, paramResultStack []uint6
paramResultPtr = &paramResultStack[0]
}
fmt.Printf("stackGrowCallSequenceAddress ===== %#x\n", c.execCtx.stackGrowCallSequenceAddress)
entrypoint(c.executable, c.execCtxPtr, c.parent.opaquePtr, paramResultPtr, c.stackTop)
for {
switch ec := c.execCtx.exitCode; ec & wazevoapi.ExitCodeMask {

View File

@@ -13,6 +13,8 @@ import (
"github.com/tetratelabs/wazero/internal/engine/wazevo"
"github.com/tetratelabs/wazero/internal/engine/wazevo/testcases"
"github.com/tetratelabs/wazero/internal/filecache"
"github.com/tetratelabs/wazero/internal/integration_test/spectest"
v1 "github.com/tetratelabs/wazero/internal/integration_test/spectest/v1"
"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
@@ -25,6 +27,16 @@ const (
f64 = wasm.ValueTypeF64
)
func TestSpectestV1(t *testing.T) {
config := wazero.NewRuntimeConfigCompiler().WithCoreFeatures(api.CoreFeaturesV1)
// Configure the new optimizing backend!
configureWazevo(config)
// TODO: adds incrementally one by one as we support more test cases. And eventually remove this
// and migrate to integration_test/spectest/v1/spec_test.go by the time when closing https://github.com/tetratelabs/wazero/issues/1496
spectest.RunJson(t, v1.Testcases, "binary.json", context.Background(), config)
}
func TestE2E(t *testing.T) {
type callCase struct {
funcName string // defaults to testcases.ExportedFunctionName

View File

@@ -909,7 +909,13 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
}
index := state.pop()
c.lowerBrTable(labels, index)
if labelCount == 0 { // If this br_table is empty, we can just emit the unconditional jump.
targetBlk, argNum := state.brTargetArgNumFor(labels[0])
args := c.loweringState.nPeekDup(argNum)
c.insertJumpToBlock(args, targetBlk)
} else {
c.lowerBrTable(labels, index)
}
state.unreachable = true
case wasm.OpcodeNop:

View File

@@ -330,7 +330,7 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, config wazero.R
for _, f := range files {
filename := f.Name()
if strings.HasSuffix(filename, ".json") {
jsonfiles = append(jsonfiles, testdataPath(filename))
jsonfiles = append(jsonfiles, filename)
}
}
@@ -339,161 +339,166 @@ func Run(t *testing.T, testDataFS embed.FS, ctx context.Context, config wazero.R
require.True(t, len(jsonfiles) > 1, "len(jsonfiles)=%d (not greater than one)", len(jsonfiles))
for _, f := range jsonfiles {
raw, err := testDataFS.ReadFile(f)
RunJson(t, testDataFS, f, ctx, config)
}
}
// RunJson runs the test case described by the given spectest JSON file name in the testDataFS file system.
func RunJson(t *testing.T, testDataFS embed.FS, f string, ctx context.Context, config wazero.RuntimeConfig) {
raw, err := testDataFS.ReadFile(testdataPath(f))
require.NoError(t, err)
var base testbase
require.NoError(t, json.Unmarshal(raw, &base))
wastName := basename(base.SourceFile)
t.Run(wastName, func(t *testing.T) {
r := wazero.NewRuntimeWithConfig(ctx, config)
defer func() {
require.NoError(t, r.Close(ctx))
}()
_, err := r.InstantiateWithConfig(ctx, spectestWasm, wazero.NewModuleConfig())
require.NoError(t, err)
var base testbase
require.NoError(t, json.Unmarshal(raw, &base))
modules := make(map[string]api.Module)
var lastInstantiatedModule api.Module
for i := 0; i < len(base.Commands); i++ {
c := &base.Commands[i]
t.Run(fmt.Sprintf("%s/line:%d", c.CommandType, c.Line), func(t *testing.T) {
msg := fmt.Sprintf("%s:%d %s", wastName, c.Line, c.CommandType)
switch c.CommandType {
case "module":
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
wastName := basename(base.SourceFile)
t.Run(wastName, func(t *testing.T) {
r := wazero.NewRuntimeWithConfig(ctx, config)
defer func() {
require.NoError(t, r.Close(ctx))
}()
_, err := r.InstantiateWithConfig(ctx, spectestWasm, wazero.NewModuleConfig())
require.NoError(t, err)
modules := make(map[string]api.Module)
var lastInstantiatedModule api.Module
for i := 0; i < len(base.Commands); i++ {
c := &base.Commands[i]
t.Run(fmt.Sprintf("%s/line:%d", c.CommandType, c.Line), func(t *testing.T) {
msg := fmt.Sprintf("%s:%d %s", wastName, c.Line, c.CommandType)
switch c.CommandType {
case "module":
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
var registeredName string
if next := i + 1; next < len(base.Commands) && base.Commands[next].CommandType == "register" {
registeredName = base.Commands[next].As
i++ // Skip the entire "register" command.
}
mod, err := r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig().WithName(registeredName))
require.NoError(t, err, msg)
if c.Name != "" {
modules[c.Name] = mod
}
lastInstantiatedModule = mod
case "assert_return", "action":
m := lastInstantiatedModule
if c.Action.Module != "" {
m = modules[c.Action.Module]
}
switch c.Action.ActionType {
case "invoke":
args, exps := c.getAssertReturnArgsExps()
msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
if c.Action.Module != "" {
msg += " in module " + c.Action.Module
}
fn := m.ExportedFunction(c.Action.Field)
results, err := fn.Call(ctx, args...)
require.NoError(t, err, msg)
require.Equal(t, len(exps), len(results), msg)
laneTypes := map[int]string{}
for i, expV := range c.Exps {
if expV.ValType == "v128" {
laneTypes[i] = expV.LaneType
}
}
matched, valuesMsg := valuesEq(results, exps, fn.Definition().ResultTypes(), laneTypes)
require.True(t, matched, msg+"\n"+valuesMsg)
case "get":
_, exps := c.getAssertReturnArgsExps()
require.Equal(t, 1, len(exps))
msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
if c.Action.Module != "" {
msg += " in module " + c.Action.Module
}
global := m.ExportedGlobal(c.Action.Field)
require.NotNil(t, global)
require.Equal(t, exps[0], global.Get(), msg)
default:
t.Fatalf("unsupported action type type: %v", c)
}
case "assert_malformed":
if c.ModuleType != "text" {
// We don't support direct loading of wast yet.
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
require.Error(t, err, msg)
}
case "assert_trap":
m := lastInstantiatedModule
if c.Action.Module != "" {
m = modules[c.Action.Module]
}
switch c.Action.ActionType {
case "invoke":
args := c.getAssertReturnArgs()
msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
if c.Action.Module != "" {
msg += " in module " + c.Action.Module
}
_, err := m.ExportedFunction(c.Action.Field).Call(ctx, args...)
require.ErrorIs(t, err, c.expectedError(), msg)
default:
t.Fatalf("unsupported action type type: %v", c)
}
case "assert_invalid":
if c.ModuleType == "text" {
// We don't support direct loading of wast yet.
t.Skip()
}
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
require.Error(t, err, msg)
case "assert_exhaustion":
switch c.Action.ActionType {
case "invoke":
args := c.getAssertReturnArgs()
msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
if c.Action.Module != "" {
msg += " in module " + c.Action.Module
}
_, err := lastInstantiatedModule.ExportedFunction(c.Action.Field).Call(ctx, args...)
require.ErrorIs(t, err, wasmruntime.ErrRuntimeStackOverflow, msg)
default:
t.Fatalf("unsupported action type type: %v", c)
}
case "assert_unlinkable":
if c.ModuleType == "text" {
// We don't support direct loading of wast yet.
t.Skip()
}
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
require.Error(t, err, msg)
case "assert_uninstantiable":
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
if c.Text == "out of bounds table access" {
// This is not actually an instantiation error, but assert_trap in the original wast, but wast2json translates it to assert_uninstantiable.
// Anyway, this spectest case expects the error due to active element offset ouf of bounds
// "after" instantiation while retaining function instances used for elements.
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274
//
// In practice, such a module instance can be used for invoking functions without any issue. In addition, we have to
// retain functions after the expected "instantiation" failure, so in wazero we choose to not raise error in that case.
require.NoError(t, err, msg)
} else {
require.Error(t, err, msg)
}
default:
t.Fatalf("unsupported command type: %s", c)
var registeredName string
if next := i + 1; next < len(base.Commands) && base.Commands[next].CommandType == "register" {
registeredName = base.Commands[next].As
i++ // Skip the entire "register" command.
}
})
}
})
}
mod, err := r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig().WithName(registeredName))
require.NoError(t, err, msg)
if c.Name != "" {
modules[c.Name] = mod
}
lastInstantiatedModule = mod
case "assert_return", "action":
m := lastInstantiatedModule
if c.Action.Module != "" {
m = modules[c.Action.Module]
}
switch c.Action.ActionType {
case "invoke":
args, exps := c.getAssertReturnArgsExps()
msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
if c.Action.Module != "" {
msg += " in module " + c.Action.Module
}
fn := m.ExportedFunction(c.Action.Field)
results, err := fn.Call(ctx, args...)
require.NoError(t, err, msg)
require.Equal(t, len(exps), len(results), msg)
laneTypes := map[int]string{}
for i, expV := range c.Exps {
if expV.ValType == "v128" {
laneTypes[i] = expV.LaneType
}
}
matched, valuesMsg := valuesEq(results, exps, fn.Definition().ResultTypes(), laneTypes)
require.True(t, matched, msg+"\n"+valuesMsg)
case "get":
_, exps := c.getAssertReturnArgsExps()
require.Equal(t, 1, len(exps))
msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
if c.Action.Module != "" {
msg += " in module " + c.Action.Module
}
global := m.ExportedGlobal(c.Action.Field)
require.NotNil(t, global)
require.Equal(t, exps[0], global.Get(), msg)
default:
t.Fatalf("unsupported action type type: %v", c)
}
case "assert_malformed":
if c.ModuleType != "text" {
// We don't support direct loading of wast yet.
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
require.Error(t, err, msg)
}
case "assert_trap":
m := lastInstantiatedModule
if c.Action.Module != "" {
m = modules[c.Action.Module]
}
switch c.Action.ActionType {
case "invoke":
args := c.getAssertReturnArgs()
msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
if c.Action.Module != "" {
msg += " in module " + c.Action.Module
}
_, err := m.ExportedFunction(c.Action.Field).Call(ctx, args...)
require.ErrorIs(t, err, c.expectedError(), msg)
default:
t.Fatalf("unsupported action type type: %v", c)
}
case "assert_invalid":
if c.ModuleType == "text" {
// We don't support direct loading of wast yet.
t.Skip()
}
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
require.Error(t, err, msg)
case "assert_exhaustion":
switch c.Action.ActionType {
case "invoke":
args := c.getAssertReturnArgs()
msg = fmt.Sprintf("%s invoke %s (%s)", msg, c.Action.Field, c.Action.Args)
if c.Action.Module != "" {
msg += " in module " + c.Action.Module
}
_, err := lastInstantiatedModule.ExportedFunction(c.Action.Field).Call(ctx, args...)
require.ErrorIs(t, err, wasmruntime.ErrRuntimeStackOverflow, msg)
default:
t.Fatalf("unsupported action type type: %v", c)
}
case "assert_unlinkable":
if c.ModuleType == "text" {
// We don't support direct loading of wast yet.
t.Skip()
}
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
require.Error(t, err, msg)
case "assert_uninstantiable":
buf, err := testDataFS.ReadFile(testdataPath(c.Filename))
require.NoError(t, err, msg)
_, err = r.InstantiateWithConfig(ctx, buf, wazero.NewModuleConfig())
if c.Text == "out of bounds table access" {
// This is not actually an instantiation error, but assert_trap in the original wast, but wast2json translates it to assert_uninstantiable.
// Anyway, this spectest case expects the error due to active element offset ouf of bounds
// "after" instantiation while retaining function instances used for elements.
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274
//
// In practice, such a module instance can be used for invoking functions without any issue. In addition, we have to
// retain functions after the expected "instantiation" failure, so in wazero we choose to not raise error in that case.
require.NoError(t, err, msg)
} else {
require.Error(t, err, msg)
}
default:
t.Fatalf("unsupported command type: %s", c)
}
})
}
})
}
// basename avoids filepath.Base to ensure a forward slash is used even in Windows.

View File

@@ -2,8 +2,6 @@ package v1
import (
"embed"
"github.com/tetratelabs/wazero/api"
)
// Testcases is exported for cross-process file cache tests.
@@ -11,6 +9,3 @@ import (
//go:embed testdata/*.wasm
//go:embed testdata/*.json
var Testcases embed.FS
// EnabledFeatures is exported for cross-process file cache tests.
const EnabledFeatures = api.CoreFeaturesV1