Repository: tetratelabs/wazero
Pull request #556 (2022-05-13 04:35:30)

Implements v128.const and adds support for vector value type. 
This commit implements the `v128.const`, `i32x4.add` and  `i64x2.add` in
interpreter mode and this adds support for the vector value types in the 
locals and globals.

Notably, the vector type values can be passed and returned by exported functions
as well as host functions via two-uint64 encodings as described in https://github.com/tetratelabs/wazero/issues/484#issuecomment-1116866086.

Note: implementation of these instructions on JIT will be done in subsequent PR.

part of #484 


Patch:
diff --git a/README.md b/README.md
index 6e5a1a76..f750165d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 # wazero: the zero dependency WebAssembly runtime for Go developers
 
+[![WebAssembly Core Specification Test](https://github.com/tetratelabs/wazero/actions/workflows/spectest.yaml/badge.svg)](https://github.com/tetratelabs/wazero/actions/workflows/spectest.yaml) [![Go Reference](https://pkg.go.dev/badge/github.com/tetratelabs/wazero.svg)](https://pkg.go.dev/github.com/tetratelabs/wazero) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+
 WebAssembly is a way to safely run code compiled in other languages. Runtimes
 execute WebAssembly Modules (Wasm), which are most often binaries with a `.wasm`
 extension.
@@ -178,170 +180,6 @@ differentiator between wazero and alternatives.
 We verify zero dependencies by running tests in Docker's [scratch image][12].
 This approach ensures compatibility with any parent image.
 
-## Specifications
-
-wazero understands that while no-one desired to create confusion, confusion
-exists both in what is a standard and what in practice is in fact a standard
-feature. To help with this, we created some guidance both on the status quo
-of WebAssembly portability and what we support.
-
-The WebAssembly Core Specification is the only specification relevant to
-wazero, governed by a standards body. Release [1.0][1] is a Web Standard (REC).
-Release [2.0][2] is a Working Draft (WD), so not yet a Web Standard.
-
-Many compilers implement system calls using the WebAssembly System Interface,
-[WASI][5]. WASI is a WebAssembly community [subgroup][4], but has not published
-any working drafts as a result of their work. WASI's last stable point was
-[wasi_snapshot_preview1][13], tagged at the end of 2020.
-
-While this seems scary, the confusion caused by pre-standard features is not as
-bad as it sounds. The WebAssembly ecosystem is generally responsive regardless
-of where things are written down and wazero provides tools, such as built-in
-support for WASI, to reduce pain.
-
-The goal of this section isn't to promote a W3C recommendation exclusive
-approach, rather to help you understand common language around portable
-features and which of those wazero supports at the moment. While we consider
-features formalized through W3C recommendation status mandatory, we actively
-pursue pre-standard features as well interop with commonly used infrastructure
-such as AssemblyScript.
-
-In summary, we hope this section can guide you in terms of what wazero supports
-as well as how to classify a request for a feature we don't yet support.
-
-### WebAssembly Core
-wazero conforms with [spectests][7] defined alongside WebAssembly Core
-Specification [1.0][1]. This is the default [RuntimeConfig][18].
-
-The WebAssembly Core Specification [2.0][2] is in draft form and wazero has
-[work in progress][14] towards that. Opt in via the below configuration:
-```go
-rConfig = wazero.NewRuntimeConfig().WithWasmCore2()
-```
-
-One current limitation of wazero is that it doesn't fully implement the Text
-Format, yet, e.g. compiling `.wat` files. The intent is to [finish this][15],
-and meanwhile users can work around this using tools such as `wat2wasm` to
-compile the text format into the binary format. In practice, the text format is
-too low level for most users, so delays here have limited impact.
-
-#### Post 2.0 Features
-Features regardless of W3C release are inventoried in the [Proposals][16].
-repository. wazero implements [Finished Proposals][17] based on user demand,
-using [wazero.RuntimeConfig][18] feature flags.
-
-Features not yet assigned to a W3C release are not reliable. Encourage the
-[WebAssembly community][19] to formalize features you rely on, so that they
-become assigned to a release, and reach the W3C recommendation (REC) phase.
-
-### WebAssembly System Interface (WASI)
-
-Many compilers implement system calls using the WebAssembly System Interface,
-[WASI][5]. WASI is a WebAssembly community [subgroup][4], but has not published
-any working drafts as a result of their work. WASI's last stable point was
-[wasi_snapshot_preview1][13], tagged at the end of 2020.
-
-Some functions in this tag are used in practice while some others are not known
-to be used at all. Further confusion exists because some compilers, like
-GrainLang, import functions not used. Finally, [wasi_snapshot_preview1][13]
-includes features such as "rights" that [will be removed][20].
-
-For all of these reasons, wazero will not implement all WASI features, just to
-complete the below chart. If you desire something not yet implemented, please
-[raise an issue](https://github.com/tetratelabs/wazero/issues/new) and include
-your use case (ex which language you are using to compile, a.k.a. target Wasm).
-
-<details><summary>Click to see the full list of supported WASI functions</summary>
-<p>
-
-| Function                | Status | Known Usage    |
-|:-----------------------:|:------:|---------------:|
-| args_get                |   ✅   | TinyGo         |
-| args_sizes_get          |   ✅   | TinyGo         |
-| environ_get             |   ✅   | TinyGo         |
-| environ_sizes_get       |   ✅   | TinyGo         |
-| clock_res_get           |   ❌   |                |
-| clock_time_get          |   ✅   | TinyGo         |
-| fd_advise               |   ❌   |                |
-| fd_allocate             |   ❌   |                |
-| fd_close                |   ✅   | TinyGo         |
-| fd_datasync             |   ❌   |                |
-| fd_fdstat_get           |   ✅   | TinyGo         |
-| fd_fdstat_set_flags     |   ❌   |                |
-| fd_fdstat_set_rights    |   ❌   |                |
-| fd_filestat_get         |   ❌   |                |
-| fd_filestat_set_size    |   ❌   |                |
-| fd_filestat_set_times   |   ❌   |                |
-| fd_pread                |   ❌   |                |
-| fd_prestat_get          |   ✅   | TinyGo,`fs.FS` |
-| fd_prestat_dir_name     |   ✅   | TinyGo         |
-| fd_pwrite               |   ❌   |                |
-| fd_read                 |   ✅   | TinyGo,`fs.FS` |
-| fd_readdir              |   ❌   |                |
-| fd_renumber             |   ❌   |                |
-| fd_seek                 |   ✅   | TinyGo         |
-| fd_sync                 |   ❌   |                |
-| fd_tell                 |   ❌   |                |
-| fd_write                |   ✅   | `fs.FS`        |
-| path_create_directory   |   ❌   |                |
-| path_filestat_get       |   ❌   |                |
-| path_filestat_set_times |   ❌   |                |
-| path_link               |   ❌   |                |
-| path_open               |   ✅   | TinyGo,`fs.FS` |
-| path_readlink           |   ❌   |                |
-| path_remove_directory   |   ❌   |                |
-| path_rename             |   ❌   |                |
-| path_symlink            |   ❌   |                |
-| path_unlink_file        |   ❌   |                |
-| poll_oneoff             |   ✅   | TinyGo         |
-| proc_exit               |   ✅   | AssemblyScript |
-| proc_raise              |   ❌   |                |
-| sched_yield             |   ❌   |                |
-| random_get              |   ✅   |                |
-| sock_recv               |   ❌   |                |
-| sock_send               |   ❌   |                |
-| sock_shutdown           |   ❌   |                |
-
-</p>
-</details>
-
-## History of wazero
-
-wazero was originally developed by [Takeshi Yoneda][21] as a hobby project in
-mid 2020. In late 2021, it was sponsored by Tetrate as a top-level project.
-That said, Takeshi's original motivation is as relevant today as when he
-started the project, and worthwhile reading:
-
-If you want to provide Wasm host environments in your Go programs, currently
-there's no other choice than using CGO leveraging the state-of-the-art
-runtimes written in C++/Rust (e.g. V8, Wasmtime, Wasmer, WAVM, etc.), and
-there's no pure Go Wasm runtime out there. (There's only one exception named
-[wagon][22], but it was archived with the mention to this project.)
-
-First, why do you want to write host environments in Go? You might want to have
-plugin systems in your Go project and want these plugin systems to be
-safe/fast/flexible, and enable users to write plugins in their favorite
-languages. That's where Wasm comes into play. You write your own Wasm host
-environments and embed Wasm runtime in your projects, and now users are able to
-write plugins in their own favorite languages (AssemblyScript, C, C++, Rust,
-Zig, etc.). As a specific example, you maybe write proxy severs in Go and want
-to allow users to extend the proxy via [Proxy-Wasm ABI][23]. Maybe you are
-writing server-side rendering applications via Wasm, or [OpenPolicyAgent][24]
-is using Wasm for plugin system.
-
-However, experienced Golang developers often avoid using CGO because it
-introduces complexity. For example, CGO projects are larger and complicated to
-consume due to their libc + shared library dependency. Debugging is more
-difficult for Go developers when most of a library is written in Rustlang.
-[_CGO is not Go_][25] [ -- _Rob_ _Pike_][26] dives in deeper. In short, the
-primary motivation to start wazero was to avoid CGO.
-
-wazero compiles WebAssembly modules into native assembly (JIT) by default. You
-may be surprised to find equal or better performance vs mature JIT-style
-runtimes because [CGO is slow][27]. More specifically, if you make large amount
-of CGO calls which cross the boundary between Go and C (stack) space, then the
-usage of CGO could be a bottleneck.
-
 -----
 wazero is a registered trademark of Tetrate.io, Inc. in the United States and/or other countries
 
@@ -358,17 +196,3 @@ wazero is a registered trademark of Tetrate.io, Inc. in the United States and/or
 [11]: https://github.com/actions/virtual-environments
 [12]: https://docs.docker.com/develop/develop-images/baseimages/#create-a-simple-parent-image-using-scratch
 [13]: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
-[14]: https://github.com/tetratelabs/wazero/issues/484
-[15]: https://github.com/tetratelabs/wazero/issues/59
-[16]: https://github.com/WebAssembly/proposals
-[17]: https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md
-[18]: https://pkg.go.dev/github.com/tetratelabs/wazero#RuntimeConfig
-[19]: https://www.w3.org/community/webassembly/
-[20]: https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844
-[21]: https://github.com/mathetake
-[22]: https://github.com/go-interpreter/wagon
-[23]: https://github.com/proxy-wasm/spec
-[24]: https://www.openpolicyagent.org/docs/latest/wasm/
-[25]: https://dave.cheney.net/2016/01/18/cgo-is-not-go
-[26]: https://www.youtube.com/watch?v=PAAkCSZUG1c&t=757s
-[27]: https://github.com/golang/go/issues/19574
diff --git a/api/wasm.go b/api/wasm.go
index f366ef70..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"
 	}
@@ -129,22 +141,6 @@ type Module interface {
 	// Name is the name this module was instantiated with. Exported functions can be imported with this name.
 	Name() string
 
-	// Close is a convenience that invokes CloseWithExitCode with zero.
-	// Note: When the context is nil, it defaults to context.Background.
-	Close(context.Context) error
-
-	// CloseWithExitCode releases resources allocated for this Module. Use a non-zero exitCode parameter to indicate a
-	// failure to ExportedFunction callers.
-	//
-	// The error returned here, if present, is about resource de-allocation (such as I/O errors). Only the last error is
-	// returned, so a non-nil return means at least one error happened. Regardless of error, this module instance will
-	// be removed, making its name available again.
-	//
-	// Calling this inside a host function is safe, and may cause ExportedFunction callers to receive a sys.ExitError
-	// with the exitCode.
-	// Note: When the context is nil, it defaults to context.Background.
-	CloseWithExitCode(ctx context.Context, exitCode uint32) error
-
 	// Memory returns a memory defined in this module or nil if there are none wasn't.
 	Memory() Memory
 
@@ -162,6 +158,30 @@ type Module interface {
 
 	// ExportedGlobal a global exported from this module or nil if it wasn't.
 	ExportedGlobal(name string) Global
+
+	// CloseWithExitCode releases resources allocated for this Module. Use a non-zero exitCode parameter to indicate a
+	// failure to ExportedFunction callers.
+	//
+	// The error returned here, if present, is about resource de-allocation (such as I/O errors). Only the last error is
+	// returned, so a non-nil return means at least one error happened. Regardless of error, this module instance will
+	// be removed, making its name available again.
+	//
+	// Calling this inside a host function is safe, and may cause ExportedFunction callers to receive a sys.ExitError
+	// with the exitCode.
+	// Note: When the context is nil, it defaults to context.Background.
+	CloseWithExitCode(ctx context.Context, exitCode uint32) error
+
+	// Closer closes this module by delegating to CloseWithExitCode with an exit code of zero.
+	Closer
+}
+
+// Closer closes a resource.
+//
+// Note: This is an interface for decoupling, not third-party implementations. All implementations are in wazero.
+type Closer interface {
+	// Close closes the resource.
+	// Note: When the context is nil, it defaults to context.Background.
+	Close(context.Context) error
 }
 
 // Function is a WebAssembly 1.0 (20191205) function exported from an instantiated module (wazero.Runtime InstantiateModule).
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")
 }
diff --git a/site/config.toml b/site/config.toml
index d7a185b1..8ade8642 100644
--- a/site/config.toml
+++ b/site/config.toml
@@ -15,12 +15,33 @@ theme = "hello-friend"
       [[languages.en.menu.main]]
         url = "https://pkg.go.dev/github.com/tetratelabs/wazero"
         name = "API"
-        weight = 10
+        weight = 1
       [[languages.en.menu.main]]
         url = "https://github.com/tetratelabs/wazero/blob/main/examples"
         name = "Examples"
-        weight = 10
+        weight = 2
       [[languages.en.menu.main]]
-        url = "https://github.com/tetratelabs/wazero"
-        name = "GitHub"
-        weight = 20
+        identifier = "community"
+        name = "Community"
+        weight = 3
+        [[languages.en.menu.main]]
+          parent = "community"
+          url = "https://github.com/tetratelabs/wazero"
+          name = "GitHub"
+          weight = 1
+        [[languages.en.menu.main]]
+          parent = "community"
+          url = "/history/"
+          identifier = "history"
+          name = "History"
+          weight = 2
+      [[languages.en.menu.main]]
+        identifier = "appendix"
+        name = "Appendix"
+        weight = 4
+        [[languages.en.menu.main]]
+          parent = "appendix"
+          url = "/specs/"
+          identifier = "specs"
+          name = "Specifications"
+          weight = 1
diff --git a/site/content/history.md b/site/content/history.md
new file mode 100644
index 00000000..6de04325
--- /dev/null
+++ b/site/content/history.md
@@ -0,0 +1,46 @@
++++
+title = "History of wazero"
++++
+
+wazero was originally developed by [Takeshi Yoneda][1] as a hobby project in
+mid 2020. In late 2021, it was sponsored by Tetrate as a top-level project.
+That said, Takeshi's original motivation is as relevant today as when he
+started the project, and worthwhile reading:
+
+If you want to provide Wasm host environments in your Go programs, currently
+there's no other choice than using CGO leveraging the state-of-the-art
+runtimes written in C++/Rust (e.g. V8, Wasmtime, Wasmer, WAVM, etc.), and
+there's no pure Go Wasm runtime out there. (There's only one exception named
+[wagon][2], but it was archived with the mention to this project.)
+
+First, why do you want to write host environments in Go? You might want to have
+plugin systems in your Go project and want these plugin systems to be
+safe/fast/flexible, and enable users to write plugins in their favorite
+languages. That's where Wasm comes into play. You write your own Wasm host
+environments and embed Wasm runtime in your projects, and now users are able to
+write plugins in their own favorite languages (AssemblyScript, C, C++, Rust,
+Zig, etc.). As a specific example, you maybe write proxy severs in Go and want
+to allow users to extend the proxy via [Proxy-Wasm ABI][3]. Maybe you are
+writing server-side rendering applications via Wasm, or [OpenPolicyAgent][4]
+is using Wasm for plugin system.
+
+However, experienced Golang developers often avoid using CGO because it
+introduces complexity. For example, CGO projects are larger and complicated to
+consume due to their libc + shared library dependency. Debugging is more
+difficult for Go developers when most of a library is written in Rustlang.
+[_CGO is not Go_][5] [ -- _Rob_ _Pike_][6] dives in deeper. In short, the
+primary motivation to start wazero was to avoid CGO.
+
+wazero compiles WebAssembly modules into native assembly (JIT) by default. You
+may be surprised to find equal or better performance vs mature JIT-style
+runtimes because [CGO is slow][7]. More specifically, if you make large amount
+of CGO calls which cross the boundary between Go and C (stack) space, then the
+usage of CGO could be a bottleneck.
+
+[1]: https://github.com/mathetake
+[2]: https://github.com/go-interpreter/wagon
+[3]: https://github.com/proxy-wasm/spec
+[4]: https://www.openpolicyagent.org/docs/latest/wasm/
+[5]: https://dave.cheney.net/2016/01/18/cgo-is-not-go
+[6]: https://www.youtube.com/watch?v=PAAkCSZUG1c&t=757s
+[7]: https://github.com/golang/go/issues/19574
diff --git a/site/content/specs.md b/site/content/specs.md
new file mode 100644
index 00000000..ba58d43e
--- /dev/null
+++ b/site/content/specs.md
@@ -0,0 +1,142 @@
++++
+title = "Specifications"
++++
+
+wazero understands that while no-one desired to create confusion, confusion
+exists both in what is a standard and what in practice is in fact a standard
+feature. To help with this, we created some guidance both on the status quo
+of WebAssembly portability and what we support.
+
+The WebAssembly Core Specification is the only specification relevant to
+wazero, governed by a standards body. Release [1.0][1] is a Web Standard (REC).
+Release [2.0][2] is a Working Draft (WD), so not yet a Web Standard.
+
+Many compilers implement system calls using the WebAssembly System Interface,
+[WASI][5]. WASI is a WebAssembly community [subgroup][3], but has not published
+any working drafts as a result of their work. WASI's last stable point was
+[wasi_snapshot_preview1][4], tagged at the end of 2020.
+
+While this seems scary, the confusion caused by pre-standard features is not as
+bad as it sounds. The WebAssembly ecosystem is generally responsive regardless
+of where things are written down and wazero provides tools, such as built-in
+support for WASI, to reduce pain.
+
+The goal of this section isn't to promote a W3C recommendation exclusive
+approach, rather to help you understand common language around portable
+features and which of those wazero supports at the moment. While we consider
+features formalized through W3C recommendation status mandatory, we actively
+pursue pre-standard features as well interop with commonly used infrastructure
+such as AssemblyScript.
+
+In summary, we hope this section can guide you in terms of what wazero supports
+as well as how to classify a request for a feature we don't yet support.
+
+### WebAssembly Core
+wazero conforms with [spectests][6] defined alongside WebAssembly Core
+Specification [1.0][1]. This is the default [RuntimeConfig][7].
+
+The WebAssembly Core Specification [2.0][2] is in draft form and wazero has
+[work in progress][8] towards that. Opt in via the below configuration:
+```go
+rConfig = wazero.NewRuntimeConfig().WithWasmCore2()
+```
+
+One current limitation of wazero is that it doesn't fully implement the Text
+Format, yet, e.g. compiling `.wat` files. The intent is to [finish this][9],
+and meanwhile users can work around this using tools such as `wat2wasm` to
+compile the text format into the binary format. In practice, the text format is
+too low level for most users, so delays here have limited impact.
+
+#### Post 2.0 Features
+Features regardless of W3C release are inventoried in the [Proposals][10].
+repository. wazero implements [Finished Proposals][11] based on user demand,
+using [wazero.RuntimeConfig][7] feature flags.
+
+Features not yet assigned to a W3C release are not reliable. Encourage the
+[WebAssembly community][12] to formalize features you rely on, so that they
+become assigned to a release, and reach the W3C recommendation (REC) phase.
+
+### WebAssembly System Interface (WASI)
+
+Many compilers implement system calls using the WebAssembly System Interface,
+[WASI][5]. WASI is a WebAssembly community [subgroup][3], but has not published
+any working drafts as a result of their work. WASI's last stable point was
+[wasi_snapshot_preview1][4], tagged at the end of 2020.
+
+Some functions in this tag are used in practice while some others are not known
+to be used at all. Further confusion exists because some compilers, like
+GrainLang, import functions not used. Finally, [wasi_snapshot_preview1][4]
+includes features such as "rights" that [will be removed][13].
+
+For all of these reasons, wazero will not implement all WASI features, just to
+complete the below chart. If you desire something not yet implemented, please
+[raise an issue](https://github.com/tetratelabs/wazero/issues/new) and include
+your use case (ex which language you are using to compile, a.k.a. target Wasm).
+
+<details><summary>Click to see the full list of supported WASI functions</summary>
+<p>
+
+| Function                | Status | Known Usage    |
+|:-----------------------:|:------:|---------------:|
+| args_get                |   ✅   | TinyGo         |
+| args_sizes_get          |   ✅   | TinyGo         |
+| environ_get             |   ✅   | TinyGo         |
+| environ_sizes_get       |   ✅   | TinyGo         |
+| clock_res_get           |   ❌   |                |
+| clock_time_get          |   ✅   | TinyGo         |
+| fd_advise               |   ❌   |                |
+| fd_allocate             |   ❌   |                |
+| fd_close                |   ✅   | TinyGo         |
+| fd_datasync             |   ❌   |                |
+| fd_fdstat_get           |   ✅   | TinyGo         |
+| fd_fdstat_set_flags     |   ❌   |                |
+| fd_fdstat_set_rights    |   ❌   |                |
+| fd_filestat_get         |   ❌   |                |
+| fd_filestat_set_size    |   ❌   |                |
+| fd_filestat_set_times   |   ❌   |                |
+| fd_pread                |   ❌   |                |
+| fd_prestat_get          |   ✅   | TinyGo,`fs.FS` |
+| fd_prestat_dir_name     |   ✅   | TinyGo         |
+| fd_pwrite               |   ❌   |                |
+| fd_read                 |   ✅   | TinyGo,`fs.FS` |
+| fd_readdir              |   ❌   |                |
+| fd_renumber             |   ❌   |                |
+| fd_seek                 |   ✅   | TinyGo         |
+| fd_sync                 |   ❌   |                |
+| fd_tell                 |   ❌   |                |
+| fd_write                |   ✅   | `fs.FS`        |
+| path_create_directory   |   ❌   |                |
+| path_filestat_get       |   ❌   |                |
+| path_filestat_set_times |   ❌   |                |
+| path_link               |   ❌   |                |
+| path_open               |   ✅   | TinyGo,`fs.FS` |
+| path_readlink           |   ❌   |                |
+| path_remove_directory   |   ❌   |                |
+| path_rename             |   ❌   |                |
+| path_symlink            |   ❌   |                |
+| path_unlink_file        |   ❌   |                |
+| poll_oneoff             |   ✅   | TinyGo         |
+| proc_exit               |   ✅   | AssemblyScript |
+| proc_raise              |   ❌   |                |
+| sched_yield             |   ❌   |                |
+| random_get              |   ✅   |                |
+| sock_recv               |   ❌   |                |
+| sock_send               |   ❌   |                |
+| sock_shutdown           |   ❌   |                |
+
+</p>
+</details>
+
+[1]: https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/
+[2]: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/
+[3]: https://github.com/WebAssembly/meetings/blob/main/process/subgroups.md
+[4]: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md
+[5]: https://github.com/WebAssembly/WASI
+[6]: https://github.com/WebAssembly/spec/tree/wg-1.0/test/core
+[7]: https://pkg.go.dev/github.com/tetratelabs/wazero#RuntimeConfig
+[8]: https://github.com/tetratelabs/wazero/issues/484
+[9]: https://github.com/tetratelabs/wazero/issues/59
+[10]: https://github.com/WebAssembly/proposals
+[11]: https://github.com/WebAssembly/proposals/blob/main/finished-proposals.md
+[12]: https://www.w3.org/community/webassembly/
+[13]: https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844
diff --git a/site/layouts/partials/menu.html b/site/layouts/partials/menu.html
new file mode 100644
index 00000000..df00a70f
--- /dev/null
+++ b/site/layouts/partials/menu.html
@@ -0,0 +1,38 @@
+<nav class="menu">
+  <ul class="menu__inner menu__inner--desktop">
+    {{ range $.Site.Menus.main }}
+      {{ if not .HasChildren }}
+        <li><a href="{{ .URL }}">{{ .Name }}</a></li>
+      <!-- The default theme supports a drop-down for showMenuItems > 0. We re-use this class
+           for child menus instead. -->
+      {{ else }}
+        <ul class="menu__sub-inner">
+          <li class="menu__sub-inner-more-trigger">
+            {{ .Name }}
+            <span class="menu__sub-inner-more-trigger-icon">{{ partial "greater-icon.html" . }}</span>
+          </li>
+          <ul class="menu__sub-inner-more hidden">
+            {{ range .Children }}
+              <li><a href="{{ .URL }}">{{ .Name }}</a></li>
+            {{ end }}
+          </ul>
+        </ul>
+      {{ end }}
+    {{ end }}
+  </ul>
+
+  <!-- Handle mobile -->
+  <ul class="menu__inner menu__inner--mobile">
+    {{ range $.Site.Menus.main }}
+      {{ if not .HasChildren }}
+        <li><a href="{{ .URL }}">{{ .Name }}</a></li>
+      {{ else }}
+        <!-- TODO: At some point, this will get too long, and also this loses
+                   categorization. Implement CSS like desktop for sub-menus -->
+        {{ range .Children }}
+        <li><a href="{{ .URL }}">{{ .Name }}</a></li>
+        {{ end }}
+      {{ end }}
+    {{ end }}
+  </ul>
+</nav>
diff --git a/site/static/assets/main.js b/site/static/assets/main.js
new file mode 100644
index 00000000..e2071f50
--- /dev/null
+++ b/site/static/assets/main.js
@@ -0,0 +1,1 @@
+!function(){var e,t,n,o,i,r,c,d,s,a,l;e=document.querySelector(".container"),t=document.querySelector(".menu"),n=document.querySelector(".menu-trigger"),o=document.querySelector(".menu__inner--desktop"),i=document.querySelectorAll(".menu__sub-inner-more"),r=getComputedStyle(document.body).getPropertyValue("--phoneWidth"),c=function(){return window.matchMedia(r).matches},d=function(){n&&n.classList.toggle("hidden",!c()),t&&t.classList.toggle("hidden",c()),i.forEach((function(e){return e.classList.toggle("hidden",!c())}))},t&&t.addEventListener("click",(function(e){return e.stopPropagation()})),i.forEach((function(e){return e.addEventListener("click",(function(e){return e.stopPropagation()}))})),d(),document.body.addEventListener("click",(function(){c()?c()&&!t.classList.contains("hidden")&&t.classList.add("hidden"):i.forEach((function(e){return e.classList.add("hidden")}))})),window.addEventListener("resize",d),n&&n.addEventListener("click",(function(e){e.stopPropagation(),t&&t.classList.toggle("hidden")})),o&&o.querySelectorAll(".menu__sub-inner").forEach((function(t){var n=t.querySelector(".menu__sub-inner-more-trigger"),o=t.querySelector(".menu__sub-inner-more");n.addEventListener("click",(function(t){i.forEach((function(e){e!==o&&e.classList.add("hidden")})),t.stopPropagation(),o&&o.classList.toggle("hidden"),o&&o.getBoundingClientRect().right>e.getBoundingClientRect().right&&(o.style.left="auto",o.style.right=0)}))})),s=window.localStorage&&window.localStorage.getItem("theme"),a=document.querySelector(".theme-toggle"),l="dark"===s,null!==s&&document.body.classList.toggle("dark-theme",l),a.addEventListener("click",(function(){document.body.classList.toggle("dark-theme"),window.localStorage&&window.localStorage.setItem("theme",document.body.classList.contains("dark-theme")?"dark":"light")}))}();
\ No newline at end of file
diff --git a/wasi/wasi.go b/wasi/wasi.go
index 8288847c..6e2e0949 100644
--- a/wasi/wasi.go
+++ b/wasi/wasi.go
@@ -36,8 +36,8 @@ const ModuleSnapshotPreview1 = "wasi_snapshot_preview1"
 //	mod, _ := r.InstantiateModuleFromCode(ctx, source)
 //
 // Note: All WASI functions return a single Errno result, ErrnoSuccess on success.
-// Note: Closing the wazero.Runtime closes any api.Module it instantiated.
-func InstantiateSnapshotPreview1(ctx context.Context, r wazero.Runtime) (api.Module, error) {
+// Note: Closing the wazero.Runtime closes this instance of WASI as well.
+func InstantiateSnapshotPreview1(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
 	_, fns := snapshotPreview1Functions(ctx)
 	return r.NewModuleBuilder(ModuleSnapshotPreview1).ExportFunctions(fns).Instantiate(ctx)
 }
diff --git a/wasm.go b/wasm.go
index ce73069c..bd34be14 100644
--- a/wasm.go
+++ b/wasm.go
@@ -98,19 +98,6 @@ type Runtime interface {
 	// Note: When the context is nil, it defaults to context.Background.
 	InstantiateModule(ctx context.Context, compiled CompiledModule, config ModuleConfig) (api.Module, error)
 
-	// Close closes all the modules that have been initialized in this Runtime with an exit code of 0.
-	// An error is returned if any module returns an error when closed.
-	//
-	// Ex.
-	//	ctx := context.Background()
-	//	r := wazero.NewRuntime()
-	//	defer r.Close(ctx) // This closes everything this Runtime created.
-	//
-	//	// Everything below here can be closed, but will anyway due to above.
-	//	_, _ = wasi.InstantiateSnapshotPreview1(ctx, r)
-	//	mod, _ := r.InstantiateModuleFromCode(ctx, source)
-	Close(context.Context) error
-
 	// CloseWithExitCode closes all the modules that have been initialized in this Runtime with the provided exit code.
 	// An error is returned if any module returns an error when closed.
 	//
@@ -123,6 +110,10 @@ type Runtime interface {
 	//	_, _ = wasi.InstantiateSnapshotPreview1(ctx, r)
 	//	mod, _ := r.InstantiateModuleFromCode(ctx, source)
 	CloseWithExitCode(ctx context.Context, exitCode uint32) error
+
+	// Closer closes resources initialized by this Runtime by delegating to CloseWithExitCode with an exit code of
+	// zero.
+	api.Closer
 }
 
 func NewRuntime() Runtime {


Test patch:
diff --git a/.github/workflows/spectest.yaml b/.github/workflows/spectest.yaml
index b18a9fc9..9dceecfa 100644
--- a/.github/workflows/spectest.yaml
+++ b/.github/workflows/spectest.yaml
@@ -2,8 +2,16 @@ name: WebAssembly Core Specification Test
 on:
   pull_request:
     branches: [main]
+    paths-ignore:  # ignore docs as they are built with Netlify.
+      - '**/*.md'
+      - 'site/**'
+      - 'netlify.toml'
   push:
     branches: [main]
+    paths-ignore:  # ignore docs as they are built with Netlify.
+      - '**/*.md'
+      - 'site/**'
+      - 'netlify.toml'
 
 defaults:
   run:  # use bash for all operating systems unless overridden
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/integration_test/vs/runtime.go b/internal/integration_test/vs/runtime.go
index 28d104ec..7cff9c02 100644
--- a/internal/integration_test/vs/runtime.go
+++ b/internal/integration_test/vs/runtime.go
@@ -58,8 +58,9 @@ type wazeroRuntime struct {
 }
 
 type wazeroModule struct {
-	wasi, env, mod api.Module
-	funcs          map[string]api.Function
+	wasi     api.Closer
+	env, mod api.Module
+	funcs    map[string]api.Function
 }
 
 func (r *wazeroRuntime) Name() string {
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)
+		})
+	}
+}