Repository: tetratelabs/wazero
Pull request #1328 (2023-03-31 06:41:49)

Reuses allocated slices in func validation 
```
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      134.4µ ± 1%   120.5µ ± 1%  -10.36% (p=0.001 n=7)
Compilation/without_extern_cache-10   2.058m ± 1%   2.027m ± 1%   -1.50% (p=0.001 n=7)
geomean                               526.0µ        494.3µ        -6.04%

                                    │   old.txt    │               new.txt               │
                                    │     B/op     │     B/op      vs base               │
Compilation/with_extern_cache-10      53.38Ki ± 0%   40.80Ki ± 0%  -23.57% (p=0.001 n=7)
Compilation/without_extern_cache-10   1.082Mi ± 0%   1.070Mi ± 0%   -1.13% (p=0.001 n=7)
geomean                               243.2Ki        211.4Ki       -13.07%

                                    │   old.txt   │              new.txt               │
                                    │  allocs/op  │  allocs/op   vs base               │
Compilation/with_extern_cache-10       979.0 ± 0%    632.0 ± 0%  -35.44% (p=0.001 n=7)
Compilation/without_extern_cache-10   7.956k ± 0%   7.609k ± 0%   -4.36% (p=0.001 n=7)
geomean                               2.791k        2.193k       -21.43%
```


Patch:
diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go
index 181fce05..5cabcb60 100644
--- a/internal/wasm/func_validation.go
+++ b/internal/wasm/func_validation.go
@@ -26,10 +26,10 @@ const maximumValuesOnStack = 1 << 27
 //
 // Returns an error if the instruction sequence is not valid,
 // or potentially it can exceed the maximum number of values on the stack.
-func (m *Module) validateFunction(enabledFeatures api.CoreFeatures, idx Index, functions []Index,
+func (m *Module) validateFunction(sts *stacks, enabledFeatures api.CoreFeatures, idx Index, functions []Index,
 	globals []GlobalType, memory *Memory, tables []Table, declaredFunctionIndexes map[Index]struct{},
 ) error {
-	return m.validateFunctionWithMaxStackValues(enabledFeatures, idx, functions, globals, memory, tables, maximumValuesOnStack, declaredFunctionIndexes)
+	return m.validateFunctionWithMaxStackValues(sts, enabledFeatures, idx, functions, globals, memory, tables, maximumValuesOnStack, declaredFunctionIndexes)
 }
 
 func readMemArg(pc uint64, body []byte) (align, offset uint32, read uint64, err error) {
@@ -52,8 +52,10 @@ func readMemArg(pc uint64, body []byte) (align, offset uint32, read uint64, err
 
 // validateFunctionWithMaxStackValues is like validateFunction, but allows overriding maxStackValues for testing.
 //
+// * stacks is to track the state of Wasm value and control frame stacks at anypoint of execution, and reused to reduce allocation.
 // * maxStackValues is the maximum height of values stack which the target is allowed to reach.
 func (m *Module) validateFunctionWithMaxStackValues(
+	sts *stacks,
 	enabledFeatures api.CoreFeatures,
 	idx Index,
 	functions []Index,
@@ -68,10 +70,10 @@ func (m *Module) validateFunctionWithMaxStackValues(
 	body := code.Body
 	localTypes := code.LocalTypes
 
+	sts.reset(functionType)
+	valueTypeStack := &sts.vs
 	// We start with the outermost control block which is for function return if the code branches into it.
-	controlBlockStack := []*controlBlock{{blockType: functionType}}
-	// Create the valueTypeStack to track the state of Wasm value stacks at anypoint of execution.
-	valueTypeStack := &valueTypeStack{}
+	controlBlockStack := &sts.cs
 
 	// Create bytes.Reader once as it causes allocation, and
 	// we frequently need it (e.g. on every If instruction).
@@ -90,7 +92,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			} else {
 				instName = InstructionName(op)
 			}
-			fmt.Printf("handling %s, stack=%s, blocks: %v\n", instName, valueTypeStack, controlBlockStack)
+			fmt.Printf("handling %s, stack=%s, blocks: %v\n", instName, valueTypeStack.stack, controlBlockStack)
 		}
 
 		if OpcodeI32Load <= op && op <= OpcodeI64Store32 {
@@ -391,12 +393,12 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			index, num, err := leb128.LoadUint32(body[pc:])
 			if err != nil {
 				return fmt.Errorf("read immediate: %v", err)
-			} else if int(index) >= len(controlBlockStack) {
+			} else if int(index) >= len(controlBlockStack.stack) {
 				return fmt.Errorf("invalid %s operation: index out of range", OpcodeBrName)
 			}
 			pc += num - 1
 			// Check type soundness.
-			target := controlBlockStack[len(controlBlockStack)-int(index)-1]
+			target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
 			var targetResultType []ValueType
 			if target.op == OpcodeLoop {
 				targetResultType = target.blockType.Params
@@ -413,17 +415,17 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			index, num, err := leb128.LoadUint32(body[pc:])
 			if err != nil {
 				return fmt.Errorf("read immediate: %v", err)
-			} else if int(index) >= len(controlBlockStack) {
+			} else if int(index) >= len(controlBlockStack.stack) {
 				return fmt.Errorf(
 					"invalid ln param given for %s: index=%d with %d for the current label stack length",
-					OpcodeBrIfName, index, len(controlBlockStack))
+					OpcodeBrIfName, index, len(controlBlockStack.stack))
 			}
 			pc += num - 1
 			if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
 				return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrIfName)
 			}
 			// Check type soundness.
-			target := controlBlockStack[len(controlBlockStack)-int(index)-1]
+			target := &controlBlockStack.stack[len(controlBlockStack.stack)-int(index)-1]
 			var targetResultType []ValueType
 			if target.op == OpcodeLoop {
 				targetResultType = target.blockType.Params
@@ -457,17 +459,17 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			ln, n, err := leb128.DecodeUint32(br)
 			if err != nil {
 				return fmt.Errorf("read immediate: %w", err)
-			} else if int(ln) >= len(controlBlockStack) {
+			} else if int(ln) >= len(controlBlockStack.stack) {
 				return fmt.Errorf(
 					"invalid ln param given for %s: ln=%d with %d for the current label stack length",
-					OpcodeBrTableName, ln, len(controlBlockStack))
+					OpcodeBrTableName, ln, len(controlBlockStack.stack))
 			}
 			pc += n + num - 1
 			// Check type soundness.
 			if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
 				return fmt.Errorf("cannot pop the required operand for %s", OpcodeBrTableName)
 			}
-			lnLabel := controlBlockStack[len(controlBlockStack)-1-int(ln)]
+			lnLabel := &controlBlockStack.stack[len(controlBlockStack.stack)-1-int(ln)]
 			var defaultLabelType []ValueType
 			// Below, we might modify the slice in case of unreachable. Therefore,
 			// we have to copy the content of block result types, otherwise the original
@@ -506,10 +508,10 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			}
 
 			for _, l := range list {
-				if int(l) >= len(controlBlockStack) {
+				if int(l) >= len(controlBlockStack.stack) {
 					return fmt.Errorf("invalid l param given for %s", OpcodeBrTableName)
 				}
-				label := controlBlockStack[len(controlBlockStack)-1-int(l)]
+				label := &controlBlockStack.stack[len(controlBlockStack.stack)-1-int(l)]
 				var tableLabelType []ValueType
 				if label.op != OpcodeLoop {
 					tableLabelType = label.blockType.Results
@@ -1395,11 +1397,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			if err != nil {
 				return fmt.Errorf("read block: %w", err)
 			}
-			controlBlockStack = append(controlBlockStack, &controlBlock{
-				startAt:        pc,
-				blockType:      bt,
-				blockTypeBytes: num,
-			})
+			controlBlockStack.push(pc, 0, 0, bt, num, 0)
 			if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
 				return err
 			}
@@ -1415,12 +1413,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			if err != nil {
 				return fmt.Errorf("read block: %w", err)
 			}
-			controlBlockStack = append(controlBlockStack, &controlBlock{
-				startAt:        pc,
-				blockType:      bt,
-				blockTypeBytes: num,
-				op:             op,
-			})
+			controlBlockStack.push(pc, 0, 0, bt, num, op)
 			if err = valueTypeStack.popParams(op, bt.Params, false); err != nil {
 				return err
 			}
@@ -1436,12 +1429,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			if err != nil {
 				return fmt.Errorf("read block: %w", err)
 			}
-			controlBlockStack = append(controlBlockStack, &controlBlock{
-				startAt:        pc,
-				blockType:      bt,
-				blockTypeBytes: num,
-				op:             op,
-			})
+			controlBlockStack.push(pc, 0, 0, bt, num, op)
 			if err = valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
 				return fmt.Errorf("cannot pop the operand for 'if': %v", err)
 			}
@@ -1455,10 +1443,10 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			valueTypeStack.pushStackLimit(len(bt.Params))
 			pc += num
 		} else if op == OpcodeElse {
-			if len(controlBlockStack) == 0 {
+			if len(controlBlockStack.stack) == 0 {
 				return fmt.Errorf("redundant Else instruction at %#x", pc)
 			}
-			bl := controlBlockStack[len(controlBlockStack)-1]
+			bl := &controlBlockStack.stack[len(controlBlockStack.stack)-1]
 			bl.elseAt = pc
 			// Check the type soundness of the instructions *before* entering this else Op.
 			if err := valueTypeStack.popResults(OpcodeIf, bl.blockType.Results, true); err != nil {
@@ -1471,12 +1459,11 @@ func (m *Module) validateFunctionWithMaxStackValues(
 				valueTypeStack.push(p)
 			}
 		} else if op == OpcodeEnd {
-			if len(controlBlockStack) == 0 {
+			if len(controlBlockStack.stack) == 0 {
 				return fmt.Errorf("redundant End instruction at %#x", pc)
 			}
-			bl := controlBlockStack[len(controlBlockStack)-1]
+			bl := controlBlockStack.pop()
 			bl.endAt = pc
-			controlBlockStack = controlBlockStack[:len(controlBlockStack)-1]
 
 			// OpcodeEnd can end a block or the function itself. Check to see what it is:
 
@@ -1573,7 +1560,7 @@ func (m *Module) validateFunctionWithMaxStackValues(
 		}
 	}
 
-	if len(controlBlockStack) > 0 {
+	if len(controlBlockStack.stack) > 0 {
 		return fmt.Errorf("ill-nested block exists")
 	}
 	if valueTypeStack.maximumStackPointer > maxStackValues {
@@ -1637,16 +1624,50 @@ var vecSplatValueTypes = [...]ValueType{
 	OpcodeVecF64x2Splat: ValueTypeF64,
 }
 
+type stacks struct {
+	vs valueTypeStack
+	cs controlBlockStack
+}
+
+func (sts *stacks) reset(functionType *FunctionType) {
+	// Reset valueStack for reuse.
+	sts.vs.stack = sts.vs.stack[:0]
+	sts.vs.stackLimits = sts.vs.stackLimits[:0]
+	sts.vs.maximumStackPointer = 0
+	sts.cs.stack = sts.cs.stack[:0]
+	sts.cs.stack = append(sts.cs.stack, controlBlock{blockType: functionType})
+}
+
+type controlBlockStack struct {
+	stack []controlBlock
+}
+
+func (s *controlBlockStack) pop() *controlBlock {
+	tail := len(s.stack) - 1
+	ret := &s.stack[tail]
+	s.stack = s.stack[:tail]
+	return ret
+}
+
+func (s *controlBlockStack) push(startAt, elseAt, endAt uint64, blockType *FunctionType, blockTypeBytes uint64, op Opcode) {
+	s.stack = append(s.stack, controlBlock{
+		startAt:        startAt,
+		elseAt:         elseAt,
+		endAt:          endAt,
+		blockType:      blockType,
+		blockTypeBytes: blockTypeBytes,
+		op:             op,
+	})
+}
+
 type valueTypeStack struct {
 	stack               []ValueType
 	stackLimits         []int
 	maximumStackPointer int
 }
 
-const (
-	// Only used in the analyzeFunction below.
-	valueTypeUnknown = ValueType(0xFF)
-)
+// Only used in the analyzeFunction below.
+const valueTypeUnknown = ValueType(0xFF)
 
 func (s *valueTypeStack) tryPop() (vt ValueType, limit int, ok bool) {
 	if len(s.stackLimits) > 0 {
diff --git a/internal/wasm/module.go b/internal/wasm/module.go
index 427ebe34..54b68606 100644
--- a/internal/wasm/module.go
+++ b/internal/wasm/module.go
@@ -326,6 +326,7 @@ func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions [
 		return err
 	}
 
+	vs := &stacks{}
 	for idx, typeIndex := range m.FunctionSection {
 		if typeIndex >= typeCount {
 			return fmt.Errorf("invalid %s: type section index %d out of range", m.funcDesc(SectionIDFunction, Index(idx)), typeIndex)
@@ -334,7 +335,7 @@ func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions [
 		if c.GoFunc != nil {
 			continue
 		}
-		if err = m.validateFunction(enabledFeatures, Index(idx), functions, globals, memory, tables, declaredFuncIndexes); err != nil {
+		if err = m.validateFunction(vs, enabledFeatures, Index(idx), functions, globals, memory, tables, declaredFuncIndexes); err != nil {
 			return fmt.Errorf("invalid %s: %w", m.funcDesc(SectionIDFunction, Index(idx)), err)
 		}
 	}


Test patch:
diff --git a/internal/wasm/func_validation_test.go b/internal/wasm/func_validation_test.go
index 36a3f084..5e3898c4 100644
--- a/internal/wasm/func_validation_test.go
+++ b/internal/wasm/func_validation_test.go
@@ -35,11 +35,11 @@ func TestModule_ValidateFunction_validateFunctionWithMaxStackValues(t *testing.T
 	}
 
 	t.Run("not exceed", func(t *testing.T) {
-		err := m.validateFunctionWithMaxStackValues(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max+1, nil)
+		err := m.validateFunctionWithMaxStackValues(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max+1, nil)
 		require.NoError(t, err)
 	})
 	t.Run("exceed", func(t *testing.T) {
-		err := m.validateFunctionWithMaxStackValues(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max, nil)
+		err := m.validateFunctionWithMaxStackValues(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, max, nil)
 		require.Error(t, err)
 		expMsg := fmt.Sprintf("function may have %d stack values, which exceeds limit %d", valuesNum, max)
 		require.Equal(t, expMsg, err.Error())
@@ -82,7 +82,7 @@ func TestModule_ValidateFunction_SignExtensionOps(t *testing.T) {
 					FunctionSection: []Index{0},
 					CodeSection:     []Code{{Body: []byte{tc.input}}},
 				}
-				err := m.validateFunction(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
+				err := m.validateFunction(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
 				require.EqualError(t, err, tc.expectedErrOnDisable)
 			})
 			t.Run("enabled", func(t *testing.T) {
@@ -99,7 +99,7 @@ func TestModule_ValidateFunction_SignExtensionOps(t *testing.T) {
 					FunctionSection: []Index{0},
 					CodeSection:     []Code{{Body: body}},
 				}
-				err := m.validateFunction(api.CoreFeatureSignExtensionOps, 0, []Index{0}, nil, nil, nil, nil)
+				err := m.validateFunction(&stacks{}, api.CoreFeatureSignExtensionOps, 0, []Index{0}, nil, nil, nil, nil)
 				require.NoError(t, err)
 			})
 		})
@@ -154,7 +154,7 @@ func TestModule_ValidateFunction_NonTrappingFloatToIntConversion(t *testing.T) {
 					FunctionSection: []Index{0},
 					CodeSection:     []Code{{Body: []byte{OpcodeMiscPrefix, tc.input}}},
 				}
-				err := m.validateFunction(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
+				err := m.validateFunction(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
 				require.EqualError(t, err, tc.expectedErrOnDisable)
 			})
 			t.Run("enabled", func(t *testing.T) {
@@ -172,7 +172,7 @@ func TestModule_ValidateFunction_NonTrappingFloatToIntConversion(t *testing.T) {
 					FunctionSection: []Index{0},
 					CodeSection:     []Code{{Body: body}},
 				}
-				err := m.validateFunction(api.CoreFeatureNonTrappingFloatToIntConversion, 0, []Index{0}, nil, nil, nil, nil)
+				err := m.validateFunction(&stacks{}, api.CoreFeatureNonTrappingFloatToIntConversion, 0, []Index{0}, nil, nil, nil, nil)
 				require.NoError(t, err)
 			})
 		})
@@ -249,11 +249,11 @@ func TestModule_ValidateFunction_MultiValue(t *testing.T) {
 		tc := tt
 		t.Run(tc.name, func(t *testing.T) {
 			t.Run("disabled", func(t *testing.T) {
-				err := tc.module.validateFunction(api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
+				err := tc.module.validateFunction(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, nil, nil, nil)
 				require.EqualError(t, err, tc.expectedErrOnDisable)
 			})
 			t.Run("enabled", func(t *testing.T) {
-				err := tc.module.validateFunction(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
+				err := tc.module.validateFunction(&stacks{}, api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
 				require.NoError(t, err)
 			})
 		})
@@ -290,7 +290,7 @@ func TestModule_ValidateFunction_BulkMemoryOperations(t *testing.T) {
 					ElementSection:   []ElementSegment{{}},
 					DataCountSection: &c,
 				}
-				err := m.validateFunction(api.CoreFeatureBulkMemoryOperations, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil)
+				err := m.validateFunction(&stacks{}, api.CoreFeatureBulkMemoryOperations, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil)
 				require.NoError(t, err)
 			})
 		}
@@ -654,7 +654,7 @@ func TestModule_ValidateFunction_BulkMemoryOperations(t *testing.T) {
 					c := uint32(0)
 					m.DataCountSection = &c
 				}
-				err := m.validateFunction(tc.flag, 0, []Index{0}, nil, tc.memory, tc.tables, nil)
+				err := m.validateFunction(&stacks{}, tc.flag, 0, []Index{0}, nil, tc.memory, tc.tables, nil)
 				require.EqualError(t, err, tc.expectedErr)
 			})
 		}
@@ -679,7 +679,7 @@ var (
 	v_i64i64                      = FunctionType{Results: []ValueType{i64, i64}}
 )
 
-// TestModule_ValidateFunction_TypeMismatchSpecTests are "type mismatch" tests when "multi-value" was merged.
+// TestModule_ValidateFunction_MultiValue_TypeMismatch are "type mismatch" tests when "multi-value" was merged.
 //
 // See https://github.com/WebAssembly/spec/commit/484180ba3d9d7638ba1cb400b699ffede796927c
 func TestModule_ValidateFunction_MultiValue_TypeMismatch(t *testing.T) {
@@ -2182,7 +2182,7 @@ func TestModule_ValidateFunction_MultiValue_TypeMismatch(t *testing.T) {
 		tc := tt
 
 		t.Run(tc.name, func(t *testing.T) {
-			err := tc.module.validateFunction(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
+			err := tc.module.validateFunction(&stacks{}, api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
 			require.EqualError(t, err, tc.expectedErr)
 		})
 	}
@@ -2199,7 +2199,7 @@ func TestModule_funcValidation_CallIndirect(t *testing.T) {
 				OpcodeEnd,
 			}}},
 		}
-		err := m.validateFunction(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeFuncref}}, nil)
+		err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeFuncref}}, nil)
 		require.NoError(t, err)
 	})
 	t.Run("non zero table index", func(t *testing.T) {
@@ -2213,11 +2213,11 @@ func TestModule_funcValidation_CallIndirect(t *testing.T) {
 			}}},
 		}
 		t.Run("disabled", func(t *testing.T) {
-			err := m.validateFunction(api.CoreFeaturesV1, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil)
+			err := m.validateFunction(&stacks{}, api.CoreFeaturesV1, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil)
 			require.EqualError(t, err, "table index must be zero but was 100: feature \"reference-types\" is disabled")
 		})
 		t.Run("enabled but out of range", func(t *testing.T) {
-			err := m.validateFunction(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil)
+			err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{}, {}}, nil)
 			require.EqualError(t, err, "unknown table index: 100")
 		})
 	})
@@ -2231,7 +2231,7 @@ func TestModule_funcValidation_CallIndirect(t *testing.T) {
 				OpcodeEnd,
 			}}},
 		}
-		err := m.validateFunction(api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeExternref}}, nil)
+		err := m.validateFunction(&stacks{}, api.CoreFeatureReferenceTypes, 0, []Index{0}, nil, &Memory{}, []Table{{Type: RefTypeExternref}}, nil)
 		require.EqualError(t, err, "table is not funcref type but was externref for call_indirect")
 	})
 }
@@ -2326,7 +2326,7 @@ func TestModule_funcValidation_RefTypes(t *testing.T) {
 				FunctionSection: []Index{0},
 				CodeSection:     []Code{{Body: tc.body}},
 			}
-			err := m.validateFunction(tc.flag, 0, []Index{0}, nil, nil, nil, tc.declaredFunctionIndexes)
+			err := m.validateFunction(&stacks{}, tc.flag, 0, []Index{0}, nil, nil, nil, tc.declaredFunctionIndexes)
 			if tc.expectedErr != "" {
 				require.EqualError(t, err, tc.expectedErr)
 			} else {
@@ -2494,7 +2494,7 @@ func TestModule_funcValidation_TableGrowSizeFill(t *testing.T) {
 				FunctionSection: []Index{0},
 				CodeSection:     []Code{{Body: tc.body}},
 			}
-			err := m.validateFunction(tc.flag, 0, []Index{0}, nil, nil, tables, nil)
+			err := m.validateFunction(&stacks{}, tc.flag, 0, []Index{0}, nil, nil, tables, nil)
 			if tc.expectedErr != "" {
 				require.EqualError(t, err, tc.expectedErr)
 			} else {
@@ -2606,7 +2606,7 @@ func TestModule_funcValidation_TableGetSet(t *testing.T) {
 				FunctionSection: []Index{0},
 				CodeSection:     []Code{{Body: tc.body}},
 			}
-			err := m.validateFunction(tc.flag, 0, []Index{0}, nil, nil, tables, nil)
+			err := m.validateFunction(&stacks{}, tc.flag, 0, []Index{0}, nil, nil, tables, nil)
 			if tc.expectedErr != "" {
 				require.EqualError(t, err, tc.expectedErr)
 			} else {
@@ -2663,7 +2663,7 @@ func TestModule_funcValidation_Select_error(t *testing.T) {
 				FunctionSection: []Index{0},
 				CodeSection:     []Code{{Body: tc.body}},
 			}
-			err := m.validateFunction(tc.flag, 0, []Index{0}, nil, nil, nil, nil)
+			err := m.validateFunction(&stacks{}, tc.flag, 0, []Index{0}, nil, nil, nil, nil)
 			require.EqualError(t, err, tc.expectedErr)
 		})
 	}
@@ -3148,7 +3148,7 @@ func TestModule_funcValidation_SIMD(t *testing.T) {
 				FunctionSection: []Index{0},
 				CodeSection:     []Code{{Body: tc.body}},
 			}
-			err := m.validateFunction(api.CoreFeatureSIMD, 0, []Index{0}, nil, &Memory{}, nil, nil)
+			err := m.validateFunction(&stacks{}, api.CoreFeatureSIMD, 0, []Index{0}, nil, &Memory{}, nil, nil)
 			require.NoError(t, err)
 		})
 	}
@@ -3293,7 +3293,7 @@ func TestModule_funcValidation_SIMD_error(t *testing.T) {
 				FunctionSection: []Index{0},
 				CodeSection:     []Code{{Body: tc.body}},
 			}
-			err := m.validateFunction(tc.flag, 0, []Index{0}, nil, &Memory{}, nil, nil)
+			err := m.validateFunction(&stacks{}, tc.flag, 0, []Index{0}, nil, &Memory{}, nil, nil)
 			require.EqualError(t, err, tc.expectedErr)
 		})
 	}
@@ -3410,7 +3410,7 @@ func TestFuncValidation_UnreachableBrTable_NotModifyTypes(t *testing.T) {
 	} {
 		tc := tc
 		t.Run(tc.name, func(t *testing.T) {
-			err := tc.m.validateFunction(api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil)
+			err := tc.m.validateFunction(&stacks{}, api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil)
 			require.NoError(t, err)
 
 			// Ensures that funcType has remained intact.
@@ -3534,7 +3534,7 @@ func TestModule_funcValidation_loopWithParams(t *testing.T) {
 				FunctionSection: []Index{0},
 				CodeSection:     []Code{{Body: tc.body}},
 			}
-			err := m.validateFunction(api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
+			err := m.validateFunction(&stacks{}, api.CoreFeatureMultiValue, 0, []Index{0}, nil, nil, nil, nil)
 			if tc.expErr != "" {
 				require.EqualError(t, err, tc.expErr)
 			} else {
@@ -3551,7 +3551,7 @@ func TestFunctionValidation_redundantEnd(t *testing.T) {
 		FunctionSection: []Index{0},
 		CodeSection:     []Code{{Body: []byte{OpcodeEnd, OpcodeEnd}}},
 	}
-	err := m.validateFunction(api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil)
+	err := m.validateFunction(&stacks{}, api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil)
 	require.EqualError(t, err, "redundant End instruction at 0x1")
 }
 
@@ -3562,6 +3562,6 @@ func TestFunctionValidation_redundantElse(t *testing.T) {
 		FunctionSection: []Index{0},
 		CodeSection:     []Code{{Body: []byte{OpcodeEnd, OpcodeElse}}},
 	}
-	err := m.validateFunction(api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil)
+	err := m.validateFunction(&stacks{}, api.CoreFeaturesV2, 0, nil, nil, nil, nil, nil)
 	require.EqualError(t, err, "redundant Else instruction at 0x1")
 }