Repository: tetratelabs/wazero
Pull request #1323 (2023-03-31 01:19:30)

compiler: bitmask for tracking used registers
_arm64_
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
                                    │   old.txt   │              new.txt               │
                                    │   sec/op    │   sec/op     vs base               │
Compilation/with_extern_cache-10      102.6µ ± 3%   103.1µ ± 9%        ~ (p=0.620 n=7)
Compilation/without_extern_cache-10   2.089m ± 1%   1.767m ± 2%  -15.42% (p=0.001 n=7)
geomean                               462.9µ        426.9µ        -7.78%

                                    │   old.txt    │              new.txt               │
                                    │     B/op     │     B/op      vs base              │
Compilation/with_extern_cache-10      53.36Ki ± 0%   53.36Ki ± 0%       ~ (p=0.738 n=7)
Compilation/without_extern_cache-10   1.211Mi ± 0%   1.160Mi ± 0%  -4.23% (p=0.001 n=7)
geomean                               257.2Ki        251.7Ki       -2.14%

                                    │   old.txt   │               new.txt                │
                                    │  allocs/op  │  allocs/op   vs base                 │
Compilation/with_extern_cache-10       979.0 ± 0%    979.0 ± 0%        ~ (p=1.000 n=7) ¹
Compilation/without_extern_cache-10   12.18k ± 0%   10.58k ± 0%  -13.13% (p=0.001 n=7)
geomean                               3.453k        3.218k        -6.80%
¹ all samples are equal
```

_amd64_

```
goos: darwin
goarch: amd64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
cpu: VirtualApple @ 2.50GHz
                                    │ old_amd64.txt │           new_amd64.txt            │
                                    │    sec/op     │   sec/op     vs base               │
Compilation/with_extern_cache-10        252.2µ ± 1%   250.6µ ± 1%        ~ (p=0.383 n=7)
Compilation/without_extern_cache-10     4.206m ± 1%   3.692m ± 1%  -12.22% (p=0.001 n=7)
geomean                                 1.030m        961.8µ        -6.61%

                                    │ old_amd64.txt │           new_amd64.txt            │
                                    │     B/op      │     B/op      vs base              │
Compilation/with_extern_cache-10       53.56Ki ± 0%   53.55Ki ± 0%       ~ (p=0.177 n=7)
Compilation/without_extern_cache-10    1.195Mi ± 0%   1.168Mi ± 0%  -2.23% (p=0.001 n=7)
geomean                                256.0Ki        253.1Ki       -1.13%

                                    │ old_amd64.txt │           new_amd64.txt           │
                                    │   allocs/op   │  allocs/op   vs base              │
Compilation/with_extern_cache-10         981.0 ± 0%    981.0 ± 0%       ~ (p=0.462 n=7)
Compilation/without_extern_cache-10     12.32k ± 0%   11.48k ± 0%  -6.76% (p=0.001 n=7)
geomean                                 3.476k        3.356k       -3.44%

```


Patch:
diff --git a/internal/engine/compiler/arch_amd64.go b/internal/engine/compiler/arch_amd64.go
index ff2ec46b..b09a1cab 100644
--- a/internal/engine/compiler/arch_amd64.go
+++ b/internal/engine/compiler/arch_amd64.go
@@ -1,6 +1,7 @@
 package compiler
 
 import (
+	"github.com/tetratelabs/wazero/internal/asm"
 	"github.com/tetratelabs/wazero/internal/asm/amd64"
 )
 
@@ -24,3 +25,11 @@ func newArchContextImpl() (ret archContext) { return }
 func newCompiler() compiler {
 	return newAmd64Compiler()
 }
+
+func registerMaskShift(r asm.Register) int {
+	return int(r - amd64.RegAX)
+}
+
+func registerFromMaskShift(s int) asm.Register {
+	return amd64.RegAX + asm.Register(s)
+}
diff --git a/internal/engine/compiler/arch_arm64.go b/internal/engine/compiler/arch_arm64.go
index c176e5d6..4262da61 100644
--- a/internal/engine/compiler/arch_arm64.go
+++ b/internal/engine/compiler/arch_arm64.go
@@ -3,6 +3,7 @@ package compiler
 import (
 	"math"
 
+	"github.com/tetratelabs/wazero/internal/asm"
 	"github.com/tetratelabs/wazero/internal/asm/arm64"
 )
 
@@ -49,3 +50,21 @@ func newArchContextImpl() archContext {
 func newCompiler() compiler {
 	return newArm64Compiler()
 }
+
+func registerMaskShift(r asm.Register) (ret int) {
+	ret = int(r - arm64.RegR0)
+	if r > arm64.RegSP {
+		// Skips arm64.RegSP which is not a real register.
+		ret--
+	}
+	return
+}
+
+func registerFromMaskShift(s int) asm.Register {
+	if s < 32 {
+		return arm64.RegR0 + asm.Register(s)
+	} else {
+		// Skips arm64.RegSP which is not a real register.
+		return arm64.RegR0 + asm.Register(s) + 1
+	}
+}
diff --git a/internal/engine/compiler/arch_other.go b/internal/engine/compiler/arch_other.go
index b0910fdd..e53ac8df 100644
--- a/internal/engine/compiler/arch_other.go
+++ b/internal/engine/compiler/arch_other.go
@@ -5,6 +5,8 @@ package compiler
 import (
 	"fmt"
 	"runtime"
+
+	"github.com/tetratelabs/wazero/internal/asm"
 )
 
 // archContext is empty on an unsupported architecture.
@@ -14,3 +16,11 @@ type archContext struct{}
 func newCompiler() compiler {
 	panic(fmt.Sprintf("unsupported GOARCH %s", runtime.GOARCH))
 }
+
+func registerMaskShift(r asm.Register) (ret int) {
+	panic(fmt.Sprintf("unsupported GOARCH %s", runtime.GOARCH))
+}
+
+func registerFromMaskShift(s int) asm.Register {
+	panic(fmt.Sprintf("unsupported GOARCH %s", runtime.GOARCH))
+}
diff --git a/internal/engine/compiler/compiler_value_location.go b/internal/engine/compiler/compiler_value_location.go
index 608d5654..1b5b18dc 100644
--- a/internal/engine/compiler/compiler_value_location.go
+++ b/internal/engine/compiler/compiler_value_location.go
@@ -112,7 +112,6 @@ func (v *runtimeValueLocation) String() string {
 func newRuntimeValueLocationStack() runtimeValueLocationStack {
 	return runtimeValueLocationStack{
 		stack:                             make([]runtimeValueLocation, 10),
-		usedRegisters:                     map[asm.Register]struct{}{},
 		unreservedVectorRegisters:         unreservedVectorRegisters,
 		unreservedGeneralPurposeRegisters: unreservedGeneralPurposeRegisters,
 	}
@@ -133,8 +132,8 @@ type runtimeValueLocationStack struct {
 	stack []runtimeValueLocation
 	// sp is the current stack pointer.
 	sp uint64
-	// usedRegisters stores the used registers.
-	usedRegisters map[asm.Register]struct{}
+	// usedRegisters is the bit map to track the used registers.
+	usedRegisters usedRegistersMask
 	// stackPointerCeil tracks max(.sp) across the lifespan of this struct.
 	stackPointerCeil uint64
 	// unreservedGeneralPurposeRegisters and unreservedVectorRegisters hold
@@ -149,7 +148,7 @@ func (v *runtimeValueLocationStack) initialized() bool {
 func (v *runtimeValueLocationStack) reset() {
 	v.stackPointerCeil, v.sp = 0, 0
 	v.stack = v.stack[:0]
-	v.usedRegisters = map[asm.Register]struct{}{}
+	v.usedRegisters = usedRegistersMask(0)
 }
 
 func (v *runtimeValueLocationStack) String() string {
@@ -157,20 +156,14 @@ func (v *runtimeValueLocationStack) String() string {
 	for i := uint64(0); i < v.sp; i++ {
 		stackStr = append(stackStr, v.stack[i].String())
 	}
-	var usedRegisters []string
-	for reg := range v.usedRegisters {
-		usedRegisters = append(usedRegisters, registerNameFn(reg))
-	}
+	usedRegisters := v.usedRegisters.list()
 	return fmt.Sprintf("sp=%d, stack=[%s], used_registers=[%s]", v.sp, strings.Join(stackStr, ","), strings.Join(usedRegisters, ","))
 }
 
 func (v *runtimeValueLocationStack) clone() runtimeValueLocationStack {
 	ret := runtimeValueLocationStack{}
 	ret.sp = v.sp
-	ret.usedRegisters = make(map[asm.Register]struct{}, len(ret.usedRegisters))
-	for r := range v.usedRegisters {
-		ret.markRegisterUsed(r)
-	}
+	ret.usedRegisters = v.usedRegisters
 	ret.stack = make([]runtimeValueLocation, len(v.stack))
 	copy(ret.stack, v.stack)
 	ret.stackPointerCeil = v.stackPointerCeil
@@ -245,13 +238,13 @@ func (v *runtimeValueLocationStack) releaseRegister(loc *runtimeValueLocation) {
 
 func (v *runtimeValueLocationStack) markRegisterUnused(regs ...asm.Register) {
 	for _, reg := range regs {
-		delete(v.usedRegisters, reg)
+		v.usedRegisters.remove(reg)
 	}
 }
 
 func (v *runtimeValueLocationStack) markRegisterUsed(regs ...asm.Register) {
 	for _, reg := range regs {
-		v.usedRegisters[reg] = struct{}{}
+		v.usedRegisters.add(reg)
 	}
 }
 
@@ -291,7 +284,7 @@ func (v *runtimeValueLocationStack) takeFreeRegister(tp registerType) (reg asm.R
 		targetRegs = v.unreservedGeneralPurposeRegisters
 	}
 	for _, candidate := range targetRegs {
-		if _, ok := v.usedRegisters[candidate]; ok {
+		if v.usedRegisters.exist(candidate) {
 			continue
 		}
 		return candidate, true
@@ -310,7 +303,7 @@ func (v *runtimeValueLocationStack) takeFreeRegisters(tp registerType, num int)
 
 	regs = make([]asm.Register, 0, num)
 	for _, candidate := range targetRegs {
-		if _, ok := v.usedRegisters[candidate]; ok {
+		if v.usedRegisters.exist(candidate) {
 			continue
 		}
 		regs = append(regs, candidate)
@@ -421,3 +414,34 @@ func (v *runtimeValueLocationStack) pushCallFrame(callTargetFunctionType *wasm.F
 	callerFunction.valueType = runtimeValueTypeI64
 	return
 }
+
+// usedRegistersMask tracks the used registers in its bits.
+type usedRegistersMask uint64
+
+// add adds the given `r` to the mask.
+func (u *usedRegistersMask) add(r asm.Register) {
+	*u = *u | (1 << registerMaskShift(r))
+}
+
+// remove drops the given `r` from the mask.
+func (u *usedRegistersMask) remove(r asm.Register) {
+	*u = *u & ^(1 << registerMaskShift(r))
+}
+
+// exist returns true if the given `r` is used.
+func (u *usedRegistersMask) exist(r asm.Register) bool {
+	shift := registerMaskShift(r)
+	return (*u & (1 << shift)) > 0
+}
+
+// list returns the list of debug string of used registers.
+// Only used for debugging and testing.
+func (u *usedRegistersMask) list() (ret []string) {
+	mask := *u
+	for i := 0; i < 64; i++ {
+		if mask&(1<<i) > 0 {
+			ret = append(ret, registerNameFn(registerFromMaskShift(i)))
+		}
+	}
+	return
+}


Test patch:
diff --git a/internal/engine/compiler/compiler_controlflow_test.go b/internal/engine/compiler/compiler_controlflow_test.go
index a00fd082..682422d0 100644
--- a/internal/engine/compiler/compiler_controlflow_test.go
+++ b/internal/engine/compiler/compiler_controlflow_test.go
@@ -444,7 +444,7 @@ func TestCompiler_compileBrTable(t *testing.T) {
 			err = compiler.compileBrTable(tc.o)
 			require.NoError(t, err)
 
-			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			requireRunAndExpectedValueReturned(t, env, compiler, tc.expectedValue)
 		})
diff --git a/internal/engine/compiler/compiler_drop_test.go b/internal/engine/compiler/compiler_drop_test.go
index 50c00e9f..ff93bd80 100644
--- a/internal/engine/compiler/compiler_drop_test.go
+++ b/internal/engine/compiler/compiler_drop_test.go
@@ -35,14 +35,14 @@ func Test_compileDropRange(t *testing.T) {
 
 		unreservedRegisterTotal := len(unreservedGeneralPurposeRegisters) + len(unreservedVectorRegisters)
 		ls := c.runtimeValueLocationStack()
-		require.Equal(t, unreservedRegisterTotal, len(ls.usedRegisters))
+		require.Equal(t, unreservedRegisterTotal, len(ls.usedRegisters.list()))
 
 		// Drop all the values.
 		err := compileDropRange(c, &wazeroir.InclusiveRange{Start: 0, End: int(ls.sp - 1)})
 		require.NoError(t, err)
 
 		// All the registers must be marked unused.
-		require.Equal(t, 0, len(ls.usedRegisters))
+		require.Equal(t, 0, len(ls.usedRegisters.list()))
 		// Also, stack pointer must be zero.
 		require.Equal(t, 0, int(ls.sp))
 	})
@@ -118,7 +118,7 @@ func Test_getTemporariesForStackedLiveValues(t *testing.T) {
 						c.pushRuntimeValueLocationOnRegister(reg, runtimeValueTypeI32)
 					}
 					// Ensures actually we used them up all.
-					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters),
+					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()),
 						len(unreservedGeneralPurposeRegisters))
 				}
 
@@ -127,7 +127,7 @@ func Test_getTemporariesForStackedLiveValues(t *testing.T) {
 
 				if !freeRegisterExists {
 					// At this point, one register should be marked as unused.
-					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters),
+					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()),
 						len(unreservedGeneralPurposeRegisters)-1)
 				}
 
@@ -158,7 +158,7 @@ func Test_getTemporariesForStackedLiveValues(t *testing.T) {
 						c.pushVectorRuntimeValueLocationOnRegister(reg)
 					}
 					// Ensures actually we used them up all.
-					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters),
+					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()),
 						len(unreservedVectorRegisters))
 				}
 
@@ -167,7 +167,7 @@ func Test_getTemporariesForStackedLiveValues(t *testing.T) {
 
 				if !freeRegisterExists {
 					// At this point, one register should be marked as unused.
-					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters),
+					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()),
 						len(unreservedVectorRegisters)-1)
 				}
 
diff --git a/internal/engine/compiler/compiler_global_test.go b/internal/engine/compiler/compiler_global_test.go
index ca4e0cad..0bbbf3f4 100644
--- a/internal/engine/compiler/compiler_global_test.go
+++ b/internal/engine/compiler/compiler_global_test.go
@@ -35,7 +35,7 @@ func TestCompiler_compileGlobalGet(t *testing.T) {
 			// At this point, the top of stack must be the retrieved global on a register.
 			global := compiler.runtimeValueLocationStack().peek()
 			require.True(t, global.onRegister())
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 			switch tp {
 			case wasm.ValueTypeF32, wasm.ValueTypeF64:
 				require.True(t, isVectorRegister(global.register))
@@ -82,7 +82,7 @@ func TestCompiler_compileGlobalGet_v128(t *testing.T) {
 	// At this point, the top of stack must be the retrieved global on a register.
 	global := compiler.runtimeValueLocationStack().peek()
 	require.True(t, global.onRegister())
-	require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+	require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 	require.True(t, isVectorRegister(global.register))
 	err = compiler.compileReturnFunction()
 	require.NoError(t, err)
diff --git a/internal/engine/compiler/compiler_initialization_test.go b/internal/engine/compiler/compiler_initialization_test.go
index 2a9e0afc..b21c1fa0 100644
--- a/internal/engine/compiler/compiler_initialization_test.go
+++ b/internal/engine/compiler/compiler_initialization_test.go
@@ -125,7 +125,7 @@ func TestCompiler_compileModuleContextInitialization(t *testing.T) {
 
 			err := compiler.compileModuleContextInitialization()
 			require.NoError(t, err)
-			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters), "expected no usedRegisters")
+			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list()), "expected no usedRegisters")
 
 			compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
 
diff --git a/internal/engine/compiler/compiler_memory_test.go b/internal/engine/compiler/compiler_memory_test.go
index ff92556f..4cd425c2 100644
--- a/internal/engine/compiler/compiler_memory_test.go
+++ b/internal/engine/compiler/compiler_memory_test.go
@@ -251,7 +251,7 @@ func TestCompiler_compileLoad(t *testing.T) {
 
 			// At this point, the loaded value must be on top of the stack, and placed on a register.
 			requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 			loadedLocation := compiler.runtimeValueLocationStack().peek()
 			require.True(t, loadedLocation.onRegister())
 			if tc.isFloatTarget {
@@ -391,7 +391,7 @@ func TestCompiler_compileStore(t *testing.T) {
 			tc.operationSetupFn(t, compiler)
 
 			// At this point, no registers must be in use, and no values on the stack since we consumed two values.
-			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 			requireRuntimeLocationStackPointerEqual(t, uint64(0), compiler)
 
 			// Generate the code under test.
diff --git a/internal/engine/compiler/compiler_numeric_test.go b/internal/engine/compiler/compiler_numeric_test.go
index c7e25558..9b8d9c58 100644
--- a/internal/engine/compiler/compiler_numeric_test.go
+++ b/internal/engine/compiler/compiler_numeric_test.go
@@ -1027,13 +1027,13 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 
 					// At this point two values are pushed.
 					requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-					require.Equal(t, 2, len(compiler.runtimeValueLocationStack().usedRegisters))
+					require.Equal(t, 2, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 					tc.setupFunc(t, compiler)
 
 					// We consumed two values, but push one value after operation.
 					requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
-					require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+					require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 					err = compiler.compileReturnFunction()
 					require.NoError(t, err)
@@ -1330,13 +1330,13 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 
 					// At this point two values are pushed.
 					requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
-					require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+					require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 					tc.setupFunc(t, compiler)
 
 					// We consumed one value, but push the result after operation.
 					requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
-					require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+					require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 					err = compiler.compileReturnFunction()
 					require.NoError(t, err)
diff --git a/internal/engine/compiler/compiler_stack_test.go b/internal/engine/compiler/compiler_stack_test.go
index d1230a3c..56b0b5d9 100644
--- a/internal/engine/compiler/compiler_stack_test.go
+++ b/internal/engine/compiler/compiler_stack_test.go
@@ -5,7 +5,6 @@ import (
 	"math"
 	"testing"
 
-	"github.com/tetratelabs/wazero/internal/asm"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/wazeroir"
 )
@@ -37,7 +36,6 @@ func TestCompiler_releaseRegisterToStack(t *testing.T) {
 			s := runtimeValueLocationStack{
 				sp:                                tc.stackPointer,
 				stack:                             make([]runtimeValueLocation, tc.stackPointer),
-				usedRegisters:                     map[asm.Register]struct{}{},
 				unreservedVectorRegisters:         unreservedVectorRegisters,
 				unreservedGeneralPurposeRegisters: unreservedGeneralPurposeRegisters,
 			}
@@ -102,7 +100,7 @@ func TestCompiler_compileLoadValueOnStackToRegister(t *testing.T) {
 			compiler.runtimeValueLocationStack().sp = tc.stackPointer
 			compiler.runtimeValueLocationStack().stack = make([]runtimeValueLocation, tc.stackPointer)
 
-			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 			loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
 			if tc.isFloat {
 				loc.valueType = runtimeValueTypeF64
@@ -115,7 +113,7 @@ func TestCompiler_compileLoadValueOnStackToRegister(t *testing.T) {
 			// Release the stack-allocated value to register.
 			err = compiler.compileEnsureOnRegister(loc)
 			require.NoError(t, err)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 			require.True(t, loc.onRegister())
 
 			// To verify the behavior, increment the value on the register.
diff --git a/internal/engine/compiler/compiler_value_location_test.go b/internal/engine/compiler/compiler_value_location_test.go
index 46283879..d381db60 100644
--- a/internal/engine/compiler/compiler_value_location_test.go
+++ b/internal/engine/compiler/compiler_value_location_test.go
@@ -37,10 +37,10 @@ func TestRuntimeValueLocationStack_basic(t *testing.T) {
 	// markRegisterUsed.
 	tmpReg2 := unreservedGeneralPurposeRegisters[1]
 	s.markRegisterUsed(tmpReg2)
-	require.NotNil(t, s.usedRegisters[tmpReg2], tmpReg2)
+	require.True(t, s.usedRegisters.exist(tmpReg2))
 	// releaseRegister.
 	s.releaseRegister(loc)
-	require.Equal(t, s.usedRegisters[loc.register], struct{}{}, "expected %v to not contain %v", s.usedRegisters, loc.register)
+	require.False(t, s.usedRegisters.exist(loc.register))
 	require.Equal(t, asm.NilRegister, loc.register)
 	// Clone.
 	cloned := s.clone()
@@ -218,3 +218,15 @@ func TestRuntimeValueLocation_pushCallFrame(t *testing.T) {
 		})
 	}
 }
+
+func Test_usedRegistersMask(t *testing.T) {
+	for _, r := range append(unreservedVectorRegisters, unreservedGeneralPurposeRegisters...) {
+		mask := usedRegistersMask(0)
+		mask.add(r)
+		require.False(t, mask == 0)
+		require.True(t, mask.exist(r))
+		mask.remove(r)
+		require.True(t, mask == 0)
+		require.False(t, mask.exist(r))
+	}
+}
diff --git a/internal/engine/compiler/compiler_vec_test.go b/internal/engine/compiler/compiler_vec_test.go
index bd28468b..5096abc3 100644
--- a/internal/engine/compiler/compiler_vec_test.go
+++ b/internal/engine/compiler/compiler_vec_test.go
@@ -87,7 +87,7 @@ func TestCompiler_compileV128Add(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -184,7 +184,7 @@ func TestCompiler_compileV128Sub(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -559,7 +559,7 @@ func TestCompiler_compileV128Load(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 			loadedLocation := compiler.runtimeValueLocationStack().peek()
 			require.True(t, loadedLocation.onRegister())
 
@@ -767,7 +767,7 @@ func TestCompiler_compileV128LoadLane(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 			loadedLocation := compiler.runtimeValueLocationStack().peek()
 			require.True(t, loadedLocation.onRegister())
 
@@ -821,7 +821,7 @@ func TestCompiler_compileV128Store(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(0), compiler)
-			require.Equal(t, 0, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 0, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -965,7 +965,7 @@ func TestCompiler_compileV128StoreLane(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(0), compiler)
-			require.Equal(t, 0, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 0, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -1136,7 +1136,7 @@ func TestCompiler_compileV128ExtractLane(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			vt := compiler.runtimeValueLocationStack().peek().valueType
 			switch tc.shape {
@@ -1385,7 +1385,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -1483,7 +1483,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -1692,7 +1692,7 @@ func TestCompiler_compileV128AllTrue(t *testing.T) {
 			err = compiler.compileV128AllTrue(wazeroir.OperationV128AllTrue{Shape: tc.shape})
 			require.NoError(t, err)
 
-			require.Equal(t, 0, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 0, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -1798,7 +1798,7 @@ func TestCompiler_compileV128Swizzle(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -1908,7 +1908,7 @@ func TestCompiler_compileV128Shuffle(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -2043,7 +2043,7 @@ func TestCompiler_compileV128Bitmask(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -2080,7 +2080,7 @@ func TestCompiler_compileV128_Not(t *testing.T) {
 	require.NoError(t, err)
 
 	requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-	require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+	require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 	err = compiler.compileReturnFunction()
 	require.NoError(t, err)
@@ -2307,7 +2307,7 @@ func TestCompiler_compileV128_And_Or_Xor_AndNot(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -2401,7 +2401,7 @@ func TestCompiler_compileV128Bitselect(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -2686,7 +2686,7 @@ func TestCompiler_compileV128Shl(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -2962,7 +2962,7 @@ func TestCompiler_compileV128Shr(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -3397,7 +3397,7 @@ func TestCompiler_compileV128Cmp(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -3478,7 +3478,7 @@ func TestCompiler_compileV128AvgrU(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -3544,7 +3544,7 @@ func TestCompiler_compileV128Sqrt(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -3634,7 +3634,7 @@ func TestCompiler_compileV128Mul(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -3731,7 +3731,7 @@ func TestCompiler_compileV128Neg(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -3828,7 +3828,7 @@ func TestCompiler_compileV128Abs(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -3904,7 +3904,7 @@ func TestCompiler_compileV128Div(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -4096,7 +4096,7 @@ func TestCompiler_compileV128Min(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -4323,7 +4323,7 @@ func TestCompiler_compileV128Max(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -4464,7 +4464,7 @@ func TestCompiler_compileV128AddSat(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -4576,7 +4576,7 @@ func TestCompiler_compileV128SubSat(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -4646,7 +4646,7 @@ func TestCompiler_compileV128Popcnt(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -4826,7 +4826,7 @@ func TestCompiler_compileV128Round(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -5118,7 +5118,7 @@ func TestCompiler_compileV128_Pmax_Pmin(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -5813,7 +5813,7 @@ func TestCompiler_compileV128ExtMul(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -6286,7 +6286,7 @@ func TestCompiler_compileV128Extend(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -6370,7 +6370,7 @@ func TestCompiler_compileV128Q15mulrSatS(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -6443,7 +6443,7 @@ func TestCompiler_compileFloatPromote(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -6527,7 +6527,7 @@ func TestCompiler_compileV128FloatDemote(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -6737,7 +6737,7 @@ func TestCompiler_compileV128ExtAddPairwise(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -6985,7 +6985,7 @@ func TestCompiler_compileV128Narrow(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -7122,7 +7122,7 @@ func TestCompiler_compileV128FConvertFromI(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -7194,7 +7194,7 @@ func TestCompiler_compileV128Dot(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -7345,7 +7345,7 @@ func TestCompiler_compileV128ITruncSatFromF(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
@@ -7398,7 +7398,7 @@ func TestCompiler_compileSelect_v128(t *testing.T) {
 		require.NoError(t, err)
 
 		requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-		require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+		require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 		err = compiler.compileReturnFunction()
 		require.NoError(t, err)
diff --git a/internal/engine/compiler/impl_amd64_test.go b/internal/engine/compiler/impl_amd64_test.go
index 589321f7..437d5b57 100644
--- a/internal/engine/compiler/impl_amd64_test.go
+++ b/internal/engine/compiler/impl_amd64_test.go
@@ -175,7 +175,7 @@ func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
 
 						require.Equal(t, registerTypeGeneralPurpose, compiler.runtimeValueLocationStack().peek().getRegisterType())
 						requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-						require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+						require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 						// At this point, the previous value on the DX register is saved to the stack.
 						require.True(t, prevOnDX.onStack())
 
@@ -301,7 +301,7 @@ func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
 
 						require.Equal(t, registerTypeGeneralPurpose, compiler.runtimeValueLocationStack().peek().getRegisterType())
 						requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
-						require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+						require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 						// At this point, the previous value on the DX register is saved to the stack.
 						require.True(t, prevOnDX.onStack())
 
diff --git a/internal/engine/compiler/impl_vec_amd64_test.go b/internal/engine/compiler/impl_vec_amd64_test.go
index f5812b84..c5c3d841 100644
--- a/internal/engine/compiler/impl_vec_amd64_test.go
+++ b/internal/engine/compiler/impl_vec_amd64_test.go
@@ -223,7 +223,7 @@ func TestAmd64Compiler_compileV128ShrI64x2SignedImpl(t *testing.T) {
 			err = amdCompiler.compileV128ShrI64x2SignedImpl()
 			require.NoError(t, err)
 
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)
diff --git a/internal/engine/compiler/impl_vec_arm64_test.go b/internal/engine/compiler/impl_vec_arm64_test.go
index e6b1113c..d14c17a1 100644
--- a/internal/engine/compiler/impl_vec_arm64_test.go
+++ b/internal/engine/compiler/impl_vec_arm64_test.go
@@ -196,7 +196,7 @@ func TestArm64Compiler_V128Shuffle_combinations(t *testing.T) {
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, tc.expStackPointerAfterShuffle, compiler)
-			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
 
 			err = compiler.compileReturnFunction()
 			require.NoError(t, err)