From 967d8df56d018386a821a3c340d4f085aa9750bd Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Tue, 22 Aug 2023 13:50:40 +0900 Subject: [PATCH] Sets up spectest infra for wazevo (#1647) Signed-off-by: Takeshi Yoneda --- internal/engine/wazevo/call_engine.go | 3 - internal/engine/wazevo/e2e_test.go | 12 + internal/engine/wazevo/frontend/lower.go | 8 +- .../integration_test/spectest/spectest.go | 309 +++++++++--------- .../integration_test/spectest/v1/spectest.go | 5 - 5 files changed, 176 insertions(+), 161 deletions(-) diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go index 329c7b14..c7aeb21d 100644 --- a/internal/engine/wazevo/call_engine.go +++ b/internal/engine/wazevo/call_engine.go @@ -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 = ¶mResultStack[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 { diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go index 335883ac..f589ca05 100644 --- a/internal/engine/wazevo/e2e_test.go +++ b/internal/engine/wazevo/e2e_test.go @@ -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 diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go index 451ceb98..e2f9fd62 100644 --- a/internal/engine/wazevo/frontend/lower.go +++ b/internal/engine/wazevo/frontend/lower.go @@ -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: diff --git a/internal/integration_test/spectest/spectest.go b/internal/integration_test/spectest/spectest.go index 414335d7..f7b731c7 100644 --- a/internal/integration_test/spectest/spectest.go +++ b/internal/integration_test/spectest/spectest.go @@ -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. diff --git a/internal/integration_test/spectest/v1/spectest.go b/internal/integration_test/spectest/v1/spectest.go index 7d4fb394..1a8e2eab 100644 --- a/internal/integration_test/spectest/v1/spectest.go +++ b/internal/integration_test/spectest/v1/spectest.go @@ -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