interpreter,compiler(amd64): complete SIMD instructions (#624)

This completes the implementation of SIMD proposal for both
the interpreter and compiler(amd64).
This also fixes #210 by adding the complete documentation
over all the wazeroir operations.

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Co-authored-by: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com>
This commit is contained in:
Takeshi Yoneda
2022-06-15 11:52:47 +09:00
committed by GitHub
parent 119b069ba2
commit 3068d17c77
30 changed files with 11606 additions and 954 deletions

View File

@@ -66,25 +66,51 @@ type (
}
commandActionVal struct {
ValType string `json:"type"`
LaneType string `json:"lane_type"`
ValType string `json:"type"`
// LaneType is not empty if ValueType == "v128"
LaneType laneType `json:"lane_type"`
Value interface{} `json:"value"`
}
)
// laneType is a type of each lane of vector value.
//
// See https://github.com/WebAssembly/wabt/blob/main/docs/wast2json.md#const
type laneType = string
const (
laneTypeI8 laneType = "i8"
laneTypeI16 laneType = "i16"
laneTypeI32 laneType = "i32"
laneTypeI64 laneType = "i64"
laneTypeF32 laneType = "f32"
laneTypeF64 laneType = "f64"
)
func (c commandActionVal) String() string {
var v string
valTypeStr := c.ValType
switch c.ValType {
case "i32":
v = c.Value.(string)
case "f32":
ret, _ := strconv.ParseUint(c.Value.(string), 10, 32)
v = fmt.Sprintf("%f", math.Float32frombits(uint32(ret)))
str := c.Value.(string)
if strings.Contains(str, "nan") {
v = "nan"
} else {
ret, _ := strconv.ParseUint(str, 10, 32)
v = fmt.Sprintf("%f", math.Float32frombits(uint32(ret)))
}
case "i64":
v = c.Value.(string)
case "f64":
ret, _ := strconv.ParseUint(c.Value.(string), 10, 64)
v = fmt.Sprintf("%f", math.Float64frombits(ret))
str := c.Value.(string)
if strings.Contains(str, "nan") {
v = "nan"
} else {
ret, _ := strconv.ParseUint(str, 10, 64)
v = fmt.Sprintf("%f", math.Float64frombits(ret))
}
case "externref":
if c.Value == "null" {
v = "null"
@@ -107,8 +133,9 @@ func (c commandActionVal) String() string {
strs = append(strs, v.(string))
}
v = strings.Join(strs, ",")
valTypeStr = fmt.Sprintf("v128[lane=%s]", c.LaneType)
}
return fmt.Sprintf("{type: %s, value: %v}", c.ValType, v)
return fmt.Sprintf("{type: %s, value: %v}", valTypeStr, v)
}
func (c command) String() string {
@@ -153,15 +180,14 @@ func (c command) getAssertReturnArgs() []uint64 {
return args
}
func (c command) getAssertReturnArgsExps() ([]uint64, []uint64) {
var args, exps []uint64
func (c command) getAssertReturnArgsExps() (args []uint64, exps []uint64) {
for _, arg := range c.Action.Args {
args = append(args, arg.toUint64s()...)
}
for _, exp := range c.Exps {
exps = append(exps, exp.toUint64s()...)
}
return args, exps
return
}
func (c commandActionVal) toUint64s() (ret []uint64) {
@@ -170,7 +196,6 @@ func (c commandActionVal) toUint64s() (ret []uint64) {
if !ok {
panic("BUG")
}
var low, high uint64
var width, valNum int
switch c.LaneType {
case "i8":
@@ -188,26 +213,41 @@ func (c commandActionVal) toUint64s() (ret []uint64) {
default:
panic("BUG")
}
for i := 0; i < valNum/2; i++ {
v, err := strconv.ParseUint(strValues[i].(string), 10, width)
if err != nil {
panic(err)
}
low |= (v << (i * width))
}
for i := valNum / 2; i < valNum; i++ {
v, err := strconv.ParseUint(strValues[i].(string), 10, width)
if err != nil {
panic(err)
}
high |= (v << ((i - valNum/2) * width))
}
return []uint64{low, high}
lo, hi := buildLaneUint64(strValues, width, valNum)
return []uint64{lo, hi}
} else {
return []uint64{c.toUint64()}
}
}
func buildLaneUint64(raw []interface{}, width, valNum int) (lo, hi uint64) {
for i := 0; i < valNum; i++ {
str := raw[i].(string)
var v uint64
var err error
if strings.Contains(str, "nan") {
if width == 64 {
v = math.Float64bits(math.NaN())
} else {
v = uint64(math.Float32bits(float32(math.NaN())))
}
} else {
v, err = strconv.ParseUint(str, 10, width)
if err != nil {
panic(err)
}
}
if half := valNum / 2; i < half {
lo |= v << (i * width)
} else {
hi |= v << ((i - half) * width)
}
}
return
}
func (c commandActionVal) toUint64() (ret uint64) {
strValue := c.Value.(string)
if strings.Contains(strValue, "nan") {
@@ -441,7 +481,14 @@ func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.E
vals, types, err := callFunction(ns, moduleName, c.Action.Field, args...)
require.NoError(t, err, msg)
require.Equal(t, len(exps), len(vals), msg)
requireValuesEq(t, vals, exps, types, msg)
laneTypes := map[int]string{}
for i, expV := range c.Exps {
if expV.ValType == "v128" {
laneTypes[i] = expV.LaneType
}
}
matched, valuesMsg := valuesEq(vals, exps, types, laneTypes)
require.True(t, matched, msg+"\n"+valuesMsg)
case "get":
_, exps := c.getAssertReturnArgsExps()
require.Equal(t, 1, len(exps))
@@ -597,52 +644,157 @@ func testdataPath(filename string) string {
return fmt.Sprintf("testdata/%s", filename)
}
func requireValuesEq(t *testing.T, actual, exps []uint64, valTypes []wasm.ValueType, msg string) {
var expectedTypesVectorFlattend []wasm.ValueType
for _, tp := range valTypes {
if tp != wasm.ValueTypeV128 {
expectedTypesVectorFlattend = append(expectedTypesVectorFlattend, tp)
} else {
expectedTypesVectorFlattend = append(expectedTypesVectorFlattend, wasm.ValueTypeI64)
expectedTypesVectorFlattend = append(expectedTypesVectorFlattend, wasm.ValueTypeI64)
// valuesEq returns true if all the actual result matches exps which are all expressed as uint64.
// * actual,exps: comparison target values which are all represented as uint64, meaning that if valTypes = [V128,I32], then
// we have actual/exp = [(lower-64bit of the first V128), (higher-64bit of the first V128), I32].
// * valTypes holds the wasm.ValueType(s) of the original values in Wasm.
// * laneTypes maps the index of valueTypes to laneType if valueTypes[i] == wasm.ValueTypeV128.
//
// Also, if matched == false this returns non-empty valuesMsg which can be used to augment the test failure message.
func valuesEq(actual, exps []uint64, valTypes []wasm.ValueType, laneTypes map[int]laneType) (matched bool, valuesMsg string) {
matched = true
var msgExpValuesStrs, msgActualValuesStrs []string
var uint64RepPos int // the index to actual and exps slice.
for i, tp := range valTypes {
switch tp {
case wasm.ValueTypeI32:
msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", uint32(exps[uint64RepPos])))
msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", uint32(actual[uint64RepPos])))
matched = matched && uint32(exps[uint64RepPos]) == uint32(actual[uint64RepPos])
uint64RepPos++
case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%d", exps[uint64RepPos]))
msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%d", actual[uint64RepPos]))
matched = matched && exps[uint64RepPos] == actual[uint64RepPos]
uint64RepPos++
case wasm.ValueTypeF32:
a := math.Float32frombits(uint32(actual[uint64RepPos]))
e := math.Float32frombits(uint32(exps[uint64RepPos]))
msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e))
msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a))
matched = matched && f32Equal(e, a)
uint64RepPos++
case wasm.ValueTypeF64:
e := math.Float64frombits(exps[uint64RepPos])
a := math.Float64frombits(actual[uint64RepPos])
msgExpValuesStrs = append(msgExpValuesStrs, fmt.Sprintf("%f", e))
msgActualValuesStrs = append(msgActualValuesStrs, fmt.Sprintf("%f", a))
matched = matched && f64Equal(e, a)
uint64RepPos++
case wasm.ValueTypeV128:
actualLo, actualHi := actual[uint64RepPos], actual[uint64RepPos+1]
expLo, expHi := exps[uint64RepPos], exps[uint64RepPos+1]
switch laneTypes[i] {
case laneTypeI8:
msgExpValuesStrs = append(msgExpValuesStrs,
fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
byte(expLo), byte(expLo>>8), byte(expLo>>16), byte(expLo>>24),
byte(expLo>>32), byte(expLo>>40), byte(expLo>>48), byte(expLo>>56),
byte(expHi), byte(expHi>>8), byte(expHi>>16), byte(expHi>>24),
byte(expHi>>32), byte(expHi>>40), byte(expHi>>48), byte(expHi>>56),
),
)
msgActualValuesStrs = append(msgActualValuesStrs,
fmt.Sprintf("i8x16(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
byte(actualLo), byte(actualLo>>8), byte(actualLo>>16), byte(actualLo>>24),
byte(actualLo>>32), byte(actualLo>>40), byte(actualLo>>48), byte(actualLo>>56),
byte(actualHi), byte(actualHi>>8), byte(actualHi>>16), byte(actualHi>>24),
byte(actualHi>>32), byte(actualHi>>40), byte(actualHi>>48), byte(actualHi>>56),
),
)
matched = matched && (expLo == actualLo) && (expHi == actualHi)
case laneTypeI16:
msgExpValuesStrs = append(msgExpValuesStrs,
fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
uint16(expLo), uint16(expLo>>16), uint16(expLo>>32), uint16(expLo>>48),
uint16(expHi), uint16(expHi>>16), uint16(expHi>>32), uint16(expHi>>48),
),
)
msgActualValuesStrs = append(msgActualValuesStrs,
fmt.Sprintf("i16x8(%#x, %#x, %#x, %#x, %#x, %#x, %#x, %#x)",
uint16(actualLo), uint16(actualLo>>16), uint16(actualLo>>32), uint16(actualLo>>48),
uint16(actualHi), uint16(actualHi>>16), uint16(actualHi>>32), uint16(actualHi>>48),
),
)
matched = matched && (expLo == actualLo) && (expHi == actualHi)
case laneTypeI32:
msgExpValuesStrs = append(msgExpValuesStrs,
fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(expLo), uint32(expLo>>32), uint32(expHi), uint32(expHi>>32)),
)
msgActualValuesStrs = append(msgActualValuesStrs,
fmt.Sprintf("i32x4(%#x, %#x, %#x, %#x)", uint32(actualLo), uint32(actualLo>>32), uint32(actualHi), uint32(actualHi>>32)),
)
matched = matched && (expLo == actualLo) && (expHi == actualHi)
case laneTypeI64:
msgExpValuesStrs = append(msgExpValuesStrs,
fmt.Sprintf("i64x2(%#x, %#x)", expLo, expHi),
)
msgActualValuesStrs = append(msgActualValuesStrs,
fmt.Sprintf("i64x2(%#x, %#x)", actualLo, actualHi),
)
matched = matched && (expLo == actualLo) && (expHi == actualHi)
case laneTypeF32:
msgExpValuesStrs = append(msgExpValuesStrs,
fmt.Sprintf("f32x4(%f, %f, %f, %f)",
math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(expLo>>32)),
math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(expHi>>32)),
),
)
msgActualValuesStrs = append(msgActualValuesStrs,
fmt.Sprintf("f32x4(%f, %f, %f, %f)",
math.Float32frombits(uint32(actualLo)), math.Float32frombits(uint32(actualLo>>32)),
math.Float32frombits(uint32(actualHi)), math.Float32frombits(uint32(actualHi>>32)),
),
)
matched = matched &&
f32Equal(math.Float32frombits(uint32(expLo)), math.Float32frombits(uint32(actualLo))) &&
f32Equal(math.Float32frombits(uint32(expLo>>32)), math.Float32frombits(uint32(actualLo>>32))) &&
f32Equal(math.Float32frombits(uint32(expHi)), math.Float32frombits(uint32(actualHi))) &&
f32Equal(math.Float32frombits(uint32(expHi>>32)), math.Float32frombits(uint32(actualHi>>32)))
case laneTypeF64:
msgExpValuesStrs = append(msgExpValuesStrs,
fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(expLo), math.Float64frombits(expHi)),
)
msgActualValuesStrs = append(msgActualValuesStrs,
fmt.Sprintf("f64x2(%f, %f)", math.Float64frombits(actualLo), math.Float64frombits(actualHi)),
)
matched = matched &&
f64Equal(math.Float64frombits(expLo), math.Float64frombits(actualLo)) &&
f64Equal(math.Float64frombits(expHi), math.Float64frombits(actualHi))
default:
panic("BUG")
}
uint64RepPos += 2
default:
panic("BUG")
}
}
result := fmt.Sprintf("\thave (%v)\n\twant (%v)", actual, exps)
for i := range exps {
requireValueEq(t, actual[i], exps[i], expectedTypesVectorFlattend[i], msg+"\n"+result)
if !matched {
valuesMsg = fmt.Sprintf("\thave [%s]\n\twant [%s]",
strings.Join(msgActualValuesStrs, ", "),
strings.Join(msgExpValuesStrs, ", "))
}
return
}
func requireValueEq(t *testing.T, actual, expected uint64, valType wasm.ValueType, msg string) {
switch valType {
case wasm.ValueTypeI32:
require.Equal(t, uint32(expected), uint32(actual), msg)
case wasm.ValueTypeI64:
require.Equal(t, expected, actual, msg)
case wasm.ValueTypeF32:
expF := math.Float32frombits(uint32(expected))
actualF := math.Float32frombits(uint32(actual))
if math.IsNaN(float64(expF)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actualF)), msg)
} else {
require.Equal(t, expF, actualF, msg)
}
case wasm.ValueTypeF64:
expF := math.Float64frombits(expected)
actualF := math.Float64frombits(actual)
if math.IsNaN(expF) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actualF), msg)
} else {
require.Equal(t, expF, actualF, msg)
}
case wasm.ValueTypeExternref:
require.Equal(t, expected, actual, msg)
case wasm.ValueTypeFuncref:
require.Equal(t, expected, actual, msg)
default:
t.Fatal(msg)
func f32Equal(expected, actual float32) (matched bool) {
if math.IsNaN(float64(expected)) { // NaN cannot be compared with themselves, so we have to use IsNaN
matched = math.IsNaN(float64(actual))
} else {
matched = expected == actual
}
return
}
func f64Equal(actual, expected float64) (matched bool) {
if math.IsNaN(expected) { // NaN cannot be compared with themselves, so we have to use IsNaN
matched = math.IsNaN(actual)
} else {
matched = expected == actual
}
return
}
// callFunction is inlined here as the spectest needs to validate the signature was correct

View File

@@ -0,0 +1,581 @@
package spectest
import (
"encoding/json"
"math"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func Test_f32Equal(t *testing.T) {
tests := []struct {
name string
f1, f2 float32
exp bool
}{
{name: "1", f1: 1.1, f2: 1.1, exp: true},
{name: "2", f1: float32(math.NaN()), f2: float32(math.NaN()), exp: true},
{name: "3", f1: float32(math.Inf(1)), f2: float32(math.Inf(1)), exp: true},
{name: "4", f1: float32(math.Inf(-1)), f2: float32(math.Inf(-1)), exp: true},
{name: "5", f1: 1.1, f2: -1.1, exp: false},
{name: "6", f1: float32(math.NaN()), f2: -1.1, exp: false},
{name: "7", f1: -1.1, f2: float32(math.NaN()), exp: false},
{name: "8", f1: float32(math.NaN()), f2: float32(math.Inf(1)), exp: false},
{name: "9", f1: float32(math.Inf(1)), f2: float32(math.NaN()), exp: false},
{name: "10", f1: float32(math.NaN()), f2: float32(math.Inf(-1)), exp: false},
{name: "11", f1: float32(math.Inf(-1)), f2: float32(math.NaN()), exp: false},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.exp, f32Equal(tc.f1, tc.f2))
})
}
}
func Test_f64Equal(t *testing.T) {
tests := []struct {
name string
f1, f2 float64
exp bool
}{
{name: "1", f1: 1.1, f2: 1.1, exp: true},
{name: "2", f1: math.NaN(), f2: math.NaN(), exp: true},
{name: "3", f1: math.Inf(1), f2: math.Inf(1), exp: true},
{name: "4", f1: math.Inf(-1), f2: math.Inf(-1), exp: true},
{name: "5", f1: 1.1, f2: -1.1, exp: false},
{name: "6", f1: math.NaN(), f2: -1.1, exp: false},
{name: "7", f1: -1.1, f2: math.NaN(), exp: false},
{name: "8", f1: math.NaN(), f2: math.Inf(1), exp: false},
{name: "9", f1: math.Inf(1), f2: math.NaN(), exp: false},
{name: "10", f1: math.NaN(), f2: math.Inf(-1), exp: false},
{name: "11", f1: math.Inf(-1), f2: math.NaN(), exp: false},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.exp, f64Equal(tc.f1, tc.f2))
})
}
}
func Test_valuesEq(t *testing.T) {
i32, i64, f32, f64, v128 := wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeV128
tests := []struct {
name string
exps, actual []uint64
valueTypes []wasm.ValueType
laneTypes map[int]laneType
expMatched bool
expValuesMsg string
}{
{
name: "matched/i32",
exps: []uint64{0},
actual: []uint64{0},
valueTypes: []wasm.ValueType{i32},
expMatched: true,
},
{
name: "unmatched/i32",
exps: []uint64{1},
actual: []uint64{0},
valueTypes: []wasm.ValueType{i32},
expMatched: false,
expValuesMsg: ` have [0]
want [1]`,
},
{
name: "unmatched/i32",
exps: []uint64{math.MaxUint32},
actual: []uint64{1123},
valueTypes: []wasm.ValueType{i32},
expMatched: false,
expValuesMsg: ` have [1123]
want [4294967295]`,
},
{
name: "matched/i64",
exps: []uint64{0},
actual: []uint64{0},
valueTypes: []wasm.ValueType{i64},
expMatched: true,
},
{
name: "unmatched/i64",
exps: []uint64{1},
actual: []uint64{0},
valueTypes: []wasm.ValueType{i64},
expMatched: false,
expValuesMsg: ` have [0]
want [1]`,
},
{
name: "unmatched/i64",
exps: []uint64{math.MaxUint64},
actual: []uint64{1123},
valueTypes: []wasm.ValueType{i64},
expMatched: false,
expValuesMsg: ` have [1123]
want [18446744073709551615]`,
},
{
name: "matched/f32",
exps: []uint64{0},
actual: []uint64{0},
valueTypes: []wasm.ValueType{f32},
expMatched: true,
},
{
name: "unmatched/f32",
exps: []uint64{uint64(math.Float32bits(-13123.1))},
actual: []uint64{0},
valueTypes: []wasm.ValueType{f32},
expMatched: false,
expValuesMsg: ` have [0.000000]
want [-13123.099609]`,
},
{
name: "matched/f64",
exps: []uint64{0},
actual: []uint64{0},
valueTypes: []wasm.ValueType{f64},
expMatched: true,
},
{
name: "unmatched/f64",
exps: []uint64{math.Float64bits(1.0)},
actual: []uint64{0},
valueTypes: []wasm.ValueType{f64},
expMatched: false,
expValuesMsg: ` have [0.000000]
want [1.000000]`,
},
{
name: "unmatched/f64",
actual: []uint64{math.Float64bits(-1231231.0)},
exps: []uint64{0},
valueTypes: []wasm.ValueType{f64},
expMatched: false,
expValuesMsg: ` have [-1231231.000000]
want [0.000000]`,
},
{
name: "matched/i8x16",
exps: []uint64{math.MaxUint64, 123},
actual: []uint64{math.MaxUint64, 123},
laneTypes: map[int]laneType{0: laneTypeI8},
valueTypes: []wasm.ValueType{v128},
expMatched: true,
},
{
name: "unmatched/i8x16",
exps: []uint64{0, 0xff<<56 | 0xaa},
actual: []uint64{math.MaxUint64, 0xff<<48 | 0xcc},
laneTypes: map[int]laneType{0: laneTypeI8},
valueTypes: []wasm.ValueType{v128},
expMatched: false,
expValuesMsg: ` have [i8x16(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0)]
want [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff)]`,
},
{
name: "matched/i16x8",
exps: []uint64{math.MaxUint64, 123},
actual: []uint64{math.MaxUint64, 123},
laneTypes: map[int]laneType{0: laneTypeI16},
valueTypes: []wasm.ValueType{v128},
expMatched: true,
},
{
name: "unmatched/i16x8",
exps: []uint64{0xffff << 32, 0},
actual: []uint64{0xaabb << 16, ^uint64(0)},
laneTypes: map[int]laneType{0: laneTypeI16},
valueTypes: []wasm.ValueType{v128},
expMatched: false,
expValuesMsg: ` have [i16x8(0x0, 0xaabb, 0x0, 0x0, 0xffff, 0xffff, 0xffff, 0xffff)]
want [i16x8(0x0, 0x0, 0xffff, 0x0, 0x0, 0x0, 0x0, 0x0)]`,
},
{
name: "matched/i32x4",
exps: []uint64{math.MaxUint64, 123},
actual: []uint64{math.MaxUint64, 123},
laneTypes: map[int]laneType{0: laneTypeI32},
valueTypes: []wasm.ValueType{v128},
expMatched: true,
},
{
name: "matched/i32x4",
exps: []uint64{0xffff_ffff<<32 | 0xa, 123},
actual: []uint64{0x1a1a_1a1a<<32 | 0xa, 123},
laneTypes: map[int]laneType{0: laneTypeI32},
valueTypes: []wasm.ValueType{v128},
expMatched: false,
expValuesMsg: ` have [i32x4(0xa, 0x1a1a1a1a, 0x7b, 0x0)]
want [i32x4(0xa, 0xffffffff, 0x7b, 0x0)]`,
},
{
name: "matched/i64x2",
exps: []uint64{math.MaxUint64, 123},
actual: []uint64{math.MaxUint64, 123},
laneTypes: map[int]laneType{0: laneTypeI64},
valueTypes: []wasm.ValueType{v128},
expMatched: true,
},
{
name: "unmatched/i64x2",
exps: []uint64{math.MaxUint64, 123},
actual: []uint64{math.MaxUint64, 0},
laneTypes: map[int]laneType{0: laneTypeI64},
valueTypes: []wasm.ValueType{v128},
expMatched: false,
expValuesMsg: ` have [i64x2(0xffffffffffffffff, 0x0)]
want [i64x2(0xffffffffffffffff, 0x7b)]`,
},
{
name: "matched/f32x4",
exps: []uint64{
(uint64(math.Float32bits(float32(math.NaN()))) << 32) | uint64(math.Float32bits(float32(math.NaN()))),
(uint64(math.Float32bits(float32(math.NaN()))) << 32) | uint64(math.Float32bits(float32(math.NaN()))),
},
actual: []uint64{
(uint64(math.Float32bits(float32(math.NaN()))) << 32) | uint64(math.Float32bits(float32(math.NaN()))),
(uint64(math.Float32bits(float32(math.NaN()))) << 32) | uint64(math.Float32bits(float32(math.NaN()))),
},
valueTypes: []wasm.ValueType{v128},
laneTypes: map[int]laneType{0: laneTypeF32},
expMatched: true,
},
{
name: "unmatched/f32x4",
exps: []uint64{
(uint64(math.Float32bits(float32(1.213))) << 32) | uint64(math.Float32bits(float32(math.NaN()))),
(uint64(math.Float32bits(float32(math.NaN()))) << 32) | uint64(math.Float32bits(float32(math.NaN()))),
},
actual: []uint64{
(uint64(math.Float32bits(float32(math.NaN()))) << 32) | uint64(math.Float32bits(float32(math.Inf(1)))),
(uint64(math.Float32bits(float32(math.Inf(-1)))) << 32) | uint64(math.Float32bits(float32(math.NaN()))),
},
valueTypes: []wasm.ValueType{v128},
laneTypes: map[int]laneType{0: laneTypeF32},
expMatched: false,
expValuesMsg: ` have [f32x4(+Inf, NaN, NaN, -Inf)]
want [f32x4(NaN, 1.213000, NaN, NaN)]`,
},
{
name: "matched/f64x2",
exps: []uint64{math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{math.Float64bits(1.0), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{v128},
laneTypes: map[int]laneType{0: laneTypeF64},
expMatched: true,
},
{
name: "unmatched/f64x2",
exps: []uint64{math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{math.Float64bits(-1.0), math.Float64bits(math.Inf(1))},
valueTypes: []wasm.ValueType{v128},
laneTypes: map[int]laneType{0: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [f64x2(-1.000000, +Inf)]
want [f64x2(1.000000, NaN)]`,
},
{
name: "unmatched/f64x2",
exps: []uint64{math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())},
actual: []uint64{math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{v128},
laneTypes: map[int]laneType{0: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [f64x2(-Inf, NaN)]
want [f64x2(+Inf, NaN)]`,
},
{
name: "matched/[i32,f64x2]",
exps: []uint64{1, math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{1, math.Float64bits(1.0), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{i32, v128},
laneTypes: map[int]laneType{1: laneTypeF64},
expMatched: true,
},
{
name: "unmatched/[i32,f64x2]",
exps: []uint64{123, math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())},
actual: []uint64{123, math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{i32, v128},
laneTypes: map[int]laneType{1: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [123, f64x2(-Inf, NaN)]
want [123, f64x2(+Inf, NaN)]`,
},
{
name: "matched/[i32,f64x2]",
exps: []uint64{math.Float64bits(1.0), math.Float64bits(math.NaN()), 1},
actual: []uint64{math.Float64bits(1.0), math.Float64bits(math.NaN()), 1},
valueTypes: []wasm.ValueType{v128, i32},
laneTypes: map[int]laneType{0: laneTypeF64},
expMatched: true,
},
{
name: "unmatched/[f64x2,i32]",
exps: []uint64{math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN()), 123},
actual: []uint64{math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN()), 123},
valueTypes: []wasm.ValueType{v128, i32},
laneTypes: map[int]laneType{0: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [f64x2(-Inf, NaN), 123]
want [f64x2(+Inf, NaN), 123]`,
},
{
name: "matched/[f32,i32,f64x2]",
exps: []uint64{uint64(math.Float32bits(float32(math.NaN()))), math.Float64bits(1.0), math.Float64bits(math.NaN()), 1},
actual: []uint64{uint64(math.Float32bits(float32(math.NaN()))), math.Float64bits(1.0), math.Float64bits(math.NaN()), 1},
valueTypes: []wasm.ValueType{f32, v128, i32},
laneTypes: map[int]laneType{1: laneTypeF64},
expMatched: true,
},
{
name: "unmatched/[f32,f64x2,i32]",
exps: []uint64{uint64(math.Float32bits(1.0)), math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN()), 123},
actual: []uint64{uint64(math.Float32bits(1.0)), math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN()), 123},
valueTypes: []wasm.ValueType{f32, v128, i32},
laneTypes: map[int]laneType{1: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [1.000000, f64x2(-Inf, NaN), 123]
want [1.000000, f64x2(+Inf, NaN), 123]`,
},
{
name: "matched/[i8x16,f64x2]",
exps: []uint64{0, 0, math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{0, 0, math.Float64bits(1.0), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{v128, v128},
laneTypes: map[int]laneType{0: laneTypeI8, 1: laneTypeF64},
expMatched: true,
},
{
name: "unmatched/[i8x16,f64x2]",
exps: []uint64{0, 0xff << 56, math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{0, 0xaa << 56, math.Float64bits(1.0), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{v128, v128},
laneTypes: map[int]laneType{0: laneTypeI8, 1: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa), f64x2(1.000000, NaN)]
want [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff), f64x2(1.000000, NaN)]`,
},
{
name: "unmatched/[i8x16,f64x2]",
exps: []uint64{0, 0xff << 56, math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{0, 0xff << 56, math.Float64bits(1.0), math.Float64bits(math.Inf(1))},
valueTypes: []wasm.ValueType{v128, v128},
laneTypes: map[int]laneType{0: laneTypeI8, 1: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff), f64x2(1.000000, +Inf)]
want [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff), f64x2(1.000000, NaN)]`,
},
{
name: "matched/[i8x16,i32,f64x2]",
exps: []uint64{0, 0, math.MaxUint32, math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{0, 0, math.MaxUint32, math.Float64bits(1.0), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{v128, i32, v128},
laneTypes: map[int]laneType{0: laneTypeI8, 2: laneTypeF64},
expMatched: true,
},
{
name: "matched/[i8x16,i32,f64x2]",
exps: []uint64{0, 0, math.MaxUint32, math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{0, 0, math.MaxUint32 - 1, math.Float64bits(1.0), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{v128, i32, v128},
laneTypes: map[int]laneType{0: laneTypeI8, 2: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0), 4294967294, f64x2(1.000000, NaN)]
want [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0), 4294967295, f64x2(1.000000, NaN)]`,
},
{
name: "matched/[i8x16,i32,f64x2]",
exps: []uint64{0, 0, math.MaxUint32, math.Float64bits(1.0), math.Float64bits(math.NaN())},
actual: []uint64{0, 0xff << 16, math.MaxUint32, math.Float64bits(1.0), math.Float64bits(math.NaN())},
valueTypes: []wasm.ValueType{v128, i32, v128},
laneTypes: map[int]laneType{0: laneTypeI8, 2: laneTypeF64},
expMatched: false,
expValuesMsg: ` have [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0), 4294967295, f64x2(1.000000, NaN)]
want [i8x16(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0), 4294967295, f64x2(1.000000, NaN)]`,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
actualMatched, actualValuesMsg := valuesEq(tc.actual, tc.exps, tc.valueTypes, tc.laneTypes)
require.Equal(t, tc.expMatched, actualMatched)
require.Equal(t, tc.expValuesMsg, actualValuesMsg)
})
}
}
func TestCommandActionVal_toUint64s(t *testing.T) {
tests := []struct {
name string
rawCommandActionVal string
exp []uint64
}{
{
name: "i32",
rawCommandActionVal: `{"type": "i32", "value": "0"}`,
exp: []uint64{0},
},
{
name: "i32",
rawCommandActionVal: `{"type": "i32", "value": "4294967295"}`,
exp: []uint64{4294967295},
},
{
name: "i64",
rawCommandActionVal: `{"type": "i64", "value": "0"}`,
exp: []uint64{0},
},
{
name: "i64",
rawCommandActionVal: `{"type": "i64", "value": "7034535277573963776"}`,
exp: []uint64{7034535277573963776},
},
{
name: "f32",
rawCommandActionVal: `{"type": "f32", "value": "0"}`,
exp: []uint64{0},
},
{
name: "f32",
rawCommandActionVal: `{"type": "f32", "value": "2147483648"}`,
exp: []uint64{2147483648},
},
{
name: "f64",
rawCommandActionVal: `{"type": "f64", "value": "0"}`,
exp: []uint64{0},
},
{
name: "f64",
rawCommandActionVal: `{"type": "f64", "value": "4616189618054758400"}`,
exp: []uint64{4616189618054758400},
},
{
name: "f32x4",
rawCommandActionVal: `{"type": "v128", "lane_type": "f32", "value": ["645922816", "645922816", "645922816", "645922816"]}`,
exp: []uint64{645922816<<32 | 645922816, 645922816<<32 | 645922816},
},
{
name: "f32x4",
rawCommandActionVal: `{"type": "v128", "lane_type": "f32", "value": ["nan:canonical", "nan:arithmetic", "nan:canonical", "nan:arithmetic"]}`,
exp: []uint64{
uint64(math.Float32bits(float32(math.NaN()))) | (uint64(math.Float32bits(float32(math.NaN()))) << 32),
uint64(math.Float32bits(float32(math.NaN()))) | (uint64(math.Float32bits(float32(math.NaN()))) << 32),
},
},
{
name: "f64x2",
rawCommandActionVal: `{"type": "v128", "lane_type": "f64", "value": ["9223372036854775808", "9223372036854775808"]}`,
exp: []uint64{9223372036854775808, 9223372036854775808},
},
{
name: "f64x2",
rawCommandActionVal: `{"type": "v128", "lane_type": "f64", "value": ["nan:canonical", "nan:arithmetic"]}`,
exp: []uint64{math.Float64bits(math.NaN()), math.Float64bits(math.NaN())},
},
{
name: "i8x16",
rawCommandActionVal: `{"type": "v128", "lane_type": "i8", "value": ["128", "129", "130", "131", "253", "254", "255", "0", "0", "1", "2", "127", "128", "253", "254", "255"]}`,
exp: []uint64{
128 | (129 << 8) | (130 << 16) | (131 << 24) | (253 << 32) | (254 << 40) | (255 << 48),
1<<8 | 2<<16 | 127<<24 | 128<<32 | 253<<40 | 254<<48 | 255<<56,
},
},
{
name: "i16x8",
rawCommandActionVal: `{"type": "v128", "lane_type": "i16", "value": ["256", "770", "1284", "1798", "2312", "2826", "3340", "3854"]}`,
exp: []uint64{
256 | 770<<16 | 1284<<32 | 1798<<48,
2312 | 2826<<16 | 3340<<32 | 3854<<48,
},
},
{
name: "i32x4",
rawCommandActionVal: `{"type": "v128", "lane_type": "i32", "value": ["123", "32766", "32766", "40000"]}`,
exp: []uint64{
123 | 32766<<32,
32766 | 40000<<32,
},
},
{
name: "i64x2",
rawCommandActionVal: `{"type": "v128", "lane_type": "i64", "value": ["18446744073709551615", "123124"]}`,
exp: []uint64{
18446744073709551615,
123124,
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var c commandActionVal
err := json.Unmarshal([]byte(tc.rawCommandActionVal), &c)
require.NoError(t, err)
actual := c.toUint64s()
require.Equal(t, tc.exp, actual)
})
}
}
func TestCommand_getAssertReturnArgsExps(t *testing.T) {
tests := []struct {
name string
rawCommand string
args, exps []uint64
}{
{
name: "1",
rawCommand: `
{
"type": "assert_return",
"line": 148,
"action": {
"type": "invoke", "field": "f32x4.min",
"args": [
{"type": "v128", "lane_type": "f32", "value": ["2147483648", "123", "2147483648", "1"]},
{"type": "v128", "lane_type": "i8", "value": ["128", "129", "130", "131", "253", "254", "255", "0", "0", "1", "2", "127", "128", "253", "254", "255"]}
]
},
"expected": [
{"type": "v128", "lane_type": "f32", "value": ["2147483648", "0", "0", "2147483648"]}
]
}`,
args: []uint64{
123<<32 | 2147483648,
1<<32 | 2147483648,
128 | (129 << 8) | (130 << 16) | (131 << 24) | (253 << 32) | (254 << 40) | (255 << 48),
1<<8 | 2<<16 | 127<<24 | 128<<32 | 253<<40 | 254<<48 | 255<<56,
},
exps: []uint64{
2147483648,
2147483648 << 32,
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var c command
err := json.Unmarshal([]byte(tc.rawCommand), &c)
require.NoError(t, err)
actualArgs, actualExps := c.getAssertReturnArgsExps()
require.Equal(t, tc.args, actualArgs)
require.Equal(t, tc.exps, actualExps)
})
}
}

View File

@@ -4,7 +4,6 @@ import (
"embed"
"path"
"runtime"
"strings"
"testing"
"github.com/tetratelabs/wazero/internal/engine/compiler"
@@ -26,43 +25,28 @@ func TestCompiler(t *testing.T) {
}
spectest.Run(t, testcases, compiler.NewEngine, enabledFeatures, func(jsonname string) bool {
// TODO: remove after SIMD proposal
if strings.Contains(jsonname, "simd") {
switch path.Base(jsonname) {
case "simd_address.json", "simd_const.json", "simd_align.json", "simd_load16_lane.json", "simd_load32_lane.json",
"simd_load64_lane.json", "simd_load8_lane.json", "simd_lane.json", "simd_load_extend.json",
"simd_load_splat.json", "simd_load_zero.json", "simd_store.json", "simd_store16_lane.json",
"simd_store32_lane.json", "simd_store64_lane.json", "simd_store8_lane.json":
return true
case "simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json",
"simd_i8x16_cmp.json", "simd_i16x8_cmp.json", "simd_i32x4_cmp.json", "simd_i64x2_cmp.json",
"simd_f32x4_cmp.json", "simd_f64x2_cmp.json":
// TODO: implement on arm64.
return runtime.GOARCH == "amd64"
default:
return false // others not supported, yet!
}
switch path.Base(jsonname) {
case "simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json",
"simd_i8x16_cmp.json", "simd_i16x8_cmp.json", "simd_i32x4_cmp.json", "simd_i64x2_cmp.json",
"simd_f32x4_cmp.json", "simd_f64x2_cmp.json", "simd_f32x4_arith.json", "simd_f64x2_arith.json",
"simd_i16x8_arith.json", "simd_i64x2_arith.json", "simd_i32x4_arith.json", "simd_i8x16_arith.json",
"simd_i16x8_sat_arith.json", "simd_i8x16_sat_arith.json",
"simd_i16x8_arith2.json", "simd_i8x16_arith2.json", "simd_i32x4_arith2.json", "simd_i64x2_arith2.json",
"simd_f64x2.json", "simd_f32x4.json", "simd_f32x4_rounding.json", "simd_f64x2_rounding.json",
"simd_f64x2_pmin_pmax.json", "simd_f32x4_pmin_pmax.json", "simd_int_to_int_extend.json",
"simd_i64x2_extmul_i32x4.json", "simd_i32x4_extmul_i16x8.json", "simd_i16x8_extmul_i8x16.json",
"simd_i16x8_q15mulr_sat_s.json", "simd_i16x8_extadd_pairwise_i8x16.json", "simd_i32x4_extadd_pairwise_i16x8.json",
"simd_i32x4_dot_i16x8.json", "simd_i32x4_trunc_sat_f32x4.json",
"simd_splat.json", "simd_load.json", "simd_i32x4_trunc_sat_f64x2.json",
"simd_conversions.json":
// TODO: implement on arm64.
return runtime.GOARCH == "amd64"
default:
return true
}
return true
})
}
func TestInterpreter(t *testing.T) {
spectest.Run(t, testcases, interpreter.NewEngine, enabledFeatures, func(jsonname string) bool {
// TODO: remove after SIMD proposal
if strings.Contains(jsonname, "simd") {
switch path.Base(jsonname) {
case "simd_address.json", "simd_const.json", "simd_align.json", "simd_load16_lane.json",
"simd_load32_lane.json", "simd_load64_lane.json", "simd_load8_lane.json", "simd_lane.json",
"simd_load_extend.json", "simd_load_splat.json", "simd_load_zero.json", "simd_store.json",
"simd_store16_lane.json", "simd_store32_lane.json", "simd_store64_lane.json", "simd_store8_lane.json",
"simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json", "simd_i8x16_cmp.json", "simd_i16x8_cmp.json",
"simd_i32x4_cmp.json", "simd_i64x2_cmp.json", "simd_f32x4_cmp.json", "simd_f64x2_cmp.json":
return true
default:
return false // others not supported, yet!
}
}
return true
})
spectest.Run(t, testcases, interpreter.NewEngine, enabledFeatures, func(string) bool { return true })
}