Repository: tetratelabs/wazero
Pull request #1625 (2023-08-11 04:33:32)

wazevo: adds support for globals
#1496 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_go_entry.go b/internal/engine/wazevo/backend/isa/arm64/abi_go_entry.go
index 24137625..3910ef61 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_go_entry.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_go_entry.go
@@ -23,7 +23,7 @@ var (
 	executionContextPtrReg = x0VReg
 	// callee-saved regs so that they can be used in the prologue and epilogue.
 	savedExecutionContextPtr = x18VReg
-	paramResultSlicePtr      = v19VReg
+	paramResultSlicePtr      = x19VReg
 	// goAllocatedStackPtr is not used in the epilogue.
 	goAllocatedStackPtr = x26VReg
 )
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index ae286c38..2398eaae 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -167,6 +167,10 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		x, y := instr.BinaryData()
 		result := instr.Return()
 		m.lowerImul(x, y, result)
+	case ssa.OpcodeUndefined:
+		undef := m.allocateInstr()
+		undef.asUDF()
+		m.insert(undef)
 	default:
 		panic("TODO: lowering " + instr.Opcode().String())
 	}
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_mem.go b/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
index c83ca8f1..36863c7e 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_mem.go
@@ -98,6 +98,12 @@ const (
 
 func (a addressMode) format(dstSizeBits byte) (ret string) {
 	base := formatVRegSized(a.rn, 64)
+	if rn := a.rn; rn.RegType() != regalloc.RegTypeInt {
+		panic("invalid base register type: " + a.rn.RegType().String())
+	} else if rn.IsRealReg() && v0 <= a.rn.RealReg() && a.rn.RealReg() <= v30 {
+		panic("BUG: likely a bug in reg alloc or reset behavior")
+	}
+
 	switch a.kind {
 	case addressModeKindRegScaledExtended:
 		amount := a.sizeInBitsToShiftAmount(dstSizeBits)
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine.go b/internal/engine/wazevo/backend/isa/arm64/machine.go
index 8d1ca8f4..cc17b5a5 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine.go
@@ -132,6 +132,7 @@ func (m *machine) Reset() {
 	m.clobberedRegs = m.clobberedRegs[:0]
 	m.orderedLabels = m.orderedLabels[:0]
 	m.regAllocFn.reset()
+	m.spillSlotSize = 0
 	m.unresolvedAddressModes = m.unresolvedAddressModes[:0]
 }
 
diff --git a/internal/engine/wazevo/backend/regalloc/regalloc.go b/internal/engine/wazevo/backend/regalloc/regalloc.go
index 10f3197c..f00961ee 100644
--- a/internal/engine/wazevo/backend/regalloc/regalloc.go
+++ b/internal/engine/wazevo/backend/regalloc/regalloc.go
@@ -410,6 +410,7 @@ func (a *Allocator) buildLiveRangesForReals(blkID int, info *blockInfo) {
 // Reset resets the allocator's internal state so that it can be reused.
 func (a *Allocator) Reset() {
 	a.nodePool.Reset()
+	a.blockInfos = a.blockInfos[:0]
 	for i := range a.vRegIDToNode {
 		a.vRegIDToNode[i] = nil
 	}
diff --git a/internal/engine/wazevo/frontend/frontend.go b/internal/engine/wazevo/frontend/frontend.go
index 473fd39d..8e2e9318 100644
--- a/internal/engine/wazevo/frontend/frontend.go
+++ b/internal/engine/wazevo/frontend/frontend.go
@@ -31,6 +31,9 @@ type Compiler struct {
 	wasmFunctionBody                      []byte
 	memoryBaseVariable, memoryLenVariable ssa.Variable
 	needMemory                            bool
+	globalVariables                       []ssa.Variable
+	globalVariablesTypes                  []ssa.Type
+	mutableGlobalVariablesIndexes         []wasm.Index // index to ^.
 	// br is reused during lowering.
 	br            *bytes.Reader
 	loweringState loweringState
@@ -172,7 +175,45 @@ func (c *Compiler) declareNecessaryVariables() {
 		c.memoryBaseVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64)
 		c.memoryLenVariable = c.ssaBuilder.DeclareVariable(ssa.TypeI64)
 	}
-	// TODO: add tables, globals.
+
+	c.globalVariables = c.globalVariables[:0]
+	c.mutableGlobalVariablesIndexes = c.mutableGlobalVariablesIndexes[:0]
+	c.globalVariablesTypes = c.globalVariablesTypes[:0]
+	for _, imp := range c.m.ImportSection {
+		if imp.Type == wasm.ExternTypeGlobal {
+			desc := imp.DescGlobal
+			c.declareWasmGlobal(desc.ValType, desc.Mutable)
+		}
+	}
+	for _, g := range c.m.GlobalSection {
+		desc := g.Type
+		c.declareWasmGlobal(desc.ValType, desc.Mutable)
+	}
+
+	// TODO: add tables.
+}
+
+func (c *Compiler) declareWasmGlobal(typ wasm.ValueType, mutable bool) {
+	var st ssa.Type
+	switch typ {
+	case wasm.ValueTypeI32:
+		st = ssa.TypeI32
+	case wasm.ValueTypeI64:
+		st = ssa.TypeI64
+	case wasm.ValueTypeF32:
+		st = ssa.TypeF32
+	case wasm.ValueTypeF64:
+		st = ssa.TypeF64
+	default:
+		panic("TODO: " + wasm.ValueTypeName(typ))
+	}
+	v := c.ssaBuilder.DeclareVariable(st)
+	index := wasm.Index(len(c.globalVariables))
+	c.globalVariables = append(c.globalVariables, v)
+	c.globalVariablesTypes = append(c.globalVariablesTypes, st)
+	if mutable {
+		c.mutableGlobalVariablesIndexes = append(c.mutableGlobalVariablesIndexes, index)
+	}
 }
 
 // wasmToSSA converts wasm.ValueType to ssa.Type.
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index bcd7168c..63d01de7 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -418,6 +418,20 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 		builder.InsertInstruction(ishl)
 		value := ishl.Return()
 		state.push(value)
+	case wasm.OpcodeGlobalGet:
+		index := c.readI32u()
+		if state.unreachable {
+			return
+		}
+		v := c.getWasmGlobalValue(index, false)
+		state.push(v)
+	case wasm.OpcodeGlobalSet:
+		index := c.readI32u()
+		if state.unreachable {
+			return
+		}
+		v := state.pop()
+		c.setWasmGlobalValue(index, v)
 	case wasm.OpcodeLocalGet:
 		index := c.readI32u()
 		if state.unreachable {
@@ -484,7 +498,7 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 		builder.InsertInstruction(extBaseAddr)
 
 		// Note: memLen is already zero extended to 64-bit space at the load time.
-		memLen := c.getMemoryLenValue()
+		memLen := c.getMemoryLenValue(false)
 
 		// baseAddrPlusCeil = baseAddr + ceil
 		baseAddrPlusCeil := builder.AllocateInstruction()
@@ -500,7 +514,7 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 		builder.InsertInstruction(exitIfNZ)
 
 		// Load the value from memBase + extBaseAddr.
-		memBase := c.getMemoryBaseValue()
+		memBase := c.getMemoryBaseValue(false)
 		addrCalc := builder.AllocateInstruction()
 		addrCalc.AsIadd(memBase, extBaseAddr.Return())
 		builder.InsertInstruction(addrCalc)
@@ -815,18 +829,15 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 		}
 
 		first, rest := call.Returns()
-		state.push(first)
+		if first.Valid() {
+			state.push(first)
+		}
 		for _, v := range rest {
 			state.push(v)
 		}
 
-		// After calling any function, memory buffer might have changed. So we need to re-defined the variable.
-		if c.needMemory {
-			// When these are not used in the following instructions, they will be optimized out.
-			// So in any ways, we define them!
-			_ = c.getMemoryBaseValue()
-			_ = c.getMemoryLenValue()
-		}
+		c.reloadAfterCall()
+
 	case wasm.OpcodeDrop:
 		_ = state.pop()
 	default:
@@ -834,24 +845,86 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 	}
 }
 
-func (c *Compiler) getMemoryBaseValue() ssa.Value {
+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!
+
+	// After calling any function, memory buffer might have changed. So we need to re-defined the variable.
+	if c.needMemory {
+		_ = c.getMemoryBaseValue(true)
+		_ = c.getMemoryLenValue(true)
+	}
+
+	// Also, any mutable Global can change.
+	for _, index := range c.mutableGlobalVariablesIndexes {
+		_ = c.getWasmGlobalValue(index, true)
+	}
+}
+
+// globalInstanceValueOffset is the offsetOf .Value field of wasm.GlobalInstance.
+const globalInstanceValueOffset = 8
+
+func (c *Compiler) setWasmGlobalValue(index wasm.Index, v ssa.Value) {
+	variable := c.globalVariables[index]
+	instanceOffset := c.offset.GlobalInstanceOffset(index)
+
+	builder := c.ssaBuilder
+	loadGlobalInstPtr := builder.AllocateInstruction()
+	loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(instanceOffset), ssa.TypeI64)
+	builder.InsertInstruction(loadGlobalInstPtr)
+
+	store := builder.AllocateInstruction()
+	store.AsStore(v, loadGlobalInstPtr.Return(), uint32(globalInstanceValueOffset))
+	builder.InsertInstruction(store)
+
+	// The value has changed to `v`, so we record it.
+	builder.DefineVariableInCurrentBB(variable, v)
+}
+
+func (c *Compiler) getWasmGlobalValue(index wasm.Index, forceLoad bool) ssa.Value {
+	variable := c.globalVariables[index]
+	typ := c.globalVariablesTypes[index]
+	instanceOffset := c.offset.GlobalInstanceOffset(index)
+
+	builder := c.ssaBuilder
+	if !forceLoad {
+		if v := builder.FindValue(variable); v.Valid() {
+			return v
+		}
+	}
+
+	loadGlobalInstPtr := builder.AllocateInstruction()
+	loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(instanceOffset), ssa.TypeI64)
+	builder.InsertInstruction(loadGlobalInstPtr)
+
+	load := builder.AllocateInstruction()
+	load.AsLoad(loadGlobalInstPtr.Return(), uint32(globalInstanceValueOffset), typ)
+	builder.InsertInstruction(load)
+	ret := load.Return()
+	builder.DefineVariableInCurrentBB(variable, ret)
+	return ret
+}
+
+func (c *Compiler) getMemoryBaseValue(forceReload bool) ssa.Value {
 	if c.offset.LocalMemoryBegin < 0 {
 		panic("TODO: imported memory")
 	}
-	return c.getModuleCtxValue(c.memoryBaseVariable, c.offset.LocalMemoryBase(), false)
+	return c.getModuleCtxValueI32Or64(c.memoryBaseVariable, c.offset.LocalMemoryBase(), false, forceReload)
 }
 
-func (c *Compiler) getMemoryLenValue() ssa.Value {
+func (c *Compiler) getMemoryLenValue(forceReload bool) ssa.Value {
 	if c.offset.LocalMemoryBegin < 0 {
 		panic("TODO: imported memory")
 	}
-	return c.getModuleCtxValue(c.memoryLenVariable, c.offset.LocalMemoryLen(), true)
+	return c.getModuleCtxValueI32Or64(c.memoryLenVariable, c.offset.LocalMemoryLen(), true, forceReload)
 }
 
-func (c *Compiler) getModuleCtxValue(variable ssa.Variable, offset wazevoapi.Offset, zeroExt bool) ssa.Value {
+func (c *Compiler) getModuleCtxValueI32Or64(variable ssa.Variable, offset wazevoapi.Offset, zeroExt bool, forceReload bool) ssa.Value {
 	builder := c.ssaBuilder
-	if v := builder.FindValue(variable); v.Valid() {
-		return v
+	if !forceReload {
+		if v := builder.FindValue(variable); v.Valid() {
+			return v
+		}
 	}
 	load := builder.AllocateInstruction()
 	if zeroExt {
diff --git a/internal/engine/wazevo/module_engine.go b/internal/engine/wazevo/module_engine.go
index d6e4b332..a8415dc4 100644
--- a/internal/engine/wazevo/module_engine.go
+++ b/internal/engine/wazevo/module_engine.go
@@ -28,11 +28,12 @@ type (
 	// 	    localMemoryBufferPtr                      *byte                (optional)
 	// 	    localMemoryLength                         uint64               (optional)
 	// 	    importedMemoryInstance                    *wasm.MemoryInstance (optional)
-	// 	    importedFunctions [len(vm.importedFunctions)] struct { the total size depends on # of imported functions.
+	// 	    importedFunctions [importedFunctions] struct { the total size depends on # of imported functions.
 	// 	        executable      *byte
 	// 	        opaqueCtx       *moduleContextOpaque
 	// 	    }
-	// 	    TODO: add more fields, like tables and globals
+	//      globals                                    []*wasm.GlobalInstance (optional)
+	// 	    TODO: add more fields, like tables, etc.
 	// 	}
 	//
 	// See wazevoapi.NewModuleContextOffsetData for the details of the offsets.
@@ -57,6 +58,14 @@ func (m *moduleEngine) setupOpaque() {
 	}
 
 	// Note: imported functions are resolved in ResolveImportedFunction.
+
+	if globalOffset := offsets.GlobalsBegin; globalOffset >= 0 {
+		for _, g := range inst.Globals {
+			b := uint64(uintptr(unsafe.Pointer(g)))
+			binary.LittleEndian.PutUint64(opaque[globalOffset:], b)
+			globalOffset += 8
+		}
+	}
 }
 
 // NewFunction implements wasm.ModuleEngine.
diff --git a/internal/engine/wazevo/ssa/builder.go b/internal/engine/wazevo/ssa/builder.go
index 282648af..668e903c 100644
--- a/internal/engine/wazevo/ssa/builder.go
+++ b/internal/engine/wazevo/ssa/builder.go
@@ -112,6 +112,9 @@ type Builder interface {
 
 	// ReturnBlock returns the BasicBlock which is used to return from the function.
 	ReturnBlock() BasicBlock
+
+	// InsertUndefined inserts an undefined instruction at the current position.
+	InsertUndefined()
 }
 
 // NewBuilder returns a new Builder implementation.
@@ -906,3 +909,10 @@ func swapInstruction(blk *basicBlock, old, New *Instruction) {
 	}
 	old.prev, old.next = nil, nil
 }
+
+// InsertUndefined implements Builder.InsertUndefined.
+func (b *builder) InsertUndefined() {
+	instr := b.AllocateInstruction()
+	instr.opcode = OpcodeUndefined
+	b.InsertInstruction(instr)
+}
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 38ed758a..b29cd7ba 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -116,6 +116,10 @@ func (i *Instruction) IsBranching() bool {
 const (
 	OpcodeInvalid Opcode = iota
 
+	// OpcodeUndefined is a placeholder for undefined opcode. This can be used for debugging to intentionally
+	// cause a crash at certain point.
+	OpcodeUndefined
+
 	// OpcodeJump takes the list of args to the `block` and unconditionally jumps to it.
 	OpcodeJump
 
@@ -826,6 +830,7 @@ const (
 // instructionSideEffects provides the info to determine if an instruction has side effects.
 // Instructions with side effects must not be eliminated regardless whether the result is used or not.
 var instructionSideEffects = [opcodeEnd]sideEffect{
+	OpcodeUndefined:             sideEffectTrue,
 	OpcodeJump:                  sideEffectTrue,
 	OpcodeIconst:                sideEffectFalse,
 	OpcodeCall:                  sideEffectTrue,
@@ -874,13 +879,14 @@ func (i *Instruction) HasSideEffects() bool {
 
 // instructionReturnTypes provides the function to determine the return types of an instruction.
 var instructionReturnTypes = [opcodeEnd]returnTypesFn{
-	OpcodeIshl:    returnTypesFnSingle,
-	OpcodeSshr:    returnTypesFnSingle,
-	OpcodeUshr:    returnTypesFnSingle,
-	OpcodeJump:    returnTypesFnNoReturns,
-	OpcodeIconst:  returnTypesFnSingle,
-	OpcodeSExtend: returnTypesFnSingle,
-	OpcodeUExtend: returnTypesFnSingle,
+	OpcodeIshl:      returnTypesFnSingle,
+	OpcodeSshr:      returnTypesFnSingle,
+	OpcodeUshr:      returnTypesFnSingle,
+	OpcodeJump:      returnTypesFnNoReturns,
+	OpcodeUndefined: returnTypesFnNoReturns,
+	OpcodeIconst:    returnTypesFnSingle,
+	OpcodeSExtend:   returnTypesFnSingle,
+	OpcodeUExtend:   returnTypesFnSingle,
 	OpcodeCallIndirect: func(b *builder, instr *Instruction) (t1 Type, ts []Type) {
 		sigID := SignatureID(instr.v)
 		sig, ok := b.signatures[sigID]
@@ -1454,6 +1460,8 @@ func (o Opcode) String() (ret string) {
 	switch o {
 	case OpcodeInvalid:
 		return "invalid"
+	case OpcodeUndefined:
+		return "undefined"
 	case OpcodeJump:
 		return "Jump"
 	case OpcodeBrz:
diff --git a/internal/engine/wazevo/wazevoapi/offsetdata.go b/internal/engine/wazevo/wazevoapi/offsetdata.go
index 8fd21fe2..55bbb832 100644
--- a/internal/engine/wazevo/wazevoapi/offsetdata.go
+++ b/internal/engine/wazevo/wazevoapi/offsetdata.go
@@ -43,15 +43,22 @@ type ExecutionContextOffsetData struct {
 // ModuleContextOffsetData allows the compilers to get the information about offsets to the fields of wazevo.moduleContextOpaque,
 // This is unique per module.
 type ModuleContextOffsetData struct {
-	TotalSize                                                     int
-	LocalMemoryBegin, ImportedMemoryBegin, ImportedFunctionsBegin Offset
+	TotalSize int
+	LocalMemoryBegin, ImportedMemoryBegin, ImportedFunctionsBegin,
+	GlobalsBegin Offset
 }
 
+// ImportedFunctionOffset returns an offset of the i-th imported function.
 func (m *ModuleContextOffsetData) ImportedFunctionOffset(i wasm.Index) (ptr, moduleCtx Offset) {
 	base := m.ImportedFunctionsBegin + Offset(i)*16
 	return base, base + 8
 }
 
+// GlobalInstanceOffset returns an offset of the i-th global instance.
+func (m *ModuleContextOffsetData) GlobalInstanceOffset(i wasm.Index) Offset {
+	return m.GlobalsBegin + Offset(i)*8
+}
+
 // Offset represents an offset of a field of a struct.
 type Offset int32
 
@@ -108,9 +115,19 @@ func NewModuleContextOffsetData(m *wasm.Module) ModuleContextOffsetData {
 	if m.ImportFunctionCount > 0 {
 		ret.ImportedFunctionsBegin = offset
 		// Each function consists of the pointer to the executable and the pointer to its moduleContextOpaque (16 bytes).
-		ret.TotalSize += int(m.ImportFunctionCount) * 16
+		size := int(m.ImportFunctionCount) * 16
+		offset += Offset(size)
+		ret.TotalSize += size
 	} else {
 		ret.ImportedFunctionsBegin = -1
 	}
+
+	if globals := int(m.ImportGlobalCount) + len(m.GlobalSection); globals > 0 {
+		ret.GlobalsBegin = offset
+		// Pointers to *wasm.GlobalInstance.
+		ret.TotalSize += globals * 8
+	} else {
+		ret.GlobalsBegin = -1
+	}
 	return ret
 }


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index b24828c3..9d6f0d4b 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -2505,6 +2505,124 @@ L1 (SSA Block: blk0):
 	ldr x18, [sp], #0x10
 	ldr x30, [sp], #0x10
 	ret
+`,
+		},
+		{
+			name: "globals_mutable[0]",
+			m:    testcases.GlobalsMutable.Module,
+			afterLoweringARM64: `
+L1 (SSA Block: blk0):
+	mov x0?, x0
+	mov x1?, x1
+	ldr x2?, [x1?]
+	ldr w3?, [x2?, #0x8]
+	ldr x4?, [x1?, #0x8]
+	ldr x5?, [x4?, #0x8]
+	ldr x6?, [x1?, #0x10]
+	ldr s7?, [x6?, #0x8]
+	ldr x8?, [x1?, #0x18]
+	ldr d9?, [x8?, #0x8]
+	str x1?, [x0?, #0x8]
+	mov x0, x0?
+	mov x1, x1?
+	bl f1
+	ldr x10?, [x1?]
+	ldr w11?, [x10?, #0x8]
+	ldr x12?, [x1?, #0x8]
+	ldr x13?, [x12?, #0x8]
+	ldr x14?, [x1?, #0x10]
+	ldr s15?, [x14?, #0x8]
+	ldr x16?, [x1?, #0x18]
+	ldr d17?, [x16?, #0x8]
+	mov q3.8b, q17?.8b
+	mov q2.8b, q15?.8b
+	mov x3, x13?
+	mov x2, x11?
+	mov q1.8b, q9?.8b
+	mov q0.8b, q7?.8b
+	mov x1, x5?
+	mov x0, x3?
+	ret
+`,
+			afterFinalizeARM64: `
+L1 (SSA Block: blk0):
+	str x30, [sp, #-0x10]!
+	sub sp, sp, #0x20
+	mov x10, x1
+	ldr x8, [x10]
+	ldr w8, [x8, #0x8]
+	ldr x9, [x10, #0x8]
+	ldr x9, [x9, #0x8]
+	ldr x11, [x10, #0x10]
+	ldr s0, [x11, #0x8]
+	ldr x11, [x10, #0x18]
+	ldr d1, [x11, #0x8]
+	str x10, [x0, #0x8]
+	mov x1, x10
+	str x10, [sp]
+	str w8, [sp, #0x8]
+	str x9, [sp, #0xc]
+	str s0, [sp, #0x14]
+	str d1, [sp, #0x18]
+	bl f1
+	ldr d1, [sp, #0x18]
+	ldr s0, [sp, #0x14]
+	ldr x9, [sp, #0xc]
+	ldr w8, [sp, #0x8]
+	ldr x10, [sp]
+	ldr x11, [x10]
+	ldr w2, [x11, #0x8]
+	ldr x11, [x10, #0x8]
+	ldr x3, [x11, #0x8]
+	ldr x11, [x10, #0x10]
+	ldr s2, [x11, #0x8]
+	ldr x10, [x10, #0x18]
+	ldr d3, [x10, #0x8]
+	mov x1, x9
+	mov x0, x8
+	add sp, sp, #0x20
+	ldr x30, [sp], #0x10
+	ret
+`,
+		},
+		{
+			name:        "globals_mutable[1]",
+			m:           testcases.GlobalsMutable.Module,
+			targetIndex: 1,
+			afterLoweringARM64: `
+L1 (SSA Block: blk0):
+	mov x1?, x1
+	ldr x3?, [x1?]
+	orr w13?, wzr, #0x1
+	str w13?, [x3?, #0x8]
+	ldr x5?, [x1?, #0x8]
+	orr x12?, xzr, #0x2
+	str x12?, [x5?, #0x8]
+	ldr x7?, [x1?, #0x10]
+	ldr s11?, #8; b 8; data.f32 3.000000
+	str s11?, [x7?, #0x8]
+	ldr x9?, [x1?, #0x18]
+	ldr d10?, #8; b 16; data.f64 4.000000
+	str d10?, [x9?, #0x8]
+	ret
+`,
+			afterFinalizeARM64: `
+L1 (SSA Block: blk0):
+	str x30, [sp, #-0x10]!
+	ldr x9, [x1]
+	orr w8, wzr, #0x1
+	str w8, [x9, #0x8]
+	ldr x9, [x1, #0x8]
+	orr x8, xzr, #0x2
+	str x8, [x9, #0x8]
+	ldr x8, [x1, #0x10]
+	ldr s8, #8; b 8; data.f32 3.000000
+	str s8, [x8, #0x8]
+	ldr x8, [x1, #0x18]
+	ldr d8, #8; b 16; data.f64 4.000000
+	str d8, [x8, #0x8]
+	ldr x30, [sp], #0x10
+	ret
 `,
 		},
 	} {
diff --git a/internal/engine/wazevo/backend/isa/arm64/abi_go_entry_test.go b/internal/engine/wazevo/backend/isa/arm64/abi_go_entry_test.go
index 66c77498..db8dbbf4 100644
--- a/internal/engine/wazevo/backend/isa/arm64/abi_go_entry_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/abi_go_entry_test.go
@@ -49,11 +49,11 @@ func TestAbiImpl_constructGoEntryPreamble(t *testing.T) {
 	str x27, [x18, #0x18]
 	str x30, [x18, #0x20]
 	mov sp, x26
-	ldr s0, [d19]
-	ldr s1, [d19, #0x8]
-	ldr s2, [d19, #0x10]
-	ldr s3, [d19, #0x18]
-	ldr d4, [d19, #0x20]
+	ldr s0, [x19]
+	ldr s1, [x19, #0x8]
+	ldr s2, [x19, #0x10]
+	ldr s3, [x19, #0x18]
+	ldr d4, [x19, #0x20]
 	bl #0x18
 	ldr x29, [x18, #0x10]
 	ldr x27, [x18, #0x18]
@@ -77,11 +77,11 @@ func TestAbiImpl_constructGoEntryPreamble(t *testing.T) {
 	str x27, [x18, #0x18]
 	str x30, [x18, #0x20]
 	mov sp, x26
-	ldr w2, [d19]
-	ldr w3, [d19, #0x8]
-	ldr w4, [d19, #0x10]
-	ldr x5, [d19, #0x18]
-	ldr w6, [d19, #0x20]
+	ldr w2, [x19]
+	ldr w3, [x19, #0x8]
+	ldr w4, [x19, #0x10]
+	ldr x5, [x19, #0x18]
+	ldr w6, [x19, #0x20]
 	bl #0x18
 	ldr x29, [x18, #0x10]
 	ldr x27, [x18, #0x18]
@@ -105,16 +105,16 @@ func TestAbiImpl_constructGoEntryPreamble(t *testing.T) {
 	str x27, [x18, #0x18]
 	str x30, [x18, #0x20]
 	mov sp, x26
-	ldr w2, [d19]
-	ldr d0, [d19, #0x8]
-	ldr w3, [d19, #0x10]
-	ldr s1, [d19, #0x18]
-	ldr x4, [d19, #0x20]
-	ldr w5, [d19, #0x28]
-	ldr x6, [d19, #0x30]
-	ldr d2, [d19, #0x38]
-	ldr w7, [d19, #0x40]
-	ldr s3, [d19, #0x48]
+	ldr w2, [x19]
+	ldr d0, [x19, #0x8]
+	ldr w3, [x19, #0x10]
+	ldr s1, [x19, #0x18]
+	ldr x4, [x19, #0x20]
+	ldr w5, [x19, #0x28]
+	ldr x6, [x19, #0x30]
+	ldr d2, [x19, #0x38]
+	ldr w7, [x19, #0x40]
+	ldr s3, [x19, #0x48]
 	bl #0x18
 	ldr x29, [x18, #0x10]
 	ldr x27, [x18, #0x18]
@@ -139,19 +139,19 @@ func TestAbiImpl_constructGoEntryPreamble(t *testing.T) {
 	str x27, [x18, #0x18]
 	str x30, [x18, #0x20]
 	mov sp, x26
-	ldr w2, [d19]
-	ldr d0, [d19, #0x8]
-	ldr w3, [d19, #0x10]
-	ldr s1, [d19, #0x18]
-	ldr x4, [d19, #0x20]
+	ldr w2, [x19]
+	ldr d0, [x19, #0x8]
+	ldr w3, [x19, #0x10]
+	ldr s1, [x19, #0x18]
+	ldr x4, [x19, #0x20]
 	bl #0x34
-	str s0, [d19], #0x8
-	str d1, [d19], #0x8
-	str w0, [d19], #0x8
-	str s2, [d19], #0x8
-	str x1, [d19], #0x8
-	str w2, [d19], #0x8
-	str d3, [d19], #0x8
+	str s0, [x19], #0x8
+	str d1, [x19], #0x8
+	str w0, [x19], #0x8
+	str s2, [x19], #0x8
+	str x1, [x19], #0x8
+	str w2, [x19], #0x8
+	str d3, [x19], #0x8
 	ldr x29, [x18, #0x10]
 	ldr x27, [x18, #0x18]
 	mov sp, x27
@@ -176,32 +176,32 @@ func TestAbiImpl_constructGoEntryPreamble(t *testing.T) {
 	sub x26, x26, #0x30
 	mov sp, x26
 	bl #0x80
-	str s0, [d19], #0x8
-	str d1, [d19], #0x8
-	str w0, [d19], #0x8
-	str s2, [d19], #0x8
-	str x1, [d19], #0x8
-	str w2, [d19], #0x8
-	str w3, [d19], #0x8
-	str x4, [d19], #0x8
-	str w5, [d19], #0x8
-	str x6, [d19], #0x8
-	str s3, [d19], #0x8
-	str d4, [d19], #0x8
-	str w7, [d19], #0x8
-	str s5, [d19], #0x8
+	str s0, [x19], #0x8
+	str d1, [x19], #0x8
+	str w0, [x19], #0x8
+	str s2, [x19], #0x8
+	str x1, [x19], #0x8
+	str w2, [x19], #0x8
+	str w3, [x19], #0x8
+	str x4, [x19], #0x8
+	str w5, [x19], #0x8
+	str x6, [x19], #0x8
+	str s3, [x19], #0x8
+	str d4, [x19], #0x8
+	str w7, [x19], #0x8
+	str s5, [x19], #0x8
 	ldr x27, [sp]
-	str x27, [d19], #0x8
+	str x27, [x19], #0x8
 	ldr w27, [sp, #0x8]
-	str w27, [d19], #0x8
+	str w27, [x19], #0x8
 	ldr w27, [sp, #0x10]
-	str w27, [d19], #0x8
+	str w27, [x19], #0x8
 	ldr x27, [sp, #0x18]
-	str x27, [d19], #0x8
+	str x27, [x19], #0x8
 	ldr w27, [sp, #0x20]
-	str w27, [d19], #0x8
+	str w27, [x19], #0x8
 	ldr x27, [sp, #0x28]
-	str x27, [d19], #0x8
+	str x27, [x19], #0x8
 	ldr x29, [x18, #0x10]
 	ldr x27, [x18, #0x18]
 	mov sp, x27
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 944cce0e..092c0a0a 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -115,6 +115,26 @@ func TestE2E(t *testing.T) {
 				{params: []uint64{0xf}, expResults: []uint64{0x1211100f, 0x161514131211100f, 0x1211100f, 0x161514131211100f, 0x21201f1e, 0x2524232221201f1e, 0x21201f1e, 0x2524232221201f1e, 0xf, 0x1e, 0xf, 0x1e, 0x100f, 0x1f1e, 0x100f, 0x1f1e, 0xf, 0x1e, 0xf, 0x1e, 0x100f, 0x1f1e, 0x100f, 0x1f1e, 0x1211100f, 0x21201f1e, 0x1211100f, 0x21201f1e}},
 			},
 		},
+		{
+			name: "globals_get",
+			m:    testcases.GlobalsGet.Module,
+			calls: []callCase{
+				{expResults: []uint64{0x80000000, 0x8000000000000000, 0x7f7fffff, 0x7fefffffffffffff}},
+			},
+		},
+		{
+			name:  "globals_set",
+			m:     testcases.GlobalsSet.Module,
+			calls: []callCase{{expResults: []uint64{1, 2, uint64(math.Float32bits(3.0)), math.Float64bits(4.0)}}},
+		},
+		{
+			name: "globals_mutable",
+			m:    testcases.GlobalsMutable.Module,
+			calls: []callCase{{expResults: []uint64{
+				100, 200, uint64(math.Float32bits(300.0)), math.Float64bits(400.0),
+				1, 2, uint64(math.Float32bits(3.0)), math.Float64bits(4.0),
+			}}},
+		},
 	} {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
diff --git a/internal/engine/wazevo/frontend/frontend_test.go b/internal/engine/wazevo/frontend/frontend_test.go
index 76856dd8..ca677d9f 100644
--- a/internal/engine/wazevo/frontend/frontend_test.go
+++ b/internal/engine/wazevo/frontend/frontend_test.go
@@ -1159,9 +1159,100 @@ blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
 	v171:i64 = Iadd v8, v168
 	v172:i64 = Uload32 v171, 0xf
 	Jump blk_ret, v10, v16, v22, v28, v34, v40, v46, v52, v58, v64, v70, v76, v82, v88, v94, v100, v106, v112, v118, v124, v130, v136, v142, v148, v154, v160, v166, v172
+`,
+		},
+		{
+			name: "globals_get",
+			m:    testcases.GlobalsGet.Module,
+			exp: `
+blk0: (exec_ctx:i64, module_ctx:i64)
+	v2:i64 = Load module_ctx, 0x0
+	v3:i32 = Load v2, 0x8
+	v4:i64 = Load module_ctx, 0x8
+	v5:i64 = Load v4, 0x8
+	v6:i64 = Load module_ctx, 0x10
+	v7:f32 = Load v6, 0x8
+	v8:i64 = Load module_ctx, 0x18
+	v9:f64 = Load v8, 0x8
+	Jump blk_ret, v3, v5, v7, v9
+`,
+		},
+		{
+			name: "globals_set",
+			m:    testcases.GlobalsSet.Module,
+			exp: `
+blk0: (exec_ctx:i64, module_ctx:i64)
+	v2:i32 = Iconst_32 0x1
+	v3:i64 = Load module_ctx, 0x0
+	Store v2, v3, 0x8
+	v4:i64 = Iconst_64 0x2
+	v5:i64 = Load module_ctx, 0x8
+	Store v4, v5, 0x8
+	v6:f32 = F32const 3.000000
+	v7:i64 = Load module_ctx, 0x10
+	Store v6, v7, 0x8
+	v8:f64 = F64const 4.000000
+	v9:i64 = Load module_ctx, 0x18
+	Store v8, v9, 0x8
+	Jump blk_ret, v2, v4, v6, v8
+`,
+		},
+		{
+			name: "globals_mutable",
+			m:    testcases.GlobalsMutable.Module,
+			exp: `
+signatures:
+	sig1: i64i64_v
+
+blk0: (exec_ctx:i64, module_ctx:i64)
+	v2:i64 = Load module_ctx, 0x0
+	v3:i32 = Load v2, 0x8
+	v4:i64 = Load module_ctx, 0x8
+	v5:i64 = Load v4, 0x8
+	v6:i64 = Load module_ctx, 0x10
+	v7:f32 = Load v6, 0x8
+	v8:i64 = Load module_ctx, 0x18
+	v9:f64 = Load v8, 0x8
+	Store module_ctx, exec_ctx, 0x8
+	Call f1:sig1, exec_ctx, module_ctx
+	v10:i64 = Load module_ctx, 0x0
+	v11:i32 = Load v10, 0x8
+	v12:i64 = Load module_ctx, 0x8
+	v13:i64 = Load v12, 0x8
+	v14:i64 = Load module_ctx, 0x10
+	v15:f32 = Load v14, 0x8
+	v16:i64 = Load module_ctx, 0x18
+	v17:f64 = Load v16, 0x8
+	Jump blk_ret, v3, v5, v7, v9, v11, v13, v15, v17
+`,
+			expAfterOpt: `
+signatures:
+	sig1: i64i64_v
+
+blk0: (exec_ctx:i64, module_ctx:i64)
+	v2:i64 = Load module_ctx, 0x0
+	v3:i32 = Load v2, 0x8
+	v4:i64 = Load module_ctx, 0x8
+	v5:i64 = Load v4, 0x8
+	v6:i64 = Load module_ctx, 0x10
+	v7:f32 = Load v6, 0x8
+	v8:i64 = Load module_ctx, 0x18
+	v9:f64 = Load v8, 0x8
+	Store module_ctx, exec_ctx, 0x8
+	Call f1:sig1, exec_ctx, module_ctx
+	v10:i64 = Load module_ctx, 0x0
+	v11:i32 = Load v10, 0x8
+	v12:i64 = Load module_ctx, 0x8
+	v13:i64 = Load v12, 0x8
+	v14:i64 = Load module_ctx, 0x10
+	v15:f32 = Load v14, 0x8
+	v16:i64 = Load module_ctx, 0x18
+	v17:f64 = Load v16, 0x8
+	Jump blk_ret, v3, v5, v7, v9, v11, v13, v15, v17
 `,
 		},
 	} {
+
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
 			// Just in case let's check the test module is valid.
diff --git a/internal/engine/wazevo/frontend/lower_test.go b/internal/engine/wazevo/frontend/lower_test.go
new file mode 100644
index 00000000..9d4983aa
--- /dev/null
+++ b/internal/engine/wazevo/frontend/lower_test.go
@@ -0,0 +1,14 @@
+package frontend
+
+import (
+	"testing"
+	"unsafe"
+
+	"github.com/tetratelabs/wazero/internal/testing/require"
+	"github.com/tetratelabs/wazero/internal/wasm"
+)
+
+func TestGlobalInstanceValueOffset(t *testing.T) {
+	var globalInstance wasm.GlobalInstance
+	require.Equal(t, int(unsafe.Offsetof(globalInstance.Val)), globalInstanceValueOffset)
+}
diff --git a/internal/engine/wazevo/module_engine_test.go b/internal/engine/wazevo/module_engine_test.go
index 9a63980b..46ac3f43 100644
--- a/internal/engine/wazevo/module_engine_test.go
+++ b/internal/engine/wazevo/module_engine_test.go
@@ -21,6 +21,7 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 				LocalMemoryBegin:       10,
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: -1,
+				GlobalsBegin:           -1,
 			},
 			m: &wasm.ModuleInstance{MemoryInstance: &wasm.MemoryInstance{
 				Buffer: make([]byte, 0xff),
@@ -30,12 +31,24 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 			offset: wazevoapi.ModuleContextOffsetData{
 				LocalMemoryBegin:       -1,
 				ImportedMemoryBegin:    30,
+				GlobalsBegin:           -1,
 				ImportedFunctionsBegin: -1,
 			},
 			m: &wasm.ModuleInstance{MemoryInstance: &wasm.MemoryInstance{
 				Buffer: make([]byte, 0xff),
 			}},
 		},
+		{
+			offset: wazevoapi.ModuleContextOffsetData{
+				LocalMemoryBegin:       -1,
+				ImportedMemoryBegin:    -1,
+				ImportedFunctionsBegin: -1,
+				GlobalsBegin:           30,
+			},
+			m: &wasm.ModuleInstance{
+				Globals: []*wasm.GlobalInstance{{}, {}, {}, {}, {}, {}},
+			},
+		},
 	} {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
 			tc.offset.TotalSize = 1000 // arbitrary large number to ensure we don't panic.
@@ -59,6 +72,13 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 				expPtr := uintptr(unsafe.Pointer(&tc.m.MemoryInstance))
 				require.Equal(t, expPtr, actualPtr)
 			}
+			if tc.offset.GlobalsBegin >= 0 {
+				for i, g := range tc.m.Globals {
+					actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.GlobalsBegin)+8*i:]))
+					expPtr := uintptr(unsafe.Pointer(g))
+					require.Equal(t, expPtr, actualPtr)
+				}
+			}
 		})
 	}
 }
diff --git a/internal/engine/wazevo/testcases/testcases.go b/internal/engine/wazevo/testcases/testcases.go
index f7000d65..a01d7f54 100644
--- a/internal/engine/wazevo/testcases/testcases.go
+++ b/internal/engine/wazevo/testcases/testcases.go
@@ -794,7 +794,7 @@ var (
 				wasm.OpcodeI32Load, 0x2, 0x0, // alignment=2 (natural alignment) staticOffset=0
 				wasm.OpcodeEnd,
 			}}},
-			DataSection: []wasm.DataSegment{{OffsetExpression: constOffsetExpr(0), Init: maskedBuf(int(wasm.MemoryPageSize))}},
+			DataSection: []wasm.DataSegment{{OffsetExpression: constExprI32(0), Init: maskedBuf(int(wasm.MemoryPageSize))}},
 		},
 	}
 
@@ -823,7 +823,142 @@ var (
 				}},
 				{Body: []byte{wasm.OpcodeEnd}},
 			},
-			DataSection: []wasm.DataSegment{{OffsetExpression: constOffsetExpr(0), Init: maskedBuf(int(wasm.MemoryPageSize))}},
+			DataSection: []wasm.DataSegment{{OffsetExpression: constExprI32(0), Init: maskedBuf(int(wasm.MemoryPageSize))}},
+		},
+	}
+
+	GlobalsGet = TestCase{
+		Name: "globals_get",
+		Module: &wasm.Module{
+			TypeSection:     []wasm.FunctionType{{Results: []wasm.ValueType{i32, i64, f32, f64}}},
+			ExportSection:   []wasm.Export{{Name: ExportName, Type: wasm.ExternTypeFunc, Index: 0}},
+			FunctionSection: []wasm.Index{0},
+			GlobalSection: []wasm.Global{
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: false},
+					Init: constExprI32(math.MinInt32),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: false},
+					Init: constExprI64(math.MinInt64),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeF32, Mutable: false},
+					Init: constExprF32(math.MaxFloat32),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeF64, Mutable: false},
+					Init: constExprF64(math.MaxFloat64),
+				},
+			},
+			CodeSection: []wasm.Code{
+				{Body: []byte{
+					wasm.OpcodeGlobalGet, 0,
+					wasm.OpcodeGlobalGet, 1,
+					wasm.OpcodeGlobalGet, 2,
+					wasm.OpcodeGlobalGet, 3,
+					wasm.OpcodeEnd,
+				}},
+			},
+		},
+	}
+
+	GlobalsSet = TestCase{
+		Name: "globals_get",
+		Module: &wasm.Module{
+			TypeSection:     []wasm.FunctionType{{Results: []wasm.ValueType{i32, i64, f32, f64}}},
+			ExportSection:   []wasm.Export{{Name: ExportName, Type: wasm.ExternTypeFunc, Index: 0}},
+			FunctionSection: []wasm.Index{0},
+			GlobalSection: []wasm.Global{
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true},
+					Init: constExprI32(0),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
+					Init: constExprI64(0),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeF32, Mutable: true},
+					Init: constExprF32(0),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeF64, Mutable: true},
+					Init: constExprF64(0),
+				},
+			},
+			CodeSection: []wasm.Code{
+				{Body: []byte{
+					wasm.OpcodeI32Const, 1,
+					wasm.OpcodeGlobalSet, 0,
+					wasm.OpcodeGlobalGet, 0,
+					wasm.OpcodeI64Const, 2,
+					wasm.OpcodeGlobalSet, 1,
+					wasm.OpcodeGlobalGet, 1,
+					wasm.OpcodeF32Const, 0, 0, 64, 64, // 3.0
+					wasm.OpcodeGlobalSet, 2,
+					wasm.OpcodeGlobalGet, 2,
+					wasm.OpcodeF64Const, 0, 0, 0, 0, 0, 0, 16, 64, // 4.0
+					wasm.OpcodeGlobalSet, 3,
+					wasm.OpcodeGlobalGet, 3,
+					wasm.OpcodeEnd,
+				}},
+			},
+		},
+	}
+
+	GlobalsMutable = TestCase{
+		Module: &wasm.Module{
+			TypeSection: []wasm.FunctionType{
+				{Results: []wasm.ValueType{i32, i64, f32, f64, i32, i64, f32, f64}},
+				{},
+			},
+			ExportSection:   []wasm.Export{{Name: ExportName, Type: wasm.ExternTypeFunc, Index: 0}},
+			FunctionSection: []wasm.Index{0, 1},
+			GlobalSection: []wasm.Global{
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true},
+					Init: constExprI32(100),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
+					Init: constExprI64(200),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeF32, Mutable: true},
+					Init: constExprF32(300.0),
+				},
+				{
+					Type: wasm.GlobalType{ValType: wasm.ValueTypeF64, Mutable: true},
+					Init: constExprF64(400.0),
+				},
+			},
+			CodeSection: []wasm.Code{
+				{Body: []byte{
+					wasm.OpcodeGlobalGet, 0,
+					wasm.OpcodeGlobalGet, 1,
+					wasm.OpcodeGlobalGet, 2,
+					wasm.OpcodeGlobalGet, 3,
+					wasm.OpcodeCall, 1,
+					wasm.OpcodeGlobalGet, 0,
+					wasm.OpcodeGlobalGet, 1,
+					wasm.OpcodeGlobalGet, 2,
+					wasm.OpcodeGlobalGet, 3,
+					wasm.OpcodeEnd,
+				}},
+				{Body: []byte{
+					wasm.OpcodeI32Const, 1,
+					wasm.OpcodeGlobalSet, 0,
+					wasm.OpcodeI64Const, 2,
+					wasm.OpcodeGlobalSet, 1,
+					wasm.OpcodeF32Const, 0, 0, 64, 64, // 3.0
+					wasm.OpcodeGlobalSet, 2,
+					wasm.OpcodeF64Const, 0, 0, 0, 0, 0, 0, 16, 64, // 4.0
+					wasm.OpcodeGlobalSet, 3,
+					wasm.OpcodeReturn,
+					wasm.OpcodeEnd,
+				}},
+			},
 		},
 	}
 
@@ -906,7 +1041,7 @@ var (
 
 				wasm.OpcodeEnd,
 			}}},
-			DataSection: []wasm.DataSegment{{OffsetExpression: constOffsetExpr(0), Init: maskedBuf(int(wasm.MemoryPageSize))}},
+			DataSection: []wasm.DataSegment{{OffsetExpression: constExprI32(0), Init: maskedBuf(int(wasm.MemoryPageSize))}},
 		},
 	}
 )
@@ -958,9 +1093,35 @@ func maskedBuf(size int) []byte {
 	return ret
 }
 
-func constOffsetExpr(i int32) wasm.ConstantExpression {
+func constExprI32(i int32) wasm.ConstantExpression {
 	return wasm.ConstantExpression{
 		Opcode: wasm.OpcodeI32Const,
 		Data:   leb128.EncodeInt32(i),
 	}
 }
+
+func constExprI64(i int64) wasm.ConstantExpression {
+	return wasm.ConstantExpression{
+		Opcode: wasm.OpcodeI64Const,
+		Data:   leb128.EncodeInt64(i),
+	}
+}
+
+func constExprF32(i float32) wasm.ConstantExpression {
+	b := math.Float32bits(i)
+	return wasm.ConstantExpression{
+		Opcode: wasm.OpcodeF32Const,
+		Data:   []byte{byte(b), byte(b >> 8), byte(b >> 16), byte(b >> 24)},
+	}
+}
+
+func constExprF64(i float64) wasm.ConstantExpression {
+	b := math.Float64bits(i)
+	return wasm.ConstantExpression{
+		Opcode: wasm.OpcodeF64Const,
+		Data: []byte{
+			byte(b), byte(b >> 8), byte(b >> 16), byte(b >> 24),
+			byte(b >> 32), byte(b >> 40), byte(b >> 48), byte(b >> 56),
+		},
+	}
+}
diff --git a/internal/engine/wazevo/wazevoapi/offsetdata_test.go b/internal/engine/wazevo/wazevoapi/offsetdata_test.go
index f5b1c741..dea56b70 100644
--- a/internal/engine/wazevo/wazevoapi/offsetdata_test.go
+++ b/internal/engine/wazevo/wazevoapi/offsetdata_test.go
@@ -20,6 +20,7 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				LocalMemoryBegin:       -1,
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: -1,
+				GlobalsBegin:           -1,
 				TotalSize:              0,
 			},
 		},
@@ -30,6 +31,7 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				LocalMemoryBegin:       0,
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: -1,
+				GlobalsBegin:           -1,
 				TotalSize:              16,
 			},
 		},
@@ -40,6 +42,7 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				LocalMemoryBegin:       -1,
 				ImportedMemoryBegin:    0,
 				ImportedFunctionsBegin: -1,
+				GlobalsBegin:           -1,
 				TotalSize:              8,
 			},
 		},
@@ -50,6 +53,7 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				LocalMemoryBegin:       -1,
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: 0,
+				GlobalsBegin:           -1,
 				TotalSize:              160,
 			},
 		},
@@ -60,17 +64,24 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				LocalMemoryBegin:       -1,
 				ImportedMemoryBegin:    0,
 				ImportedFunctionsBegin: 8,
+				GlobalsBegin:           -1,
 				TotalSize:              168,
 			},
 		},
 		{
-			name: "local mem / imported func",
-			m:    &wasm.Module{MemorySection: &wasm.Memory{}, ImportFunctionCount: 10},
+			name: "local mem / imported func / globals",
+			m: &wasm.Module{
+				ImportGlobalCount:   10,
+				ImportFunctionCount: 10,
+				MemorySection:       &wasm.Memory{},
+				GlobalSection:       make([]wasm.Global, 20),
+			},
 			exp: ModuleContextOffsetData{
 				LocalMemoryBegin:       0,
 				ImportedMemoryBegin:    -1,
 				ImportedFunctionsBegin: 16,
-				TotalSize:              176,
+				GlobalsBegin:           16 + 16*10,
+				TotalSize:              16 + 16*10 + 8*30,
 			},
 		},
 	} {