Repository: tetratelabs/wazero
Pull request #1463 (2023-05-14 05:50:57)

asm: optimize node allocation
This PR modifies the allocation of `nodeImpl` objects in the arm64 and amd64 assemblers to reduce memory utilization.

The core change is to use a slice of pointers instead of a slice of values. The issue with `[][N]nodeImpl` is that when using `append`, the Go runtime may grow the slice which copies the existing content, but the pointers taken to the previous backing array remain, which results in an amplification of memory usage.

This was further impacted by the page size of 1000 items, which quickly crossed the threshold of 1024 values used by the Go runtime to determine the growth factor, resulting in increasing the backing array 1.25x (instead of 2x for small slices). More frequent reallocations of the backing array meant more waste of space due to existing pointers retaining the previous arrays.

I tested this compiling python with the profilers added in #1462, with a command like:
```
$ wazero compile -memprofile profile.out ~/wasm/bin/python-3.11.3.wasm
```
Overall, the changes **reduces the amount of memory allocated by ~35%**:
```
Showing nodes accounting for 123.50MB, 97.92% of 126.13MB total
```
```
Showing nodes accounting for 84.38MB, 100% of 84.38MB total
```

Here are the detailed comparisons for memory and CPU profiles:
```
Type: inuse_space
Time: May 13, 2023 at 9:57pm (PDT)
Showing nodes accounting for -41.75MB, 19.83% of 210.50MB total
Dropped 2 nodes (cum <= 1.05MB)
      flat  flat%   sum%        cum   cum%
  -41.37MB 19.65% 19.65%   -41.37MB 19.65%  github.com/tetratelabs/wazero/internal/asm/arm64.(*nodePool).allocNode (inline)
       3MB  1.43% 18.22%        3MB  1.43%  debug/dwarf.(*Data).parseAbbrev
   -2.92MB  1.39% 19.61%   -42.01MB 19.96%  github.com/tetratelabs/wazero/internal/engine/compiler.compileWasmFunction
    2.30MB  1.09% 18.52%   -41.75MB 19.83%  github.com/tetratelabs/wazero.(*runtime).CompileModule
   -1.99MB  0.95% 19.47%    -1.99MB  0.95%  github.com/tetratelabs/wazero/internal/wasm/binary.decodeCode
    1.78MB  0.85% 18.62%     1.78MB  0.85%  github.com/tetratelabs/wazero/internal/engine/compiler.(*arm64Compiler).label
   -1.27MB   0.6% 19.22%   -44.47MB 21.12%  github.com/tetratelabs/wazero/internal/engine/compiler.(*engine).CompileModule
   -1.18MB  0.56% 19.78%    -1.18MB  0.56%  github.com/tetratelabs/wazero/internal/wasm/binary.decodeCustomSection (inline)
    1.09MB  0.52% 19.26%     1.09MB  0.52%  github.com/tetratelabs/wazero/internal/wasm/binary.decodeDataSegment
       1MB  0.48% 18.79%        1MB  0.48%  github.com/tetratelabs/wazero/internal/engine/compiler.(*runtimeValueLocationStack).push (inline)
   -0.69MB  0.33% 19.12%    -0.69MB  0.33%  github.com/tetratelabs/wazero/internal/wazeroir.(*Compiler).emit
   -0.50MB  0.24% 19.36%    -0.50MB  0.24%  github.com/tetratelabs/wazero/internal/wasm/binary.decodeTypeSection
   -0.50MB  0.24% 19.59%    -0.50MB  0.24%  github.com/tetratelabs/wazero/internal/wazeroir.(*controlFrames).push (inline)
   -0.50MB  0.24% 19.83%    -0.50MB  0.24%  github.com/tetratelabs/wazero/internal/engine/compiler.(*runtimeValueLocationStack).cloneFrom (inline)
```
```
Type: cpu
Time: May 13, 2023 at 10:23pm (PDT)
Duration: 1.46s, Total samples = 1.14s (78.06%)
Showing nodes accounting for 0, 0% of 1.14s total
Dropped 4 nodes (cum <= 0.01s)
      flat  flat%   sum%        cum   cum%
     0.05s  4.39%  4.39%      0.05s  4.39%  runtime.madvise
    -0.03s  2.63%  1.75%     -0.07s  6.14%  github.com/tetratelabs/wazero/internal/asm/arm64.(*AssemblerImpl).Assemble
    -0.03s  2.63%  0.88%     -0.03s  2.63%  github.com/tetratelabs/wazero/internal/leb128.decodeUint32
     0.03s  2.63%  1.75%      0.03s  2.63%  runtime.memclrNoHeapPointers
    -0.02s  1.75%     0%     -0.09s  7.89%  github.com/tetratelabs/wazero/internal/engine/compiler.compileWasmFunction
    -0.01s  0.88%  0.88%     -0.03s  2.63%  github.com/tetratelabs/wazero/internal/asm/arm64.(*AssemblerImpl).encodeNode
    -0.01s  0.88%  1.75%     -0.01s  0.88%  github.com/tetratelabs/wazero/internal/asm/arm64.(*AssemblerImpl).maybeFlushConstPool
    -0.01s  0.88%  2.63%     -0.01s  0.88%  github.com/tetratelabs/wazero/internal/asm/arm64.intRegisterBits
     0.01s  0.88%  1.75%      0.01s  0.88%  github.com/tetratelabs/wazero/internal/wasm/binary.decodeDataSegment
     0.01s  0.88%  0.88%      0.01s  0.88%  runtime.(*atomicHeadTailIndex).incTail
     0.01s  0.88%     0%      0.01s  0.88%  runtime.(*mspan).init
     0.01s  0.88%  0.88%      0.01s  0.88%  runtime.(*wbBuf).putFast (inline)
    -0.01s  0.88%     0%     -0.01s  0.88%  runtime.findObject
     0.01s  0.88%  0.88%      0.01s  0.88%  runtime.markrootSpans
     0.01s  0.88%  1.75%      0.01s  0.88%  runtime.memmove
    -0.01s  0.88%  0.88%     -0.01s  0.88%  runtime.mmap
    -0.01s  0.88%     0%     -0.01s  0.88%  runtime.scanobject
    -0.01s  0.88%  0.88%     -0.01s  0.88%  runtime.writeHeapBits.write
     0.01s  0.88%     0%      0.01s  0.88%  runtime/internal/atomic.(*UnsafePointer).Load (inline)
```

I changed the page size from 1000 to 128 items to support more granular memory allocations as well, which reduced memory utilization a bit more (~2%), while still remaining effective at amortizing heap allocations.

For completeness, here's a comparison of the compilation benchmarks:
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
                                 │ /tmp/bench.base │           /tmp/bench.new           │
                                 │     sec/op      │   sec/op     vs base               │
Compilation/with_extern_cache          145.0µ ± 2%   149.2µ ± 2%  +2.92% (p=0.002 n=10)
Compilation/without_extern_cache       2.037m ± 3%   1.995m ± 3%       ~ (p=0.089 n=10)
Compilation/interpreter                660.6µ ± 1%   683.2µ ± 1%  +3.43% (p=0.000 n=10)
geomean                                579.9µ        588.0µ       +1.40%

                                 │ /tmp/bench.base │            /tmp/bench.new            │
                                 │      B/op       │     B/op      vs base                │
Compilation/with_extern_cache         48.87Ki ± 0%   48.84Ki ± 0%   -0.06% (p=0.000 n=10)
Compilation/without_extern_cache     1113.6Ki ± 0%   887.5Ki ± 0%  -20.30% (p=0.000 n=10)
Compilation/interpreter               758.9Ki ± 0%   759.0Ki ± 0%   +0.00% (p=0.015 n=10)
geomean                               345.7Ki        320.4Ki        -7.30%

                                 │ /tmp/bench.base │            /tmp/bench.new            │
                                 │    allocs/op    │  allocs/op   vs base                 │
Compilation/with_extern_cache           420.0 ± 0%    420.0 ± 0%       ~ (p=1.000 n=10) ¹
Compilation/without_extern_cache        983.0 ± 0%   1015.0 ± 0%  +3.26% (p=0.000 n=10)
Compilation/interpreter                 626.0 ± 0%    626.0 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                 637.0         643.8       +1.07%
¹ all samples are equal
```
```
goos: linux
goarch: amd64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
cpu: Intel(R) Xeon(R) CPU @ 2.60GHz
                                 │ /tmp/bench.base │           /tmp/bench.new           │
                                 │     sec/op      │   sec/op     vs base               │
Compilation/with_extern_cache          319.0µ ± 3%   317.1µ ± 2%       ~ (p=0.796 n=10)
Compilation/without_extern_cache       6.122m ± 3%   5.835m ± 2%  -4.69% (p=0.000 n=10)
Compilation/interpreter                1.253m ± 0%   1.261m ± 0%  +0.67% (p=0.000 n=10)
geomean                                1.347m        1.326m       -1.57%

                                 │ /tmp/bench.base │            /tmp/bench.new            │
                                 │      B/op       │     B/op      vs base                │
Compilation/with_extern_cache         49.12Ki ± 0%   49.02Ki ± 0%   -0.21% (p=0.000 n=10)
Compilation/without_extern_cache     1262.6Ki ± 0%   900.8Ki ± 0%  -28.65% (p=0.000 n=10)
Compilation/interpreter               759.0Ki ± 0%   759.0Ki ± 0%        ~ (p=0.402 n=10)
geomean                               361.1Ki        322.4Ki       -10.71%

                                 │ /tmp/bench.base │            /tmp/bench.new            │
                                 │    allocs/op    │  allocs/op   vs base                 │
Compilation/with_extern_cache           420.0 ± 0%    420.0 ± 0%       ~ (p=1.000 n=10) ¹
Compilation/without_extern_cache       1.131k ± 0%   1.160k ± 0%  +2.56% (p=0.000 n=10)
Compilation/interpreter                 627.0 ± 0%    627.0 ± 0%       ~ (p=0.211 n=10)
geomean                                 667.8         673.5       +0.85%
¹ all samples are equal
```

achille-roussel: Thanks for the review!

Patch:
diff --git a/internal/asm/amd64/impl.go b/internal/asm/amd64/impl.go
index 264a0302..e2d45a54 100644
--- a/internal/asm/amd64/impl.go
+++ b/internal/asm/amd64/impl.go
@@ -218,7 +218,7 @@ func (o operandTypes) String() (ret string) {
 type (
 	// AssemblerImpl implements Assembler.
 	AssemblerImpl struct {
-		nodePool *nodePool
+		nodePool nodePool
 		asm.BaseAssemblerImpl
 		root, current   *nodeImpl
 		buf             *bytes.Buffer
@@ -244,39 +244,53 @@ type (
 
 func NewAssembler() *AssemblerImpl {
 	return &AssemblerImpl{
-		nodePool:                       &nodePool{pages: [][nodePoolPageSize]nodeImpl{{}}},
+		nodePool:                       nodePool{index: nodePageSize},
 		buf:                            bytes.NewBuffer(nil),
 		pool:                           asm.NewStaticConstPool(),
 		MaxDisplacementForConstantPool: defaultMaxDisplacementForConstantPool,
 	}
 }
 
-const nodePoolPageSize = 1000
+const nodePageSize = 128
+
+type nodePage = [nodePageSize]nodeImpl
 
 // nodePool is the central allocation pool for nodeImpl used by a single AssemblerImpl.
 // This reduces the allocations over compilation by reusing AssemblerImpl.
 type nodePool struct {
-	pages [][nodePoolPageSize]nodeImpl
-	// page is the index on pages to allocate node on.
-	page,
-	// pos is the index on pages[.page] where the next allocation target exists.
-	pos int
+	pages []*nodePage
+	index int
 }
 
 // allocNode allocates a new nodeImpl for use from the pool.
 // This expands the pool if there is no space left for it.
-func (n *nodePool) allocNode() (ret *nodeImpl) {
-	if n.pos == nodePoolPageSize {
-		if len(n.pages)-1 == n.page {
-			n.pages = append(n.pages, [nodePoolPageSize]nodeImpl{})
+func (n *nodePool) allocNode() *nodeImpl {
+	if n.index == nodePageSize {
+		if len(n.pages) == cap(n.pages) {
+			n.pages = append(n.pages, new(nodePage))
+		} else {
+			i := len(n.pages)
+			n.pages = n.pages[:i+1]
+			if n.pages[i] == nil {
+				n.pages[i] = new(nodePage)
+			}
 		}
-		n.page++
-		n.pos = 0
+		n.index = 0
 	}
-	ret = &n.pages[n.page][n.pos]
-	*ret = nodeImpl{}
-	n.pos++
-	return
+	ret := &n.pages[len(n.pages)-1][n.index]
+	n.index++
+	return ret
+}
+
+func (n *nodePool) reset() {
+	for _, ns := range n.pages {
+		pages := ns[:]
+		for i := range pages {
+			pages[i] = nodeImpl{}
+		}
+	}
+	n.pages = n.pages[:0]
+	n.index = nodePageSize
 }
 
 // AllocateNOP implements asm.AssemblerBase.
@@ -307,8 +321,8 @@ func (a *AssemblerImpl) Reset() {
 			JumpTableEntries:           a.JumpTableEntries[:0],
 		},
 	}
-	a.nodePool.pos, a.nodePool.page = 0, 0
 	a.buf.Reset()
+	a.nodePool.reset()
 }
 
 // newNode creates a new Node and appends it into the linked list.
diff --git a/internal/asm/arm64/impl.go b/internal/asm/arm64/impl.go
index b271dc00..69662d14 100644
--- a/internal/asm/arm64/impl.go
+++ b/internal/asm/arm64/impl.go
@@ -201,7 +201,7 @@ const (
 
 // AssemblerImpl implements Assembler.
 type AssemblerImpl struct {
-	nodePool *nodePool
+	nodePool nodePool
 	asm.BaseAssemblerImpl
 	root, current     *nodeImpl
 	buf               *bytes.Buffer
@@ -214,37 +214,51 @@ type AssemblerImpl struct {
 	relativeJumpNodes, adrInstructionNodes []*nodeImpl
 }
 
-const nodePoolPageSize = 1000
+const nodePageSize = 128
+
+type nodePage = [nodePageSize]nodeImpl
 
 // nodePool is the central allocation pool for nodeImpl used by a single AssemblerImpl.
 // This reduces the allocations over compilation by reusing AssemblerImpl.
 type nodePool struct {
-	pages [][nodePoolPageSize]nodeImpl
-	// page is the index on pages to allocate node on.
-	page,
-	// pos is the index on pages[.page] where the next allocation target exists.
-	pos int
+	pages []*nodePage
+	index int
 }
 
 // allocNode allocates a new nodeImpl for use from the pool.
 // This expands the pool if there is no space left for it.
-func (n *nodePool) allocNode() (ret *nodeImpl) {
-	if n.pos == nodePoolPageSize {
-		if len(n.pages)-1 == n.page {
-			n.pages = append(n.pages, [nodePoolPageSize]nodeImpl{})
+func (n *nodePool) allocNode() *nodeImpl {
+	if n.index == nodePageSize {
+		if len(n.pages) == cap(n.pages) {
+			n.pages = append(n.pages, new(nodePage))
+		} else {
+			i := len(n.pages)
+			n.pages = n.pages[:i+1]
+			if n.pages[i] == nil {
+				n.pages[i] = new(nodePage)
+			}
 		}
-		n.page++
-		n.pos = 0
+		n.index = 0
 	}
-	ret = &n.pages[n.page][n.pos]
-	*ret = nodeImpl{}
-	n.pos++
-	return
+	ret := &n.pages[len(n.pages)-1][n.index]
+	n.index++
+	return ret
+}
+
+func (n *nodePool) reset() {
+	for _, ns := range n.pages {
+		pages := ns[:]
+		for i := range pages {
+			pages[i] = nodeImpl{}
+		}
+	}
+	n.pages = n.pages[:0]
+	n.index = nodePageSize
 }
 
 func NewAssembler(temporaryRegister asm.Register) *AssemblerImpl {
 	return &AssemblerImpl{
-		nodePool:                       &nodePool{pages: [][nodePoolPageSize]nodeImpl{{}}},
+		nodePool:                       nodePool{index: nodePageSize},
 		buf:                            bytes.NewBuffer(nil),
 		temporaryRegister:              temporaryRegister,
 		pool:                           asm.NewStaticConstPool(),
@@ -267,12 +281,13 @@ func (a *AssemblerImpl) Add(n asm.Node) {
 
 // Reset implements asm.AssemblerBase.
 func (a *AssemblerImpl) Reset() {
-	buf, np, tmp := a.buf, a.nodePool, a.temporaryRegister
 	pool := a.pool
 	pool.Reset()
 	*a = AssemblerImpl{
-		buf: buf, nodePool: np, pool: pool,
-		temporaryRegister:   tmp,
+		buf:                 a.buf,
+		nodePool:            a.nodePool,
+		pool:                pool,
+		temporaryRegister:   a.temporaryRegister,
 		adrInstructionNodes: a.adrInstructionNodes[:0],
 		relativeJumpNodes:   a.relativeJumpNodes[:0],
 		BaseAssemblerImpl: asm.BaseAssemblerImpl{
@@ -280,8 +295,8 @@ func (a *AssemblerImpl) Reset() {
 			JumpTableEntries:           a.JumpTableEntries[:0],
 		},
 	}
-	a.nodePool.pos, a.nodePool.page = 0, 0
 	a.buf.Reset()
+	a.nodePool.reset()
 }
 
 // newNode creates a new Node and appends it into the linked list.


Test patch:
diff --git a/internal/asm/amd64/impl_1_test.go b/internal/asm/amd64/impl_1_test.go
index 7fcc7146..23dc8c24 100644
--- a/internal/asm/amd64/impl_1_test.go
+++ b/internal/asm/amd64/impl_1_test.go
@@ -10,50 +10,26 @@ import (
 )
 
 func TestNodePool_allocNode(t *testing.T) {
-	np := nodePool{pages: [][nodePoolPageSize]nodeImpl{{}}}
+	np := nodePool{index: nodePageSize}
 
-	for i := 0; i < nodePoolPageSize; i++ {
-		require.Equal(t, i, np.pos)
-		require.Equal(t, 0, np.page)
+	for i := 0; i < nodePageSize; i++ {
 		n := np.allocNode()
 		require.Equal(t, &np.pages[0][i], n)
+		require.Equal(t, i+1, np.index)
+		require.Equal(t, 1, len(np.pages))
 	}
-	require.Equal(t, nodePoolPageSize, np.pos)
+	require.Equal(t, nodePageSize, np.index)
 
 	// Reached the next page.
 	secondPageBegin := np.allocNode()
-	require.Equal(t, 1, np.pos)
-	require.Equal(t, 1, np.page)
-	require.Equal(t, &np.pages[0][0], secondPageBegin)
-
-	// Taint the existing content on the page.
-	np.pages[np.page][np.pos] = nodeImpl{
-		offsetInBinary: 10,
-		jumpTarget:     &nodeImpl{},
-		flag:           nodeFlagInitializedForEncoding,
-		next:           &nodeImpl{},
-		staticConst:    asm.NewStaticConst([]byte{1, 2}),
-		readInstructionAddressBeforeTargetInstruction: RET,
-		arg:    1,
-		types:  operandTypesConstToRegister,
-		srcReg: RegBX, dstReg: RegBX,
-		srcConst: 1234, dstConst: 1234,
-		srcMemIndex: RegBX, dstMemIndex: RegBX,
-		srcMemScale: 0xf, dstMemScale: 0xf,
-	}
-
-	ptr := &np.pages[np.page][np.pos]
-
-	// Ensure allocation clears the existing content.
-	n := np.allocNode()
-	require.Equal(t, ptr, n)
-	require.Equal(t, &nodeImpl{}, n)
+	require.Equal(t, 1, np.index)
+	require.Equal(t, 2, len(np.pages))
+	require.Equal(t, &np.pages[1][0], secondPageBegin)
 }
 
 func TestAssemblerImpl_Reset(t *testing.T) {
 	// Existing values.
 	buf := bytes.NewBuffer(make([]byte, 5, 100))
-	n := &nodePool{pages: [][nodePoolPageSize]nodeImpl{{}, {}}, page: 1, pos: 12}
 	staticConsts := asm.NewStaticConstPool()
 	staticConsts.AddConst(asm.NewStaticConst(nil), 1234)
 	readInstructionAddressNodes := make([]*nodeImpl, 5)
@@ -64,8 +40,11 @@ func TestAssemblerImpl_Reset(t *testing.T) {
 
 	// Create assembler and reset.
 	a := &AssemblerImpl{
+		nodePool: nodePool{
+			pages: []*nodePage{new(nodePage), new(nodePage)},
+			index: 12,
+		},
 		buf:                         buf,
-		nodePool:                    n,
 		pool:                        staticConsts,
 		readInstructionAddressNodes: readInstructionAddressNodes,
 		BaseAssemblerImpl:           ba,
@@ -77,9 +56,8 @@ func TestAssemblerImpl_Reset(t *testing.T) {
 	require.Equal(t, 100, buf.Cap())
 	require.Equal(t, 0, buf.Len())
 
-	require.Equal(t, n, a.nodePool)
-	require.Zero(t, a.nodePool.pos)
-	require.Zero(t, a.nodePool.page)
+	require.Zero(t, len(a.nodePool.pages))
+	require.Equal(t, nodePageSize, a.nodePool.index)
 
 	require.NotEqual(t, staticConsts, a.pool)
 
diff --git a/internal/asm/arm64/impl_1_test.go b/internal/asm/arm64/impl_1_test.go
index 6ea75adb..68cf8034 100644
--- a/internal/asm/arm64/impl_1_test.go
+++ b/internal/asm/arm64/impl_1_test.go
@@ -10,49 +10,26 @@ import (
 )
 
 func TestNodePool_allocNode(t *testing.T) {
-	np := nodePool{pages: [][nodePoolPageSize]nodeImpl{{}}}
+	np := nodePool{index: nodePageSize}
 
-	for i := 0; i < nodePoolPageSize; i++ {
-		require.Equal(t, i, np.pos)
-		require.Equal(t, 0, np.page)
+	for i := 0; i < nodePageSize; i++ {
 		n := np.allocNode()
 		require.Equal(t, &np.pages[0][i], n)
+		require.Equal(t, i+1, np.index)
+		require.Equal(t, 1, len(np.pages))
 	}
-	require.Equal(t, nodePoolPageSize, np.pos)
+	require.Equal(t, nodePageSize, np.index)
 
 	// Reached the next page.
 	secondPageBegin := np.allocNode()
-	require.Equal(t, 1, np.pos)
-	require.Equal(t, 1, np.page)
-	require.Equal(t, &np.pages[0][0], secondPageBegin)
-
-	// Taint the existing content on the page.
-	np.pages[np.page][np.pos] = nodeImpl{
-		offsetInBinaryField: 1234,
-		staticConst:         asm.NewStaticConst([]byte{1, 2}),
-		jumpTarget:          &nodeImpl{},
-		next:                &nodeImpl{},
-		srcReg:              RegR15, srcReg2: RegR15, dstReg: RegR15, dstReg2: RegR15,
-		types:             operandTypesConstToRegister,
-		vectorArrangement: VectorArrangement2S,
-		srcVectorIndex:    123,
-		dstVectorIndex:    53,
-		srcConst:          1234, dstConst: 1234,
-		readInstructionAddressBeforeTargetInstruction: RET,
-	}
-
-	ptr := &np.pages[np.page][np.pos]
-
-	// Ensure allocation clears the existing content.
-	n := np.allocNode()
-	require.Equal(t, ptr, n)
-	require.Equal(t, &nodeImpl{}, n)
+	require.Equal(t, 1, np.index)
+	require.Equal(t, 2, len(np.pages))
+	require.Equal(t, &np.pages[1][0], secondPageBegin)
 }
 
 func TestAssemblerImpl_Reset(t *testing.T) {
 	// Existing values.
 	buf := bytes.NewBuffer(make([]byte, 5, 100))
-	n := &nodePool{pages: [][nodePoolPageSize]nodeImpl{{}, {}}, page: 1, pos: 12}
 	staticConsts := asm.NewStaticConstPool()
 	staticConsts.AddConst(asm.NewStaticConst(nil), 1234)
 	adrInstructionNodes := make([]*nodeImpl, 5)
@@ -64,8 +41,11 @@ func TestAssemblerImpl_Reset(t *testing.T) {
 
 	// Create assembler and reset.
 	a := &AssemblerImpl{
+		nodePool: nodePool{
+			pages: []*nodePage{new(nodePage), new(nodePage)},
+			index: 12,
+		},
 		buf:                 buf,
-		nodePool:            n,
 		pool:                staticConsts,
 		temporaryRegister:   RegV2,
 		relativeJumpNodes:   relativeJumpNodes,
@@ -79,9 +59,8 @@ func TestAssemblerImpl_Reset(t *testing.T) {
 	require.Equal(t, 100, buf.Cap())
 	require.Equal(t, 0, buf.Len())
 
-	require.Equal(t, n, a.nodePool)
-	require.Zero(t, a.nodePool.pos)
-	require.Zero(t, a.nodePool.page)
+	require.Zero(t, len(a.nodePool.pages))
+	require.Equal(t, nodePageSize, a.nodePool.index)
 
 	require.NotEqual(t, staticConsts, a.pool)