Repository: tetratelabs/wazero
Pull request #1927 (2024-01-18 08:27:58)

threads: for arm64, when shared memory ignore memory length cache
When looking at arm64 code for threads, I believe this is the only invasive change to current code, then it will be just implementing new ops. So sending this separately first.

Currently, we cache the buffer length into the call engine on every function preamble, which is also updated when calling memory.Grow, but this does not work when there are concurrent accesses. So now, when memory is shared, it is assumed there may be concurrent grows and length is read directly every time.

Unlike wazerox, I have made this happen only with shared memory (there I actually removed the length cache from the call engine) so this code is somewhat new to me, notably the large-ish `MemoryType` change.

anuraaga: @ncruces Pre-pinging you as I guess @mathetake would :)
anuraaga: It loads the current length of the memory buffer, most commonly for bounds checks on memory accesses I think 
ncruces: > It loads the current length of the memory buffer, most commonly for bounds checks on memory accesses I think

I get that, but I guess it depends on _usage_ what's the required “happens before” relationship. Do we need atomic because we're dereference a pointer and memory must have grown before the pointer being created?
anuraaga: While I'm not sure, one example that comes to mind is

Thread 1 malloc's, growing, and creates pointer
Thread 1 stores pointer in given location in memory, a pointer pointer
Thread 2 reads pointer pointer than dereferences.

Does it seem like a valid example of happens before?
ncruces: Makes sense, and atomic load/store of the length should be enough to fix that (no need for CAS, or anything more complicated), as long as the pointer is also moved race-free (which it needs to, otherwise that's a bug in the guest).

Patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index a57ad662..ce5f191f 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,3 +1,3 @@
 # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
 
-* @codefromthecrypt @mathetake @evacchi
+* @mathetake @evacchi
diff --git a/internal/engine/compiler/impl_amd64.go b/internal/engine/compiler/impl_amd64.go
index 7de2b331..8b065bd5 100644
--- a/internal/engine/compiler/impl_amd64.go
+++ b/internal/engine/compiler/impl_amd64.go
@@ -4938,7 +4938,7 @@ func (c *amd64Compiler) compileReservedStackBasePointerInitialization() {
 }
 
 func (c *amd64Compiler) compileReservedMemoryPointerInitialization() {
-	if c.ir.HasMemory || c.ir.UsesMemory {
+	if c.ir.Memory != wazeroir.MemoryTypeNone || c.ir.UsesMemory {
 		c.assembler.CompileMemoryToRegister(amd64.MOVQ,
 			amd64ReservedRegisterForCallEngine, callEngineModuleContextMemoryElement0AddressOffset,
 			amd64ReservedRegisterForMemory,
@@ -5065,7 +5065,7 @@ func (c *amd64Compiler) compileModuleContextInitialization() error {
 	// Note: if there's memory instruction in the function, memory instance must be non-nil.
 	// That is ensured by function validation at module instantiation phase, and that's
 	// why it is ok to skip the initialization if the module's memory instance is nil.
-	if c.ir.HasMemory {
+	if c.ir.Memory != wazeroir.MemoryTypeNone {
 		c.assembler.CompileMemoryToRegister(amd64.MOVQ,
 			amd64CallingConventionDestinationFunctionModuleInstanceAddressRegister, moduleInstanceMemoryOffset,
 			tmpRegister)
diff --git a/internal/engine/compiler/impl_arm64.go b/internal/engine/compiler/impl_arm64.go
index b5be128f..ef65b5ae 100644
--- a/internal/engine/compiler/impl_arm64.go
+++ b/internal/engine/compiler/impl_arm64.go
@@ -2852,9 +2852,7 @@ func (c *arm64Compiler) compileMemoryAccessOffsetSetup(offsetArg uint32, targetS
 	}
 
 	// "arm64ReservedRegisterForTemporary = len(memory.Buffer)"
-	c.assembler.CompileMemoryToRegister(arm64.LDRD,
-		arm64ReservedRegisterForCallEngine, callEngineModuleContextMemorySliceLenOffset,
-		arm64ReservedRegisterForTemporary)
+	c.compileLoadMemoryBufferLen(arm64ReservedRegisterForTemporary)
 
 	// Check if offsetRegister(= base+offsetArg+targetSizeInBytes) > len(memory.Buffer).
 	c.assembler.CompileTwoRegistersToNone(arm64.CMP, arm64ReservedRegisterForTemporary, offsetRegister)
@@ -2896,11 +2894,7 @@ func (c *arm64Compiler) compileMemorySize() error {
 	}
 
 	// "reg = len(memory.Buffer)"
-	c.assembler.CompileMemoryToRegister(
-		arm64.LDRD,
-		arm64ReservedRegisterForCallEngine, callEngineModuleContextMemorySliceLenOffset,
-		reg,
-	)
+	c.compileLoadMemoryBufferLen(reg)
 
 	// memory.size loads the page size of memory, so we have to divide by the page size.
 	// "reg = reg >> wasm.MemoryPageSizeInBits (== reg / wasm.MemoryPageSize) "
@@ -3142,9 +3136,7 @@ func (c *arm64Compiler) compileInitImpl(isTable bool, index, tableIndex uint32)
 			tableInstanceAddressReg, tableInstanceTableLenOffset,
 			arm64ReservedRegisterForTemporary)
 	} else {
-		c.assembler.CompileMemoryToRegister(arm64.LDRD,
-			arm64ReservedRegisterForCallEngine, callEngineModuleContextMemorySliceLenOffset,
-			arm64ReservedRegisterForTemporary)
+		c.compileLoadMemoryBufferLen(arm64ReservedRegisterForTemporary)
 	}
 
 	c.assembler.CompileTwoRegistersToNone(arm64.CMP, arm64ReservedRegisterForTemporary, destinationOffset.register)
@@ -3328,9 +3320,7 @@ func (c *arm64Compiler) compileCopyImpl(isTable bool, srcTableIndex, dstTableInd
 			arm64ReservedRegisterForTemporary)
 	} else {
 		// arm64ReservedRegisterForTemporary = len(memoryInst.Buffer).
-		c.assembler.CompileMemoryToRegister(arm64.LDRD,
-			arm64ReservedRegisterForCallEngine, callEngineModuleContextMemorySliceLenOffset,
-			arm64ReservedRegisterForTemporary)
+		c.compileLoadMemoryBufferLen(arm64ReservedRegisterForTemporary)
 	}
 
 	// Check memory len >= sourceOffset.
@@ -3577,9 +3567,7 @@ func (c *arm64Compiler) compileFillImpl(isTable bool, tableIndex uint32) error {
 			arm64ReservedRegisterForTemporary)
 	} else {
 		// arm64ReservedRegisterForTemporary = len(memoryInst.Buffer).
-		c.assembler.CompileMemoryToRegister(arm64.LDRD,
-			arm64ReservedRegisterForCallEngine, callEngineModuleContextMemorySliceLenOffset,
-			arm64ReservedRegisterForTemporary)
+		c.compileLoadMemoryBufferLen(arm64ReservedRegisterForTemporary)
 	}
 
 	// Check  len >= destinationOffset.
@@ -4049,6 +4037,21 @@ func (c *arm64Compiler) allocateRegister(t registerType) (reg asm.Register, err
 	return
 }
 
+func (c *arm64Compiler) compileLoadMemoryBufferLen(destReg asm.Register) {
+	if c.ir.Memory != wazeroir.MemoryTypeShared {
+		// No concurrent accesses so the length we cached during the preamble is still valid.
+		c.assembler.CompileMemoryToRegister(arm64.LDRD,
+			arm64ReservedRegisterForCallEngine, callEngineModuleContextMemorySliceLenOffset,
+			destReg)
+		return
+	}
+	// destReg = ce.moduleContext.MemoryInstance (pointer)
+	c.assembler.CompileMemoryToRegister(arm64.LDRD, arm64ReservedRegisterForCallEngine, callEngineModuleContextMemoryInstanceOffset, destReg)
+	// destReg = len(mem.Buffer)
+	c.assembler.CompileConstToRegister(arm64.ADD, memoryInstanceBufferLenOffset, destReg)
+	c.assembler.CompileMemoryWithRegisterSourceToRegister(arm64.LDARD, destReg, destReg)
+}
+
 // compileReleaseAllRegistersToStack adds instructions to store all the values located on
 // either general purpose or conditional registers onto the memory stack.
 // See releaseRegisterToStack.
@@ -4112,7 +4115,7 @@ func (c *arm64Compiler) compileReservedStackBasePointerRegisterInitialization()
 }
 
 func (c *arm64Compiler) compileReservedMemoryRegisterInitialization() {
-	if c.ir.HasMemory || c.ir.UsesMemory {
+	if c.ir.Memory != wazeroir.MemoryTypeNone || c.ir.UsesMemory {
 		// "arm64ReservedRegisterForMemory = ce.MemoryElement0Address"
 		c.assembler.CompileMemoryToRegister(
 			arm64.LDRD,
@@ -4186,7 +4189,7 @@ func (c *arm64Compiler) compileModuleContextInitialization() error {
 	// Note: if there's memory instruction in the function, memory instance must be non-nil.
 	// That is ensured by function validation at module instantiation phase, and that's
 	// why it is ok to skip the initialization if the module's memory instance is nil.
-	if c.ir.HasMemory {
+	if c.ir.Memory != wazeroir.MemoryTypeNone {
 		// "tmpX = moduleInstance.Memory"
 		c.assembler.CompileMemoryToRegister(
 			arm64.LDRD,
diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go
index f3c78eb7..e3b3ef49 100644
--- a/internal/wasm/memory.go
+++ b/internal/wasm/memory.go
@@ -7,6 +7,7 @@ import (
 	"math"
 	"reflect"
 	"sync"
+	"sync/atomic"
 	"time"
 	"unsafe"
 
@@ -263,7 +264,12 @@ func (m *MemoryInstance) Grow(delta uint32) (result uint32, ok bool) {
 		return currentPages, true
 	} else { // We already have the capacity we need.
 		sp := (*reflect.SliceHeader)(unsafe.Pointer(&m.Buffer))
-		sp.Len = int(MemoryPagesToBytesNum(newPages))
+		if m.Shared {
+			// Use atomic write to ensure new length is visible across threads.
+			atomic.StoreUintptr((*uintptr)(unsafe.Pointer(&sp.Len)), uintptr(MemoryPagesToBytesNum(newPages)))
+		} else {
+			sp.Len = int(MemoryPagesToBytesNum(newPages))
+		}
 		return currentPages, true
 	}
 }
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index 1dc2cd62..2699cb87 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -213,6 +213,18 @@ func (c *Compiler) resetUnreachable() {
 	c.unreachableState.on = false
 }
 
+// MemoryType is the type of memory in a compiled module.
+type MemoryType byte
+
+const (
+	// MemoryTypeNone indicates there is no memory.
+	MemoryTypeNone MemoryType = iota
+	// MemoryTypeStandard indicates there is a non-shared memory.
+	MemoryTypeStandard
+	// MemoryTypeShared indicates there is a shared memory.
+	MemoryTypeShared
+)
+
 type CompilationResult struct {
 	// Operations holds wazeroir operations compiled from Wasm instructions in a Wasm function.
 	Operations []UnionOperation
@@ -246,8 +258,8 @@ type CompilationResult struct {
 	Functions []wasm.Index
 	// Types holds all the types in the module from which this function is compiled.
 	Types []wasm.FunctionType
-	// HasMemory is true if the module from which this function is compiled has memory declaration.
-	HasMemory bool
+	// Memory indicates the type of memory of the module.
+	Memory MemoryType
 	// HasTable is true if the module from which this function is compiled has table declaration.
 	HasTable bool
 	// HasDataInstances is true if the module has data instances which might be used by memory.init or data.drop instructions.
@@ -264,9 +276,19 @@ func NewCompiler(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint64 in
 		return nil, err
 	}
 
-	hasMemory, hasTable, hasDataInstances, hasElementInstances := mem != nil, len(tables) > 0,
+	hasTable, hasDataInstances, hasElementInstances := len(tables) > 0,
 		len(module.DataSection) > 0, len(module.ElementSection) > 0
 
+	var mt MemoryType
+	switch {
+	case mem == nil:
+		mt = MemoryTypeNone
+	case mem.IsShared:
+		mt = MemoryTypeShared
+	default:
+		mt = MemoryTypeStandard
+	}
+
 	types := module.TypeSection
 
 	c := &Compiler{
@@ -278,7 +300,7 @@ func NewCompiler(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint64 in
 			Globals:             globals,
 			Functions:           functions,
 			Types:               types,
-			HasMemory:           hasMemory,
+			Memory:              mt,
 			HasTable:            hasTable,
 			HasDataInstances:    hasDataInstances,
 			HasElementInstances: hasElementInstances,


Test patch:
diff --git a/internal/engine/compiler/compiler_bench_test.go b/internal/engine/compiler/compiler_bench_test.go
index 03c7b793..8022a63e 100644
--- a/internal/engine/compiler/compiler_bench_test.go
+++ b/internal/engine/compiler/compiler_bench_test.go
@@ -32,7 +32,7 @@ func BenchmarkCompiler_compileMemoryCopy(b *testing.B) {
 				}
 
 				compiler := newCompiler()
-				compiler.Init(&wasm.FunctionType{}, &wazeroir.CompilationResult{HasMemory: true}, false)
+				compiler.Init(&wasm.FunctionType{}, &wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard}, false)
 				err := compiler.compilePreamble()
 				require.NoError(b, err)
 
@@ -83,7 +83,7 @@ func BenchmarkCompiler_compileMemoryFill(b *testing.B) {
 			}()
 
 			compiler := newCompiler()
-			compiler.Init(&wasm.FunctionType{}, &wazeroir.CompilationResult{HasMemory: true}, false)
+			compiler.Init(&wasm.FunctionType{}, &wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard}, false)
 
 			var startOffset uint32 = 100
 			var value uint8 = 5
diff --git a/internal/engine/compiler/compiler_initialization_test.go b/internal/engine/compiler/compiler_initialization_test.go
index adbb5237..843e5d8b 100644
--- a/internal/engine/compiler/compiler_initialization_test.go
+++ b/internal/engine/compiler/compiler_initialization_test.go
@@ -16,6 +16,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 	tests := []struct {
 		name           string
 		moduleInstance *wasm.ModuleInstance
+		memoryType     wazeroir.MemoryType
 	}{
 		{
 			name: "no nil",
@@ -30,6 +31,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 				DataInstances:    make([][]byte, 10),
 				ElementInstances: make([]wasm.ElementInstance, 10),
 			},
+			memoryType: wazeroir.MemoryTypeStandard,
 		},
 		{
 			name: "element instances nil",
@@ -41,6 +43,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 				DataInstances:    make([][]byte, 10),
 				ElementInstances: nil,
 			},
+			memoryType: wazeroir.MemoryTypeStandard,
 		},
 		{
 			name: "data instances nil",
@@ -52,6 +55,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 				DataInstances:    nil,
 				ElementInstances: make([]wasm.ElementInstance, 10),
 			},
+			memoryType: wazeroir.MemoryTypeStandard,
 		},
 		{
 			name: "globals nil",
@@ -62,6 +66,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 				DataInstances:    make([][]byte, 10),
 				ElementInstances: make([]wasm.ElementInstance, 10),
 			},
+			memoryType: wazeroir.MemoryTypeStandard,
 		},
 		{
 			name: "memory nil",
@@ -82,6 +87,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 				DataInstances:    make([][]byte, 10),
 				ElementInstances: make([]wasm.ElementInstance, 10),
 			},
+			memoryType: wazeroir.MemoryTypeStandard,
 		},
 		{
 			name: "table empty",
@@ -97,6 +103,14 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 			moduleInstance: &wasm.ModuleInstance{
 				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 0)},
 			},
+			memoryType: wazeroir.MemoryTypeStandard,
+		},
+		{
+			name: "memory shared",
+			moduleInstance: &wasm.ModuleInstance{
+				MemoryInstance: &wasm.MemoryInstance{Buffer: make([]byte, 10), Shared: true},
+			},
+			memoryType: wazeroir.MemoryTypeShared,
 		},
 		{
 			name:           "all nil except mod engine",
@@ -112,7 +126,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 			ce := env.callEngine()
 
 			ir := &wazeroir.CompilationResult{
-				HasMemory:           tc.moduleInstance.MemoryInstance != nil,
+				Memory:              tc.memoryType,
 				HasTable:            len(tc.moduleInstance.Tables) > 0,
 				HasDataInstances:    len(tc.moduleInstance.DataInstances) > 0,
 				HasElementInstances: len(tc.moduleInstance.ElementInstances) > 0,
diff --git a/internal/engine/compiler/compiler_memory_test.go b/internal/engine/compiler/compiler_memory_test.go
index ccd440a5..c66aafc5 100644
--- a/internal/engine/compiler/compiler_memory_test.go
+++ b/internal/engine/compiler/compiler_memory_test.go
@@ -55,7 +55,7 @@ func TestCompiler_compileMemoryGrow(t *testing.T) {
 
 func TestCompiler_compileMemorySize(t *testing.T) {
 	env := newCompilerEnvironment()
-	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
+	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 	err := compiler.compilePreamble()
 	require.NoError(t, err)
@@ -242,7 +242,7 @@ func TestCompiler_compileLoad(t *testing.T) {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
-			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
+			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -382,7 +382,7 @@ func TestCompiler_compileStore(t *testing.T) {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
-			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
+			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
diff --git a/internal/engine/compiler/compiler_post1_0_test.go b/internal/engine/compiler/compiler_post1_0_test.go
index 7632b354..b76be975 100644
--- a/internal/engine/compiler/compiler_post1_0_test.go
+++ b/internal/engine/compiler/compiler_post1_0_test.go
@@ -202,7 +202,7 @@ func TestCompiler_compileMemoryCopy(t *testing.T) {
 		tc := tt
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
 			env := newCompilerEnvironment()
-			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
+			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -289,7 +289,7 @@ func TestCompiler_compileMemoryFill(t *testing.T) {
 		tc := tt
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
 			env := newCompilerEnvironment()
-			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
+			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -432,7 +432,7 @@ func TestCompiler_compileMemoryInit(t *testing.T) {
 			env.module().DataInstances = dataInstances
 
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
-				HasDataInstances: true, HasMemory: true,
+				HasDataInstances: true, Memory: wazeroir.MemoryTypeStandard,
 			})
 
 			err := compiler.compilePreamble()
diff --git a/internal/engine/compiler/compiler_vec_test.go b/internal/engine/compiler/compiler_vec_test.go
index 946f104b..23385c31 100644
--- a/internal/engine/compiler/compiler_vec_test.go
+++ b/internal/engine/compiler/compiler_vec_test.go
@@ -67,7 +67,7 @@ func TestCompiler_compileV128Add(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -161,7 +161,7 @@ func TestCompiler_compileV128Sub(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -540,7 +540,7 @@ func TestCompiler_compileV128Load(t *testing.T) {
 			tc.memSetupFn(env.memory())
 
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -743,7 +743,7 @@ func TestCompiler_compileV128LoadLane(t *testing.T) {
 			tc.memSetupFn(env.memory())
 
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -801,7 +801,7 @@ func TestCompiler_compileV128Store(t *testing.T) {
 			env := newCompilerEnvironment()
 
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -943,7 +943,7 @@ func TestCompiler_compileV128StoreLane(t *testing.T) {
 			env := newCompilerEnvironment()
 
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -1113,7 +1113,7 @@ func TestCompiler_compileV128ExtractLane(t *testing.T) {
 			env := newCompilerEnvironment()
 
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -1360,7 +1360,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -1464,7 +1464,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -1514,7 +1514,7 @@ func TestCompiler_compileV128AnyTrue(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -1679,7 +1679,7 @@ func TestCompiler_compileV128AllTrue(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -1778,7 +1778,7 @@ func TestCompiler_compileV128Swizzle(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -1870,7 +1870,7 @@ func TestCompiler_compileV128Shuffle(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -2008,7 +2008,7 @@ func TestCompiler_compileV128Bitmask(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -2042,7 +2042,7 @@ func TestCompiler_compileV128Bitmask(t *testing.T) {
 func TestCompiler_compileV128_Not(t *testing.T) {
 	env := newCompilerEnvironment()
 	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-		&wazeroir.CompilationResult{HasMemory: true})
+		&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 	err := compiler.compilePreamble()
 	require.NoError(t, err)
@@ -2256,7 +2256,7 @@ func TestCompiler_compileV128_And_Or_Xor_AndNot(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -2350,7 +2350,7 @@ func TestCompiler_compileV128Bitselect(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -2638,7 +2638,7 @@ func TestCompiler_compileV128Shl(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -2914,7 +2914,7 @@ func TestCompiler_compileV128Shr(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -3346,7 +3346,7 @@ func TestCompiler_compileV128Cmp(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -3424,7 +3424,7 @@ func TestCompiler_compileV128AvgrU(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -3493,7 +3493,7 @@ func TestCompiler_compileV128Sqrt(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -3577,7 +3577,7 @@ func TestCompiler_compileV128Mul(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -3677,7 +3677,7 @@ func TestCompiler_compileV128Neg(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -3774,7 +3774,7 @@ func TestCompiler_compileV128Abs(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -3844,7 +3844,7 @@ func TestCompiler_compileV128Div(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -4033,7 +4033,7 @@ func TestCompiler_compileV128Min(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -4257,7 +4257,7 @@ func TestCompiler_compileV128Max(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -4395,7 +4395,7 @@ func TestCompiler_compileV128AddSat(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -4504,7 +4504,7 @@ func TestCompiler_compileV128SubSat(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -4577,7 +4577,7 @@ func TestCompiler_compileV128Popcnt(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -4747,7 +4747,7 @@ func TestCompiler_compileV128Round(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -5037,7 +5037,7 @@ func TestCompiler_compileV128_Pmax_Pmin(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -5733,7 +5733,7 @@ func TestCompiler_compileV128ExtMul(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -6207,7 +6207,7 @@ func TestCompiler_compileV128Extend(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -6285,7 +6285,7 @@ func TestCompiler_compileV128Q15mulrSatS(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -6361,7 +6361,7 @@ func TestCompiler_compileFloatPromote(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -6445,7 +6445,7 @@ func TestCompiler_compileV128FloatDemote(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -6653,7 +6653,7 @@ func TestCompiler_compileV128ExtAddPairwise(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -6893,7 +6893,7 @@ func TestCompiler_compileV128Narrow(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -7030,7 +7030,7 @@ func TestCompiler_compileV128FConvertFromI(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -7096,7 +7096,7 @@ func TestCompiler_compileV128Dot(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -7247,7 +7247,7 @@ func TestCompiler_compileV128ITruncSatFromF(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -7291,7 +7291,7 @@ func TestCompiler_compileSelect_v128(t *testing.T) {
 	for _, selector := range []uint32{0, 1} {
 		env := newCompilerEnvironment()
 		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-			&wazeroir.CompilationResult{HasMemory: true})
+			&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 		err := compiler.compilePreamble()
 		require.NoError(t, err)
diff --git a/internal/engine/compiler/impl_vec_amd64_test.go b/internal/engine/compiler/impl_vec_amd64_test.go
index 34b387bf..e7180b17 100644
--- a/internal/engine/compiler/impl_vec_amd64_test.go
+++ b/internal/engine/compiler/impl_vec_amd64_test.go
@@ -17,7 +17,7 @@ import (
 func TestAmd64Compiler_V128Shuffle_ConstTable_MiddleOfFunction(t *testing.T) {
 	env := newCompilerEnvironment()
 	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-		&wazeroir.CompilationResult{HasMemory: true})
+		&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 	err := compiler.compilePreamble()
 	require.NoError(t, err)
@@ -200,7 +200,7 @@ func TestAmd64Compiler_compileV128ShrI64x2SignedImpl(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
@@ -280,7 +280,7 @@ func TestAmd64Compiler_compileV128Neg_NaNOnTemporary(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
diff --git a/internal/engine/compiler/impl_vec_arm64_test.go b/internal/engine/compiler/impl_vec_arm64_test.go
index bb600752..caa0c17c 100644
--- a/internal/engine/compiler/impl_vec_arm64_test.go
+++ b/internal/engine/compiler/impl_vec_arm64_test.go
@@ -16,7 +16,7 @@ import (
 func TestArm64Compiler_V128Shuffle_ConstTable_MiddleOfFunction(t *testing.T) {
 	env := newCompilerEnvironment()
 	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-		&wazeroir.CompilationResult{HasMemory: true})
+		&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 	err := compiler.compilePreamble()
 	require.NoError(t, err)
@@ -157,7 +157,7 @@ func TestArm64Compiler_V128Shuffle_combinations(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			env := newCompilerEnvironment()
 			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
-				&wazeroir.CompilationResult{HasMemory: true})
+				&wazeroir.CompilationResult{Memory: wazeroir.MemoryTypeStandard})
 
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go
index 37687c56..90a3742d 100644
--- a/internal/wazeroir/compiler_test.go
+++ b/internal/wazeroir/compiler_test.go
@@ -301,7 +301,7 @@ func TestCompile_BulkMemoryOperations(t *testing.T) {
 			NewOperationDataDrop(1),                      // []
 			NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 		},
-		HasMemory:        true,
+		Memory:           MemoryTypeStandard,
 		UsesMemory:       true,
 		HasDataInstances: true,
 		LabelCallers:     map[Label]uint32{},