Repository: tetratelabs/wazero
Pull request #616 (2022-06-02 09:56:46)

Adds sys.Walltime and sys.Nanotime for security and determinism
This adds two clock interfaces: `sys.Walltime` and `sys.Nanotime` to
allow implementations to override readings for purposes of security or
determinism.

The default values of both are a fake timestamp, to avoid the sandbox
break we formerly had by returning the real time. This is similar to how
we don't inherit OS Env values.

To use real clocks, users can do the following:
```go
moduleBuilder.WithSysWalltime().WithSysNanotime()
```

They can also provide their own deterministic or higher performing clock like so:
```go
moduleBuilder.
	 WithWalltime(walltime, sys.ClockResolution(time.Microsecond.Nanoseconds())).
	 WithNanotime(nanotime, sys.ClockResolution(1))
```

This change allowing consistent implementation of WASI, WASI 2, and wasm_exec clock imports.

See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_time_getid-clockid-precision-timestamp---errno-timestamp
See https://github.com/WebAssembly/wasi-clocks
See https://github.com/golang/go/blob/252324e879e32f948d885f787decf8af06f82be9/misc/wasm/wasm_exec.js#L243-L255


codefromthecrypt: cc @pkedy @sam-at-luther. I'll implement both of these with `time.Now()` for the default impl. Scream if something concerns you!
codefromthecrypt: PS one thing I had considered is just a `func` instead of an interface for the two clocks. The clock res function isn't used anywhere in wasm I can find that runs outside the browser, and it is going to be very wrong in most cases. It is possible we could just stub that out independently to make the time plugins a func and just default via different config the resolution. For example resolution should be static unlike the returned value of the clock.

Ex. `builder.WithMontonicTime(clock func() int64, uint32 resolution)`

@mathetake @anuraaga wdyt?

Patch:
diff --git a/Makefile b/Makefile
index 23590110..6b359e62 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@ bench:
 bench.check:
 	@go build ./internal/integration_test/bench/...
 	@# Don't use -test.benchmem as it isn't accurate when comparing against CGO libs
-	@for d in vs/wasmedge vs/wasmer vs/wasmtime ; do \
+	@for d in vs/time vs/wasmedge vs/wasmer vs/wasmtime ; do \
 		cd ./internal/integration_test/$$d ; \
 		go test -bench=. . -tags='wasmedge' $(ensureCompilerFastest) ; \
 		cd - ;\
@@ -131,7 +131,7 @@ golangci_lint_goarch ?= $(shell go env GOARCH)
 
 .PHONY: lint
 lint: $(golangci_lint_path)
-	@GOARCH=$(golangci_lint_goarch) $(golangci_lint_path) run --timeout 5m
+	@GOARCH=$(golangci_lint_goarch) CGO_ENABLED=0 $(golangci_lint_path) run --timeout 5m
 
 .PHONY: format
 format:
diff --git a/RATIONALE.md b/RATIONALE.md
index 9e192da1..32108c08 100644
--- a/RATIONALE.md
+++ b/RATIONALE.md
@@ -396,7 +396,56 @@ See https://github.com/bytecodealliance/wasmtime/blob/2ca01ae9478f199337cf743a6a
 
 Their semantics match when `pathLen` == the length of `path`, so in practice this difference won't matter match.
 
-### ClockResGet
+## sys.Walltime and Nanotime
+
+The `sys` package has two function types, `Walltime` and `Nanotime` for real
+and monotonic clock exports. The naming matches conventions used in Go.
+
+```go
+func time_now() (sec int64, nsec int32, mono int64) {
+	sec, nsec = walltime()
+	return sec, nsec, nanotime()
+}
+```
+
+Splitting functions for wall and clock time allow implementations to choose
+whether to implement the clock once (as in Go), or split them out.
+
+Each can be configured with a `ClockResolution`, although is it usually
+incorrect as detailed in a sub-heading below. The only reason for exposing this
+is to satisfy WASI:
+
+See https://github.com/WebAssembly/wasi-clocks
+
+## Why default to fake time?
+
+WebAssembly has an implicit design pattern of capabilities based security. By
+defaulting to a fake time, we reduce the chance of timing attacks, at the cost
+of requiring configuration to opt-into real clocks.
+
+See https://gruss.cc/files/fantastictimers.pdf for an example attacks.
+
+## Why not `time.Clock`?
+
+wazero can't use `time.Clock` as a plugin for clock implementation as it is
+only substitutable with build flags (`faketime`) and conflates wall and
+monotonic time in the same call.
+
+Go's `time.Clock` was added monotonic time after the fact. For portability with
+prior APIs, a decision was made to combine readings into the same API call.
+
+See https://go.googlesource.com/proposal/+/master/design/12914-monotonic.md
+
+WebAssembly time imports do not have the same concern. In fact even Go's
+imports for clocks split walltime from nanotime readings.
+
+See https://github.com/golang/go/blob/252324e879e32f948d885f787decf8af06f82be9/misc/wasm/wasm_exec.js#L243-L255
+
+Finally, Go's clock is not an interface. WebAssembly users who want determinism
+or security need to be able to substitute an alternative clock implementation
+from the host process one.
+
+### `ClockResolution`
 
 A clock's resolution is hardware and OS dependent so requires a system call to retrieve an accurate value.
 Go does not provide a function for getting resolution, so without CGO we don't have an easy way to get an actual
diff --git a/README.md b/README.md
index cbe1caf3..badca976 100644
--- a/README.md
+++ b/README.md
@@ -85,14 +85,17 @@ The [wasi_snapshot_preview1][13] tag of WASI is widely implemented, so wazero
 bundles an implementation. That way, you don't have to write these functions.
 
 For example, here's how you can allow WebAssembly modules to read
-"/work/home/a.txt" as "/a.txt" or "./a.txt":
+"/work/home/a.txt" as "/a.txt" or "./a.txt" as well the system clock:
 ```go
 _, err := wasi_snapshot_preview1.Instantiate(ctx, r)
 if err != nil {
     log.Panicln(err)
 }
 
-config := wazero.NewModuleConfig().WithFS(os.DirFS("/work/home"))
+config := wazero.NewModuleConfig().
+	WithFS(os.DirFS("/work/home")). // instead of no file system
+    WithSysWalltime().WithSysNanotime() // instead of fake time
+
 module, err := r.InstantiateModule(ctx, compiled, config)
 ...
 ```
diff --git a/assemblyscript/assemblyscript.go b/assemblyscript/assemblyscript.go
index ca19816d..3aeaf91f 100644
--- a/assemblyscript/assemblyscript.go
+++ b/assemblyscript/assemblyscript.go
@@ -27,6 +27,7 @@ import (
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/ieee754"
+	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
 
@@ -155,7 +156,7 @@ func (a *assemblyscript) abort(
 	columnNumber uint32,
 ) {
 	if !a.abortMessageDisabled {
-		sys := sysCtx(mod)
+		sysCtx := sysCtx(mod)
 		msg, err := readAssemblyScriptString(ctx, mod, message)
 		if err != nil {
 			return
@@ -164,7 +165,7 @@ func (a *assemblyscript) abort(
 		if err != nil {
 			return
 		}
-		_, _ = fmt.Fprintf(sys.Stderr(), "%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
+		_, _ = fmt.Fprintf(sysCtx.Stderr(), "%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
 	}
 	_ = mod.CloseWithExitCode(ctx, 255)
 }
@@ -269,7 +270,7 @@ func decodeUTF16(b []byte) string {
 	return string(utf16.Decode(u16s))
 }
 
-func sysCtx(m api.Module) *wasm.SysContext {
+func sysCtx(m api.Module) *internalsys.Context {
 	if internal, ok := m.(*wasm.CallContext); !ok {
 		panic(fmt.Errorf("unsupported wasm.Module implementation: %v", m))
 	} else {
diff --git a/config.go b/config.go
index 8d6dbfe2..1cc86891 100644
--- a/config.go
+++ b/config.go
@@ -6,12 +6,15 @@ import (
 	"io"
 	"io/fs"
 	"math"
+	"time"
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/engine/compiler"
 	"github.com/tetratelabs/wazero/internal/engine/interpreter"
-	"github.com/tetratelabs/wazero/internal/sys"
+	"github.com/tetratelabs/wazero/internal/platform"
+	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/wasm"
+	"github.com/tetratelabs/wazero/sys"
 )
 
 // RuntimeConfig controls runtime behavior, with the default implementation as NewRuntimeConfig
@@ -337,7 +340,7 @@ func (c *compileConfig) WithMemorySizer(memorySizer api.MemorySizer) CompileConf
 //
 // Ex.
 //	// Initialize base configuration:
-//	config := wazero.NewModuleConfig().WithStdout(buf)
+//	config := wazero.NewModuleConfig().WithStdout(buf).WithSysNanotime()
 //
 //	// Assign different configuration on each instantiation
 //	module, _ := r.InstantiateModule(ctx, compiled, config.WithName("rotate").WithArgs("rotate", "angle=90", "dir=cw"))
@@ -442,6 +445,43 @@ type ModuleConfig interface {
 	// See https://linux.die.net/man/3/stdout
 	WithStdout(io.Writer) ModuleConfig
 
+	// WithWalltime configures the wall clock, sometimes referred to as the
+	// real time clock. Defaults to a constant fake result.
+	//
+	// Ex. To override with your own clock:
+	//	moduleConfig = moduleConfig.
+	//		WithWalltime(func(context.Context) (sec int64, nsec int32) {
+	//			return clock.walltime()
+	//		}, sys.ClockResolution(time.Microsecond.Nanoseconds()))
+	//
+	// Note: This does not default to time.Now as that violates sandboxing. Use
+	// WithSysWalltime for a usable implementation.
+	WithWalltime(sys.Walltime, sys.ClockResolution) ModuleConfig
+
+	// WithSysWalltime uses time.Now for sys.Walltime with a resolution of 1us
+	// (1000ns).
+	//
+	// See WithWalltime
+	WithSysWalltime() ModuleConfig
+
+	// WithNanotime configures the monotonic clock, used to measure elapsed
+	// time in nanoseconds. Defaults to a constant fake result.
+	//
+	// Ex. To override with your own clock:
+	//	moduleConfig = moduleConfig.
+	//		WithNanotime(func(context.Context) int64 {
+	//			return clock.nanotime()
+	//		}, sys.ClockResolution(time.Microsecond.Nanoseconds()))
+	//
+	// Note: This does not default to time.Since as that violates sandboxing.
+	// Use WithSysNanotime for a usable implementation.
+	WithNanotime(sys.Nanotime, sys.ClockResolution) ModuleConfig
+
+	// WithSysNanotime uses time.Now for sys.Nanotime with a resolution of 1us.
+	//
+	// See WithNanotime
+	WithSysNanotime() ModuleConfig
+
 	// WithRandSource configures a source of random bytes. Defaults to crypto/rand.Reader.
 	//
 	// This reader is most commonly used by the functions like "random_get" in "wasi_snapshot_preview1" or "seed" in
@@ -466,19 +506,22 @@ type ModuleConfig interface {
 }
 
 type moduleConfig struct {
-	name           string
-	startFunctions []string
-	stdin          io.Reader
-	stdout         io.Writer
-	stderr         io.Writer
-	randSource     io.Reader
-	args           []string
+	name               string
+	startFunctions     []string
+	stdin              io.Reader
+	stdout             io.Writer
+	stderr             io.Writer
+	randSource         io.Reader
+	walltimeTime       *sys.Walltime
+	walltimeResolution sys.ClockResolution
+	nanotimeTime       *sys.Nanotime
+	nanotimeResolution sys.ClockResolution
+	args               []string
 	// environ is pair-indexed to retain order similar to os.Environ.
 	environ []string
 	// environKeys allow overwriting of existing values.
 	environKeys map[string]int
-
-	fs *sys.FSConfig
+	fs          *internalsys.FSConfig
 }
 
 // NewModuleConfig returns a ModuleConfig that can be used for configuring module instantiation.
@@ -487,7 +530,7 @@ func NewModuleConfig() ModuleConfig {
 		startFunctions: []string{"_start"},
 		environKeys:    map[string]int{},
 
-		fs: sys.NewFSConfig(),
+		fs: internalsys.NewFSConfig(),
 	}
 }
 
@@ -553,6 +596,36 @@ func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig {
 	return &ret
 }
 
+// WithWalltime implements ModuleConfig.WithWalltime
+func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig {
+	ret := *c // copy
+	ret.walltimeTime = &walltime
+	ret.walltimeResolution = resolution
+	return &ret
+}
+
+// We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the
+// source in time.go, windows monotonic resolution can be 15ms. This chooses arbitrarily 1us for wall time and
+// 1ns for monotonic. See RATIONALE.md for more context.
+
+// WithSysWalltime implements ModuleConfig.WithSysWalltime
+func (c *moduleConfig) WithSysWalltime() ModuleConfig {
+	return c.WithWalltime(platform.Walltime, sys.ClockResolution(time.Microsecond.Nanoseconds()))
+}
+
+// WithNanotime implements ModuleConfig.WithNanotime
+func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig {
+	ret := *c // copy
+	ret.nanotimeTime = &nanotime
+	ret.nanotimeResolution = resolution
+	return &ret
+}
+
+// WithSysNanotime implements ModuleConfig.WithSysNanotime
+func (c *moduleConfig) WithSysNanotime() ModuleConfig {
+	return c.WithNanotime(platform.Nanotime, sys.ClockResolution(1))
+}
+
 // WithRandSource implements ModuleConfig.WithRandSource
 func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig {
 	ret := *c // copy
@@ -567,8 +640,8 @@ func (c *moduleConfig) WithWorkDirFS(fs fs.FS) ModuleConfig {
 	return &ret
 }
 
-// toSysContext creates a baseline wasm.SysContext configured by ModuleConfig.
-func (c *moduleConfig) toSysContext() (sys *wasm.SysContext, err error) {
+// toSysContext creates a baseline wasm.Context configured by ModuleConfig.
+func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
 	var environ []string // Intentionally doesn't pre-allocate to reduce logic to default to nil.
 	// Same validation as syscall.Setenv for Linux
 	for i := 0; i < len(c.environ); i += 2 {
@@ -578,7 +651,7 @@ func (c *moduleConfig) toSysContext() (sys *wasm.SysContext, err error) {
 			return
 		}
 		for j := 0; j < len(key); j++ {
-			if key[j] == '=' { // NUL enforced in NewSysContext
+			if key[j] == '=' { // NUL enforced in NewContext
 				err = errors.New("environ invalid: key contains '=' character")
 				return
 			}
@@ -591,5 +664,16 @@ func (c *moduleConfig) toSysContext() (sys *wasm.SysContext, err error) {
 		return nil, err
 	}
 
-	return wasm.NewSysContext(math.MaxUint32, c.args, environ, c.stdin, c.stdout, c.stderr, c.randSource, preopens)
+	return internalsys.NewContext(
+		math.MaxUint32,
+		c.args,
+		environ,
+		c.stdin,
+		c.stdout,
+		c.stderr,
+		c.randSource,
+		c.walltimeTime, c.walltimeResolution,
+		c.nanotimeTime, c.nanotimeResolution,
+		preopens,
+	)
 }
diff --git a/examples/assemblyscript/README.md b/examples/assemblyscript/README.md
index cd2257b6..ed0a07a6 100644
--- a/examples/assemblyscript/README.md
+++ b/examples/assemblyscript/README.md
@@ -1,9 +1,13 @@
 ## AssemblyScript example
 
-This example runs a WebAssembly program compiled using AssemblyScript, built with `npm install && npm run build`.
-The program exports two functions, `hello_world` which executes simple integer math, and `goodbye_world`, which
-throws an error that is logged using the AssemblyScript `abort` built-in function. Wazero is configured to export
-functions used by WebAssembly for reporting errors and trace messages.
+This example runs a WebAssembly program compiled using AssemblyScript, built
+with `npm install && npm run build`.
+
+AssemblyScript program exports two functions, `hello_world` which executes
+simple math, and `goodbye_world`, which throws an error that is logged using
+AssemblyScript `abort` built-in function.
+
+This demo configures AssemblyScript imports for errors and trace messages.
 
 Ex.
 ```bash
diff --git a/examples/assemblyscript/assemblyscript.go b/examples/assemblyscript/assemblyscript.go
index 297eac84..3114fb51 100644
--- a/examples/assemblyscript/assemblyscript.go
+++ b/examples/assemblyscript/assemblyscript.go
@@ -25,8 +25,8 @@ func main() {
 	// Choose the context to use for function calls.
 	ctx := context.Background()
 
-	// Create a new WebAssembly Runtime. AssemblyScript enables certain wasm 2.0 features by default, so
-	// we go ahead and configure the runtime for wasm 2.0 compatibility.
+	// Create a new WebAssembly Runtime.
+	// Use WebAssembly 2.0 because AssemblyScript uses some >1.0 features.
 	r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().
 		WithWasmCore2())
 	defer r.Close(ctx) // This closes everything this Runtime created.
@@ -44,10 +44,12 @@ func main() {
 		log.Panicln(err)
 	}
 
-	// Instantiate a WebAssembly module that imports the "abort" and "trace" functions defined by
-	// assemblyscript.Instantiate and exports functions we'll use in this example. We override the
-	// default module config that discards stdout and stderr.
-	mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().WithStdout(os.Stdout).WithStderr(os.Stderr))
+	// Instantiate a WebAssembly module that imports the "abort" and "trace"
+	// functions defined by assemblyscript.Instantiate and exports functions
+	// we'll use in this example.
+	mod, err := r.InstantiateModule(ctx, code, wazero.NewModuleConfig().
+		// Override the default module config that discards stdout and stderr.
+		WithStdout(os.Stdout).WithStderr(os.Stderr))
 	if err != nil {
 		log.Panicln(err)
 	}
@@ -63,16 +65,16 @@ func main() {
 		log.Panicln(err)
 	}
 
-	// Call hello_world, which returns the input value incremented by 3. It includes a call to trace()
-	// for detailed logging but the above assemblyscript.Instantiate does not enable it by default.
+	// Call hello_world, which returns the input value incremented by 3.
+	// While this calls trace(), our configuration didn't enable it.
 	results, err := helloWorld.Call(ctx, uint64(num))
 	if err != nil {
 		log.Panicln(err)
 	}
 	fmt.Printf("hello_world returned: %v", results[0])
 
-	// Call goodbye_world, which aborts with an error. assemblyscript.Instantiate configures abort
-	// to print to stderr.
+	// Call goodbye_world, which aborts with an error.
+	// assemblyscript.Instantiate was configured above to abort to stderr.
 	results, err = goodbyeWorld.Call(ctx)
 	if err == nil {
 		log.Panicln("goodbye_world did not fail")
diff --git a/examples/wasi/README.md b/examples/wasi/README.md
index 73e2f3d2..44847591 100644
--- a/examples/wasi/README.md
+++ b/examples/wasi/README.md
@@ -1,3 +1,4 @@
 ## WASI example
 
-This example shows how to use I/O in your WebAssembly modules using WASI (WebAssembly System Interface).
+This example shows how to use I/O in your WebAssembly modules using WASI
+(WebAssembly System Interface).
diff --git a/examples/wasi/cat.go b/examples/wasi/cat.go
index 285559af..ae0f17a4 100644
--- a/examples/wasi/cat.go
+++ b/examples/wasi/cat.go
@@ -38,7 +38,7 @@ func main() {
 		log.Panicln(err)
 	}
 
-	// Combine the above into our baseline config, overriding defaults (which discard stdout and have no file system).
+	// Combine the above into our baseline config, overriding defaults (which discards stdout and has no file system).
 	config := wazero.NewModuleConfig().WithStdout(os.Stdout).WithFS(rooted)
 
 	// Instantiate WASI, which implements system I/O such as console output.
diff --git a/experimental/time.go b/experimental/time.go
deleted file mode 100644
index 44c0cca0..00000000
--- a/experimental/time.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package experimental
-
-import (
-	"context"
-
-	"github.com/tetratelabs/wazero/internal/sys"
-)
-
-// WithTimeNowUnixNano allows you to control the value otherwise returned by time.Now().UnixNano()
-func WithTimeNowUnixNano(ctx context.Context, timeUnixNano func() uint64) context.Context {
-	return context.WithValue(ctx, sys.TimeNowUnixNanoKey{}, timeUnixNano)
-}
diff --git a/internal/asm/amd64/consts.go b/internal/asm/amd64/consts.go
index 55444881..ec71cb86 100644
--- a/internal/asm/amd64/consts.go
+++ b/internal/asm/amd64/consts.go
@@ -1,6 +1,10 @@
 package amd64
 
-import "github.com/tetratelabs/wazero/internal/asm"
+import (
+	"fmt"
+
+	"github.com/tetratelabs/wazero/internal/asm"
+)
 
 // AMD64-specific conditional register states.
 //
@@ -81,9 +85,9 @@ const (
 	CVTSL2SD
 	// CVTSL2SS is the CVTSI2SS instruction in 32-bit mode. https://www.felixcloutier.com/x86/cvtsi2ss
 	CVTSL2SS
-	// CVTSL2SD is the CVTSI2SD instruction in 64-bit mode. https://www.felixcloutier.com/x86/cvtsi2sd
+	// CVTSQ2SD is the CVTSI2SD instruction in 64-bit mode. https://www.felixcloutier.com/x86/cvtsi2sd
 	CVTSQ2SD
-	// CVTSL2SS is the CVTSI2SS instruction in 64-bit mode. https://www.felixcloutier.com/x86/cvtsi2ss
+	// CVTSQ2SS is the CVTSI2SS instruction in 64-bit mode. https://www.felixcloutier.com/x86/cvtsi2ss
 	CVTSQ2SS
 	// CVTSS2SD is the CVTSS2SD instruction. https://www.felixcloutier.com/x86/cvtss2sd
 	CVTSS2SD
@@ -409,6 +413,45 @@ const (
 	PUNPCKLBW
 	// PUNPCKHBW is the PUNPCKHBW instruction https://www.felixcloutier.com/x86/punpckhbw:punpckhwd:punpckhdq:punpckhqdq
 	PUNPCKHBW
+	// CMPPS is the CMPPS instruction https://www.felixcloutier.com/x86/cmpps
+	CMPPS
+	// CMPPD is the https://www.felixcloutier.com/x86/cmppd
+	CMPPD
+	// PCMPGTQ is the PCMPGTQ instruction https://www.felixcloutier.com/x86/pcmpgtq
+	PCMPGTQ
+	// PCMPGTD is the PCMPGTD instruction https://www.felixcloutier.com/x86/pcmpgtb:pcmpgtw:pcmpgtd
+	PCMPGTD
+	// PCMPGTW is the PCMPGTW instruction https://www.felixcloutier.com/x86/pcmpgtb:pcmpgtw:pcmpgtd
+	PCMPGTW
+	// PCMPGTB is the PCMPGTB instruction https://www.felixcloutier.com/x86/pcmpgtb:pcmpgtw:pcmpgtd
+	PCMPGTB
+	// PMINSD is the PMINSD instruction https://www.felixcloutier.com/x86/pminsd:pminsq
+	PMINSD
+	// PMINSW is the PMINSW instruction https://www.felixcloutier.com/x86/pminsb:pminsw
+	PMINSW
+	// PMINSB is the PMINSB instruction https://www.felixcloutier.com/x86/pminsb:pminsw
+	PMINSB
+	// PMAXSD is the PMAXSD instruction https://www.felixcloutier.com/x86/pmaxsb:pmaxsw:pmaxsd:pmaxsq
+	PMAXSD
+	// PMAXSW is the PMAXSW instruction https://www.felixcloutier.com/x86/pmaxsb:pmaxsw:pmaxsd:pmaxsq
+	PMAXSW
+	// PMAXSB is the PMAXSB instruction https://www.felixcloutier.com/x86/pmaxsb:pmaxsw:pmaxsd:pmaxsq
+	PMAXSB
+	// PMINUD is the PMINUD instruction https://www.felixcloutier.com/x86/pminud:pminuq
+	PMINUD
+	// PMINUW is the PMINUW instruction https://www.felixcloutier.com/x86/pminub:pminuw
+	PMINUW
+	// PMINUB is the PMINUB instruction https://www.felixcloutier.com/x86/pminub:pminuw
+	PMINUB
+	// PMAXUD is the PMAXUD instruction https://www.felixcloutier.com/x86/pmaxud:pmaxuq
+	PMAXUD
+	// PMAXUW is the PMAXUW instruction https://www.felixcloutier.com/x86/pmaxub:pmaxuw
+	PMAXUW
+	// PMAXUB is the PMAXUB instruction https://www.felixcloutier.com/x86/pmaxub:pmaxuw
+	PMAXUB
+
+	// instructionEnd is always placed at the bottom of this iota definition to be used in the test.
+	instructionEnd
 )
 
 // InstructionName returns the name for an instruction
@@ -784,8 +827,44 @@ func InstructionName(instruction asm.Instruction) string {
 		return "NEGQ"
 	case NONE:
 		return "NONE"
+	case CMPPS:
+		return "CMPPS"
+	case CMPPD:
+		return "CMPPD"
+	case PCMPGTQ:
+		return "PCMPGTQ"
+	case PCMPGTD:
+		return "PCMPGTD"
+	case PMINSD:
+		return "PMINSD"
+	case PMAXSD:
+		return "PMAXSD"
+	case PMINSW:
+		return "PMINSW"
+	case PCMPGTB:
+		return "PCMPGTB"
+	case PMINSB:
+		return "PMINSB"
+	case PMINUD:
+		return "PMINUD"
+	case PMINUW:
+		return "PMINUW"
+	case PMINUB:
+		return "PMINUB"
+	case PMAXUD:
+		return "PMAXUD"
+	case PMAXUW:
+		return "PMAXUW"
+	case PMAXUB:
+		return "PMAXUB"
+	case PCMPGTW:
+		return "PCMPGTW"
+	case PMAXSW:
+		return "PMAXSW"
+	case PMAXSB:
+		return "PMAXSB"
 	}
-	panic(instruction)
+	panic(fmt.Errorf("unknown instruction %d", instruction))
 }
 
 // Amd64-specific registers.
diff --git a/internal/asm/amd64/impl.go b/internal/asm/amd64/impl.go
index cc58f074..c9b213ae 100644
--- a/internal/asm/amd64/impl.go
+++ b/internal/asm/amd64/impl.go
@@ -1294,6 +1294,42 @@ var registerToRegisterOpcode = map[asm.Instruction]struct {
 	PUNPCKLBW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x60}, requireSrcFloat: true, requireDstFloat: true},
 	// https://www.felixcloutier.com/x86/punpckhbw:punpckhwd:punpckhdq:punpckhqdq
 	PUNPCKHBW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x68}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/cmpps
+	CMPPS: {opcode: []byte{0x0f, 0xc2}, requireSrcFloat: true, requireDstFloat: true, needArg: true},
+	// https://www.felixcloutier.com/x86/cmppd
+	CMPPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xc2}, requireSrcFloat: true, requireDstFloat: true, needArg: true},
+	// https://www.felixcloutier.com/x86/pcmpgtq
+	PCMPGTQ: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x37}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pcmpgtb:pcmpgtw:pcmpgtd
+	PCMPGTD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x66}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pcmpgtb:pcmpgtw:pcmpgtd
+	PCMPGTW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x65}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pcmpgtb:pcmpgtw:pcmpgtd
+	PCMPGTB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x64}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pminsd:pminsq
+	PMINSD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x39}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pmaxsb:pmaxsw:pmaxsd:pmaxsq
+	PMAXSD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x3d}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pmaxsb:pmaxsw:pmaxsd:pmaxsq
+	PMAXSW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xee}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pmaxsb:pmaxsw:pmaxsd:pmaxsq
+	PMAXSB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x3c}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pminsb:pminsw
+	PMINSW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xea}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pminsb:pminsw
+	PMINSB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x38}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pminud:pminuq
+	PMINUD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x3b}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pminub:pminuw
+	PMINUW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x3a}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pminub:pminuw
+	PMINUB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xda}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pmaxud:pmaxuq
+	PMAXUD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x3f}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pmaxub:pmaxuw
+	PMAXUW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x38, 0x3e}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/pmaxub:pmaxuw
+	PMAXUB: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xde}, requireSrcFloat: true, requireDstFloat: true},
 }
 
 var RegisterToRegisterShiftOpcode = map[asm.Instruction]struct {
diff --git a/internal/asm/arm64/consts.go b/internal/asm/arm64/consts.go
index 022c1bc6..85e5c1be 100644
--- a/internal/asm/arm64/consts.go
+++ b/internal/asm/arm64/consts.go
@@ -1,6 +1,8 @@
 package arm64
 
 import (
+	"fmt"
+
 	"github.com/tetratelabs/wazero/internal/asm"
 )
 
@@ -725,6 +727,9 @@ const (
 	TBL1
 	// TBL2 is the TBL instruction whose source is two vectors. https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/TBL--Table-vector-Lookup-
 	TBL2
+
+	// instructionEnd is always placed at the bottom of this iota definition to be used in the test.
+	instructionEnd
 )
 
 // VectorArrangement is the arrangement of data within a vector register.
@@ -1082,6 +1087,10 @@ func InstructionName(i asm.Instruction) string {
 		return "CMEQ"
 	case ADDP:
 		return "ADDP"
+	case TBL1:
+		return "TBL1"
+	case TBL2:
+		return "TBL2"
 	}
-	panic("unknown instruction")
+	panic(fmt.Errorf("unknown instruction %d", i))
 }
diff --git a/internal/engine/compiler/compiler.go b/internal/engine/compiler/compiler.go
index 2d58a0da..8e57a6ea 100644
--- a/internal/engine/compiler/compiler.go
+++ b/internal/engine/compiler/compiler.go
@@ -470,8 +470,20 @@ type compiler interface {
 	// See wasm.OpcodeVecI8x16ShrSName wasm.OpcodeVecI8x16ShrUName wasm.OpcodeVecI16x8ShrSName
 	// wasm.OpcodeVecI16x8ShrUName wasm.OpcodeVecI32x4ShrSName wasm.OpcodeVecI32x4ShrUName.
 	// wasm.OpcodeVecI64x2ShrSName wasm.OpcodeVecI64x2ShrUName.
-	compileV128Shr(shr *wazeroir.OperationV128Shr) error
+	compileV128Shr(*wazeroir.OperationV128Shr) error
 	// compileV128Shl adds instructions which are equivalent to wasm.OpcodeVecXXXShl instructions.
 	// See wasm.OpcodeVecI8x16ShlName wasm.OpcodeVecI16x8ShlName wasm.OpcodeVecI32x4ShlName wasm.OpcodeVecI64x2ShlName
-	compileV128Shl(shr *wazeroir.OperationV128Shl) error
+	compileV128Shl(*wazeroir.OperationV128Shl) error
+	// compileV128Cmp adds instructions which are equivalent to various vector comparison instructions.
+	// See wasm.OpcodeVecI8x16EqName, wasm.OpcodeVecI8x16NeName, wasm.OpcodeVecI8x16LtSName, wasm.OpcodeVecI8x16LtUName, wasm.OpcodeVecI8x16GtSName,
+	//	wasm.OpcodeVecI8x16GtUName, wasm.OpcodeVecI8x16LeSName, wasm.OpcodeVecI8x16LeUName, wasm.OpcodeVecI8x16GeSName, wasm.OpcodeVecI8x16GeUName,
+	//	wasm.OpcodeVecI16x8EqName, wasm.OpcodeVecI16x8NeName, wasm.OpcodeVecI16x8LtSName, wasm.OpcodeVecI16x8LtUName, wasm.OpcodeVecI16x8GtSName,
+	//	wasm.OpcodeVecI16x8GtUName, wasm.OpcodeVecI16x8LeSName, wasm.OpcodeVecI16x8LeUName, wasm.OpcodeVecI16x8GeSName, wasm.OpcodeVecI16x8GeUName,
+	//	wasm.OpcodeVecI32x4EqName, wasm.OpcodeVecI32x4NeName, wasm.OpcodeVecI32x4LtSName, wasm.OpcodeVecI32x4LtUName, wasm.OpcodeVecI32x4GtSName,
+	//	wasm.OpcodeVecI32x4GtUName, wasm.OpcodeVecI32x4LeSName, wasm.OpcodeVecI32x4LeUName, wasm.OpcodeVecI32x4GeSName, wasm.OpcodeVecI32x4GeUName,
+	//	wasm.OpcodeVecI64x2EqName, wasm.OpcodeVecI64x2NeName, wasm.OpcodeVecI64x2LtSName, wasm.OpcodeVecI64x2GtSName, wasm.OpcodeVecI64x2LeSName,
+	//	wasm.OpcodeVecI64x2GeSName, wasm.OpcodeVecF32x4EqName, wasm.OpcodeVecF32x4NeName, wasm.OpcodeVecF32x4LtName, wasm.OpcodeVecF32x4GtName,
+	//	wasm.OpcodeVecF32x4LeName, wasm.OpcodeVecF32x4GeName, wasm.OpcodeVecF64x2EqName, wasm.OpcodeVecF64x2NeName, wasm.OpcodeVecF64x2LtName,
+	//	wasm.OpcodeVecF64x2GtName, wasm.OpcodeVecF64x2LeName, wasm.OpcodeVecF64x2GeName
+	compileV128Cmp(*wazeroir.OperationV128Cmp) error
 }
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index aef6037a..fbf7c078 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -1092,6 +1092,8 @@ func compileWasmFunction(_ wasm.Features, ir *wazeroir.CompilationResult) (*code
 			err = compiler.compileV128Shr(o)
 		case *wazeroir.OperationV128Shl:
 			err = compiler.compileV128Shl(o)
+		case *wazeroir.OperationV128Cmp:
+			err = compiler.compileV128Cmp(o)
 		default:
 			err = errors.New("unsupported")
 		}
diff --git a/internal/engine/compiler/impl_vec_amd64.go b/internal/engine/compiler/impl_vec_amd64.go
index 5b6f0bb5..d280faa0 100644
--- a/internal/engine/compiler/impl_vec_amd64.go
+++ b/internal/engine/compiler/impl_vec_amd64.go
@@ -1063,3 +1063,270 @@ func (c *amd64Compiler) compileV128Shl(o *wazeroir.OperationV128Shl) error {
 	c.pushVectorRuntimeValueLocationOnRegister(x1.register)
 	return nil
 }
+
+// compileV128Cmp implements compiler.compileV128Cmp for amd64.
+func (c *amd64Compiler) compileV128Cmp(o *wazeroir.OperationV128Cmp) error {
+	x2 := c.locationStack.popV128()
+	if err := c.compileEnsureOnGeneralPurposeRegister(x2); err != nil {
+		return err
+	}
+
+	x1 := c.locationStack.popV128()
+	if err := c.compileEnsureOnGeneralPurposeRegister(x1); err != nil {
+		return err
+	}
+
+	const (
+		// See https://www.felixcloutier.com/x86/cmppd and https://www.felixcloutier.com/x86/cmpps
+		floatEqualArg           = 0
+		floatLessThanArg        = 1
+		floatLessThanOrEqualArg = 2
+		floatNotEqualARg        = 4
+	)
+
+	x1Reg, x2Reg, result := x1.register, x2.register, asm.NilRegister
+	switch o.Type {
+	case wazeroir.V128CmpTypeF32x4Eq:
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPS, x2Reg, x1Reg, floatEqualArg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeF32x4Ne:
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPS, x2Reg, x1Reg, floatNotEqualARg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeF32x4Lt:
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPS, x2Reg, x1Reg, floatLessThanArg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeF32x4Gt:
+		// Without AVX, there's no float Gt instruction, so we swap the register and use Lt instead.
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPS, x1Reg, x2Reg, floatLessThanArg)
+		result = x2Reg
+	case wazeroir.V128CmpTypeF32x4Le:
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPS, x2Reg, x1Reg, floatLessThanOrEqualArg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeF32x4Ge:
+		// Without AVX, there's no float Ge instruction, so we swap the register and use Le instead.
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPS, x1Reg, x2Reg, floatLessThanOrEqualArg)
+		result = x2Reg
+	case wazeroir.V128CmpTypeF64x2Eq:
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPD, x2Reg, x1Reg, floatEqualArg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeF64x2Ne:
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPD, x2Reg, x1Reg, floatNotEqualARg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeF64x2Lt:
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPD, x2Reg, x1Reg, floatLessThanArg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeF64x2Gt:
+		// Without AVX, there's no float Gt instruction, so we swap the register and use Lt instead.
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPD, x1Reg, x2Reg, floatLessThanArg)
+		result = x2Reg
+	case wazeroir.V128CmpTypeF64x2Le:
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPD, x2Reg, x1Reg, floatLessThanOrEqualArg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeF64x2Ge:
+		// Without AVX, there's no float Ge instruction, so we swap the register and use Le instead.
+		c.assembler.CompileRegisterToRegisterWithArg(amd64.CMPPD, x1Reg, x2Reg, floatLessThanOrEqualArg)
+		result = x2Reg
+	case wazeroir.V128CmpTypeI8x16Eq:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQB, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI8x16Ne:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQB, x2Reg, x1Reg)
+		// Set all bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x2Reg)
+		// Swap the bits on x1Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI8x16LtS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTB, x1Reg, x2Reg)
+		result = x2Reg
+	case wazeroir.V128CmpTypeI8x16LtU, wazeroir.V128CmpTypeI8x16GtU:
+		// Take the unsigned min/max values on each byte on x1 and x2 onto x1Reg.
+		if o.Type == wazeroir.V128CmpTypeI8x16LtU {
+			c.assembler.CompileRegisterToRegister(amd64.PMINUB, x2Reg, x1Reg)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXUB, x2Reg, x1Reg)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQB, x2Reg, x1Reg)
+		// Set all bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x2Reg)
+		// Swap the bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI8x16GtS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTB, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI8x16LeS, wazeroir.V128CmpTypeI8x16LeU:
+		tmp, err := c.allocateRegister(registerTypeVector)
+		if err != nil {
+			return err
+		}
+		// Copy the value on the src to tmp.
+		c.assembler.CompileRegisterToRegister(amd64.MOVDQA, x1Reg, tmp)
+		if o.Type == wazeroir.V128CmpTypeI8x16LeS {
+			c.assembler.CompileRegisterToRegister(amd64.PMINSB, x2Reg, tmp)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMINUB, x2Reg, tmp)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQB, tmp, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI8x16GeS, wazeroir.V128CmpTypeI8x16GeU:
+		tmp, err := c.allocateRegister(registerTypeVector)
+		if err != nil {
+			return err
+		}
+		c.assembler.CompileRegisterToRegister(amd64.MOVDQA, x1Reg, tmp)
+		if o.Type == wazeroir.V128CmpTypeI8x16GeS {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXSB, x2Reg, tmp)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXUB, x2Reg, tmp)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQB, tmp, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI16x8Eq:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQW, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI16x8Ne:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQW, x2Reg, x1Reg)
+		// Set all bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x2Reg)
+		// Swap the bits on x1Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI16x8LtS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTW, x1Reg, x2Reg)
+		result = x2Reg
+	case wazeroir.V128CmpTypeI16x8LtU, wazeroir.V128CmpTypeI16x8GtU:
+		// Take the unsigned min/max values on each byte on x1 and x2 onto x1Reg.
+		if o.Type == wazeroir.V128CmpTypeI16x8LtU {
+			c.assembler.CompileRegisterToRegister(amd64.PMINUW, x2Reg, x1Reg)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXUW, x2Reg, x1Reg)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQW, x2Reg, x1Reg)
+		// Set all bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x2Reg)
+		// Swap the bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI16x8GtS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTW, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI16x8LeS, wazeroir.V128CmpTypeI16x8LeU:
+		tmp, err := c.allocateRegister(registerTypeVector)
+		if err != nil {
+			return err
+		}
+		// Copy the value on the src to tmp.
+		c.assembler.CompileRegisterToRegister(amd64.MOVDQA, x1Reg, tmp)
+		if o.Type == wazeroir.V128CmpTypeI16x8LeS {
+			c.assembler.CompileRegisterToRegister(amd64.PMINSW, x2Reg, tmp)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMINUW, x2Reg, tmp)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQW, tmp, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI16x8GeS, wazeroir.V128CmpTypeI16x8GeU:
+		tmp, err := c.allocateRegister(registerTypeVector)
+		if err != nil {
+			return err
+		}
+		c.assembler.CompileRegisterToRegister(amd64.MOVDQA, x1Reg, tmp)
+		if o.Type == wazeroir.V128CmpTypeI16x8GeS {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXSW, x2Reg, tmp)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXUW, x2Reg, tmp)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQW, tmp, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI32x4Eq:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI32x4Ne:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x1Reg)
+		// Set all bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x2Reg)
+		// Swap the bits on x1Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI32x4LtS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTD, x1Reg, x2Reg)
+		result = x2Reg
+	case wazeroir.V128CmpTypeI32x4LtU, wazeroir.V128CmpTypeI32x4GtU:
+		// Take the unsigned min/max values on each byte on x1 and x2 onto x1Reg.
+		if o.Type == wazeroir.V128CmpTypeI32x4LtU {
+			c.assembler.CompileRegisterToRegister(amd64.PMINUD, x2Reg, x1Reg)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXUD, x2Reg, x1Reg)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x1Reg)
+		// Set all bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x2Reg)
+		// Swap the bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI32x4GtS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTD, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI32x4LeS, wazeroir.V128CmpTypeI32x4LeU:
+		tmp, err := c.allocateRegister(registerTypeVector)
+		if err != nil {
+			return err
+		}
+		// Copy the value on the src to tmp.
+		c.assembler.CompileRegisterToRegister(amd64.MOVDQA, x1Reg, tmp)
+		if o.Type == wazeroir.V128CmpTypeI32x4LeS {
+			c.assembler.CompileRegisterToRegister(amd64.PMINSD, x2Reg, tmp)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMINUD, x2Reg, tmp)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, tmp, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI32x4GeS, wazeroir.V128CmpTypeI32x4GeU:
+		tmp, err := c.allocateRegister(registerTypeVector)
+		if err != nil {
+			return err
+		}
+		c.assembler.CompileRegisterToRegister(amd64.MOVDQA, x1Reg, tmp)
+		if o.Type == wazeroir.V128CmpTypeI32x4GeS {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXSD, x2Reg, tmp)
+		} else {
+			c.assembler.CompileRegisterToRegister(amd64.PMAXUD, x2Reg, tmp)
+		}
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, tmp, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI64x2Eq:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQQ, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI64x2Ne:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQQ, x2Reg, x1Reg)
+		// Set all bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x2Reg)
+		// Swap the bits on x1Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI64x2LtS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTQ, x1Reg, x2Reg)
+		result = x2Reg
+	case wazeroir.V128CmpTypeI64x2GtS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTQ, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI64x2LeS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTQ, x2Reg, x1Reg)
+		// Set all bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x2Reg, x2Reg)
+		// Swap the bits on x1Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x2Reg, x1Reg)
+		result = x1Reg
+	case wazeroir.V128CmpTypeI64x2GeS:
+		c.assembler.CompileRegisterToRegister(amd64.PCMPGTQ, x1Reg, x2Reg)
+		// Set all bits on x1Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PCMPEQD, x1Reg, x1Reg)
+		// Swap the bits on x2Reg register.
+		c.assembler.CompileRegisterToRegister(amd64.PXOR, x1Reg, x2Reg)
+		result = x2Reg
+	}
+
+	c.locationStack.markRegisterUnused(x1Reg, x2Reg)
+	c.pushVectorRuntimeValueLocationOnRegister(result)
+	return nil
+}
diff --git a/internal/engine/compiler/impl_vec_arm64.go b/internal/engine/compiler/impl_vec_arm64.go
index 2825ae85..ee1d368c 100644
--- a/internal/engine/compiler/impl_vec_arm64.go
+++ b/internal/engine/compiler/impl_vec_arm64.go
@@ -697,3 +697,8 @@ func (c *arm64Compiler) compileV128Shr(o *wazeroir.OperationV128Shr) error {
 func (c *arm64Compiler) compileV128Shl(o *wazeroir.OperationV128Shl) error {
 	return fmt.Errorf("TODO: %s is not implemented yet on arm64 compiler", o.Kind())
 }
+
+// compileV128Cmp implements compiler.compileV128Cmp for arm64.
+func (c *arm64Compiler) compileV128Cmp(o *wazeroir.OperationV128Cmp) error {
+	return fmt.Errorf("TODO: %s is not implemented yet on arm64 compiler", o.Kind())
+}
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index 047f560a..75dfd41a 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -643,6 +643,8 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 			op.b3 = o.Signed
 		case *wazeroir.OperationV128Shl:
 			op.b1 = o.Shape
+		case *wazeroir.OperationV128Cmp:
+			op.b1 = o.Type
 		default:
 			panic(fmt.Errorf("BUG: unimplemented operation %s", op.kind.String()))
 		}
@@ -2551,10 +2553,377 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont
 					lo = lo >> s
 					hi = hi >> s
 				}
+
 			}
 			ce.pushValue(lo)
 			ce.pushValue(hi)
 			frame.pc++
+		case wazeroir.OperationKindV128Cmp:
+			x2Hi, x2Lo := ce.popValue(), ce.popValue()
+			x1Hi, x1Lo := ce.popValue(), ce.popValue()
+			var result []bool
+			switch op.b1 {
+			case wazeroir.V128CmpTypeI8x16Eq:
+				result = []bool{
+					byte(x1Lo>>0) == byte(x2Lo>>0), byte(x1Lo>>8) == byte(x2Lo>>8),
+					byte(x1Lo>>16) == byte(x2Lo>>16), byte(x1Lo>>24) == byte(x2Lo>>24),
+					byte(x1Lo>>32) == byte(x2Lo>>32), byte(x1Lo>>40) == byte(x2Lo>>40),
+					byte(x1Lo>>48) == byte(x2Lo>>48), byte(x1Lo>>56) == byte(x2Lo>>56),
+					byte(x1Hi>>0) == byte(x2Hi>>0), byte(x1Hi>>8) == byte(x2Hi>>8),
+					byte(x1Hi>>16) == byte(x2Hi>>16), byte(x1Hi>>24) == byte(x2Hi>>24),
+					byte(x1Hi>>32) == byte(x2Hi>>32), byte(x1Hi>>40) == byte(x2Hi>>40),
+					byte(x1Hi>>48) == byte(x2Hi>>48), byte(x1Hi>>56) == byte(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16Ne:
+				result = []bool{
+					byte(x1Lo>>0) != byte(x2Lo>>0), byte(x1Lo>>8) != byte(x2Lo>>8),
+					byte(x1Lo>>16) != byte(x2Lo>>16), byte(x1Lo>>24) != byte(x2Lo>>24),
+					byte(x1Lo>>32) != byte(x2Lo>>32), byte(x1Lo>>40) != byte(x2Lo>>40),
+					byte(x1Lo>>48) != byte(x2Lo>>48), byte(x1Lo>>56) != byte(x2Lo>>56),
+					byte(x1Hi>>0) != byte(x2Hi>>0), byte(x1Hi>>8) != byte(x2Hi>>8),
+					byte(x1Hi>>16) != byte(x2Hi>>16), byte(x1Hi>>24) != byte(x2Hi>>24),
+					byte(x1Hi>>32) != byte(x2Hi>>32), byte(x1Hi>>40) != byte(x2Hi>>40),
+					byte(x1Hi>>48) != byte(x2Hi>>48), byte(x1Hi>>56) != byte(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16LtS:
+				result = []bool{
+					int8(x1Lo>>0) < int8(x2Lo>>0), int8(x1Lo>>8) < int8(x2Lo>>8),
+					int8(x1Lo>>16) < int8(x2Lo>>16), int8(x1Lo>>24) < int8(x2Lo>>24),
+					int8(x1Lo>>32) < int8(x2Lo>>32), int8(x1Lo>>40) < int8(x2Lo>>40),
+					int8(x1Lo>>48) < int8(x2Lo>>48), int8(x1Lo>>56) < int8(x2Lo>>56),
+					int8(x1Hi>>0) < int8(x2Hi>>0), int8(x1Hi>>8) < int8(x2Hi>>8),
+					int8(x1Hi>>16) < int8(x2Hi>>16), int8(x1Hi>>24) < int8(x2Hi>>24),
+					int8(x1Hi>>32) < int8(x2Hi>>32), int8(x1Hi>>40) < int8(x2Hi>>40),
+					int8(x1Hi>>48) < int8(x2Hi>>48), int8(x1Hi>>56) < int8(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16LtU:
+				result = []bool{
+					byte(x1Lo>>0) < byte(x2Lo>>0), byte(x1Lo>>8) < byte(x2Lo>>8),
+					byte(x1Lo>>16) < byte(x2Lo>>16), byte(x1Lo>>24) < byte(x2Lo>>24),
+					byte(x1Lo>>32) < byte(x2Lo>>32), byte(x1Lo>>40) < byte(x2Lo>>40),
+					byte(x1Lo>>48) < byte(x2Lo>>48), byte(x1Lo>>56) < byte(x2Lo>>56),
+					byte(x1Hi>>0) < byte(x2Hi>>0), byte(x1Hi>>8) < byte(x2Hi>>8),
+					byte(x1Hi>>16) < byte(x2Hi>>16), byte(x1Hi>>24) < byte(x2Hi>>24),
+					byte(x1Hi>>32) < byte(x2Hi>>32), byte(x1Hi>>40) < byte(x2Hi>>40),
+					byte(x1Hi>>48) < byte(x2Hi>>48), byte(x1Hi>>56) < byte(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16GtS:
+				result = []bool{
+					int8(x1Lo>>0) > int8(x2Lo>>0), int8(x1Lo>>8) > int8(x2Lo>>8),
+					int8(x1Lo>>16) > int8(x2Lo>>16), int8(x1Lo>>24) > int8(x2Lo>>24),
+					int8(x1Lo>>32) > int8(x2Lo>>32), int8(x1Lo>>40) > int8(x2Lo>>40),
+					int8(x1Lo>>48) > int8(x2Lo>>48), int8(x1Lo>>56) > int8(x2Lo>>56),
+					int8(x1Hi>>0) > int8(x2Hi>>0), int8(x1Hi>>8) > int8(x2Hi>>8),
+					int8(x1Hi>>16) > int8(x2Hi>>16), int8(x1Hi>>24) > int8(x2Hi>>24),
+					int8(x1Hi>>32) > int8(x2Hi>>32), int8(x1Hi>>40) > int8(x2Hi>>40),
+					int8(x1Hi>>48) > int8(x2Hi>>48), int8(x1Hi>>56) > int8(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16GtU:
+				result = []bool{
+					byte(x1Lo>>0) > byte(x2Lo>>0), byte(x1Lo>>8) > byte(x2Lo>>8),
+					byte(x1Lo>>16) > byte(x2Lo>>16), byte(x1Lo>>24) > byte(x2Lo>>24),
+					byte(x1Lo>>32) > byte(x2Lo>>32), byte(x1Lo>>40) > byte(x2Lo>>40),
+					byte(x1Lo>>48) > byte(x2Lo>>48), byte(x1Lo>>56) > byte(x2Lo>>56),
+					byte(x1Hi>>0) > byte(x2Hi>>0), byte(x1Hi>>8) > byte(x2Hi>>8),
+					byte(x1Hi>>16) > byte(x2Hi>>16), byte(x1Hi>>24) > byte(x2Hi>>24),
+					byte(x1Hi>>32) > byte(x2Hi>>32), byte(x1Hi>>40) > byte(x2Hi>>40),
+					byte(x1Hi>>48) > byte(x2Hi>>48), byte(x1Hi>>56) > byte(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16LeS:
+				result = []bool{
+					int8(x1Lo>>0) <= int8(x2Lo>>0), int8(x1Lo>>8) <= int8(x2Lo>>8),
+					int8(x1Lo>>16) <= int8(x2Lo>>16), int8(x1Lo>>24) <= int8(x2Lo>>24),
+					int8(x1Lo>>32) <= int8(x2Lo>>32), int8(x1Lo>>40) <= int8(x2Lo>>40),
+					int8(x1Lo>>48) <= int8(x2Lo>>48), int8(x1Lo>>56) <= int8(x2Lo>>56),
+					int8(x1Hi>>0) <= int8(x2Hi>>0), int8(x1Hi>>8) <= int8(x2Hi>>8),
+					int8(x1Hi>>16) <= int8(x2Hi>>16), int8(x1Hi>>24) <= int8(x2Hi>>24),
+					int8(x1Hi>>32) <= int8(x2Hi>>32), int8(x1Hi>>40) <= int8(x2Hi>>40),
+					int8(x1Hi>>48) <= int8(x2Hi>>48), int8(x1Hi>>56) <= int8(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16LeU:
+				result = []bool{
+					byte(x1Lo>>0) <= byte(x2Lo>>0), byte(x1Lo>>8) <= byte(x2Lo>>8),
+					byte(x1Lo>>16) <= byte(x2Lo>>16), byte(x1Lo>>24) <= byte(x2Lo>>24),
+					byte(x1Lo>>32) <= byte(x2Lo>>32), byte(x1Lo>>40) <= byte(x2Lo>>40),
+					byte(x1Lo>>48) <= byte(x2Lo>>48), byte(x1Lo>>56) <= byte(x2Lo>>56),
+					byte(x1Hi>>0) <= byte(x2Hi>>0), byte(x1Hi>>8) <= byte(x2Hi>>8),
+					byte(x1Hi>>16) <= byte(x2Hi>>16), byte(x1Hi>>24) <= byte(x2Hi>>24),
+					byte(x1Hi>>32) <= byte(x2Hi>>32), byte(x1Hi>>40) <= byte(x2Hi>>40),
+					byte(x1Hi>>48) <= byte(x2Hi>>48), byte(x1Hi>>56) <= byte(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16GeS:
+				result = []bool{
+					int8(x1Lo>>0) >= int8(x2Lo>>0), int8(x1Lo>>8) >= int8(x2Lo>>8),
+					int8(x1Lo>>16) >= int8(x2Lo>>16), int8(x1Lo>>24) >= int8(x2Lo>>24),
+					int8(x1Lo>>32) >= int8(x2Lo>>32), int8(x1Lo>>40) >= int8(x2Lo>>40),
+					int8(x1Lo>>48) >= int8(x2Lo>>48), int8(x1Lo>>56) >= int8(x2Lo>>56),
+					int8(x1Hi>>0) >= int8(x2Hi>>0), int8(x1Hi>>8) >= int8(x2Hi>>8),
+					int8(x1Hi>>16) >= int8(x2Hi>>16), int8(x1Hi>>24) >= int8(x2Hi>>24),
+					int8(x1Hi>>32) >= int8(x2Hi>>32), int8(x1Hi>>40) >= int8(x2Hi>>40),
+					int8(x1Hi>>48) >= int8(x2Hi>>48), int8(x1Hi>>56) >= int8(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI8x16GeU:
+				result = []bool{
+					byte(x1Lo>>0) >= byte(x2Lo>>0), byte(x1Lo>>8) >= byte(x2Lo>>8),
+					byte(x1Lo>>16) >= byte(x2Lo>>16), byte(x1Lo>>24) >= byte(x2Lo>>24),
+					byte(x1Lo>>32) >= byte(x2Lo>>32), byte(x1Lo>>40) >= byte(x2Lo>>40),
+					byte(x1Lo>>48) >= byte(x2Lo>>48), byte(x1Lo>>56) >= byte(x2Lo>>56),
+					byte(x1Hi>>0) >= byte(x2Hi>>0), byte(x1Hi>>8) >= byte(x2Hi>>8),
+					byte(x1Hi>>16) >= byte(x2Hi>>16), byte(x1Hi>>24) >= byte(x2Hi>>24),
+					byte(x1Hi>>32) >= byte(x2Hi>>32), byte(x1Hi>>40) >= byte(x2Hi>>40),
+					byte(x1Hi>>48) >= byte(x2Hi>>48), byte(x1Hi>>56) >= byte(x2Hi>>56),
+				}
+			case wazeroir.V128CmpTypeI16x8Eq:
+				result = []bool{
+					uint16(x1Lo>>0) == uint16(x2Lo>>0), uint16(x1Lo>>16) == uint16(x2Lo>>16),
+					uint16(x1Lo>>32) == uint16(x2Lo>>32), uint16(x1Lo>>48) == uint16(x2Lo>>48),
+					uint16(x1Hi>>0) == uint16(x2Hi>>0), uint16(x1Hi>>16) == uint16(x2Hi>>16),
+					uint16(x1Hi>>32) == uint16(x2Hi>>32), uint16(x1Hi>>48) == uint16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8Ne:
+				result = []bool{
+					uint16(x1Lo>>0) != uint16(x2Lo>>0), uint16(x1Lo>>16) != uint16(x2Lo>>16),
+					uint16(x1Lo>>32) != uint16(x2Lo>>32), uint16(x1Lo>>48) != uint16(x2Lo>>48),
+					uint16(x1Hi>>0) != uint16(x2Hi>>0), uint16(x1Hi>>16) != uint16(x2Hi>>16),
+					uint16(x1Hi>>32) != uint16(x2Hi>>32), uint16(x1Hi>>48) != uint16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8LtS:
+				result = []bool{
+					int16(x1Lo>>0) < int16(x2Lo>>0), int16(x1Lo>>16) < int16(x2Lo>>16),
+					int16(x1Lo>>32) < int16(x2Lo>>32), int16(x1Lo>>48) < int16(x2Lo>>48),
+					int16(x1Hi>>0) < int16(x2Hi>>0), int16(x1Hi>>16) < int16(x2Hi>>16),
+					int16(x1Hi>>32) < int16(x2Hi>>32), int16(x1Hi>>48) < int16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8LtU:
+				result = []bool{
+					uint16(x1Lo>>0) < uint16(x2Lo>>0), uint16(x1Lo>>16) < uint16(x2Lo>>16),
+					uint16(x1Lo>>32) < uint16(x2Lo>>32), uint16(x1Lo>>48) < uint16(x2Lo>>48),
+					uint16(x1Hi>>0) < uint16(x2Hi>>0), uint16(x1Hi>>16) < uint16(x2Hi>>16),
+					uint16(x1Hi>>32) < uint16(x2Hi>>32), uint16(x1Hi>>48) < uint16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8GtS:
+				result = []bool{
+					int16(x1Lo>>0) > int16(x2Lo>>0), int16(x1Lo>>16) > int16(x2Lo>>16),
+					int16(x1Lo>>32) > int16(x2Lo>>32), int16(x1Lo>>48) > int16(x2Lo>>48),
+					int16(x1Hi>>0) > int16(x2Hi>>0), int16(x1Hi>>16) > int16(x2Hi>>16),
+					int16(x1Hi>>32) > int16(x2Hi>>32), int16(x1Hi>>48) > int16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8GtU:
+				result = []bool{
+					uint16(x1Lo>>0) > uint16(x2Lo>>0), uint16(x1Lo>>16) > uint16(x2Lo>>16),
+					uint16(x1Lo>>32) > uint16(x2Lo>>32), uint16(x1Lo>>48) > uint16(x2Lo>>48),
+					uint16(x1Hi>>0) > uint16(x2Hi>>0), uint16(x1Hi>>16) > uint16(x2Hi>>16),
+					uint16(x1Hi>>32) > uint16(x2Hi>>32), uint16(x1Hi>>48) > uint16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8LeS:
+				result = []bool{
+					int16(x1Lo>>0) <= int16(x2Lo>>0), int16(x1Lo>>16) <= int16(x2Lo>>16),
+					int16(x1Lo>>32) <= int16(x2Lo>>32), int16(x1Lo>>48) <= int16(x2Lo>>48),
+					int16(x1Hi>>0) <= int16(x2Hi>>0), int16(x1Hi>>16) <= int16(x2Hi>>16),
+					int16(x1Hi>>32) <= int16(x2Hi>>32), int16(x1Hi>>48) <= int16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8LeU:
+				result = []bool{
+					uint16(x1Lo>>0) <= uint16(x2Lo>>0), uint16(x1Lo>>16) <= uint16(x2Lo>>16),
+					uint16(x1Lo>>32) <= uint16(x2Lo>>32), uint16(x1Lo>>48) <= uint16(x2Lo>>48),
+					uint16(x1Hi>>0) <= uint16(x2Hi>>0), uint16(x1Hi>>16) <= uint16(x2Hi>>16),
+					uint16(x1Hi>>32) <= uint16(x2Hi>>32), uint16(x1Hi>>48) <= uint16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8GeS:
+				result = []bool{
+					int16(x1Lo>>0) >= int16(x2Lo>>0), int16(x1Lo>>16) >= int16(x2Lo>>16),
+					int16(x1Lo>>32) >= int16(x2Lo>>32), int16(x1Lo>>48) >= int16(x2Lo>>48),
+					int16(x1Hi>>0) >= int16(x2Hi>>0), int16(x1Hi>>16) >= int16(x2Hi>>16),
+					int16(x1Hi>>32) >= int16(x2Hi>>32), int16(x1Hi>>48) >= int16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI16x8GeU:
+				result = []bool{
+					uint16(x1Lo>>0) >= uint16(x2Lo>>0), uint16(x1Lo>>16) >= uint16(x2Lo>>16),
+					uint16(x1Lo>>32) >= uint16(x2Lo>>32), uint16(x1Lo>>48) >= uint16(x2Lo>>48),
+					uint16(x1Hi>>0) >= uint16(x2Hi>>0), uint16(x1Hi>>16) >= uint16(x2Hi>>16),
+					uint16(x1Hi>>32) >= uint16(x2Hi>>32), uint16(x1Hi>>48) >= uint16(x2Hi>>48),
+				}
+			case wazeroir.V128CmpTypeI32x4Eq:
+				result = []bool{
+					uint32(x1Lo>>0) == uint32(x2Lo>>0), uint32(x1Lo>>32) == uint32(x2Lo>>32),
+					uint32(x1Hi>>0) == uint32(x2Hi>>0), uint32(x1Hi>>32) == uint32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4Ne:
+				result = []bool{
+					uint32(x1Lo>>0) != uint32(x2Lo>>0), uint32(x1Lo>>32) != uint32(x2Lo>>32),
+					uint32(x1Hi>>0) != uint32(x2Hi>>0), uint32(x1Hi>>32) != uint32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4LtS:
+				result = []bool{
+					int32(x1Lo>>0) < int32(x2Lo>>0), int32(x1Lo>>32) < int32(x2Lo>>32),
+					int32(x1Hi>>0) < int32(x2Hi>>0), int32(x1Hi>>32) < int32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4LtU:
+				result = []bool{
+					uint32(x1Lo>>0) < uint32(x2Lo>>0), uint32(x1Lo>>32) < uint32(x2Lo>>32),
+					uint32(x1Hi>>0) < uint32(x2Hi>>0), uint32(x1Hi>>32) < uint32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4GtS:
+				result = []bool{
+					int32(x1Lo>>0) > int32(x2Lo>>0), int32(x1Lo>>32) > int32(x2Lo>>32),
+					int32(x1Hi>>0) > int32(x2Hi>>0), int32(x1Hi>>32) > int32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4GtU:
+				result = []bool{
+					uint32(x1Lo>>0) > uint32(x2Lo>>0), uint32(x1Lo>>32) > uint32(x2Lo>>32),
+					uint32(x1Hi>>0) > uint32(x2Hi>>0), uint32(x1Hi>>32) > uint32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4LeS:
+				result = []bool{
+					int32(x1Lo>>0) <= int32(x2Lo>>0), int32(x1Lo>>32) <= int32(x2Lo>>32),
+					int32(x1Hi>>0) <= int32(x2Hi>>0), int32(x1Hi>>32) <= int32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4LeU:
+				result = []bool{
+					uint32(x1Lo>>0) <= uint32(x2Lo>>0), uint32(x1Lo>>32) <= uint32(x2Lo>>32),
+					uint32(x1Hi>>0) <= uint32(x2Hi>>0), uint32(x1Hi>>32) <= uint32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4GeS:
+				result = []bool{
+					int32(x1Lo>>0) >= int32(x2Lo>>0), int32(x1Lo>>32) >= int32(x2Lo>>32),
+					int32(x1Hi>>0) >= int32(x2Hi>>0), int32(x1Hi>>32) >= int32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI32x4GeU:
+				result = []bool{
+					uint32(x1Lo>>0) >= uint32(x2Lo>>0), uint32(x1Lo>>32) >= uint32(x2Lo>>32),
+					uint32(x1Hi>>0) >= uint32(x2Hi>>0), uint32(x1Hi>>32) >= uint32(x2Hi>>32),
+				}
+			case wazeroir.V128CmpTypeI64x2Eq:
+				result = []bool{x1Lo == x2Lo, x1Hi == x2Hi}
+			case wazeroir.V128CmpTypeI64x2Ne:
+				result = []bool{x1Lo != x2Lo, x1Hi != x2Hi}
+			case wazeroir.V128CmpTypeI64x2LtS:
+				result = []bool{int64(x1Lo) < int64(x2Lo), int64(x1Hi) < int64(x2Hi)}
+			case wazeroir.V128CmpTypeI64x2GtS:
+				result = []bool{int64(x1Lo) > int64(x2Lo), int64(x1Hi) > int64(x2Hi)}
+			case wazeroir.V128CmpTypeI64x2LeS:
+				result = []bool{int64(x1Lo) <= int64(x2Lo), int64(x1Hi) <= int64(x2Hi)}
+			case wazeroir.V128CmpTypeI64x2GeS:
+				result = []bool{int64(x1Lo) >= int64(x2Lo), int64(x1Hi) >= int64(x2Hi)}
+			case wazeroir.V128CmpTypeF32x4Eq:
+				result = []bool{
+					math.Float32frombits(uint32(x1Lo>>0)) == math.Float32frombits(uint32(x2Lo>>0)),
+					math.Float32frombits(uint32(x1Lo>>32)) == math.Float32frombits(uint32(x2Lo>>32)),
+					math.Float32frombits(uint32(x1Hi>>0)) == math.Float32frombits(uint32(x2Hi>>0)),
+					math.Float32frombits(uint32(x1Hi>>32)) == math.Float32frombits(uint32(x2Hi>>32)),
+				}
+			case wazeroir.V128CmpTypeF32x4Ne:
+				result = []bool{
+					math.Float32frombits(uint32(x1Lo>>0)) != math.Float32frombits(uint32(x2Lo>>0)),
+					math.Float32frombits(uint32(x1Lo>>32)) != math.Float32frombits(uint32(x2Lo>>32)),
+					math.Float32frombits(uint32(x1Hi>>0)) != math.Float32frombits(uint32(x2Hi>>0)),
+					math.Float32frombits(uint32(x1Hi>>32)) != math.Float32frombits(uint32(x2Hi>>32)),
+				}
+			case wazeroir.V128CmpTypeF32x4Lt:
+				result = []bool{
+					math.Float32frombits(uint32(x1Lo>>0)) < math.Float32frombits(uint32(x2Lo>>0)),
+					math.Float32frombits(uint32(x1Lo>>32)) < math.Float32frombits(uint32(x2Lo>>32)),
+					math.Float32frombits(uint32(x1Hi>>0)) < math.Float32frombits(uint32(x2Hi>>0)),
+					math.Float32frombits(uint32(x1Hi>>32)) < math.Float32frombits(uint32(x2Hi>>32)),
+				}
+			case wazeroir.V128CmpTypeF32x4Gt:
+				result = []bool{
+					math.Float32frombits(uint32(x1Lo>>0)) > math.Float32frombits(uint32(x2Lo>>0)),
+					math.Float32frombits(uint32(x1Lo>>32)) > math.Float32frombits(uint32(x2Lo>>32)),
+					math.Float32frombits(uint32(x1Hi>>0)) > math.Float32frombits(uint32(x2Hi>>0)),
+					math.Float32frombits(uint32(x1Hi>>32)) > math.Float32frombits(uint32(x2Hi>>32)),
+				}
+			case wazeroir.V128CmpTypeF32x4Le:
+				result = []bool{
+					math.Float32frombits(uint32(x1Lo>>0)) <= math.Float32frombits(uint32(x2Lo>>0)),
+					math.Float32frombits(uint32(x1Lo>>32)) <= math.Float32frombits(uint32(x2Lo>>32)),
+					math.Float32frombits(uint32(x1Hi>>0)) <= math.Float32frombits(uint32(x2Hi>>0)),
+					math.Float32frombits(uint32(x1Hi>>32)) <= math.Float32frombits(uint32(x2Hi>>32)),
+				}
+			case wazeroir.V128CmpTypeF32x4Ge:
+				result = []bool{
+					math.Float32frombits(uint32(x1Lo>>0)) >= math.Float32frombits(uint32(x2Lo>>0)),
+					math.Float32frombits(uint32(x1Lo>>32)) >= math.Float32frombits(uint32(x2Lo>>32)),
+					math.Float32frombits(uint32(x1Hi>>0)) >= math.Float32frombits(uint32(x2Hi>>0)),
+					math.Float32frombits(uint32(x1Hi>>32)) >= math.Float32frombits(uint32(x2Hi>>32)),
+				}
+			case wazeroir.V128CmpTypeF64x2Eq:
+				result = []bool{
+					math.Float64frombits(x1Lo) == math.Float64frombits(x2Lo),
+					math.Float64frombits(x1Hi) == math.Float64frombits(x2Hi),
+				}
+			case wazeroir.V128CmpTypeF64x2Ne:
+				result = []bool{
+					math.Float64frombits(x1Lo) != math.Float64frombits(x2Lo),
+					math.Float64frombits(x1Hi) != math.Float64frombits(x2Hi),
+				}
+			case wazeroir.V128CmpTypeF64x2Lt:
+				result = []bool{
+					math.Float64frombits(x1Lo) < math.Float64frombits(x2Lo),
+					math.Float64frombits(x1Hi) < math.Float64frombits(x2Hi),
+				}
+			case wazeroir.V128CmpTypeF64x2Gt:
+				result = []bool{
+					math.Float64frombits(x1Lo) > math.Float64frombits(x2Lo),
+					math.Float64frombits(x1Hi) > math.Float64frombits(x2Hi),
+				}
+			case wazeroir.V128CmpTypeF64x2Le:
+				result = []bool{
+					math.Float64frombits(x1Lo) <= math.Float64frombits(x2Lo),
+					math.Float64frombits(x1Hi) <= math.Float64frombits(x2Hi),
+				}
+			case wazeroir.V128CmpTypeF64x2Ge:
+				result = []bool{
+					math.Float64frombits(x1Lo) >= math.Float64frombits(x2Lo),
+					math.Float64frombits(x1Hi) >= math.Float64frombits(x2Hi),
+				}
+			}
+
+			var retLo, retHi uint64
+			laneNum := len(result)
+			switch laneNum {
+			case 16:
+				for i, b := range result {
+					if b {
+						if i < 8 {
+							retLo |= 0xff << (i * 8)
+						} else {
+							retHi |= 0xff << ((i - 8) * 8)
+						}
+					}
+				}
+			case 8:
+				for i, b := range result {
+					if b {
+						if i < 4 {
+							retLo |= 0xffff << (i * 16)
+						} else {
+							retHi |= 0xffff << ((i - 4) * 16)
+						}
+					}
+				}
+			case 4:
+				for i, b := range result {
+					if b {
+						if i < 2 {
+							retLo |= 0xffff_ffff << (i * 32)
+						} else {
+							retHi |= 0xffff_ffff << ((i - 2) * 32)
+						}
+					}
+				}
+			case 2:
+				if result[0] {
+					retLo = ^uint64(0)
+				}
+				if result[1] {
+					retHi = ^uint64(0)
+				}
+			}
+
+			ce.pushValue(retLo)
+			ce.pushValue(retHi)
+			frame.pc++
 		}
 	}
 	ce.popFrame()
diff --git a/internal/platform/platform.go b/internal/platform/platform.go
index fda01a19..621f403e 100644
--- a/internal/platform/platform.go
+++ b/internal/platform/platform.go
@@ -9,8 +9,8 @@ import (
 	"runtime"
 )
 
-// IsSupported is exported for tests and includes constraints here and also the assembler.
-func IsSupported() bool {
+// CompilerSupported is exported for tests and includes constraints here and also the assembler.
+func CompilerSupported() bool {
 	switch runtime.GOOS {
 	case "darwin", "windows", "linux":
 	default:
diff --git a/internal/platform/time.go b/internal/platform/time.go
new file mode 100644
index 00000000..64203b73
--- /dev/null
+++ b/internal/platform/time.go
@@ -0,0 +1,48 @@
+package platform
+
+import (
+	"context"
+	"time"
+)
+
+const FakeEpochNanos = int64(1640995200000000000) // midnight UTC 2022-01-01
+
+// FakeWalltime implements sys.Walltime with FakeEpochNanos.
+func FakeWalltime(context.Context) (sec int64, nsec int32) {
+	return FakeEpochNanos / 1e9, int32(FakeEpochNanos % 1e9)
+}
+
+// FakeNanotime implements sys.Nanotime with FakeEpochNanos.
+func FakeNanotime(context.Context) int64 {
+	return FakeEpochNanos
+}
+
+// Walltime implements sys.Walltime with time.Now.
+//
+// Note: This is only notably less efficient than it could be is reading
+// runtime.walltime(). time.Now defensively reads nanotime also, just in case
+// time.Since is used. This doubles the performance impact. However, wall time
+// is likely to be read less frequently than Nanotime. Also, doubling the cost
+// matters less on fast platforms that can return both in <=100ns.
+func Walltime(context.Context) (sec int64, nsec int32) {
+	t := time.Now()
+	return t.Unix(), int32(t.Nanosecond())
+}
+
+// nanoBase uses time.Now to ensure a monotonic clock reading on all platforms
+// via time.Since.
+var nanoBase = time.Now()
+
+// nanotimePortable implements sys.Nanotime with time.Since.
+//
+// Note: This is less efficient than it could be is reading runtime.nanotime(),
+// Just to do that requires CGO.
+func nanotimePortable() int64 {
+	return time.Since(nanoBase).Nanoseconds()
+}
+
+// Nanotime implements sys.Nanotime with runtime.nanotime() if CGO is available
+// and time.Since if not.
+func Nanotime(context.Context) int64 {
+	return nanotime()
+}
diff --git a/internal/platform/time_cgo.go b/internal/platform/time_cgo.go
new file mode 100644
index 00000000..6857f490
--- /dev/null
+++ b/internal/platform/time_cgo.go
@@ -0,0 +1,11 @@
+//go:build cgo
+
+package platform
+
+import _ "unsafe" // for go:linkname
+
+// nanotime uses runtime.nanotime as it is available on all platforms and
+// benchmarks faster than using time.Since.
+//go:noescape
+//go:linkname nanotime runtime.nanotime
+func nanotime() int64
diff --git a/internal/platform/time_notcgo.go b/internal/platform/time_notcgo.go
new file mode 100644
index 00000000..8dccceb9
--- /dev/null
+++ b/internal/platform/time_notcgo.go
@@ -0,0 +1,7 @@
+//go:build !cgo
+
+package platform
+
+func nanotime() int64 {
+	return nanotimePortable()
+}
diff --git a/internal/wasm/sys.go b/internal/sys/sys.go
similarity index 53%
rename from internal/wasm/sys.go
rename to internal/sys/sys.go
index f8c9256f..f1143ef5 100644
--- a/internal/wasm/sys.go
+++ b/internal/sys/sys.go
@@ -1,30 +1,41 @@
-package wasm
+package sys
 
 import (
+	"context"
 	"crypto/rand"
 	"errors"
 	"fmt"
 	"io"
+	"time"
 
-	"github.com/tetratelabs/wazero/internal/sys"
+	"github.com/tetratelabs/wazero/internal/platform"
+	"github.com/tetratelabs/wazero/sys"
 )
 
-// SysContext holds module-scoped system resources currently only used by internalwasi.
-type SysContext struct {
+// Context holds module-scoped system resources currently only supported by
+// built-in host functions.
+type Context struct {
 	args, environ         []string
 	argsSize, environSize uint32
 	stdin                 io.Reader
 	stdout, stderr        io.Writer
-	randSource            io.Reader
 
-	fs *sys.FSContext
+	// Note: Using function pointers here keeps them stable for tests.
+
+	walltime           *sys.Walltime
+	walltimeResolution sys.ClockResolution
+	nanotime           *sys.Nanotime
+	nanotimeResolution sys.ClockResolution
+	randSource         io.Reader
+
+	fs *FSContext
 }
 
 // Args is like os.Args and defaults to nil.
 //
 // Note: The count will never be more than math.MaxUint32.
 // See wazero.ModuleConfig WithArgs
-func (c *SysContext) Args() []string {
+func (c *Context) Args() []string {
 	return c.args
 }
 
@@ -33,7 +44,7 @@ func (c *SysContext) Args() []string {
 // Note: To get the size without null-terminators, subtract the length of Args from this value.
 // See wazero.ModuleConfig WithArgs
 // See https://en.wikipedia.org/wiki/Null-terminated_string
-func (c *SysContext) ArgsSize() uint32 {
+func (c *Context) ArgsSize() uint32 {
 	return c.argsSize
 }
 
@@ -41,7 +52,7 @@ func (c *SysContext) ArgsSize() uint32 {
 //
 // Note: The count will never be more than math.MaxUint32.
 // See wazero.ModuleConfig WithEnv
-func (c *SysContext) Environ() []string {
+func (c *Context) Environ() []string {
 	return c.environ
 }
 
@@ -50,36 +61,56 @@ func (c *SysContext) Environ() []string {
 // Note: To get the size without null-terminators, subtract the length of Environ from this value.
 // See wazero.ModuleConfig WithEnv
 // See https://en.wikipedia.org/wiki/Null-terminated_string
-func (c *SysContext) EnvironSize() uint32 {
+func (c *Context) EnvironSize() uint32 {
 	return c.environSize
 }
 
 // Stdin is like exec.Cmd Stdin and defaults to a reader of os.DevNull.
 // See wazero.ModuleConfig WithStdin
-func (c *SysContext) Stdin() io.Reader {
+func (c *Context) Stdin() io.Reader {
 	return c.stdin
 }
 
 // Stdout is like exec.Cmd Stdout and defaults to io.Discard.
 // See wazero.ModuleConfig WithStdout
-func (c *SysContext) Stdout() io.Writer {
+func (c *Context) Stdout() io.Writer {
 	return c.stdout
 }
 
 // Stderr is like exec.Cmd Stderr and defaults to io.Discard.
 // See wazero.ModuleConfig WithStderr
-func (c *SysContext) Stderr() io.Writer {
+func (c *Context) Stderr() io.Writer {
 	return c.stderr
 }
 
+// Walltime implements sys.Walltime.
+func (c *Context) Walltime(ctx context.Context) (sec int64, nsec int32) {
+	return (*(c.walltime))(ctx)
+}
+
+// WalltimeResolution returns resolution of Walltime.
+func (c *Context) WalltimeResolution() sys.ClockResolution {
+	return c.walltimeResolution
+}
+
+// Nanotime implements sys.Nanotime.
+func (c *Context) Nanotime(ctx context.Context) int64 {
+	return (*(c.nanotime))(ctx)
+}
+
+// NanotimeResolution returns resolution of Nanotime.
+func (c *Context) NanotimeResolution() sys.ClockResolution {
+	return c.nanotimeResolution
+}
+
 // FS returns the file system context.
-func (c *SysContext) FS() *sys.FSContext {
+func (c *Context) FS() *FSContext {
 	return c.fs
 }
 
 // RandSource is a source of random bytes and defaults to crypto/rand.Reader.
 // see wazero.ModuleConfig WithRandSource
-func (c *SysContext) RandSource() io.Reader {
+func (c *Context) RandSource() io.Reader {
 	return c.randSource
 }
 
@@ -92,24 +123,35 @@ func (eofReader) Read([]byte) (int, error) {
 	return 0, io.EOF
 }
 
-// DefaultSysContext returns SysContext with no values set.
+// DefaultContext returns Context with no values set.
 //
-// Note: This isn't a constant because SysContext.openedFiles is currently mutable even when empty.
+// Note: This isn't a constant because Context.openedFiles is currently mutable even when empty.
 // TODO: Make it an error to open or close files when no FS was assigned.
-func DefaultSysContext() *SysContext {
-	if sysCtx, err := NewSysContext(0, nil, nil, nil, nil, nil, nil, nil); err != nil {
-		panic(fmt.Errorf("BUG: DefaultSysContext should never error: %w", err))
+func DefaultContext() *Context {
+	if sysCtx, err := NewContext(0, nil, nil, nil, nil, nil, nil, nil, 0, nil, 0, nil); err != nil {
+		panic(fmt.Errorf("BUG: DefaultContext should never error: %w", err))
 	} else {
 		return sysCtx
 	}
 }
 
-var _ = DefaultSysContext() // Force panic on bug.
+var _ = DefaultContext() // Force panic on bug.
+var wt sys.Walltime = platform.FakeWalltime
+var nt sys.Nanotime = platform.FakeNanotime
 
-// NewSysContext is a factory function which helps avoid needing to know defaults or exporting all fields.
+// NewContext is a factory function which helps avoid needing to know defaults or exporting all fields.
 // Note: max is exposed for testing. max is only used for env/args validation.
-func NewSysContext(max uint32, args, environ []string, stdin io.Reader, stdout, stderr io.Writer, randSource io.Reader, openedFiles map[uint32]*sys.FileEntry) (sysCtx *SysContext, err error) {
-	sysCtx = &SysContext{args: args, environ: environ}
+func NewContext(
+	max uint32,
+	args, environ []string,
+	stdin io.Reader,
+	stdout, stderr io.Writer,
+	randSource io.Reader,
+	walltime *sys.Walltime, walltimeResolution sys.ClockResolution,
+	nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution,
+	openedFiles map[uint32]*FileEntry,
+) (sysCtx *Context, err error) {
+	sysCtx = &Context{args: args, environ: environ}
 
 	if sysCtx.argsSize, err = nullTerminatedByteCount(max, args); err != nil {
 		return nil, fmt.Errorf("args invalid: %w", err)
@@ -143,11 +185,38 @@ func NewSysContext(max uint32, args, environ []string, stdin io.Reader, stdout,
 		sysCtx.randSource = randSource
 	}
 
-	sysCtx.fs = sys.NewFSContext(openedFiles)
+	if walltime != nil {
+		if clockResolutionInvalid(walltimeResolution) {
+			return nil, fmt.Errorf("invalid Walltime resolution: %d", walltimeResolution)
+		}
+		sysCtx.walltime = walltime
+		sysCtx.walltimeResolution = walltimeResolution
+	} else {
+		sysCtx.walltime = &wt
+		sysCtx.walltimeResolution = sys.ClockResolution(time.Microsecond.Nanoseconds())
+	}
+
+	if nanotime != nil {
+		if clockResolutionInvalid(nanotimeResolution) {
+			return nil, fmt.Errorf("invalid Nanotime resolution: %d", nanotimeResolution)
+		}
+		sysCtx.nanotime = nanotime
+		sysCtx.nanotimeResolution = nanotimeResolution
+	} else {
+		sysCtx.nanotime = &nt
+		sysCtx.nanotimeResolution = sys.ClockResolution(time.Nanosecond)
+	}
+
+	sysCtx.fs = NewFSContext(openedFiles)
 
 	return
 }
 
+// clockResolutionInvalid returns true if the value stored isn't reasonable.
+func clockResolutionInvalid(resolution sys.ClockResolution) bool {
+	return resolution < 1 || resolution > sys.ClockResolution(time.Hour.Nanoseconds())
+}
+
 // nullTerminatedByteCount ensures the count or Nul-terminated length of the elements doesn't exceed max, and that no
 // element includes the nul character.
 func nullTerminatedByteCount(max uint32, elements []string) (uint32, error) {
diff --git a/internal/sys/time.go b/internal/sys/time.go
deleted file mode 100644
index fa5e9431..00000000
--- a/internal/sys/time.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package sys
-
-// TimeNowUnixNanoKey is a context.Context Value key. Its associated value should be a func() uint64.
-//
-// See https://github.com/tetratelabs/wazero/issues/491
-type TimeNowUnixNanoKey struct{}
diff --git a/internal/wasm/call_context.go b/internal/wasm/call_context.go
index e358485f..0ba82697 100644
--- a/internal/wasm/call_context.go
+++ b/internal/wasm/call_context.go
@@ -6,13 +6,14 @@ import (
 	"sync/atomic"
 
 	"github.com/tetratelabs/wazero/api"
+	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/sys"
 )
 
 // compile time check to ensure CallContext implements api.Module
 var _ api.Module = &CallContext{}
 
-func NewCallContext(ns *Namespace, instance *ModuleInstance, Sys *SysContext) *CallContext {
+func NewCallContext(ns *Namespace, instance *ModuleInstance, Sys *internalsys.Context) *CallContext {
 	zero := uint64(0)
 	return &CallContext{memory: instance.Memory, module: instance, ns: ns, Sys: Sys, closed: &zero}
 }
@@ -34,9 +35,16 @@ type CallContext struct {
 	memory api.Memory
 	ns     *Namespace
 
-	// Note: This is a part of CallContext so that scope is correct and Close is coherent.
-	// Sys is exposed only for WASI
-	Sys *SysContext
+	// Sys is exposed for use in special imports such as WASI, assemblyscript
+	// and wasm_exec.
+	//
+	// Notes
+	//
+	//	* This is a part of CallContext so that scope and Close is coherent.
+	//	* This is not exposed outside this repository (as a host function
+	//	  parameter) because we haven't thought through capabilities based
+	//	  security implications.
+	Sys *internalsys.Context
 
 	// closed is the pointer used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
 	//
diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go
index 4b21f6f0..899edda0 100644
--- a/internal/wasm/func_validation.go
+++ b/internal/wasm/func_validation.go
@@ -1300,6 +1300,23 @@ func (m *Module) validateFunctionWithMaxStackValues(
 					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
 				}
 				valueTypeStack.push(ValueTypeV128)
+			case OpcodeVecI8x16Eq, OpcodeVecI8x16Ne, OpcodeVecI8x16LtS, OpcodeVecI8x16LtU, OpcodeVecI8x16GtS,
+				OpcodeVecI8x16GtU, OpcodeVecI8x16LeS, OpcodeVecI8x16LeU, OpcodeVecI8x16GeS, OpcodeVecI8x16GeU,
+				OpcodeVecI16x8Eq, OpcodeVecI16x8Ne, OpcodeVecI16x8LtS, OpcodeVecI16x8LtU, OpcodeVecI16x8GtS,
+				OpcodeVecI16x8GtU, OpcodeVecI16x8LeS, OpcodeVecI16x8LeU, OpcodeVecI16x8GeS, OpcodeVecI16x8GeU,
+				OpcodeVecI32x4Eq, OpcodeVecI32x4Ne, OpcodeVecI32x4LtS, OpcodeVecI32x4LtU, OpcodeVecI32x4GtS,
+				OpcodeVecI32x4GtU, OpcodeVecI32x4LeS, OpcodeVecI32x4LeU, OpcodeVecI32x4GeS, OpcodeVecI32x4GeU,
+				OpcodeVecI64x2Eq, OpcodeVecI64x2Ne, OpcodeVecI64x2LtS, OpcodeVecI64x2GtS, OpcodeVecI64x2LeS,
+				OpcodeVecI64x2GeS, OpcodeVecF32x4Eq, OpcodeVecF32x4Ne, OpcodeVecF32x4Lt, OpcodeVecF32x4Gt,
+				OpcodeVecF32x4Le, OpcodeVecF32x4Ge, OpcodeVecF64x2Eq, OpcodeVecF64x2Ne, OpcodeVecF64x2Lt,
+				OpcodeVecF64x2Gt, OpcodeVecF64x2Le, OpcodeVecF64x2Ge:
+				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
+					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
+					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
+				}
+				valueTypeStack.push(ValueTypeV128)
 			default:
 				return fmt.Errorf("TODO: SIMD instruction %s will be implemented in #506", vectorInstructionName[vecOpcode])
 			}
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index 023ceb64..65aec08f 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -13,6 +13,7 @@ import (
 	experimentalapi "github.com/tetratelabs/wazero/experimental"
 	"github.com/tetratelabs/wazero/internal/ieee754"
 	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/sys"
 )
 
 type (
@@ -344,7 +345,7 @@ func (s *Store) Instantiate(
 	ns *Namespace,
 	module *Module,
 	name string,
-	sys *SysContext,
+	sys *sys.Context,
 	functionListenerFactory experimentalapi.FunctionListenerFactory,
 ) (*CallContext, error) {
 	if ctx == nil {
@@ -385,7 +386,7 @@ func (s *Store) instantiate(
 	ns *Namespace,
 	module *Module,
 	name string,
-	sys *SysContext,
+	sys *sys.Context,
 	functionListenerFactory experimentalapi.FunctionListenerFactory,
 	modules map[string]*ModuleInstance,
 ) (*CallContext, error) {
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index 2e18e7db..0aa1a817 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -2180,6 +2180,198 @@ operatorSwitch:
 			c.emit(
 				&OperationV128Shr{Shape: ShapeI64x2, Signed: false},
 			)
+		case wasm.OpcodeVecI8x16Eq:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16Eq},
+			)
+		case wasm.OpcodeVecI8x16Ne:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16Ne},
+			)
+		case wasm.OpcodeVecI8x16LtS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16LtS},
+			)
+		case wasm.OpcodeVecI8x16LtU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16LtU},
+			)
+		case wasm.OpcodeVecI8x16GtS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16GtS},
+			)
+		case wasm.OpcodeVecI8x16GtU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16GtU},
+			)
+		case wasm.OpcodeVecI8x16LeS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16LeS},
+			)
+		case wasm.OpcodeVecI8x16LeU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16LeU},
+			)
+		case wasm.OpcodeVecI8x16GeS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16GeS},
+			)
+		case wasm.OpcodeVecI8x16GeU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI8x16GeU},
+			)
+		case wasm.OpcodeVecI16x8Eq:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8Eq},
+			)
+		case wasm.OpcodeVecI16x8Ne:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8Ne},
+			)
+		case wasm.OpcodeVecI16x8LtS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8LtS},
+			)
+		case wasm.OpcodeVecI16x8LtU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8LtU},
+			)
+		case wasm.OpcodeVecI16x8GtS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8GtS},
+			)
+		case wasm.OpcodeVecI16x8GtU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8GtU},
+			)
+		case wasm.OpcodeVecI16x8LeS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8LeS},
+			)
+		case wasm.OpcodeVecI16x8LeU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8LeU},
+			)
+		case wasm.OpcodeVecI16x8GeS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8GeS},
+			)
+		case wasm.OpcodeVecI16x8GeU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI16x8GeU},
+			)
+		case wasm.OpcodeVecI32x4Eq:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4Eq},
+			)
+		case wasm.OpcodeVecI32x4Ne:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4Ne},
+			)
+		case wasm.OpcodeVecI32x4LtS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4LtS},
+			)
+		case wasm.OpcodeVecI32x4LtU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4LtU},
+			)
+		case wasm.OpcodeVecI32x4GtS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4GtS},
+			)
+		case wasm.OpcodeVecI32x4GtU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4GtU},
+			)
+		case wasm.OpcodeVecI32x4LeS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4LeS},
+			)
+		case wasm.OpcodeVecI32x4LeU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4LeU},
+			)
+		case wasm.OpcodeVecI32x4GeS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4GeS},
+			)
+		case wasm.OpcodeVecI32x4GeU:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI32x4GeU},
+			)
+		case wasm.OpcodeVecI64x2Eq:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI64x2Eq},
+			)
+		case wasm.OpcodeVecI64x2Ne:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI64x2Ne},
+			)
+		case wasm.OpcodeVecI64x2LtS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI64x2LtS},
+			)
+		case wasm.OpcodeVecI64x2GtS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI64x2GtS},
+			)
+		case wasm.OpcodeVecI64x2LeS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI64x2LeS},
+			)
+		case wasm.OpcodeVecI64x2GeS:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeI64x2GeS},
+			)
+		case wasm.OpcodeVecF32x4Eq:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF32x4Eq},
+			)
+		case wasm.OpcodeVecF32x4Ne:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF32x4Ne},
+			)
+		case wasm.OpcodeVecF32x4Lt:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF32x4Lt},
+			)
+		case wasm.OpcodeVecF32x4Gt:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF32x4Gt},
+			)
+		case wasm.OpcodeVecF32x4Le:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF32x4Le},
+			)
+		case wasm.OpcodeVecF32x4Ge:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF32x4Ge},
+			)
+		case wasm.OpcodeVecF64x2Eq:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF64x2Eq},
+			)
+		case wasm.OpcodeVecF64x2Ne:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF64x2Ne},
+			)
+		case wasm.OpcodeVecF64x2Lt:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF64x2Lt},
+			)
+		case wasm.OpcodeVecF64x2Gt:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF64x2Gt},
+			)
+		case wasm.OpcodeVecF64x2Le:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF64x2Le},
+			)
+		case wasm.OpcodeVecF64x2Ge:
+			c.emit(
+				&OperationV128Cmp{Type: V128CmpTypeF64x2Ge},
+			)
 		default:
 			return fmt.Errorf("unsupported vector instruction in wazeroir: %s", wasm.VectorInstructionName(vecOp))
 		}
diff --git a/internal/wazeroir/operations.go b/internal/wazeroir/operations.go
index 741fab77..db25340b 100644
--- a/internal/wazeroir/operations.go
+++ b/internal/wazeroir/operations.go
@@ -319,25 +319,37 @@ func (o OperationKind) String() (ret string) {
 	case OperationKindV128AllTrue:
 		ret = "V128AllTrue"
 	case OperationKindV128And:
-		return "V128And"
+		ret = "V128And"
 	case OperationKindV128Not:
-		return "V128Not"
+		ret = "V128Not"
 	case OperationKindV128Or:
-		return "V128Or"
+		ret = "V128Or"
 	case OperationKindV128Xor:
-		return "V128Xor"
+		ret = "V128Xor"
 	case OperationKindV128Bitselect:
-		return "V128Bitselect"
+		ret = "V128Bitselect"
 	case OperationKindV128AndNot:
-		return "V128AndNot"
+		ret = "V128AndNot"
 	case OperationKindV128BitMask:
-		return "V128BitMask"
+		ret = "V128BitMask"
 	case OperationKindV128Shl:
-		return "V128Shl"
+		ret = "V128Shl"
 	case OperationKindV128Shr:
-		return "V128Shr"
+		ret = "V128Shr"
+	case OperationKindV128Cmp:
+		ret = "V128Cmp"
+	case OperationKindSignExtend32From8:
+		ret = "SignExtend32From8"
+	case OperationKindSignExtend32From16:
+		ret = "SignExtend32From16"
+	case OperationKindSignExtend64From8:
+		ret = "SignExtend64From8"
+	case OperationKindSignExtend64From16:
+		ret = "SignExtend64From16"
+	case OperationKindSignExtend64From32:
+		ret = "SignExtend64From32"
 	default:
-		panic("BUG")
+		panic(fmt.Errorf("unknown operation %d", o))
 	}
 	return
 }
@@ -456,6 +468,10 @@ const (
 	OperationKindV128AndNot
 	OperationKindV128Shl
 	OperationKindV128Shr
+	OperationKindV128Cmp
+
+	// operationKindEnd is always placed at the bottom of this iota definition to be used in the test.
+	operationKindEnd
 )
 
 type Label struct {
@@ -1524,3 +1540,114 @@ type OperationV128Shr struct {
 func (o *OperationV128Shr) Kind() OperationKind {
 	return OperationKindV128Shr
 }
+
+// OperationV128Cmp implements Operation.
+type OperationV128Cmp struct {
+	Type V128CmpType
+}
+
+type V128CmpType = byte
+
+const (
+	// V128CmpTypeI8x16Eq corresponds to wasm.OpcodeVecI8x16EqName.
+	V128CmpTypeI8x16Eq V128CmpType = iota
+	// V128CmpTypeI8x16Ne corresponds to wasm.OpcodeVecI8x16NeName.
+	V128CmpTypeI8x16Ne
+	// V128CmpTypeI8x16LtS corresponds to wasm.OpcodeVecI8x16LtSName.
+	V128CmpTypeI8x16LtS
+	// V128CmpTypeI8x16LtU corresponds to wasm.OpcodeVecI8x16LtUName.
+	V128CmpTypeI8x16LtU
+	// V128CmpTypeI8x16GtS corresponds to wasm.OpcodeVecI8x16GtSName.
+	V128CmpTypeI8x16GtS
+	// V128CmpTypeI8x16GtU corresponds to wasm.OpcodeVecI8x16GtUName.
+	V128CmpTypeI8x16GtU
+	// V128CmpTypeI8x16LeS corresponds to wasm.OpcodeVecI8x16LeSName.
+	V128CmpTypeI8x16LeS
+	// V128CmpTypeI8x16LeU corresponds to wasm.OpcodeVecI8x16LeUName.
+	V128CmpTypeI8x16LeU
+	// V128CmpTypeI8x16GeS corresponds to wasm.OpcodeVecI8x16GeSName.
+	V128CmpTypeI8x16GeS
+	// V128CmpTypeI8x16GeU corresponds to wasm.OpcodeVecI8x16GeUName.
+	V128CmpTypeI8x16GeU
+	// V128CmpTypeI16x8Eq corresponds to wasm.OpcodeVecI16x8EqName.
+	V128CmpTypeI16x8Eq
+	// V128CmpTypeI16x8Ne corresponds to wasm.OpcodeVecI16x8NeName.
+	V128CmpTypeI16x8Ne
+	// V128CmpTypeI16x8LtS corresponds to wasm.OpcodeVecI16x8LtSName.
+	V128CmpTypeI16x8LtS
+	// V128CmpTypeI16x8LtU corresponds to wasm.OpcodeVecI16x8LtUName.
+	V128CmpTypeI16x8LtU
+	// V128CmpTypeI16x8GtS corresponds to wasm.OpcodeVecI16x8GtSName.
+	V128CmpTypeI16x8GtS
+	// V128CmpTypeI16x8GtU corresponds to wasm.OpcodeVecI16x8GtUName.
+	V128CmpTypeI16x8GtU
+	// V128CmpTypeI16x8LeS corresponds to wasm.OpcodeVecI16x8LeSName.
+	V128CmpTypeI16x8LeS
+	// V128CmpTypeI16x8LeU corresponds to wasm.OpcodeVecI16x8LeUName.
+	V128CmpTypeI16x8LeU
+	// V128CmpTypeI16x8GeS corresponds to wasm.OpcodeVecI16x8GeSName.
+	V128CmpTypeI16x8GeS
+	// V128CmpTypeI16x8GeU corresponds to wasm.OpcodeVecI16x8GeUName.
+	V128CmpTypeI16x8GeU
+	// V128CmpTypeI32x4Eq corresponds to wasm.OpcodeVecI32x4EqName.
+	V128CmpTypeI32x4Eq
+	// V128CmpTypeI32x4Ne corresponds to wasm.OpcodeVecI32x4NeName.
+	V128CmpTypeI32x4Ne
+	// V128CmpTypeI32x4LtS corresponds to wasm.OpcodeVecI32x4LtSName.
+	V128CmpTypeI32x4LtS
+	// V128CmpTypeI32x4LtU corresponds to wasm.OpcodeVecI32x4LtUName.
+	V128CmpTypeI32x4LtU
+	// V128CmpTypeI32x4GtS corresponds to wasm.OpcodeVecI32x4GtSName.
+	V128CmpTypeI32x4GtS
+	// V128CmpTypeI32x4GtU corresponds to wasm.OpcodeVecI32x4GtUName.
+	V128CmpTypeI32x4GtU
+	// V128CmpTypeI32x4LeS corresponds to wasm.OpcodeVecI32x4LeSName.
+	V128CmpTypeI32x4LeS
+	// V128CmpTypeI32x4LeU corresponds to wasm.OpcodeVecI32x4LeUName.
+	V128CmpTypeI32x4LeU
+	// V128CmpTypeI32x4GeS corresponds to wasm.OpcodeVecI32x4GeSName.
+	V128CmpTypeI32x4GeS
+	// V128CmpTypeI32x4GeU corresponds to wasm.OpcodeVecI32x4GeUName.
+	V128CmpTypeI32x4GeU
+	// V128CmpTypeI64x2Eq corresponds to wasm.OpcodeVecI64x2EqName.
+	V128CmpTypeI64x2Eq
+	// V128CmpTypeI64x2Ne corresponds to wasm.OpcodeVecI64x2NeName.
+	V128CmpTypeI64x2Ne
+	// V128CmpTypeI64x2LtS corresponds to wasm.OpcodeVecI64x2LtSName.
+	V128CmpTypeI64x2LtS
+	// V128CmpTypeI64x2GtS corresponds to wasm.OpcodeVecI64x2GtSName.
+	V128CmpTypeI64x2GtS
+	// V128CmpTypeI64x2LeS corresponds to wasm.OpcodeVecI64x2LeSName.
+	V128CmpTypeI64x2LeS
+	// V128CmpTypeI64x2GeS corresponds to wasm.OpcodeVecI64x2GeSName.
+	V128CmpTypeI64x2GeS
+	// V128CmpTypeF32x4Eq corresponds to wasm.OpcodeVecF32x4EqName.
+	V128CmpTypeF32x4Eq
+	// V128CmpTypeF32x4Ne corresponds to wasm.OpcodeVecF32x4NeName.
+	V128CmpTypeF32x4Ne
+	// V128CmpTypeF32x4Lt corresponds to wasm.OpcodeVecF32x4LtName.
+	V128CmpTypeF32x4Lt
+	// V128CmpTypeF32x4Gt corresponds to wasm.OpcodeVecF32x4GtName.
+	V128CmpTypeF32x4Gt
+	// V128CmpTypeF32x4Le corresponds to wasm.OpcodeVecF32x4LeName.
+	V128CmpTypeF32x4Le
+	// V128CmpTypeF32x4Ge corresponds to wasm.OpcodeVecF32x4GeName.
+	V128CmpTypeF32x4Ge
+	// V128CmpTypeF64x2Eq corresponds to wasm.OpcodeVecF64x2EqName.
+	V128CmpTypeF64x2Eq
+	// V128CmpTypeF64x2Ne corresponds to wasm.OpcodeVecF64x2NeName.
+	V128CmpTypeF64x2Ne
+	// V128CmpTypeF64x2Lt corresponds to wasm.OpcodeVecF64x2LtName.
+	V128CmpTypeF64x2Lt
+	// V128CmpTypeF64x2Gt corresponds to wasm.OpcodeVecF64x2GtName.
+	V128CmpTypeF64x2Gt
+	// V128CmpTypeF64x2Le corresponds to wasm.OpcodeVecF64x2LeName.
+	V128CmpTypeF64x2Le
+	// V128CmpTypeF64x2Ge corresponds to wasm.OpcodeVecF64x2GeName.
+	V128CmpTypeF64x2Ge
+)
+
+//Kind implements Operation.Kind.
+func (o *OperationV128Cmp) Kind() OperationKind {
+	return OperationKindV128Cmp
+}
diff --git a/internal/wazeroir/signature.go b/internal/wazeroir/signature.go
index 924baac3..f3ff5ac6 100644
--- a/internal/wazeroir/signature.go
+++ b/internal/wazeroir/signature.go
@@ -536,6 +536,17 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature
 			return signature_V128_V128, nil
 		case wasm.OpcodeVecV128Bitselect:
 			return signature_V128V128V128_V32, nil
+		case wasm.OpcodeVecI8x16Eq, wasm.OpcodeVecI8x16Ne, wasm.OpcodeVecI8x16LtS, wasm.OpcodeVecI8x16LtU, wasm.OpcodeVecI8x16GtS,
+			wasm.OpcodeVecI8x16GtU, wasm.OpcodeVecI8x16LeS, wasm.OpcodeVecI8x16LeU, wasm.OpcodeVecI8x16GeS, wasm.OpcodeVecI8x16GeU,
+			wasm.OpcodeVecI16x8Eq, wasm.OpcodeVecI16x8Ne, wasm.OpcodeVecI16x8LtS, wasm.OpcodeVecI16x8LtU, wasm.OpcodeVecI16x8GtS,
+			wasm.OpcodeVecI16x8GtU, wasm.OpcodeVecI16x8LeS, wasm.OpcodeVecI16x8LeU, wasm.OpcodeVecI16x8GeS, wasm.OpcodeVecI16x8GeU,
+			wasm.OpcodeVecI32x4Eq, wasm.OpcodeVecI32x4Ne, wasm.OpcodeVecI32x4LtS, wasm.OpcodeVecI32x4LtU, wasm.OpcodeVecI32x4GtS,
+			wasm.OpcodeVecI32x4GtU, wasm.OpcodeVecI32x4LeS, wasm.OpcodeVecI32x4LeU, wasm.OpcodeVecI32x4GeS, wasm.OpcodeVecI32x4GeU,
+			wasm.OpcodeVecI64x2Eq, wasm.OpcodeVecI64x2Ne, wasm.OpcodeVecI64x2LtS, wasm.OpcodeVecI64x2GtS, wasm.OpcodeVecI64x2LeS,
+			wasm.OpcodeVecI64x2GeS, wasm.OpcodeVecF32x4Eq, wasm.OpcodeVecF32x4Ne, wasm.OpcodeVecF32x4Lt, wasm.OpcodeVecF32x4Gt,
+			wasm.OpcodeVecF32x4Le, wasm.OpcodeVecF32x4Ge, wasm.OpcodeVecF64x2Eq, wasm.OpcodeVecF64x2Ne, wasm.OpcodeVecF64x2Lt,
+			wasm.OpcodeVecF64x2Gt, wasm.OpcodeVecF64x2Le, wasm.OpcodeVecF64x2Ge:
+			return signature_V128V128_V128, nil
 		default:
 			return nil, fmt.Errorf("unsupported vector instruction in wazeroir: %s", wasm.VectorInstructionName(vecOp))
 		}
diff --git a/namespace.go b/namespace.go
index ac03b7e8..3d0638e5 100644
--- a/namespace.go
+++ b/namespace.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/tetratelabs/wazero/api"
 	experimentalapi "github.com/tetratelabs/wazero/experimental"
+	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/wasm"
 	"github.com/tetratelabs/wazero/sys"
 )
@@ -76,7 +77,7 @@ func (ns *namespace) InstantiateModule(
 		panic(fmt.Errorf("unsupported wazero.ModuleConfig implementation: %#v", mConfig))
 	}
 
-	var sysCtx *wasm.SysContext
+	var sysCtx *internalsys.Context
 	if sysCtx, err = config.toSysContext(); err != nil {
 		return
 	}
diff --git a/sys/clock.go b/sys/clock.go
new file mode 100644
index 00000000..e900390d
--- /dev/null
+++ b/sys/clock.go
@@ -0,0 +1,21 @@
+package sys
+
+import "context"
+
+// ClockResolution is a positive granularity of clock precision in
+// nanoseconds. For example, if the resolution is 1us, this returns 1000.
+//
+// Note: Some implementations return arbitrary resolution because there's
+// no perfect alternative. For example, according to the source in time.go,
+// windows monotonic resolution can be 15ms. See /RATIONALE.md.
+type ClockResolution uint32
+
+// Walltime returns the current time in epoch seconds with a nanosecond fraction.
+type Walltime func(context.Context) (sec int64, nsec int32)
+
+// Nanotime returns nanoseconds since an arbitrary start point, used to measure
+// elapsed time. This is sometimes referred to as a tick or monotonic time.
+//
+// Note: There are no constraints on the value return except that it
+// increments. For example, -1 is a valid if the next value is >= 0.
+type Nanotime func(context.Context) int64
diff --git a/sys/sys.go b/sys/error.go
similarity index 100%
rename from sys/sys.go
rename to sys/error.go
diff --git a/sys/sys_test.go b/sys/error_test.go
similarity index 100%
rename from sys/sys_test.go
rename to sys/error_test.go
diff --git a/wasi_snapshot_preview1/wasi.go b/wasi_snapshot_preview1/wasi.go
index 301b4675..8845c1b3 100644
--- a/wasi_snapshot_preview1/wasi.go
+++ b/wasi_snapshot_preview1/wasi.go
@@ -559,8 +559,8 @@ func wasiFunctions() map[string]interface{} {
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_get
 // See https://en.wikipedia.org/wiki/Null-terminated_string
 func (a *wasi) ArgsGet(ctx context.Context, m api.Module, argv, argvBuf uint32) Errno {
-	sys := sysCtx(m)
-	return writeOffsetsAndNullTerminatedValues(ctx, m.Memory(), sys.Args(), argv, argvBuf)
+	sysCtx := getSysCtx(m)
+	return writeOffsetsAndNullTerminatedValues(ctx, m.Memory(), sysCtx.Args(), argv, argvBuf)
 }
 
 // ArgsSizesGet is the WASI function named functionArgsSizesGet that reads command-line argument data (WithArgs)
@@ -590,13 +590,13 @@ func (a *wasi) ArgsGet(ctx context.Context, m api.Module, argv, argvBuf uint32)
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#args_sizes_get
 // See https://en.wikipedia.org/wiki/Null-terminated_string
 func (a *wasi) ArgsSizesGet(ctx context.Context, m api.Module, resultArgc, resultArgvBufSize uint32) Errno {
-	sys := sysCtx(m)
+	sysCtx := getSysCtx(m)
 	mem := m.Memory()
 
-	if !mem.WriteUint32Le(ctx, resultArgc, uint32(len(sys.Args()))) {
+	if !mem.WriteUint32Le(ctx, resultArgc, uint32(len(sysCtx.Args()))) {
 		return ErrnoFault
 	}
-	if !mem.WriteUint32Le(ctx, resultArgvBufSize, sys.ArgsSize()) {
+	if !mem.WriteUint32Le(ctx, resultArgvBufSize, sysCtx.ArgsSize()) {
 		return ErrnoFault
 	}
 	return ErrnoSuccess
@@ -629,8 +629,8 @@ func (a *wasi) ArgsSizesGet(ctx context.Context, m api.Module, resultArgc, resul
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_get
 // See https://en.wikipedia.org/wiki/Null-terminated_string
 func (a *wasi) EnvironGet(ctx context.Context, m api.Module, environ uint32, environBuf uint32) Errno {
-	sys := sysCtx(m)
-	return writeOffsetsAndNullTerminatedValues(ctx, m.Memory(), sys.Environ(), environ, environBuf)
+	env := getSysCtx(m).Environ()
+	return writeOffsetsAndNullTerminatedValues(ctx, m.Memory(), env, environ, environBuf)
 }
 
 // EnvironSizesGet is the WASI function named functionEnvironSizesGet that reads environment variable
@@ -661,13 +661,13 @@ func (a *wasi) EnvironGet(ctx context.Context, m api.Module, environ uint32, env
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#environ_sizes_get
 // See https://en.wikipedia.org/wiki/Null-terminated_string
 func (a *wasi) EnvironSizesGet(ctx context.Context, m api.Module, resultEnvironc uint32, resultEnvironBufSize uint32) Errno {
-	sys := sysCtx(m)
+	sysCtx := getSysCtx(m)
 	mem := m.Memory()
 
-	if !mem.WriteUint32Le(ctx, resultEnvironc, uint32(len(sys.Environ()))) {
+	if !mem.WriteUint32Le(ctx, resultEnvironc, uint32(len(sysCtx.Environ()))) {
 		return ErrnoFault
 	}
-	if !mem.WriteUint32Le(ctx, resultEnvironBufSize, sys.EnvironSize()) {
+	if !mem.WriteUint32Le(ctx, resultEnvironBufSize, sysCtx.EnvironSize()) {
 		return ErrnoFault
 	}
 
@@ -693,22 +693,19 @@ func (a *wasi) EnvironSizesGet(ctx context.Context, m api.Module, resultEnvironc
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-clock_res_getid-clockid---errno-timestamp
 // See https://linux.die.net/man/3/clock_getres
 func (a *wasi) ClockResGet(ctx context.Context, m api.Module, id uint32, resultResolution uint32) Errno {
-	// We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the
-	// source in time.go, windows monotonic resolution can be 15ms. This chooses arbitrarily 1us for wall time and
-	// 1ns for monotonic. See RATIONALE.md for more context.
+	sysCtx := getSysCtx(m)
 
 	var resolution uint64 // ns
 	switch id {
 	case clockIDRealtime:
-		resolution = 1000 // 1us
+		resolution = uint64(sysCtx.WalltimeResolution())
 	case clockIDMonotonic:
-		resolution = 1 // 1ns
+		resolution = uint64(sysCtx.NanotimeResolution())
 	default:
 		// Similar to many other runtimes, we only support realtime and monotonic clocks. Other types
 		// are slated to be removed from the next version of WASI.
 		return ErrnoNosys
 	}
-	// fixed for GrainLang per #271 and Swift per https://github.com/tetratelabs/wazero/issues/526#issuecomment-1134034760
 	if !m.Memory().WriteUint64Le(ctx, resultResolution, resolution) {
 		return ErrnoFault
 	}
@@ -737,21 +734,15 @@ func (a *wasi) ClockResGet(ctx context.Context, m api.Module, id uint32, resultR
 // See https://linux.die.net/man/3/clock_gettime
 func (a *wasi) ClockTimeGet(ctx context.Context, m api.Module, id uint32, precision uint64, resultTimestamp uint32) Errno {
 	// TODO: precision is currently ignored.
+	sysCtx := getSysCtx(m)
+
 	var val uint64
 	switch id {
 	case clockIDRealtime:
-		clock := timeNowUnixNano
-		// Override Context when it is passed via context
-		if clockVal := ctx.Value(sys.TimeNowUnixNanoKey{}); clockVal != nil {
-			clockCtx, ok := clockVal.(func() uint64)
-			if !ok {
-				panic(fmt.Errorf("unsupported clock key: %v", clockVal))
-			}
-			clock = clockCtx
-		}
-		val = clock()
+		sec, nsec := sysCtx.Walltime(ctx)
+		val = (uint64(sec) * uint64(time.Second.Nanoseconds())) + uint64(nsec)
 	case clockIDMonotonic:
-		val = uint64(time.Since(monotonicClockBase))
+		val = uint64(sysCtx.Nanotime(ctx))
 	default:
 		// Similar to many other runtimes, we only support realtime and monotonic clocks. Other types
 		// are slated to be removed from the next version of WASI.
@@ -1011,13 +1002,13 @@ func (a *wasi) FdPwrite(ctx context.Context, m api.Module, fd, iovs, iovsCount u
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#iovec
 // See https://linux.die.net/man/3/readv
 func (a *wasi) FdRead(ctx context.Context, m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
-	sys, fsc := sysFSCtx(ctx, m)
+	sysCtx, fsCtx := sysFSCtx(ctx, m)
 
 	var reader io.Reader
 
 	if fd == fdStdin {
-		reader = sys.Stdin()
-	} else if f, ok := fsc.OpenedFile(fd); !ok || f.File == nil {
+		reader = sysCtx.Stdin()
+	} else if f, ok := fsCtx.OpenedFile(fd); !ok || f.File == nil {
 		return ErrnoBadf
 	} else {
 		reader = f.File
@@ -1180,18 +1171,18 @@ func (a *wasi) FdTell(ctx context.Context, m api.Module, fd, resultOffset uint32
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
 // See https://linux.die.net/man/3/writev
 func (a *wasi) FdWrite(ctx context.Context, m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
-	sys, fsc := sysFSCtx(ctx, m)
+	sysCtx, fsCtx := sysFSCtx(ctx, m)
 
 	var writer io.Writer
 
 	switch fd {
 	case fdStdout:
-		writer = sys.Stdout()
+		writer = sysCtx.Stdout()
 	case fdStderr:
-		writer = sys.Stderr()
+		writer = sysCtx.Stderr()
 	default:
 		// Check to see if the file descriptor is available
-		if f, ok := fsc.OpenedFile(fd); !ok || f.File == nil {
+		if f, ok := fsCtx.OpenedFile(fd); !ok || f.File == nil {
 			return ErrnoBadf
 			// fs.FS doesn't declare io.Writer, but implementations such as os.File implement it.
 		} else if writer, ok = f.File.(io.Writer); !ok {
@@ -1393,8 +1384,8 @@ func (a *wasi) SchedYield(m api.Module) Errno {
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
 func (a *wasi) RandomGet(ctx context.Context, m api.Module, buf uint32, bufLen uint32) (errno Errno) {
 	randomBytes := make([]byte, bufLen)
-	sys := sysCtx(m)
-	n, err := sys.RandSource().Read(randomBytes)
+	sysCtx := getSysCtx(m)
+	n, err := sysCtx.RandSource().Read(randomBytes)
 	if n != int(bufLen) || err != nil {
 		// TODO: handle different errors that syscal to entropy source can return
 		return ErrnoIo
@@ -1434,14 +1425,7 @@ const (
 	clockIDMonotonic = 1
 )
 
-// monotonicClockBase uses time.Now to ensure a monotonic clock reading on all platforms via time.Since.
-var monotonicClockBase = time.Now()
-
-func timeNowUnixNano() uint64 {
-	return uint64(time.Now().UnixNano())
-}
-
-func sysCtx(m api.Module) *wasm.SysContext {
+func getSysCtx(m api.Module) *sys.Context {
 	if internal, ok := m.(*wasm.CallContext); !ok {
 		panic(fmt.Errorf("unsupported wasm.Module implementation: %v", m))
 	} else {
@@ -1449,7 +1433,7 @@ func sysCtx(m api.Module) *wasm.SysContext {
 	}
 }
 
-func sysFSCtx(ctx context.Context, m api.Module) (*wasm.SysContext, *sys.FSContext) {
+func sysFSCtx(ctx context.Context, m api.Module) (*sys.Context, *sys.FSContext) {
 	if internal, ok := m.(*wasm.CallContext); !ok {
 		panic(fmt.Errorf("unsupported wasm.Module implementation: %v", m))
 	} else {


Test patch:
diff --git a/config_test.go b/config_test.go
index ffa5a2d2..9f519d4a 100644
--- a/config_test.go
+++ b/config_test.go
@@ -9,9 +9,10 @@ import (
 	"testing/fstest"
 
 	"github.com/tetratelabs/wazero/api"
-	"github.com/tetratelabs/wazero/internal/sys"
+	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/wasm"
+	"github.com/tetratelabs/wazero/sys"
 )
 
 func TestRuntimeConfig(t *testing.T) {
@@ -299,7 +300,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 	tests := []struct {
 		name     string
 		input    ModuleConfig
-		expected *wasm.SysContext
+		expected *internalsys.Context
 	}{
 		{
 			name:  "empty",
@@ -312,7 +313,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,            // stdout
 				nil,            // stderr
 				nil,            // randSource
-				nil,            // openedFiles
+				nil, 0,         // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
 		{
@@ -326,7 +329,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,                 // stdout
 				nil,                 // stderr
 				nil,                 // randSource
-				nil,                 // openedFiles
+				nil, 0,              // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
 		{
@@ -340,7 +345,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,                // stdout
 				nil,                // stderr
 				nil,                // randSource
-				nil,                // openedFiles
+				nil, 0,             // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
 		{
@@ -354,7 +361,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,                 // stdout
 				nil,                 // stderr
 				nil,                 // randSource
-				nil,                 // openedFiles
+				nil, 0,              // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
 		{
@@ -368,7 +377,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,             // stdout
 				nil,             // stderr
 				nil,             // randSource
-				nil,             // openedFiles
+				nil, 0,          // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
 		{
@@ -382,7 +393,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,            // stdout
 				nil,            // stderr
 				nil,            // randSource
-				nil,            // openedFiles
+				nil, 0,         // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
 		{
@@ -396,7 +409,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,                     // stdout
 				nil,                     // stderr
 				nil,                     // randSource
-				nil,                     // openedFiles
+				nil, 0,                  // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
 		{
@@ -410,10 +425,11 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,                      // stdout
 				nil,                      // stderr
 				nil,                      // randSource
-				nil,                      // openedFiles
+				nil, 0,                   // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
-
 		{
 			name:  "WithEnv twice",
 			input: NewModuleConfig().WithEnv("a", "b").WithEnv("c", "de"),
@@ -425,7 +441,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,                     // stdout
 				nil,                     // stderr
 				nil,                     // randSource
-				nil,                     // openedFiles
+				nil, 0,                  // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
 			),
 		},
 		{
@@ -439,7 +457,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,            // stdout
 				nil,            // stderr
 				nil,            // randSource
-				map[uint32]*sys.FileEntry{ // openedFiles
+				nil, 0,         // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				map[uint32]*internalsys.FileEntry{ // openedFiles
 					3: {Path: "/", FS: testFS},
 					4: {Path: ".", FS: testFS},
 				},
@@ -456,7 +476,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,            // stdout
 				nil,            // stderr
 				nil,            // randSource
-				map[uint32]*sys.FileEntry{ // openedFiles
+				nil, 0,         // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				map[uint32]*internalsys.FileEntry{ // openedFiles
 					3: {Path: "/", FS: testFS2},
 					4: {Path: ".", FS: testFS2},
 				},
@@ -473,7 +495,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,            // stdout
 				nil,            // stderr
 				nil,            // randSource
-				map[uint32]*sys.FileEntry{ // openedFiles
+				nil, 0,         // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				map[uint32]*internalsys.FileEntry{ // openedFiles
 					3: {Path: ".", FS: testFS},
 				},
 			),
@@ -489,7 +513,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,            // stdout
 				nil,            // stderr
 				nil,            // randSource
-				map[uint32]*sys.FileEntry{ // openedFiles
+				nil, 0,         // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				map[uint32]*internalsys.FileEntry{ // openedFiles
 					3: {Path: "/", FS: testFS},
 					4: {Path: ".", FS: testFS2},
 				},
@@ -506,22 +532,169 @@ func TestModuleConfig_toSysContext(t *testing.T) {
 				nil,            // stdout
 				nil,            // stderr
 				nil,            // randSource
-				map[uint32]*sys.FileEntry{ // openedFiles
+				nil, 0,         // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				map[uint32]*internalsys.FileEntry{ // openedFiles
 					3: {Path: ".", FS: testFS},
 					4: {Path: "/", FS: testFS2},
 				},
 			),
 		},
 	}
+
 	for _, tt := range tests {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			sys, err := tc.input.(*moduleConfig).toSysContext()
+			sysCtx, err := tc.input.(*moduleConfig).toSysContext()
 			require.NoError(t, err)
-			require.Equal(t, tc.expected, sys)
+			require.Equal(t, tc.expected, sysCtx)
+		})
+	}
+}
+
+// TestModuleConfig_toSysContext_WithWalltime has to test differently because we can't
+// compare function pointers when functions are passed by value.
+func TestModuleConfig_toSysContext_WithWalltime(t *testing.T) {
+	tests := []struct {
+		name               string
+		input              ModuleConfig
+		expectedSec        int64
+		expectedNsec       int32
+		expectedResolution sys.ClockResolution
+		expectedErr        string
+	}{
+		{
+			name: "ok",
+			input: NewModuleConfig().
+				WithWalltime(func(context.Context) (sec int64, nsec int32) {
+					return 1, 2
+				}, 3),
+			expectedSec:        1,
+			expectedNsec:       2,
+			expectedResolution: 3,
+		},
+		{
+			name: "overwrites",
+			input: NewModuleConfig().
+				WithWalltime(func(context.Context) (sec int64, nsec int32) {
+					return 3, 4
+				}, 5).
+				WithWalltime(func(context.Context) (sec int64, nsec int32) {
+					return 1, 2
+				}, 3),
+			expectedSec:        1,
+			expectedNsec:       2,
+			expectedResolution: 3,
+		},
+		{
+			name: "invalid resolution",
+			input: NewModuleConfig().
+				WithWalltime(func(context.Context) (sec int64, nsec int32) {
+					return 1, 2
+				}, 0),
+			expectedErr: "invalid Walltime resolution: 0",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			sysCtx, err := tc.input.(*moduleConfig).toSysContext()
+			if tc.expectedErr == "" {
+				require.Nil(t, err)
+				sec, nsec := sysCtx.Walltime(testCtx)
+				require.Equal(t, tc.expectedSec, sec)
+				require.Equal(t, tc.expectedNsec, nsec)
+				require.Equal(t, tc.expectedResolution, sysCtx.WalltimeResolution())
+			} else {
+				require.EqualError(t, err, tc.expectedErr)
+			}
 		})
 	}
+
+	t.Run("context", func(t *testing.T) {
+		sysCtx, err := NewModuleConfig().
+			WithWalltime(func(ctx context.Context) (sec int64, nsec int32) {
+				require.Equal(t, testCtx, ctx)
+				return 1, 2
+			}, 3).(*moduleConfig).toSysContext()
+		require.NoError(t, err)
+		sec, nsec := sysCtx.Walltime(testCtx)
+		// If below pass, the context was correct!
+		require.Equal(t, int64(1), sec)
+		require.Equal(t, int32(2), nsec)
+	})
+}
+
+// TestModuleConfig_toSysContext_WithNanotime has to test differently because we can't
+// compare function pointers when functions are passed by value.
+func TestModuleConfig_toSysContext_WithNanotime(t *testing.T) {
+	tests := []struct {
+		name               string
+		input              ModuleConfig
+		expectedNanos      int64
+		expectedResolution sys.ClockResolution
+		expectedErr        string
+	}{
+		{
+			name: "ok",
+			input: NewModuleConfig().
+				WithNanotime(func(context.Context) int64 {
+					return 1
+				}, 2),
+			expectedNanos:      1,
+			expectedResolution: 2,
+		},
+		{
+			name: "overwrites",
+			input: NewModuleConfig().
+				WithNanotime(func(context.Context) int64 {
+					return 3
+				}, 4).
+				WithNanotime(func(context.Context) int64 {
+					return 1
+				}, 2),
+			expectedNanos:      1,
+			expectedResolution: 2,
+		},
+		{
+			name: "invalid resolution",
+			input: NewModuleConfig().
+				WithNanotime(func(context.Context) int64 {
+					return 1
+				}, 0),
+			expectedErr: "invalid Nanotime resolution: 0",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			sysCtx, err := tc.input.(*moduleConfig).toSysContext()
+			if tc.expectedErr == "" {
+				require.Nil(t, err)
+				nanos := sysCtx.Nanotime(testCtx)
+				require.Equal(t, tc.expectedNanos, nanos)
+				require.Equal(t, tc.expectedResolution, sysCtx.NanotimeResolution())
+			} else {
+				require.EqualError(t, err, tc.expectedErr)
+			}
+		})
+	}
+
+	t.Run("context", func(t *testing.T) {
+		sysCtx, err := NewModuleConfig().
+			WithNanotime(func(ctx context.Context) int64 {
+				require.Equal(t, testCtx, ctx)
+				return 1
+			}, 2).(*moduleConfig).toSysContext()
+		require.NoError(t, err)
+		// If below pass, the context was correct!
+		require.Equal(t, int64(1), sysCtx.Nanotime(testCtx))
+	})
 }
 
 func TestModuleConfig_toSysContext_Errors(t *testing.T) {
@@ -576,9 +749,30 @@ func TestModuleConfig_toSysContext_Errors(t *testing.T) {
 	}
 }
 
-// requireSysContext ensures wasm.NewSysContext doesn't return an error, which makes it usable in test matrices.
-func requireSysContext(t *testing.T, max uint32, args, environ []string, stdin io.Reader, stdout, stderr io.Writer, randsource io.Reader, openedFiles map[uint32]*sys.FileEntry) *wasm.SysContext {
-	sysCtx, err := wasm.NewSysContext(max, args, environ, stdin, stdout, stderr, randsource, openedFiles)
+// requireSysContext ensures wasm.NewContext doesn't return an error, which makes it usable in test matrices.
+func requireSysContext(
+	t *testing.T,
+	max uint32,
+	args, environ []string,
+	stdin io.Reader,
+	stdout, stderr io.Writer,
+	randSource io.Reader,
+	walltime *sys.Walltime, walltimeResolution sys.ClockResolution,
+	nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution,
+	openedFiles map[uint32]*internalsys.FileEntry,
+) *internalsys.Context {
+	sysCtx, err := internalsys.NewContext(
+		max,
+		args,
+		environ,
+		stdin,
+		stdout,
+		stderr,
+		randSource,
+		walltime, walltimeResolution,
+		nanotime, nanotimeResolution,
+		openedFiles,
+	)
 	require.NoError(t, err)
 	return sysCtx
 }
diff --git a/experimental/time_example_test.go b/experimental/time_example_test.go
deleted file mode 100644
index 6487798d..00000000
--- a/experimental/time_example_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package experimental_test
-
-import (
-	"context"
-	_ "embed"
-	"fmt"
-	"log"
-	"time"
-
-	"github.com/tetratelabs/wazero"
-	"github.com/tetratelabs/wazero/experimental"
-	"github.com/tetratelabs/wazero/wasi_snapshot_preview1"
-)
-
-const epochNanos = uint64(1640995200000000000) // midnight UTC 2022-01-01
-
-// clockWasm was generated by the following:
-//	cd testdata; wat2wasm --debug-names clock.wat
-//go:embed testdata/clock.wasm
-var clockWasm []byte
-
-// This is a basic example of overriding the clock via WithTimeNowUnixNano. The main goal is to show how it is configured.
-func Example_withTimeNowUnixNano() {
-	ctx := context.Background()
-
-	r := wazero.NewRuntime()
-	defer r.Close(ctx) // This closes everything this Runtime created.
-
-	if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {
-		log.Panicln(err)
-	}
-
-	// Instantiate a module that only re-exports a WASI function that uses the clock.
-	mod, err := r.InstantiateModuleFromBinary(ctx, clockWasm)
-	if err != nil {
-		log.Panicln(err)
-	}
-
-	// Call clock_time_get in context of an experimental clock function
-	ctx = experimental.WithTimeNowUnixNano(ctx, func() uint64 { return epochNanos })
-	results, err := mod.ExportedFunction("clock_time_get").Call(ctx, 0, 0, 0)
-	if err != nil {
-		log.Panicln(err)
-	}
-	if results[0] != 0 {
-		log.Panicf("received errno %d\n", results[0])
-	}
-
-	// Try to read the time WASI wrote to memory at offset zero.
-	if nanos, ok := mod.Memory().ReadUint64Le(ctx, 0); !ok {
-		log.Panicf("Memory.ReadUint64Le(0) out of range of memory size %d", mod.Memory().Size(ctx))
-	} else {
-		fmt.Println(time.UnixMicro(int64(nanos / 1000)).UTC())
-	}
-
-	// Output:
-	// 2022-01-01 00:00:00 +0000 UTC
-}
diff --git a/internal/asm/amd64/consts_test.go b/internal/asm/amd64/consts_test.go
new file mode 100644
index 00000000..6d9964b3
--- /dev/null
+++ b/internal/asm/amd64/consts_test.go
@@ -0,0 +1,15 @@
+package amd64
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/asm"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+// TestInstructionName ensures that all the instruction's name is defined.
+func TestInstructionName(t *testing.T) {
+	for inst := asm.Instruction(0); inst < instructionEnd; inst++ {
+		require.NotEqual(t, "", InstructionName(inst))
+	}
+}
diff --git a/internal/asm/amd64/impl_test.go b/internal/asm/amd64/impl_test.go
index df9d2e73..0a49b7ab 100644
--- a/internal/asm/amd64/impl_test.go
+++ b/internal/asm/amd64/impl_test.go
@@ -733,6 +733,210 @@ func TestAssemblerImpl_EncodeRegisterToRegister(t *testing.T) {
 			},
 			exp: []byte{0x66, 0x45, 0xf, 0xf3, 0xdf},
 		},
+		{
+			name: "cmpeqps xmm11, xmm15",
+			n: &NodeImpl{
+				Instruction: CMPPS,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX15,
+				DstReg:      RegX11,
+				Arg:         0, // CMPPS with arg=0 == Pseudo-Op CMPEQPS.
+			},
+			exp: []byte{0x45, 0xf, 0xc2, 0xdf, 0x0},
+		},
+		{
+			name: "cmpordps xmm1, xmm5",
+			n: &NodeImpl{
+				Instruction: CMPPS,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX5,
+				DstReg:      RegX1,
+				Arg:         7, // CMPPS with arg=7 == Pseudo-Op CMPORDPS.
+			},
+			exp: []byte{0xf, 0xc2, 0xcd, 0x7},
+		},
+		{
+			name: "cmplepd xmm11, xmm15",
+			n: &NodeImpl{
+				Instruction: CMPPD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX15,
+				DstReg:      RegX11,
+				Arg:         2, // CMPPD with arg=2 == Pseudo-Op CMPLEPD.
+			},
+			exp: []byte{0x66, 0x45, 0xf, 0xc2, 0xdf, 0x2},
+		},
+		{
+			name: "cmpneqpd xmm1, xmm5",
+			n: &NodeImpl{
+				Instruction: CMPPD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX5,
+				DstReg:      RegX1,
+				Arg:         4, // CMPPD with arg=4 == Pseudo-Op CMPNEQPD.
+			},
+			exp: []byte{0x66, 0xf, 0xc2, 0xcd, 0x4},
+		},
+		{
+			name: "pcmpgtq xmm10, xmm3",
+			n: &NodeImpl{
+				Instruction: PCMPGTQ,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX3,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x44, 0xf, 0x38, 0x37, 0xd3},
+		},
+		{
+			name: "pcmpgtd xmm10, xmm3",
+			n: &NodeImpl{
+				Instruction: PCMPGTD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX3,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x44, 0xf, 0x66, 0xd3},
+		},
+		{
+			name: "pminsd xmm10, xmm3",
+			n: &NodeImpl{
+				Instruction: PMINSD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX3,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x44, 0xf, 0x38, 0x39, 0xd3},
+		},
+		{
+			name: "pmaxsd xmm1, xmm12",
+			n: &NodeImpl{
+				Instruction: PMAXSD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX12,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x38, 0x3d, 0xcc},
+		},
+		{
+			name: "pmaxsw xmm1, xmm12",
+			n: &NodeImpl{
+				Instruction: PMAXSW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX12,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0xee, 0xcc},
+		},
+		{
+			name: "pminsw xmm1, xmm12",
+			n: &NodeImpl{
+				Instruction: PMINSW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX12,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0xea, 0xcc},
+		},
+		{
+			name: "pcmpgtb xmm1, xmm12",
+			n: &NodeImpl{
+				Instruction: PCMPGTB,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX12,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x64, 0xcc},
+		},
+		{
+			name: "pminsb xmm1, xmm12",
+			n: &NodeImpl{
+				Instruction: PMINSB,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX12,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x38, 0x38, 0xcc},
+		},
+		{
+			name: "pmaxsb xmm1, xmm2",
+			n: &NodeImpl{
+				Instruction: PMAXSB,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX2,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0x38, 0x3c, 0xca},
+		},
+		{
+			name: "pminud xmm1, xmm2",
+			n: &NodeImpl{
+				Instruction: PMINUD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX2,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0x38, 0x3b, 0xca},
+		},
+		{
+			name: "pminuw xmm1, xmm2",
+			n: &NodeImpl{
+				Instruction: PMINUW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX2,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0x38, 0x3a, 0xca},
+		},
+		{
+			name: "pminub xmm1, xmm2",
+			n: &NodeImpl{
+				Instruction: PMINUB,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX2,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0xda, 0xca},
+		},
+		{
+			name: "pmaxud xmm1, xmm2",
+			n: &NodeImpl{
+				Instruction: PMAXUD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX2,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0x38, 0x3f, 0xca},
+		},
+		{
+			name: "pmaxuw xmm1, xmm2",
+			n: &NodeImpl{
+				Instruction: PMAXUW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX2,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0x38, 0x3e, 0xca},
+		},
+		{
+			name: "pmaxub xmm1, xmm2",
+			n: &NodeImpl{
+				Instruction: PMAXUB,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX2,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0xde, 0xca},
+		},
+		{
+			name: "pcmpgtw xmm1, xmm2",
+			n: &NodeImpl{
+				Instruction: PCMPGTW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX2,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0x65, 0xca},
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/asm/arm64/consts_test.go b/internal/asm/arm64/consts_test.go
new file mode 100644
index 00000000..93410f38
--- /dev/null
+++ b/internal/asm/arm64/consts_test.go
@@ -0,0 +1,15 @@
+package arm64
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/asm"
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+// TestInstructionName ensures that all the instruction's name is defined.
+func TestInstructionName(t *testing.T) {
+	for inst := asm.Instruction(0); inst < instructionEnd; inst++ {
+		require.NotEqual(t, "", InstructionName(inst))
+	}
+}
diff --git a/internal/engine/compiler/compiler_test.go b/internal/engine/compiler/compiler_test.go
index 8f916442..bfe0d211 100644
--- a/internal/engine/compiler/compiler_test.go
+++ b/internal/engine/compiler/compiler_test.go
@@ -13,7 +13,7 @@ import (
 )
 
 func TestMain(m *testing.M) {
-	if !platform.IsSupported() {
+	if !platform.CompilerSupported() {
 		os.Exit(0)
 	}
 	os.Exit(m.Run())
diff --git a/internal/engine/compiler/compiler_vec_test.go b/internal/engine/compiler/compiler_vec_test.go
index 3216a914..a658d72d 100644
--- a/internal/engine/compiler/compiler_vec_test.go
+++ b/internal/engine/compiler/compiler_vec_test.go
@@ -2838,3 +2838,439 @@ func TestCompiler_compileV128Shr(t *testing.T) {
 		})
 	}
 }
+
+func i16x8(i1, i2, i3, i4, i5, i6, i7, i8 uint16) (ret [16]byte) {
+	binary.LittleEndian.PutUint16(ret[0:], i1)
+	binary.LittleEndian.PutUint16(ret[2:], i2)
+	binary.LittleEndian.PutUint16(ret[4:], i3)
+	binary.LittleEndian.PutUint16(ret[6:], i4)
+	binary.LittleEndian.PutUint16(ret[8:], i5)
+	binary.LittleEndian.PutUint16(ret[10:], i6)
+	binary.LittleEndian.PutUint16(ret[12:], i7)
+	binary.LittleEndian.PutUint16(ret[14:], i8)
+	return
+}
+
+func i32x4(i1, i2, i3, i4 uint32) (ret [16]byte) {
+	binary.LittleEndian.PutUint32(ret[0:], i1)
+	binary.LittleEndian.PutUint32(ret[4:], i2)
+	binary.LittleEndian.PutUint32(ret[8:], i3)
+	binary.LittleEndian.PutUint32(ret[12:], i4)
+	return
+}
+
+func f32x4(f1, f2, f3, f4 float32) (ret [16]byte) {
+	binary.LittleEndian.PutUint32(ret[0:], math.Float32bits(f1))
+	binary.LittleEndian.PutUint32(ret[4:], math.Float32bits(f2))
+	binary.LittleEndian.PutUint32(ret[8:], math.Float32bits(f3))
+	binary.LittleEndian.PutUint32(ret[12:], math.Float32bits(f4))
+	return
+}
+
+func i64x2(i1, i2 uint64) (ret [16]byte) {
+	binary.LittleEndian.PutUint64(ret[0:], i1)
+	binary.LittleEndian.PutUint64(ret[8:], i2)
+	return
+}
+
+func f64x2(f1, f2 float64) (ret [16]byte) {
+	binary.LittleEndian.PutUint64(ret[0:], math.Float64bits(f1))
+	binary.LittleEndian.PutUint64(ret[8:], math.Float64bits(f2))
+	return
+}
+
+func TestCompiler_compileV128Cmp(t *testing.T) {
+	if runtime.GOARCH != "amd64" {
+		// TODO: implement on amd64.
+		t.Skip()
+	}
+
+	tests := []struct {
+		name        string
+		cmpType     wazeroir.V128CmpType
+		x1, x2, exp [16]byte
+	}{
+		{
+			name:    "f32x4 eq",
+			cmpType: wazeroir.V128CmpTypeF32x4Eq,
+			x1:      f32x4(1.0, -123.123, 0, 3214231),
+			x2:      f32x4(0, 0, 0, 0),
+			exp:     [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0},
+		},
+		{
+			name:    "f32x4 ne",
+			cmpType: wazeroir.V128CmpTypeF32x4Ne,
+			x1:      f32x4(1.0, -123.123, 123, 3214231),
+			x2:      f32x4(2.0, 213123123.1231, 123, 0),
+			exp:     [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:    "f32x4 lt",
+			cmpType: wazeroir.V128CmpTypeF32x4Lt,
+			x1:      f32x4(2.0, -123.123, 1234, 3214231),
+			x2:      f32x4(2.0, 213123123.1231, 123, 0),
+			exp:     [16]byte{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
+		},
+		{
+			name:    "f32x4 le",
+			cmpType: wazeroir.V128CmpTypeF32x4Le,
+			x1:      f32x4(2.0, -123.123, 1234, 3214231),
+			x2:      f32x4(2.0, 213123123.1231, 123, 0),
+			exp:     [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
+		},
+		{
+			name:    "f32x4 gt",
+			cmpType: wazeroir.V128CmpTypeF32x4Gt,
+			x1:      f32x4(2.0, -123.123, 1234, 3214231),
+			x2:      f32x4(2.0, 213123123.1231, 123, 0),
+			exp:     [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:    "f32x4 ge",
+			cmpType: wazeroir.V128CmpTypeF32x4Ge,
+			x1:      f32x4(2.0, -123.123, 1234, 3214231),
+			x2:      f32x4(2.0, 213123123.1231, 123, 0),
+			exp:     [16]byte{0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:    "f64x2 eq",
+			cmpType: wazeroir.V128CmpTypeF64x2Eq,
+			x1:      f64x2(1.0, -123.12412),
+			x2:      f64x2(1.0, 123.123124),
+			exp:     [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
+		},
+		{
+			name:    "f64x2 ne",
+			cmpType: wazeroir.V128CmpTypeF64x2Ne,
+			x1:      f64x2(1.0, -123.12412),
+			x2:      f64x2(1.0, 123.123124),
+			exp:     [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:    "f64x2 lt",
+			cmpType: wazeroir.V128CmpTypeF64x2Lt,
+			x1:      f64x2(-123, math.Inf(-1)),
+			x2:      f64x2(-123, -1234515),
+			exp:     [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:    "f64x2 le",
+			cmpType: wazeroir.V128CmpTypeF64x2Le,
+			x1:      f64x2(-123, 123),
+			x2:      f64x2(-123, math.MaxFloat64),
+			exp:     [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:    "f64x2 gt",
+			cmpType: wazeroir.V128CmpTypeF64x2Gt,
+			x1:      f64x2(math.MaxFloat64, -123.0),
+			x2:      f64x2(123, -123.0),
+			exp: [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0, 0, 0, 0, 0, 0, 0, 0},
+		},
+		{
+			name:    "f64x2 ge",
+			cmpType: wazeroir.V128CmpTypeF64x2Ge,
+			x1:      f64x2(math.MaxFloat64, -123.0),
+			x2:      f64x2(123, -123.0),
+			exp: [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+		{
+			name:    "i8x16 eq",
+			cmpType: wazeroir.V128CmpTypeI8x16Eq,
+			x1:      [16]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1},
+			x2:      [16]byte{1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0},
+			exp:     [16]byte{0, 0xff, 0, 0xff, 0xff, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xff, 0xff, 0},
+		},
+		{
+			name:    "i8x16 ne",
+			cmpType: wazeroir.V128CmpTypeI8x16Ne,
+			x1:      [16]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1},
+			x2:      [16]byte{1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0},
+			exp:     [16]byte{0xff, 0, 0xff, 0, 0, 0xff, 0, 0, 0, 0, 0, 0, 0xff, 0, 0, 0xff},
+		},
+		{
+			name:    "i8x16 lt_s",
+			cmpType: wazeroir.V128CmpTypeI8x16LtS,
+			x1:      [16]byte{0: i8ToU8(-1), 15: 0},
+			x2:      [16]byte{0: 0x7f, 15: i8ToU8(-1)},
+			exp:     [16]byte{0: 0xff},
+		},
+		{
+			name:    "i8x16 lt_u",
+			cmpType: wazeroir.V128CmpTypeI8x16LtU,
+			x1:      [16]byte{0: 0xff, 15: 0},
+			x2:      [16]byte{0: 0x7f, 15: 0xff},
+			exp:     [16]byte{15: 0xff},
+		},
+		{
+			name:    "i8x16 gt_s",
+			cmpType: wazeroir.V128CmpTypeI8x16GtS,
+			x1:      [16]byte{0: i8ToU8(-1), 15: 0},
+			x2:      [16]byte{0: 0x7f, 15: i8ToU8(-1)},
+			exp:     [16]byte{15: 0xff},
+		},
+		{
+			name:    "i8x16 gt_u",
+			cmpType: wazeroir.V128CmpTypeI8x16GtU,
+			x1:      [16]byte{0: 0xff, 15: 0},
+			x2:      [16]byte{0: 0x7f, 15: 0xff},
+			exp:     [16]byte{0: 0xff},
+		},
+		{
+			name:    "i8x16 le_s",
+			cmpType: wazeroir.V128CmpTypeI8x16LeS,
+			x1:      [16]byte{i8ToU8(-1), 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, i8ToU8(-1)},
+			x2:      [16]byte{0: 0x7f, 15: i8ToU8(-1)},
+			exp:     [16]byte{0: 0xff, 15: 0xff},
+		},
+		{
+			name:    "i8x16 le_u",
+			cmpType: wazeroir.V128CmpTypeI8x16LeU,
+			x1:      [16]byte{0x80, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xff},
+			x2:      [16]byte{0: 0x7f, 5: 0x1, 15: 0xff},
+			exp:     [16]byte{5: 0xff, 15: 0xff},
+		},
+		{
+			name:    "i8x16 ge_s",
+			cmpType: wazeroir.V128CmpTypeI8x16GeS,
+			x1:      [16]byte{0: 0x7f, 15: i8ToU8(-1)},
+			x2:      [16]byte{i8ToU8(-1), 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, i8ToU8(-1)},
+			exp:     [16]byte{0: 0xff, 15: 0xff},
+		},
+		{
+			name:    "i8x16 ge_u",
+			cmpType: wazeroir.V128CmpTypeI8x16GeU,
+			x1:      [16]byte{0: 0x7f, 3: 0xe, 15: 0xff},
+			x2:      [16]byte{0xff, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0xff},
+			exp:     [16]byte{3: 0xff, 15: 0xff},
+		},
+		{
+			name:    "i16x8 eq",
+			cmpType: wazeroir.V128CmpTypeI16x8Eq,
+			x1:      i16x8(0, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0xffff, 0, 0xffff, 0xffff, 0, 0xffff, 0xffff, 0),
+		},
+		{
+			name:    "i8x16 ne",
+			cmpType: wazeroir.V128CmpTypeI16x8Ne,
+			x1:      i16x8(0, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0, 0xffff, 0, 0, 0xffff, 0, 0, 0xffff),
+		},
+		{
+			name:    "i8x16 lt_s",
+			cmpType: wazeroir.V128CmpTypeI16x8LtS,
+			x1:      i16x8(0xffff, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0xffff, 0, 0, 0, 0xffff, 0, 0, 0),
+		},
+		{
+			name:    "i8x16 lt_u",
+			cmpType: wazeroir.V128CmpTypeI16x8LtU,
+			x1:      i16x8(0xffff, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0, 0, 0, 0, 0xffff, 0, 0, 0),
+		},
+		{
+			name:    "i8x16 gt_s",
+			cmpType: wazeroir.V128CmpTypeI16x8GtS,
+			x1:      i16x8(0, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0xffff, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0xffff, 0xffff, 0, 0, 0, 0, 0, 0xffff),
+		},
+		{
+			name:    "i8x16 gt_u",
+			cmpType: wazeroir.V128CmpTypeI16x8GtU,
+			x1:      i16x8(0, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0xffff, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0, 0xffff, 0, 0, 0, 0, 0, 0xffff),
+		},
+		{
+			name:    "i8x16 le_s",
+			cmpType: wazeroir.V128CmpTypeI16x8LeS,
+			x1:      i16x8(0xffff, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0xffff, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0),
+		},
+		{
+			name:    "i8x16 le_u",
+			cmpType: wazeroir.V128CmpTypeI16x8LeU,
+			x1:      i16x8(0xffff, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0),
+		},
+		{
+			name:    "i8x16 ge_s",
+			cmpType: wazeroir.V128CmpTypeI16x8GeS,
+			x1:      i16x8(0, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0xffff, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0xffff, 0xffff, 0xffff, 0xffff, 0, 0xffff, 0xffff, 0xffff),
+		},
+		{
+			name:    "i8x16 ge_u",
+			cmpType: wazeroir.V128CmpTypeI16x8GeU,
+			x1:      i16x8(0, 1, 0, 1, 0, 1, 0, 1),
+			x2:      i16x8(0xffff, 0, 0, 1, 1, 1, 0, 0),
+			exp:     i16x8(0, 0xffff, 0xffff, 0xffff, 0, 0xffff, 0xffff, 0xffff),
+		},
+		{
+			name:    "i32x4 eq",
+			cmpType: wazeroir.V128CmpTypeI32x4Eq,
+			x1:      i32x4(0, 1, 1, 0),
+			x2:      i32x4(0, 1, 0, 1),
+			exp:     i32x4(0xffffffff, 0xffffffff, 0, 0),
+		},
+		{
+			name:    "i32x4 ne",
+			cmpType: wazeroir.V128CmpTypeI32x4Ne,
+			x1:      i32x4(0, 1, 1, 0),
+			x2:      i32x4(0, 1, 0, 1),
+			exp:     i32x4(0, 0, 0xffffffff, 0xffffffff),
+		},
+		{
+			name:    "i32x4 lt_s",
+			cmpType: wazeroir.V128CmpTypeI32x4LtS,
+			x1:      i32x4(0xffffffff, 1, 1, 0),
+			x2:      i32x4(0, 1, 0, 1),
+			exp:     i32x4(0xffffffff, 0, 0, 0xffffffff),
+		},
+		{
+			name:    "i32x4 lt_u",
+			cmpType: wazeroir.V128CmpTypeI32x4LtU,
+			x1:      i32x4(0xffffffff, 1, 1, 0),
+			x2:      i32x4(0, 1, 0, 1),
+			exp:     i32x4(0, 0, 0, 0xffffffff),
+		},
+		{
+			name:    "i32x4 gt_s",
+			cmpType: wazeroir.V128CmpTypeI32x4GtS,
+			x1:      i32x4(0, 1, 1, 1),
+			x2:      i32x4(0xffffffff, 1, 0, 0),
+			exp:     i32x4(0xffffffff, 0, 0xffffffff, 0xffffffff),
+		},
+		{
+			name:    "i32x4 gt_u",
+			cmpType: wazeroir.V128CmpTypeI32x4GtU,
+			x1:      i32x4(0, 1, 1, 1),
+			x2:      i32x4(0xffffffff, 1, 0, 0),
+			exp:     i32x4(0, 0, 0xffffffff, 0xffffffff),
+		},
+		{
+			name:    "i32x4 le_s",
+			cmpType: wazeroir.V128CmpTypeI32x4LeS,
+			x1:      i32x4(0xffffffff, 1, 1, 0),
+			x2:      i32x4(0, 1, 0, 1),
+			exp:     i32x4(0xffffffff, 0xffffffff, 0, 0xffffffff),
+		},
+		{
+			name:    "i32x4 le_u",
+			cmpType: wazeroir.V128CmpTypeI32x4LeU,
+			x1:      i32x4(0xffffffff, 1, 1, 0),
+			x2:      i32x4(0, 1, 0, 1),
+			exp:     i32x4(0, 0xffffffff, 0, 0xffffffff),
+		},
+		{
+			name:    "i32x4 ge_s",
+			cmpType: wazeroir.V128CmpTypeI32x4GeS,
+			x1:      i32x4(0, 1, 1, 0),
+			x2:      i32x4(0xffffffff, 1, 0, 1),
+			exp:     i32x4(0xffffffff, 0xffffffff, 0xffffffff, 0),
+		},
+		{
+			name:    "i32x4 ge_u",
+			cmpType: wazeroir.V128CmpTypeI32x4GeU,
+			x1:      i32x4(0, 1, 1, 0),
+			x2:      i32x4(0xffffffff, 1, 0, 1),
+			exp:     i32x4(0, 0xffffffff, 0xffffffff, 0),
+		},
+		{
+			name:    "i64x2 eq",
+			cmpType: wazeroir.V128CmpTypeI64x2Eq,
+			x1:      i64x2(1, 0),
+			x2:      i64x2(0, 0),
+			exp:     i64x2(0, 0xffffffffffffffff),
+		},
+		{
+			name:    "i64x2 ne",
+			cmpType: wazeroir.V128CmpTypeI64x2Ne,
+			x1:      i64x2(1, 0),
+			x2:      i64x2(0, 0),
+			exp:     i64x2(0xffffffffffffffff, 0),
+		},
+		{
+			name:    "i64x2 lt_s",
+			cmpType: wazeroir.V128CmpTypeI64x2LtS,
+			x1:      i64x2(0xffffffffffffffff, 0),
+			x2:      i64x2(0, 0),
+			exp:     i64x2(0xffffffffffffffff, 0),
+		},
+		{
+			name:    "i64x2 gt_s",
+			cmpType: wazeroir.V128CmpTypeI64x2GtS,
+			x1:      i64x2(123, 0),
+			x2:      i64x2(123, 0xffffffffffffffff),
+			exp:     i64x2(0, 0xffffffffffffffff),
+		},
+		{
+			name:    "i64x2 le_s",
+			cmpType: wazeroir.V128CmpTypeI64x2LeS,
+			x1:      i64x2(123, 0xffffffffffffffff),
+			x2:      i64x2(123, 0),
+			exp:     i64x2(0xffffffffffffffff, 0xffffffffffffffff),
+		},
+		{
+			name:    "i64x2 ge_s",
+			cmpType: wazeroir.V128CmpTypeI64x2GeS,
+			x1:      i64x2(123, 0),
+			x2:      i64x2(123, 0xffffffffffffffff),
+			exp:     i64x2(0xffffffffffffffff, 0xffffffffffffffff),
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			env := newCompilerEnvironment()
+			compiler := env.requireNewCompiler(t, newCompiler,
+				&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
+
+			err := compiler.compilePreamble()
+			require.NoError(t, err)
+
+			err = compiler.compileV128Const(&wazeroir.OperationV128Const{
+				Lo: binary.LittleEndian.Uint64(tc.x1[:8]),
+				Hi: binary.LittleEndian.Uint64(tc.x1[8:]),
+			})
+			require.NoError(t, err)
+
+			err = compiler.compileV128Const(&wazeroir.OperationV128Const{
+				Lo: binary.LittleEndian.Uint64(tc.x2[:8]),
+				Hi: binary.LittleEndian.Uint64(tc.x2[8:]),
+			})
+			require.NoError(t, err)
+
+			err = compiler.compileV128Cmp(&wazeroir.OperationV128Cmp{Type: tc.cmpType})
+			require.NoError(t, err)
+
+			require.Equal(t, uint64(2), compiler.runtimeValueLocationStack().sp)
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+
+			err = compiler.compileReturnFunction()
+			require.NoError(t, err)
+
+			// Generate and run the code under test.
+			code, _, _, err := compiler.compile()
+			require.NoError(t, err)
+			env.exec(code)
+
+			lo, hi := env.stackTopAsV128()
+			var actual [16]byte
+			binary.LittleEndian.PutUint64(actual[:8], lo)
+			binary.LittleEndian.PutUint64(actual[8:], hi)
+			require.Equal(t, tc.exp, actual)
+		})
+	}
+}
diff --git a/internal/engine/compiler/engine_test.go b/internal/engine/compiler/engine_test.go
index 7e5f70cb..6ea3bfa0 100644
--- a/internal/engine/compiler/engine_test.go
+++ b/internal/engine/compiler/engine_test.go
@@ -176,7 +176,7 @@ func TestCompiler_ModuleEngine_Memory(t *testing.T) {
 
 // requireSupportedOSArch is duplicated also in the platform package to ensure no cyclic dependency.
 func requireSupportedOSArch(t *testing.T) {
-	if !platform.IsSupported() {
+	if !platform.CompilerSupported() {
 		t.Skip()
 	}
 }
diff --git a/internal/integration_test/engine/adhoc_test.go b/internal/integration_test/engine/adhoc_test.go
index f3219c50..7bf94376 100644
--- a/internal/integration_test/engine/adhoc_test.go
+++ b/internal/integration_test/engine/adhoc_test.go
@@ -48,7 +48,7 @@ var tests = map[string]func(t *testing.T, r wazero.Runtime){
 }
 
 func TestEngineCompiler(t *testing.T) {
-	if !platform.IsSupported() {
+	if !platform.CompilerSupported() {
 		t.Skip()
 	}
 	runAllTests(t, tests, wazero.NewRuntimeConfigCompiler())
diff --git a/internal/integration_test/engine/hammer_test.go b/internal/integration_test/engine/hammer_test.go
index 06a2844b..16faca90 100644
--- a/internal/integration_test/engine/hammer_test.go
+++ b/internal/integration_test/engine/hammer_test.go
@@ -19,7 +19,7 @@ var hammers = map[string]func(t *testing.T, r wazero.Runtime){
 }
 
 func TestEngineCompiler_hammer(t *testing.T) {
-	if !platform.IsSupported() {
+	if !platform.CompilerSupported() {
 		t.Skip()
 	}
 	runAllTests(t, hammers, wazero.NewRuntimeConfigCompiler())
diff --git a/internal/integration_test/spectest/spectest.go b/internal/integration_test/spectest/spectest.go
index 32bd0afd..1281bca2 100644
--- a/internal/integration_test/spectest/spectest.go
+++ b/internal/integration_test/spectest/spectest.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/u64"
 	"github.com/tetratelabs/wazero/internal/wasm"
@@ -344,7 +345,7 @@ func addSpectestModule(t *testing.T, s *wasm.Store, ns *wasm.Namespace) {
 	err = s.Engine.CompileModule(testCtx, mod)
 	require.NoError(t, err)
 
-	_, err = s.Instantiate(testCtx, ns, mod, mod.NameSection.ModuleName, wasm.DefaultSysContext(), nil)
+	_, err = s.Instantiate(testCtx, ns, mod, mod.NameSection.ModuleName, sys.DefaultContext(), nil)
 	require.NoError(t, err)
 }
 
diff --git a/internal/integration_test/spectest/v2/spec_test.go b/internal/integration_test/spectest/v2/spec_test.go
index 663e179c..7ebf37d9 100644
--- a/internal/integration_test/spectest/v2/spec_test.go
+++ b/internal/integration_test/spectest/v2/spec_test.go
@@ -21,7 +21,7 @@ var testcases embed.FS
 const enabledFeatures = wasm.Features20220419
 
 func TestCompiler(t *testing.T) {
-	if !platform.IsSupported() {
+	if !platform.CompilerSupported() {
 		t.Skip()
 	}
 
@@ -34,7 +34,9 @@ func TestCompiler(t *testing.T) {
 				"simd_load_splat.json", "simd_load_zero.json", "simd_store.json", "simd_store16_lane.json",
 				"simd_store32_lane.json", "simd_store64_lane.json", "simd_store8_lane.json":
 				return true
-			case "simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json":
+			case "simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json",
+				"simd_i8x16_cmp.json", "simd_i16x8_cmp.json", "simd_i32x4_cmp.json", "simd_i64x2_cmp.json",
+				"simd_f32x4_cmp.json", "simd_f64x2_cmp.json":
 				// TODO: implement on arm64.
 				return runtime.GOARCH == "amd64"
 			default:
@@ -54,7 +56,8 @@ func TestInterpreter(t *testing.T) {
 				"simd_load32_lane.json", "simd_load64_lane.json", "simd_load8_lane.json", "simd_lane.json",
 				"simd_load_extend.json", "simd_load_splat.json", "simd_load_zero.json", "simd_store.json",
 				"simd_store16_lane.json", "simd_store32_lane.json", "simd_store64_lane.json", "simd_store8_lane.json",
-				"simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json":
+				"simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json", "simd_i8x16_cmp.json", "simd_i16x8_cmp.json",
+				"simd_i32x4_cmp.json", "simd_i64x2_cmp.json", "simd_f32x4_cmp.json", "simd_f64x2_cmp.json":
 				return true
 			default:
 				return false // others not supported, yet!
diff --git a/internal/integration_test/vs/time/go.mod b/internal/integration_test/vs/time/go.mod
new file mode 100644
index 00000000..683367e0
--- /dev/null
+++ b/internal/integration_test/vs/time/go.mod
@@ -0,0 +1,9 @@
+module github.com/tetratelabs/wazero/internal/integration_test/vs/clock
+
+go 1.17
+
+require github.com/tetratelabs/wazero v0.0.0
+
+require golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
+
+replace github.com/tetratelabs/wazero => ../../../..
diff --git a/internal/integration_test/vs/time/go.sum b/internal/integration_test/vs/time/go.sum
new file mode 100644
index 00000000..af14a66e
--- /dev/null
+++ b/internal/integration_test/vs/time/go.sum
@@ -0,0 +1,2 @@
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/internal/integration_test/vs/time/time_test.go b/internal/integration_test/vs/time/time_test.go
new file mode 100644
index 00000000..67e310ad
--- /dev/null
+++ b/internal/integration_test/vs/time/time_test.go
@@ -0,0 +1,58 @@
+// Package time benchmarks shows ways to implementing the platform clock more
+// efficiently. As long as CGO is available, all platforms can use
+// `runtime.nanotime` to more efficiently implement sys.Nanotime vs using
+// time.Since or x/sys.
+//
+// While results would be more impressive, this doesn't show how to use
+// `runtime.walltime` to avoid the double-performance vs using time.Now. The
+// corresponding function only exists in darwin, so prevents this benchmark
+// from running on other platforms.
+package time
+
+import (
+	"testing"
+	"time"
+	_ "unsafe" // for go:linkname
+
+	"golang.org/x/sys/unix"
+)
+
+//go:noescape
+//go:linkname nanotime runtime.nanotime
+func nanotime() int64
+
+func BenchmarkClock(b *testing.B) {
+	b.Run("time.Now", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_ = time.Now()
+		}
+	})
+	b.Run("ClockGettime(CLOCK_REALTIME)", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			var now unix.Timespec
+			if err := unix.ClockGettime(unix.CLOCK_REALTIME, &now); err != nil {
+				b.Fatal(err)
+			}
+		}
+	})
+
+	base := time.Now()
+	b.Run("time.Since", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_ = time.Since(base)
+		}
+	})
+	b.Run("runtime.nanotime", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_ = nanotime()
+		}
+	})
+	b.Run("ClockGettime(CLOCK_MONOTONIC)", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			var tick unix.Timespec
+			if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &tick); err != nil {
+				b.Fatal(err)
+			}
+		}
+	})
+}
diff --git a/internal/platform/mmap_test.go b/internal/platform/mmap_test.go
index 6aeb7a6a..ac7af344 100644
--- a/internal/platform/mmap_test.go
+++ b/internal/platform/mmap_test.go
@@ -11,7 +11,7 @@ import (
 var testCode, _ = io.ReadAll(io.LimitReader(rand.Reader, 8*1024))
 
 func Test_MmapCodeSegment(t *testing.T) {
-	if !IsSupported() {
+	if !CompilerSupported() {
 		t.Skip()
 	}
 
@@ -30,7 +30,7 @@ func Test_MmapCodeSegment(t *testing.T) {
 }
 
 func Test_MunmapCodeSegment(t *testing.T) {
-	if !IsSupported() {
+	if !CompilerSupported() {
 		t.Skip()
 	}
 
diff --git a/internal/platform/time_test.go b/internal/platform/time_test.go
new file mode 100644
index 00000000..34358291
--- /dev/null
+++ b/internal/platform/time_test.go
@@ -0,0 +1,47 @@
+package platform
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+	"github.com/tetratelabs/wazero/sys"
+)
+
+func Test_Walltime(t *testing.T) {
+	now := time.Now().Unix()
+	sec, nsec := Walltime(context.Background())
+
+	// Loose test that the second variant is close to now.
+	// The only thing that could flake this is a time adjustment during the test.
+	require.True(t, now == sec || now == sec-1)
+
+	// Verify bounds of nanosecond fraction as measuring it precisely won't work.
+	require.True(t, nsec >= 0)
+	require.True(t, nsec < int32(time.Second.Nanoseconds()))
+}
+
+func Test_Nanotime(t *testing.T) {
+	tests := []struct {
+		name     string
+		nanotime sys.Nanotime
+	}{
+		{"Nanotime", Nanotime},
+		{"nanotimePortable", func(ctx context.Context) int64 {
+			return nanotimePortable()
+		}},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			delta := time.Since(nanoBase).Nanoseconds()
+			nanos := Nanotime(context.Background())
+
+			// It takes more than a nanosecond to make the two clock readings required
+			// to implement time.Now. Hence, delta will always be less than nanos.
+			require.True(t, delta <= nanos)
+		})
+	}
+}
diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go
new file mode 100644
index 00000000..53fd38bc
--- /dev/null
+++ b/internal/sys/sys_test.go
@@ -0,0 +1,289 @@
+package sys
+
+import (
+	"bytes"
+	"crypto/rand"
+	"io"
+	"testing"
+	"time"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+	"github.com/tetratelabs/wazero/sys"
+)
+
+func TestDefaultSysContext(t *testing.T) {
+	sysCtx, err := NewContext(
+		0,      // max
+		nil,    // args
+		nil,    // environ
+		nil,    // stdin
+		nil,    // stdout
+		nil,    // stderr
+		nil,    // randSource
+		nil, 0, // walltime, walltimeResolution
+		nil, 0, // nanotime, nanotimeResolution
+		nil, // openedFiles
+	)
+	require.NoError(t, err)
+
+	require.Nil(t, sysCtx.Args())
+	require.Zero(t, sysCtx.ArgsSize())
+	require.Nil(t, sysCtx.Environ())
+	require.Zero(t, sysCtx.EnvironSize())
+	require.Equal(t, eofReader{}, sysCtx.Stdin())
+	require.Equal(t, io.Discard, sysCtx.Stdout())
+	require.Equal(t, io.Discard, sysCtx.Stderr())
+	require.Equal(t, &wt, sysCtx.walltime) // To compare functions, we can only compare pointers.
+	require.Equal(t, sys.ClockResolution(1_000), sysCtx.WalltimeResolution())
+	require.Equal(t, &nt, sysCtx.nanotime) // To compare functions, we can only compare pointers.
+	require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution())
+	require.Equal(t, rand.Reader, sysCtx.RandSource())
+	require.Equal(t, NewFSContext(map[uint32]*FileEntry{}), sysCtx.FS())
+}
+
+func TestNewContext_Args(t *testing.T) {
+	tests := []struct {
+		name         string
+		args         []string
+		maxSize      uint32
+		expectedSize uint32
+		expectedErr  string
+	}{
+		{
+			name:         "ok",
+			maxSize:      10,
+			args:         []string{"a", "bc"},
+			expectedSize: 5,
+		},
+		{
+			name:        "exceeds max count",
+			maxSize:     1,
+			args:        []string{"a", "bc"},
+			expectedErr: "args invalid: exceeds maximum count",
+		},
+		{
+			name:        "exceeds max size",
+			maxSize:     4,
+			args:        []string{"a", "bc"},
+			expectedErr: "args invalid: exceeds maximum size",
+		},
+		{
+			name:        "null character",
+			maxSize:     10,
+			args:        []string{"a", string([]byte{'b', 0})},
+			expectedErr: "args invalid: contains NUL character",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			sysCtx, err := NewContext(
+				tc.maxSize, // max
+				tc.args,
+				nil,                              // environ
+				bytes.NewReader(make([]byte, 0)), // stdin
+				nil,                              // stdout
+				nil,                              // stderr
+				nil,                              // randSource
+				nil, 0,                           // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
+			)
+			if tc.expectedErr == "" {
+				require.Nil(t, err)
+				require.Equal(t, tc.args, sysCtx.Args())
+				require.Equal(t, tc.expectedSize, sysCtx.ArgsSize())
+			} else {
+				require.EqualError(t, err, tc.expectedErr)
+			}
+		})
+	}
+}
+
+func TestNewContext_Environ(t *testing.T) {
+	tests := []struct {
+		name         string
+		environ      []string
+		maxSize      uint32
+		expectedSize uint32
+		expectedErr  string
+	}{
+		{
+			name:         "ok",
+			maxSize:      10,
+			environ:      []string{"a=b", "c=de"},
+			expectedSize: 9,
+		},
+		{
+			name:        "exceeds max count",
+			maxSize:     1,
+			environ:     []string{"a=b", "c=de"},
+			expectedErr: "environ invalid: exceeds maximum count",
+		},
+		{
+			name:        "exceeds max size",
+			maxSize:     4,
+			environ:     []string{"a=b", "c=de"},
+			expectedErr: "environ invalid: exceeds maximum size",
+		},
+		{
+			name:        "null character",
+			maxSize:     10,
+			environ:     []string{"a=b", string(append([]byte("c=d"), 0))},
+			expectedErr: "environ invalid: contains NUL character",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			sysCtx, err := NewContext(
+				tc.maxSize, // max
+				nil,        // args
+				tc.environ,
+				bytes.NewReader(make([]byte, 0)), // stdin
+				nil,                              // stdout
+				nil,                              // stderr
+				nil,                              // randSource
+				nil, 0,                           // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
+			)
+			if tc.expectedErr == "" {
+				require.Nil(t, err)
+				require.Equal(t, tc.environ, sysCtx.Environ())
+				require.Equal(t, tc.expectedSize, sysCtx.EnvironSize())
+			} else {
+				require.EqualError(t, err, tc.expectedErr)
+			}
+		})
+	}
+}
+
+func TestNewContext_Walltime(t *testing.T) {
+	tests := []struct {
+		name        string
+		time        *sys.Walltime
+		resolution  sys.ClockResolution
+		expectedErr string
+	}{
+		{
+			name:       "ok",
+			time:       &wt,
+			resolution: 3,
+		},
+		{
+			name:        "invalid resolution",
+			time:        &wt,
+			resolution:  0,
+			expectedErr: "invalid Walltime resolution: 0",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			sysCtx, err := NewContext(
+				0,   // max
+				nil, // args
+				nil,
+				nil,                    // stdin
+				nil,                    // stdout
+				nil,                    // stderr
+				nil,                    // randSource
+				tc.time, tc.resolution, // walltime, walltimeResolution
+				nil, 0, // nanotime, nanotimeResolution
+				nil, // openedFiles
+			)
+			if tc.expectedErr == "" {
+				require.Nil(t, err)
+				require.Equal(t, tc.time, sysCtx.walltime)
+				require.Equal(t, tc.resolution, sysCtx.WalltimeResolution())
+			} else {
+				require.EqualError(t, err, tc.expectedErr)
+			}
+		})
+	}
+}
+
+func TestNewContext_Nanotime(t *testing.T) {
+	tests := []struct {
+		name        string
+		time        *sys.Nanotime
+		resolution  sys.ClockResolution
+		expectedErr string
+	}{
+		{
+			name:       "ok",
+			time:       &nt,
+			resolution: 3,
+		},
+		{
+			name:        "invalid resolution",
+			time:        &nt,
+			resolution:  0,
+			expectedErr: "invalid Nanotime resolution: 0",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			sysCtx, err := NewContext(
+				0,   // max
+				nil, // args
+				nil,
+				nil,    // stdin
+				nil,    // stdout
+				nil,    // stderr
+				nil,    // randSource
+				nil, 0, // nanotime, nanotimeResolution
+				tc.time, tc.resolution, // nanotime, nanotimeResolution
+				nil, // openedFiles
+			)
+			if tc.expectedErr == "" {
+				require.Nil(t, err)
+				require.Equal(t, tc.time, sysCtx.nanotime)
+				require.Equal(t, tc.resolution, sysCtx.NanotimeResolution())
+			} else {
+				require.EqualError(t, err, tc.expectedErr)
+			}
+		})
+	}
+}
+
+func Test_clockResolutionInvalid(t *testing.T) {
+	tests := []struct {
+		name       string
+		resolution sys.ClockResolution
+		expected   bool
+	}{
+		{
+			name:       "ok",
+			resolution: 1,
+		},
+		{
+			name:       "zero",
+			resolution: 0,
+			expected:   true,
+		},
+		{
+			name:       "too big",
+			resolution: sys.ClockResolution(time.Hour.Nanoseconds() * 2),
+			expected:   true,
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			require.Equal(t, tc.expected, clockResolutionInvalid(tc.resolution))
+		})
+	}
+}
diff --git a/internal/wasm/call_context_test.go b/internal/wasm/call_context_test.go
index 5f0e18c1..d5f32d57 100644
--- a/internal/wasm/call_context_test.go
+++ b/internal/wasm/call_context_test.go
@@ -142,15 +142,15 @@ func TestCallContext_Close(t *testing.T) {
 		})
 	}
 
-	t.Run("calls SysContext.Close()", func(t *testing.T) {
-		sysCtx := DefaultSysContext()
+	t.Run("calls Context.Close()", func(t *testing.T) {
+		sysCtx := sys.DefaultContext()
 		sysCtx.FS().OpenFile(&sys.FileEntry{Path: "."})
 
 		m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx, nil)
 		require.NoError(t, err)
 
-		// We use side effects to determine if Close in fact called SysContext.Close (without repeating sys_test.go).
-		// One side effect of SysContext.Close is that it clears the openedFiles map. Verify our base case.
+		// We use side effects to determine if Close in fact called Context.Close (without repeating sys_test.go).
+		// One side effect of Context.Close is that it clears the openedFiles map. Verify our base case.
 		fsCtx := sysCtx.FS()
 		_, ok := fsCtx.OpenedFile(3)
 		require.True(t, ok, "sysCtx.openedFiles was empty")
@@ -168,7 +168,7 @@ func TestCallContext_Close(t *testing.T) {
 
 	t.Run("error closing", func(t *testing.T) {
 		// Right now, the only way to err closing the sys context is if a File.Close erred.
-		sysCtx := DefaultSysContext()
+		sysCtx := sys.DefaultContext()
 		sysCtx.FS().OpenFile(&sys.FileEntry{Path: ".", File: &testFile{errors.New("error closing")}})
 
 		m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx, nil)
diff --git a/internal/wasm/func_validation_test.go b/internal/wasm/func_validation_test.go
index 2b3fb8d5..4b7084d2 100644
--- a/internal/wasm/func_validation_test.go
+++ b/internal/wasm/func_validation_test.go
@@ -2974,6 +2974,54 @@ func TestModule_funcValidation_SIMD(t *testing.T) {
 		{name: OpcodeVecI64x2ShlName, body: vi2v(OpcodeVecI64x2Shl)},
 		{name: OpcodeVecI64x2ShrSName, body: vi2v(OpcodeVecI64x2ShrS)},
 		{name: OpcodeVecI64x2ShrUName, body: vi2v(OpcodeVecI64x2ShrU)},
+		{name: OpcodeVecI8x16EqName, body: vv2v(OpcodeVecI8x16Eq)},
+		{name: OpcodeVecI8x16NeName, body: vv2v(OpcodeVecI8x16Ne)},
+		{name: OpcodeVecI8x16LtSName, body: vv2v(OpcodeVecI8x16LtS)},
+		{name: OpcodeVecI8x16LtUName, body: vv2v(OpcodeVecI8x16LtU)},
+		{name: OpcodeVecI8x16GtSName, body: vv2v(OpcodeVecI8x16GtS)},
+		{name: OpcodeVecI8x16GtUName, body: vv2v(OpcodeVecI8x16GtU)},
+		{name: OpcodeVecI8x16LeSName, body: vv2v(OpcodeVecI8x16LeS)},
+		{name: OpcodeVecI8x16LeUName, body: vv2v(OpcodeVecI8x16LeU)},
+		{name: OpcodeVecI8x16GeSName, body: vv2v(OpcodeVecI8x16GeS)},
+		{name: OpcodeVecI8x16GeUName, body: vv2v(OpcodeVecI8x16GeU)},
+		{name: OpcodeVecI16x8EqName, body: vv2v(OpcodeVecI16x8Eq)},
+		{name: OpcodeVecI16x8NeName, body: vv2v(OpcodeVecI16x8Ne)},
+		{name: OpcodeVecI16x8LtSName, body: vv2v(OpcodeVecI16x8LtS)},
+		{name: OpcodeVecI16x8LtUName, body: vv2v(OpcodeVecI16x8LtU)},
+		{name: OpcodeVecI16x8GtSName, body: vv2v(OpcodeVecI16x8GtS)},
+		{name: OpcodeVecI16x8GtUName, body: vv2v(OpcodeVecI16x8GtU)},
+		{name: OpcodeVecI16x8LeSName, body: vv2v(OpcodeVecI16x8LeS)},
+		{name: OpcodeVecI16x8LeUName, body: vv2v(OpcodeVecI16x8LeU)},
+		{name: OpcodeVecI16x8GeSName, body: vv2v(OpcodeVecI16x8GeS)},
+		{name: OpcodeVecI16x8GeUName, body: vv2v(OpcodeVecI16x8GeU)},
+		{name: OpcodeVecI32x4EqName, body: vv2v(OpcodeVecI32x4Eq)},
+		{name: OpcodeVecI32x4NeName, body: vv2v(OpcodeVecI32x4Ne)},
+		{name: OpcodeVecI32x4LtSName, body: vv2v(OpcodeVecI32x4LtS)},
+		{name: OpcodeVecI32x4LtUName, body: vv2v(OpcodeVecI32x4LtU)},
+		{name: OpcodeVecI32x4GtSName, body: vv2v(OpcodeVecI32x4GtS)},
+		{name: OpcodeVecI32x4GtUName, body: vv2v(OpcodeVecI32x4GtU)},
+		{name: OpcodeVecI32x4LeSName, body: vv2v(OpcodeVecI32x4LeS)},
+		{name: OpcodeVecI32x4LeUName, body: vv2v(OpcodeVecI32x4LeU)},
+		{name: OpcodeVecI32x4GeSName, body: vv2v(OpcodeVecI32x4GeS)},
+		{name: OpcodeVecI32x4GeUName, body: vv2v(OpcodeVecI32x4GeU)},
+		{name: OpcodeVecI64x2EqName, body: vv2v(OpcodeVecI64x2Eq)},
+		{name: OpcodeVecI64x2NeName, body: vv2v(OpcodeVecI64x2Ne)},
+		{name: OpcodeVecI64x2LtSName, body: vv2v(OpcodeVecI64x2LtS)},
+		{name: OpcodeVecI64x2GtSName, body: vv2v(OpcodeVecI64x2GtS)},
+		{name: OpcodeVecI64x2LeSName, body: vv2v(OpcodeVecI64x2LeS)},
+		{name: OpcodeVecI64x2GeSName, body: vv2v(OpcodeVecI64x2GeS)},
+		{name: OpcodeVecF32x4EqName, body: vv2v(OpcodeVecF32x4Eq)},
+		{name: OpcodeVecF32x4NeName, body: vv2v(OpcodeVecF32x4Ne)},
+		{name: OpcodeVecF32x4LtName, body: vv2v(OpcodeVecF32x4Lt)},
+		{name: OpcodeVecF32x4GtName, body: vv2v(OpcodeVecF32x4Gt)},
+		{name: OpcodeVecF32x4LeName, body: vv2v(OpcodeVecF32x4Le)},
+		{name: OpcodeVecF32x4GeName, body: vv2v(OpcodeVecF32x4Ge)},
+		{name: OpcodeVecF64x2EqName, body: vv2v(OpcodeVecF64x2Eq)},
+		{name: OpcodeVecF64x2NeName, body: vv2v(OpcodeVecF64x2Ne)},
+		{name: OpcodeVecF64x2LtName, body: vv2v(OpcodeVecF64x2Lt)},
+		{name: OpcodeVecF64x2GtName, body: vv2v(OpcodeVecF64x2Gt)},
+		{name: OpcodeVecF64x2LeName, body: vv2v(OpcodeVecF64x2Le)},
+		{name: OpcodeVecF64x2GeName, body: vv2v(OpcodeVecF64x2Ge)},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/wasm/namespace_test.go b/internal/wasm/namespace_test.go
index 459fa19e..e91fc93a 100644
--- a/internal/wasm/namespace_test.go
+++ b/internal/wasm/namespace_test.go
@@ -162,7 +162,7 @@ func TestNamespace_CloseWithExitCode(t *testing.T) {
 	}
 
 	t.Run("error closing", func(t *testing.T) {
-		sysCtx := DefaultSysContext()
+		sysCtx := sys.DefaultContext()
 		sysCtx.FS().OpenFile(&sys.FileEntry{Path: ".", File: &testFile{errors.New("error closing")}})
 
 		ns, m1, m2 := newTestNamespace()
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index b49290a4..7db78348 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/leb128"
+	"github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/hammer"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/u64"
@@ -99,8 +100,8 @@ func TestStore_Instantiate(t *testing.T) {
 	)
 	require.NoError(t, err)
 
-	sys := DefaultSysContext()
-	mod, err := s.Instantiate(testCtx, ns, m, "", sys, nil)
+	sysCtx := sys.DefaultContext()
+	mod, err := s.Instantiate(testCtx, ns, m, "", sysCtx, nil)
 	require.NoError(t, err)
 	defer mod.Close(testCtx)
 
@@ -108,7 +109,7 @@ func TestStore_Instantiate(t *testing.T) {
 		require.Equal(t, ns.modules[""], mod.module)
 		require.Equal(t, ns.modules[""].Memory, mod.memory)
 		require.Equal(t, ns, mod.ns)
-		require.Equal(t, sys, mod.Sys)
+		require.Equal(t, sysCtx, mod.Sys)
 	})
 }
 
@@ -210,7 +211,7 @@ func TestStore_hammer(t *testing.T) {
 		N = 100
 	}
 	hammer.NewHammer(t, P, N).Run(func(name string) {
-		mod, instantiateErr := s.Instantiate(testCtx, ns, importingModule, name, DefaultSysContext(), nil)
+		mod, instantiateErr := s.Instantiate(testCtx, ns, importingModule, name, sys.DefaultContext(), nil)
 		require.NoError(t, instantiateErr)
 		require.NoError(t, mod.Close(testCtx))
 	}, nil)
diff --git a/internal/wasm/sys_test.go b/internal/wasm/sys_test.go
deleted file mode 100644
index 52b08953..00000000
--- a/internal/wasm/sys_test.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package wasm
-
-import (
-	"bytes"
-	"io"
-	"testing"
-
-	"github.com/tetratelabs/wazero/internal/testing/require"
-)
-
-func TestDefaultSysContext(t *testing.T) {
-	sys, err := NewSysContext(
-		0,   // max
-		nil, // args
-		nil, // environ
-		nil, // stdin
-		nil, // stdout
-		nil, // stderr
-		nil, // randSource
-		nil, // openedFiles
-	)
-	require.NoError(t, err)
-
-	require.Nil(t, sys.Args())
-	require.Zero(t, sys.ArgsSize())
-	require.Nil(t, sys.Environ())
-	require.Zero(t, sys.EnvironSize())
-	require.Equal(t, eofReader{}, sys.Stdin())
-	require.Equal(t, io.Discard, sys.Stdout())
-	require.Equal(t, io.Discard, sys.Stderr())
-	require.Equal(t, sys, DefaultSysContext())
-}
-
-func TestNewSysContext_Args(t *testing.T) {
-	tests := []struct {
-		name         string
-		args         []string
-		maxSize      uint32
-		expectedSize uint32
-		expectedErr  string
-	}{
-		{
-			name:         "ok",
-			maxSize:      10,
-			args:         []string{"a", "bc"},
-			expectedSize: 5,
-		},
-		{
-			name:        "exceeds max count",
-			maxSize:     1,
-			args:        []string{"a", "bc"},
-			expectedErr: "args invalid: exceeds maximum count",
-		},
-		{
-			name:        "exceeds max size",
-			maxSize:     4,
-			args:        []string{"a", "bc"},
-			expectedErr: "args invalid: exceeds maximum size",
-		},
-		{
-			name:        "null character",
-			maxSize:     10,
-			args:        []string{"a", string([]byte{'b', 0})},
-			expectedErr: "args invalid: contains NUL character",
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			sys, err := NewSysContext(
-				tc.maxSize, // max
-				tc.args,
-				nil,                              // environ
-				bytes.NewReader(make([]byte, 0)), // stdin
-				nil,                              // stdout
-				nil,                              // stderr
-				nil,                              // randSource
-				nil,                              // openedFiles
-			)
-			if tc.expectedErr == "" {
-				require.Nil(t, err)
-				require.Equal(t, tc.args, sys.Args())
-				require.Equal(t, tc.expectedSize, sys.ArgsSize())
-			} else {
-				require.EqualError(t, err, tc.expectedErr)
-			}
-		})
-	}
-}
-
-func TestNewSysContext_Environ(t *testing.T) {
-	tests := []struct {
-		name         string
-		environ      []string
-		maxSize      uint32
-		expectedSize uint32
-		expectedErr  string
-	}{
-		{
-			name:         "ok",
-			maxSize:      10,
-			environ:      []string{"a=b", "c=de"},
-			expectedSize: 9,
-		},
-		{
-			name:        "exceeds max count",
-			maxSize:     1,
-			environ:     []string{"a=b", "c=de"},
-			expectedErr: "environ invalid: exceeds maximum count",
-		},
-		{
-			name:        "exceeds max size",
-			maxSize:     4,
-			environ:     []string{"a=b", "c=de"},
-			expectedErr: "environ invalid: exceeds maximum size",
-		},
-		{
-			name:        "null character",
-			maxSize:     10,
-			environ:     []string{"a=b", string(append([]byte("c=d"), 0))},
-			expectedErr: "environ invalid: contains NUL character",
-		},
-	}
-
-	for _, tt := range tests {
-		tc := tt
-
-		t.Run(tc.name, func(t *testing.T) {
-			sys, err := NewSysContext(
-				tc.maxSize, // max
-				nil,        // args
-				tc.environ,
-				bytes.NewReader(make([]byte, 0)), // stdin
-				nil,                              // stdout
-				nil,                              // stderr
-				nil,                              // randSource
-				nil,                              // openedFiles
-			)
-			if tc.expectedErr == "" {
-				require.Nil(t, err)
-				require.Equal(t, tc.environ, sys.Environ())
-				require.Equal(t, tc.expectedSize, sys.EnvironSize())
-			} else {
-				require.EqualError(t, err, tc.expectedErr)
-			}
-		})
-	}
-}
diff --git a/internal/wazeroir/operations_test.go b/internal/wazeroir/operations_test.go
new file mode 100644
index 00000000..ba5c9deb
--- /dev/null
+++ b/internal/wazeroir/operations_test.go
@@ -0,0 +1,14 @@
+package wazeroir
+
+import (
+	"testing"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+)
+
+// TestInstructionName ensures that all the operation kind's stringer is well-defined.
+func TestOperationKind_String(t *testing.T) {
+	for k := OperationKind(0); k < operationKindEnd; k++ {
+		require.NotEqual(t, "", k.String())
+	}
+}
diff --git a/wasi_snapshot_preview1/wasi_bench_test.go b/wasi_snapshot_preview1/wasi_bench_test.go
index 9e2e545c..61217a9e 100644
--- a/wasi_snapshot_preview1/wasi_bench_test.go
+++ b/wasi_snapshot_preview1/wasi_bench_test.go
@@ -3,6 +3,7 @@ package wasi_snapshot_preview1
 import (
 	"testing"
 
+	"github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/wasm"
 )
@@ -57,7 +58,7 @@ func Benchmark_EnvironGet(b *testing.B) {
 	})
 }
 
-func newModule(buf []byte, sys *wasm.SysContext) *wasm.CallContext {
+func newModule(buf []byte, sys *sys.Context) *wasm.CallContext {
 	return wasm.NewCallContext(nil, &wasm.ModuleInstance{
 		Memory: &wasm.MemoryInstance{Min: 1, Buffer: buf},
 	}, sys)
diff --git a/wasi_snapshot_preview1/wasi_test.go b/wasi_snapshot_preview1/wasi_test.go
index c0b992f9..534846da 100644
--- a/wasi_snapshot_preview1/wasi_test.go
+++ b/wasi_snapshot_preview1/wasi_test.go
@@ -15,11 +15,11 @@ import (
 	"testing"
 	"testing/fstest"
 	"testing/iotest"
-	"time"
 
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/experimental"
+	"github.com/tetratelabs/wazero/internal/platform"
 	internalsys "github.com/tetratelabs/wazero/internal/sys"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/wasm"
@@ -27,13 +27,14 @@ import (
 	"github.com/tetratelabs/wazero/sys"
 )
 
-const (
-	epochNanos = uint64(1640995200000000000) // midnight UTC 2022-01-01
-	seed       = int64(42)                   // fixed seed value
-)
+const seed = int64(42) // fixed seed value
+
+var deterministicRandomSource = func() io.Reader {
+	return rand.New(rand.NewSource(seed))
+}
 
-// testCtx ensures the fake clock is used for WASI functions.
-var testCtx = experimental.WithTimeNowUnixNano(context.Background(), func() uint64 { return epochNanos })
+// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
+var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
 
 var a = &wasi{}
 
@@ -600,18 +601,9 @@ func TestSnapshotPreview1_ClockTimeGet_Monotonic(t *testing.T) {
 			errno := tc.invocation()
 			require.Zero(t, errno, ErrnoName(errno))
 
-			start, ok := mod.Memory().ReadUint64Le(testCtx, resultTimestamp)
-			require.True(t, ok)
-
-			time.Sleep(1 * time.Millisecond)
-
-			errno = tc.invocation()
-			require.Zero(t, errno, ErrnoName(errno))
-			end, ok := mod.Memory().ReadUint64Le(testCtx, resultTimestamp)
+			tick, ok := mod.Memory().ReadUint64Le(testCtx, resultTimestamp)
 			require.True(t, ok)
-
-			// Time is monotonic
-			require.True(t, end > start)
+			require.Equal(t, uint64(platform.FakeEpochNanos), tick)
 		})
 	}
 }
@@ -2105,11 +2097,7 @@ func TestSnapshotPreview1_RandomGet(t *testing.T) {
 	offset := uint32(1) // offset,
 
 	t.Run("wasi.RandomGet", func(t *testing.T) {
-		source := rand.New(rand.NewSource(seed))
-		sysCtx, err := wasm.NewSysContext(math.MaxUint32, nil, nil, new(bytes.Buffer), nil, nil, source, nil)
-		require.NoError(t, err)
-
-		mod, _ := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, sysCtx)
+		mod, _ := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, nil)
 		defer mod.Close(testCtx)
 
 		maskMemory(t, testCtx, mod, len(expectedMemory))
@@ -2124,11 +2112,7 @@ func TestSnapshotPreview1_RandomGet(t *testing.T) {
 	})
 
 	t.Run(functionRandomGet, func(t *testing.T) {
-		source := rand.New(rand.NewSource(seed))
-		sysCtx, err := wasm.NewSysContext(math.MaxUint32, nil, nil, new(bytes.Buffer), nil, nil, source, nil)
-		require.NoError(t, err)
-
-		mod, fn := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, sysCtx)
+		mod, fn := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, nil)
 		defer mod.Close(testCtx)
 
 		maskMemory(t, testCtx, mod, len(expectedMemory))
@@ -2198,17 +2182,24 @@ func TestSnapshotPreview1_RandomGet_SourceError(t *testing.T) {
 	for _, tt := range tests {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
-			var errCtx = experimental.WithTimeNowUnixNano(context.Background(), func() uint64 {
-				panic(errors.New("TimeNowUnixNano error"))
-			})
-
-			sysCtx, err := wasm.NewSysContext(math.MaxUint32, nil, nil, new(bytes.Buffer), nil, nil, tc.randSource, nil)
+			sysCtx, err := internalsys.NewContext(
+				math.MaxUint32,
+				nil,
+				nil,
+				new(bytes.Buffer),
+				nil,
+				nil,
+				tc.randSource,
+				nil, 0,
+				nil, 0,
+				nil,
+			)
 			require.NoError(t, err)
 
-			mod, _ := instantiateModule(errCtx, t, functionRandomGet, importRandomGet, sysCtx)
-			defer mod.Close(errCtx)
+			mod, _ := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, sysCtx)
+			defer mod.Close(testCtx)
 
-			errno := a.RandomGet(errCtx, mod, uint32(1), uint32(5)) // arbitrary offset and length
+			errno := a.RandomGet(testCtx, mod, uint32(1), uint32(5)) // arbitrary offset and length
 			require.Equal(t, ErrnoIo, errno, ErrnoName(errno))
 		})
 	}
@@ -2277,7 +2268,7 @@ func maskMemory(t *testing.T, ctx context.Context, mod api.Module, size int) {
 	}
 }
 
-func instantiateModule(ctx context.Context, t *testing.T, wasifunction, wasiimport string, sysCtx *wasm.SysContext) (api.Module, api.Function) {
+func instantiateModule(ctx context.Context, t *testing.T, wasiFunction, wasiImport string, sysCtx *internalsys.Context) (api.Module, api.Function) {
 	r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
 
 	_, err := Instantiate(testCtx, r)
@@ -2288,28 +2279,41 @@ func instantiateModule(ctx context.Context, t *testing.T, wasifunction, wasiimpo
   (memory 1 1)  ;; just an arbitrary size big enough for tests
   (export "memory" (memory 0))
   (export "%[1]s" (func $wasi.%[1]s))
-)`, wasifunction, wasiimport))
+)`, wasiFunction, wasiImport))
 	require.NoError(t, err)
 
 	compiled, err := r.CompileModule(ctx, binary, wazero.NewCompileConfig())
 	require.NoError(t, err)
 	defer compiled.Close(ctx)
 
-	mod, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName(t.Name()))
+	mod, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().
+		WithName(t.Name()).
+		WithRandSource(deterministicRandomSource()))
 	require.NoError(t, err)
 
 	if sysCtx != nil {
 		mod.(*wasm.CallContext).Sys = sysCtx
 	}
 
-	fn := mod.ExportedFunction(wasifunction)
+	fn := mod.ExportedFunction(wasiFunction)
 	require.NotNil(t, fn)
 
 	return mod, fn
 }
 
-func newSysContext(args, environ []string, openedFiles map[uint32]*internalsys.FileEntry) (sysCtx *wasm.SysContext, err error) {
-	return wasm.NewSysContext(math.MaxUint32, args, environ, new(bytes.Buffer), nil, nil, nil, openedFiles)
+func newSysContext(args, environ []string, openedFiles map[uint32]*internalsys.FileEntry) (sysCtx *internalsys.Context, err error) {
+	return internalsys.NewContext(
+		math.MaxUint32,
+		args,
+		environ,
+		new(bytes.Buffer),
+		nil,
+		nil,
+		deterministicRandomSource(),
+		nil, 0,
+		nil, 0,
+		openedFiles,
+	)
 }
 
 func createFile(t *testing.T, pathName string, data []byte) (fs.File, fs.FS) {