Repository: tetratelabs/wazero
Pull request #2151 (2024-03-12 06:03:06)

wazevo: fixes accidental heap allocation in VarLengthPool.Allocate
```
>> benchstat old.txt new8.txt
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/stdlibs
                             │   old.txt   │              new8.txt               │
                             │   sec/op    │   sec/op     vs base                │
Zig/Compile/test-opt.wasm-10   4.750 ± ∞ ¹   4.842 ± ∞ ¹       ~ (p=1.000 n=1) ²
Zig/Compile/test.wasm-10       5.931 ± ∞ ¹   5.899 ± ∞ ¹       ~ (p=1.000 n=1) ²
geomean                        5.308         5.345        +0.69%
¹ need >= 6 samples for confidence interval at level 0.95
² need >= 4 samples to detect a difference at alpha level 0.05

                             │    old.txt    │               new8.txt                │
                             │     B/op      │     B/op       vs base                │
Zig/Compile/test-opt.wasm-10   464.0Mi ± ∞ ¹   455.0Mi ± ∞ ¹       ~ (p=1.000 n=1) ²
Zig/Compile/test.wasm-10       717.8Mi ± ∞ ¹   703.8Mi ± ∞ ¹       ~ (p=1.000 n=1) ²
geomean                        577.1Mi         565.9Mi        -1.94%
¹ need >= 6 samples for confidence interval at level 0.95
² need >= 4 samples to detect a difference at alpha level 0.05

                             │    old.txt    │               new8.txt                │
                             │   allocs/op   │  allocs/op    vs base                 │
Zig/Compile/test-opt.wasm-10    953.9k ± ∞ ¹   436.8k ± ∞ ¹        ~ (p=1.000 n=1) ²
Zig/Compile/test.wasm-10       1137.6k ± ∞ ¹   555.1k ± ∞ ¹        ~ (p=1.000 n=1) ²
geomean                         1.042M         492.4k        -52.73%
¹ need >= 6 samples for confidence interval at level 0.95
² need >= 4 samples to detect a difference at alpha level 0.05
```


Patch:
diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
index f894414d..b7430175 100644
--- a/.github/workflows/integration.yaml
+++ b/.github/workflows/integration.yaml
@@ -126,7 +126,7 @@ jobs:
       - name: Run built test binaries
         run: |
           cd ${{ env.STDLIB_TESTS }}
-          go test -bench='BenchmarkZig' -timeout=20m
+          go test -bench='BenchmarkZig' -timeout=20m -benchtime=1x
 
   build_tinygo_test_binary:
     name: Build TinyGo test binary
@@ -213,7 +213,7 @@ jobs:
       - name: Run test binaries
         run: |
           cd ${{ env.STDLIB_TESTS }}
-          go test -bench='BenchmarkTinyGo' -timeout=20m
+          go test -bench='BenchmarkTinyGo' -timeout=20m -benchtime=1x
 
   wasi-testsuite:
     name: wasi-testsuite
@@ -347,7 +347,7 @@ jobs:
       - name: Run built test binaries
         run: |
           cd ${{ env.STDLIB_TESTS }}
-          go test -bench='BenchmarkWasip1' -timeout=20m
+          go test -bench='BenchmarkWasip1' -timeout=20m -benchtime=1x
 
   libsodium:
     name: libsodium (${{ matrix.os.name }}, ${{ matrix.os.arch }})
@@ -388,5 +388,4 @@ jobs:
         run: make libsodium
 
       - name: Run test binaries
-        #
         run: go test ./internal/integration_test/libsodium -bench=. -benchtime=1x
diff --git a/internal/engine/wazevo/frontend/frontend.go b/internal/engine/wazevo/frontend/frontend.go
index 6e2d40b1..d9714065 100644
--- a/internal/engine/wazevo/frontend/frontend.go
+++ b/internal/engine/wazevo/frontend/frontend.go
@@ -478,13 +478,11 @@ func (k *knownSafeBound) valid() bool {
 	return k != nil && k.bound > 0
 }
 
-func (c *Compiler) allocateVarLengthValues(vs ...ssa.Value) ssa.Values {
+func (c *Compiler) allocateVarLengthValues(_cap int, vs ...ssa.Value) ssa.Values {
 	builder := c.ssaBuilder
 	pool := builder.VarLengthPool()
-	args := pool.Allocate(len(vs))
-	for _, v := range vs {
-		args = args.Append(builder.VarLengthPool(), v)
-	}
+	args := pool.Allocate(_cap)
+	args = args.Append(builder.VarLengthPool(), vs...)
 	return args
 }
 
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index 28d5ba5d..11a9dbc4 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -119,13 +119,12 @@ func (c *Compiler) nPeekDup(n int) ssa.Values {
 	if n == 0 {
 		return ssa.ValuesNil
 	}
-	args := c.allocateVarLengthValues()
 
 	l := c.state()
 	tail := len(l.values)
-	for _, v := range l.values[tail-n : tail] {
-		args = args.Append(c.ssaBuilder.VarLengthPool(), v)
-	}
+
+	args := c.allocateVarLengthValues(n)
+	args = args.Append(c.ssaBuilder.VarLengthPool(), l.values[tail-n:tail]...)
 	return args
 }
 
@@ -581,7 +580,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 					ssa.TypeI64,
 				).Insert(builder).Return()
 
-			args := c.allocateVarLengthValues(c.execCtxPtrValue, tableIndexVal, num, r)
+			args := c.allocateVarLengthValues(4, c.execCtxPtrValue, tableIndexVal, num, r)
 			callGrowRet := builder.
 				AllocateInstruction().
 				AsCallIndirect(tableGrowPtr, &c.tableGrowSig, args).
@@ -690,7 +689,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 			builder.SetCurrentBlock(beforeLoop)
 			builder.AllocateInstruction().AsStore(ssa.OpcodeStore, value, addr, 0).Insert(builder)
 			initValue := builder.AllocateInstruction().AsIconst64(8).Insert(builder).Return()
-			c.insertJumpToBlock(c.allocateVarLengthValues(initValue), loopBlk)
+			c.insertJumpToBlock(c.allocateVarLengthValues(1, initValue), loopBlk)
 
 			builder.SetCurrentBlock(loopBlk)
 			dstAddr := builder.AllocateInstruction().AsIadd(addr, loopVar).Insert(builder).Return()
@@ -714,7 +713,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 				AsIcmp(newLoopVar, fillSizeInBytes, ssa.IntegerCmpCondUnsignedLessThan).Insert(builder).Return()
 
 			builder.AllocateInstruction().
-				AsBrnz(loopVarLessThanFillSize, c.allocateVarLengthValues(newLoopVar), loopBlk).
+				AsBrnz(loopVarLessThanFillSize, c.allocateVarLengthValues(1, newLoopVar), loopBlk).
 				Insert(builder)
 
 			c.insertJumpToBlock(ssa.ValuesNil, followingBlk)
@@ -766,7 +765,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 			builder.SetCurrentBlock(beforeLoop)
 			builder.AllocateInstruction().AsStore(ssa.OpcodeIstore8, value, addr, 0).Insert(builder)
 			initValue := builder.AllocateInstruction().AsIconst64(1).Insert(builder).Return()
-			c.insertJumpToBlock(c.allocateVarLengthValues(initValue), loopBlk)
+			c.insertJumpToBlock(c.allocateVarLengthValues(1, initValue), loopBlk)
 
 			builder.SetCurrentBlock(loopBlk)
 			dstAddr := builder.AllocateInstruction().AsIadd(addr, loopVar).Insert(builder).Return()
@@ -790,7 +789,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 				AsIcmp(newLoopVar, fillSize, ssa.IntegerCmpCondUnsignedLessThan).Insert(builder).Return()
 
 			builder.AllocateInstruction().
-				AsBrnz(loopVarLessThanFillSize, c.allocateVarLengthValues(newLoopVar), loopBlk).
+				AsBrnz(loopVarLessThanFillSize, c.allocateVarLengthValues(1, newLoopVar), loopBlk).
 				Insert(builder)
 
 			c.insertJumpToBlock(ssa.ValuesNil, followingBlk)
@@ -1182,7 +1181,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 				ssa.TypeI64,
 			).Insert(builder).Return()
 
-		args := c.allocateVarLengthValues(c.execCtxPtrValue, pages)
+		args := c.allocateVarLengthValues(1, c.execCtxPtrValue, pages)
 		callGrowRet := builder.
 			AllocateInstruction().
 			AsCallIndirect(memoryGrowPtr, &c.memoryGrowSig, args).
@@ -1352,10 +1351,8 @@ func (c *Compiler) lowerCurrentOpcode() {
 			blockType:                    bt,
 		})
 
-		args := c.allocateVarLengthValues()
-		for _, v := range state.values[originalLen:] {
-			args = args.Append(builder.VarLengthPool(), v)
-		}
+		args := c.allocateVarLengthValues(originalLen)
+		args = args.Append(builder.VarLengthPool(), state.values[originalLen:]...)
 
 		// Insert the jump to the header of loop.
 		br := builder.AllocateInstruction()
@@ -1371,7 +1368,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 					ssa.TypeI64,
 				).Insert(builder).Return()
 
-			args := c.allocateVarLengthValues(c.execCtxPtrValue)
+			args := c.allocateVarLengthValues(1, c.execCtxPtrValue)
 			builder.AllocateInstruction().
 				AsCallIndirect(checkModuleExitCodePtr, &c.checkModuleExitCodeSig, args).
 				Insert(builder)
@@ -1394,10 +1391,8 @@ func (c *Compiler) lowerCurrentOpcode() {
 		// multiple definitions (one in Then and another in Else blocks).
 		c.addBlockParamsFromWasmTypes(bt.Results, followingBlk)
 
-		args := c.allocateVarLengthValues()
-		for _, v := range state.values[len(state.values)-len(bt.Params):] {
-			args = args.Append(builder.VarLengthPool(), v)
-		}
+		args := c.allocateVarLengthValues(len(bt.Params))
+		args = args.Append(builder.VarLengthPool(), state.values[len(state.values)-len(bt.Params):]...)
 
 		// Insert the conditional jump to the Else block.
 		brz := builder.AllocateInstruction()
@@ -1636,15 +1631,13 @@ func (c *Compiler) lowerCurrentOpcode() {
 		tail := len(state.values) - argN
 		vs := state.values[tail:]
 		state.values = state.values[:tail]
-		args := c.allocateVarLengthValues(c.execCtxPtrValue)
+		args := c.allocateVarLengthValues(2+len(vs), c.execCtxPtrValue)
 
 		sig := c.signatures[typ]
 		call := builder.AllocateInstruction()
 		if fnIndex >= c.m.ImportFunctionCount {
 			args = args.Append(builder.VarLengthPool(), c.moduleCtxPtrValue) // This case the callee module is itself.
-			for _, v := range vs {
-				args = args.Append(builder.VarLengthPool(), v)
-			}
+			args = args.Append(builder.VarLengthPool(), vs...)
 			call.AsCall(FunctionIndexToFuncRef(fnIndex), sig, args)
 			builder.InsertInstruction(call)
 		} else {
@@ -1657,10 +1650,8 @@ func (c *Compiler) lowerCurrentOpcode() {
 			builder.InsertInstruction(loadFuncPtr)
 			builder.InsertInstruction(loadModuleCtxPtr)
 
-			args = args.Append(builder.VarLengthPool(), loadModuleCtxPtr.Return()) // This case the callee module is itself.
-			for _, v := range vs {
-				args = args.Append(builder.VarLengthPool(), v)
-			}
+			args = args.Append(builder.VarLengthPool(), loadModuleCtxPtr.Return())
+			args = args.Append(builder.VarLengthPool(), vs...)
 			call.AsCallIndirect(loadFuncPtr.Return(), sig, args)
 			builder.InsertInstruction(call)
 		}
@@ -3207,7 +3198,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 					ssa.TypeI64,
 				).Insert(builder).Return()
 
-			args := c.allocateVarLengthValues(c.execCtxPtrValue, timeout, exp, addr)
+			args := c.allocateVarLengthValues(3, c.execCtxPtrValue, timeout, exp, addr)
 			memoryWaitRet := builder.AllocateInstruction().
 				AsCallIndirect(memoryWaitPtr, sig, args).
 				Insert(builder).Return()
@@ -3228,7 +3219,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 					wazevoapi.ExecutionContextOffsetMemoryNotifyTrampolineAddress.U32(),
 					ssa.TypeI64,
 				).Insert(builder).Return()
-			args := c.allocateVarLengthValues(c.execCtxPtrValue, count, addr)
+			args := c.allocateVarLengthValues(2, c.execCtxPtrValue, count, addr)
 			memoryNotifyRet := builder.AllocateInstruction().
 				AsCallIndirect(memoryNotifyPtr, &c.memoryNotifySig, args).
 				Insert(builder).Return()
@@ -3432,7 +3423,7 @@ func (c *Compiler) lowerCurrentOpcode() {
 				ssa.TypeI64,
 			).Insert(builder).Return()
 
-		args := c.allocateVarLengthValues(c.execCtxPtrValue, funcIndexVal)
+		args := c.allocateVarLengthValues(2, c.execCtxPtrValue, funcIndexVal)
 		refFuncRet := builder.
 			AllocateInstruction().
 			AsCallIndirect(refFuncPtr, &c.refFuncSig, args).
@@ -3611,10 +3602,8 @@ func (c *Compiler) lowerCallIndirect(typeIndex, tableIndex uint32) {
 	tail := len(state.values) - len(typ.Params)
 	vs := state.values[tail:]
 	state.values = state.values[:tail]
-	args := c.allocateVarLengthValues(c.execCtxPtrValue, moduleContextOpaquePtr)
-	for _, v := range vs {
-		args = args.Append(builder.VarLengthPool(), v)
-	}
+	args := c.allocateVarLengthValues(2+len(vs), c.execCtxPtrValue, moduleContextOpaquePtr)
+	args = args.Append(builder.VarLengthPool(), vs...)
 
 	// Before transfer the control to the callee, we have to store the current module's moduleContextPtr
 	// into execContext.callerModuleContextPtr in case when the callee is a Go function.
@@ -3743,7 +3732,7 @@ func (c *Compiler) memAlignmentCheck(addr ssa.Value, operationSizeInBytes uint64
 }
 
 func (c *Compiler) callMemmove(dst, src, size ssa.Value) {
-	args := c.allocateVarLengthValues(dst, src, size)
+	args := c.allocateVarLengthValues(3, dst, src, size)
 	if size.Type() != ssa.TypeI64 {
 		panic("TODO: memmove size must be i64")
 	}
@@ -4141,7 +4130,7 @@ func (c *Compiler) callListenerBefore() {
 	entry := builder.EntryBlock()
 	ps := entry.Params()
 
-	args := c.allocateVarLengthValues(c.execCtxPtrValue,
+	args := c.allocateVarLengthValues(ps, c.execCtxPtrValue,
 		builder.AllocateInstruction().AsIconst32(c.wasmLocalFunctionIndex).Insert(builder).Return())
 	for i := 2; i < ps; i++ {
 		args = args.Append(builder.VarLengthPool(), entry.Param(i))
@@ -4170,14 +4159,15 @@ func (c *Compiler) callListenerAfter() {
 		Return()
 
 	afterSig := c.listenerSignatures[c.wasmFunctionTyp][1]
-	args := c.allocateVarLengthValues(c.execCtxPtrValue,
-		builder.AllocateInstruction().AsIconst32(c.wasmLocalFunctionIndex).Insert(builder).Return())
+	args := c.allocateVarLengthValues(
+		c.results()+2,
+		c.execCtxPtrValue,
+		builder.AllocateInstruction().AsIconst32(c.wasmLocalFunctionIndex).Insert(builder).Return(),
+	)
 
 	l := c.state()
 	tail := len(l.values)
-	for _, v := range l.values[tail-c.results() : tail] {
-		args = args.Append(c.ssaBuilder.VarLengthPool(), v)
-	}
+	args = args.Append(c.ssaBuilder.VarLengthPool(), l.values[tail-c.results():tail]...)
 	builder.AllocateInstruction().
 		AsCallIndirect(afterListenerPtr, afterSig, args).
 		Insert(builder)
diff --git a/internal/engine/wazevo/wazevoapi/pool.go b/internal/engine/wazevo/wazevoapi/pool.go
index 87ae8733..3149fdc9 100644
--- a/internal/engine/wazevo/wazevoapi/pool.go
+++ b/internal/engine/wazevo/wazevoapi/pool.go
@@ -108,23 +108,35 @@ func (p *IDedPool[T]) MaxIDEncountered() int {
 	return p.maxIDEncountered
 }
 
-const arraySize = 20
+// arraySize is the size of the array used in VarLengthPool's arrayPool.
+// This is chosen to be 8, which is empirically a good number among 8, 12, 16 and 20.
+const arraySize = 8
 
 // VarLengthPool is a pool of VarLength[T] that can be allocated and reset.
-type VarLengthPool[T any] struct {
-	arrayPool Pool[[arraySize]T]
-	slicePool Pool[[]T]
-}
+type (
+	VarLengthPool[T any] struct {
+		arrayPool Pool[varLengthPoolArray[T]]
+		slicePool Pool[[]T]
+	}
+	// varLengthPoolArray wraps an array and keeps track of the next index to be used to avoid the heap allocation.
+	varLengthPoolArray[T any] struct {
+		arr  [arraySize]T
+		next int
+	}
+)
 
 // VarLength is a variable length array that can be reused via a pool.
 type VarLength[T any] struct {
-	backing *[]T
+	arr *varLengthPoolArray[T]
+	slc *[]T
 }
 
 // NewVarLengthPool returns a new VarLengthPool.
 func NewVarLengthPool[T any]() VarLengthPool[T] {
 	return VarLengthPool[T]{
-		arrayPool: NewPool[[arraySize]T](nil),
+		arrayPool: NewPool[varLengthPoolArray[T]](func(v *varLengthPoolArray[T]) {
+			v.next = 0
+		}),
 		slicePool: NewPool[[]T](func(i *[]T) {
 			*i = (*i)[:0]
 		}),
@@ -133,18 +145,17 @@ func NewVarLengthPool[T any]() VarLengthPool[T] {
 
 // NewNilVarLength returns a new VarLength[T] with a nil backing.
 func NewNilVarLength[T any]() VarLength[T] {
-	var v *[]T = nil
-	return VarLength[T]{backing: v}
+	return VarLength[T]{}
 }
 
 // Allocate allocates a new VarLength[T] from the pool.
 func (p *VarLengthPool[T]) Allocate(knownMin int) VarLength[T] {
 	if knownMin <= arraySize {
-		arr := p.arrayPool.Allocate()[:0]
-		return VarLength[T]{backing: &arr}
+		arr := p.arrayPool.Allocate()
+		return VarLength[T]{arr: arr}
 	}
 	slc := p.slicePool.Allocate()
-	return VarLength[T]{backing: slc}
+	return VarLength[T]{slc: slc}
 }
 
 // Reset resets the pool.
@@ -153,35 +164,52 @@ func (p *VarLengthPool[T]) Reset() {
 	p.slicePool.Reset()
 }
 
-// Append appends an item to the backing slice.
-func (i VarLength[T]) Append(p *VarLengthPool[T], item T) VarLength[T] {
-	if i.backing == nil {
-		arr := p.arrayPool.Allocate()[:0]
-		i.backing = &arr
+// Append appends items to the backing slice just like the `append` builtin function in Go.
+func (i VarLength[T]) Append(p *VarLengthPool[T], items ...T) VarLength[T] {
+	if i.slc != nil {
+		*i.slc = append(*i.slc, items...)
+		return i
 	}
 
-	if len(*i.backing)+1 <= arraySize {
-		*i.backing = append(*i.backing, item)
-		return i
-	} else if len(*i.backing) == arraySize {
+	if i.arr == nil {
+		i.arr = p.arrayPool.Allocate()
+	}
+
+	arr := i.arr
+	if arr.next+len(items) <= arraySize {
+		for _, item := range items {
+			arr.arr[arr.next] = item
+			arr.next++
+		}
+	} else {
 		slc := p.slicePool.Allocate()
 		// Copy the array to the slice.
-		*slc = append(*slc, *i.backing...)
+		for ptr := 0; ptr < arr.next; ptr++ {
+			*slc = append(*slc, arr.arr[ptr])
+		}
+		i.slc = slc
+		*i.slc = append(*i.slc, items...)
 	}
-	*i.backing = append(*i.backing, item)
 	return i
 }
 
 // View returns the backing slice.
 func (i VarLength[T]) View() []T {
-	if i.backing == nil {
-		return nil
+	if i.slc != nil {
+		return *i.slc
+	} else if i.arr != nil {
+		arr := i.arr
+		return arr.arr[:arr.next]
 	}
-	return *i.backing
+	return nil
 }
 
 // Cut cuts the backing slice to the given length.
 // Precondition: n <= len(i.backing).
 func (i VarLength[T]) Cut(n int) {
-	*i.backing = (*i.backing)[:n]
+	if i.slc != nil {
+		*i.slc = (*i.slc)[:n]
+	} else if i.arr != nil {
+		i.arr.next = n
+	}
 }


Test patch:
diff --git a/internal/engine/wazevo/ssa/pass_blk_layouts_test.go b/internal/engine/wazevo/ssa/pass_blk_layouts_test.go
index 26a5d529..f81bd80d 100644
--- a/internal/engine/wazevo/ssa/pass_blk_layouts_test.go
+++ b/internal/engine/wazevo/ssa/pass_blk_layouts_test.go
@@ -277,9 +277,7 @@ func TestBuilder_LayoutBlocks(t *testing.T) {
 		b.SetCurrentBlock(src)
 		jump := b.AllocateInstruction()
 		args := b.varLengthPool.Allocate(len(vs))
-		for _, v := range vs {
-			args = args.Append(&b.varLengthPool, v)
-		}
+		args = args.Append(&b.varLengthPool, vs...)
 		jump.AsJump(args, dst)
 		b.InsertInstruction(jump)
 	}
@@ -290,9 +288,7 @@ func TestBuilder_LayoutBlocks(t *testing.T) {
 		b.InsertInstruction(vinst)
 		brz := b.AllocateInstruction()
 		args := b.varLengthPool.Allocate(len(vs))
-		for _, v := range vs {
-			args = args.Append(&b.varLengthPool, v)
-		}
+		args = args.Append(&b.varLengthPool, vs...)
 		brz.AsBrz(condVal, args, dst)
 		b.InsertInstruction(brz)
 	}
diff --git a/internal/engine/wazevo/wazevoapi/pool_test.go b/internal/engine/wazevo/wazevoapi/pool_test.go
index e76aa060..d99b089d 100644
--- a/internal/engine/wazevo/wazevoapi/pool_test.go
+++ b/internal/engine/wazevo/wazevoapi/pool_test.go
@@ -17,34 +17,34 @@ func TestNewNilVarLength(t *testing.T) {
 func TestAllocate(t *testing.T) {
 	pool := NewVarLengthPool[uint64]()
 	// Array:
-	v := pool.Allocate(10)
-	require.NotNil(t, v.backing)
-	require.Equal(t, 0, len(*v.backing))
-	require.Equal(t, arraySize, cap(*v.backing))
+	v := pool.Allocate(5)
+	require.NotNil(t, v.arr)
+	require.Equal(t, 0, v.arr.next)
+	require.Equal(t, arraySize, cap(v.arr.arr))
 
 	// Slice backed:
 	v = pool.Allocate(25)
-	require.NotNil(t, v.backing)
-	require.Equal(t, 0, len(*v.backing))
+	require.NotNil(t, v.slc)
+	require.Equal(t, 0, len(*v.slc))
 	v.Append(&pool, 1)
-	require.NotNil(t, v.backing)
-	require.Equal(t, 1, len(*v.backing))
+	require.NotNil(t, v.slc)
+	require.Equal(t, 1, len(*v.slc))
 	v.Append(&pool, 2)
-	require.NotNil(t, v.backing)
-	require.Equal(t, 2, len(*v.backing))
-	capacity := cap(*v.backing)
+	require.NotNil(t, v.slc)
+	require.Equal(t, 2, len(*v.slc))
+	capacity := cap(*v.slc)
 
 	// Reset the pool and ensure the backing slice is reused.
 	pool.Reset()
 
-	v = pool.Allocate(10)
-	require.NotNil(t, v.backing)
-	require.Equal(t, 0, len(*v.backing))
-	require.Equal(t, arraySize, cap(*v.backing))
+	v = pool.Allocate(5)
+	require.NotNil(t, v.arr)
+	require.Equal(t, 0, v.arr.next)
+	require.Equal(t, arraySize, cap(v.arr.arr))
 	v = pool.Allocate(25)
-	require.NotNil(t, v.backing)
-	require.Equal(t, 0, len(*v.backing))
-	require.Equal(t, capacity, cap(*v.backing))
+	require.NotNil(t, v.slc)
+	require.Equal(t, 0, len(*v.slc))
+	require.Equal(t, capacity, cap(*v.slc))
 }
 
 func TestAppendAndView(t *testing.T) {
diff --git a/internal/integration_test/stdlibs/bench_test.go b/internal/integration_test/stdlibs/bench_test.go
index b11275b9..544cef8f 100644
--- a/internal/integration_test/stdlibs/bench_test.go
+++ b/internal/integration_test/stdlibs/bench_test.go
@@ -129,35 +129,31 @@ func runtBenches(b *testing.B, ctx context.Context, rc wazero.RuntimeConfig, tc
 			continue
 		}
 
-		for _, compile := range []bool{false, true} {
-			if compile {
-				b.Run("Compile/"+fname, func(b *testing.B) {
-					b.ResetTimer()
-					for i := 0; i < b.N; i++ {
-						r := wazero.NewRuntimeWithConfig(ctx, rc)
-						_, err := r.CompileModule(ctx, bin)
-						require.NoError(b, err)
-						require.NoError(b, r.Close(ctx))
-					}
-				})
-			} else {
+		b.Run("Compile/"+fname, func(b *testing.B) {
+			b.ResetTimer()
+			for i := 0; i < b.N; i++ {
 				r := wazero.NewRuntimeWithConfig(ctx, rc)
-				wasi_snapshot_preview1.MustInstantiate(ctx, r)
-				b.Cleanup(func() { r.Close(ctx) })
-
-				cm, err := r.CompileModule(ctx, bin)
+				_, err := r.CompileModule(ctx, bin)
 				require.NoError(b, err)
-				b.Run("Run/"+fname, func(b *testing.B) {
-					b.ResetTimer()
-					for i := 0; i < b.N; i++ {
-						// Instantiate in the loop as _start cannot be called multiple times.
-						m, err := r.InstantiateModule(ctx, cm, modCfg)
-						requireZeroExitCode(b, err, stdout, stderr)
-						require.NoError(b, m.Close(ctx))
-					}
-				})
+				require.NoError(b, r.Close(ctx))
 			}
-		}
+		})
+		b.Run("Run/"+fname, func(b *testing.B) {
+			r := wazero.NewRuntimeWithConfig(ctx, rc)
+			wasi_snapshot_preview1.MustInstantiate(ctx, r)
+			b.Cleanup(func() { r.Close(ctx) })
+
+			cm, err := r.CompileModule(ctx, bin)
+			require.NoError(b, err)
+
+			b.ResetTimer()
+			for i := 0; i < b.N; i++ {
+				// Instantiate in the loop as _start cannot be called multiple times.
+				m, err := r.InstantiateModule(ctx, cm, modCfg)
+				requireZeroExitCode(b, err, stdout, stderr)
+				require.NoError(b, m.Close(ctx))
+			}
+		})
 	}
 }