Repository: tetratelabs/wazero
Pull request #561 (2022-05-16 01:23:14)

Update LICENSE/NOTICE to reference wazero authors


Patch:
diff --git a/LICENSE b/LICENSE
index cef6245a..bf89209d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright 2020-2021 Tetrate
+   Copyright 2020-2021 wazero authors
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/NOTICE b/NOTICE
index 5a4903d8..273990ee 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,2 +1,2 @@
 wazero
-Copyright 2020-2021 Tetrate
+Copyright 2020-2021 wazero authors
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/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/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/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/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/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/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/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/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/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/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")
 }


Test patch:
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/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_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/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_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/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_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_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_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_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)
+		})
+	}
+}