Repository: tetratelabs/wazero
Pull request #1345 (2023-04-07 02:56:32)

wazeroir: avoids allocation with InclusiveRange
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/bench
                                   │  main.txt   │              new.txt               │
                                   │   sec/op    │   sec/op     vs base               │
Compilation_sqlite3/compiler-10      232.0m ± 0%   182.5m ± 2%  -21.34% (p=0.001 n=7)
Compilation_sqlite3/interpreter-10   68.86m ± 1%   49.32m ± 0%  -28.37% (p=0.001 n=7)
geomean                              126.4m        94.87m       -24.94%

                                   │   main.txt   │               new.txt               │
                                   │     B/op     │     B/op      vs base               │
Compilation_sqlite3/compiler-10      117.9Mi ± 0%   114.1Mi ± 0%   -3.23% (p=0.001 n=7)
Compilation_sqlite3/interpreter-10   67.40Mi ± 0%   51.84Mi ± 0%  -23.08% (p=0.001 n=7)
geomean                              89.15Mi        76.92Mi       -13.72%

                                   │   main.txt   │              new.txt               │
                                   │  allocs/op   │  allocs/op   vs base               │
Compilation_sqlite3/compiler-10       253.7k ± 0%   168.5k ± 0%  -33.56% (p=0.001 n=7)
Compilation_sqlite3/interpreter-10   107.42k ± 0%   22.29k ± 0%  -79.25% (p=0.001 n=7)
geomean                               165.1k        61.29k       -62.87%

```


Patch:
diff --git a/internal/engine/compiler/compiler_drop.go b/internal/engine/compiler/compiler_drop.go
index 24853e30..342cee0e 100644
--- a/internal/engine/compiler/compiler_drop.go
+++ b/internal/engine/compiler/compiler_drop.go
@@ -7,12 +7,13 @@ import (
 
 // compileDropRange adds instruction to drop the values on the target range
 // in an architecture independent way.
-func compileDropRange(c compiler, r *wazeroir.InclusiveRange) (err error) {
+func compileDropRange(c compiler, raw uint64) (err error) {
+	r := wazeroir.InclusiveRangeFromU64(raw)
 	locationStack := c.runtimeValueLocationStack()
-	if r == nil {
+	if r.Start < 0 {
 		return
 	} else if r.Start == 0 {
-		for i := 0; i <= r.End; i++ {
+		for i := 0; i <= int(r.End); i++ {
 			if loc := locationStack.pop(); loc.onRegister() {
 				locationStack.releaseRegister(loc)
 			}
@@ -115,7 +116,7 @@ func getTemporariesForStackedLiveValues(c compiler, liveValues []runtimeValueLoc
 
 // dropsLivesForInclusiveRange returns the live and drop target values for the given wazeroir.InclusiveRange.
 func (v *runtimeValueLocationStack) dropsLivesForInclusiveRange(
-	r *wazeroir.InclusiveRange,
+	r wazeroir.InclusiveRange,
 ) (dropValues, liveValues []runtimeValueLocation) {
 	// liveValues are must be pushed backed after dropping the target range.
 	liveValues = v.stack[v.sp-uint64(r.Start) : v.sp]
diff --git a/internal/engine/compiler/impl_amd64.go b/internal/engine/compiler/impl_amd64.go
index 77c36b2f..60640caa 100644
--- a/internal/engine/compiler/impl_amd64.go
+++ b/internal/engine/compiler/impl_amd64.go
@@ -511,8 +511,8 @@ func (c *amd64Compiler) compileBrIf(o *wazeroir.UnionOperation) error {
 
 	// Make sure that the next coming label is the else jump target.
 	thenTarget := wazeroir.Label(o.U1)
-	thenToDrop := o.Rs[0]
 	elseTarget := wazeroir.Label(o.U2)
+	thenToDrop := o.U3
 
 	// Here's the diagram of how we organize the instructions necessarily for brif operation.
 	//
@@ -587,13 +587,9 @@ func (c *amd64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 	index := c.locationStack.pop()
 
 	// If the operation only consists of the default target, we branch into it and return early.
-	if len(o.Us) == 1 {
+	if len(o.Us) == 2 {
 		c.locationStack.releaseRegister(index)
-		var r *wazeroir.InclusiveRange
-		if len(o.Rs) > 0 {
-			r = o.Rs[0]
-		}
-		if err := compileDropRange(c, r); err != nil {
+		if err := compileDropRange(c, o.Us[1]); err != nil {
 			return err
 		}
 		return c.branchInto(wazeroir.Label(o.Us[0]))
@@ -610,7 +606,7 @@ func (c *amd64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 	}
 
 	// First, we move the length of target list into the tmp register.
-	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(len(o.Us)-1), tmp)
+	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(len(o.Us)/2-1), tmp)
 
 	// Then, we compare the value with the length of targets.
 	c.assembler.CompileRegisterToRegister(amd64.CMPL, tmp, index.register)
@@ -646,7 +642,7 @@ func (c *amd64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 	// the above example's offsetData would be [0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0].
 	//
 	// Note: this is similar to how GCC implements Switch statements in C.
-	offsetData := asm.NewStaticConst(make([]byte, 4*(len(o.Us))))
+	offsetData := asm.NewStaticConst(make([]byte, 4*(len(o.Us)/2)))
 
 	// Load the offsetData's address into tmp.
 	if err = c.assembler.CompileStaticConstToRegister(amd64.LEAQ, offsetData, tmp); err != nil {
@@ -673,7 +669,7 @@ func (c *amd64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 	c.locationStack.markRegisterUnused(index.register)
 
 	// [Emit the code for each targets and default branch]
-	labelInitialInstructions := make([]asm.Node, len(o.Us))
+	labelInitialInstructions := make([]asm.Node, len(o.Us)/2)
 	saved := c.locationStack
 	for i := range labelInitialInstructions {
 		// Emit the initial instruction of each target.
@@ -681,31 +677,21 @@ func (c *amd64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 		// Assembler would optimize out this NOP during code generation, so this is harmless.
 		labelInitialInstructions[i] = c.assembler.CompileStandAlone(amd64.NOP)
 
-		var locationStack runtimeValueLocationStack
-		var targetToDrop *wazeroir.InclusiveRange
-		var targetLabel wazeroir.Label
-		if i < len(o.Us)-1 {
-			targetLabel = wazeroir.Label(o.Us[i+1])
-			if len(o.Rs) > i+1 {
-				targetToDrop = o.Rs[i+1]
-			}
+		targetLabel := wazeroir.Label(o.Us[i*2])
+		targetToDrop := o.Us[i*2+1]
+		if i < len(labelInitialInstructions)-1 {
 			// Clone the location stack so the branch-specific code doesn't
 			// affect others.
-			locationStack = saved.clone()
+			c.setLocationStack(saved.clone())
 		} else {
-			targetLabel = wazeroir.Label(o.Us[0])
-			if len(o.Rs) > 0 {
-				targetToDrop = o.Rs[0]
-			}
 			// If this is the default branch, we use the original one
 			// as this is the last code in this block.
-			locationStack = saved
+			c.setLocationStack(saved)
 		}
-		c.setLocationStack(locationStack)
-		if err := compileDropRange(c, targetToDrop); err != nil {
+		if err = compileDropRange(c, targetToDrop); err != nil {
 			return err
 		}
-		if err := c.branchInto(targetLabel); err != nil {
+		if err = c.branchInto(targetLabel); err != nil {
 			return err
 		}
 	}
@@ -868,8 +854,7 @@ func (c *amd64Compiler) compileCallIndirect(o *wazeroir.UnionOperation) error {
 
 // compileDrop implements compiler.compileDrop for the amd64 architecture.
 func (c *amd64Compiler) compileDrop(o *wazeroir.UnionOperation) error {
-	depth := o.Rs[0]
-	return compileDropRange(c, depth)
+	return compileDropRange(c, o.U1)
 }
 
 // compileSelectV128Impl implements compileSelect for vector values.
@@ -1958,7 +1943,7 @@ func (c *amd64Compiler) compileMinOrMax(is32Bit, isMin bool, minOrMaxInstruction
 
 	sameExitJmp := c.assembler.CompileJump(amd64.JMP)
 
-	// Start handling 3).
+	// start handling 3).
 	c.assembler.SetJumpTargetOnNext(includeNaNJmp)
 
 	// We emit the ADD instruction to produce the NaN in x1.
diff --git a/internal/engine/compiler/impl_arm64.go b/internal/engine/compiler/impl_arm64.go
index 24c2d34a..a89e6865 100644
--- a/internal/engine/compiler/impl_arm64.go
+++ b/internal/engine/compiler/impl_arm64.go
@@ -757,11 +757,7 @@ func (c *arm64Compiler) compileBrIf(o *wazeroir.UnionOperation) error {
 	// and we have to avoid affecting the code generation for Then branch afterwards.
 	saved := c.locationStack
 	c.setLocationStack(saved.clone())
-	elseToDrop := o.Rs[1]
 	elseTarget := wazeroir.Label(o.U2)
-	if err := compileDropRange(c, elseToDrop); err != nil {
-		return err
-	}
 	if err := c.compileBranchInto(elseTarget); err != nil {
 		return err
 	}
@@ -771,9 +767,8 @@ func (c *arm64Compiler) compileBrIf(o *wazeroir.UnionOperation) error {
 	c.setLocationStack(saved)
 	// We branch into here from the original conditional BR (conditionalBR).
 	c.assembler.SetJumpTargetOnNext(conditionalBR)
-	thenToDrop := o.Rs[0]
 	thenTarget := wazeroir.Label(o.U1)
-	if err := compileDropRange(c, thenToDrop); err != nil {
+	if err := compileDropRange(c, o.U3); err != nil {
 		return err
 	}
 	return c.compileBranchInto(thenTarget)
@@ -822,16 +817,12 @@ func (c *arm64Compiler) assignBranchTarget(label wazeroir.Label, br asm.Node) {
 // compileBrTable implements compiler.compileBrTable for the arm64 architecture.
 func (c *arm64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 	// If the operation only consists of the default target, we branch into it and return early.
-	if len(o.Us) == 1 {
+	if len(o.Us) == 2 {
 		loc := c.locationStack.pop()
 		if loc.onRegister() {
 			c.markRegisterUnused(loc.register)
 		}
-		var r *wazeroir.InclusiveRange
-		if len(o.Rs) > 0 {
-			r = o.Rs[0]
-		}
-		if err := compileDropRange(c, r); err != nil {
+		if err := compileDropRange(c, o.Us[1]); err != nil {
 			return err
 		}
 		return c.compileBranchInto(wazeroir.Label(o.Us[0]))
@@ -861,7 +852,7 @@ func (c *arm64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 
 	// Load the branch table's length.
 	// "tmpReg = len(o.Targets)"
-	c.assembler.CompileConstToRegister(arm64.MOVW, int64(len(o.Us)-1), tmpReg)
+	c.assembler.CompileConstToRegister(arm64.MOVW, int64(len(o.Us)/2-1), tmpReg)
 	// Compare the length with offset.
 	c.assembler.CompileTwoRegistersToNone(arm64.CMPW, tmpReg, index.register)
 	// If the value exceeds the length, we will branch into the default target (corresponding to len(o.Targets) index).
@@ -887,14 +878,13 @@ func (c *arm64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 	//
 	// then offsetData becomes like [0x0, 0x5, 0x8].
 	// By using this offset list, we could jump into the label for the index by
-	// "jmp offsetData[index]+0x123001" and "0x123001" can be acquired by "LEA"
-	// instruction.
+	// "jmp offsetData[index]+0x123001" and "0x123001" can be acquired by ADR instruction.
 	//
 	// Note: We store each offset of 32-bit unsigned integer as 4 consecutive bytes. So more precisely,
 	// the above example's offsetData would be [0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0].
 	//
 	// Note: this is similar to how GCC implements Switch statements in C.
-	offsetData := asm.NewStaticConst(make([]byte, 4*(len(o.Us))))
+	offsetData := asm.NewStaticConst(make([]byte, 4*(len(o.Us)/2)))
 
 	// "tmpReg = &offsetData[0]"
 	c.assembler.CompileStaticConstToRegister(arm64.ADR, offsetData, tmpReg)
@@ -919,7 +909,7 @@ func (c *arm64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 	c.markRegisterUnused(index.register)
 
 	// [Emit the code for each targets and default branch]
-	labelInitialInstructions := make([]asm.Node, len(o.Us))
+	labelInitialInstructions := make([]asm.Node, len(o.Us)/2)
 	saved := c.locationStack
 	for i := range labelInitialInstructions {
 		// Emit the initial instruction of each target where
@@ -928,30 +918,22 @@ func (c *arm64Compiler) compileBrTable(o *wazeroir.UnionOperation) error {
 		labelInitialInstructions[i] = init
 
 		var locationStack runtimeValueLocationStack
-		var targetToDrop *wazeroir.InclusiveRange
-		var targetLabel wazeroir.Label
-		if i < len(o.Us)-1 {
-			targetLabel = wazeroir.Label(o.Us[i+1])
-			if len(o.Rs) > i+1 {
-				targetToDrop = o.Rs[i+1]
-			}
+		targetLabel := wazeroir.Label(o.Us[i*2])
+		targetToDrop := o.Us[i*2+1]
+		if i < len(labelInitialInstructions)-1 {
 			// Clone the location stack so the branch-specific code doesn't
 			// affect others.
 			locationStack = saved.clone()
 		} else {
-			targetLabel = wazeroir.Label(o.Us[0])
-			if len(o.Rs) > 0 {
-				targetToDrop = o.Rs[0]
-			}
 			// If this is the default branch, we use the original one
 			// as this is the last code in this block.
 			locationStack = saved
 		}
 		c.setLocationStack(locationStack)
-		if err := compileDropRange(c, targetToDrop); err != nil {
+		if err = compileDropRange(c, targetToDrop); err != nil {
 			return err
 		}
-		if err := c.compileBranchInto(targetLabel); err != nil {
+		if err = c.compileBranchInto(targetLabel); err != nil {
 			return err
 		}
 	}
@@ -1220,7 +1202,7 @@ func (c *arm64Compiler) compileCallIndirect(o *wazeroir.UnionOperation) (err err
 
 // compileDrop implements compiler.compileDrop for the arm64 architecture.
 func (c *arm64Compiler) compileDrop(o *wazeroir.UnionOperation) error {
-	return compileDropRange(c, o.Rs[0])
+	return compileDropRange(c, o.U1)
 }
 
 func (c *arm64Compiler) compileSelectV128Impl(selectorRegister asm.Register) error {
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index a9e11f2a..9c3732f8 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -132,20 +132,15 @@ func (ce *callEngine) peekValues(count int) []uint64 {
 	return ce.stack[stackLen-count : stackLen]
 }
 
-func (ce *callEngine) drop(r *wazeroir.InclusiveRange) {
-	// No need to check stack bound
-	// as we can assume that all the operations
-	// are valid thanks to validateFunction
-	// at module validation phase
-	// and wazeroir translation
-	// before compilation.
-	if r == nil {
+func (ce *callEngine) drop(raw uint64) {
+	r := wazeroir.InclusiveRangeFromU64(raw)
+	if r.Start == -1 {
 		return
 	} else if r.Start == 0 {
-		ce.stack = ce.stack[:len(ce.stack)-1-r.End]
+		ce.stack = ce.stack[:int32(len(ce.stack))-1-r.End]
 	} else {
-		newStack := ce.stack[:len(ce.stack)-1-r.End]
-		newStack = append(newStack, ce.stack[len(ce.stack)-r.Start:]...)
+		newStack := ce.stack[:int32(len(ce.stack))-1-r.End]
+		newStack = append(newStack, ce.stack[int32(len(ce.stack))-r.Start:]...)
 		ce.stack = newStack
 	}
 }
@@ -318,7 +313,8 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 			e.setLabelAddress(&op.U1, wazeroir.Label(op.U1))
 			e.setLabelAddress(&op.U2, wazeroir.Label(op.U2))
 		case wazeroir.OperationKindBrTable:
-			for j, target := range op.Us {
+			for j := 0; j < len(op.Us); j += 2 {
+				target := op.Us[j]
 				e.setLabelAddress(&op.Us[j], wazeroir.Label(target))
 			}
 		}
@@ -530,25 +526,20 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
 			frame.pc = op.U1
 		case wazeroir.OperationKindBrIf:
 			if ce.popValue() > 0 {
-				ce.drop(op.Rs[0])
+				ce.drop(op.U3)
 				frame.pc = op.U1
 			} else {
-				ce.drop(op.Rs[1])
 				frame.pc = op.U2
 			}
 		case wazeroir.OperationKindBrTable:
-			if v := uint64(ce.popValue()); v < uint64(len(op.Us)-1) {
-				if uint64(len(op.Rs)) > v+1 {
-					ce.drop(op.Rs[v+1])
-				}
-				frame.pc = op.Us[v+1]
-			} else {
-				// Default branch.
-				if len(op.Rs) > 0 {
-					ce.drop(op.Rs[0])
-				}
-				frame.pc = op.Us[0]
+			v := ce.popValue()
+			defaultAt := uint64(len(op.Us))/2 - 1
+			if v > defaultAt {
+				v = defaultAt
 			}
+			v *= 2
+			ce.drop(op.Us[v+1])
+			frame.pc = op.Us[v]
 		case wazeroir.OperationKindCall:
 			ce.callFunction(ctx, f.moduleInstance, &functions[op.U1])
 			frame.pc++
@@ -571,7 +562,7 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
 			ce.callFunction(ctx, f.moduleInstance, tf)
 			frame.pc++
 		case wazeroir.OperationKindDrop:
-			ce.drop(op.Rs[0])
+			ce.drop(op.U1)
 			frame.pc++
 		case wazeroir.OperationKindSelect:
 			c := ce.popValue()
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index 01c889e1..47ddc92b 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -27,7 +27,7 @@ type (
 	controlFrame struct {
 		frameID uint32
 		// originalStackLen holds the number of values on the stack
-		// when start executing this control frame minus params for the block.
+		// when Start executing this control frame minus params for the block.
 		originalStackLenWithoutParam int
 		blockType                    *wasm.FunctionType
 		kind                         controlFrameKind
@@ -38,7 +38,7 @@ type (
 func (c *controlFrame) ensureContinuation() {
 	// Make sure that if the frame is block and doesn't have continuation,
 	// change the Kind so we can emit the continuation block
-	// later when we reach the end instruction of this frame.
+	// later when we reach the End instruction of this frame.
 	if c.kind == controlFrameKindBlockWithoutContinuationLabel {
 		c.kind = controlFrameKindBlockWithContinuationLabel
 	}
@@ -121,7 +121,7 @@ func (c *Compiler) initializeStack() {
 		}
 	}
 
-	// Non-func param locals start after the return call frame.
+	// Non-func param locals Start after the return call frame.
 	current += c.callFrameStackSizeInUint64
 
 	for _, lt := range c.localTypes {
@@ -507,10 +507,7 @@ operatorSwitch:
 		c.result.LabelCallers[elseLabel]++
 
 		// Emit the branch operation to enter the then block.
-		c.emit(NewOperationBrIf(
-			thenLabel.asBranchTargetDrop(),
-			elseLabel.asBranchTargetDrop(),
-		))
+		c.emit(NewOperationBrIf(thenLabel, elseLabel, NopInclusiveRange))
 		c.emit(NewOperationLabel(thenLabel))
 	case wasm.OpcodeElse:
 		frame := c.controlFrames.top()
@@ -690,15 +687,12 @@ operatorSwitch:
 		targetFrame := c.controlFrames.get(int(targetIndex))
 		targetFrame.ensureContinuation()
 		drop := c.getFrameDropRange(targetFrame, false)
-		targetID := targetFrame.asLabel()
-		c.result.LabelCallers[targetID]++
+		target := targetFrame.asLabel()
+		c.result.LabelCallers[target]++
 
 		continuationLabel := NewLabel(LabelKindHeader, c.nextFrameID())
 		c.result.LabelCallers[continuationLabel]++
-		c.emit(NewOperationBrIf(
-			BranchTargetDrop{ToDrop: drop, Target: targetID},
-			continuationLabel.asBranchTargetDrop(),
-		))
+		c.emit(NewOperationBrIf(target, continuationLabel, drop))
 		// Start emitting else block operations.
 		c.emit(NewOperationLabel(continuationLabel))
 	case wasm.OpcodeBrTable:
@@ -725,9 +719,9 @@ operatorSwitch:
 		}
 
 		// Read the branch targets.
-		targetLabels := make([]uint64, numTargets)
-		targetDrops := make([]*InclusiveRange, numTargets)
-		for i := uint32(0); i < numTargets; i++ {
+		s := numTargets * 2
+		targetLabels := make([]uint64, 2+s) // (label, InclusiveRange) * (default+numTargets)
+		for i := uint32(0); i < s; i += 2 {
 			l, n, err := leb128.DecodeUint32(r)
 			if err != nil {
 				return fmt.Errorf("error reading target %d in br_table: %w", i, err)
@@ -738,7 +732,7 @@ operatorSwitch:
 			drop := c.getFrameDropRange(targetFrame, false)
 			targetLabel := targetFrame.asLabel()
 			targetLabels[i] = uint64(targetLabel)
-			targetDrops[i] = drop
+			targetLabels[i+1] = drop.AsU64()
 			c.result.LabelCallers[targetLabel]++
 		}
 
@@ -751,16 +745,13 @@ operatorSwitch:
 		defaultTargetFrame := c.controlFrames.get(int(l))
 		defaultTargetFrame.ensureContinuation()
 		defaultTargetDrop := c.getFrameDropRange(defaultTargetFrame, false)
-		defaultTargetID := defaultTargetFrame.asLabel()
-		c.result.LabelCallers[defaultTargetID]++
+		defaultLabel := defaultTargetFrame.asLabel()
+		c.result.LabelCallers[defaultLabel]++
+		targetLabels[s] = uint64(defaultLabel)
+		targetLabels[s+1] = defaultTargetDrop.AsU64()
+		c.emit(NewOperationBrTable(targetLabels))
 
-		c.emit(
-			NewOperationBrTable(
-				append([]uint64{uint64(defaultTargetID)}, targetLabels...),
-				append([]*InclusiveRange{defaultTargetDrop}, targetDrops...),
-			),
-		)
-		// Br operation is stack-polymorphic, and mark the state as unreachable.
+		// br_table operation is stack-polymorphic, and mark the state as unreachable.
 		// That means subsequent instructions in the current control frame are "unreachable"
 		// and can be safely removed.
 		c.markUnreachable()
@@ -791,15 +782,13 @@ operatorSwitch:
 			NewOperationCallIndirect(typeIndex, tableIndex),
 		)
 	case wasm.OpcodeDrop:
-		r := &InclusiveRange{Start: 0, End: 0}
+		r := InclusiveRange{Start: 0, End: 0}
 		if peekValueType == UnsignedTypeV128 {
 			// InclusiveRange is the range in uint64 representation, so dropping a vector value on top
 			// should be translated as drop [0..1] inclusively.
 			r.End++
 		}
-		c.emit(
-			NewOperationDrop(r),
-		)
+		c.emit(NewOperationDrop(r))
 	case wasm.OpcodeSelect:
 		// If it is on the unreachable state, ignore the instruction.
 		if c.unreachableState.on {
@@ -2973,7 +2962,7 @@ func (c *Compiler) emit(op UnionOperation) {
 			// we could remove such operations.
 			// That happens when drop operation is unnecessary.
 			// i.e. when there's no need to adjust stack before jmp.
-			if op.Rs[0] == nil {
+			if int64(op.U1) == -1 {
 				return
 			}
 		}
@@ -3027,11 +3016,11 @@ func (c *Compiler) localType(index wasm.Index) (t wasm.ValueType) {
 //
 // * frame is the control frame which the call-site is trying to branch into or exit.
 // * isEnd true if the call-site is handling wasm.OpcodeEnd.
-func (c *Compiler) getFrameDropRange(frame *controlFrame, isEnd bool) *InclusiveRange {
+func (c *Compiler) getFrameDropRange(frame *controlFrame, isEnd bool) InclusiveRange {
 	var start int
 	if !isEnd && frame.kind == controlFrameKindLoop {
 		// If this is not End and the call-site is trying to branch into the Loop control frame,
-		// we have to start executing from the beginning of the loop block.
+		// we have to Start executing from the beginning of the loop block.
 		// Therefore, we have to pass the inputs to the frame.
 		start = frame.blockType.ParamNumInUint64
 	} else {
@@ -3046,9 +3035,10 @@ func (c *Compiler) getFrameDropRange(frame *controlFrame, isEnd bool) *Inclusive
 		end = c.stackLenInUint64(len(c.stack)) - 1 - c.stackLenInUint64(frame.originalStackLenWithoutParam)
 	}
 	if start <= end {
-		return &InclusiveRange{Start: start, End: end}
+		return InclusiveRange{Start: int32(start), End: int32(end)}
+	} else {
+		return NopInclusiveRange
 	}
-	return nil
 }
 
 func (c *Compiler) stackLenInUint64(ceil int) (ret int) {
diff --git a/internal/wazeroir/operations.go b/internal/wazeroir/operations.go
index b34c7fe7..4041944a 100644
--- a/internal/wazeroir/operations.go
+++ b/internal/wazeroir/operations.go
@@ -718,7 +718,7 @@ func NewOperationBuiltinFunctionCheckExitCode() UnionOperation {
 }
 
 // Label is the unique identifier for each block in a single function in wazeroir
-// where "block" consists of multiple operations, and must end with branching operations
+// where "block" consists of multiple operations, and must End with branching operations
 // (e.g. OperationKindBr or OperationKindBrIf).
 type Label uint64
 
@@ -779,27 +779,6 @@ const (
 	LabelKindNum
 )
 
-func (l Label) asBranchTargetDrop() BranchTargetDrop {
-	return BranchTargetDrop{Target: l}
-}
-
-// BranchTargetDrop represents the branch target and the drop range which must be dropped
-// before give the control over to the target label.
-type BranchTargetDrop struct {
-	Target Label
-	ToDrop *InclusiveRange
-}
-
-// String implements fmt.Stringer.
-func (b BranchTargetDrop) String() (ret string) {
-	if b.ToDrop != nil {
-		ret = fmt.Sprintf("%s(drop %d..%d)", b.Target, b.ToDrop.Start, b.ToDrop.End)
-	} else {
-		ret = b.Target.String()
-	}
-	return
-}
-
 // UnionOperation implements Operation and is the compilation (engine.lowerIR) result of a wazeroir.Operation.
 //
 // Not all operations result in a UnionOperation, e.g. wazeroir.OperationI32ReinterpretFromF32, and some operations are
@@ -813,8 +792,8 @@ type UnionOperation struct {
 	B1, B2 byte
 	B3     bool
 	U1, U2 uint64
+	U3     uint64
 	Us     []uint64
-	Rs     []*InclusiveRange
 }
 
 // String implements fmt.Stringer.
@@ -884,12 +863,8 @@ func (o UnionOperation) String() string {
 		return fmt.Sprintf("%s: type=%d, table=%d", o.Kind, o.U1, o.U2)
 
 	case OperationKindDrop:
-		start := -1
-		end := -1
-		if len(o.Rs) > 0 {
-			start = o.Rs[0].Start
-			end = o.Rs[0].End
-		}
+		start := int64(o.U1)
+		end := int64(o.U2)
 		return fmt.Sprintf("%s %d..%d", o.Kind, start, end)
 
 	case OperationKindPick, OperationKindSet:
@@ -1074,11 +1049,12 @@ func NewOperationBr(target Label) UnionOperation {
 //
 // The engines are expected to pop a value and branch into U1 label if the value equals 1.
 // Otherwise, the code branches into U2 label.
-func NewOperationBrIf(thenTarget, elseTarget BranchTargetDrop) UnionOperation {
+func NewOperationBrIf(thenTarget, elseTarget Label, thenDrop InclusiveRange) UnionOperation {
 	return UnionOperation{
 		Kind: OperationKindBrIf,
-		U1:   uint64(thenTarget.Target), U2: uint64(elseTarget.Target),
-		Rs: []*InclusiveRange{thenTarget.ToDrop, elseTarget.ToDrop},
+		U1:   uint64(thenTarget),
+		U2:   uint64(elseTarget),
+		U3:   thenDrop.AsU64(),
 	}
 }
 
@@ -1087,20 +1063,17 @@ func NewOperationBrIf(thenTarget, elseTarget BranchTargetDrop) UnionOperation {
 // This corresponds to wasm.OpcodeBrTableName except that the label
 // here means the wazeroir level, not the ones of Wasm.
 //
-// The engines are expected to do the br_table operation base on the default (Us[0], Rs[0]) and
-// targets (Us[1:], Rs[1:]). More precisely, this pops a value from the stack (called "index")
+// The engines are expected to do the br_table operation based on the default (Us[len(Us)-1], Us[len(Us)-2]) and
+// targets (Us[:len(Us)-1], Rs[:len(Us)-1]). More precisely, this pops a value from the stack (called "index")
 // and decides which branch we go into next based on the value.
 //
 // For example, assume we have operations like {default: L_DEFAULT, targets: [L0, L1, L2]}.
 // If "index" >= len(defaults), then branch into the L_DEFAULT label.
 // Otherwise, we enter label of targets[index].
-//
-// targetRanges must be the same length of targetLabels, padded with `nil`s if necessary
-func NewOperationBrTable(targetLabels []uint64, targetRanges []*InclusiveRange) UnionOperation {
+func NewOperationBrTable(targetLabelsAndRanges []uint64) UnionOperation {
 	return UnionOperation{
 		Kind: OperationKindBrTable,
-		Us:   targetLabels,
-		Rs:   targetRanges,
+		Us:   targetLabelsAndRanges,
 	}
 }
 
@@ -1131,17 +1104,33 @@ func NewOperationCallIndirect(typeIndex, tableIndex uint32) UnionOperation {
 // InclusiveRange is the range which spans across the value stack starting from the top to the bottom, and
 // both boundary are included in the range.
 type InclusiveRange struct {
-	Start, End int
+	Start, End int32
+}
+
+// AsU64 is be used to convert InclusiveRange to uint64 so that it can be stored in UnionOperation.
+func (i InclusiveRange) AsU64() uint64 {
+	return uint64(uint32(i.Start))<<32 | uint64(uint32(i.End))
 }
 
+// InclusiveRangeFromU64 retrieves InclusiveRange from the given uint64 which is stored in UnionOperation.
+func InclusiveRangeFromU64(v uint64) InclusiveRange {
+	return InclusiveRange{
+		Start: int32(uint32(v >> 32)),
+		End:   int32(uint32(v)),
+	}
+}
+
+// NopInclusiveRange is InclusiveRange which corresponds to no-operation.
+var NopInclusiveRange = InclusiveRange{Start: -1, End: -1}
+
 // NewOperationDrop is a constructor for UnionOperation with OperationKindDrop.
 //
 // The engines are expected to discard the values selected by NewOperationDrop.Depth which
 // starts from the top of the stack to the bottom.
 //
 // depth spans across the uint64 value stack at runtime to be dropped by this operation.
-func NewOperationDrop(depth *InclusiveRange) UnionOperation {
-	return UnionOperation{Kind: OperationKindDrop, Rs: []*InclusiveRange{depth}}
+func NewOperationDrop(depth InclusiveRange) UnionOperation {
+	return UnionOperation{Kind: OperationKindDrop, U1: depth.AsU64()}
 }
 
 // NewOperationSelect is a constructor for UnionOperation with OperationKindSelect.


Test patch:
diff --git a/internal/engine/compiler/compiler_controlflow_test.go b/internal/engine/compiler/compiler_controlflow_test.go
index 6e6836b0..6adf4b1f 100644
--- a/internal/engine/compiler/compiler_controlflow_test.go
+++ b/internal/engine/compiler/compiler_controlflow_test.go
@@ -70,8 +70,8 @@ func TestCompiler_compileLabel(t *testing.T) {
 
 func TestCompiler_compileBrIf(t *testing.T) {
 	unreachableStatus, thenLabelExitStatus, elseLabelExitStatus := nativeCallStatusCodeUnreachable, nativeCallStatusCodeUnreachable+1, nativeCallStatusCodeUnreachable+2
-	thenBranchTarget := wazeroir.BranchTargetDrop{Target: wazeroir.NewLabel(wazeroir.LabelKindHeader, 1)}
-	elseBranchTarget := wazeroir.BranchTargetDrop{Target: wazeroir.NewLabel(wazeroir.LabelKindHeader, 2)}
+	thenBranchTarget := wazeroir.NewLabel(wazeroir.LabelKindHeader, 1)
+	elseBranchTarget := wazeroir.NewLabel(wazeroir.LabelKindHeader, 2)
 
 	tests := []struct {
 		name      string
@@ -245,17 +245,17 @@ func TestCompiler_compileBrIf(t *testing.T) {
 					tc.setupFunc(t, compiler, shouldGoToElse)
 					requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
 
-					err = compiler.compileBrIf(operationPtr(wazeroir.NewOperationBrIf(thenBranchTarget, elseBranchTarget)))
+					err = compiler.compileBrIf(operationPtr(wazeroir.NewOperationBrIf(thenBranchTarget, elseBranchTarget, wazeroir.NopInclusiveRange)))
 					require.NoError(t, err)
 					compiler.compileExitFromNativeCode(unreachableStatus)
 
 					// Emit code for .then label.
-					skip := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(thenBranchTarget.Target)))
+					skip := compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(thenBranchTarget)))
 					require.False(t, skip)
 					compiler.compileExitFromNativeCode(thenLabelExitStatus)
 
 					// Emit code for .else label.
-					skip = compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(elseBranchTarget.Target)))
+					skip = compiler.compileLabel(operationPtr(wazeroir.NewOperationLabel(elseBranchTarget)))
 					require.False(t, skip)
 					compiler.compileExitFromNativeCode(elseLabelExitStatus)
 
@@ -321,35 +321,46 @@ func TestCompiler_compileBrTable(t *testing.T) {
 		expectedValue uint32
 	}{
 		{
-			name:          "only default with index 0",
-			o:             operationPtr(wazeroir.NewOperationBrTable([]uint64{getBranchLabelFromFrameID(6)}, nil)),
+			name: "only default with index 0",
+			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
+				getBranchLabelFromFrameID(6),
+				wazeroir.NopInclusiveRange.AsU64(),
+			})),
 			index:         0,
 			expectedValue: 6,
 		},
 		{
-			name:          "only default with index 100",
-			o:             operationPtr(wazeroir.NewOperationBrTable([]uint64{getBranchLabelFromFrameID(6)}, nil)),
+			name: "only default with index 100",
+			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
+				getBranchLabelFromFrameID(6),
+				wazeroir.NopInclusiveRange.AsU64(),
+			})),
 			index:         100,
 			expectedValue: 6,
 		},
 		{
 			name: "select default with targets and good index",
 			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
-				getBranchLabelFromFrameID(6), // default
 				getBranchLabelFromFrameID(1),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(2),
-			}, nil)),
+				wazeroir.NopInclusiveRange.AsU64(),
+				getBranchLabelFromFrameID(6), // default
+				wazeroir.NopInclusiveRange.AsU64(),
+			})),
 			index:         3,
 			expectedValue: 6,
 		},
 		{
 			name: "select default with targets and huge index",
 			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
-				getBranchLabelFromFrameID(6), // default
 				getBranchLabelFromFrameID(1),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(2),
+				wazeroir.NopInclusiveRange.AsU64(),
+				getBranchLabelFromFrameID(6), // default
+				wazeroir.NopInclusiveRange.AsU64(),
 			},
-				nil,
 			)),
 			index:         100000,
 			expectedValue: 6,
@@ -357,59 +368,83 @@ func TestCompiler_compileBrTable(t *testing.T) {
 		{
 			name: "select first with two targets",
 			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
-				getBranchLabelFromFrameID(5), // default
 				getBranchLabelFromFrameID(1),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(2),
-			}, nil)),
+				wazeroir.NopInclusiveRange.AsU64(),
+				getBranchLabelFromFrameID(5), // default
+				wazeroir.NopInclusiveRange.AsU64(),
+			})),
 			index:         0,
 			expectedValue: 1,
 		},
 		{
 			name: "select last with two targets",
 			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
-				getBranchLabelFromFrameID(6), // default
 				getBranchLabelFromFrameID(1),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(2),
-			}, nil)),
+				wazeroir.NopInclusiveRange.AsU64(),
+				getBranchLabelFromFrameID(6), // default
+				wazeroir.NopInclusiveRange.AsU64(),
+			})),
 			index:         1,
 			expectedValue: 2,
 		},
 		{
 			name: "select first with five targets",
 			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
-				getBranchLabelFromFrameID(5), // default
 				getBranchLabelFromFrameID(1),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(2),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(3),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(4),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(5),
-			}, nil)),
+				wazeroir.NopInclusiveRange.AsU64(),
+				getBranchLabelFromFrameID(5), // default
+				wazeroir.NopInclusiveRange.AsU64(),
+			})),
 			index:         0,
 			expectedValue: 1,
 		},
 		{
 			name: "select middle with five targets",
 			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
-				getBranchLabelFromFrameID(5), // default
 				getBranchLabelFromFrameID(1),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(2),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(3),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(4),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(5),
-			}, nil)),
+				wazeroir.NopInclusiveRange.AsU64(),
+				getBranchLabelFromFrameID(5), // default
+				wazeroir.NopInclusiveRange.AsU64(),
+			})),
 			index:         2,
 			expectedValue: 3,
 		},
 		{
 			name: "select last with five targets",
 			o: operationPtr(wazeroir.NewOperationBrTable([]uint64{
-				getBranchLabelFromFrameID(5), // default
 				getBranchLabelFromFrameID(1),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(2),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(3),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(4),
+				wazeroir.NopInclusiveRange.AsU64(),
 				getBranchLabelFromFrameID(5),
-			}, nil)),
+				wazeroir.NopInclusiveRange.AsU64(),
+				getBranchLabelFromFrameID(5), // default
+				wazeroir.NopInclusiveRange.AsU64(),
+			})),
 			index:         4,
 			expectedValue: 5,
 		},
diff --git a/internal/engine/compiler/compiler_drop_test.go b/internal/engine/compiler/compiler_drop_test.go
index ff93bd80..cb24c2c2 100644
--- a/internal/engine/compiler/compiler_drop_test.go
+++ b/internal/engine/compiler/compiler_drop_test.go
@@ -10,10 +10,10 @@ import (
 )
 
 func Test_compileDropRange(t *testing.T) {
-	t.Run("nil range", func(t *testing.T) {
+	t.Run("nop range", func(t *testing.T) {
 		c := newCompiler()
 
-		err := compileDropRange(c, nil)
+		err := compileDropRange(c, wazeroir.NopInclusiveRange.AsU64())
 		require.NoError(t, err)
 	})
 
@@ -38,7 +38,7 @@ func Test_compileDropRange(t *testing.T) {
 		require.Equal(t, unreservedRegisterTotal, len(ls.usedRegisters.list()))
 
 		// Drop all the values.
-		err := compileDropRange(c, &wazeroir.InclusiveRange{Start: 0, End: int(ls.sp - 1)})
+		err := compileDropRange(c, wazeroir.InclusiveRange{Start: 0, End: int32(ls.sp - 1)}.AsU64())
 		require.NoError(t, err)
 
 		// All the registers must be marked unused.
@@ -51,7 +51,7 @@ func Test_compileDropRange(t *testing.T) {
 func TestRuntimeValueLocationStack_dropsLivesForInclusiveRange(t *testing.T) {
 	tests := []struct {
 		v            *runtimeValueLocationStack
-		ir           *wazeroir.InclusiveRange
+		ir           wazeroir.InclusiveRange
 		lives, drops []runtimeValueLocation
 	}{
 		{
@@ -59,7 +59,7 @@ func TestRuntimeValueLocationStack_dropsLivesForInclusiveRange(t *testing.T) {
 				stack: []runtimeValueLocation{{register: 0}, {register: 1} /* drop target */, {register: 2}},
 				sp:    3,
 			},
-			ir:    &wazeroir.InclusiveRange{Start: 1, End: 1},
+			ir:    wazeroir.InclusiveRange{Start: 1, End: 1},
 			drops: []runtimeValueLocation{{register: 1}},
 			lives: []runtimeValueLocation{{register: 2}},
 		},
@@ -76,7 +76,7 @@ func TestRuntimeValueLocationStack_dropsLivesForInclusiveRange(t *testing.T) {
 				},
 				sp: 7,
 			},
-			ir:    &wazeroir.InclusiveRange{Start: 2, End: 4},
+			ir:    wazeroir.InclusiveRange{Start: 2, End: 4},
 			drops: []runtimeValueLocation{{register: 2}, {register: 3}, {register: 4}},
 			lives: []runtimeValueLocation{{register: 5}, {register: 6}},
 		},
diff --git a/internal/engine/compiler/compiler_global_test.go b/internal/engine/compiler/compiler_global_test.go
index b1cb44cf..6e64c061 100644
--- a/internal/engine/compiler/compiler_global_test.go
+++ b/internal/engine/compiler/compiler_global_test.go
@@ -20,7 +20,7 @@ func TestCompiler_compileGlobalGet(t *testing.T) {
 				Globals: []wasm.GlobalType{{}, {ValType: tp}},
 			})
 
-			// Setup the global. (Start with nil as a dummy so that global index can be non-trivial.)
+			// Setup the global. (start with nil as a dummy so that global index can be non-trivial.)
 			globals := []*wasm.GlobalInstance{nil, {Val: globalValue, Type: wasm.GlobalType{ValType: tp}}}
 			env.addGlobals(globals...)
 
@@ -66,7 +66,7 @@ func TestCompiler_compileGlobalGet_v128(t *testing.T) {
 		Globals: []wasm.GlobalType{{}, {ValType: v128Type}},
 	})
 
-	// Setup the global. (Start with nil as a dummy so that global index can be non-trivial.)
+	// Setup the global. (start with nil as a dummy so that global index can be non-trivial.)
 	globals := []*wasm.GlobalInstance{nil, {Val: 12345, ValHi: 6789, Type: wasm.GlobalType{ValType: v128Type}}}
 	env.addGlobals(globals...)
 
@@ -117,7 +117,7 @@ func TestCompiler_compileGlobalSet(t *testing.T) {
 				Globals: []wasm.GlobalType{{}, {ValType: tp}},
 			})
 
-			// Setup the global. (Start with nil as a dummy so that global index can be non-trivial.)
+			// Setup the global. (start with nil as a dummy so that global index can be non-trivial.)
 			env.addGlobals(nil, &wasm.GlobalInstance{Val: 40, Type: wasm.GlobalType{ValType: tp}})
 
 			err := compiler.compilePreamble()
@@ -170,7 +170,7 @@ func TestCompiler_compileGlobalSet_v128(t *testing.T) {
 		Globals: []wasm.GlobalType{{}, {ValType: v128Type}},
 	})
 
-	// Setup the global. (Start with nil as a dummy so that global index can be non-trivial.)
+	// Setup the global. (start with nil as a dummy so that global index can be non-trivial.)
 	env.addGlobals(nil, &wasm.GlobalInstance{Val: 0, ValHi: 0, Type: wasm.GlobalType{ValType: v128Type}})
 
 	err := compiler.compilePreamble()
diff --git a/internal/engine/compiler/compiler_stack_test.go b/internal/engine/compiler/compiler_stack_test.go
index 868215f1..4cbf5ddd 100644
--- a/internal/engine/compiler/compiler_stack_test.go
+++ b/internal/engine/compiler/compiler_stack_test.go
@@ -327,7 +327,7 @@ func TestCompiler_compilePick(t *testing.T) {
 }
 
 func TestCompiler_compileDrop(t *testing.T) {
-	t.Run("range nil", func(t *testing.T) {
+	t.Run("range nop", func(t *testing.T) {
 		env := newCompilerEnvironment()
 		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
 
@@ -341,7 +341,7 @@ func TestCompiler_compileDrop(t *testing.T) {
 		}
 		requireRuntimeLocationStackPointerEqual(t, uint64(liveNum), compiler)
 
-		err = compiler.compileDrop(operationPtr(wazeroir.NewOperationDrop(nil)))
+		err = compiler.compileDrop(operationPtr(wazeroir.NewOperationDrop(wazeroir.NopInclusiveRange)))
 		require.NoError(t, err)
 
 		// After the nil range drop, the stack must remain the same.
@@ -357,8 +357,8 @@ func TestCompiler_compileDrop(t *testing.T) {
 		require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
 	})
 	t.Run("start top", func(t *testing.T) {
-		r := &wazeroir.InclusiveRange{Start: 0, End: 2}
-		dropTargetNum := r.End - r.Start + 1 // +1 as the range is inclusive!
+		r := wazeroir.InclusiveRange{Start: 0, End: 2}
+		dropTargetNum := int(r.End - r.Start + 1) // +1 as the range is inclusive!
 		liveNum := 5
 
 		env := newCompilerEnvironment()
@@ -401,9 +401,9 @@ func TestCompiler_compileDrop(t *testing.T) {
 	})
 
 	t.Run("start from middle", func(t *testing.T) {
-		r := &wazeroir.InclusiveRange{Start: 2, End: 3}
+		r := wazeroir.InclusiveRange{Start: 2, End: 3}
 		liveAboveDropStartNum := 3
-		dropTargetNum := r.End - r.Start + 1 // +1 as the range is inclusive!
+		dropTargetNum := int(r.End - r.Start + 1) // +1 as the range is inclusive!
 		liveBelowDropEndNum := 5
 		total := liveAboveDropStartNum + dropTargetNum + liveBelowDropEndNum
 		liveTotal := liveAboveDropStartNum + liveBelowDropEndNum
diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go
index 507546d1..b167bbf0 100644
--- a/internal/wazeroir/compiler_test.go
+++ b/internal/wazeroir/compiler_test.go
@@ -80,9 +80,9 @@ func TestCompile(t *testing.T) {
 			},
 			expected: &CompilationResult{
 				Operations: []UnionOperation{ // begin with params: [$x]
-					NewOperationPick(0, false),                          // [$x, $x]
-					NewOperationDrop(&InclusiveRange{Start: 1, End: 1}), // [$x]
-					NewOperationBr(NewLabel(LabelKindReturn, 0)),        // return!
+					NewOperationPick(0, false),                         // [$x, $x]
+					NewOperationDrop(InclusiveRange{Start: 1, End: 1}), // [$x]
+					NewOperationBr(NewLabel(LabelKindReturn, 0)),       // return!
 				},
 				LabelCallers: map[Label]uint32{},
 				Types: []wasm.FunctionType{
@@ -111,7 +111,7 @@ func TestCompile(t *testing.T) {
 				Operations: []UnionOperation{ // begin with params: []
 					NewOperationConstI32(8), // [8]
 					NewOperationLoad(UnsignedTypeI32, MemoryArg{Alignment: 2, Offset: 0}), // [x]
-					NewOperationDrop(&InclusiveRange{}),                                   // []
+					NewOperationDrop(InclusiveRange{}),                                    // []
 					NewOperationBr(NewLabel(LabelKindReturn, 0)),                          // return!
 				},
 				LabelCallers: map[Label]uint32{},
@@ -136,7 +136,7 @@ func TestCompile(t *testing.T) {
 				Operations: []UnionOperation{ // begin with params: []
 					NewOperationConstI32(8), // [8]
 					NewOperationLoad(UnsignedTypeI32, MemoryArg{Alignment: 2, Offset: 0}), // [x]
-					NewOperationDrop(&InclusiveRange{}),                                   // []
+					NewOperationDrop(InclusiveRange{}),                                    // []
 					NewOperationBr(NewLabel(LabelKindReturn, 0)),                          // return!
 				},
 				LabelCallers: map[Label]uint32{},
@@ -156,10 +156,10 @@ func TestCompile(t *testing.T) {
 			},
 			expected: &CompilationResult{
 				Operations: []UnionOperation{ // begin with params: [$delta]
-					NewOperationPick(0, false),                          // [$delta, $delta]
-					NewOperationMemoryGrow(),                            // [$delta, $old_size]
-					NewOperationDrop(&InclusiveRange{Start: 1, End: 1}), // [$old_size]
-					NewOperationBr(NewLabel(LabelKindReturn, 0)),        // return!
+					NewOperationPick(0, false),                         // [$delta, $delta]
+					NewOperationMemoryGrow(),                           // [$delta, $old_size]
+					NewOperationDrop(InclusiveRange{Start: 1, End: 1}), // [$old_size]
+					NewOperationBr(NewLabel(LabelKindReturn, 0)),       // return!
 				},
 				LabelCallers: map[Label]uint32{},
 				Types: []wasm.FunctionType{{
@@ -348,10 +348,10 @@ func TestCompile_MultiValue(t *testing.T) {
 			},
 			expected: &CompilationResult{
 				Operations: []UnionOperation{ // begin with params: [$x, $y]
-					NewOperationPick(0, false),                          // [$x, $y, $y]
-					NewOperationPick(2, false),                          // [$x, $y, $y, $x]
-					NewOperationDrop(&InclusiveRange{Start: 2, End: 3}), // [$y, $x]
-					NewOperationBr(NewLabel(LabelKindReturn, 0)),        // return!
+					NewOperationPick(0, false),                         // [$x, $y, $y]
+					NewOperationPick(2, false),                         // [$x, $y, $y, $x]
+					NewOperationDrop(InclusiveRange{Start: 2, End: 3}), // [$y, $x]
+					NewOperationBr(NewLabel(LabelKindReturn, 0)),       // return!
 				},
 				LabelCallers: map[Label]uint32{},
 				Functions:    []wasm.Index{0},
@@ -443,8 +443,9 @@ func TestCompile_MultiValue(t *testing.T) {
 					NewOperationConstI32(1),    // [$0, 1]
 					NewOperationPick(1, false), // [$0, 1, $0]
 					NewOperationBrIf( // [$0, 1]
-						BranchTargetDrop{Target: NewLabel(LabelKindHeader, 2)},
-						BranchTargetDrop{Target: NewLabel(LabelKindElse, 2)},
+						NewLabel(LabelKindHeader, 2),
+						NewLabel(LabelKindElse, 2),
+						NopInclusiveRange,
 					),
 					NewOperationLabel(NewLabel(LabelKindHeader, 2)),
 					NewOperationConstI32(2),          // [$0, 1, 2]
@@ -455,7 +456,7 @@ func TestCompile_MultiValue(t *testing.T) {
 					NewOperationAdd(UnsignedTypeI32),                // [$0, -1]
 					NewOperationBr(NewLabel(LabelKindContinuation, 2)),
 					NewOperationLabel(NewLabel(LabelKindContinuation, 2)),
-					NewOperationDrop(&InclusiveRange{Start: 1, End: 1}), // .L2 = [3], .L2_else = [-1]
+					NewOperationDrop(InclusiveRange{Start: 1, End: 1}), // .L2 = [3], .L2_else = [-1]
 					NewOperationBr(NewLabel(LabelKindReturn, 0)),
 				},
 				LabelCallers: map[Label]uint32{
@@ -500,8 +501,9 @@ func TestCompile_MultiValue(t *testing.T) {
 					NewOperationConstI32(2),    // [$0, 1, 2]
 					NewOperationPick(2, false), // [$0, 1, 2, $0]
 					NewOperationBrIf( // [$0, 1, 2]
-						BranchTargetDrop{Target: NewLabel(LabelKindHeader, 2)},
-						BranchTargetDrop{Target: NewLabel(LabelKindElse, 2)},
+						NewLabel(LabelKindHeader, 2),
+						NewLabel(LabelKindElse, 2),
+						NopInclusiveRange,
 					),
 					NewOperationLabel(NewLabel(LabelKindHeader, 2)),
 					NewOperationAdd(UnsignedTypeI32), // [$0, 3]
@@ -510,7 +512,7 @@ func TestCompile_MultiValue(t *testing.T) {
 					NewOperationSub(UnsignedTypeI32), // [$0, -1]
 					NewOperationBr(NewLabel(LabelKindContinuation, 2)),
 					NewOperationLabel(NewLabel(LabelKindContinuation, 2)),
-					NewOperationDrop(&InclusiveRange{Start: 1, End: 1}), // .L2 = [3], .L2_else = [-1]
+					NewOperationDrop(InclusiveRange{Start: 1, End: 1}), // .L2 = [3], .L2_else = [-1]
 					NewOperationBr(NewLabel(LabelKindReturn, 0)),
 				},
 				LabelCallers: map[Label]uint32{
@@ -555,8 +557,9 @@ func TestCompile_MultiValue(t *testing.T) {
 					NewOperationConstI32(2),    // [$0, 1, 2]
 					NewOperationPick(2, false), // [$0, 1, 2, $0]
 					NewOperationBrIf( // [$0, 1, 2]
-						BranchTargetDrop{Target: NewLabel(LabelKindHeader, 2)},
-						BranchTargetDrop{Target: NewLabel(LabelKindElse, 2)},
+						NewLabel(LabelKindHeader, 2),
+						NewLabel(LabelKindElse, 2),
+						NopInclusiveRange,
 					),
 					NewOperationLabel(NewLabel(LabelKindHeader, 2)),
 					NewOperationAdd(UnsignedTypeI32), // [$0, 3]
@@ -565,7 +568,7 @@ func TestCompile_MultiValue(t *testing.T) {
 					NewOperationSub(UnsignedTypeI32), // [$0, -1]
 					NewOperationBr(NewLabel(LabelKindContinuation, 2)),
 					NewOperationLabel(NewLabel(LabelKindContinuation, 2)),
-					NewOperationDrop(&InclusiveRange{Start: 1, End: 1}), // .L2 = [3], .L2_else = [-1]
+					NewOperationDrop(InclusiveRange{Start: 1, End: 1}), // .L2 = [3], .L2_else = [-1]
 					NewOperationBr(NewLabel(LabelKindReturn, 0)),
 				},
 				LabelCallers: map[Label]uint32{
@@ -619,8 +622,8 @@ func TestCompile_NonTrappingFloatToIntConversion(t *testing.T) {
 				SignedInt32,
 				true,
 			),
-			NewOperationDrop(&InclusiveRange{Start: 1, End: 1}), // [i32.trunc_sat_f32_s($0)]
-			NewOperationBr(NewLabel(LabelKindReturn, 0)),        // return!
+			NewOperationDrop(InclusiveRange{Start: 1, End: 1}), // [i32.trunc_sat_f32_s($0)]
+			NewOperationBr(NewLabel(LabelKindReturn, 0)),       // return!
 		},
 		LabelCallers: map[Label]uint32{},
 		Functions:    []wasm.Index{0},
@@ -646,10 +649,10 @@ func TestCompile_SignExtensionOps(t *testing.T) {
 
 	expected := &CompilationResult{
 		Operations: []UnionOperation{ // begin with params: [$0]
-			NewOperationPick(0, false),                          // [$0, $0]
-			NewOperationSignExtend32From8(),                     // [$0, i32.extend8_s($0)]
-			NewOperationDrop(&InclusiveRange{Start: 1, End: 1}), // [i32.extend8_s($0)]
-			NewOperationBr(NewLabel(LabelKindReturn, 0)),        // return!
+			NewOperationPick(0, false),                         // [$0, $0]
+			NewOperationSignExtend32From8(),                    // [$0, i32.extend8_s($0)]
+			NewOperationDrop(InclusiveRange{Start: 1, End: 1}), // [i32.extend8_s($0)]
+			NewOperationBr(NewLabel(LabelKindReturn, 0)),       // return!
 		},
 		LabelCallers: map[Label]uint32{},
 		Functions:    []wasm.Index{0},
@@ -733,7 +736,7 @@ func TestCompile_Refs(t *testing.T) {
 			},
 			expected: []UnionOperation{
 				NewOperationRefFunc(100),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -746,7 +749,7 @@ func TestCompile_Refs(t *testing.T) {
 			},
 			expected: []UnionOperation{
 				NewOperationConstI64(0),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -759,7 +762,7 @@ func TestCompile_Refs(t *testing.T) {
 			},
 			expected: []UnionOperation{
 				NewOperationConstI64(0),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -774,7 +777,7 @@ func TestCompile_Refs(t *testing.T) {
 			expected: []UnionOperation{
 				NewOperationRefFunc(100),
 				NewOperationEqz(UnsignedInt64),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -789,7 +792,7 @@ func TestCompile_Refs(t *testing.T) {
 			expected: []UnionOperation{
 				NewOperationConstI64(0),
 				NewOperationEqz(UnsignedInt64),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -830,7 +833,7 @@ func TestCompile_TableGetOrSet(t *testing.T) {
 			expected: []UnionOperation{
 				NewOperationConstI32(10),
 				NewOperationTableGet(0),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -903,7 +906,7 @@ func TestCompile_TableGrowFillSize(t *testing.T) {
 				NewOperationConstI64(0), // Null ref.
 				NewOperationConstI32(1),
 				NewOperationTableGrow(1),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -932,7 +935,7 @@ func TestCompile_TableGrowFillSize(t *testing.T) {
 			},
 			expected: []UnionOperation{
 				NewOperationTableSize(1),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -976,7 +979,7 @@ func TestCompile_Locals(t *testing.T) {
 			},
 			expected: []UnionOperation{
 				NewOperationPick(1, true), // [param[0].low, param[0].high] -> [param[0].low, param[0].high, param[0].low, param[0].high]
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 3}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 3}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -992,7 +995,7 @@ func TestCompile_Locals(t *testing.T) {
 			},
 			expected: []UnionOperation{
 				NewOperationPick(0, false), // [param[0]] -> [param[0], param[0]]
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 1}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 1}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -1012,7 +1015,7 @@ func TestCompile_Locals(t *testing.T) {
 			expected: []UnionOperation{
 				NewOperationV128Const(0, 0),
 				NewOperationPick(1, true), // [p[0].low, p[0].high] -> [p[0].low, p[0].high, p[0].low, p[0].high]
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 3}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 3}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -1034,7 +1037,7 @@ func TestCompile_Locals(t *testing.T) {
 				NewOperationV128Const(0x01, 0x02),
 				// [p[0].lo, p[1].hi, 0x01, 0x02] -> [0x01, 0x02]
 				NewOperationSet(3, true),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 1}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 1}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -1052,7 +1055,7 @@ func TestCompile_Locals(t *testing.T) {
 			expected: []UnionOperation{
 				NewOperationConstI32(0x1),
 				NewOperationSet(1, false),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 0}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 0}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -1078,7 +1081,7 @@ func TestCompile_Locals(t *testing.T) {
 				NewOperationV128Const(0x01, 0x02),
 				// [p[0].lo, p[1].hi, 0x01, 0x02] -> [0x01, 0x02]
 				NewOperationSet(3, true),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 1}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 1}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -1102,7 +1105,7 @@ func TestCompile_Locals(t *testing.T) {
 				NewOperationPick(1, true),
 				// [p[0].lo, p[1].hi, 0x01, 0x02, 0x01, 0x02] -> [0x01, 0x02, 0x01, 0x02]
 				NewOperationSet(5, true),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 3}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 3}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -1121,7 +1124,7 @@ func TestCompile_Locals(t *testing.T) {
 				NewOperationConstF32(math.Float32frombits(1)),
 				NewOperationPick(0, false),
 				NewOperationSet(2, false),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 1}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 1}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -1149,7 +1152,7 @@ func TestCompile_Locals(t *testing.T) {
 				NewOperationPick(1, true),
 				// [p[0].lo, p[1].hi, 0x01, 0x02, 0x01, 0x2] -> [0x01, 0x02, 0x01, 0x02]
 				NewOperationSet(5, true),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 3}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 3}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)), // return!
 			},
 		},
@@ -2755,7 +2758,7 @@ func TestCompile_drop_vectors(t *testing.T) {
 				NewOperationV128Const(0x1, 0x2),
 				// InclusiveRange is the range in uint64 representation, so dropping a vector value on top
 				// should be translated as drop [0..1] inclusively.
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 1}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 1}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)),
 			},
 		},
@@ -2802,7 +2805,7 @@ func TestCompile_select_vectors(t *testing.T) {
 				NewOperationV128Const(0x3, 0x4),
 				NewOperationConstI32(0),
 				NewOperationSelect(true),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 1}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 1}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)),
 			},
 		},
@@ -2828,7 +2831,7 @@ func TestCompile_select_vectors(t *testing.T) {
 				NewOperationV128Const(0x3, 0x4),
 				NewOperationConstI32(0),
 				NewOperationSelect(true),
-				NewOperationDrop(&InclusiveRange{Start: 0, End: 1}),
+				NewOperationDrop(InclusiveRange{Start: 0, End: 1}),
 				NewOperationBr(NewLabel(LabelKindReturn, 0)),
 			},
 		},