diff --git a/api/wasm.go b/api/wasm.go index 8c101e7d..8af76301 100644 --- a/api/wasm.go +++ b/api/wasm.go @@ -59,6 +59,7 @@ func ExternTypeName(et ExternType) string { // * ValueTypeI64 - uint64(int64) // * ValueTypeF32 - EncodeF32 DecodeF32 from float32 // * ValueTypeF64 - EncodeF64 DecodeF64 from float64 +// * ValueTypeV128 TODO: // * ValueTypeExternref - unintptr(unsafe.Pointer(p)) where p is any pointer type in Go (e.g. *string) // // Ex. Given a Text Format type use (param i64) (result i64), no conversion is necessary. @@ -84,6 +85,15 @@ const ( ValueTypeF32 ValueType = 0x7d // ValueTypeF64 is a 64-bit floating point number. ValueTypeF64 ValueType = 0x7c + // ValueTypeV128 is a 128-bit vector value. + // + // The type corresponds to a 128 bit vector of packed integer or floating-point data. + // The packed data can be interpreted as signed or unsigned integers, single or double + // precision floating-point values, or a single 128 bit type. The interpretation is + // determined by individual operations. + // + // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/types.html#syntax-vectype + ValueTypeV128 ValueType = 0x7b // ValueTypeExternref is a externref type. // // Note: in wazero, externref type value are opaque raw 64-bit pointers, and the ValueTypeExternref type @@ -112,6 +122,8 @@ func ValueTypeName(t ValueType) string { return "f32" case ValueTypeF64: return "f64" + case ValueTypeV128: + return "v128" case ValueTypeExternref: return "externref" } diff --git a/builder_test.go b/builder_test.go index 57b123e0..91703f91 100644 --- a/builder_test.go +++ b/builder_test.go @@ -51,7 +51,7 @@ func TestNewModuleBuilder_Build(t *testing.T) { }, expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, + {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, }, FunctionSection: []wasm.Index{0}, HostFunctionSection: []*reflect.Value{&fnUint32_uint32}, @@ -70,7 +70,7 @@ func TestNewModuleBuilder_Build(t *testing.T) { }, expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, + {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, }, FunctionSection: []wasm.Index{0}, HostFunctionSection: []*reflect.Value{&fnUint64_uint32}, @@ -90,8 +90,8 @@ func TestNewModuleBuilder_Build(t *testing.T) { }, expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, - {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, + {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, + {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, }, FunctionSection: []wasm.Index{0, 1}, HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32}, @@ -114,8 +114,8 @@ func TestNewModuleBuilder_Build(t *testing.T) { }, expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, - {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, + {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, + {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, }, FunctionSection: []wasm.Index{0, 1}, HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32}, @@ -139,8 +139,8 @@ func TestNewModuleBuilder_Build(t *testing.T) { }, expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, - {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, + {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, + {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, }, FunctionSection: []wasm.Index{0, 1}, HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32}, diff --git a/internal/integration_test/spectest/spectest.go b/internal/integration_test/spectest/spectest.go index 485c0187..c6b788ef 100644 --- a/internal/integration_test/spectest/spectest.go +++ b/internal/integration_test/spectest/spectest.go @@ -65,8 +65,9 @@ type ( } commandActionVal struct { - ValType string `json:"type"` - Value string `json:"value"` + ValType string `json:"type"` + LaneType string `json:"lane_type"` + Value interface{} `json:"value"` } ) @@ -74,20 +75,20 @@ func (c commandActionVal) String() string { var v string switch c.ValType { case "i32": - v = c.Value + v = c.Value.(string) case "f32": - ret, _ := strconv.ParseUint(c.Value, 10, 32) + ret, _ := strconv.ParseUint(c.Value.(string), 10, 32) v = fmt.Sprintf("%f", math.Float32frombits(uint32(ret))) case "i64": - v = c.Value + v = c.Value.(string) case "f64": - ret, _ := strconv.ParseUint(c.Value, 10, 64) + ret, _ := strconv.ParseUint(c.Value.(string), 10, 64) v = fmt.Sprintf("%f", math.Float64frombits(ret)) case "externref": if c.Value == "null" { v = "null" } else { - original, _ := strconv.ParseUint(c.Value, 10, 64) + original, _ := strconv.ParseUint(c.Value.(string), 10, 64) // In wazero, externref is opaque pointer, so "0" is considered as null. // So in order to treat "externref 0" in spectest non nullref, we increment the value. v = fmt.Sprintf("%d", original+1) @@ -95,6 +96,16 @@ func (c commandActionVal) String() string { case "funcref": // All the in and out funcref params are null in spectest (cannot represent non-null as it depends on runtime impl). v = "null" + case "v128": + simdValues, ok := c.Value.([]interface{}) + if !ok { + panic("BUG") + } + var strs []string + for _, v := range simdValues { + strs = append(strs, v.(string)) + } + v = strings.Join(strs, ",") } return fmt.Sprintf("{type: %s, value: %v}", c.ValType, v) } @@ -136,7 +147,7 @@ func (c command) String() string { func (c command) getAssertReturnArgs() []uint64 { var args []uint64 for _, arg := range c.Action.Args { - args = append(args, arg.toUint64()) + args = append(args, arg.toUint64s()...) } return args } @@ -144,16 +155,61 @@ func (c command) getAssertReturnArgs() []uint64 { func (c command) getAssertReturnArgsExps() ([]uint64, []uint64) { var args, exps []uint64 for _, arg := range c.Action.Args { - args = append(args, arg.toUint64()) + args = append(args, arg.toUint64s()...) } for _, exp := range c.Exps { - exps = append(exps, exp.toUint64()) + exps = append(exps, exp.toUint64s()...) } return args, exps } +func (c commandActionVal) toUint64s() (ret []uint64) { + if c.ValType == "v128" { + strValues, ok := c.Value.([]interface{}) + if !ok { + panic("BUG") + } + var low, high uint64 + var width, valNum int + switch c.LaneType { + case "i8": + width, valNum = 8, 16 + case "i16": + width, valNum = 16, 8 + case "i32": + width, valNum = 32, 4 + case "i64": + width, valNum = 64, 2 + case "f32": + width, valNum = 32, 4 + case "f64": + width, valNum = 64, 2 + 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} + } else { + return []uint64{c.toUint64()} + } +} + func (c commandActionVal) toUint64() (ret uint64) { - if strings.Contains(c.Value, "nan") { + strValue := c.Value.(string) + if strings.Contains(strValue, "nan") { if c.ValType == "f32" { return uint64(math.Float32bits(float32(math.NaN()))) } @@ -162,15 +218,15 @@ func (c commandActionVal) toUint64() (ret uint64) { if c.Value == "null" { ret = 0 } else { - original, _ := strconv.ParseUint(c.Value, 10, 64) + original, _ := strconv.ParseUint(strValue, 10, 64) // In wazero, externref is opaque pointer, so "0" is considered as null. // So in order to treat "externref 0" in spectest non nullref, we increment the value. ret = original + 1 } } else if strings.Contains(c.ValType, "32") { - ret, _ = strconv.ParseUint(c.Value, 10, 32) + ret, _ = strconv.ParseUint(strValue, 10, 32) } else { - ret, _ = strconv.ParseUint(c.Value, 10, 64) + ret, _ = strconv.ParseUint(strValue, 10, 64) } return } @@ -298,7 +354,10 @@ func maybeSetMemoryCap(mod *wasm.Module) { // Run runs all the test inside the testDataFS file system where all the cases are described // via JSON files created from wast2json. -func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.Engine, enabledFeatures wasm.Features) { +// +// filter is a callback which is called with the target json file name and should return true if the engine wants to run tests against it, false otherwise. +// TODO: remove filter after SIMD completion. +func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.Engine, enabledFeatures wasm.Features, filter func(jsonname string) bool) { files, err := testDataFS.ReadDir("testdata") require.NoError(t, err) @@ -315,8 +374,7 @@ func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.E require.True(t, len(jsonfiles) > 1, "len(jsonfiles)=%d (not greater than one)", len(jsonfiles)) for _, f := range jsonfiles { - if strings.Contains(f, "simd") { - // TODO: enable after SIMD proposal + if !filter(f) { continue } raw, err := testDataFS.ReadFile(f) @@ -379,10 +437,7 @@ func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.E vals, types, err := callFunction(store, moduleName, c.Action.Field, args...) require.NoError(t, err, msg) require.Equal(t, len(exps), len(vals), msg) - require.Equal(t, len(exps), len(types), msg) - for i, exp := range exps { - requireValueEq(t, vals[i], exp, types[i], msg) - } + requireValuesEq(t, vals, exps, types, msg) case "get": _, exps := c.getAssertReturnArgsExps() require.Equal(t, 1, len(exps)) @@ -411,13 +466,12 @@ func Run(t *testing.T, testDataFS embed.FS, newEngine func(wasm.Features) wasm.E t.Fatalf("unsupported action type type: %v", c) } case "assert_malformed": - if c.ModuleType == "text" { + 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) + requireInstantiationError(t, store, buf, msg) } - buf, err := testDataFS.ReadFile(testdataPath(c.Filename)) - require.NoError(t, err, msg) - requireInstantiationError(t, store, buf, msg) case "assert_trap": moduleName := lastInstantiatedModuleName if c.Action.Module != "" { @@ -539,6 +593,23 @@ 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) + } + } + + 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) + } +} + func requireValueEq(t *testing.T, actual, expected uint64, valType wasm.ValueType, msg string) { switch valType { case wasm.ValueTypeI32: diff --git a/internal/integration_test/spectest/v1/spec_test.go b/internal/integration_test/spectest/v1/spec_test.go index e9391843..62679528 100644 --- a/internal/integration_test/spectest/v1/spec_test.go +++ b/internal/integration_test/spectest/v1/spec_test.go @@ -22,11 +22,11 @@ func TestJIT(t *testing.T) { t.Skip() } - spectest.Run(t, testcases, jit.NewEngine, enabledFeatures) + spectest.Run(t, testcases, jit.NewEngine, enabledFeatures, func(string) bool { return true }) } func TestInterpreter(t *testing.T) { - spectest.Run(t, testcases, interpreter.NewEngine, enabledFeatures) + spectest.Run(t, testcases, interpreter.NewEngine, enabledFeatures, func(jsonname string) bool { return true }) } func TestBinaryEncoder(t *testing.T) { diff --git a/internal/integration_test/spectest/v2/spec_test.go b/internal/integration_test/spectest/v2/spec_test.go index 05d5a32e..165cc887 100644 --- a/internal/integration_test/spectest/v2/spec_test.go +++ b/internal/integration_test/spectest/v2/spec_test.go @@ -2,7 +2,9 @@ package spectest import ( "embed" + "path" "runtime" + "strings" "testing" "github.com/tetratelabs/wazero/internal/integration_test/spectest" @@ -22,9 +24,18 @@ func TestJIT(t *testing.T) { t.Skip() } - spectest.Run(t, testcases, jit.NewEngine, enabledFeatures) + spectest.Run(t, testcases, jit.NewEngine, enabledFeatures, func(jsonname string) bool { + // TODO: remove after SIMD proposal + return !strings.Contains(jsonname, "simd") + }) } func TestInterpreter(t *testing.T) { - spectest.Run(t, testcases, interpreter.NewEngine, enabledFeatures) + spectest.Run(t, testcases, interpreter.NewEngine, enabledFeatures, func(jsonname string) bool { + // TODO: remove after SIMD proposal + if strings.Contains(jsonname, "simd") { + return path.Base(jsonname) == "simd_const.json" + } + return true + }) } diff --git a/internal/integration_test/vs/codec.go b/internal/integration_test/vs/codec.go index c23d4c37..a1aee16b 100644 --- a/internal/integration_test/vs/codec.go +++ b/internal/integration_test/vs/codec.go @@ -24,12 +24,12 @@ func newExample() *wasm.Module { f32, i32, i64 := wasm.ValueTypeF32, wasm.ValueTypeI32, wasm.ValueTypeI64 return &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}, - {}, - {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}}, - {Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}}, - {Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}}, - {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}, + {ParamNumInUint64: 0, ResultNumInUint64: 0}, + {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1}, + {Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}, ParamNumInUint64: 1, ResultNumInUint64: 1}, + {Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}, }, ImportSection: []*wasm.Import{ { diff --git a/internal/testing/enginetest/enginetest.go b/internal/testing/enginetest/enginetest.go index 6b648f4c..165b34dd 100644 --- a/internal/testing/enginetest/enginetest.go +++ b/internal/testing/enginetest/enginetest.go @@ -132,7 +132,7 @@ func RunTestModuleEngine_Call(t *testing.T, et EngineTester) { // Define a basic function which defines one parameter. This is used to test results when incorrect arity is used. i64 := wasm.ValueTypeI64 m := &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}, ParamNumInUint64: 1, ResultNumInUint64: 1}}, FunctionSection: []uint32{0}, CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}, LocalTypes: []wasm.ValueType{wasm.ValueTypeI64}}}, } @@ -353,8 +353,9 @@ func runTestModuleEngine_Call_HostFn_ModuleContext(t *testing.T, et EngineTester e := et.NewEngine(features) sig := &wasm.FunctionType{ - Params: []wasm.ValueType{wasm.ValueTypeI64}, - Results: []wasm.ValueType{wasm.ValueTypeI64}, + Params: []wasm.ValueType{wasm.ValueTypeI64}, + Results: []wasm.ValueType{wasm.ValueTypeI64}, + ParamNumInUint64: 1, ResultNumInUint64: 1, } memory := &wasm.MemoryInstance{} @@ -588,7 +589,7 @@ func RunTestModuleEngine_Memory(t *testing.T, et EngineTester) { // Define a basic function which defines one parameter. This is used to test results when incorrect arity is used. one := uint32(1) m := &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}, {}}, + TypeSection: []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}, ParamNumInUint64: 1}, {}}, FunctionSection: []wasm.Index{0, 1}, MemorySection: &wasm.Memory{Min: 1, Cap: 1, Max: 2}, DataSection: []*wasm.DataSegment{ @@ -706,7 +707,7 @@ func divBy(d uint32) uint32 { func setupCallTests(t *testing.T, e wasm.Engine) (*wasm.ModuleInstance, *wasm.ModuleInstance, *wasm.ModuleInstance, func()) { i32 := wasm.ValueTypeI32 - ft := &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}} + ft := &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1} hostFnVal := reflect.ValueOf(divBy) hostFnModule := &wasm.Module{ diff --git a/internal/wasm/binary/code.go b/internal/wasm/binary/code.go index a5fb60fe..8264c222 100644 --- a/internal/wasm/binary/code.go +++ b/internal/wasm/binary/code.go @@ -48,7 +48,7 @@ func decodeCode(r *bytes.Reader) (*wasm.Code, error) { } switch vt := b; vt { case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64, - wasm.ValueTypeFuncref, wasm.ValueTypeExternref: + wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128: types = append(types, vt) default: return nil, fmt.Errorf("invalid local type: 0x%x", vt) diff --git a/internal/wasm/binary/const_expr.go b/internal/wasm/binary/const_expr.go index f410213d..e4a8bb56 100644 --- a/internal/wasm/binary/const_expr.go +++ b/internal/wasm/binary/const_expr.go @@ -48,6 +48,28 @@ func decodeConstantExpression(r *bytes.Reader, enabledFeatures wasm.Features) (* } // Parsing index. _, _, err = leb128.DecodeUint32(r) + case wasm.OpcodeVecPrefix: + if err := enabledFeatures.Require(wasm.FeatureSIMD); err != nil { + return nil, fmt.Errorf("vector instructions are not supported as %w", err) + } + opcode, err = r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read vector instruction opcode suffix: %w", err) + } + + if opcode != wasm.OpcodeVecV128Const { + return nil, fmt.Errorf("invalid vector opcode for const expression: %#x", opcode) + } + + remainingBeforeData = int64(r.Len()) + offsetAtData = r.Size() - remainingBeforeData + + n, err := r.Read(make([]byte, 16)) + if err != nil { + return nil, fmt.Errorf("read vector const instruction immediates: %w", err) + } else if n != 16 { + return nil, fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n) + } default: return nil, fmt.Errorf("%v for const expression opt code: %#x", ErrInvalidByte, b) } diff --git a/internal/wasm/binary/const_expr_test.go b/internal/wasm/binary/const_expr_test.go index b516f903..2c55b68c 100644 --- a/internal/wasm/binary/const_expr_test.go +++ b/internal/wasm/binary/const_expr_test.go @@ -62,10 +62,27 @@ func TestDecodeConstantExpression(t *testing.T) { }, }, }, + { + in: []byte{ + wasm.OpcodeVecPrefix, + wasm.OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + wasm.OpcodeEnd, + }, + exp: &wasm.ConstantExpression{ + Opcode: wasm.OpcodeVecV128Const, + Data: []byte{ + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + }, + }, } { tc := tc t.Run(strconv.Itoa(i), func(t *testing.T) { - actual, err := decodeConstantExpression(bytes.NewReader(tc.in), wasm.FeatureBulkMemoryOperations) + actual, err := decodeConstantExpression(bytes.NewReader(tc.in), + wasm.FeatureBulkMemoryOperations|wasm.FeatureSIMD) require.NoError(t, err) require.Equal(t, tc.exp, actual) }) @@ -120,6 +137,43 @@ func TestDecodeConstantExpression_errors(t *testing.T) { expectedErr: "ref.func is not supported as feature \"bulk-memory-operations\" is disabled", features: wasm.Features20191205, }, + { + in: []byte{ + wasm.OpcodeVecPrefix, + wasm.OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + wasm.OpcodeEnd, + }, + expectedErr: "vector instructions are not supported as feature \"simd\" is disabled", + features: wasm.Features20191205, + }, + { + in: []byte{ + wasm.OpcodeVecPrefix, + }, + expectedErr: "read vector instruction opcode suffix: EOF", + features: wasm.FeatureSIMD, + }, + { + in: []byte{ + wasm.OpcodeVecPrefix, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + wasm.OpcodeEnd, + }, + expectedErr: "invalid vector opcode for const expression: 0x1", + features: wasm.FeatureSIMD, + }, + { + in: []byte{ + wasm.OpcodeVecPrefix, + wasm.OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + expectedErr: "read vector const instruction immediates: needs 16 bytes but was 8 bytes", + features: wasm.FeatureSIMD, + }, } { t.Run(tc.expectedErr, func(t *testing.T) { _, err := decodeConstantExpression(bytes.NewReader(tc.in), tc.features) diff --git a/internal/wasm/binary/decoder_test.go b/internal/wasm/binary/decoder_test.go index a55c28fc..68efa1d9 100644 --- a/internal/wasm/binary/decoder_test.go +++ b/internal/wasm/binary/decoder_test.go @@ -30,8 +30,14 @@ func TestDecodeModule(t *testing.T) { input: &wasm.Module{ TypeSection: []*wasm.FunctionType{ {}, - {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}, - {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 2, + ResultNumInUint64: 1, + }, + {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 4, + ResultNumInUint64: 1, + }, }, }, }, @@ -39,8 +45,14 @@ func TestDecodeModule(t *testing.T) { name: "type and import section", input: &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}, - {Params: []wasm.ValueType{f32, f32}, Results: []wasm.ValueType{f32}}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 2, + ResultNumInUint64: 1, + }, + {Params: []wasm.ValueType{f32, f32}, Results: []wasm.ValueType{f32}, + ParamNumInUint64: 2, + ResultNumInUint64: 1, + }, }, ImportSection: []*wasm.Import{ { diff --git a/internal/wasm/binary/function.go b/internal/wasm/binary/function.go index 8c01f370..67d4a2b9 100644 --- a/internal/wasm/binary/function.go +++ b/internal/wasm/binary/function.go @@ -92,8 +92,11 @@ func decodeFunctionType(enabledFeatures wasm.Features, r *bytes.Reader) (*wasm.F return nil, fmt.Errorf("could not read result types: %w", err) } - return &wasm.FunctionType{ + ret := &wasm.FunctionType{ Params: paramTypes, Results: resultTypes, - }, nil + } + + ret.CacheNumInUint64() + return ret, nil } diff --git a/internal/wasm/binary/function_test.go b/internal/wasm/binary/function_test.go index 8acbdf32..efeb30af 100644 --- a/internal/wasm/binary/function_test.go +++ b/internal/wasm/binary/function_test.go @@ -23,52 +23,52 @@ func TestFunctionType(t *testing.T) { }, { name: "one param no result", - input: &wasm.FunctionType{Params: []wasm.ValueType{i32}}, + input: &wasm.FunctionType{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}, expected: []byte{0x60, 1, i32, 0}, }, { name: "no param one result", - input: &wasm.FunctionType{Results: []wasm.ValueType{i32}}, + input: &wasm.FunctionType{Results: []wasm.ValueType{i32}, ResultNumInUint64: 1}, expected: []byte{0x60, 0, 1, i32}, }, { name: "one param one result", - input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}}, + input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 1, ResultNumInUint64: 1}, expected: []byte{0x60, 1, i64, 1, i32}, }, { name: "two params no result", - input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}}, + input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, ParamNumInUint64: 2}, expected: []byte{0x60, 2, i32, i64, 0}, }, { name: "two param one result", - input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32}}, + input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}, expected: []byte{0x60, 2, i32, i64, 1, i32}, }, { name: "no param two results", - input: &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}}, + input: &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}, ResultNumInUint64: 2}, expected: []byte{0x60, 0, 2, i32, i64}, }, { name: "one param two results", - input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32, i64}}, + input: &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i32, i64}, ParamNumInUint64: 1, ResultNumInUint64: 2}, 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}}, + input: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32, i64}, ParamNumInUint64: 2, ResultNumInUint64: 2}, expected: []byte{0x60, 2, i32, i64, 2, i32, i64}, }, { name: "two param two results with funcrefs", - input: &wasm.FunctionType{Params: []wasm.ValueType{i32, funcRef}, Results: []wasm.ValueType{funcRef, i64}}, + input: &wasm.FunctionType{Params: []wasm.ValueType{i32, funcRef}, Results: []wasm.ValueType{funcRef, i64}, ParamNumInUint64: 2, ResultNumInUint64: 2}, expected: []byte{0x60, 2, i32, funcRef, 2, funcRef, i64}, }, { name: "two param two results with externrefs", - input: &wasm.FunctionType{Params: []wasm.ValueType{i32, externRef}, Results: []wasm.ValueType{externRef, i64}}, + input: &wasm.FunctionType{Params: []wasm.ValueType{i32, externRef}, Results: []wasm.ValueType{externRef, i64}, ParamNumInUint64: 2, ResultNumInUint64: 2}, expected: []byte{0x60, 2, i32, externRef, 2, externRef, i64}, }, } diff --git a/internal/wasm/binary/value.go b/internal/wasm/binary/value.go index ed2568d0..123db09b 100644 --- a/internal/wasm/binary/value.go +++ b/internal/wasm/binary/value.go @@ -20,6 +20,7 @@ var encodedValTypes = map[wasm.ValueType][]byte{ wasm.ValueTypeF64: {1, wasm.ValueTypeF64}, wasm.ValueTypeExternref: {1, wasm.ValueTypeExternref}, wasm.ValueTypeFuncref: {1, wasm.ValueTypeFuncref}, + wasm.ValueTypeV128: {1, wasm.ValueTypeV128}, } // encodeValTypes fast paths binary encoding of common value type lengths @@ -58,7 +59,7 @@ func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) { for i, v := range buf { switch v { case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64, - wasm.ValueTypeExternref, wasm.ValueTypeFuncref: + wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128: ret[i] = v default: return nil, fmt.Errorf("invalid value type: %d", v) diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go index c57db4b7..e54fec02 100644 --- a/internal/wasm/func_validation.go +++ b/internal/wasm/func_validation.go @@ -1044,7 +1044,7 @@ func (m *Module) validateFunctionWithMaxStackValues( } } else if op == OpcodeVecPrefix { pc++ - // Vector instructions come with two bytes which starts with OpcodeVecPrefix, + // Vector instructions come with two bytes where the first byte is always OpcodeVecPrefix, // and the second byte determines the actual instruction. vecOpcode := body[pc] if err := enabledFeatures.Require(FeatureSIMD); err != nil { @@ -1052,6 +1052,27 @@ func (m *Module) validateFunctionWithMaxStackValues( } switch vecOpcode { + case OpcodeVecV128Const: + // Read 128-bit = 16 bytes constants + if int(pc+16) >= len(body) { + return fmt.Errorf("cannot read constant vector value for %s", vectorInstructionName[vecOpcode]) + } + pc += 16 + valueTypeStack.push(ValueTypeV128) + case OpcodeVecI32x4Add: + for i := 0; i < 2; i++ { + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecI32x4AddName, err) + } + } + valueTypeStack.push(ValueTypeV128) + case OpcodeVecI64x2Add: + for i := 0; i < 2; i++ { + if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil { + return fmt.Errorf("cannot pop the operand for %s: %v", OpcodeVecI64x2AddName, err) + } + } + valueTypeStack.push(ValueTypeV128) default: return fmt.Errorf("TODO: SIMD instruction %s will be implemented in #506", vectorInstructionName[vecOpcode]) } @@ -1470,22 +1491,13 @@ type controlBlock struct { op Opcode } -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, enabledFeatures) -} - -// decodeBlockTypeImpl decodes the type index from a positive 33-bit signed integer. Negative numbers indicate up to one +// DecodeBlockType 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) { +func DecodeBlockType(types []*FunctionType, 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) @@ -1496,22 +1508,27 @@ func decodeBlockTypeImpl(functionTypeResolver func(index int64) (*FunctionType, case -64: // 0x40 in original byte = nil ret = &FunctionType{} case -1: // 0x7f in original byte = i32 - ret = &FunctionType{Results: []ValueType{ValueTypeI32}} + ret = &FunctionType{Results: []ValueType{ValueTypeI32}, ResultNumInUint64: 1} case -2: // 0x7e in original byte = i64 - ret = &FunctionType{Results: []ValueType{ValueTypeI64}} + ret = &FunctionType{Results: []ValueType{ValueTypeI64}, ResultNumInUint64: 1} case -3: // 0x7d in original byte = f32 - ret = &FunctionType{Results: []ValueType{ValueTypeF32}} + ret = &FunctionType{Results: []ValueType{ValueTypeF32}, ResultNumInUint64: 1} case -4: // 0x7c in original byte = f64 - ret = &FunctionType{Results: []ValueType{ValueTypeF64}} + ret = &FunctionType{Results: []ValueType{ValueTypeF64}, ResultNumInUint64: 1} + case -5: // 0x7b in original byte = f64 + ret = &FunctionType{Results: []ValueType{ValueTypeV128}, ResultNumInUint64: 2} case -16: // 0x70 in original byte = funcref - ret = &FunctionType{Results: []ValueType{ValueTypeExternref}} + ret = &FunctionType{Results: []ValueType{ValueTypeExternref}, ResultNumInUint64: 1} case -17: // 0x6f in original byte = externref - ret = &FunctionType{Results: []ValueType{ValueTypeExternref}} + ret = &FunctionType{Results: []ValueType{ValueTypeExternref}, ResultNumInUint64: 1} 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) + if raw < 0 || (raw >= int64(len(types))) { + return nil, 0, fmt.Errorf("type index out of range: %d", raw) + } + ret = types[raw] } return ret, num, err } diff --git a/internal/wasm/func_validation_test.go b/internal/wasm/func_validation_test.go index 0b81160b..f1a7b1c5 100644 --- a/internal/wasm/func_validation_test.go +++ b/internal/wasm/func_validation_test.go @@ -2655,6 +2655,71 @@ func TestModule_funcValidation_Select_error(t *testing.T) { } } +func TestModule_funcValidation_SIMD(t *testing.T) { + for _, tc := range []struct { + name string + body []byte + expectedErr string + }{ + { + name: "v128.const", + body: []byte{ + OpcodeVecPrefix, + OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + OpcodeDrop, + OpcodeEnd, + }, + }, + { + name: "i32x4.add", + body: []byte{ + OpcodeVecPrefix, + OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + OpcodeVecPrefix, + OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + OpcodeVecPrefix, + OpcodeVecI32x4Add, + OpcodeDrop, + OpcodeEnd, + }, + }, + { + name: "i64x2.add", + body: []byte{ + OpcodeVecPrefix, + OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + OpcodeVecPrefix, + OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + OpcodeVecPrefix, + OpcodeVecI64x2Add, + OpcodeDrop, + OpcodeEnd, + }, + }, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + m := &Module{ + TypeSection: []*FunctionType{v_v}, + FunctionSection: []Index{0}, + CodeSection: []*Code{{Body: tc.body}}, + } + err := m.validateFunction(FeatureSIMD, 0, []Index{0}, nil, nil, nil, nil) + require.NoError(t, err) + }) + } +} + func TestModule_funcValidation_SIMD_error(t *testing.T) { for _, tc := range []struct { name string @@ -2671,6 +2736,47 @@ func TestModule_funcValidation_SIMD_error(t *testing.T) { flag: Features20191205, expectedErr: "f32x4.abs invalid as feature \"simd\" is disabled", }, + { + name: "v128.const immediate", + body: []byte{ + OpcodeVecPrefix, + OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + }, + flag: FeatureSIMD, + expectedErr: "cannot read constant vector value for v128.const", + }, + { + name: "i32x4.add operand", + body: []byte{ + OpcodeVecPrefix, + OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + OpcodeVecPrefix, + OpcodeVecI32x4Add, + OpcodeDrop, + OpcodeEnd, + }, + flag: FeatureSIMD, + expectedErr: "cannot pop the operand for i32x4.add: v128 missing", + }, + { + name: "i64x2.add operand", + body: []byte{ + OpcodeVecPrefix, + OpcodeVecV128Const, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + OpcodeVecPrefix, + OpcodeVecI64x2Add, + OpcodeDrop, + OpcodeEnd, + }, + flag: FeatureSIMD, + expectedErr: "cannot pop the operand for i64x2.add: v128 missing", + }, { // TODO delete this case after SIMD impl completion. name: "unimplemented", diff --git a/internal/wasm/gofunc.go b/internal/wasm/gofunc.go index ccd48c52..98747342 100644 --- a/internal/wasm/gofunc.go +++ b/internal/wasm/gofunc.go @@ -178,6 +178,7 @@ func getFunctionType(fn *reflect.Value, enabledFeatures Features) (fk FunctionKi } ft = &FunctionType{Params: make([]ValueType, p.NumIn()-pOffset), Results: make([]ValueType, rCount)} + ft.CacheNumInUint64() for i := 0; i < len(ft.Params); i++ { pI := p.In(i + pOffset) diff --git a/internal/wasm/gofunc_test.go b/internal/wasm/gofunc_test.go index 386564b8..a090783a 100644 --- a/internal/wasm/gofunc_test.go +++ b/internal/wasm/gofunc_test.go @@ -49,7 +49,7 @@ func TestGetFunctionType(t *testing.T) { name: "all supported params and i32 result", inputFunc: func(uint32, uint64, float32, float64, uintptr) uint32 { return 0 }, expectedKind: FunctionKindGoNoContext, - expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}}, + expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1}, }, { name: "all supported params and all supported results", @@ -58,27 +58,28 @@ func TestGetFunctionType(t *testing.T) { }, expectedKind: FunctionKindGoNoContext, expectedType: &FunctionType{ - Params: []ValueType{i32, i64, f32, f64, externref}, - Results: []ValueType{i32, i64, f32, f64, externref}, + Params: []ValueType{i32, i64, f32, f64, externref}, + Results: []ValueType{i32, i64, f32, f64, externref}, + ParamNumInUint64: 5, ResultNumInUint64: 5, }, }, { name: "all supported params and i32 result - wasm.Module", inputFunc: func(api.Module, uint32, uint64, float32, float64, uintptr) uint32 { return 0 }, expectedKind: FunctionKindGoModule, - expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}}, + expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1}, }, { name: "all supported params and i32 result - context.Context", inputFunc: func(context.Context, uint32, uint64, float32, float64, uintptr) uint32 { return 0 }, expectedKind: FunctionKindGoContext, - expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}}, + expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1}, }, { name: "all supported params and i32 result - context.Context and api.Module", inputFunc: func(context.Context, api.Module, uint32, uint64, float32, float64, uintptr) uint32 { return 0 }, expectedKind: FunctionKindGoContextModule, - expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}}, + expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}, ParamNumInUint64: 5, ResultNumInUint64: 1}, }, } for _, tt := range tests { diff --git a/internal/wasm/host_test.go b/internal/wasm/host_test.go index ce12c932..5ee672a3 100644 --- a/internal/wasm/host_test.go +++ b/internal/wasm/host_test.go @@ -65,8 +65,8 @@ func TestNewHostModule(t *testing.T) { }, expected: &Module{ TypeSection: []*FunctionType{ - {Params: []ValueType{i32, i32}, Results: []ValueType{i32}}, - {Params: []ValueType{i32, i32, i32, i32}, Results: []ValueType{i32}}, + {Params: []ValueType{i32, i32}, Results: []ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}, + {Params: []ValueType{i32, i32, i32, i32}, Results: []ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1}, }, FunctionSection: []Index{0, 1}, HostFunctionSection: []*reflect.Value{&fnArgsSizesGet, &fnFdWrite}, @@ -90,7 +90,7 @@ func TestNewHostModule(t *testing.T) { functionSwap: swap, }, expected: &Module{ - TypeSection: []*FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}}}, + TypeSection: []*FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}}, FunctionSection: []Index{0}, HostFunctionSection: []*reflect.Value{&fnSwap}, ExportSection: []*Export{{Name: "swap", Type: ExternTypeFunc, Index: 0}}, @@ -151,7 +151,7 @@ func TestNewHostModule(t *testing.T) { }, expected: &Module{ TypeSection: []*FunctionType{ - {Params: []ValueType{i32, i32}, Results: []ValueType{i32}}, + {Params: []ValueType{i32, i32}, Results: []ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}, }, FunctionSection: []Index{0}, HostFunctionSection: []*reflect.Value{&fnArgsSizesGet}, diff --git a/internal/wasm/interpreter/interpreter.go b/internal/wasm/interpreter/interpreter.go index 01fb9f18..17a01a53 100644 --- a/internal/wasm/interpreter/interpreter.go +++ b/internal/wasm/interpreter/interpreter.go @@ -575,6 +575,12 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) { case *wazeroir.OperationTableFill: op.us = make([]uint64, 1) op.us[0] = uint64(o.TableIndex) + case *wazeroir.OperationConstV128: + op.us = make([]uint64, 2) + op.us[0] = o.Lo + op.us[1] = o.Hi + case *wazeroir.OperationI32x4Add: + case *wazeroir.OperationI64x2Add: default: return nil, fmt.Errorf("unreachable: a bug in wazeroir engine") } @@ -636,10 +642,10 @@ func (me *moduleEngine) Call(ctx context.Context, m *wasm.CallContext, f *wasm.F return } - paramSignature := f.Type.Params + paramSignature := f.Type.ParamNumInUint64 paramCount := len(params) - if len(paramSignature) != paramCount { - return nil, fmt.Errorf("expected %d params, but passed %d", len(paramSignature), paramCount) + if paramSignature != paramCount { + return nil, fmt.Errorf("expected %d params, but passed %d", paramSignature, paramCount) } ce := me.newCallEngine() @@ -670,7 +676,7 @@ func (me *moduleEngine) Call(ctx context.Context, m *wasm.CallContext, f *wasm.F ce.pushValue(param) } ce.callNativeFunc(ctx, m, compiled) - results = wasm.PopValues(len(f.Type.Results), ce.popValue) + results = wasm.PopValues(f.Type.ResultNumInUint64, ce.popValue) if f.FunctionListener != nil { // TODO: This doesn't get the error due to use of panic to propagate them. f.FunctionListener.After(ctx, nil, results) @@ -815,11 +821,17 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont { g := globals[op.us[0]] // TODO: Not yet traceable as it doesn't use the types in global.go ce.pushValue(g.Val) + if g.Type.ValType == wasm.ValueTypeV128 { + ce.pushValue(g.ValHi) + } frame.pc++ } case wazeroir.OperationKindGlobalSet: { g := globals[op.us[0]] // TODO: Not yet traceable as it doesn't use the types in global.go + if g.Type.ValType == wasm.ValueTypeV128 { + g.ValHi = ce.popValue() + } g.Val = ce.popValue() frame.pc++ } @@ -1923,6 +1935,23 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont } } frame.pc++ + case wazeroir.OperationKindConstV128: + lo, hi := op.us[0], op.us[1] + ce.pushValue(lo) + ce.pushValue(hi) + frame.pc++ + case wazeroir.OperationKindI32x4Add: + xHigh, xLow := ce.popValue(), ce.popValue() + yHigh, yLow := ce.popValue(), ce.popValue() + ce.pushValue(uint64((uint64(uint32(xLow>>32+yLow>>32)) << 32)) + uint64((uint32(xLow) + uint32(yLow)))) + ce.pushValue(uint64((uint64(uint32(xHigh>>32+yHigh>>32)) << 32)) + uint64((uint32(xHigh) + uint32(yHigh)))) + frame.pc++ + case wazeroir.OperationKindI64x2Add: + xHigh, xLow := ce.popValue(), ce.popValue() + yHigh, yLow := ce.popValue(), ce.popValue() + ce.pushValue(xLow + yLow) + ce.pushValue(xHigh + yHigh) + frame.pc++ } } ce.popFrame() diff --git a/internal/wasm/jit/engine.go b/internal/wasm/jit/engine.go index 74566d8b..99a26f03 100644 --- a/internal/wasm/jit/engine.go +++ b/internal/wasm/jit/engine.go @@ -2,6 +2,7 @@ package jit import ( "context" + "errors" "fmt" "reflect" "runtime" @@ -600,7 +601,7 @@ func (me *moduleEngine) Call(ctx context.Context, callCtx *wasm.CallContext, f * ce.pushValue(v) } ce.execWasmFunction(ctx, callCtx, compiled) - results = wasm.PopValues(len(f.Type.Results), ce.popValue) + results = wasm.PopValues(f.Type.ResultNumInUint64, ce.popValue) } else { results = wasm.CallGoFunc(ctx, callCtx, compiled.source, params) } @@ -866,6 +867,8 @@ func compileWasmFunction(enabledFeatures wasm.Features, ir *wazeroir.Compilation } var err error switch o := op.(type) { + case *wazeroir.OperationLabel: + // Label op is already handled ^^. case *wazeroir.OperationUnreachable: err = compiler.compileUnreachable() case *wazeroir.OperationBr: @@ -1038,6 +1041,8 @@ func compileWasmFunction(enabledFeatures wasm.Features, ir *wazeroir.Compilation err = compiler.compileTableSize(o) case *wazeroir.OperationTableFill: err = compiler.compileTableFill(o) + default: + err = errors.New("unsupported") } if err != nil { return nil, fmt.Errorf("operation %s: %w", op.Kind().String(), err) diff --git a/internal/wasm/jit/engine_test.go b/internal/wasm/jit/engine_test.go index c4c03a95..1021c3d2 100644 --- a/internal/wasm/jit/engine_test.go +++ b/internal/wasm/jit/engine_test.go @@ -298,7 +298,7 @@ func TestJIT_SliceAllocatedOnHeap(t *testing.T) { const expectedReturnValue = 0x1 m := &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []wasm.ValueType{}, Results: []wasm.ValueType{wasm.ValueTypeI32}}, + {Params: []wasm.ValueType{}, Results: []wasm.ValueType{wasm.ValueTypeI32}, ResultNumInUint64: 1}, {Params: []wasm.ValueType{}, Results: []wasm.ValueType{}}, }, FunctionSection: []wasm.Index{ diff --git a/internal/wasm/module.go b/internal/wasm/module.go index 1444f203..fc93c5d6 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -526,6 +526,11 @@ func validateConstExpression(globals []*GlobalType, numFuncs uint32, expr *Const return fmt.Errorf("ref.func index out of range [%d] with length %d", index, numFuncs-1) } actualType = ValueTypeFuncref + case OpcodeVecV128Const: + if len(expr.Data) != 16 { + return fmt.Errorf("%s needs 16 bytes but was %d bytes", OpcodeVecV128ConstName, len(expr.Data)) + } + actualType = ValueTypeV128 default: return fmt.Errorf("invalid opcode for const expression: 0x%x", expr.Opcode) } @@ -547,20 +552,22 @@ func (m *Module) validateDataCountSection() (err error) { func (m *Module) buildGlobals(importedGlobals []*GlobalInstance) (globals []*GlobalInstance) { for _, gs := range m.GlobalSection { - var gv uint64 + g := &GlobalInstance{Type: gs.Type} switch v := executeConstExpression(importedGlobals, gs.Init).(type) { case int32: - gv = uint64(v) + g.Val = uint64(v) case int64: - gv = uint64(v) + g.Val = uint64(v) case float32: - gv = api.EncodeF32(v) + g.Val = api.EncodeF32(v) case float64: - gv = api.EncodeF64(v) + g.Val = api.EncodeF64(v) + case [2]uint64: + g.Val, g.ValHi = v[0], v[1] default: panic(fmt.Errorf("BUG: invalid conversion %d", v)) } - globals = append(globals, &GlobalInstance{Type: gs.Type, Val: gv}) + globals = append(globals, g) } return } @@ -667,6 +674,28 @@ type FunctionType struct { // string is cached as it is used both for String and key string string + + // ParamNumInUint64 is the number of uint64 values requires to represent the Wasm param type. + ParamNumInUint64 int + + // ResultsNumInUint64 is the number of uint64 values requires to represent the Wasm result type. + ResultNumInUint64 int +} + +func (f *FunctionType) CacheNumInUint64() { + for _, tp := range f.Params { + f.ParamNumInUint64++ + if tp == ValueTypeV128 { + f.ParamNumInUint64++ + } + } + + for _, tp := range f.Results { + f.ResultNumInUint64++ + if tp == ValueTypeV128 { + f.ResultNumInUint64++ + } + } } // EqualsSignature returns true if the function type has the same parameters and results. @@ -972,10 +1001,11 @@ func SectionIDName(sectionID SectionID) string { type ValueType = api.ValueType const ( - ValueTypeI32 = api.ValueTypeI32 - ValueTypeI64 = api.ValueTypeI64 - ValueTypeF32 = api.ValueTypeF32 - ValueTypeF64 = api.ValueTypeF64 + ValueTypeI32 = api.ValueTypeI32 + ValueTypeI64 = api.ValueTypeI64 + ValueTypeF32 = api.ValueTypeF32 + ValueTypeF64 = api.ValueTypeF64 + ValueTypeV128 = api.ValueTypeV128 // TODO: ValueTypeFuncref is not exposed in the api pkg yet. ValueTypeFuncref ValueType = 0x70 ValueTypeExternref ValueType = api.ValueTypeExternref diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go index d03b1b3d..b88d03b0 100644 --- a/internal/wasm/module_test.go +++ b/internal/wasm/module_test.go @@ -732,7 +732,7 @@ func TestModule_validateExports(t *testing.T) { } } -func TestModule_buildGlobalInstances(t *testing.T) { +func TestModule_buildGlobals(t *testing.T) { m := Module{GlobalSection: []*Global{ { Type: &GlobalType{Mutable: true, ValType: ValueTypeF64}, @@ -744,17 +744,27 @@ func TestModule_buildGlobalInstances(t *testing.T) { Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(math.MaxInt32)}, }, + { + Type: &GlobalType{Mutable: false, ValType: ValueTypeV128}, + Init: &ConstantExpression{Opcode: OpcodeVecV128Const, + Data: []byte{ + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + }, + }, + }, }} globals := m.buildGlobals(nil) expectedGlobals := []*GlobalInstance{ {Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: api.EncodeF64(math.MaxFloat64)}, {Type: &GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: math.MaxInt32}, + {Type: &GlobalType{ValType: ValueTypeV128, Mutable: false}, Val: 0x1, ValHi: 0x2}, } require.Equal(t, expectedGlobals, globals) } -func TestModule_buildFunctionInstances(t *testing.T) { +func TestModule_buildFunctions(t *testing.T) { nopCode := &Code{nil, []byte{OpcodeEnd}} m := Module{ TypeSection: []*FunctionType{{}}, diff --git a/internal/wasm/store.go b/internal/wasm/store.go index 68983190..35780727 100644 --- a/internal/wasm/store.go +++ b/internal/wasm/store.go @@ -3,6 +3,7 @@ package wasm import ( "bytes" "context" + "encoding/binary" "errors" "fmt" "reflect" @@ -163,6 +164,8 @@ type ( Type *GlobalType // Val holds a 64-bit representation of the actual value. Val uint64 + // ValHi is only used for vector type globals, and holds the higher bits of the vector. + ValHi uint64 // ^^ TODO: this should be guarded with atomics when mutable } @@ -590,7 +593,7 @@ func errorInvalidImport(i *Import, idx int, err error) error { // Global initialization constant expression can only reference the imported globals. // See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0 -func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression) (v interface{}) { +func executeConstExpression(importedGlobals []*GlobalInstance, expr *ConstantExpression) (v interface{}) { r := bytes.NewReader(expr.Data) switch expr.Opcode { case OpcodeI32Const: @@ -605,7 +608,7 @@ func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression) v, _ = ieee754.DecodeFloat64(r) case OpcodeGlobalGet: id, _, _ := leb128.DecodeUint32(r) - g := globals[id] + g := importedGlobals[id] switch g.Type.ValType { case ValueTypeI32: v = int32(g.Val) @@ -615,6 +618,8 @@ func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression) v = api.DecodeF32(g.Val) case ValueTypeF64: v = api.DecodeF64(g.Val) + case ValueTypeV128: + v = [2]uint64{g.Val, g.ValHi} } case OpcodeRefNull: switch expr.Data[0] { @@ -630,6 +635,8 @@ func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression) // and if this is the const expr for global, the value will be further downed to // opaque pointer of the engine-specific compiled function. v, _, _ = leb128.DecodeInt32(r) + case OpcodeVecV128Const: + v = [2]uint64{binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])} } return } diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index 5dd56b33..5b96d6b4 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -591,18 +591,19 @@ func TestExecuteConstExpression(t *testing.T) { }) t.Run("global expr", func(t *testing.T) { for _, tc := range []struct { - valueType ValueType - val uint64 + valueType ValueType + val, valHi uint64 }{ {valueType: ValueTypeI32, val: 10}, {valueType: ValueTypeI64, val: 20}, {valueType: ValueTypeF32, val: uint64(math.Float32bits(634634432.12311))}, {valueType: ValueTypeF64, val: math.Float64bits(1.12312311)}, + {valueType: ValueTypeV128, val: 0x1, valHi: 0x2}, } { t.Run(ValueTypeName(tc.valueType), func(t *testing.T) { // The index specified in Data equals zero. expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet} - globals := []*GlobalInstance{{Val: tc.val, Type: &GlobalType{ValType: tc.valueType}}} + globals := []*GlobalInstance{{Val: tc.val, ValHi: tc.valHi, Type: &GlobalType{ValType: tc.valueType}}} val := executeConstExpression(globals, expr) require.NotNil(t, val) @@ -624,10 +625,28 @@ func TestExecuteConstExpression(t *testing.T) { actual, ok := val.(float64) require.True(t, ok) require.Equal(t, api.DecodeF64(tc.val), actual) + case ValueTypeV128: + vector, ok := val.([2]uint64) + require.True(t, ok) + require.Equal(t, uint64(0x1), vector[0]) + require.Equal(t, uint64(0x2), vector[1]) } }) } }) + + t.Run("vector", func(t *testing.T) { + expr := &ConstantExpression{Data: []byte{ + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + }, Opcode: OpcodeVecV128Const} + val := executeConstExpression(nil, expr) + require.NotNil(t, val) + vector, ok := val.([2]uint64) + require.True(t, ok) + require.Equal(t, uint64(0x1), vector[0]) + require.Equal(t, uint64(0x2), vector[1]) + }) } func TestStore_resolveImports(t *testing.T) { diff --git a/internal/wasm/text/decoder.go b/internal/wasm/text/decoder.go index e0e36b21..1a5db886 100644 --- a/internal/wasm/text/decoder.go +++ b/internal/wasm/text/decoder.go @@ -108,7 +108,7 @@ func DecodeModule( source []byte, enabledFeatures wasm.Features, memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32), -) (result *wasm.Module, err error) { +) (module *wasm.Module, err error) { // TODO: when globals are supported, err on global vars if disabled // names are the wasm.Module NameSection @@ -117,7 +117,7 @@ func DecodeModule( // * FunctionNames: nil when neither imported nor module-defined functions had a name // * LocalNames: nil when neither imported nor module-defined functions had named (param) fields. names := &wasm.NameSection{} - module := &wasm.Module{NameSection: names} + module = &wasm.Module{NameSection: names} p := newModuleParser(module, enabledFeatures, memorySizer) p.source = source @@ -144,7 +144,10 @@ func DecodeModule( module.NameSection = nil } - return module, nil + for _, tp := range module.TypeSection { + tp.CacheNumInUint64() + } + return } func newModuleParser( diff --git a/internal/wasm/text/decoder_test.go b/internal/wasm/text/decoder_test.go index 126dad7b..43545f98 100644 --- a/internal/wasm/text/decoder_test.go +++ b/internal/wasm/text/decoder_test.go @@ -63,7 +63,7 @@ func TestDecodeModule(t *testing.T) { 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}}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}, }, }, }, @@ -72,7 +72,7 @@ func TestDecodeModule(t *testing.T) { 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}}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}, }, }, }, @@ -362,8 +362,8 @@ func TestDecodeModule(t *testing.T) { expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ v_v, - {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}, - {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}, + {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1}, }, ImportSection: []*wasm.Import{ { @@ -549,7 +549,7 @@ func TestDecodeModule(t *testing.T) { input: "(module (import \"\" \"\" (func (param i32 i32) (param $v i32) (param i64) (param $t f32))))", expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []wasm.ValueType{i32, i32, i32, i64, f32}}, + {Params: []wasm.ValueType{i32, i32, i32, i64, f32}, ParamNumInUint64: 5}, }, ImportSection: []*wasm.Import{{Type: wasm.ExternTypeFunc, DescFunc: 0}}, NameSection: &wasm.NameSection{ @@ -569,8 +569,8 @@ func TestDecodeModule(t *testing.T) { expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ v_v, - {Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}}, - {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}}, + {Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 9, ResultNumInUint64: 1}, + {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1}, }, ImportSection: []*wasm.Import{ { @@ -598,7 +598,7 @@ func TestDecodeModule(t *testing.T) { (import "foo" "bar" (func (type 0) (param i32))) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}}, ImportSection: []*wasm.Import{{ Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, @@ -613,7 +613,7 @@ func TestDecodeModule(t *testing.T) { (type $i32 (func (param i32))) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}}, ImportSection: []*wasm.Import{{ Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, @@ -628,7 +628,7 @@ func TestDecodeModule(t *testing.T) { (import "foo" "bar" (func (type $i32) (param i32))) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}}, ImportSection: []*wasm.Import{{ Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, @@ -643,7 +643,7 @@ func TestDecodeModule(t *testing.T) { (type $i32 (func (param i32))) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}}, ImportSection: []*wasm.Import{{ Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, @@ -655,7 +655,7 @@ 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}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}}, ImportSection: []*wasm.Import{{ Module: "misc", Name: "swap", Type: wasm.ExternTypeFunc, @@ -856,8 +856,8 @@ func TestDecodeModule(t *testing.T) { expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ v_v, - {Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}}, - {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}}, + {Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 9, ResultNumInUint64: 1}, + {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1}, }, FunctionSection: []wasm.Index{1, 2}, CodeSection: []*wasm.Code{{Body: localGet0End}, {Body: localGet0End}}, @@ -881,8 +881,8 @@ func TestDecodeModule(t *testing.T) { expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ v_v, - {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}, - {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}, + {Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 4, ResultNumInUint64: 1}, }, FunctionSection: []wasm.Index{1, 2}, CodeSection: []*wasm.Code{{Body: localGet0End}, {Body: localGet0End}}, @@ -922,7 +922,7 @@ func TestDecodeModule(t *testing.T) { (func (type 0) (param i32)) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}}, FunctionSection: []wasm.Index{0}, CodeSection: []*wasm.Code{codeEnd}, }, @@ -934,7 +934,7 @@ func TestDecodeModule(t *testing.T) { (type $i32 (func (param i32))) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}}, FunctionSection: []wasm.Index{0}, CodeSection: []*wasm.Code{codeEnd}, }, @@ -946,7 +946,7 @@ func TestDecodeModule(t *testing.T) { (func (type $i32) (param i32)) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}}, FunctionSection: []wasm.Index{0}, CodeSection: []*wasm.Code{codeEnd}, }, @@ -958,7 +958,7 @@ func TestDecodeModule(t *testing.T) { (type $i32 (func (param i32))) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, ParamNumInUint64: 1}}, FunctionSection: []wasm.Index{0}, CodeSection: []*wasm.Code{codeEnd}, }, @@ -1101,7 +1101,7 @@ func TestDecodeModule(t *testing.T) { name: "func param IDs", input: "(module (func $one (param $x i32) (param $y i32) (result i32) local.get 0))", expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}}, FunctionSection: []wasm.Index{0}, CodeSection: []*wasm.Code{{Body: localGet0End}}, NameSection: &wasm.NameSection{ @@ -1119,7 +1119,7 @@ func TestDecodeModule(t *testing.T) { (func (param $l i32) (param $r i32) (result i32) local.get 0) )`, expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}}, FunctionSection: []wasm.Index{0, 0}, CodeSection: []*wasm.Code{{Body: localGet0End}, {Body: localGet0End}}, NameSection: &wasm.NameSection{ @@ -1134,7 +1134,7 @@ func TestDecodeModule(t *testing.T) { name: "func mixed param IDs", // Verifies we can handle less param fields than Params input: "(module (func (param i32 i32) (param $v i32) (param i64) (param $t f32)))", expected: &wasm.Module{ - TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32, i32, i64, f32}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32, i32, i64, f32}, ParamNumInUint64: 5}}, FunctionSection: []wasm.Index{0}, CodeSection: []*wasm.Code{{Body: end}}, NameSection: &wasm.NameSection{ @@ -1148,7 +1148,7 @@ 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}}}, + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}, ParamNumInUint64: 2, ResultNumInUint64: 2}}, FunctionSection: []wasm.Index{0}, CodeSection: []*wasm.Code{{Body: []byte{wasm.OpcodeLocalGet, 0x01, wasm.OpcodeLocalGet, 0x00, wasm.OpcodeEnd}}}, NameSection: &wasm.NameSection{ @@ -1393,7 +1393,7 @@ func TestDecodeModule(t *testing.T) { )`, expected: &wasm.Module{ TypeSection: []*wasm.FunctionType{ - {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}, + {Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, ParamNumInUint64: 2, ResultNumInUint64: 1}, }, FunctionSection: []wasm.Index{0}, CodeSection: []*wasm.Code{ diff --git a/internal/wasm/text/type_parser_test.go b/internal/wasm/text/type_parser_test.go index 49409351..2c3fcf4f 100644 --- a/internal/wasm/text/type_parser_test.go +++ b/internal/wasm/text/type_parser_test.go @@ -8,19 +8,45 @@ import ( ) var ( - 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}} - f32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}} - 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}} - i32i64_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32}} - i32i32i32i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}} + f32, f64, i32, i64 = wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeI32, wasm.ValueTypeI64 + i32_v = &wasm.FunctionType{Params: []wasm.ValueType{i32}, + ParamNumInUint64: 1, + } + v_i32 = &wasm.FunctionType{Results: []wasm.ValueType{i32}, + ResultNumInUint64: 1, + } + v_i32i64 = &wasm.FunctionType{Results: []wasm.ValueType{i32, i64}, + ResultNumInUint64: 2, + } + f32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 1, + ResultNumInUint64: 1, + } + i64_i64 = &wasm.FunctionType{Params: []wasm.ValueType{i64}, Results: []wasm.ValueType{i64}, + ParamNumInUint64: 1, + ResultNumInUint64: 1, + } + i32i64_v = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, + ParamNumInUint64: 2, + } + i32i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 2, + ResultNumInUint64: 1, + } + i32i64_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 2, + ResultNumInUint64: 1, + } + i32i32i32i32_i32 = &wasm.FunctionType{ + Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 4, + ResultNumInUint64: 1, + } i32i32i32i32i32i64i64i32i32_i32 = &wasm.FunctionType{ - Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, - Results: []wasm.ValueType{i32}, + Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, + Results: []wasm.ValueType{i32}, + ParamNumInUint64: 9, + ResultNumInUint64: 1, } ) @@ -99,7 +125,7 @@ func TestTypeParser(t *testing.T) { { name: "mixed param abbreviation", // Verifies we can handle less param fields than param types input: "(type (func (param i32 i32) (param i32) (param i64) (param f32)))", - expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}}, + expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}, ParamNumInUint64: 5}, }, // Below are changes to test/core/br.wast from the commit that added "multi-value" support. @@ -108,55 +134,58 @@ func TestTypeParser(t *testing.T) { { name: "multi-value - v_i64f32 abbreviated", input: "(type (func (result i64 f32)))", - expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}}, + expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}, ResultNumInUint64: 2}, }, { 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}}, + expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}, ParamNumInUint64: 2, ResultNumInUint64: 2}, }, { name: "multi-value - v_i64f32", input: "(type (func (result i64) (result f32)))", - expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}}, + expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}, ResultNumInUint64: 2}, }, { 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}}, + expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}, ParamNumInUint64: 2, ResultNumInUint64: 2}, }, { 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}}, + expected: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}, ParamNumInUint64: 2, ResultNumInUint64: 2}, }, { 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}}, + expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32, f32, i32}, ResultNumInUint64: 5}, }, { 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}, + Params: []wasm.ValueType{i32, i32, i64, i32}, + Results: []wasm.ValueType{f32, f64, f64, i32}, + ParamNumInUint64: 4, ResultNumInUint64: 4, }, }, { 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}, + Params: []wasm.ValueType{i32, i32, i64, i32}, + Results: []wasm.ValueType{f32, f64, f64, i32}, + ParamNumInUint64: 4, ResultNumInUint64: 4, }, }, { 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}, + Params: []wasm.ValueType{i32, i32, i64, i32}, + Results: []wasm.ValueType{f32, f64, f64, i32}, + ParamNumInUint64: 4, ResultNumInUint64: 4, }, }, { @@ -164,7 +193,7 @@ func TestTypeParser(t *testing.T) { 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}}, + expected: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32}, ResultNumInUint64: 3}, }, { name: "multi-value - empty abbreviated params and results", @@ -175,8 +204,9 @@ func TestTypeParser(t *testing.T) { // 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}, + Params: []wasm.ValueType{i32, i32, i64, i32, i32}, + Results: []wasm.ValueType{f32, f64, f64, i32}, + ParamNumInUint64: 5, ResultNumInUint64: 4, }, }, } @@ -378,6 +408,9 @@ func parseFunctionType( 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)) + if parsed != nil { + parsed.CacheNumInUint64() + } return parsed, tp, err } diff --git a/internal/wasm/text/typeuse_parser_test.go b/internal/wasm/text/typeuse_parser_test.go index 64ff20e5..5a91ce3c 100644 --- a/internal/wasm/text/typeuse_parser_test.go +++ b/internal/wasm/text/typeuse_parser_test.go @@ -74,7 +74,7 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) { { name: "mixed param abbreviation", // Verifies we can handle less param fields than param types input: "((param i32 i32) (param i32) (param i64) (param f32))", - expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}}, + expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i32, i64, f32}, ParamNumInUint64: 5}, }, // Below are changes to test/core/br.wast from the commit that added "multi-value" support. @@ -83,56 +83,73 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) { { name: "multi-value - v_i64f32 abbreviated", input: "((result i64 f32))", - expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}}, + expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}, ResultNumInUint64: 2}, }, { - 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 - i32i64_f32f64 abbreviated", + input: "((param i32 i64) (result f32 f64))", + expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}, + ParamNumInUint64: 2, + ResultNumInUint64: 2, + }, }, { name: "multi-value - v_i64f32", input: "((result i64) (result f32))", - expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}}, + expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, f32}, ResultNumInUint64: 2}, }, { - 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", + input: "((param i32) (param i64) (result f32) (result f64))", + expectedInlinedType: &wasm.FunctionType{Params: []wasm.ValueType{i32, i64}, Results: []wasm.ValueType{f32, f64}, + ParamNumInUint64: 2, + ResultNumInUint64: 2, + }, }, { - 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 - 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}, + ParamNumInUint64: 2, + ResultNumInUint64: 2, + }, + 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 - 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}, + ResultNumInUint64: 5, + }, }, { 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}, + Params: []wasm.ValueType{i32, i32, i64, i32}, + Results: []wasm.ValueType{f32, f64, f64, i32}, + ParamNumInUint64: 4, + ResultNumInUint64: 4, }, }, { 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}, + Params: []wasm.ValueType{i32, i32, i64, i32}, + Results: []wasm.ValueType{f32, f64, f64, i32}, + ParamNumInUint64: 4, + ResultNumInUint64: 4, }, }, { 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}, + Params: []wasm.ValueType{i32, i32, i64, i32}, + Results: []wasm.ValueType{f32, f64, f64, i32}, + ParamNumInUint64: 4, + ResultNumInUint64: 4, }, }, { @@ -140,7 +157,9 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) { 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}}, + expectedInlinedType: &wasm.FunctionType{Results: []wasm.ValueType{i64, i64, f32}, + ResultNumInUint64: 3, + }, }, { name: "multi-value - empty abbreviated params and results", @@ -151,8 +170,10 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) { // 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}, + Params: []wasm.ValueType{i32, i32, i64, i32, i32}, + Results: []wasm.ValueType{f32, f64, f64, i32}, + ParamNumInUint64: 5, + ResultNumInUint64: 4, }, expectedParamNames: wasm.NameMap{&wasm.NameAssoc{Index: 4, Name: "x"}}, }, @@ -164,6 +185,8 @@ func TestTypeUseParser_InlinesTypesWhenNotYetAdded(t *testing.T) { 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) + exp := tp.inlinedTypes[0] + exp.CacheNumInUint64() require.Equal(t, []*wasm.FunctionType{tc.expectedInlinedType}, tp.inlinedTypes) } }) @@ -202,7 +225,9 @@ func TestTypeUseParser_UnresolvedType(t *testing.T) { if tc.expectedInlinedType == nil { require.Equal(t, 0, len(tp.inlinedTypes), "expected no inlinedTypes") } else { - require.Equal(t, tc.expectedInlinedType, tp.inlinedTypes[0]) + exp := tp.inlinedTypes[0] + exp.CacheNumInUint64() + require.Equal(t, tc.expectedInlinedType, exp) } } }) @@ -347,6 +372,9 @@ func TestTypeUseParser_ReuseExistingInlinedType(t *testing.T) { require.NoError(t, parseTypeUse(tp, tc.input, ignoreTypeUse)) return tp, func(t *testing.T) { + for _, it := range tp.inlinedTypes { + it.CacheNumInUint64() + } // verify it wasn't duplicated require.Equal(t, []*wasm.FunctionType{i32i64_v, tc.expectedInlinedType}, tp.inlinedTypes) // last two inlined types are the same @@ -392,6 +420,9 @@ func TestTypeUseParser_BeginResets(t *testing.T) { require.NoError(t, parseTypeUse(tp, tc.input, ignoreTypeUse)) return tp, func(t *testing.T) { + for _, it := range tp.inlinedTypes { + it.CacheNumInUint64() + } // this is the second inlined type require.Equal(t, []*wasm.FunctionType{i32i64_i32, tc.expectedInlinedType}, tp.inlinedTypes) } diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go index 51dc350d..730105e4 100644 --- a/internal/wazeroir/compiler.go +++ b/internal/wazeroir/compiler.go @@ -105,6 +105,28 @@ func (c *controlFrames) push(frame *controlFrame) { c.frames = append(c.frames, frame) } +// localIndexToStackHeight initializes localIndexToStackHeight field. See the comment on localIndexToStackHeight. +func (c *compiler) calcLocalIndexToStackHeight() { + c.localIndexToStackHeight = make(map[uint32]int, len(c.sig.Params)+len(c.localTypes)) + var current int + for index, lt := range c.sig.Params { + c.localIndexToStackHeight[wasm.Index(index)] = current + if lt == wasm.ValueTypeV128 { + current++ + } + current++ + } + + for index, lt := range c.localTypes { + index += len(c.sig.Params) + c.localIndexToStackHeight[wasm.Index(index)] = current + if lt == wasm.ValueTypeV128 { + current++ + } + current++ + } +} + type compiler struct { enabledFeatures wasm.Features stack []UnsignedType @@ -121,8 +143,11 @@ type compiler struct { body []byte // sig is the function type of the target function. sig *wasm.FunctionType - // localTypes holds the target function locals' value types. + // localTypes holds the target function locals' value types except function params. localTypes []wasm.ValueType + // localIndexToStackHeight maps the local index (starting with function params) to the stack height + // where the local is places. This is the necessary mapping for functions who contain vector type locals. + localIndexToStackHeight map[wasm.Index]int // types hold all the function types in the module where the targe function exists. types []*wasm.FunctionType @@ -245,9 +270,11 @@ func compile(enabledFeatures wasm.Features, types: types, } + c.calcLocalIndexToStackHeight() + // Push function arguments. for _, t := range sig.Params { - c.stackPush(wasmValueTypeToUnsignedType(t)) + c.stackPush(wasmValueTypeToUnsignedType(t)...) } // Emit const expressions for locals. // Note that here we don't take function arguments @@ -320,7 +347,7 @@ operatorSwitch: // Create a new frame -- entering this block. frame := &controlFrame{ frameID: c.nextID(), - originalStackLenWithoutParam: len(c.stack) - len(bt.Params), + originalStackLenWithoutParam: len(c.stack) - bt.ParamNumInUint64, kind: controlFrameKindBlockWithoutContinuationLabel, blockType: bt, } @@ -343,7 +370,7 @@ operatorSwitch: // Create a new frame -- entering loop. frame := &controlFrame{ frameID: c.nextID(), - originalStackLenWithoutParam: len(c.stack) - len(bt.Params), + originalStackLenWithoutParam: len(c.stack) - bt.ParamNumInUint64, kind: controlFrameKindLoop, blockType: bt, } @@ -378,7 +405,7 @@ operatorSwitch: // Create a new frame -- entering if. frame := &controlFrame{ frameID: c.nextID(), - originalStackLenWithoutParam: len(c.stack) - len(bt.Params), + originalStackLenWithoutParam: len(c.stack) - bt.ParamNumInUint64, // Note this will be set to controlFrameKindIfWithElse // when else opcode found later. kind: controlFrameKindIfWithoutElse, @@ -417,7 +444,7 @@ operatorSwitch: // 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)) + c.stackPush(wasmValueTypeToUnsignedType(t)...) } // We are no longer unreachable in else frame, @@ -443,7 +470,7 @@ operatorSwitch: c.stack = c.stack[:frame.originalStackLenWithoutParam] for _, t := range frame.blockType.Params { - c.stackPush(wasmValueTypeToUnsignedType(t)) + c.stackPush(wasmValueTypeToUnsignedType(t)...) } // Prep labels for else and the continuation of this if block. @@ -474,7 +501,7 @@ operatorSwitch: c.stack = c.stack[:frame.originalStackLenWithoutParam] for _, t := range frame.blockType.Results { - c.stackPush(wasmValueTypeToUnsignedType(t)) + c.stackPush(wasmValueTypeToUnsignedType(t)...) } continuationLabel := &Label{FrameID: frame.frameID, Kind: LabelKindContinuation} @@ -505,7 +532,7 @@ operatorSwitch: // Push the result types onto the stack. for _, t := range frame.blockType.Results { - c.stackPush(wasmValueTypeToUnsignedType(t)) + c.stackPush(wasmValueTypeToUnsignedType(t)...) } // Emit the instructions according to the kind of the current control frame. @@ -698,33 +725,67 @@ operatorSwitch: if index == nil { return fmt.Errorf("index does not exist for local.get") } - depth := c.localDepth(*index) - c.emit( - // -1 because we already manipulated the stack before - // called localDepth ^^. - &OperationPick{Depth: depth - 1}, - ) + id := *index + depth := c.localDepth(id) + if c.localType(id) == wasm.ValueTypeV128 { + c.emit( + // -2 because we already pushed the result of this operation into the c.stack before + // called localDepth ^^, + &OperationPick{Depth: depth - 2}, + &OperationPick{Depth: depth - 2}, + ) + } else { + c.emit( + // -1 because we already manipulated the stack before + // called localDepth ^^. + &OperationPick{Depth: depth - 1}, + ) + } case wasm.OpcodeLocalSet: if index == nil { return fmt.Errorf("index does not exist for local.set") } - depth := c.localDepth(*index) - c.emit( - // +1 because we already manipulated the stack before - // called localDepth ^^. - &OperationSwap{Depth: depth + 1}, - &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, - ) + id := *index + depth := c.localDepth(id) + if c.localType(id) == wasm.ValueTypeV128 { + c.emit( + // +1 because we already popped the operands for this operation from the c.stack before + // called localDepth ^^, + &OperationSwap{Depth: depth + 1}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + &OperationSwap{Depth: depth + 1}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + ) + } else { + c.emit( + // +1 because we already popped the operands for this operation from the c.stack before + // called localDepth ^^, + &OperationSwap{Depth: depth + 1}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + ) + } case wasm.OpcodeLocalTee: if index == nil { return fmt.Errorf("index does not exist for local.tee") } - depth := c.localDepth(*index) - c.emit( - &OperationPick{Depth: 0}, - &OperationSwap{Depth: depth + 1}, - &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, - ) + id := *index + depth := c.localDepth(id) + if c.localType(id) == wasm.ValueTypeV128 { + c.emit( + &OperationPick{Depth: 1}, + &OperationPick{Depth: 1}, + &OperationSwap{Depth: depth + 1}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + &OperationSwap{Depth: depth + 1}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + ) + } else { + c.emit( + &OperationPick{Depth: 0}, + &OperationSwap{Depth: depth + 1}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + ) + } case wasm.OpcodeGlobalGet: if index == nil { return fmt.Errorf("index does not exist for global.get") @@ -1660,6 +1721,29 @@ operatorSwitch: default: return fmt.Errorf("unsupported misc instruction in wazeroir: 0x%x", op) } + case wasm.OpcodeVecPrefix: + c.pc++ + switch miscOp := c.body[c.pc]; miscOp { + case wasm.OpcodeVecV128Const: + c.pc++ + lo := binary.LittleEndian.Uint64(c.body[c.pc : c.pc+8]) + c.pc += 8 + hi := binary.LittleEndian.Uint64(c.body[c.pc : c.pc+8]) + c.emit( + &OperationConstV128{Lo: lo, Hi: hi}, + ) + c.pc += 7 + case wasm.OpcodeVecI32x4Add: + c.emit( + &OperationI32x4Add{}, + ) + case wasm.OpcodeVecI64x2Add: + c.emit( + &OperationI64x2Add{}, + ) + default: + return fmt.Errorf("unsupported vector instruction in wazeroir: 0x%x", op) + } default: return fmt.Errorf("unsupported instruction in wazeroir: 0x%x", op) } @@ -1757,8 +1841,8 @@ func (c *compiler) stackPop() (ret UnsignedType) { return } -func (c *compiler) stackPush(t UnsignedType) { - c.stack = append(c.stack, t) +func (c *compiler) stackPush(ts ...UnsignedType) { + c.stack = append(c.stack, ts...) } // emit adds the operations into the result. @@ -1799,13 +1883,31 @@ func (c *compiler) emitDefaultValue(t wasm.ValueType) { case wasm.ValueTypeF64: c.stackPush(UnsignedTypeF64) c.emit(&OperationConstF64{Value: 0}) + case wasm.ValueTypeV128: + c.stackPush(UnsignedTypeI64) + c.emit(&OperationConstI64{Value: 0}) + c.stackPush(UnsignedTypeI64) + c.emit(&OperationConstI64{Value: 0}) } } // Returns the "depth" (starting from top of the stack) // of the n-th local. -func (c *compiler) localDepth(n uint32) int { - return int(len(c.stack)) - 1 - int(n) +func (c *compiler) localDepth(index wasm.Index) int { + height, ok := c.localIndexToStackHeight[index] + if !ok { + panic("BUG") + } + return int(len(c.stack)) - 1 - int(height) +} + +func (c *compiler) localType(index wasm.Index) (t wasm.ValueType) { + if params := uint32(len(c.sig.Params)); index < params { + t = c.sig.Params[index] + } else { + t = c.localTypes[index-params] + } + return } // getFrameDropRange returns the range (starting from top of the stack) that spans across the stack. The range is @@ -1819,9 +1921,9 @@ func (c *compiler) getFrameDropRange(frame *controlFrame, isEnd bool) *Inclusive // 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) + start = frame.blockType.ParamNumInUint64 } else { - start = len(frame.blockType.Results) + start = frame.blockType.ResultNumInUint64 } var end int if frame.kind == controlFrameKindFunction { diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go index 29109854..0e4284b7 100644 --- a/internal/wazeroir/compiler_test.go +++ b/internal/wazeroir/compiler_test.go @@ -2,6 +2,7 @@ package wazeroir import ( "context" + "fmt" "testing" "github.com/tetratelabs/wazero/api" @@ -15,11 +16,20 @@ var ctx = context.WithValue(context.Background(), struct{}{}, "arbitrary") var ( f32, f64, i32 = wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeI32 - f32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}} - 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}} + f32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{f32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 1, + ResultNumInUint64: 1, + } + i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 1, + ResultNumInUint64: 1, + } + i32i32_i32 = &wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 2, + ResultNumInUint64: 1, + } + v_v = &wasm.FunctionType{} + v_f64f64 = &wasm.FunctionType{Results: []wasm.ValueType{f64, f64}, ResultNumInUint64: 2} ) func TestCompile(t *testing.T) { @@ -55,10 +65,18 @@ func TestCompile(t *testing.T) { &OperationBr{Target: &BranchTarget{}}, // return! }, LabelCallers: map[string]uint32{}, - Types: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}}, - Functions: []uint32{0}, - Signature: &wasm.FunctionType{Params: []wasm.ValueType{wasm.ValueTypeI32}, Results: []wasm.ValueType{wasm.ValueTypeI32}}, - TableTypes: []wasm.RefType{}, + Types: []*wasm.FunctionType{ + {Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 1, + ResultNumInUint64: 1}, + }, + Functions: []uint32{0}, + Signature: &wasm.FunctionType{ + Params: []wasm.ValueType{wasm.ValueTypeI32}, Results: []wasm.ValueType{wasm.ValueTypeI32}, + ParamNumInUint64: 1, + ResultNumInUint64: 1, + }, + TableTypes: []wasm.RefType{}, }, }, { @@ -74,10 +92,18 @@ func TestCompile(t *testing.T) { &OperationBr{Target: &BranchTarget{}}, // return! }, LabelCallers: map[string]uint32{}, - Types: []*wasm.FunctionType{{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}}, - Functions: []uint32{0}, - Signature: &wasm.FunctionType{Params: []wasm.ValueType{wasm.ValueTypeI32}, Results: []wasm.ValueType{wasm.ValueTypeI32}}, - TableTypes: []wasm.RefType{}, + Types: []*wasm.FunctionType{{ + Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}, + ParamNumInUint64: 1, + ResultNumInUint64: 1, + }}, + Functions: []uint32{0}, + Signature: &wasm.FunctionType{ + Params: []wasm.ValueType{wasm.ValueTypeI32}, Results: []wasm.ValueType{wasm.ValueTypeI32}, + ParamNumInUint64: 1, + ResultNumInUint64: 1, + }, + TableTypes: []wasm.RefType{}, }, }, } @@ -227,11 +253,17 @@ func TestCompile_BulkMemoryOperations(t *testing.T) { } func TestCompile_MultiValue(t *testing.T) { - i32i32_i32i32 := &wasm.FunctionType{Params: []wasm.ValueType{ - wasm.ValueTypeI32, wasm.ValueTypeI32}, - Results: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, + i32i32_i32i32 := &wasm.FunctionType{ + Params: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, + Results: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, + ParamNumInUint64: 2, + ResultNumInUint64: 2, + } + _i32i64 := &wasm.FunctionType{ + Results: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI64}, + ParamNumInUint64: 0, + ResultNumInUint64: 2, } - _i32i64 := &wasm.FunctionType{Results: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI64}} tests := []struct { name string @@ -839,3 +871,189 @@ func TestCompile_TableGrowFillSize(t *testing.T) { }) } } + +func TestCompile_Locals_vector(t *testing.T) { + for _, tc := range []struct { + name string + mod *wasm.Module + expected []Operation + }{ + { + name: "local.get - func param", + mod: &wasm.Module{ + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{wasm.ValueTypeV128}}}, + FunctionSection: []wasm.Index{0}, + CodeSection: []*wasm.Code{{Body: []byte{ + wasm.OpcodeLocalGet, 0, + wasm.OpcodeEnd, + }}}, + }, + expected: []Operation{ + &OperationPick{Depth: 1}, // [param[0].low, param[0].high] -> [param[0].low, param[0].high, param[0].low] + &OperationPick{Depth: 1}, // [param[0].low, param[0].high, param[0].low] -> [param[0].low, param[0].high, param[0].low, param[0].high] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 3}}, + &OperationBr{Target: &BranchTarget{}}, // return! + }, + }, + { + name: "local.get - non func param", + mod: &wasm.Module{ + TypeSection: []*wasm.FunctionType{{}}, + FunctionSection: []wasm.Index{0}, + CodeSection: []*wasm.Code{{ + Body: []byte{ + wasm.OpcodeLocalGet, 0, + wasm.OpcodeEnd, + }, + LocalTypes: []wasm.ValueType{wasm.ValueTypeV128}, + }}, + }, + expected: []Operation{ + &OperationConstI64{Value: 0}, + &OperationConstI64{Value: 0}, + &OperationPick{Depth: 1}, // [p[0].low, p[0].high] -> [p[0].low, p[0].high, p[0].low] + &OperationPick{Depth: 1}, // [p[0].low, p[0].high, p[0].low] -> [p[0].low, p[0].high, p[0].low, p[0].high] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 3}}, + &OperationBr{Target: &BranchTarget{}}, // return! + }, + }, + { + name: "local.set - func param", + mod: &wasm.Module{ + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{wasm.ValueTypeV128}}}, + FunctionSection: []wasm.Index{0}, + CodeSection: []*wasm.Code{{Body: []byte{ + wasm.OpcodeVecPrefix, wasm.OpcodeVecV128Const, // [] -> [0x01, 0x02] + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + wasm.OpcodeLocalSet, 0, // [0x01, 0x02] -> [] + wasm.OpcodeEnd, + }}}, + }, + expected: []Operation{ + // [p[0].lo, p[1].hi] -> [p[0].lo, p[1].hi, 0x01, 0x02] + &OperationConstV128{Lo: 0x01, Hi: 0x02}, + // [p[0].lo, p[1].hi, 0x01, 0x02] -> [p[0].lo, 0x02, 0x01, p[1].hi] + &OperationSwap{Depth: 2}, + // [p[0].lo, 0x02, 0x01, p[1].hi] -> [p[0].lo, 0x02, 0x01] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + // [p[0].lo, 0x02, 0x01] -> [0x01, 0x02, p[0].lo] + &OperationSwap{Depth: 2}, + // [0x01, 0x02, p[0].lo] -> [0x01, 0x02] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 1}}, + &OperationBr{Target: &BranchTarget{}}, // return! + }, + }, + { + name: "local.set - non func param", + mod: &wasm.Module{ + TypeSection: []*wasm.FunctionType{{}}, + FunctionSection: []wasm.Index{0}, + CodeSection: []*wasm.Code{{ + Body: []byte{ + wasm.OpcodeVecPrefix, wasm.OpcodeVecV128Const, // [] -> [0x01, 0x02] + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + wasm.OpcodeLocalSet, 0, // [0x01, 0x02] -> [] + wasm.OpcodeEnd, + }, + LocalTypes: []wasm.ValueType{wasm.ValueTypeV128}, + }}, + }, + expected: []Operation{ + &OperationConstI64{Value: 0}, + &OperationConstI64{Value: 0}, + // [p[0].lo, p[1].hi] -> [p[0].lo, p[1].hi, 0x01, 0x02] + &OperationConstV128{Lo: 0x01, Hi: 0x02}, + // [p[0].lo, p[1].hi, 0x01, 0x02] -> [p[0].lo, 0x02, 0x01, p[1].hi] + &OperationSwap{Depth: 2}, + // [p[0].lo, 0x02, 0x01, p[1].hi] -> [p[0].lo, 0x02, 0x01] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + // [p[0].lo, 0x02, 0x01] -> [0x01, 0x02, p[0].lo] + &OperationSwap{Depth: 2}, + // [0x01, 0x02, p[0].lo] -> [0x01, 0x02] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 1}}, + &OperationBr{Target: &BranchTarget{}}, // return! + }, + }, + { + name: "local.tee - func param", + mod: &wasm.Module{ + TypeSection: []*wasm.FunctionType{{Params: []wasm.ValueType{wasm.ValueTypeV128}}}, + FunctionSection: []wasm.Index{0}, + CodeSection: []*wasm.Code{{Body: []byte{ + wasm.OpcodeVecPrefix, wasm.OpcodeVecV128Const, // [] -> [0x01, 0x02] + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + wasm.OpcodeLocalTee, 0, // [0x01, 0x02] -> [0x01, 0x02] + wasm.OpcodeEnd, + }}}, + }, + expected: []Operation{ + // [p[0].lo, p[1].hi] -> [p[0].lo, p[1].hi, 0x01, 0x02] + &OperationConstV128{Lo: 0x01, Hi: 0x02}, + // [p[0].lo, p[1].hi, 0x01, 0x02] -> [p[0].lo, p[1].hi, 0x01, 0x02, 0x01] + &OperationPick{Depth: 1}, + // [p[0].lo, p[1].hi, 0x01, 0x02, 0x01] -> [p[0].lo, p[1].hi, 0x01, 0x02, 0x01, 0x02] + &OperationPick{Depth: 1}, + // [p[0].lo, p[1].hi, 0x01, 0x02, 0x01, 0x02] -> [p[0].lo, 0x02, 0x01, 0x02, 0x01, p[1].hi] + &OperationSwap{Depth: 4}, + // [p[0].lo, 0x02, 0x01, 0x02, 0x01, p[1].hi] -> [p[0].lo, 0x02, 0x01, 0x02, 0x01] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + // [p[0].lo, 0x02, 0x01, 0x02, 0x01] -> [0x10, 0x02, 0x01, 0x02, p[0].lo] + &OperationSwap{Depth: 4}, + // [0x10, 0x02, 0x01, 0x02, p[0].lo] -> [0x10, 0x02, 0x01, 0x02] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 3}}, + &OperationBr{Target: &BranchTarget{}}, // return! + }, + }, + { + name: "local.tee - non func param", + mod: &wasm.Module{ + TypeSection: []*wasm.FunctionType{{}}, + FunctionSection: []wasm.Index{0}, + CodeSection: []*wasm.Code{{ + Body: []byte{ + wasm.OpcodeVecPrefix, wasm.OpcodeVecV128Const, // [] -> [0x01, 0x02] + 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, + wasm.OpcodeLocalTee, 0, // [0x01, 0x02] -> [0x01, 0x02] + wasm.OpcodeEnd, + }, + LocalTypes: []wasm.ValueType{wasm.ValueTypeV128}, + }}, + }, + expected: []Operation{ + &OperationConstI64{Value: 0}, + &OperationConstI64{Value: 0}, + // [p[0].lo, p[1].hi] -> [p[0].lo, p[1].hi, 0x01, 0x02] + &OperationConstV128{Lo: 0x01, Hi: 0x02}, + // [p[0].lo, p[1].hi, 0x01, 0x02] -> [p[0].lo, p[1].hi, 0x01, 0x02, 0x01] + &OperationPick{Depth: 1}, + // [p[0].lo, p[1].hi, 0x01, 0x02, 0x01] -> [p[0].lo, p[1].hi, 0x01, 0x02, 0x01, 0x02] + &OperationPick{Depth: 1}, + // [p[0].lo, p[1].hi, 0x01, 0x02, 0x01, 0x02] -> [p[0].lo, 0x02, 0x01, 0x02, 0x01, p[1].hi] + &OperationSwap{Depth: 4}, + // [p[0].lo, 0x02, 0x01, 0x02, 0x01, p[1].hi] -> [p[0].lo, 0x02, 0x01, 0x02, 0x01] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + // [p[0].lo, 0x02, 0x01, 0x02, 0x01] -> [0x10, 0x02, 0x01, 0x02, p[0].lo] + &OperationSwap{Depth: 4}, + // [0x10, 0x02, 0x01, 0x02, p[0].lo] -> [0x10, 0x02, 0x01, 0x02] + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}}, + &OperationDrop{Depth: &InclusiveRange{Start: 0, End: 3}}, + &OperationBr{Target: &BranchTarget{}}, // return! + }, + }, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + res, err := CompileFunctions(ctx, wasm.Features20220419, tc.mod) + require.NoError(t, err) + msg := fmt.Sprintf("\nhave:\n\t%s\nwant:\n\t%s", Format(res[0].Operations), Format(tc.expected)) + require.Equal(t, tc.expected, res[0].Operations, msg) + }) + } +} diff --git a/internal/wazeroir/format.go b/internal/wazeroir/format.go index 68f999e8..bf2fd0f3 100644 --- a/internal/wazeroir/format.go +++ b/internal/wazeroir/format.go @@ -181,6 +181,8 @@ func formatOperation(w io.StringWriter, b Operation) { out = "u64" } str = fmt.Sprintf("%s.extend_from.%s", out, in) + case *OperationConstV128: + str = fmt.Sprintf("v128.const [%#x, %#x]", o.Lo, o.Hi) default: panic("unreachable: a bug in wazeroir implementation") } diff --git a/internal/wazeroir/operations.go b/internal/wazeroir/operations.go index 5a8358c0..64b62e0e 100644 --- a/internal/wazeroir/operations.go +++ b/internal/wazeroir/operations.go @@ -287,6 +287,10 @@ func (o OperationKind) String() (ret string) { ret = "TableGrow" case OperationKindTableFill: ret = "TableFill" + case OperationKindConstV128: + ret = "ConstV128" + default: + panic("BUG") } return } @@ -379,6 +383,9 @@ const ( OperationKindTableSize OperationKindTableGrow OperationKindTableFill + OperationKindConstV128 + OperationKindI32x4Add + OperationKindI64x2Add ) type Label struct { @@ -1142,3 +1149,29 @@ type OperationTableFill struct { func (o *OperationTableFill) Kind() OperationKind { return OperationKindTableFill } + +// OperationConstV128 implements Operation. +type OperationConstV128 struct { + Lo, Hi uint64 +} + +// Kind implements Operation.Kind. +func (o *OperationConstV128) Kind() OperationKind { + return OperationKindConstV128 +} + +// OperationI32x4Add implements Operation. +type OperationI32x4Add struct{} + +// Kind implements Operation.Kind. +func (o *OperationI32x4Add) Kind() OperationKind { + return OperationKindI32x4Add +} + +// OperationI64x2Add implements Operation. +type OperationI64x2Add struct{} + +// Kind implements Operation.Kind. +func (o *OperationI64x2Add) Kind() OperationKind { + return OperationKindI64x2Add +} diff --git a/internal/wazeroir/signature.go b/internal/wazeroir/signature.go index 34dd8b39..b3304af9 100644 --- a/internal/wazeroir/signature.go +++ b/internal/wazeroir/signature.go @@ -23,6 +23,9 @@ var ( signature_None_I64 = &signature{ out: []UnsignedType{UnsignedTypeI64}, } + signature_None_I64I64 = &signature{ + out: []UnsignedType{UnsignedTypeI64, UnsignedTypeI64}, + } signature_None_F32 = &signature{ out: []UnsignedType{UnsignedTypeF32}, } @@ -151,6 +154,10 @@ var ( in: []UnsignedType{UnsignedTypeUnknown, UnsignedTypeUnknown, UnsignedTypeI32}, out: []UnsignedType{UnsignedTypeUnknown}, } + signature_I64I64I64I64_I64I64 = &signature{ + in: []UnsignedType{UnsignedTypeI64, UnsignedTypeI64, UnsignedTypeI64, UnsignedTypeI64}, + out: []UnsignedType{UnsignedTypeI64, UnsignedTypeI64}, + } ) // wasmOpcodeSignature returns the signature of given Wasm opcode. @@ -185,50 +192,50 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature if l := uint32(len(c.localTypes)) + inputLen; index >= l { return nil, fmt.Errorf("invalid local index for local.get %d >= %d", index, l) } - var t UnsignedType + var t []UnsignedType if index < inputLen { t = wasmValueTypeToUnsignedType(c.sig.Params[index]) } else { t = wasmValueTypeToUnsignedType(c.localTypes[index-inputLen]) } - return &signature{out: []UnsignedType{t}}, nil + return &signature{out: t}, nil case wasm.OpcodeLocalSet: inputLen := uint32(len(c.sig.Params)) if l := uint32(len(c.localTypes)) + inputLen; index >= l { return nil, fmt.Errorf("invalid local index for local.get %d >= %d", index, l) } - var t UnsignedType + var t []UnsignedType if index < inputLen { t = wasmValueTypeToUnsignedType(c.sig.Params[index]) } else { t = wasmValueTypeToUnsignedType(c.localTypes[index-inputLen]) } - return &signature{in: []UnsignedType{t}}, nil + return &signature{in: t}, nil case wasm.OpcodeLocalTee: inputLen := uint32(len(c.sig.Params)) if l := uint32(len(c.localTypes)) + inputLen; index >= l { return nil, fmt.Errorf("invalid local index for local.get %d >= %d", index, l) } - var t UnsignedType + var t []UnsignedType if index < inputLen { t = wasmValueTypeToUnsignedType(c.sig.Params[index]) } else { t = wasmValueTypeToUnsignedType(c.localTypes[index-inputLen]) } - return &signature{in: []UnsignedType{t}, out: []UnsignedType{t}}, nil + return &signature{in: t, out: t}, nil case wasm.OpcodeGlobalGet: if len(c.globals) <= int(index) { return nil, fmt.Errorf("invalid global index for global.get %d >= %d", index, len(c.globals)) } return &signature{ - out: []UnsignedType{wasmValueTypeToUnsignedType(c.globals[index].ValType)}, + out: wasmValueTypeToUnsignedType(c.globals[index].ValType), }, nil case wasm.OpcodeGlobalSet: if len(c.globals) <= int(index) { return nil, fmt.Errorf("invalid global index for global.get %d >= %d", index, len(c.globals)) } return &signature{ - in: []UnsignedType{wasmValueTypeToUnsignedType(c.globals[index].ValType)}, + in: wasmValueTypeToUnsignedType(c.globals[index].ValType), }, nil case wasm.OpcodeI32Load: return signature_I32_I32, nil @@ -400,6 +407,15 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature default: return nil, fmt.Errorf("unsupported misc instruction in wazeroir: 0x%x", op) } + case wasm.OpcodeVecPrefix: + switch vecOp := c.body[c.pc+1]; vecOp { + case wasm.OpcodeVecV128Const: + return signature_None_I64I64, nil + case wasm.OpcodeVecI32x4Add, wasm.OpcodeVecI64x2Add: + return signature_I64I64I64I64_I64I64, nil + default: + return nil, fmt.Errorf("unsupported vector instruction in wazeroir: 0x%x", op) + } default: return nil, fmt.Errorf("unsupported instruction in wazeroir: 0x%x", op) } @@ -408,26 +424,28 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature func funcTypeToSignature(tps *wasm.FunctionType) *signature { ret := &signature{} for _, vt := range tps.Params { - ret.in = append(ret.in, wasmValueTypeToUnsignedType(vt)) + ret.in = append(ret.in, wasmValueTypeToUnsignedType(vt)...) } for _, vt := range tps.Results { - ret.out = append(ret.out, wasmValueTypeToUnsignedType(vt)) + ret.out = append(ret.out, wasmValueTypeToUnsignedType(vt)...) } return ret } -func wasmValueTypeToUnsignedType(vt wasm.ValueType) UnsignedType { +func wasmValueTypeToUnsignedType(vt wasm.ValueType) []UnsignedType { switch vt { case wasm.ValueTypeI32: - return UnsignedTypeI32 + return []UnsignedType{UnsignedTypeI32} case wasm.ValueTypeI64, // From wazeroir layer, ref type values are opaque 64-bit pointers. wasm.ValueTypeExternref, wasm.ValueTypeFuncref: - return UnsignedTypeI64 + return []UnsignedType{UnsignedTypeI64} case wasm.ValueTypeF32: - return UnsignedTypeF32 + return []UnsignedType{UnsignedTypeF32} case wasm.ValueTypeF64: - return UnsignedTypeF64 + return []UnsignedType{UnsignedTypeF64} + case wasm.ValueTypeV128: + return []UnsignedType{UnsignedTypeI64, UnsignedTypeI64} } panic("unreachable") }