Repository: tetratelabs/wazero
Pull request #1752 (2023-10-04 07:04:05)

wazevo: implements memory.{copy,fill} and table.fill
#1496 


Patch:
diff --git a/internal/engine/wazevo/call_engine.go b/internal/engine/wazevo/call_engine.go
index 383bc222..26f658c0 100644
--- a/internal/engine/wazevo/call_engine.go
+++ b/internal/engine/wazevo/call_engine.go
@@ -78,6 +78,8 @@ type (
 		tableGrowTrampolineAddress *byte
 		// refFuncTrampolineAddress holds the address of ref-func trampoline function.
 		refFuncTrampolineAddress *byte
+		// memmoveAddress holds the address of memmove function implemented by Go runtime. See memmove.go.
+		memmoveAddress uintptr
 	}
 )
 
diff --git a/internal/engine/wazevo/frontend/frontend.go b/internal/engine/wazevo/frontend/frontend.go
index 8874cdd5..1c54c8b4 100644
--- a/internal/engine/wazevo/frontend/frontend.go
+++ b/internal/engine/wazevo/frontend/frontend.go
@@ -24,6 +24,7 @@ type Compiler struct {
 	checkModuleExitCodeSig ssa.Signature
 	tableGrowSig           ssa.Signature
 	refFuncSig             ssa.Signature
+	memmoveSig             ssa.Signature
 	checkModuleExitCodeArg [1]ssa.Value
 	ensureTermination      bool
 
@@ -125,6 +126,13 @@ func (c *Compiler) declareSignatures(listenerOn bool) {
 		Results: []ssa.Type{ssa.TypeI64},
 	}
 	c.ssaBuilder.DeclareSignature(&c.refFuncSig)
+
+	c.memmoveSig = ssa.Signature{
+		ID: c.refFuncSig.ID + 1,
+		// dst, src, and the byte count.
+		Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32},
+	}
+	c.ssaBuilder.DeclareSignature(&c.memmoveSig)
 }
 
 // SignatureForWasmFunctionType returns the ssa.Signature for the given wasm.FunctionType.
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index 3d81b052..3961b82a 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -4,7 +4,6 @@ import (
 	"encoding/binary"
 	"fmt"
 	"math"
-	"strconv"
 	"strings"
 
 	"github.com/tetratelabs/wazero/api"
@@ -586,8 +585,245 @@ func (c *Compiler) lowerCurrentOpcode() {
 				Insert(builder).Return()
 			state.push(callGrowRet)
 
+		case wasm.OpcodeMiscTableCopy:
+			dstTableIndex := c.readI32u()
+			srcTableIndex := c.readI32u()
+			if state.unreachable {
+				break
+			}
+
+			copySize := state.pop()
+			srcOffset := state.pop()
+			dstOffset := state.pop()
+			copySizeExt := builder.
+				AllocateInstruction().AsUExtend(copySize, 32, 64).Insert(builder).Return()
+			srcOffsetExt := builder.
+				AllocateInstruction().AsUExtend(srcOffset, 32, 64).Insert(builder).Return()
+			dstOffsetExt := builder.
+				AllocateInstruction().AsUExtend(dstOffset, 32, 64).Insert(builder).Return()
+
+			// Out of bounds check.
+			var dstTableInstancePtr, srcTableInstancePtr ssa.Value
+			{
+				dstCeil := builder.AllocateInstruction().AsIadd(dstOffsetExt, copySizeExt).Insert(builder).Return()
+
+				// Load the table.
+				dstTableInstancePtr = builder.AllocateInstruction().
+					AsLoad(c.moduleCtxPtrValue, c.offset.TableOffset(int(dstTableIndex)).U32(), ssa.TypeI64).
+					Insert(builder).Return()
+
+				// Load the table's length.
+				tableLen := builder.AllocateInstruction().
+					AsLoad(dstTableInstancePtr, tableInstanceLenOffset, ssa.TypeI32).Insert(builder).Return()
+				tableLenExt := builder.AllocateInstruction().AsUExtend(tableLen, 32, 64).Insert(builder).Return()
+
+				// Compare the length and the target, and trap if out of bounds.
+				checkOOB := builder.AllocateInstruction()
+				checkOOB.AsIcmp(tableLenExt, dstCeil, ssa.IntegerCmpCondUnsignedLessThan)
+				builder.InsertInstruction(checkOOB)
+				exitIfOOB := builder.AllocateInstruction()
+				exitIfOOB.AsExitIfTrueWithCode(c.execCtxPtrValue, checkOOB.Return(), wazevoapi.ExitCodeTableOutOfBounds)
+				builder.InsertInstruction(exitIfOOB)
+			}
+			{
+				srcCeil := builder.AllocateInstruction().AsIadd(srcOffsetExt, copySizeExt).Insert(builder).Return()
+
+				// Load the table.
+				srcTableInstancePtr = builder.AllocateInstruction().
+					AsLoad(c.moduleCtxPtrValue, c.offset.TableOffset(int(srcTableIndex)).U32(), ssa.TypeI64).
+					Insert(builder).Return()
+
+				// Load the table's length.
+				tableLen := builder.AllocateInstruction().
+					AsLoad(srcTableInstancePtr, tableInstanceLenOffset, ssa.TypeI32).Insert(builder).Return()
+				tableLenExt := builder.AllocateInstruction().AsUExtend(tableLen, 32, 64).Insert(builder).Return()
+
+				// Compare the length and the target, and trap if out of bounds.
+				checkOOB := builder.AllocateInstruction()
+				checkOOB.AsIcmp(tableLenExt, srcCeil, ssa.IntegerCmpCondUnsignedLessThan)
+				builder.InsertInstruction(checkOOB)
+				exitIfOOB := builder.AllocateInstruction()
+				exitIfOOB.AsExitIfTrueWithCode(c.execCtxPtrValue, checkOOB.Return(), wazevoapi.ExitCodeTableOutOfBounds)
+				builder.InsertInstruction(exitIfOOB)
+			}
+
+			var dstTableBaseAddr, srcTableBaseAddr ssa.Value
+			{
+				loadTableBaseAddress := builder.AllocateInstruction()
+				loadTableBaseAddress.AsLoad(dstTableInstancePtr, tableInstanceBaseAddressOffset, ssa.TypeI64)
+				builder.InsertInstruction(loadTableBaseAddress)
+				dstTableBaseAddr = loadTableBaseAddress.Return()
+			}
+			{
+				loadTableBaseAddress := builder.AllocateInstruction()
+				loadTableBaseAddress.AsLoad(srcTableInstancePtr, tableInstanceBaseAddressOffset, ssa.TypeI64)
+				builder.InsertInstruction(loadTableBaseAddress)
+				srcTableBaseAddr = loadTableBaseAddress.Return()
+			}
+			three := builder.AllocateInstruction().AsIconst64(3).Insert(builder).Return()
+
+			dstOffsetInBytes := builder.AllocateInstruction().AsIshl(dstOffsetExt, three).Insert(builder).Return()
+			dstAddr := builder.AllocateInstruction().AsIadd(dstTableBaseAddr, dstOffsetInBytes).Insert(builder).Return()
+			srcOffsetInBytes := builder.AllocateInstruction().AsIshl(srcOffsetExt, three).Insert(builder).Return()
+			srcAddr := builder.AllocateInstruction().AsIadd(srcTableBaseAddr, srcOffsetInBytes).Insert(builder).Return()
+
+			memmovePtr := c.loadMemmoveAddr()
+
+			copySizeExtInBytes := builder.AllocateInstruction().AsIshl(copySizeExt, three).Insert(builder).Return()
+
+			// TODO: reuse the slice.
+			args := []ssa.Value{dstAddr, srcAddr, copySizeExtInBytes}
+			builder.
+				AllocateInstruction().
+				AsCallIndirect(memmovePtr, &c.memmoveSig, args).
+				Insert(builder)
+
+		case wasm.OpcodeMiscMemoryCopy:
+			state.pc += 2 // +2 to skip two memory indexes which are fixed to zero.
+			if state.unreachable {
+				break
+			}
+
+			copySize := state.pop()
+			srcOffset := state.pop()
+			dstOffset := state.pop()
+
+			copySizeExt := builder.
+				AllocateInstruction().AsUExtend(copySize, 32, 64).Insert(builder).Return()
+			srcOffsetExt := builder.
+				AllocateInstruction().AsUExtend(srcOffset, 32, 64).Insert(builder).Return()
+			dstOffsetExt := builder.
+				AllocateInstruction().AsUExtend(dstOffset, 32, 64).Insert(builder).Return()
+
+			// Out of bounds check.
+			memLen := c.getMemoryLenValue(false)
+
+			{
+				srcCeil := builder.AllocateInstruction().AsIadd(srcOffsetExt, copySizeExt).Insert(builder).Return()
+				cmp := builder.AllocateInstruction().
+					AsIcmp(memLen, srcCeil, ssa.IntegerCmpCondUnsignedLessThan).
+					Insert(builder).
+					Return()
+				builder.AllocateInstruction().
+					AsExitIfTrueWithCode(c.execCtxPtrValue, cmp, wazevoapi.ExitCodeMemoryOutOfBounds).
+					Insert(builder)
+			}
+			{
+				dstCeil := builder.AllocateInstruction().AsIadd(dstOffsetExt, copySizeExt).Insert(builder).Return()
+				cmp := builder.AllocateInstruction().
+					AsIcmp(memLen, dstCeil, ssa.IntegerCmpCondUnsignedLessThan).
+					Insert(builder).
+					Return()
+				builder.AllocateInstruction().
+					AsExitIfTrueWithCode(c.execCtxPtrValue, cmp, wazevoapi.ExitCodeMemoryOutOfBounds).
+					Insert(builder)
+			}
+
+			memBase := c.getMemoryBaseValue(false)
+			dstAddr := builder.AllocateInstruction().AsIadd(memBase, dstOffsetExt).Insert(builder).Return()
+			srcAddr := builder.AllocateInstruction().AsIadd(memBase, srcOffsetExt).Insert(builder).Return()
+
+			memmovePtr := c.loadMemmoveAddr()
+
+			// TODO: reuse the slice.
+			args := []ssa.Value{dstAddr, srcAddr, copySize}
+			builder.
+				AllocateInstruction().
+				AsCallIndirect(memmovePtr, &c.memmoveSig, args).
+				Insert(builder)
+
+		case wasm.OpcodeMiscMemoryFill:
+			state.pc++ // Skip the memory index which is fixed to zero.
+			if state.unreachable {
+				break
+			}
+			fillSize := state.pop()
+			value := state.pop()
+			offset := state.pop()
+
+			fillSizeExt := builder.
+				AllocateInstruction().AsUExtend(fillSize, 32, 64).Insert(builder).Return()
+			offsetExt := builder.
+				AllocateInstruction().AsUExtend(offset, 32, 64).Insert(builder).Return()
+
+			// Out of bounds check.
+			memLen := c.getMemoryLenValue(false)
+			ceil := builder.AllocateInstruction().AsIadd(fillSizeExt, offsetExt).Insert(builder).Return()
+			boundsCheckFailed := builder.AllocateInstruction().
+				AsIcmp(memLen, ceil, ssa.IntegerCmpCondUnsignedLessThan).
+				Insert(builder).
+				Return()
+			builder.AllocateInstruction().
+				AsExitIfTrueWithCode(c.execCtxPtrValue, boundsCheckFailed, wazevoapi.ExitCodeMemoryOutOfBounds).
+				Insert(builder)
+
+			// Calculate the base address:
+			addr := builder.AllocateInstruction().AsIadd(c.getMemoryBaseValue(false), offsetExt).Insert(builder).Return()
+
+			// Uses the copy trick for faster filling buffer: https://gist.github.com/taylorza/df2f89d5f9ab3ffd06865062a4cf015d
+			// buf := memoryInst.Buffer[offset : offset+fillSize]
+			// buf[0] = value
+			// for i := 1; i < fillSize; i *= 2 {
+			// 	copy(buf[i:], buf[:i])
+			// }
+
+			// Prepare the loop and following block.
+			beforeLoop := builder.AllocateBasicBlock()
+			loopBlk := builder.AllocateBasicBlock()
+			loopVar := loopBlk.AddParam(builder, ssa.TypeI64)
+			followingBlk := builder.AllocateBasicBlock()
+
+			// Insert the jump to the beforeLoop block; If the fillSize is zero, then jump to the following block to skip entire logics.
+			zero := builder.AllocateInstruction().AsIconst64(0).Insert(builder).Return()
+			ifFillSizeZero := builder.AllocateInstruction().AsIcmp(fillSizeExt, zero, ssa.IntegerCmpCondEqual).
+				Insert(builder).Return()
+			builder.AllocateInstruction().AsBrnz(ifFillSizeZero, nil, followingBlk).Insert(builder)
+			c.insertJumpToBlock(nil, beforeLoop)
+
+			// buf[0] = value
+			builder.SetCurrentBlock(beforeLoop)
+			builder.AllocateInstruction().AsStore(ssa.OpcodeIstore8, value, addr, 0).Insert(builder)
+			initValue := builder.AllocateInstruction().AsIconst64(1).Insert(builder).Return()
+			c.insertJumpToBlock([]ssa.Value{initValue}, loopBlk) // TODO: reuse the slice.
+
+			builder.SetCurrentBlock(loopBlk)
+			dstAddr := builder.AllocateInstruction().AsIadd(addr, loopVar).Insert(builder).Return()
+
+			// If loopVar*2 > fillSizeExt, then count must be fillSizeExt-loopVar.
+			var count ssa.Value
+			{
+				loopVarDoubled := builder.AllocateInstruction().AsIadd(loopVar, loopVar).Insert(builder).Return()
+				loopVarDoubledLargerThanFillSize := builder.
+					AllocateInstruction().AsIcmp(loopVarDoubled, fillSizeExt, ssa.IntegerCmpCondUnsignedGreaterThanOrEqual).
+					Insert(builder).Return()
+				diff := builder.AllocateInstruction().AsIsub(fillSizeExt, loopVar).Insert(builder).Return()
+				count = builder.AllocateInstruction().AsSelect(loopVarDoubledLargerThanFillSize, diff, loopVar).Insert(builder).Return()
+			}
+
+			memmovePtr := c.loadMemmoveAddr()
+			builder.
+				AllocateInstruction().
+				AsCallIndirect(memmovePtr, &c.memmoveSig, []ssa.Value{dstAddr, addr, count}). // TODO: reuse the slice.
+				Insert(builder)
+
+			shiftAmount := builder.AllocateInstruction().AsIconst64(1).Insert(builder).Return()
+			newLoopVar := builder.AllocateInstruction().AsIshl(loopVar, shiftAmount).Insert(builder).Return()
+			loopVarLessThanFillSize := builder.AllocateInstruction().
+				AsIcmp(newLoopVar, fillSizeExt, ssa.IntegerCmpCondUnsignedLessThan).Insert(builder).Return()
+
+			builder.AllocateInstruction().
+				AsBrnz(loopVarLessThanFillSize, []ssa.Value{newLoopVar}, loopBlk). // TODO: reuse the slice.
+				Insert(builder)
+
+			c.insertJumpToBlock(nil, followingBlk)
+			builder.SetCurrentBlock(followingBlk)
+
+			builder.Seal(beforeLoop)
+			builder.Seal(loopBlk)
+			builder.Seal(followingBlk)
+
 		default:
-			panic("Unknown MiscOp " + strconv.Itoa(int(miscOpUint)))
+			panic("Unknown MiscOp " + wasm.MiscInstructionName(miscOp))
 		}
 
 	case wasm.OpcodeI32ReinterpretF32:
@@ -2695,6 +2931,16 @@ func (c *Compiler) memOpSetup(baseAddr ssa.Value, constOffset, operationSizeInBy
 	return addrCalc.Return()
 }
 
+func (c *Compiler) loadMemmoveAddr() ssa.Value {
+	builder := c.ssaBuilder
+	memmovePtr := builder.AllocateInstruction().
+		AsLoad(c.execCtxPtrValue,
+			wazevoapi.ExecutionContextOffsetMemmoveAddress.U32(),
+			ssa.TypeI64,
+		).Insert(builder).Return()
+	return memmovePtr
+}
+
 func (c *Compiler) reloadAfterCall() {
 	// Note that when these are not used in the following instructions, they will be optimized out.
 	// So in any ways, we define them!
diff --git a/internal/engine/wazevo/memmove.go b/internal/engine/wazevo/memmove.go
new file mode 100644
index 00000000..88992210
--- /dev/null
+++ b/internal/engine/wazevo/memmove.go
@@ -0,0 +1,11 @@
+package wazevo
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+//go:linkname memmove runtime.memmove
+func memmove(_, _ unsafe.Pointer, _ uintptr)
+
+var memmovPtr = reflect.ValueOf(memmove).Pointer()
diff --git a/internal/engine/wazevo/module_engine.go b/internal/engine/wazevo/module_engine.go
index 92b9b20c..93e55273 100644
--- a/internal/engine/wazevo/module_engine.go
+++ b/internal/engine/wazevo/module_engine.go
@@ -155,6 +155,7 @@ func (m *moduleEngine) NewFunction(index wasm.Index) api.Function {
 	ce.execCtx.checkModuleExitCodeTrampolineAddress = &m.parent.sharedFunctions.checkModuleExitCode[0]
 	ce.execCtx.tableGrowTrampolineAddress = &m.parent.sharedFunctions.tableGrowExecutable[0]
 	ce.execCtx.refFuncTrampolineAddress = &m.parent.sharedFunctions.refFuncExecutable[0]
+	ce.execCtx.memmoveAddress = memmovPtr
 	ce.init()
 	return ce
 }
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 1f94911a..6fd03665 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -1515,11 +1515,12 @@ func (i *Instruction) AsVFdiv(x, y Value, lane VecLane) *Instruction {
 }
 
 // AsImul initializes this instruction as an integer addition instruction with OpcodeImul.
-func (i *Instruction) AsImul(x, y Value) {
+func (i *Instruction) AsImul(x, y Value) *Instruction {
 	i.opcode = OpcodeImul
 	i.v = x
 	i.v2 = y
 	i.typ = x.Type()
+	return i
 }
 
 func (i *Instruction) Insert(b Builder) *Instruction {
@@ -1993,11 +1994,12 @@ func (i *Instruction) AsExitWithCode(ctx Value, code wazevoapi.ExitCode) {
 }
 
 // AsExitIfTrueWithCode initializes this instruction as a trap instruction with OpcodeExitIfTrueWithCode.
-func (i *Instruction) AsExitIfTrueWithCode(ctx, c Value, code wazevoapi.ExitCode) {
+func (i *Instruction) AsExitIfTrueWithCode(ctx, c Value, code wazevoapi.ExitCode) *Instruction {
 	i.opcode = OpcodeExitIfTrueWithCode
 	i.v = ctx
 	i.v2 = c
 	i.u1 = uint64(code)
+	return i
 }
 
 // ExitWithCodeData returns the context and exit code of OpcodeExitWithCode.
@@ -2079,11 +2081,12 @@ func (i *Instruction) AsBrz(v Value, args []Value, target BasicBlock) {
 }
 
 // AsBrnz initializes this instruction as a branch-if-not-zero instruction with OpcodeBrnz.
-func (i *Instruction) AsBrnz(v Value, args []Value, target BasicBlock) {
+func (i *Instruction) AsBrnz(v Value, args []Value, target BasicBlock) *Instruction {
 	i.opcode = OpcodeBrnz
 	i.v = v
 	i.vs = args
 	i.blk = target
+	return i
 }
 
 // AsBrTable initializes this instruction as a branch-table instruction with OpcodeBrTable.
@@ -2352,7 +2355,7 @@ func (i *Instruction) AsSExtend(v Value, from, to byte) {
 }
 
 // AsUExtend initializes this instruction as an unsigned extension instruction with OpcodeUExtend.
-func (i *Instruction) AsUExtend(v Value, from, to byte) {
+func (i *Instruction) AsUExtend(v Value, from, to byte) *Instruction {
 	i.opcode = OpcodeUExtend
 	i.v = v
 	i.u1 = uint64(from)<<8 | uint64(to)
@@ -2361,6 +2364,7 @@ func (i *Instruction) AsUExtend(v Value, from, to byte) {
 	} else {
 		i.typ = TypeI32
 	}
+	return i
 }
 
 func (i *Instruction) ExtendData() (from, to byte, signed bool) {
diff --git a/internal/engine/wazevo/wazevoapi/offsetdata.go b/internal/engine/wazevo/wazevoapi/offsetdata.go
index 3dfc81bd..1abaf4fb 100644
--- a/internal/engine/wazevo/wazevoapi/offsetdata.go
+++ b/internal/engine/wazevo/wazevoapi/offsetdata.go
@@ -48,6 +48,7 @@ const (
 	ExecutionContextOffsetTableGrowTrampolineAddress Offset = 1128
 	// ExecutionContextOffsetRefFuncTrampolineAddress is an offset of `refFuncTrampolineAddress` field in wazevo.executionContext
 	ExecutionContextOffsetRefFuncTrampolineAddress Offset = 1136
+	ExecutionContextOffsetMemmoveAddress           Offset = 1144
 )
 
 // ModuleContextOffsetData allows the compilers to get the information about offsets to the fields of wazevo.moduleContextOpaque,


Test patch:
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 75f0c98b..d37209d7 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -144,9 +144,12 @@ func TestSpectestV2(t *testing.T) {
 		{"conversions"},
 		{"if"},
 		{"loop"},
+		{"memory_copy"},
+		{"memory_fill"},
 		{"ref_null"},
 		{"ref_is_null"},
 		{"ref_func"},
+		{"table_copy"},
 		{"table_get"},
 		{"table_set"},
 		{"table_size"},
diff --git a/internal/engine/wazevo/frontend/frontend_test.go b/internal/engine/wazevo/frontend/frontend_test.go
index 6e70d661..a4590e27 100644
--- a/internal/engine/wazevo/frontend/frontend_test.go
+++ b/internal/engine/wazevo/frontend/frontend_test.go
@@ -1884,6 +1884,7 @@ func TestCompiler_declareSignatures(t *testing.T) {
 			{ID: 5, Params: []ssa.Type{ssa.TypeI64}},
 			{ID: 6, Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32, ssa.TypeI32, ssa.TypeI64}, Results: []ssa.Type{ssa.TypeI32}},
 			{ID: 7, Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32}, Results: []ssa.Type{ssa.TypeI64}},
+			{ID: 8, Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32}},
 		}
 
 		require.Equal(t, len(expected), len(declaredSigs))
@@ -1919,6 +1920,7 @@ func TestCompiler_declareSignatures(t *testing.T) {
 			{ID: 13, Params: []ssa.Type{ssa.TypeI64}},
 			{ID: 14, Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32, ssa.TypeI32, ssa.TypeI64}, Results: []ssa.Type{ssa.TypeI32}},
 			{ID: 15, Params: []ssa.Type{ssa.TypeI64, ssa.TypeI32}, Results: []ssa.Type{ssa.TypeI64}},
+			{ID: 16, Params: []ssa.Type{ssa.TypeI64, ssa.TypeI64, ssa.TypeI32}},
 		}
 		require.Equal(t, len(expected), len(declaredSigs))
 		for i := 0; i < len(declaredSigs); i++ {
diff --git a/internal/engine/wazevo/wazevo_test.go b/internal/engine/wazevo/wazevo_test.go
index e1edc807..92912116 100644
--- a/internal/engine/wazevo/wazevo_test.go
+++ b/internal/engine/wazevo/wazevo_test.go
@@ -65,4 +65,5 @@ func Test_ExecutionContextOffsets(t *testing.T) {
 	require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.goFunctionCallCalleeModuleContextOpaque)), wazevoapi.ExecutionContextOffsetGoFunctionCallCalleeModuleContextOpaque)
 	require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.tableGrowTrampolineAddress)), wazevoapi.ExecutionContextOffsetTableGrowTrampolineAddress)
 	require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.refFuncTrampolineAddress)), wazevoapi.ExecutionContextOffsetRefFuncTrampolineAddress)
+	require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.memmoveAddress)), wazevoapi.ExecutionContextOffsetMemmoveAddress)
 }