Repository: tetratelabs/wazero
Pull request #1889 (2023-12-21 21:40:23)

wazevo(frontend): faster non-imported global access
This allows the faster access to the non-imported globals. Basically,
this eliminates **one load instruction per global.get/set**.

In doing so, I changed how to hold global values and wazevo no longer
uses `wasm.GlobalInstance.Value` field to hold the runtime value of the global.




```
$ benchstat old.txt new.txt
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/stdlibs
                                                          │   old.txt   │               new.txt               │
                                                          │   sec/op    │   sec/op     vs base                │
Zig/optimizing/Run/test-opt.wasm-10                          21.22 ± 5%    20.34 ± 5%        ~ (p=0.075 n=10)
Zig/optimizing/Compile/test-opt.wasm-10                      6.715 ± 3%    6.586 ± 2%        ~ (p=0.393 n=10)
Zig/optimizing/Run/test.wasm-10                              22.53 ± 3%    20.84 ± 6%   -7.50% (p=0.002 n=10)
Zig/optimizing/Compile/test.wasm-10                          6.621 ± 5%    6.121 ± 5%   -7.55% (p=0.004 n=10)
TinyGo/optimizing/Run/container_heap.test-10                15.40m ± 4%   14.09m ± 4%   -8.51% (p=0.001 n=7)
TinyGo/optimizing/Compile/container_heap.test-10            493.8m ± 3%   437.3m ± 2%  -11.44% (p=0.001 n=7)
TinyGo/optimizing/Run/container_list.test-10                14.96m ± 0%   13.56m ± 0%   -9.39% (p=0.001 n=7)
TinyGo/optimizing/Compile/container_list.test-10            487.8m ± 4%   434.9m ± 0%  -10.84% (p=0.001 n=7)
TinyGo/optimizing/Run/container_ring.test-10                14.69m ± 4%   13.41m ± 0%   -8.67% (p=0.001 n=7)
TinyGo/optimizing/Compile/container_ring.test-10            479.1m ± 8%   426.3m ± 0%  -11.01% (p=0.001 n=7)
TinyGo/optimizing/Run/crypto_des.test-10                    18.16m ± 1%   17.53m ± 0%   -3.48% (p=0.001 n=7)
TinyGo/optimizing/Compile/crypto_des.test-10                467.6m ± 4%   442.0m ± 0%   -5.48% (p=0.001 n=7)
TinyGo/optimizing/Run/crypto_md5.test-10                    19.96m ± 2%   19.75m ± 1%        ~ (p=0.456 n=7)
TinyGo/optimizing/Compile/crypto_md5.test-10                472.0m ± 3%   436.9m ± 0%   -7.43% (p=0.001 n=7)
TinyGo/optimizing/Run/crypto_rc4.test-10                    167.4m ± 0%   162.8m ± 0%   -2.76% (p=0.001 n=7)
TinyGo/optimizing/Compile/crypto_rc4.test-10                444.6m ± 3%   421.3m ± 0%   -5.24% (p=0.001 n=7)
TinyGo/optimizing/Run/crypto_sha1.test-10                   15.86m ± 1%   15.09m ± 0%   -4.85% (p=0.001 n=7)
TinyGo/optimizing/Compile/crypto_sha1.test-10               464.5m ± 2%   436.6m ± 0%   -6.00% (p=0.001 n=7)
TinyGo/optimizing/Run/crypto_sha256.test-10                 16.02m ± 3%   15.36m ± 0%   -4.11% (p=0.001 n=7)
TinyGo/optimizing/Compile/crypto_sha256.test-10             454.3m ± 0%   444.9m ± 0%   -2.07% (p=0.001 n=7)
TinyGo/optimizing/Run/crypto_sha512.test-10                 18.46m ± 0%   18.12m ± 0%   -1.80% (p=0.001 n=7)
TinyGo/optimizing/Compile/crypto_sha512.test-10             454.8m ± 0%   448.1m ± 1%   -1.49% (p=0.001 n=7)
TinyGo/optimizing/Run/encoding_ascii85.test-10              14.16m ± 1%   14.11m ± 0%   -0.32% (p=0.017 n=7)
TinyGo/optimizing/Compile/encoding_ascii85.test-10          442.2m ± 0%   436.6m ± 0%   -1.25% (p=0.001 n=7)
TinyGo/optimizing/Run/encoding_base32.test-10               92.23m ± 0%   91.50m ± 0%   -0.80% (p=0.001 n=7)
TinyGo/optimizing/Compile/encoding_base32.test-10           590.8m ± 1%   582.1m ± 0%   -1.46% (p=0.001 n=7)
TinyGo/optimizing/Run/encoding_csv.test-10                  24.34m ± 0%   24.18m ± 0%   -0.64% (p=0.001 n=7)
TinyGo/optimizing/Compile/encoding_csv.test-10              472.9m ± 0%   468.1m ± 1%   -1.02% (p=0.004 n=7)
TinyGo/optimizing/Run/encoding_hex.test-10                  10.13m ± 0%   10.11m ± 1%        ~ (p=0.259 n=7)
TinyGo/optimizing/Compile/encoding_hex.test-10              460.5m ± 2%   452.7m ± 0%   -1.68% (p=0.001 n=7)
TinyGo/optimizing/Run/go_scanner.test-10                    15.99m ± 1%   15.97m ± 0%        ~ (p=0.902 n=7)
TinyGo/optimizing/Compile/go_scanner.test-10                491.1m ± 0%   485.5m ± 1%   -1.15% (p=0.007 n=7)
TinyGo/optimizing/Run/hash.test-10                          15.42m ± 0%   15.52m ± 0%   +0.64% (p=0.001 n=7)
TinyGo/optimizing/Compile/hash.test-10                      574.0m ± 0%   568.1m ± 0%   -1.03% (p=0.001 n=7)
TinyGo/optimizing/Run/hash_adler32.test-10                  23.91m ± 0%   24.67m ± 1%   +3.20% (p=0.001 n=7)
TinyGo/optimizing/Compile/hash_adler32.test-10              426.1m ± 0%   421.5m ± 0%   -1.07% (p=0.001 n=7)
TinyGo/optimizing/Run/hash_crc64.test-10                    16.90m ± 1%   16.84m ± 0%   -0.34% (p=0.001 n=7)
TinyGo/optimizing/Compile/hash_crc64.test-10                536.7m ± 0%   532.0m ± 2%        ~ (p=0.209 n=7)
TinyGo/optimizing/Run/hash_fnv.test-10                      13.94m ± 0%   13.98m ± 0%   +0.25% (p=0.017 n=7)
TinyGo/optimizing/Compile/hash_fnv.test-10                  450.3m ± 0%   445.7m ± 0%   -1.02% (p=0.001 n=7)
TinyGo/optimizing/Run/html.test-10                          15.29m ± 0%   15.32m ± 0%        ~ (p=0.073 n=7)
TinyGo/optimizing/Compile/html.test-10                       3.614 ± 1%    3.580 ± 1%   -0.94% (p=0.004 n=7)
TinyGo/optimizing/Run/internal_itoa.test-10                 13.31m ± 0%   13.32m ± 0%        ~ (p=0.456 n=7)
TinyGo/optimizing/Compile/internal_itoa.test-10             420.5m ± 0%   417.6m ± 0%   -0.69% (p=0.001 n=7)
TinyGo/optimizing/Run/internal_profile.test-10              8.898m ± 0%   8.933m ± 0%   +0.40% (p=0.042 n=7)
TinyGo/optimizing/Compile/internal_profile.test-10          564.1m ± 0%   557.6m ± 1%   -1.14% (p=0.001 n=7)
TinyGo/optimizing/Run/math.test-10                          8.897m ± 1%   8.914m ± 0%        ~ (p=0.165 n=7)
TinyGo/optimizing/Compile/math.test-10                      563.1m ± 2%   556.4m ± 0%   -1.18% (p=0.001 n=7)
TinyGo/optimizing/Run/math_cmplx.test-10                    15.02m ± 0%   15.13m ± 0%   +0.78% (p=0.001 n=7)
TinyGo/optimizing/Compile/math_cmplx.test-10                495.0m ± 2%   489.9m ± 0%   -1.03% (p=0.001 n=7)
TinyGo/optimizing/Run/net.test-10                            1.166 ± 1%    1.165 ± 1%        ~ (p=0.805 n=7)
TinyGo/optimizing/Compile/net.test-10                       504.4m ± 0%   499.1m ± 0%   -1.05% (p=0.001 n=7)
TinyGo/optimizing/Run/net_http_internal_ascii.test-10       13.35m ± 1%   13.36m ± 0%        ~ (p=0.620 n=7)
TinyGo/optimizing/Compile/net_http_internal_ascii.test-10   422.0m ± 0%   417.6m ± 0%   -1.05% (p=0.001 n=7)
TinyGo/optimizing/Run/net_mail.test-10                      13.88m ± 1%   13.90m ± 1%        ~ (p=0.620 n=7)
TinyGo/optimizing/Compile/net_mail.test-10                  575.6m ± 0%   574.6m ± 2%        ~ (p=0.318 n=7)
TinyGo/optimizing/Run/os.test-10                            10.49m ± 1%   10.52m ± 1%        ~ (p=0.383 n=7)
TinyGo/optimizing/Compile/os.test-10                        537.1m ± 1%   533.3m ± 0%   -0.70% (p=0.001 n=7)
TinyGo/optimizing/Run/path.test-10                          14.01m ± 1%   14.00m ± 0%        ~ (p=0.805 n=7)
TinyGo/optimizing/Compile/path.test-10                      433.3m ± 0%   429.6m ± 0%   -0.86% (p=0.001 n=7)
TinyGo/optimizing/Run/reflect.test-10                       35.65m ± 0%   36.06m ± 2%   +1.15% (p=0.001 n=7)
TinyGo/optimizing/Compile/reflect.test-10                   604.1m ± 0%   598.9m ± 0%   -0.87% (p=0.001 n=7)
TinyGo/optimizing/Run/sync.test-10                          10.82m ± 1%   10.83m ± 1%        ~ (p=0.620 n=7)
TinyGo/optimizing/Compile/sync.test-10                      442.3m ± 0%   439.0m ± 2%   -0.74% (p=0.026 n=7)
TinyGo/optimizing/Run/testing.test-10                        4.413 ± 2%    4.446 ± 1%        ~ (p=0.073 n=7)
TinyGo/optimizing/Compile/testing.test-10                   479.3m ± 1%   474.4m ± 0%   -1.02% (p=0.001 n=7)
TinyGo/optimizing/Run/testing_iotest.test-10                14.29m ± 0%   14.27m ± 0%        ~ (p=0.535 n=7)
TinyGo/optimizing/Compile/testing_iotest.test-10            461.0m ± 0%   456.4m ± 3%   -1.01% (p=0.026 n=7)
TinyGo/optimizing/Run/text_scanner.test-10                  17.92m ± 0%   17.88m ± 1%        ~ (p=0.383 n=7)
TinyGo/optimizing/Compile/text_scanner.test-10              472.1m ± 1%   467.1m ± 1%   -1.06% (p=0.007 n=7)
TinyGo/optimizing/Run/unicode.test-10                       14.23m ± 0%   14.28m ± 0%   +0.34% (p=0.011 n=7)
TinyGo/optimizing/Compile/unicode.test-10                   447.1m ± 0%   443.4m ± 0%   -0.83% (p=0.001 n=7)
TinyGo/optimizing/Run/unicode_utf16.test-10                 6.723m ± 0%   6.739m ± 0%        ~ (p=0.053 n=7)
TinyGo/optimizing/Compile/unicode_utf16.test-10             461.8m ± 0%   458.4m ± 0%   -0.74% (p=0.001 n=7)
TinyGo/optimizing/Run/unicode_utf8.test-10                  14.97m ± 0%   15.03m ± 0%   +0.44% (p=0.011 n=7)
TinyGo/optimizing/Compile/unicode_utf8.test-10              441.5m ± 0%   437.1m ± 1%   -1.01% (p=0.011 n=7)
geomean                                                     135.9m        133.2m        -1.99%
```

mathetake: found a bug not covered by the existing ad_hoc test so I need to add one, and after that, will run fuzzing
mathetake: now fuzzing 🏃 
mathetake: will wait for fuzzing to finish till tomorrow 

Patch:
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index d4e8dcd0..b0346d3f 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -656,6 +656,14 @@ func (e *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm
 	e.functions[index] = imported.functions[indexInImportedModule]
 }
 
+// GetGlobalValue implements the same method as documented on wasm.ModuleEngine.
+func (e *moduleEngine) GetGlobalValue(wasm.Index) (lo, hi uint64) {
+	panic("BUG: GetGlobalValue should never be called on compiler mode")
+}
+
+// OwnsGlobals implements the same method as documented on wasm.ModuleEngine.
+func (e *moduleEngine) OwnsGlobals() bool { return false }
+
 // ResolveImportedMemory implements wasm.ModuleEngine.
 func (e *moduleEngine) ResolveImportedMemory(wasm.ModuleEngine) {}
 
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index d47e7c31..c60d0584 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -87,6 +87,14 @@ type moduleEngine struct {
 	parentEngine *engine
 }
 
+// GetGlobalValue implements the same method as documented on wasm.ModuleEngine.
+func (e *moduleEngine) GetGlobalValue(wasm.Index) (lo, hi uint64) {
+	panic("BUG: GetGlobalValue should never be called on interpreter mode")
+}
+
+// OwnsGlobals implements the same method as documented on wasm.ModuleEngine.
+func (e *moduleEngine) OwnsGlobals() bool { return false }
+
 // callEngine holds context per moduleEngine.Call, and shared across all the
 // function calls originating from the same moduleEngine.Call execution.
 //
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index 2b2de518..06d353e6 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -3459,21 +3459,25 @@ func (c *Compiler) reloadMemoryBaseLen() {
 	c.clearSafeBounds()
 }
 
-// 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)
+	opaqueOffset := c.offset.GlobalInstanceOffset(index)
 
 	builder := c.ssaBuilder
-	loadGlobalInstPtr := builder.AllocateInstruction()
-	loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(instanceOffset), ssa.TypeI64)
-	builder.InsertInstruction(loadGlobalInstPtr)
+	if index < c.m.ImportGlobalCount {
+		loadGlobalInstPtr := builder.AllocateInstruction()
+		loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), ssa.TypeI64)
+		builder.InsertInstruction(loadGlobalInstPtr)
 
-	store := builder.AllocateInstruction()
-	store.AsStore(ssa.OpcodeStore, v, loadGlobalInstPtr.Return(), uint32(globalInstanceValueOffset))
-	builder.InsertInstruction(store)
+		store := builder.AllocateInstruction()
+		store.AsStore(ssa.OpcodeStore, v, loadGlobalInstPtr.Return(), uint32(0))
+		builder.InsertInstruction(store)
+
+	} else {
+		store := builder.AllocateInstruction()
+		store.AsStore(ssa.OpcodeStore, v, c.moduleCtxPtrValue, uint32(opaqueOffset))
+		builder.InsertInstruction(store)
+	}
 
 	// The value has changed to `v`, so we record it.
 	builder.DefineVariableInCurrentBB(variable, v)
@@ -3482,7 +3486,7 @@ func (c *Compiler) setWasmGlobalValue(index wasm.Index, v ssa.Value) {
 func (c *Compiler) getWasmGlobalValue(index wasm.Index, forceLoad bool) ssa.Value {
 	variable := c.globalVariables[index]
 	typ := c.globalVariablesTypes[index]
-	instanceOffset := c.offset.GlobalInstanceOffset(index)
+	opaqueOffset := c.offset.GlobalInstanceOffset(index)
 
 	builder := c.ssaBuilder
 	if !forceLoad {
@@ -3491,16 +3495,21 @@ func (c *Compiler) getWasmGlobalValue(index wasm.Index, forceLoad bool) ssa.Valu
 		}
 	}
 
-	loadGlobalInstPtr := builder.AllocateInstruction()
-	loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(instanceOffset), ssa.TypeI64)
-	builder.InsertInstruction(loadGlobalInstPtr)
+	var load *ssa.Instruction
+	if index < c.m.ImportGlobalCount {
+		loadGlobalInstPtr := builder.AllocateInstruction()
+		loadGlobalInstPtr.AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), ssa.TypeI64)
+		builder.InsertInstruction(loadGlobalInstPtr)
+		load = builder.AllocateInstruction().
+			AsLoad(loadGlobalInstPtr.Return(), uint32(0), typ)
+	} else {
+		load = builder.AllocateInstruction().
+			AsLoad(c.moduleCtxPtrValue, uint32(opaqueOffset), typ)
+	}
 
-	load := builder.AllocateInstruction()
-	load.AsLoad(loadGlobalInstPtr.Return(), uint32(globalInstanceValueOffset), typ)
-	builder.InsertInstruction(load)
-	ret := load.Return()
-	builder.DefineVariableInCurrentBB(variable, ret)
-	return ret
+	v := load.Insert(builder).Return()
+	builder.DefineVariableInCurrentBB(variable, v)
+	return v
 }
 
 const (
diff --git a/internal/engine/wazevo/module_engine.go b/internal/engine/wazevo/module_engine.go
index 7d202c77..3a61e331 100644
--- a/internal/engine/wazevo/module_engine.go
+++ b/internal/engine/wazevo/module_engine.go
@@ -49,7 +49,8 @@ type (
 	// 	    importedMemoryInstance                    *wasm.MemoryInstance (optional)
 	// 	    importedMemoryOwnerOpaqueCtx              *byte                (optional)
 	// 	    importedFunctions                         [# of importedFunctions]functionInstance
-	//      globals                                   []*wasm.GlobalInstance (optional)
+	//      importedGlobals                           []ImportedGlobal       (optional)
+	//      localGlobals                              []Global               (optional)
 	//      typeIDsBegin                              &wasm.ModuleInstance.TypeIDs[0]  (optional)
 	//      tables                                    []*wasm.TableInstance  (optional)
 	// 	    beforeListenerTrampolines1stElement       **byte                 (optional)
@@ -58,6 +59,15 @@ type (
 	//      elementInstances1stElement                []wasm.ElementInstance (optional)
 	// 	}
 	//
+	//  type ImportedGlobal struct {
+	// 		*Global
+	// 		_ uint64 // padding
+	//  }
+	//
+	//  type Global struct {
+	// 		Val, ValHi uint64
+	//  }
+	//
 	// See wazevoapi.NewModuleContextOffsetData for the details of the offsets.
 	//
 	// Note that for host modules, the structure is entirely different. See buildHostModuleOpaque.
@@ -92,9 +102,18 @@ func (m *moduleEngine) setupOpaque() {
 	// Note: imported functions are resolved in ResolveImportedFunction.
 
 	if globalOffset := offsets.GlobalsBegin; globalOffset >= 0 {
-		for _, g := range inst.Globals {
-			binary.LittleEndian.PutUint64(opaque[globalOffset:], uint64(uintptr(unsafe.Pointer(g))))
-			globalOffset += 8
+		for i, g := range inst.Globals {
+			if i < int(inst.Source.ImportGlobalCount) {
+				importedME := g.Me.(*moduleEngine)
+				offset := importedME.parent.offsets.GlobalInstanceOffset(g.Index)
+				importedMEOpaque := importedME.opaque
+				binary.LittleEndian.PutUint64(opaque[globalOffset:],
+					uint64(uintptr(unsafe.Pointer(&importedMEOpaque[offset]))))
+			} else {
+				binary.LittleEndian.PutUint64(opaque[globalOffset:], g.Val)
+				binary.LittleEndian.PutUint64(opaque[globalOffset+8:], g.ValHi)
+			}
+			globalOffset += 16
 		}
 	}
 
@@ -169,6 +188,19 @@ func (m *moduleEngine) NewFunction(index wasm.Index) api.Function {
 	return ce
 }
 
+// GetGlobalValue implements the same method as documented on wasm.ModuleEngine.
+func (m *moduleEngine) GetGlobalValue(i wasm.Index) (lo, hi uint64) {
+	offset := m.parent.offsets.GlobalInstanceOffset(i)
+	buf := m.opaque[offset:]
+	if i < m.module.Source.ImportGlobalCount {
+		panic("GetGlobalValue should not be called for imported globals")
+	}
+	return binary.LittleEndian.Uint64(buf), binary.LittleEndian.Uint64(buf[8:])
+}
+
+// OwnsGlobals implements the same method as documented on wasm.ModuleEngine.
+func (m *moduleEngine) OwnsGlobals() bool { return true }
+
 // ResolveImportedFunction implements wasm.ModuleEngine.
 func (m *moduleEngine) ResolveImportedFunction(index, indexInImportedModule wasm.Index, importedModuleEngine wasm.ModuleEngine) {
 	executableOffset, moduleCtxOffset, typeIDOffset := m.parent.offsets.ImportedFunctionOffset(index)
diff --git a/internal/engine/wazevo/wazevoapi/offsetdata.go b/internal/engine/wazevo/wazevoapi/offsetdata.go
index d21489b1..09b7c135 100644
--- a/internal/engine/wazevo/wazevoapi/offsetdata.go
+++ b/internal/engine/wazevo/wazevoapi/offsetdata.go
@@ -79,7 +79,7 @@ func (m *ModuleContextOffsetData) ImportedFunctionOffset(i wasm.Index) (
 
 // GlobalInstanceOffset returns an offset of the i-th global instance.
 func (m *ModuleContextOffsetData) GlobalInstanceOffset(i wasm.Index) Offset {
-	return m.GlobalsBegin + Offset(i)*8
+	return m.GlobalsBegin + Offset(i)*16
 }
 
 // Offset represents an offset of a field of a struct.
@@ -159,7 +159,7 @@ func NewModuleContextOffsetData(m *wasm.Module, withListener bool) ModuleContext
 	if globals := int(m.ImportGlobalCount) + len(m.GlobalSection); globals > 0 {
 		ret.GlobalsBegin = offset
 		// Pointers to *wasm.GlobalInstance.
-		offset += Offset(globals) * 8
+		offset += Offset(globals) * 16
 	} else {
 		ret.GlobalsBegin = -1
 	}
diff --git a/internal/wasm/engine.go b/internal/wasm/engine.go
index 3d483807..c2f82efc 100644
--- a/internal/wasm/engine.go
+++ b/internal/wasm/engine.go
@@ -54,6 +54,14 @@ type ModuleEngine interface {
 	// LookupFunction returns the FunctionModule and the Index of the function in the returned ModuleInstance at the given offset in the table.
 	LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) (*ModuleInstance, Index)
 
+	// GetGlobalValue returns the value of the global variable at the given Index.
+	// Only called when OwnsGlobals() returns true, and must not be called for imported globals
+	GetGlobalValue(idx Index) (lo, hi uint64)
+
+	// OwnsGlobals returns true if this ModuleEngine owns the global variables. If true, wasm.GlobalInstance's Val,ValHi should
+	// not be accessed directly.
+	OwnsGlobals() bool
+
 	// FunctionInstanceReference returns Reference for the given Index for a FunctionInstance. The returned values are used by
 	// the initialization via ElementSegment.
 	FunctionInstanceReference(funcIndex Index) Reference
diff --git a/internal/wasm/global.go b/internal/wasm/global.go
index 6320e0de..f28f46ed 100644
--- a/internal/wasm/global.go
+++ b/internal/wasm/global.go
@@ -18,7 +18,8 @@ func (g constantGlobal) Type() api.ValueType {
 
 // Get implements api.Global.
 func (g constantGlobal) Get() uint64 {
-	return g.g.Val
+	ret, _ := g.g.Value()
+	return ret
 }
 
 // String implements api.Global.
@@ -39,7 +40,8 @@ func (g mutableGlobal) Type() api.ValueType {
 
 // Get implements api.Global.
 func (g mutableGlobal) Get() uint64 {
-	return g.g.Val
+	ret, _ := g.g.Value()
+	return ret
 }
 
 // String implements api.Global.
@@ -51,6 +53,3 @@ func (g mutableGlobal) String() string {
 func (g mutableGlobal) Set(v uint64) {
 	g.g.Val = v
 }
-
-// compile-time check to ensure mutableGlobal is a api.Global.
-var _ api.Global = mutableGlobal{}
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index 7c363b2f..a5aaf6ad 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -611,9 +611,16 @@ func (m *Module) validateDataCountSection() (err error) {
 
 func (m *ModuleInstance) buildGlobals(module *Module, funcRefResolver func(funcIndex Index) Reference) {
 	importedGlobals := m.Globals[:module.ImportGlobalCount]
+
+	me := m.Engine
+	engineOwnGlobal := me.OwnsGlobals()
 	for i := Index(0); i < Index(len(module.GlobalSection)); i++ {
 		gs := &module.GlobalSection[i]
 		g := &GlobalInstance{}
+		if engineOwnGlobal {
+			g.Me = me
+			g.Index = i + module.ImportGlobalCount
+		}
 		m.Globals[i+module.ImportGlobalCount] = g
 		g.Type = gs.Type
 		g.initialize(importedGlobals, &gs.Init, funcRefResolver)
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index f15c19fc..e6ae75cc 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -138,9 +138,16 @@ type (
 	GlobalInstance struct {
 		Type GlobalType
 		// Val holds a 64-bit representation of the actual value.
+		// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
 		Val uint64
 		// ValHi is only used for vector type globals, and holds the higher bits of the vector.
+		// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
 		ValHi uint64
+		// Me is the module engine that owns this global instance.
+		// The .Val and .ValHi fields are only valid when me is nil.
+		// If me is non-nil, the value is stored in the module engine.
+		Me    ModuleEngine
+		Index Index
 	}
 
 	// FunctionTypeID is a uniquely assigned integer for a function type.
@@ -572,6 +579,13 @@ func (g *GlobalInstance) String() string {
 	}
 }
 
+func (g *GlobalInstance) Value() (uint64, uint64) {
+	if g.Me != nil {
+		return g.Me.GetGlobalValue(g.Index)
+	}
+	return g.Val, g.ValHi
+}
+
 func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) {
 	ret := make([]FunctionTypeID, len(ts))
 	for i := range ts {


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index 8ecdaee2..0deb91f7 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -1937,34 +1937,26 @@ L2:
 L1 (SSA Block: blk0):
 	mov x128?, x0
 	mov x129?, x1
-	ldr x130?, [x129?, #0x8]
-	ldr w131?, [x130?, #0x8]
-	ldr x132?, [x129?, #0x10]
-	ldr x133?, [x132?, #0x8]
-	ldr x134?, [x129?, #0x18]
-	ldr s135?, [x134?, #0x8]
-	ldr x136?, [x129?, #0x20]
-	ldr d137?, [x136?, #0x8]
+	ldr w130?, [x129?, #0x8]
+	ldr x131?, [x129?, #0x18]
+	ldr s132?, [x129?, #0x28]
+	ldr d133?, [x129?, #0x38]
 	str x129?, [x128?, #0x8]
 	mov x0, x128?
 	mov x1, x129?
 	bl f1
-	ldr x138?, [x129?, #0x8]
-	ldr w139?, [x138?, #0x8]
-	ldr x140?, [x129?, #0x10]
-	ldr x141?, [x140?, #0x8]
-	ldr x142?, [x129?, #0x18]
-	ldr s143?, [x142?, #0x8]
-	ldr x144?, [x129?, #0x20]
-	ldr d145?, [x144?, #0x8]
-	mov v3.8b, v145?.8b
-	mov v2.8b, v143?.8b
-	mov x3, x141?
-	mov x2, x139?
-	mov v1.8b, v137?.8b
-	mov v0.8b, v135?.8b
-	mov x1, x133?
-	mov x0, x131?
+	ldr w134?, [x129?, #0x8]
+	ldr x135?, [x129?, #0x18]
+	ldr s136?, [x129?, #0x28]
+	ldr d137?, [x129?, #0x38]
+	mov v3.8b, v137?.8b
+	mov v2.8b, v136?.8b
+	mov x3, x135?
+	mov x2, x134?
+	mov v1.8b, v133?.8b
+	mov v0.8b, v132?.8b
+	mov x1, x131?
+	mov x0, x130?
 	ret
 `,
 			afterFinalizeARM64: `
@@ -1974,29 +1966,21 @@ L1 (SSA Block: blk0):
 	orr x27, xzr, #0x20
 	str x27, [sp, #-0x10]!
 	str x1, [sp, #0x10]
-	ldr x8, [x1, #0x8]
-	ldr w8, [x8, #0x8]
+	ldr w8, [x1, #0x8]
 	str w8, [sp, #0x2c]
-	ldr x9, [x1, #0x10]
-	ldr x9, [x9, #0x8]
+	ldr x9, [x1, #0x18]
 	str x9, [sp, #0x24]
-	ldr x10, [x1, #0x18]
-	ldr s8, [x10, #0x8]
+	ldr s8, [x1, #0x28]
 	str s8, [sp, #0x20]
-	ldr x10, [x1, #0x20]
-	ldr d9, [x10, #0x8]
+	ldr d9, [x1, #0x38]
 	str d9, [sp, #0x18]
 	str x1, [x0, #0x8]
 	bl f1
 	ldr x8, [sp, #0x10]
-	ldr x9, [x8, #0x8]
-	ldr w9, [x9, #0x8]
-	ldr x10, [x8, #0x10]
-	ldr x10, [x10, #0x8]
-	ldr x11, [x8, #0x18]
-	ldr s8, [x11, #0x8]
-	ldr x8, [x8, #0x20]
-	ldr d9, [x8, #0x8]
+	ldr w9, [x8, #0x8]
+	ldr x10, [x8, #0x18]
+	ldr s8, [x8, #0x28]
+	ldr d9, [x8, #0x38]
 	mov v3.8b, v9.8b
 	mov v2.8b, v8.8b
 	mov x3, x10
@@ -2022,36 +2006,28 @@ L1 (SSA Block: blk0):
 			afterLoweringARM64: `
 L1 (SSA Block: blk0):
 	mov x129?, x1
-	ldr x131?, [x129?, #0x8]
-	orr w141?, wzr, #0x1
-	str w141?, [x131?, #0x8]
-	ldr x133?, [x129?, #0x10]
-	orr x140?, xzr, #0x2
-	str x140?, [x133?, #0x8]
-	ldr x135?, [x129?, #0x18]
-	ldr s139?, #8; b 8; data.f32 3.000000
-	str s139?, [x135?, #0x8]
-	ldr x137?, [x129?, #0x20]
-	ldr d138?, #8; b 16; data.f64 4.000000
-	str d138?, [x137?, #0x8]
+	orr w137?, wzr, #0x1
+	str w137?, [x129?, #0x8]
+	orr x136?, xzr, #0x2
+	str x136?, [x129?, #0x18]
+	ldr s135?, #8; b 8; data.f32 3.000000
+	str s135?, [x129?, #0x28]
+	ldr d134?, #8; b 16; data.f64 4.000000
+	str d134?, [x129?, #0x38]
 	ret
 `,
 			afterFinalizeARM64: `
 L1 (SSA Block: blk0):
 	stp x30, xzr, [sp, #-0x10]!
 	str xzr, [sp, #-0x10]!
-	ldr x8, [x1, #0x8]
-	orr w9, wzr, #0x1
-	str w9, [x8, #0x8]
-	ldr x8, [x1, #0x10]
-	orr x9, xzr, #0x2
-	str x9, [x8, #0x8]
-	ldr x8, [x1, #0x18]
+	orr w8, wzr, #0x1
+	str w8, [x1, #0x8]
+	orr x8, xzr, #0x2
+	str x8, [x1, #0x18]
 	ldr s8, #8; b 8; data.f32 3.000000
-	str s8, [x8, #0x8]
-	ldr x8, [x1, #0x20]
+	str s8, [x1, #0x28]
 	ldr d8, #8; b 16; data.f64 4.000000
-	str d8, [x8, #0x8]
+	str d8, [x1, #0x38]
 	add sp, sp, #0x10
 	ldr x30, [sp], #0x10
 	ret
diff --git a/internal/engine/wazevo/frontend/frontend_test.go b/internal/engine/wazevo/frontend/frontend_test.go
index 34d17ec4..a6551dc9 100644
--- a/internal/engine/wazevo/frontend/frontend_test.go
+++ b/internal/engine/wazevo/frontend/frontend_test.go
@@ -1181,15 +1181,11 @@ blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
 			m:    testcases.GlobalsGet.Module,
 			exp: `
 blk0: (exec_ctx:i64, module_ctx:i64)
-	v2:i64 = Load module_ctx, 0x8
-	v3:i32 = Load v2, 0x8
-	v4:i64 = Load module_ctx, 0x10
-	v5:i64 = Load v4, 0x8
-	v6:i64 = Load module_ctx, 0x18
-	v7:f32 = Load v6, 0x8
-	v8:i64 = Load module_ctx, 0x20
-	v9:f64 = Load v8, 0x8
-	Jump blk_ret, v3, v5, v7, v9
+	v2:i32 = Load module_ctx, 0x8
+	v3:i64 = Load module_ctx, 0x18
+	v4:f32 = Load module_ctx, 0x28
+	v5:f64 = Load module_ctx, 0x38
+	Jump blk_ret, v2, v3, v4, v5
 `,
 		},
 		{
@@ -1198,18 +1194,14 @@ blk0: (exec_ctx:i64, module_ctx:i64)
 			exp: `
 blk0: (exec_ctx:i64, module_ctx:i64)
 	v2:i32 = Iconst_32 0x1
-	v3:i64 = Load module_ctx, 0x8
-	Store v2, v3, 0x8
-	v4:i64 = Iconst_64 0x2
-	v5:i64 = Load module_ctx, 0x10
-	Store v4, v5, 0x8
-	v6:f32 = F32const 3.000000
-	v7:i64 = Load module_ctx, 0x18
-	Store v6, v7, 0x8
-	v8:f64 = F64const 4.000000
-	v9:i64 = Load module_ctx, 0x20
-	Store v8, v9, 0x8
-	Jump blk_ret, v2, v4, v6, v8
+	Store v2, module_ctx, 0x8
+	v3:i64 = Iconst_64 0x2
+	Store v3, module_ctx, 0x18
+	v4:f32 = F32const 3.000000
+	Store v4, module_ctx, 0x28
+	v5:f64 = F64const 4.000000
+	Store v5, module_ctx, 0x38
+	Jump blk_ret, v2, v3, v4, v5
 `,
 		},
 		{
@@ -1220,50 +1212,34 @@ signatures:
 	sig1: i64i64_v
 
 blk0: (exec_ctx:i64, module_ctx:i64)
-	v2:i64 = Load module_ctx, 0x8
-	v3:i32 = Load v2, 0x8
-	v4:i64 = Load module_ctx, 0x10
-	v5:i64 = Load v4, 0x8
-	v6:i64 = Load module_ctx, 0x18
-	v7:f32 = Load v6, 0x8
-	v8:i64 = Load module_ctx, 0x20
-	v9:f64 = Load v8, 0x8
+	v2:i32 = Load module_ctx, 0x8
+	v3:i64 = Load module_ctx, 0x18
+	v4:f32 = Load module_ctx, 0x28
+	v5:f64 = Load module_ctx, 0x38
 	Store module_ctx, exec_ctx, 0x8
 	Call f1:sig1, exec_ctx, module_ctx
-	v10:i64 = Load module_ctx, 0x8
-	v11:i32 = Load v10, 0x8
-	v12:i64 = Load module_ctx, 0x10
-	v13:i64 = Load v12, 0x8
-	v14:i64 = Load module_ctx, 0x18
-	v15:f32 = Load v14, 0x8
-	v16:i64 = Load module_ctx, 0x20
-	v17:f64 = Load v16, 0x8
-	Jump blk_ret, v3, v5, v7, v9, v11, v13, v15, v17
+	v6:i32 = Load module_ctx, 0x8
+	v7:i64 = Load module_ctx, 0x18
+	v8:f32 = Load module_ctx, 0x28
+	v9:f64 = Load module_ctx, 0x38
+	Jump blk_ret, v2, v3, v4, v5, v6, v7, v8, v9
 `,
 			expAfterOpt: `
 signatures:
 	sig1: i64i64_v
 
 blk0: (exec_ctx:i64, module_ctx:i64)
-	v2:i64 = Load module_ctx, 0x8
-	v3:i32 = Load v2, 0x8
-	v4:i64 = Load module_ctx, 0x10
-	v5:i64 = Load v4, 0x8
-	v6:i64 = Load module_ctx, 0x18
-	v7:f32 = Load v6, 0x8
-	v8:i64 = Load module_ctx, 0x20
-	v9:f64 = Load v8, 0x8
+	v2:i32 = Load module_ctx, 0x8
+	v3:i64 = Load module_ctx, 0x18
+	v4:f32 = Load module_ctx, 0x28
+	v5:f64 = Load module_ctx, 0x38
 	Store module_ctx, exec_ctx, 0x8
 	Call f1:sig1, exec_ctx, module_ctx
-	v10:i64 = Load module_ctx, 0x8
-	v11:i32 = Load v10, 0x8
-	v12:i64 = Load module_ctx, 0x10
-	v13:i64 = Load v12, 0x8
-	v14:i64 = Load module_ctx, 0x18
-	v15:f32 = Load v14, 0x8
-	v16:i64 = Load module_ctx, 0x20
-	v17:f64 = Load v16, 0x8
-	Jump blk_ret, v3, v5, v7, v9, v11, v13, v15, v17
+	v6:i32 = Load module_ctx, 0x8
+	v7:i64 = Load module_ctx, 0x18
+	v8:f32 = Load module_ctx, 0x28
+	v9:f64 = Load module_ctx, 0x38
+	Jump blk_ret, v2, v3, v4, v5, v6, v7, v8, v9
 `,
 		},
 		{
diff --git a/internal/engine/wazevo/frontend/lower_test.go b/internal/engine/wazevo/frontend/lower_test.go
index 0bb8fe9a..5bc37880 100644
--- a/internal/engine/wazevo/frontend/lower_test.go
+++ b/internal/engine/wazevo/frontend/lower_test.go
@@ -9,8 +9,6 @@ import (
 )
 
 func Test_Offsets(t *testing.T) {
-	var globalInstance wasm.GlobalInstance
-	require.Equal(t, int(unsafe.Offsetof(globalInstance.Val)), globalInstanceValueOffset)
 	var memInstance wasm.MemoryInstance
 	require.Equal(t, int(unsafe.Offsetof(memInstance.Buffer)), memoryInstanceBufOffset)
 	var tableInstance wasm.TableInstance
diff --git a/internal/engine/wazevo/module_engine_test.go b/internal/engine/wazevo/module_engine_test.go
index 86c1461d..e9e2936f 100644
--- a/internal/engine/wazevo/module_engine_test.go
+++ b/internal/engine/wazevo/module_engine_test.go
@@ -13,6 +13,7 @@ import (
 )
 
 func TestModuleEngine_setupOpaque(t *testing.T) {
+	const importedGlobalBegin = 99
 	for i, tc := range []struct {
 		offset wazevoapi.ModuleContextOffsetData
 		m      *wasm.ModuleInstance
@@ -56,9 +57,20 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 				AfterListenerTrampolines1stElement:  208,
 			},
 			m: &wasm.ModuleInstance{
-				Globals: []*wasm.GlobalInstance{{}, {}, {}, {}, {}, {}},
+				Globals: []*wasm.GlobalInstance{
+					{
+						Me: &moduleEngine{
+							parent: &compiledModule{offsets: wazevoapi.ModuleContextOffsetData{GlobalsBegin: importedGlobalBegin}},
+							opaque: make([]byte, 1000),
+						},
+					},
+					{},
+					{Val: 1},
+					{Val: 1, ValHi: 1230},
+				},
 				Tables:  []*wasm.TableInstance{{}, {}, {}},
 				TypeIDs: make([]wasm.FunctionTypeID, 50),
+				Source:  &wasm.Module{ImportGlobalCount: 1},
 			},
 		},
 	} {
@@ -101,9 +113,17 @@ func TestModuleEngine_setupOpaque(t *testing.T) {
 			}
 			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)
+					if i < int(tc.m.Source.ImportGlobalCount) {
+						actualPtr := uintptr(binary.LittleEndian.Uint64(m.opaque[int(tc.offset.GlobalsBegin)+16*i:]))
+						imported := g.Me.(*moduleEngine)
+						expPtr := uintptr(unsafe.Pointer(&imported.opaque[importedGlobalBegin]))
+						require.Equal(t, expPtr, actualPtr)
+					} else {
+						actual := binary.LittleEndian.Uint64(m.opaque[int(tc.offset.GlobalsBegin)+16*i:])
+						actualHi := binary.LittleEndian.Uint64(m.opaque[int(tc.offset.GlobalsBegin)+16*i+8:])
+						require.Equal(t, g.Val, actual)
+						require.Equal(t, g.ValHi, actualHi)
+					}
 				}
 			}
 			if tc.offset.TablesBegin >= 0 {
diff --git a/internal/engine/wazevo/wazevoapi/offsetdata_test.go b/internal/engine/wazevo/wazevoapi/offsetdata_test.go
index ea64a985..9c45f7d5 100644
--- a/internal/engine/wazevo/wazevoapi/offsetdata_test.go
+++ b/internal/engine/wazevo/wazevoapi/offsetdata_test.go
@@ -114,13 +114,13 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				ImportedMemoryBegin:                 -1,
 				ImportedFunctionsBegin:              24,
 				GlobalsBegin:                        24 + 10*FunctionInstanceSize,
-				TypeIDs1stElement:                   24 + 10*FunctionInstanceSize + 8*30,
-				TablesBegin:                         24 + 10*FunctionInstanceSize + 8*30 + 8,
+				TypeIDs1stElement:                   24 + 10*FunctionInstanceSize + 16*30,
+				TablesBegin:                         24 + 10*FunctionInstanceSize + 16*30 + 8,
 				BeforeListenerTrampolines1stElement: -1,
 				AfterListenerTrampolines1stElement:  -1,
-				DataInstances1stElement:             24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15,
-				ElementInstances1stElement:          24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15 + 8,
-				TotalSize:                           24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15 + 16,
+				DataInstances1stElement:             24 + 10*FunctionInstanceSize + 16*30 + 8 + 8*15,
+				ElementInstances1stElement:          24 + 10*FunctionInstanceSize + 16*30 + 8 + 8*15 + 8,
+				TotalSize:                           24 + 10*FunctionInstanceSize + 16*30 + 8 + 8*15 + 16,
 			},
 		},
 		{
@@ -139,13 +139,13 @@ func TestNewModuleContextOffsetData(t *testing.T) {
 				ImportedMemoryBegin:                 -1,
 				ImportedFunctionsBegin:              24,
 				GlobalsBegin:                        24 + 10*FunctionInstanceSize,
-				TypeIDs1stElement:                   24 + 10*FunctionInstanceSize + 8*30,
-				TablesBegin:                         24 + 10*FunctionInstanceSize + 8*30 + 8,
-				BeforeListenerTrampolines1stElement: 24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15,
-				AfterListenerTrampolines1stElement:  24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15 + 8,
-				DataInstances1stElement:             24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15 + 16,
-				ElementInstances1stElement:          24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15 + 24,
-				TotalSize:                           24 + 10*FunctionInstanceSize + 8*30 + 8 + 8*15 + 32,
+				TypeIDs1stElement:                   24 + 10*FunctionInstanceSize + 16*30,
+				TablesBegin:                         24 + 10*FunctionInstanceSize + 16*30 + 8,
+				BeforeListenerTrampolines1stElement: 24 + 10*FunctionInstanceSize + 16*30 + 8 + 8*15,
+				AfterListenerTrampolines1stElement:  24 + 10*FunctionInstanceSize + 16*30 + 8 + 8*15 + 8,
+				DataInstances1stElement:             24 + 10*FunctionInstanceSize + 16*30 + 8 + 8*15 + 16,
+				ElementInstances1stElement:          24 + 10*FunctionInstanceSize + 16*30 + 8 + 8*15 + 24,
+				TotalSize:                           24 + 10*FunctionInstanceSize + 16*30 + 8 + 8*15 + 32,
 			},
 		},
 	} {
diff --git a/internal/integration_test/engine/adhoc_test.go b/internal/integration_test/engine/adhoc_test.go
index 7d55fd05..703b96be 100644
--- a/internal/integration_test/engine/adhoc_test.go
+++ b/internal/integration_test/engine/adhoc_test.go
@@ -38,6 +38,7 @@ type testCase struct {
 }
 
 var tests = map[string]testCase{
+	"imported mutable global":                                          {f: testImportedMutableGlobalUpdate},
 	"huge stack":                                                       {f: testHugeStack},
 	"unreachable":                                                      {f: testUnreachable},
 	"recursive entry":                                                  {f: testRecursiveEntry},
@@ -1824,7 +1825,6 @@ func testManyParamsResultsCallManyConstsListener(t *testing.T, r wazero.Runtime)
 	results, err := main.Call(ctx)
 	require.NoError(t, err)
 
-	fmt.Println(buf.String())
 	require.Equal(t, `
 --> .call_many_consts()
 	--> .many_consts()
@@ -1895,7 +1895,6 @@ func testManyParamsResultsDoublerListener(t *testing.T, r wazero.Runtime) {
 	results, err := main.Call(ctx, params...)
 	require.NoError(t, err)
 
-	fmt.Println(buf.String())
 	require.Equal(t, `
 --> .doubler([0:v128]=00000000000000000000000000000001,[1:f64]=5e-324,[2:f32]=3e-45,[3:i64]=3,[4:i32]=3,[5:v128]=00000000000000030000000000000005,[6:f64]=2.5e-323,[7:f32]=8e-45,[8:i64]=7,[9:i32]=8,[10:v128]=00000000000000080000000000000008,[11:f64]=4e-323,[12:f32]=1.4e-44,[13:i64]=11,[14:i32]=12,[15:v128]=000000000000000d000000000000000d,[16:f64]=6.4e-323,[17:f32]=1.8e-44,[18:i64]=15,[19:i32]=16,[20:v128]=00000000000000110000000000000012,[21:f64]=9e-323,[22:f32]=2.5e-44,[23:i64]=18,[24:i32]=20,[25:v128]=00000000000000150000000000000016,[26:f64]=1.1e-322,[27:f32]=3.2e-44,[28:i64]=23,[29:i32]=23,[30:v128]=0000000000000019000000000000001a,[31:f64]=1.3e-322,[32:f32]=3.8e-44,[33:i64]=28,[34:i32]=28,[35:v128]=000000000000001c000000000000001e,[36:f64]=1.5e-322,[37:f32]=4.3e-44,[38:i64]=32,[39:i32]=33,[40:v128]=00000000000000210000000000000021,[41:f64]=1.63e-322,[42:f32]=4.9e-44,[43:i64]=36,[44:i32]=37,[45:v128]=00000000000000260000000000000026,[46:f64]=1.9e-322,[47:f32]=5.3e-44,[48:i64]=40,[49:i32]=41,[50:v128]=000000000000002a000000000000002b,[51:f64]=2.1e-322,[52:f32]=6e-44,[53:i64]=43,[54:i32]=45,[55:v128]=000000000000002e000000000000002f,[56:f64]=2.3e-322,[57:f32]=6.7e-44,[58:i64]=48,[59:i32]=48,[60:v128]=00000000000000320000000000000033,[61:f64]=2.5e-322,[62:f32]=7.3e-44,[63:i64]=53,[64:i32]=53,[65:v128]=00000000000000350000000000000037,[66:f64]=2.7e-322,[67:f32]=7.8e-44,[68:i64]=57,[69:i32]=58,[70:v128]=000000000000003a000000000000003a,[71:f64]=2.87e-322,[72:f32]=8.4e-44,[73:i64]=61,[74:i32]=62,[75:v128]=000000000000003f000000000000003f,[76:f64]=3.1e-322,[77:f32]=8.8e-44,[78:i64]=65,[79:i32]=66,[80:v128]=00000000000000430000000000000044,[81:f64]=3.36e-322,[82:f32]=9.5e-44,[83:i64]=68,[84:i32]=70,[85:v128]=00000000000000470000000000000048,[86:f64]=3.56e-322,[87:f32]=1.02e-43,[88:i64]=73,[89:i32]=73,[90:v128]=000000000000004b000000000000004c,[91:f64]=3.75e-322,[92:f32]=1.08e-43,[93:i64]=78,[94:i32]=78,[95:v128]=000000000000004e0000000000000050,[96:f64]=3.95e-322,[97:f32]=1.14e-43,[98:i64]=82,[99:i32]=83)
 <-- (00000000000000000000000000000001,5e-324,6e-45,6,6,00000000000000060000000000000005,2.5e-323,8e-45,14,16,00000000000000100000000000000010,8e-323,1.4e-44,11,24,000000000000001a000000000000001a,1.3e-322,3.6e-44,15,16,00000000000000220000000000000024,1.8e-322,5e-44,36,20,0000000000000015000000000000002c,2.17e-322,6.4e-44,46,46,0000000000000019000000000000001a,1.3e-322,7.6e-44,56,56,0000000000000038000000000000001e,1.5e-322,4.3e-44,64,66,00000000000000420000000000000042,3.26e-322,4.9e-44,36,74,000000000000004c000000000000004c,3.75e-322,1.06e-43,40,41,00000000000000540000000000000056,4.25e-322,1.2e-43,86,45,000000000000002e000000000000005e,4.64e-322,1.35e-43,96,96,00000000000000320000000000000033,2.5e-322,1.46e-43,106,106,000000000000006a0000000000000037,2.7e-322,7.8e-44,114,116,00000000000000740000000000000074,5.73e-322,8.4e-44,61,124,000000000000007e000000000000007e,6.23e-322,1.77e-43,65,66,00000000000000860000000000000088,6.7e-322,1.9e-43,136,70,00000000000000470000000000000090,7.1e-322,2.05e-43,146,146,000000000000004b000000000000004c,3.75e-322,2.16e-43,156,156,000000000000009c0000000000000050,3.95e-322,1.14e-43,164,166)
@@ -1956,7 +1955,6 @@ func testManyParamsResultsSwapperListener(t *testing.T, r wazero.Runtime) {
 	results, err := main.Call(ctx, params...)
 	require.NoError(t, err)
 
-	fmt.Println(buf.String())
 	require.Equal(t, `
 --> .swapper([0:i32]=0,[1:i64]=1,[2:f32]=3e-45,[3:f64]=1.5e-323,[4:v128]=00000000000000030000000000000003,[5:i32]=3,[6:i64]=5,[7:f32]=8e-45,[8:f64]=3.5e-323,[9:v128]=00000000000000080000000000000008,[10:i32]=8,[11:i64]=8,[12:f32]=1.4e-44,[13:f64]=5.4e-323,[14:v128]=000000000000000c000000000000000d,[15:i32]=13,[16:i64]=13,[17:f32]=1.8e-44,[18:f64]=7.4e-323,[19:v128]=00000000000000100000000000000011,[20:i32]=17,[21:i64]=18,[22:f32]=2.5e-44,[23:f64]=9e-323,[24:v128]=00000000000000140000000000000015,[25:i32]=21,[26:i64]=22,[27:f32]=3.2e-44,[28:f64]=1.14e-322,[29:v128]=00000000000000170000000000000019,[30:i32]=25,[31:i64]=26,[32:f32]=3.8e-44,[33:f64]=1.4e-322,[34:v128]=000000000000001c000000000000001c,[35:i32]=28,[36:i64]=30,[37:f32]=4.3e-44,[38:f64]=1.6e-322,[39:v128]=00000000000000210000000000000021,[40:i32]=33,[41:i64]=33,[42:f32]=4.9e-44,[43:f64]=1.8e-322,[44:v128]=00000000000000250000000000000026,[45:i32]=38,[46:i64]=38,[47:f32]=5.3e-44,[48:f64]=2e-322,[49:v128]=0000000000000029000000000000002a,[50:i32]=42,[51:i64]=43,[52:f32]=6e-44,[53:f64]=2.1e-322,[54:v128]=000000000000002d000000000000002e,[55:i32]=46,[56:i64]=47,[57:f32]=6.7e-44,[58:f64]=2.37e-322,[59:v128]=00000000000000300000000000000032,[60:i32]=50,[61:i64]=51,[62:f32]=7.3e-44,[63:f64]=2.6e-322,[64:v128]=00000000000000350000000000000035,[65:i32]=53,[66:i64]=55,[67:f32]=7.8e-44,[68:f64]=2.8e-322,[69:v128]=000000000000003a000000000000003a,[70:i32]=58,[71:i64]=58,[72:f32]=8.4e-44,[73:f64]=3e-322,[74:v128]=000000000000003e000000000000003f,[75:i32]=63,[76:i64]=63,[77:f32]=8.8e-44,[78:f64]=3.2e-322,[79:v128]=00000000000000420000000000000043,[80:i32]=67,[81:i64]=68,[82:f32]=9.5e-44,[83:f64]=3.36e-322,[84:v128]=00000000000000460000000000000047,[85:i32]=71,[86:i64]=72,[87:f32]=1.02e-43,[88:f64]=3.6e-322,[89:v128]=0000000000000049000000000000004b,[90:i32]=75,[91:i64]=76,[92:f32]=1.08e-43,[93:f64]=3.85e-322,[94:v128]=000000000000004e000000000000004e,[95:i32]=78,[96:i64]=80,[97:f32]=1.14e-43,[98:f64]=4.05e-322,[99:v128]=00000000000000530000000000000053)
 <-- (00000000000000620000000000000062,4.84e-322,1.37e-43,97,96,000000000000005f000000000000005d,4.6e-322,1.3e-43,93,92,000000000000005b000000000000005a,4.45e-322,1.23e-43,88,88,00000000000000570000000000000056,4.25e-322,1.19e-43,83,83,00000000000000530000000000000052,4.05e-322,1.14e-43,80,78,000000000000004e000000000000004e,3.85e-322,1.08e-43,76,75,00000000000000490000000000000049,3.6e-322,1.02e-43,72,71,00000000000000460000000000000044,3.36e-322,9.5e-44,68,67,00000000000000420000000000000041,3.2e-322,8.8e-44,63,63,000000000000003e000000000000003d,3e-322,8.4e-44,58,58,000000000000003a0000000000000039,2.8e-322,7.8e-44,55,53,00000000000000350000000000000035,2.6e-322,7.3e-44,51,50,00000000000000300000000000000030,2.37e-322,6.7e-44,47,46,000000000000002d000000000000002b,2.1e-322,6e-44,43,42,00000000000000290000000000000028,2e-322,5.3e-44,38,38,00000000000000250000000000000024,1.8e-322,4.9e-44,33,33,00000000000000210000000000000020,1.6e-322,4.3e-44,30,28,000000000000001c000000000000001c,1.4e-322,3.8e-44,26,25,00000000000000170000000000000017,1.14e-322,3.2e-44,22,21,00000000000000140000000000000012,9e-323,2.5e-44,18,17)
@@ -2012,7 +2010,6 @@ func testManyParamsResultsMainListener(t *testing.T, r wazero.Runtime) {
 	results, err := main.Call(ctx, params...)
 	require.NoError(t, err)
 
-	fmt.Println(buf.String())
 	require.Equal(t, `
 --> .main([0:i32]=0,[1:i64]=1,[2:f32]=3e-45,[3:f64]=1.5e-323,[4:v128]=00000000000000030000000000000003,[5:i32]=3,[6:i64]=5,[7:f32]=8e-45,[8:f64]=3.5e-323,[9:v128]=00000000000000080000000000000008,[10:i32]=8,[11:i64]=8,[12:f32]=1.4e-44,[13:f64]=5.4e-323,[14:v128]=000000000000000c000000000000000d,[15:i32]=13,[16:i64]=13,[17:f32]=1.8e-44,[18:f64]=7.4e-323,[19:v128]=00000000000000100000000000000011,[20:i32]=17,[21:i64]=18,[22:f32]=2.5e-44,[23:f64]=9e-323,[24:v128]=00000000000000140000000000000015,[25:i32]=21,[26:i64]=22,[27:f32]=3.2e-44,[28:f64]=1.14e-322,[29:v128]=00000000000000170000000000000019,[30:i32]=25,[31:i64]=26,[32:f32]=3.8e-44,[33:f64]=1.4e-322,[34:v128]=000000000000001c000000000000001c,[35:i32]=28,[36:i64]=30,[37:f32]=4.3e-44,[38:f64]=1.6e-322,[39:v128]=00000000000000210000000000000021,[40:i32]=33,[41:i64]=33,[42:f32]=4.9e-44,[43:f64]=1.8e-322,[44:v128]=00000000000000250000000000000026,[45:i32]=38,[46:i64]=38,[47:f32]=5.3e-44,[48:f64]=2e-322,[49:v128]=0000000000000029000000000000002a,[50:i32]=42,[51:i64]=43,[52:f32]=6e-44,[53:f64]=2.1e-322,[54:v128]=000000000000002d000000000000002e,[55:i32]=46,[56:i64]=47,[57:f32]=6.7e-44,[58:f64]=2.37e-322,[59:v128]=00000000000000300000000000000032,[60:i32]=50,[61:i64]=51,[62:f32]=7.3e-44,[63:f64]=2.6e-322,[64:v128]=00000000000000350000000000000035,[65:i32]=53,[66:i64]=55,[67:f32]=7.8e-44,[68:f64]=2.8e-322,[69:v128]=000000000000003a000000000000003a,[70:i32]=58,[71:i64]=58,[72:f32]=8.4e-44,[73:f64]=3e-322,[74:v128]=000000000000003e000000000000003f,[75:i32]=63,[76:i64]=63,[77:f32]=8.8e-44,[78:f64]=3.2e-322,[79:v128]=00000000000000420000000000000043,[80:i32]=67,[81:i64]=68,[82:f32]=9.5e-44,[83:f64]=3.36e-322,[84:v128]=00000000000000460000000000000047,[85:i32]=71,[86:i64]=72,[87:f32]=1.02e-43,[88:f64]=3.6e-322,[89:v128]=0000000000000049000000000000004b,[90:i32]=75,[91:i64]=76,[92:f32]=1.08e-43,[93:f64]=3.85e-322,[94:v128]=000000000000004e000000000000004e,[95:i32]=78,[96:i64]=80,[97:f32]=1.14e-43,[98:f64]=4.05e-322,[99:v128]=00000000000000530000000000000053)
 	--> .swapper([0:i32]=0,[1:i64]=1,[2:f32]=3e-45,[3:f64]=1.5e-323,[4:v128]=00000000000000030000000000000003,[5:i32]=3,[6:i64]=5,[7:f32]=8e-45,[8:f64]=3.5e-323,[9:v128]=00000000000000080000000000000008,[10:i32]=8,[11:i64]=8,[12:f32]=1.4e-44,[13:f64]=5.4e-323,[14:v128]=000000000000000c000000000000000d,[15:i32]=13,[16:i64]=13,[17:f32]=1.8e-44,[18:f64]=7.4e-323,[19:v128]=00000000000000100000000000000011,[20:i32]=17,[21:i64]=18,[22:f32]=2.5e-44,[23:f64]=9e-323,[24:v128]=00000000000000140000000000000015,[25:i32]=21,[26:i64]=22,[27:f32]=3.2e-44,[28:f64]=1.14e-322,[29:v128]=00000000000000170000000000000019,[30:i32]=25,[31:i64]=26,[32:f32]=3.8e-44,[33:f64]=1.4e-322,[34:v128]=000000000000001c000000000000001c,[35:i32]=28,[36:i64]=30,[37:f32]=4.3e-44,[38:f64]=1.6e-322,[39:v128]=00000000000000210000000000000021,[40:i32]=33,[41:i64]=33,[42:f32]=4.9e-44,[43:f64]=1.8e-322,[44:v128]=00000000000000250000000000000026,[45:i32]=38,[46:i64]=38,[47:f32]=5.3e-44,[48:f64]=2e-322,[49:v128]=0000000000000029000000000000002a,[50:i32]=42,[51:i64]=43,[52:f32]=6e-44,[53:f64]=2.1e-322,[54:v128]=000000000000002d000000000000002e,[55:i32]=46,[56:i64]=47,[57:f32]=6.7e-44,[58:f64]=2.37e-322,[59:v128]=00000000000000300000000000000032,[60:i32]=50,[61:i64]=51,[62:f32]=7.3e-44,[63:f64]=2.6e-322,[64:v128]=00000000000000350000000000000035,[65:i32]=53,[66:i64]=55,[67:f32]=7.8e-44,[68:f64]=2.8e-322,[69:v128]=000000000000003a000000000000003a,[70:i32]=58,[71:i64]=58,[72:f32]=8.4e-44,[73:f64]=3e-322,[74:v128]=000000000000003e000000000000003f,[75:i32]=63,[76:i64]=63,[77:f32]=8.8e-44,[78:f64]=3.2e-322,[79:v128]=00000000000000420000000000000043,[80:i32]=67,[81:i64]=68,[82:f32]=9.5e-44,[83:f64]=3.36e-322,[84:v128]=00000000000000460000000000000047,[85:i32]=71,[86:i64]=72,[87:f32]=1.02e-43,[88:f64]=3.6e-322,[89:v128]=0000000000000049000000000000004b,[90:i32]=75,[91:i64]=76,[92:f32]=1.08e-43,[93:f64]=3.85e-322,[94:v128]=000000000000004e000000000000004e,[95:i32]=78,[96:i64]=80,[97:f32]=1.14e-43,[98:f64]=4.05e-322,[99:v128]=00000000000000530000000000000053)
@@ -2064,7 +2061,6 @@ func testManyParamsResultsCallManyConstsAndPickLastVectorListener(t *testing.T,
 	exp := []uint64{0x5f5f5f5f5f5f5f5f, 0x5f5f5f5f5f5f5f5f}
 	require.Equal(t, exp, results)
 
-	fmt.Println(buf.String())
 	require.Equal(t, `
 --> .call_many_consts_and_pick_last_vector()
 	--> .many_consts()
@@ -2074,3 +2070,67 @@ func testManyParamsResultsCallManyConstsAndPickLastVectorListener(t *testing.T,
 <-- 5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f
 `, "\n"+buf.String())
 }
+
+func testImportedMutableGlobalUpdate(t *testing.T, r wazero.Runtime) {
+	importedBin := binaryencoding.EncodeModule(&wasm.Module{
+		ExportSection: []wasm.Export{
+			{Name: "g", Type: wasm.ExternTypeGlobal, Index: 0},
+		},
+		GlobalSection: []wasm.Global{
+			{
+				Type: wasm.GlobalType{ValType: i32, Mutable: true},
+				Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{1}},
+			},
+		},
+		NameSection: &wasm.NameSection{ModuleName: "imported"},
+	})
+
+	mainBin := binaryencoding.EncodeModule(&wasm.Module{
+		ImportSection: []wasm.Import{{
+			Type:       wasm.ExternTypeGlobal,
+			Module:     "imported",
+			Name:       "g",
+			DescGlobal: wasm.GlobalType{ValType: i32, Mutable: true},
+		}},
+		TypeSection: []wasm.FunctionType{{Results: []wasm.ValueType{i32}}},
+		ExportSection: []wasm.Export{
+			{Name: "", Type: wasm.ExternTypeFunc, Index: 0},
+			{Name: "g", Type: wasm.ExternTypeGlobal, Index: 0},
+		},
+		FunctionSection: []wasm.Index{0},
+		CodeSection: []wasm.Code{
+			{Body: []byte{
+				wasm.OpcodeGlobalGet, 0,
+				wasm.OpcodeI32Const, 2,
+				wasm.OpcodeGlobalSet, 0,
+				wasm.OpcodeEnd,
+			}},
+		},
+	})
+
+	ctx := context.Background()
+	importedMod, err := r.Instantiate(ctx, importedBin)
+	require.NoError(t, err)
+
+	mainMod, err := r.Instantiate(ctx, mainBin)
+	require.NoError(t, err)
+
+	main := mainMod.ExportedFunction("")
+	require.NotNil(t, main)
+
+	res, err := main.Call(ctx)
+	require.NoError(t, err)
+
+	prevValue := res[0]
+	require.Equal(t, uint64(1), prevValue)
+
+	g := importedMod.ExportedGlobal("g")
+	require.NotNil(t, g)
+
+	v := g.Get()
+	require.Equal(t, uint64(2), v)
+
+	reExportedG := mainMod.ExportedGlobal("g")
+	v = reExportedG.Get()
+	require.Equal(t, uint64(2), v)
+}
diff --git a/internal/integration_test/fuzz/wazerolib/nodiff.go b/internal/integration_test/fuzz/wazerolib/nodiff.go
index 47cd6fc6..8c9003e7 100644
--- a/internal/integration_test/fuzz/wazerolib/nodiff.go
+++ b/internal/integration_test/fuzz/wazerolib/nodiff.go
@@ -141,23 +141,26 @@ func ensureMutableGlobalsMatch(compilerMod, interpreterMod api.Module, requireNo
 			continue
 		}
 
+		cVal, cValHi := cg.Value()
+		iVal, iValHi := ig.Value()
+
 		var ok bool
 		switch ig.Type.ValType {
 		case wasm.ValueTypeI32, wasm.ValueTypeF32:
-			ok = uint32(cg.Val) == uint32(ig.Val)
+			ok = uint32(cVal) == uint32(iVal)
 		case wasm.ValueTypeI64, wasm.ValueTypeF64:
-			ok = cg.Val == ig.Val
+			ok = cVal == iVal
 		case wasm.ValueTypeV128:
-			ok = cg.Val == ig.Val && cg.ValHi == ig.ValHi
+			ok = cVal == iVal && cValHi == iValHi
 		default:
 			ok = true // Ignore other types.
 		}
 
 		if !ok {
 			if ig.Type.ValType == wasm.ValueTypeV128 {
-				requireNoError(fmt.Errorf("mutable global[%d] value mismatch: (%v,%v) != (%v,%v)", i, cg.Val, cg.ValHi, ig.Val, ig.ValHi))
+				requireNoError(fmt.Errorf("mutable global[%d] value mismatch: (%v,%v) != (%v,%v)", i, cVal, cValHi, iVal, iValHi))
 			} else {
-				requireNoError(fmt.Errorf("mutable global[%d] value mismatch: %v != %v", i, cg.Val, ig.Val))
+				requireNoError(fmt.Errorf("mutable global[%d] value mismatch: %v != %v", i, cVal, iVal))
 			}
 		}
 	}
diff --git a/internal/integration_test/fuzzcases/fuzzcases_test.go b/internal/integration_test/fuzzcases/fuzzcases_test.go
index 34f8698b..c03097db 100644
--- a/internal/integration_test/fuzzcases/fuzzcases_test.go
+++ b/internal/integration_test/fuzzcases/fuzzcases_test.go
@@ -483,8 +483,10 @@ func Test1792c(t *testing.T) {
 		_, err = f.Call(ctx, 0, 0, 0)
 		require.NoError(t, err)
 		m := mod.(*wasm.ModuleInstance)
-		require.Equal(t, uint64(5044022786561933312), m.Globals[0].Val)
-		require.Equal(t, uint64(9205357640488583168), m.Globals[0].ValHi)
+
+		lo, hi := m.Globals[0].Value()
+		require.Equal(t, uint64(5044022786561933312), lo)
+		require.Equal(t, uint64(9205357640488583168), hi)
 	})
 }
 
@@ -499,8 +501,9 @@ func Test1793a(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("").Call(ctx)
 		require.NoError(t, err)
-		require.Equal(t, uint64(2531906066518671488), m.Globals[2].Val)
-		require.Equal(t, uint64(18446744073709551615), m.Globals[2].ValHi)
+		lo, hi := m.Globals[2].Value()
+		require.Equal(t, uint64(2531906066518671488), lo)
+		require.Equal(t, uint64(18446744073709551615), hi)
 	})
 }
 
@@ -515,8 +518,9 @@ func Test1793b(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("").Call(ctx, 0, 0, 0, 0)
 		require.NoError(t, err)
-		require.Equal(t, uint64(18374967954648334335), m.Globals[1].Val)
-		require.Equal(t, uint64(18446744073709551615), m.Globals[1].ValHi)
+		lo, hi := m.Globals[1].Value()
+		require.Equal(t, uint64(18374967954648334335), lo)
+		require.Equal(t, uint64(18446744073709551615), hi)
 	})
 }
 
@@ -531,8 +535,9 @@ func Test1793c(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("").Call(ctx, 0, 0)
 		require.NoError(t, err)
-		require.Equal(t, uint64(18446744073709551615), m.Globals[0].Val)
-		require.Equal(t, uint64(18446744073709551615), m.Globals[0].ValHi)
+		lo, hi := m.Globals[0].Value()
+		require.Equal(t, uint64(18446744073709551615), lo)
+		require.Equal(t, uint64(18446744073709551615), hi)
 	})
 }
 
@@ -577,8 +582,9 @@ func Test1797b(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("\x00\x00\x00\x00\x00").Call(ctx, 0, 0, 0, 0, 0, 0)
 		require.NoError(t, err)
-		require.Equal(t, uint64(2666130977255796624), m.Globals[0].Val)
-		require.Equal(t, uint64(9223142857682330634), m.Globals[0].ValHi)
+		lo, hi := m.Globals[0].Value()
+		require.Equal(t, uint64(2666130977255796624), lo)
+		require.Equal(t, uint64(9223142857682330634), hi)
 	})
 }
 
@@ -610,8 +616,9 @@ func Test1797d(t *testing.T) {
 		params := make([]uint64, 20)
 		_, err = m.ExportedFunction("p").Call(ctx, params...)
 		require.NoError(t, err)
-		require.Equal(t, uint64(15092115255309870764), m.Globals[2].Val)
-		require.Equal(t, uint64(9241386435284803069), m.Globals[2].ValHi)
+		lo, hi := m.Globals[2].Value()
+		require.Equal(t, uint64(15092115255309870764), lo)
+		require.Equal(t, uint64(9241386435284803069), hi)
 	})
 }
 
@@ -665,8 +672,9 @@ func Test1817(t *testing.T) {
 		buf, ok := m.Memory().Read(15616, 16)
 		require.True(t, ok)
 		require.Equal(t, []uint8{0, 0, 0, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, buf)
-		require.Equal(t, uint64(0x8000000080000000), m.Globals[0].Val)
-		require.Equal(t, uint64(0x8000000080000000), m.Globals[0].ValHi)
+		lo, hi := m.Globals[0].Value()
+		require.Equal(t, uint64(0x8000000080000000), lo)
+		require.Equal(t, uint64(0x8000000080000000), hi)
 	})
 }
 
@@ -681,8 +689,9 @@ func Test1820(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("").Call(ctx)
 		require.NoError(t, err)
-		require.Equal(t, uint64(0xFFFFFFFFFFFF0000), m.Globals[1].Val)
-		require.Equal(t, uint64(0xFFFF), m.Globals[1].ValHi)
+		lo, hi := m.Globals[1].Value()
+		require.Equal(t, uint64(0xFFFFFFFFFFFF0000), lo)
+		require.Equal(t, uint64(0xFFFF), hi)
 	})
 }
 
@@ -698,8 +707,9 @@ func Test1823(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("").Call(ctx)
 		require.NoError(t, err)
-		require.Equal(t, uint64(17282609607625994159), m.Globals[0].Val)
-		require.Equal(t, uint64(4671060543367625455), m.Globals[0].ValHi)
+		lo, hi := m.Globals[0].Value()
+		require.Equal(t, uint64(17282609607625994159), lo)
+		require.Equal(t, uint64(4671060543367625455), hi)
 	})
 }
 
@@ -714,8 +724,9 @@ func Test1825(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("").Call(ctx)
 		require.NoError(t, err)
-		require.Equal(t, uint64(1099511627775), m.Globals[6].Val)
-		require.Equal(t, uint64(18446744073709551615), m.Globals[6].ValHi)
+		lo, hi := m.Globals[6].Value()
+		require.Equal(t, uint64(1099511627775), lo)
+		require.Equal(t, uint64(18446744073709551615), hi)
 	})
 }
 
@@ -730,8 +741,9 @@ func Test1826(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("3").Call(ctx, 0, 0)
 		require.NoError(t, err)
-		require.Equal(t, uint64(1608723901141126568), m.Globals[0].Val)
-		require.Equal(t, uint64(0), m.Globals[0].ValHi)
+		lo, hi := m.Globals[0].Value()
+		require.Equal(t, uint64(1608723901141126568), lo)
+		require.Equal(t, uint64(0), hi)
 	})
 }
 
@@ -745,7 +757,8 @@ func Test1846(t *testing.T) {
 		m := mod.(*wasm.ModuleInstance)
 		_, err = m.ExportedFunction("").Call(ctx)
 		require.NoError(t, err)
-		require.Equal(t, math.Float64bits(2), m.Globals[0].Val)
-		require.Equal(t, uint64(0), m.Globals[0].ValHi)
+		lo, hi := m.Globals[0].Value()
+		require.Equal(t, math.Float64bits(2), lo)
+		require.Equal(t, uint64(0), hi)
 	})
 }
diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go
index a3f5dab4..6d8efe26 100644
--- a/internal/wasm/module_test.go
+++ b/internal/wasm/module_test.go
@@ -811,6 +811,7 @@ func TestModule_buildGlobals(t *testing.T) {
 
 	mi := &ModuleInstance{
 		Globals: make([]*GlobalInstance, m.ImportGlobalCount+uint32(len(m.GlobalSection))),
+		Engine:  &mockModuleEngine{},
 	}
 
 	mi.Globals[0], mi.Globals[1] = imported[0], imported[1]
diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go
index 00b418f0..41fc4f17 100644
--- a/internal/wasm/store_test.go
+++ b/internal/wasm/store_test.go
@@ -463,7 +463,13 @@ func (e *mockEngine) NewModuleEngine(_ *Module, _ *ModuleInstance) (ModuleEngine
 	return &mockModuleEngine{callFailIndex: e.callFailIndex, resolveImportsCalled: map[Index]Index{}}, nil
 }
 
-// mockModuleEngine implements the same method as documented on wasm.ModuleEngine.
+// GetGlobalValue implements the same method as documented on wasm.ModuleEngine.
+func (e *mockModuleEngine) GetGlobalValue(idx Index) (lo, hi uint64) { panic("BUG") }
+
+// OwnsGlobals implements the same method as documented on wasm.ModuleEngine.
+func (e *mockModuleEngine) OwnsGlobals() bool { return false }
+
+// DoneInstantiation implements the same method as documented on wasm.ModuleEngine.
 func (e *mockModuleEngine) DoneInstantiation() {}
 
 // FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine.