Repository: tetratelabs/wazero
Pull request #1318 (2023-03-30 09:07:10)

wazeroir: migrate unary operations to UnionOperation
- refactor: OperationCall, OperationGlobalGet, OperationGlobalSet
- refactor: Constant Operations

Refactor some unary operations to use `UnionOperation`. Again, we do this in waves to avoid creating a huge big-bang PR.

Unfortunately I have to do this mostly by hand to ensure that I am not breaking something, and to preserve comments and docs. At least I am double-checking it :D

Continues implementation of #1202

mathetake: could you make sure always link the issue #1202 in the description? Also, add the package name or more descriptive prefix in the PR titles instead of `refactor`.

Patch:
diff --git a/internal/engine/compiler/compiler.go b/internal/engine/compiler/compiler.go
index 5cdecdaf..8378a83f 100644
--- a/internal/engine/compiler/compiler.go
+++ b/internal/engine/compiler/compiler.go
@@ -30,9 +30,9 @@ type compiler interface {
 	// compileSet adds instruction to perform wazeroir.OperationSet.
 	compileSet(o wazeroir.OperationSet) error
 	// compileGlobalGet adds instructions to perform wazeroir.OperationGlobalGet.
-	compileGlobalGet(o wazeroir.OperationGlobalGet) error
+	compileGlobalGet(o wazeroir.UnionOperation) error
 	// compileGlobalSet adds instructions to perform wazeroir.OperationGlobalSet.
-	compileGlobalSet(o wazeroir.OperationGlobalSet) error
+	compileGlobalSet(o wazeroir.UnionOperation) error
 	// compileBr adds instructions to perform wazeroir.OperationBr.
 	compileBr(o wazeroir.OperationBr) error
 	// compileBrIf adds instructions to perform wazeroir.OperationBrIf.
@@ -40,7 +40,7 @@ type compiler interface {
 	// compileBrTable adds instructions to perform wazeroir.OperationBrTable.
 	compileBrTable(o wazeroir.OperationBrTable) error
 	// compileCall adds instructions to perform wazeroir.OperationCall.
-	compileCall(o wazeroir.OperationCall) error
+	compileCall(o wazeroir.UnionOperation) error
 	// compileCallIndirect adds instructions to perform wazeroir.OperationCallIndirect.
 	compileCallIndirect(o wazeroir.OperationCallIndirect) error
 	// compileDrop adds instructions to perform wazeroir.OperationDrop.
@@ -153,14 +153,14 @@ type compiler interface {
 	compileMemoryGrow() error
 	// compileMemorySize adds instruction to perform wazeroir.OperationMemorySize.
 	compileMemorySize() error
-	// compileConstI32 adds instruction to perform wazeroir.OperationConstI32.
-	compileConstI32(o wazeroir.OperationConstI32) error
-	// compileConstI64 adds instruction to perform wazeroir.OperationConstI64.
-	compileConstI64(o wazeroir.OperationConstI64) error
-	// compileConstF32 adds instruction to perform wazeroir.OperationConstF32.
-	compileConstF32(o wazeroir.OperationConstF32) error
-	// compileConstF64 adds instruction to perform wazeroir.OperationConstF64.
-	compileConstF64(o wazeroir.OperationConstF64) error
+	// compileConstI32 adds instruction to perform wazeroir.NewOperationConstI32.
+	compileConstI32(o wazeroir.UnionOperation) error
+	// compileConstI64 adds instruction to perform wazeroir.NewOperationConstI64.
+	compileConstI64(o wazeroir.UnionOperation) error
+	// compileConstF32 adds instruction to perform wazeroir.NewOperationConstF32.
+	compileConstF32(o wazeroir.UnionOperation) error
+	// compileConstF64 adds instruction to perform wazeroir.NewOperationConstF64.
+	compileConstF64(o wazeroir.UnionOperation) error
 	// compileSignExtend32From8 adds instructions to perform wazeroir.OperationSignExtend32From8.
 	compileSignExtend32From8() error
 	// compileSignExtend32From16 adds instructions to perform wazeroir.OperationSignExtend32From16.
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index bccbed5d..54c6a570 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -1100,8 +1100,6 @@ func compileWasmFunction(cmp compiler, ir *wazeroir.CompilationResult) (*code, e
 			err = cmp.compileBrIf(o)
 		case wazeroir.OperationBrTable:
 			err = cmp.compileBrTable(o)
-		case wazeroir.OperationCall:
-			err = cmp.compileCall(o)
 		case wazeroir.OperationCallIndirect:
 			err = cmp.compileCallIndirect(o)
 		case wazeroir.OperationDrop:
@@ -1112,10 +1110,6 @@ func compileWasmFunction(cmp compiler, ir *wazeroir.CompilationResult) (*code, e
 			err = cmp.compilePick(o)
 		case wazeroir.OperationSet:
 			err = cmp.compileSet(o)
-		case wazeroir.OperationGlobalGet:
-			err = cmp.compileGlobalGet(o)
-		case wazeroir.OperationGlobalSet:
-			err = cmp.compileGlobalSet(o)
 		case wazeroir.OperationLoad:
 			err = cmp.compileLoad(o)
 		case wazeroir.OperationLoad8:
@@ -1132,14 +1126,6 @@ func compileWasmFunction(cmp compiler, ir *wazeroir.CompilationResult) (*code, e
 			err = cmp.compileStore16(o)
 		case wazeroir.OperationStore32:
 			err = cmp.compileStore32(o)
-		case wazeroir.OperationConstI32:
-			err = cmp.compileConstI32(o)
-		case wazeroir.OperationConstI64:
-			err = cmp.compileConstI64(o)
-		case wazeroir.OperationConstF32:
-			err = cmp.compileConstF32(o)
-		case wazeroir.OperationConstF64:
-			err = cmp.compileConstF64(o)
 		case wazeroir.OperationEq:
 			err = cmp.compileEq(o)
 		case wazeroir.OperationNe:
@@ -1338,10 +1324,26 @@ func compileWasmFunction(cmp compiler, ir *wazeroir.CompilationResult) (*code, e
 			switch op.Kind() {
 			case wazeroir.OperationKindUnreachable:
 				err = cmp.compileUnreachable()
+			case wazeroir.OperationKindCall:
+				err = cmp.compileCall(o)
+
+			case wazeroir.OperationKindGlobalGet:
+				err = cmp.compileGlobalGet(o)
+			case wazeroir.OperationKindGlobalSet:
+				err = cmp.compileGlobalSet(o)
+
 			case wazeroir.OperationKindMemorySize:
 				err = cmp.compileMemorySize()
 			case wazeroir.OperationKindMemoryGrow:
 				err = cmp.compileMemoryGrow()
+			case wazeroir.OperationKindConstI32:
+				err = cmp.compileConstI32(o)
+			case wazeroir.OperationKindConstI64:
+				err = cmp.compileConstI64(o)
+			case wazeroir.OperationKindConstF32:
+				err = cmp.compileConstF32(o)
+			case wazeroir.OperationKindConstF64:
+				err = cmp.compileConstF64(o)
 
 			case wazeroir.OperationKindI32WrapFromI64:
 				err = cmp.compileI32WrapFromI64()
diff --git a/internal/engine/compiler/impl_amd64.go b/internal/engine/compiler/impl_amd64.go
index 35236b6f..aadd85bc 100644
--- a/internal/engine/compiler/impl_amd64.go
+++ b/internal/engine/compiler/impl_amd64.go
@@ -288,7 +288,7 @@ func (c *amd64Compiler) compileSet(o wazeroir.OperationSet) error {
 }
 
 // compileGlobalGet implements compiler.compileGlobalGet for the amd64 architecture.
-func (c *amd64Compiler) compileGlobalGet(o wazeroir.OperationGlobalGet) error {
+func (c *amd64Compiler) compileGlobalGet(o wazeroir.UnionOperation) error {
 	if err := c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
 		return err
 	}
@@ -301,14 +301,16 @@ func (c *amd64Compiler) compileGlobalGet(o wazeroir.OperationGlobalGet) error {
 	// First, move the pointer to the global slice into the allocated register.
 	c.assembler.CompileMemoryToRegister(amd64.MOVQ, amd64ReservedRegisterForCallEngine, callEngineModuleContextGlobalElement0AddressOffset, intReg)
 
+	index := o.U1
+
 	// Now, move the location of the global instance into the register.
-	c.assembler.CompileMemoryToRegister(amd64.MOVQ, intReg, 8*int64(o.Index), intReg)
+	c.assembler.CompileMemoryToRegister(amd64.MOVQ, intReg, 8*int64(index), intReg)
 
 	// When an integer, reuse the pointer register for the value. Otherwise, allocate a float register for it.
 	valueReg := intReg
 	var vt runtimeValueType
 	var inst asm.Instruction
-	switch c.ir.Globals[o.Index].ValType {
+	switch c.ir.Globals[index].ValType {
 	case wasm.ValueTypeI32:
 		inst = amd64.MOVL
 		vt = runtimeValueTypeI32
@@ -353,8 +355,10 @@ func (c *amd64Compiler) compileGlobalGet(o wazeroir.OperationGlobalGet) error {
 }
 
 // compileGlobalSet implements compiler.compileGlobalSet for the amd64 architecture.
-func (c *amd64Compiler) compileGlobalSet(o wazeroir.OperationGlobalSet) error {
-	wasmValueType := c.ir.Globals[o.Index].ValType
+func (c *amd64Compiler) compileGlobalSet(o wazeroir.UnionOperation) error {
+	index := o.U1
+
+	wasmValueType := c.ir.Globals[index].ValType
 	isV128 := wasmValueType == wasm.ValueTypeV128
 
 	// First, move the value to set into a temporary register.
@@ -377,7 +381,7 @@ func (c *amd64Compiler) compileGlobalSet(o wazeroir.OperationGlobalSet) error {
 	c.assembler.CompileMemoryToRegister(amd64.MOVQ, amd64ReservedRegisterForCallEngine, callEngineModuleContextGlobalElement0AddressOffset, intReg)
 
 	// Now, move the location of the global instance into the register.
-	c.assembler.CompileMemoryToRegister(amd64.MOVQ, intReg, 8*int64(o.Index), intReg)
+	c.assembler.CompileMemoryToRegister(amd64.MOVQ, intReg, 8*int64(index), intReg)
 
 	// Now ready to write the value to the global instance location.
 	var inst asm.Instruction
@@ -722,12 +726,14 @@ func (c *amd64Compiler) compileLabel(o wazeroir.OperationLabel) (skipLabel bool)
 }
 
 // compileCall implements compiler.compileCall for the amd64 architecture.
-func (c *amd64Compiler) compileCall(o wazeroir.OperationCall) error {
+func (c *amd64Compiler) compileCall(o wazeroir.UnionOperation) error {
 	if err := c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
 		return err
 	}
 
-	target := c.ir.Functions[o.FunctionIndex]
+	functionIndex := o.U1
+
+	target := c.ir.Functions[functionIndex]
 	targetType := &c.ir.Types[target]
 
 	targetAddressRegister, err := c.allocateRegister(registerTypeGeneralPurpose)
@@ -736,7 +742,7 @@ func (c *amd64Compiler) compileCall(o wazeroir.OperationCall) error {
 	}
 
 	// First, push the index to the callEngine.functionsElement0Address into the target register.
-	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(o.FunctionIndex)*functionSize, targetAddressRegister)
+	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(functionIndex)*functionSize, targetAddressRegister)
 
 	// Next, we add the address of the first item of callEngine.functions slice (= &callEngine.functions[0])
 	// to the target register.
@@ -4194,7 +4200,7 @@ func (c *amd64Compiler) compileTableGrow(o wazeroir.OperationTableGrow) error {
 	}
 
 	// Pushes the table index.
-	if err := c.compileConstI32(wazeroir.OperationConstI32{Value: o.TableIndex}); err != nil {
+	if err := c.compileConstI32(wazeroir.NewOperationConstI32(o.TableIndex)); err != nil {
 		return err
 	}
 
@@ -4279,7 +4285,7 @@ func (c *amd64Compiler) compileRefFunc(o wazeroir.OperationRefFunc) error {
 }
 
 // compileConstI32 implements compiler.compileConstI32 for the amd64 architecture.
-func (c *amd64Compiler) compileConstI32(o wazeroir.OperationConstI32) error {
+func (c *amd64Compiler) compileConstI32(o wazeroir.UnionOperation) error {
 	if err := c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
 		return err
 	}
@@ -4289,12 +4295,12 @@ func (c *amd64Compiler) compileConstI32(o wazeroir.OperationConstI32) error {
 		return err
 	}
 	c.pushRuntimeValueLocationOnRegister(reg, runtimeValueTypeI32)
-	c.assembler.CompileConstToRegister(amd64.MOVL, int64(o.Value), reg)
+	c.assembler.CompileConstToRegister(amd64.MOVL, int64(o.U1), reg)
 	return nil
 }
 
 // compileConstI64 implements compiler.compileConstI64 for the amd64 architecture.
-func (c *amd64Compiler) compileConstI64(o wazeroir.OperationConstI64) error {
+func (c *amd64Compiler) compileConstI64(o wazeroir.UnionOperation) error {
 	if err := c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
 		return err
 	}
@@ -4305,12 +4311,12 @@ func (c *amd64Compiler) compileConstI64(o wazeroir.OperationConstI64) error {
 	}
 	c.pushRuntimeValueLocationOnRegister(reg, runtimeValueTypeI64)
 
-	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(o.Value), reg)
+	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(o.U1), reg)
 	return nil
 }
 
 // compileConstF32 implements compiler.compileConstF32 for the amd64 architecture.
-func (c *amd64Compiler) compileConstF32(o wazeroir.OperationConstF32) error {
+func (c *amd64Compiler) compileConstF32(o wazeroir.UnionOperation) error {
 	if err := c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
 		return err
 	}
@@ -4328,13 +4334,13 @@ func (c *amd64Compiler) compileConstF32(o wazeroir.OperationConstF32) error {
 		return err
 	}
 
-	c.assembler.CompileConstToRegister(amd64.MOVL, int64(math.Float32bits(o.Value)), tmpReg)
+	c.assembler.CompileConstToRegister(amd64.MOVL, int64(o.U1) /*math.Float32bits(o.Value)*/, tmpReg)
 	c.assembler.CompileRegisterToRegister(amd64.MOVL, tmpReg, reg)
 	return nil
 }
 
 // compileConstF64 implements compiler.compileConstF64 for the amd64 architecture.
-func (c *amd64Compiler) compileConstF64(o wazeroir.OperationConstF64) error {
+func (c *amd64Compiler) compileConstF64(o wazeroir.UnionOperation) error {
 	if err := c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
 		return err
 	}
@@ -4352,7 +4358,7 @@ func (c *amd64Compiler) compileConstF64(o wazeroir.OperationConstF64) error {
 		return err
 	}
 
-	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(math.Float64bits(o.Value)), tmpReg)
+	c.assembler.CompileConstToRegister(amd64.MOVQ, int64(o.U1) /* math.Float64bits(o.Value) */, tmpReg)
 	c.assembler.CompileRegisterToRegister(amd64.MOVQ, tmpReg, reg)
 	return nil
 }
diff --git a/internal/engine/compiler/impl_arm64.go b/internal/engine/compiler/impl_arm64.go
index 90411be3..53ec3fc9 100644
--- a/internal/engine/compiler/impl_arm64.go
+++ b/internal/engine/compiler/impl_arm64.go
@@ -517,15 +517,17 @@ func (c *arm64Compiler) compileSet(o wazeroir.OperationSet) error {
 }
 
 // compileGlobalGet implements compiler.compileGlobalGet for the arm64 architecture.
-func (c *arm64Compiler) compileGlobalGet(o wazeroir.OperationGlobalGet) error {
+func (c *arm64Compiler) compileGlobalGet(o wazeroir.UnionOperation) error {
 	if err := c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
 		return err
 	}
 
-	wasmValueType := c.ir.Globals[o.Index].ValType
+	index := uint32(o.U1)
+
+	wasmValueType := c.ir.Globals[index].ValType
 	isV128 := wasmValueType == wasm.ValueTypeV128
 	// Get the address of globals[index] into globalAddressReg.
-	globalAddressReg, err := c.compileReadGlobalAddress(o.Index)
+	globalAddressReg, err := c.compileReadGlobalAddress(index)
 	if err != nil {
 		return err
 	}
@@ -582,8 +584,10 @@ func (c *arm64Compiler) compileGlobalGet(o wazeroir.OperationGlobalGet) error {
 }
 
 // compileGlobalSet implements compiler.compileGlobalSet for the arm64 architecture.
-func (c *arm64Compiler) compileGlobalSet(o wazeroir.OperationGlobalSet) error {
-	wasmValueType := c.ir.Globals[o.Index].ValType
+func (c *arm64Compiler) compileGlobalSet(o wazeroir.UnionOperation) error {
+	index := uint32(o.U1)
+
+	wasmValueType := c.ir.Globals[index].ValType
 	isV128 := wasmValueType == wasm.ValueTypeV128
 
 	var val *runtimeValueLocation
@@ -596,7 +600,7 @@ func (c *arm64Compiler) compileGlobalSet(o wazeroir.OperationGlobalSet) error {
 		return err
 	}
 
-	globalInstanceAddressRegister, err := c.compileReadGlobalAddress(o.Index)
+	globalInstanceAddressRegister, err := c.compileReadGlobalAddress(index)
 	if err != nil {
 		return err
 	}
@@ -607,7 +611,7 @@ func (c *arm64Compiler) compileGlobalSet(o wazeroir.OperationGlobalSet) error {
 			arm64.VectorArrangementQ)
 	} else {
 		var str asm.Instruction
-		switch c.ir.Globals[o.Index].ValType {
+		switch c.ir.Globals[index].ValType {
 		case wasm.ValueTypeI32:
 			str = arm64.STRW
 		case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
@@ -924,12 +928,14 @@ func (c *arm64Compiler) compileBrTable(o wazeroir.OperationBrTable) error {
 }
 
 // compileCall implements compiler.compileCall for the arm64 architecture.
-func (c *arm64Compiler) compileCall(o wazeroir.OperationCall) error {
+func (c *arm64Compiler) compileCall(o wazeroir.UnionOperation) error {
 	if err := c.maybeCompileMoveTopConditionalToGeneralPurposeRegister(); err != nil {
 		return err
 	}
 
-	tp := &c.ir.Types[c.ir.Functions[o.FunctionIndex]]
+	functionIndex := o.U1
+
+	tp := &c.ir.Types[c.ir.Functions[functionIndex]]
 
 	targetFunctionAddressReg, err := c.allocateRegister(registerTypeGeneralPurpose)
 	if err != nil {
@@ -948,7 +954,7 @@ func (c *arm64Compiler) compileCall(o wazeroir.OperationCall) error {
 
 	c.assembler.CompileConstToRegister(
 		arm64.ADD,
-		int64(o.FunctionIndex)*functionSize, // * 8 because the size of *function equals 8 bytes.
+		int64(functionIndex)*functionSize, // * 8 because the size of *function equals 8 bytes.
 		targetFunctionAddressReg)
 
 	return c.compileCallImpl(targetFunctionAddressReg, tp)
@@ -2850,13 +2856,13 @@ func (c *arm64Compiler) compileCallGoFunction(compilerStatus nativeCallStatusCod
 }
 
 // compileConstI32 implements compiler.compileConstI32 for the arm64 architecture.
-func (c *arm64Compiler) compileConstI32(o wazeroir.OperationConstI32) error {
-	return c.compileIntConstant(true, uint64(o.Value))
+func (c *arm64Compiler) compileConstI32(o wazeroir.UnionOperation) error {
+	return c.compileIntConstant(true, o.U1)
 }
 
 // compileConstI64 implements compiler.compileConstI64 for the arm64 architecture.
-func (c *arm64Compiler) compileConstI64(o wazeroir.OperationConstI64) error {
-	return c.compileIntConstant(false, o.Value)
+func (c *arm64Compiler) compileConstI64(o wazeroir.UnionOperation) error {
+	return c.compileIntConstant(false, o.U1)
 }
 
 // compileIntConstant adds instructions to load an integer constant.
@@ -2894,13 +2900,13 @@ func (c *arm64Compiler) compileIntConstant(is32bit bool, value uint64) error {
 }
 
 // compileConstF32 implements compiler.compileConstF32 for the arm64 architecture.
-func (c *arm64Compiler) compileConstF32(o wazeroir.OperationConstF32) error {
-	return c.compileFloatConstant(true, uint64(math.Float32bits(o.Value)))
+func (c *arm64Compiler) compileConstF32(o wazeroir.UnionOperation) error {
+	return c.compileFloatConstant(true, o.U1 /*uint64(math.Float32bits(o.Value))*/)
 }
 
 // compileConstF64 implements compiler.compileConstF64 for the arm64 architecture.
-func (c *arm64Compiler) compileConstF64(o wazeroir.OperationConstF64) error {
-	return c.compileFloatConstant(false, math.Float64bits(o.Value))
+func (c *arm64Compiler) compileConstF64(o wazeroir.UnionOperation) error {
+	return c.compileFloatConstant(false, o.U1 /*math.Float64bits(o.Value)*/)
 }
 
 // compileFloatConstant adds instructions to load a float constant.
@@ -3765,7 +3771,7 @@ func (c *arm64Compiler) compileTableGrow(o wazeroir.OperationTableGrow) error {
 	}
 
 	// Pushes the table index.
-	if err := c.compileConstI32(wazeroir.OperationConstI32{Value: o.TableIndex}); err != nil {
+	if err := c.compileConstI32(wazeroir.NewOperationConstI32(o.TableIndex)); err != nil {
 		return err
 	}
 
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index a184a847..2017a18b 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -277,7 +277,12 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 	labelAddress := map[wazeroir.LabelID]uint64{}
 	onLabelAddressResolved := map[wazeroir.LabelID][]func(addr uint64){}
 	for i, original := range ops {
-		op := &wazeroir.UnionOperation{OpKind: original.Kind()}
+		var op *wazeroir.UnionOperation
+		if o, ok := original.(wazeroir.UnionOperation); ok {
+			op = &o
+		} else {
+			op = &wazeroir.UnionOperation{OpKind: original.Kind()}
+		}
 		if hasSourcePCs {
 			op.SourcePC = ir.IROperationSourceOffsetsInWasmBinary[i]
 		}
@@ -285,6 +290,15 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 		case wazeroir.UnionOperation:
 			// Nullary operations don't need any further processing.
 			switch o.Kind() {
+			case wazeroir.OperationKindCall:
+			case wazeroir.OperationKindGlobalGet:
+			case wazeroir.OperationKindGlobalSet:
+
+			case wazeroir.OperationKindConstI32:
+			case wazeroir.OperationKindConstI64:
+			case wazeroir.OperationKindConstF32:
+			case wazeroir.OperationKindConstF64:
+
 			case wazeroir.OperationKindI32ReinterpretFromF32,
 				wazeroir.OperationKindI64ReinterpretFromF64,
 				wazeroir.OperationKindF32ReinterpretFromI32,
@@ -376,8 +390,6 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 					}
 				}
 			}
-		case wazeroir.OperationCall:
-			op.U1 = uint64(o.FunctionIndex)
 		case wazeroir.OperationCallIndirect:
 			op.U1 = uint64(o.TypeIndex)
 			op.U2 = uint64(o.TableIndex)
@@ -392,10 +404,6 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 		case wazeroir.OperationSet:
 			op.U1 = uint64(o.Depth)
 			op.B3 = o.IsTargetVector
-		case wazeroir.OperationGlobalGet:
-			op.U1 = uint64(o.Index)
-		case wazeroir.OperationGlobalSet:
-			op.U1 = uint64(o.Index)
 		case wazeroir.OperationLoad:
 			op.B1 = byte(o.Type)
 			op.U1 = uint64(o.Arg.Alignment)
@@ -427,14 +435,7 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 		case wazeroir.OperationStore32:
 			op.U1 = uint64(o.Arg.Alignment)
 			op.U2 = uint64(o.Arg.Offset)
-		case wazeroir.OperationConstI32:
-			op.U1 = uint64(o.Value)
-		case wazeroir.OperationConstI64:
-			op.U1 = o.Value
-		case wazeroir.OperationConstF32:
-			op.U1 = uint64(math.Float32bits(o.Value))
-		case wazeroir.OperationConstF64:
-			op.U1 = math.Float64bits(o.Value)
+		// const ops...
 		case wazeroir.OperationEq:
 			op.B1 = byte(o.Type)
 		case wazeroir.OperationNe:
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index ab4818ab..04de7d16 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -825,7 +825,7 @@ operatorSwitch:
 		c.markUnreachable()
 	case wasm.OpcodeCall:
 		c.emit(
-			OperationCall{FunctionIndex: index},
+			NewOperationCall(index),
 		)
 	case wasm.OpcodeCallIndirect:
 		tableIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
@@ -913,11 +913,11 @@ operatorSwitch:
 		}
 	case wasm.OpcodeGlobalGet:
 		c.emit(
-			OperationGlobalGet{Index: index},
+			NewOperationGlobalGet(index),
 		)
 	case wasm.OpcodeGlobalSet:
 		c.emit(
-			OperationGlobalSet{Index: index},
+			NewOperationGlobalSet(index),
 		)
 	case wasm.OpcodeI32Load:
 		imm, err := c.readMemoryArg(wasm.OpcodeI32LoadName)
@@ -1108,7 +1108,7 @@ operatorSwitch:
 		}
 		c.pc += num
 		c.emit(
-			OperationConstI32{Value: uint32(val)},
+			NewOperationConstI32(uint32(val)),
 		)
 	case wasm.OpcodeI64Const:
 		val, num, err := leb128.LoadInt64(c.body[c.pc+1:])
@@ -1117,19 +1117,19 @@ operatorSwitch:
 		}
 		c.pc += num
 		c.emit(
-			OperationConstI64{Value: uint64(val)},
+			NewOperationConstI64(uint64(val)),
 		)
 	case wasm.OpcodeF32Const:
 		v := math.Float32frombits(binary.LittleEndian.Uint32(c.body[c.pc+1:]))
 		c.pc += 4
 		c.emit(
-			OperationConstF32{Value: v},
+			NewOperationConstF32(v),
 		)
 	case wasm.OpcodeF64Const:
 		v := math.Float64frombits(binary.LittleEndian.Uint64(c.body[c.pc+1:]))
 		c.pc += 8
 		c.emit(
-			OperationConstF64{Value: v},
+			NewOperationConstF64(v),
 		)
 	case wasm.OpcodeI32Eqz:
 		c.emit(
@@ -1656,7 +1656,7 @@ operatorSwitch:
 	case wasm.OpcodeRefNull:
 		c.pc++ // Skip the type of reftype as every ref value is opaque pointer.
 		c.emit(
-			OperationConstI64{Value: 0},
+			NewOperationConstI64(0),
 		)
 	case wasm.OpcodeRefIsNull:
 		// Simply compare the opaque pointer (i64) with zero.
@@ -3050,16 +3050,16 @@ func (c *compiler) emitDefaultValue(t wasm.ValueType) {
 	switch t {
 	case wasm.ValueTypeI32:
 		c.stackPush(UnsignedTypeI32)
-		c.emit(OperationConstI32{Value: 0})
+		c.emit(NewOperationConstI32(0))
 	case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
 		c.stackPush(UnsignedTypeI64)
-		c.emit(OperationConstI64{Value: 0})
+		c.emit(NewOperationConstI64(0))
 	case wasm.ValueTypeF32:
 		c.stackPush(UnsignedTypeF32)
-		c.emit(OperationConstF32{Value: 0})
+		c.emit(NewOperationConstF32(0))
 	case wasm.ValueTypeF64:
 		c.stackPush(UnsignedTypeF64)
-		c.emit(OperationConstF64{Value: 0})
+		c.emit(NewOperationConstF64(0))
 	case wasm.ValueTypeV128:
 		c.stackPush(UnsignedTypeV128)
 		c.emit(OperationV128Const{Hi: 0, Lo: 0})
diff --git a/internal/wazeroir/operations.go b/internal/wazeroir/operations.go
index 47dd592e..90ce422d 100644
--- a/internal/wazeroir/operations.go
+++ b/internal/wazeroir/operations.go
@@ -2,6 +2,7 @@ package wazeroir
 
 import (
 	"fmt"
+	"math"
 	"strings"
 )
 
@@ -474,13 +475,13 @@ const (
 	OperationKindMemorySize
 	// OperationKindMemoryGrow is the OpKind for OperationMemoryGrow.
 	OperationKindMemoryGrow
-	// OperationKindConstI32 is the OpKind for OperationConstI32.
+	// OperationKindConstI32 is the OpKind for NewOperationConstI32.
 	OperationKindConstI32
-	// OperationKindConstI64 is the OpKind for OperationConstI64.
+	// OperationKindConstI64 is the OpKind for NewOperationConstI64.
 	OperationKindConstI64
-	// OperationKindConstF32 is the OpKind for OperationConstF32.
+	// OperationKindConstF32 is the OpKind for NewOperationConstF32.
 	OperationKindConstF32
-	// OperationKindConstF64 is the OpKind for OperationConstF64.
+	// OperationKindConstF64 is the OpKind for NewOperationConstF64.
 	OperationKindConstF64
 	// OperationKindEq is the OpKind for OperationEq.
 	OperationKindEq
@@ -720,14 +721,11 @@ var (
 	_ Operation = OperationBr{}
 	_ Operation = OperationBrIf{}
 	_ Operation = OperationBrTable{}
-	_ Operation = OperationCall{}
 	_ Operation = OperationCallIndirect{}
 	_ Operation = OperationDrop{}
 	_ Operation = OperationSelect{}
 	_ Operation = OperationPick{}
 	_ Operation = OperationSet{}
-	_ Operation = OperationGlobalGet{}
-	_ Operation = OperationGlobalSet{}
 	_ Operation = OperationLoad{}
 	_ Operation = OperationLoad8{}
 	_ Operation = OperationLoad16{}
@@ -736,10 +734,6 @@ var (
 	_ Operation = OperationStore8{}
 	_ Operation = OperationStore16{}
 	_ Operation = OperationStore32{}
-	_ Operation = OperationConstI32{}
-	_ Operation = OperationConstI64{}
-	_ Operation = OperationConstF32{}
-	_ Operation = OperationConstF64{}
 	_ Operation = OperationEq{}
 	_ Operation = OperationNe{}
 	_ Operation = OperationEqz{}
@@ -943,7 +937,45 @@ type UnionOperation struct {
 }
 
 // String implements fmt.Stringer.
-func (o UnionOperation) String() string { return o.Kind().String() }
+func (o UnionOperation) String() string {
+	switch o.OpKind {
+	case OperationKindUnreachable,
+		OperationKindMemorySize,
+		OperationKindMemoryGrow,
+		OperationKindI32WrapFromI64,
+		OperationKindF32DemoteFromF64,
+		OperationKindF64PromoteFromF32,
+		OperationKindI32ReinterpretFromF32,
+		OperationKindI64ReinterpretFromF64,
+		OperationKindF32ReinterpretFromI32,
+		OperationKindF64ReinterpretFromI64,
+		OperationKindSignExtend32From8,
+		OperationKindSignExtend32From16,
+		OperationKindSignExtend64From8,
+		OperationKindSignExtend64From16,
+		OperationKindSignExtend64From32,
+		OperationKindMemoryCopy,
+		OperationKindMemoryFill,
+		OperationKindBuiltinFunctionCheckExitCode:
+		return o.Kind().String()
+
+	case OperationKindCall,
+		OperationKindGlobalGet,
+		OperationKindGlobalSet:
+		return fmt.Sprintf("%s %d", o.Kind(), o.B1)
+
+	case OperationKindConstI32,
+		OperationKindConstI64:
+		return fmt.Sprintf("%s %#x", o.Kind(), o.U1)
+
+	case OperationKindConstF32:
+		return fmt.Sprintf("%s %f", o.Kind(), math.Float32frombits(uint32(o.U1)))
+	case OperationKindConstF64:
+		return fmt.Sprintf("%s %f", o.Kind(), math.Float64frombits(o.U1))
+	default:
+		panic(fmt.Sprintf("TODO: %v", o.OpKind))
+	}
+}
 
 // Kind implements Operation.Kind
 func (o UnionOperation) Kind() OperationKind {
@@ -1037,22 +1069,12 @@ func (OperationBrTable) Kind() OperationKind {
 	return OperationKindBrTable
 }
 
-// OperationCall implements Operation.
+// NewOperationCall is a constructor for UnionOperation with Kind OperationKindCall.
 //
 // This corresponds to wasm.OpcodeCallName, and engines are expected to
 // enter into a function whose index equals OperationCall.FunctionIndex.
-type OperationCall struct {
-	FunctionIndex uint32
-}
-
-// String implements fmt.Stringer.
-func (o OperationCall) String() string {
-	return fmt.Sprintf("%s %d", o.Kind(), o.FunctionIndex)
-}
-
-// Kind implements Operation.Kind
-func (OperationCall) Kind() OperationKind {
-	return OperationKindCall
+func NewOperationCall(functionIndex uint32) UnionOperation {
+	return UnionOperation{OpKind: OperationKindCall, U1: uint64(functionIndex)}
 }
 
 // OperationCallIndirect implements Operation.
@@ -1167,40 +1189,24 @@ func (OperationSet) Kind() OperationKind {
 	return OperationKindSet
 }
 
-// OperationGlobalGet implements Operation.
+// NewOperationGlobalGet is a constructor for UnionOperation with Kind OperationKindGlobalGet.
 //
 // The engines are expected to read the global value specified by OperationGlobalGet.Index,
 // and push the copy of the value onto the stack.
 //
 // See wasm.OpcodeGlobalGet.
-type OperationGlobalGet struct{ Index uint32 }
-
-// String implements fmt.Stringer.
-func (o OperationGlobalGet) String() string {
-	return fmt.Sprintf("%s %d", o.Kind(), o.Index)
+func NewOperationGlobalGet(index uint32) UnionOperation {
+	return UnionOperation{OpKind: OperationKindGlobalGet, U1: uint64(index)}
 }
 
-// Kind implements Operation.Kind
-func (OperationGlobalGet) Kind() OperationKind {
-	return OperationKindGlobalGet
-}
-
-// OperationGlobalSet implements Operation.
+// NewOperationGlobalSet is a constructor for UnionOperation with Kind OperationKindGlobalSet.
 //
 // The engines are expected to consume the value from the top of the stack,
 // and write the value into the global specified by OperationGlobalSet.Index.
 //
 // See wasm.OpcodeGlobalSet.
-type OperationGlobalSet struct{ Index uint32 }
-
-// String implements fmt.Stringer.
-func (o OperationGlobalSet) String() string {
-	return fmt.Sprintf("%s %d", o.Kind(), o.Index)
-}
-
-// Kind implements Operation.Kind
-func (OperationGlobalSet) Kind() OperationKind {
-	return OperationKindGlobalSet
+func NewOperationGlobalSet(index uint32) UnionOperation {
+	return UnionOperation{OpKind: OperationKindGlobalSet, U1: uint64(index)}
 }
 
 // MemoryArg is the "memarg" to all memory instructions.
@@ -1409,64 +1415,32 @@ func NewOperationMemoryGrow() UnionOperation {
 	return UnionOperation{OpKind: OperationKindMemoryGrow}
 }
 
-// OperationConstI32 implements Operation.
+// NewOperationConstI32 is a constructor for UnionOperation with Kind OperationConstI32.
 //
 // This corresponds to wasm.OpcodeI32Const.
-type OperationConstI32 struct{ Value uint32 }
-
-// String implements fmt.Stringer.
-func (o OperationConstI32) String() string {
-	return fmt.Sprintf("%s %#x", o.Kind(), o.Value)
-}
-
-// Kind implements Operation.Kind.
-func (OperationConstI32) Kind() OperationKind {
-	return OperationKindConstI32
+func NewOperationConstI32(value uint32) UnionOperation {
+	return UnionOperation{OpKind: OperationKindConstI32, U1: uint64(value)}
 }
 
-// OperationConstI64 implements Operation.
+// NewOperationConstI64 is a constructor for UnionOperation with Kind OperationConstI64.
 //
 // This corresponds to wasm.OpcodeI64Const.
-type OperationConstI64 struct{ Value uint64 }
-
-// String implements fmt.Stringer.
-func (o OperationConstI64) String() string {
-	return fmt.Sprintf("%s %#x", o.Kind(), o.Value)
-}
-
-// Kind implements Operation.Kind.
-func (OperationConstI64) Kind() OperationKind {
-	return OperationKindConstI64
+func NewOperationConstI64(value uint64) UnionOperation {
+	return UnionOperation{OpKind: OperationKindConstI64, U1: value}
 }
 
-// OperationConstF32 implements Operation.
+// NewOperationConstF32 is a constructor for UnionOperation with Kind OperationConstF32.
 //
 // This corresponds to wasm.OpcodeF32Const.
-type OperationConstF32 struct{ Value float32 }
-
-// String implements fmt.Stringer.
-func (o OperationConstF32) String() string {
-	return fmt.Sprintf("%s %f", o.Kind(), o.Value)
-}
-
-// Kind implements Operation.Kind.
-func (OperationConstF32) Kind() OperationKind {
-	return OperationKindConstF32
+func NewOperationConstF32(value float32) UnionOperation {
+	return UnionOperation{OpKind: OperationKindConstF32, U1: uint64(math.Float32bits(value))}
 }
 
-// OperationConstF64 implements Operation.
+// NewOperationConstF64 is a constructor for UnionOperation with Kind OperationConstF64.
 //
 // This corresponds to wasm.OpcodeF64Const.
-type OperationConstF64 struct{ Value float64 }
-
-// String implements fmt.Stringer.
-func (o OperationConstF64) String() string {
-	return fmt.Sprintf("%s %f", o.Kind(), o.Value)
-}
-
-// Kind implements Operation.Kind.
-func (OperationConstF64) Kind() OperationKind {
-	return OperationKindConstF64
+func NewOperationConstF64(value float64) UnionOperation {
+	return UnionOperation{OpKind: OperationKindConstF64, U1: math.Float64bits(value)}
 }
 
 // OperationEq implements Operation.


Test patch:
diff --git a/internal/engine/compiler/compiler_bench_test.go b/internal/engine/compiler/compiler_bench_test.go
index 04557e77..f5e49c0a 100644
--- a/internal/engine/compiler/compiler_bench_test.go
+++ b/internal/engine/compiler/compiler_bench_test.go
@@ -37,11 +37,11 @@ func BenchmarkCompiler_compileMemoryCopy(b *testing.B) {
 					destOffset, sourceOffset = 777, 1
 				}
 
-				err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: destOffset})
+				err = compiler.compileConstI32(wazeroir.NewOperationConstI32(destOffset))
 				requireNoError(b, err)
-				err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: sourceOffset})
+				err = compiler.compileConstI32(wazeroir.NewOperationConstI32(sourceOffset))
 				requireNoError(b, err)
-				err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: size})
+				err = compiler.compileConstI32(wazeroir.NewOperationConstI32(size))
 				requireNoError(b, err)
 				err = compiler.compileMemoryCopy()
 				requireNoError(b, err)
@@ -84,11 +84,11 @@ func BenchmarkCompiler_compileMemoryFill(b *testing.B) {
 			var startOffset uint32 = 100
 			var value uint8 = 5
 
-			err := compiler.compileConstI32(wazeroir.OperationConstI32{Value: startOffset})
+			err := compiler.compileConstI32(wazeroir.NewOperationConstI32(startOffset))
 			requireNoError(b, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(value)})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(value)))
 			requireNoError(b, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: size})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(size))
 			requireNoError(b, err)
 			err = compiler.compileMemoryFill()
 			requireNoError(b, err)
diff --git a/internal/engine/compiler/compiler_conditional_save_test.go b/internal/engine/compiler/compiler_conditional_save_test.go
index 3828c999..eb87fc20 100644
--- a/internal/engine/compiler/compiler_conditional_save_test.go
+++ b/internal/engine/compiler/compiler_conditional_save_test.go
@@ -16,12 +16,12 @@ func TestCompiler_conditional_value_saving(t *testing.T) {
 	require.NoError(t, err)
 
 	// Place the f32 local.
-	err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: 1.0})
+	err = compiler.compileConstF32(wazeroir.NewOperationConstF32(1.0))
 	require.NoError(t, err)
 
 	// Generate constants to occupy all the unreserved GP registers.
 	for i := 0; i < len(unreservedGeneralPurposeRegisters); i++ {
-		err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: 100})
+		err = compiler.compileConstI32(wazeroir.NewOperationConstI32(100))
 		require.NoError(t, err)
 	}
 
diff --git a/internal/engine/compiler/compiler_controlflow_test.go b/internal/engine/compiler/compiler_controlflow_test.go
index ab786074..7e297e2e 100644
--- a/internal/engine/compiler/compiler_controlflow_test.go
+++ b/internal/engine/compiler/compiler_controlflow_test.go
@@ -84,7 +84,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				if shouldGoElse {
 					val = 0
 				}
-				err := compiler.compileConstI32(wazeroir.OperationConstI32{Value: val})
+				err := compiler.compileConstI32(wazeroir.NewOperationConstI32(val))
 				require.NoError(t, err)
 			},
 		},
@@ -295,7 +295,7 @@ func TestCompiler_compileBrTable(t *testing.T) {
 			err := c.compileBr(wazeroir.OperationBr{Target: label})
 			require.NoError(t, err)
 			_ = c.compileLabel(wazeroir.OperationLabel{Label: label})
-			_ = c.compileConstI32(wazeroir.OperationConstI32{Value: label.FrameID})
+			_ = c.compileConstI32(wazeroir.NewOperationConstI32(label.FrameID))
 			err = c.compileReturnFunction()
 			require.NoError(t, err)
 		}
@@ -438,7 +438,7 @@ func TestCompiler_compileBrTable(t *testing.T) {
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(tc.index)})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(tc.index)))
 			require.NoError(t, err)
 
 			err = compiler.compileBrTable(tc.o)
@@ -452,16 +452,16 @@ func TestCompiler_compileBrTable(t *testing.T) {
 }
 
 func requirePushTwoInt32Consts(t *testing.T, x1, x2 uint32, compiler compilerImpl) {
-	err := compiler.compileConstI32(wazeroir.OperationConstI32{Value: x1})
+	err := compiler.compileConstI32(wazeroir.NewOperationConstI32(x1))
 	require.NoError(t, err)
-	err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: x2})
+	err = compiler.compileConstI32(wazeroir.NewOperationConstI32(x2))
 	require.NoError(t, err)
 }
 
 func requirePushTwoFloat32Consts(t *testing.T, x1, x2 float32, compiler compilerImpl) {
-	err := compiler.compileConstF32(wazeroir.OperationConstF32{Value: x1})
+	err := compiler.compileConstF32(wazeroir.NewOperationConstF32(x1))
 	require.NoError(t, err)
-	err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: x2})
+	err = compiler.compileConstF32(wazeroir.NewOperationConstF32(x2))
 	require.NoError(t, err)
 }
 
@@ -550,7 +550,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 		targetOperation := wazeroir.OperationCallIndirect{}
 
 		// Place the offset value.
-		err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: 10})
+		err = compiler.compileConstI32(wazeroir.NewOperationConstI32(10))
 		require.NoError(t, err)
 
 		err = compiler.compileCallIndirect(targetOperation)
@@ -578,7 +578,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 		require.NoError(t, err)
 
 		targetOperation := wazeroir.OperationCallIndirect{}
-		targetOffset := wazeroir.OperationConstI32{Value: uint32(0)}
+		targetOffset := wazeroir.NewOperationConstI32(uint32(0))
 
 		// and the typeID doesn't match the table[targetOffset]'s type ID.
 		table := make([]wasm.Reference, 10)
@@ -614,7 +614,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 		require.NoError(t, err)
 
 		targetOperation := wazeroir.OperationCallIndirect{}
-		targetOffset := wazeroir.OperationConstI32{Value: uint32(0)}
+		targetOffset := wazeroir.NewOperationConstI32(uint32(0))
 		env.module().TypeIDs = []wasm.FunctionTypeID{1000}
 		// Ensure that the module instance has the type information for targetOperation.TypeIndex,
 		// and the typeID doesn't match the table[targetOffset]'s type ID.
@@ -673,7 +673,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 			})
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: expectedReturnValue})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(expectedReturnValue))
 			require.NoError(t, err)
 
 			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
@@ -712,7 +712,7 @@ func TestCompiler_compileCallIndirect(t *testing.T) {
 				require.NoError(t, err)
 
 				// Place the offset value. Here we try calling a function of functionaddr == table[i].FunctionIndex.
-				err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(i)})
+				err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(i)))
 				require.NoError(t, err)
 
 				// At this point, we should have one item (offset value) on the stack.
@@ -785,7 +785,7 @@ func TestCompiler_callIndirect_largeTypeIndex(t *testing.T) {
 	err := compiler.compilePreamble()
 	require.NoError(t, err)
 
-	err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: 0})
+	err = compiler.compileConstI32(wazeroir.NewOperationConstI32(0))
 	require.NoError(t, err)
 
 	require.NoError(t, compiler.compileCallIndirect(operation))
@@ -822,7 +822,7 @@ func TestCompiler_compileCall(t *testing.T) {
 		err := compiler.compilePreamble()
 		require.NoError(t, err)
 
-		err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: addTargetValue})
+		err = compiler.compileConstI32(wazeroir.NewOperationConstI32(addTargetValue))
 		require.NoError(t, err)
 		// Picks the function argument placed at the bottom of the stack.
 		err = compiler.compilePick(wazeroir.OperationPick{Depth: int(compiler.runtimeValueLocationStack().sp - 1)})
@@ -858,14 +858,14 @@ func TestCompiler_compileCall(t *testing.T) {
 
 	const initialValue = 100
 	expectedValue += initialValue
-	err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: 1234}) // Dummy value so the base pointer would be non-trivial for callees.
+	err = compiler.compileConstI32(wazeroir.NewOperationConstI32(1234)) // Dummy value so the base pointer would be non-trivial for callees.
 	require.NoError(t, err)
-	err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: initialValue})
+	err = compiler.compileConstI32(wazeroir.NewOperationConstI32(initialValue))
 	require.NoError(t, err)
 
 	// Call all the built functions.
 	for i := 0; i < numCalls; i++ {
-		err = compiler.compileCall(wazeroir.OperationCall{FunctionIndex: uint32(i)})
+		err = compiler.compileCall(wazeroir.NewOperationCall(1))
 		require.NoError(t, err)
 	}
 
diff --git a/internal/engine/compiler/compiler_conversion_test.go b/internal/engine/compiler/compiler_conversion_test.go
index 596c231f..5d101513 100644
--- a/internal/engine/compiler/compiler_conversion_test.go
+++ b/internal/engine/compiler/compiler_conversion_test.go
@@ -43,14 +43,14 @@ func TestCompiler_compileReinterpret(t *testing.T) {
 							case wazeroir.OperationKindF32ReinterpretFromI32:
 								is32Bit = true
 								if !originOnStack {
-									err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(v)})
+									err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(v)))
 									require.NoError(t, err)
 								}
 								err = compiler.compileF32ReinterpretFromI32()
 								require.NoError(t, err)
 							case wazeroir.OperationKindF64ReinterpretFromI64:
 								if !originOnStack {
-									err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: v})
+									err = compiler.compileConstI64(wazeroir.NewOperationConstI64(v))
 									require.NoError(t, err)
 								}
 								err = compiler.compileF64ReinterpretFromI64()
@@ -58,14 +58,14 @@ func TestCompiler_compileReinterpret(t *testing.T) {
 							case wazeroir.OperationKindI32ReinterpretFromF32:
 								is32Bit = true
 								if !originOnStack {
-									err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
+									err = compiler.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(v))))
 									require.NoError(t, err)
 								}
 								err = compiler.compileI32ReinterpretFromF32()
 								require.NoError(t, err)
 							case wazeroir.OperationKindI64ReinterpretFromF64:
 								if !originOnStack {
-									err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
+									err = compiler.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(v)))
 									require.NoError(t, err)
 								}
 								err = compiler.compileI64ReinterpretFromF64()
@@ -111,7 +111,7 @@ func TestCompiler_compileExtend(t *testing.T) {
 					require.NoError(t, err)
 
 					// Setup the promote target.
-					err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: v})
+					err = compiler.compileConstI32(wazeroir.NewOperationConstI32(v))
 					require.NoError(t, err)
 
 					err = compiler.compileExtend(wazeroir.OperationExtend{Signed: signed})
@@ -194,9 +194,9 @@ func TestCompiler_compileITruncFromF(t *testing.T) {
 
 					// Setup the conversion target.
 					if tc.inputType == wazeroir.Float32 {
-						err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: float32(v)})
+						err = compiler.compileConstF32(wazeroir.NewOperationConstF32(float32(v)))
 					} else {
-						err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: v})
+						err = compiler.compileConstF64(wazeroir.NewOperationConstF64(v))
 					}
 					require.NoError(t, err)
 
@@ -393,9 +393,9 @@ func TestCompiler_compileFConvertFromI(t *testing.T) {
 
 					// Setup the conversion target.
 					if tc.inputType == wazeroir.SignedInt32 || tc.inputType == wazeroir.SignedUint32 {
-						err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(v)})
+						err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(v)))
 					} else {
-						err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: uint64(v)})
+						err = compiler.compileConstI64(wazeroir.NewOperationConstI64(uint64(v)))
 					}
 					require.NoError(t, err)
 
@@ -469,7 +469,7 @@ func TestCompiler_compileF64PromoteFromF32(t *testing.T) {
 			require.NoError(t, err)
 
 			// Setup the promote target.
-			err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: v})
+			err = compiler.compileConstF32(wazeroir.NewOperationConstF32(v))
 			require.NoError(t, err)
 
 			err = compiler.compileF64PromoteFromF32()
@@ -515,7 +515,7 @@ func TestCompiler_compileF32DemoteFromF64(t *testing.T) {
 			require.NoError(t, err)
 
 			// Setup the demote target.
-			err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: v})
+			err = compiler.compileConstF64(wazeroir.NewOperationConstF64(v))
 			require.NoError(t, err)
 
 			err = compiler.compileF32DemoteFromF64()
diff --git a/internal/engine/compiler/compiler_global_test.go b/internal/engine/compiler/compiler_global_test.go
index 9a4e841a..ca4e0cad 100644
--- a/internal/engine/compiler/compiler_global_test.go
+++ b/internal/engine/compiler/compiler_global_test.go
@@ -28,7 +28,7 @@ func TestCompiler_compileGlobalGet(t *testing.T) {
 			// Emit the code.
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
-			op := wazeroir.OperationGlobalGet{Index: 1}
+			op := wazeroir.NewOperationGlobalGet(1)
 			err = compiler.compileGlobalGet(op)
 			require.NoError(t, err)
 
@@ -75,7 +75,7 @@ func TestCompiler_compileGlobalGet_v128(t *testing.T) {
 	// Emit the code.
 	err := compiler.compilePreamble()
 	require.NoError(t, err)
-	op := wazeroir.OperationGlobalGet{Index: 1}
+	op := wazeroir.NewOperationGlobalGet(1)
 	err = compiler.compileGlobalGet(op)
 	require.NoError(t, err)
 
@@ -140,7 +140,8 @@ func TestCompiler_compileGlobalSet(t *testing.T) {
 			}
 			env.stack()[loc.stackPointer] = valueToSet
 
-			op := wazeroir.OperationGlobalSet{Index: 1}
+			const index = 1
+			op := wazeroir.NewOperationGlobalSet(index)
 			err = compiler.compileGlobalSet(op)
 			requireRuntimeLocationStackPointerEqual(t, 0, compiler)
 
@@ -155,7 +156,7 @@ func TestCompiler_compileGlobalSet(t *testing.T) {
 			env.exec(code)
 
 			// The global value should be set to valueToSet.
-			actual := env.globals()[op.Index]
+			actual := env.globals()[index]
 			require.Equal(t, valueToSet, actual.Val)
 			// Plus we consumed the top of the stack, the stack pointer must be decremented.
 			require.Equal(t, uint64(0), env.stackPointer())
@@ -187,7 +188,8 @@ func TestCompiler_compileGlobalSet_v128(t *testing.T) {
 	hi.valueType = runtimeValueTypeV128Hi
 	env.stack()[hi.stackPointer] = valueToSetHi
 
-	op := wazeroir.OperationGlobalSet{Index: 1}
+	const index = 1
+	op := wazeroir.NewOperationGlobalSet(index)
 	err = compiler.compileGlobalSet(op)
 	requireRuntimeLocationStackPointerEqual(t, 0, compiler)
 	require.NoError(t, err)
@@ -204,7 +206,7 @@ func TestCompiler_compileGlobalSet_v128(t *testing.T) {
 	require.Equal(t, nativeCallStatusCodeReturned, env.callEngine().statusCode)
 
 	// The global value should be set to valueToSet.
-	actual := env.globals()[op.Index]
+	actual := env.globals()[index]
 	require.Equal(t, valueToSetLo, actual.Val)
 	require.Equal(t, valueToSetHi, actual.ValHi)
 }
diff --git a/internal/engine/compiler/compiler_memory_test.go b/internal/engine/compiler/compiler_memory_test.go
index d1e90f9f..ff92556f 100644
--- a/internal/engine/compiler/compiler_memory_test.go
+++ b/internal/engine/compiler/compiler_memory_test.go
@@ -24,7 +24,7 @@ func TestCompiler_compileMemoryGrow(t *testing.T) {
 	// Emit arbitrary code after MemoryGrow returned so that we can verify
 	// that the code can set the return address properly.
 	const expValue uint32 = 100
-	err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: expValue})
+	err = compiler.compileConstI32(wazeroir.NewOperationConstI32(expValue))
 	require.NoError(t, err)
 	err = compiler.compileReturnFunction()
 	require.NoError(t, err)
@@ -244,7 +244,7 @@ func TestCompiler_compileLoad(t *testing.T) {
 			binary.LittleEndian.PutUint64(env.memory()[offset:], loadTargetValue)
 
 			// Before load operation, we must push the base offset value.
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: baseOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(baseOffset))
 			require.NoError(t, err)
 
 			tc.operationSetupFn(t, compiler)
@@ -379,12 +379,12 @@ func TestCompiler_compileStore(t *testing.T) {
 			require.NoError(t, err)
 
 			// Before store operations, we must push the base offset, and the store target values.
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: baseOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(baseOffset))
 			require.NoError(t, err)
 			if tc.isFloatTarget {
-				err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(storeTargetValue)})
+				err = compiler.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(storeTargetValue)))
 			} else {
-				err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: storeTargetValue})
+				err = compiler.compileConstI64(wazeroir.NewOperationConstI64(storeTargetValue))
 			}
 			require.NoError(t, err)
 
@@ -443,7 +443,7 @@ func TestCompiler_MemoryOutOfBounds(t *testing.T) {
 					err := compiler.compilePreamble()
 					require.NoError(t, err)
 
-					err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: base})
+					err = compiler.compileConstI32(wazeroir.NewOperationConstI32(base))
 					require.NoError(t, err)
 
 					arg := wazeroir.MemoryArg{Offset: offset}
diff --git a/internal/engine/compiler/compiler_numeric_test.go b/internal/engine/compiler/compiler_numeric_test.go
index 167ce515..fecc50f9 100644
--- a/internal/engine/compiler/compiler_numeric_test.go
+++ b/internal/engine/compiler/compiler_numeric_test.go
@@ -44,13 +44,13 @@ func TestCompiler_compileConsts(t *testing.T) {
 
 					switch op {
 					case wazeroir.OperationKindConstI32:
-						err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(val)})
+						err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(val)))
 					case wazeroir.OperationKindConstI64:
-						err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: val})
+						err = compiler.compileConstI64(wazeroir.NewOperationConstI64(val))
 					case wazeroir.OperationKindConstF32:
-						err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(val))})
+						err = compiler.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(val))))
 					case wazeroir.OperationKindConstF64:
-						err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(val)})
+						err = compiler.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(val)))
 					case wazeroir.OperationKindV128Const:
 						err = compiler.compileV128Const(wazeroir.OperationV128Const{Lo: val, Hi: ^val})
 					}
@@ -152,13 +152,13 @@ func TestCompiler_compile_Add_Sub_Mul(t *testing.T) {
 							for _, v := range []uint64{x1, x2} {
 								switch unsignedType {
 								case wazeroir.UnsignedTypeI32:
-									err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(v)})
+									err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(v)))
 								case wazeroir.UnsignedTypeI64:
-									err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: v})
+									err = compiler.compileConstI64(wazeroir.NewOperationConstI64(v))
 								case wazeroir.UnsignedTypeF32:
-									err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
+									err = compiler.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(v))))
 								case wazeroir.UnsignedTypeF64:
-									err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
+									err = compiler.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(v)))
 								}
 								require.NoError(t, err)
 							}
@@ -323,16 +323,16 @@ func TestCompiler_compile_And_Or_Xor_Shl_Rotl_Rotr(t *testing.T) {
 								var x1Location *runtimeValueLocation
 								switch unsignedInt {
 								case wazeroir.UnsignedInt32:
-									err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(x1)})
+									err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(x1)))
 									require.NoError(t, err)
 									x1Location = compiler.runtimeValueLocationStack().peek()
-									err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: x2})
+									err = compiler.compileConstI64(wazeroir.NewOperationConstI64(x2))
 									require.NoError(t, err)
 								case wazeroir.UnsignedInt64:
-									err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: x1})
+									err = compiler.compileConstI64(wazeroir.NewOperationConstI64(x1))
 									require.NoError(t, err)
 									x1Location = compiler.runtimeValueLocationStack().peek()
-									err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: x2})
+									err = compiler.compileConstI64(wazeroir.NewOperationConstI64(x2))
 									require.NoError(t, err)
 								}
 
@@ -464,13 +464,13 @@ func TestCompiler_compileShr(t *testing.T) {
 						for _, v := range []uint64{x1, x2} {
 							switch signedInt {
 							case wazeroir.SignedInt32:
-								err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(int32(v))})
+								err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(int32(v))))
 							case wazeroir.SignedInt64:
-								err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: v})
+								err = compiler.compileConstI64(wazeroir.NewOperationConstI64(v))
 							case wazeroir.SignedUint32:
-								err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(v)})
+								err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(v)))
 							case wazeroir.SignedUint64:
-								err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: v})
+								err = compiler.compileConstI64(wazeroir.NewOperationConstI64(v))
 							}
 							require.NoError(t, err)
 						}
@@ -593,15 +593,15 @@ func TestCompiler_compile_Le_Lt_Gt_Ge_Eq_Eqz_Ne(t *testing.T) {
 							for _, v := range []uint64{x1, x2} {
 								switch signedType {
 								case wazeroir.SignedTypeUint32:
-									err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(v)})
+									err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(v)))
 								case wazeroir.SignedTypeInt32:
-									err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(int32(v))})
+									err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(int32(v))))
 								case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
-									err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: v})
+									err = compiler.compileConstI64(wazeroir.NewOperationConstI64(v))
 								case wazeroir.SignedTypeFloat32:
-									err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
+									err = compiler.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(v))))
 								case wazeroir.SignedTypeFloat64:
-									err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
+									err = compiler.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(v)))
 								}
 								require.NoError(t, err)
 							}
@@ -802,9 +802,9 @@ func TestCompiler_compile_Clz_Ctz_Popcnt(t *testing.T) {
 							require.NoError(t, err)
 
 							if is32bit {
-								err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(v)})
+								err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(v)))
 							} else {
-								err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: v})
+								err = compiler.compileConstI64(wazeroir.NewOperationConstI64(v))
 							}
 							require.NoError(t, err)
 
@@ -1014,14 +1014,14 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 
 					// Setup the target values.
 					if tc.is32bit {
-						err := compiler.compileConstF32(wazeroir.OperationConstF32{Value: float32(x1)})
+						err := compiler.compileConstF32(wazeroir.NewOperationConstF32(float32(x1)))
 						require.NoError(t, err)
-						err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: float32(x2)})
+						err = compiler.compileConstF32(wazeroir.NewOperationConstF32(float32(x2)))
 						require.NoError(t, err)
 					} else {
-						err := compiler.compileConstF64(wazeroir.OperationConstF64{Value: x1})
+						err := compiler.compileConstF64(wazeroir.NewOperationConstF64(x1))
 						require.NoError(t, err)
-						err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: x2})
+						err = compiler.compileConstF64(wazeroir.NewOperationConstF64(x2))
 						require.NoError(t, err)
 					}
 
@@ -1321,10 +1321,10 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 					require.NoError(t, err)
 
 					if tc.is32bit {
-						err := compiler.compileConstF32(wazeroir.OperationConstF32{Value: float32(v)})
+						err := compiler.compileConstF32(wazeroir.NewOperationConstF32(float32(v)))
 						require.NoError(t, err)
 					} else {
-						err := compiler.compileConstF64(wazeroir.OperationConstF64{Value: v})
+						err := compiler.compileConstF64(wazeroir.NewOperationConstF64(v))
 						require.NoError(t, err)
 					}
 
@@ -1446,13 +1446,13 @@ func TestCompiler_compile_Div_Rem(t *testing.T) {
 									require.NoError(t, err)
 									env.stack()[loc.stackPointer] = uint64(v)
 								case wazeroir.SignedTypeInt32:
-									err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(int32(v))})
+									err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(int32(v))))
 								case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
-									err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: v})
+									err = compiler.compileConstI64(wazeroir.NewOperationConstI64(v))
 								case wazeroir.SignedTypeFloat32:
-									err = compiler.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
+									err = compiler.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(v))))
 								case wazeroir.SignedTypeFloat64:
-									err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
+									err = compiler.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(v)))
 								}
 								require.NoError(t, err)
 							}
diff --git a/internal/engine/compiler/compiler_post1_0_test.go b/internal/engine/compiler/compiler_post1_0_test.go
index b1b7763b..40515a61 100644
--- a/internal/engine/compiler/compiler_post1_0_test.go
+++ b/internal/engine/compiler/compiler_post1_0_test.go
@@ -49,7 +49,7 @@ func TestCompiler_compileSignExtend(t *testing.T) {
 				require.NoError(t, err)
 
 				// Setup the promote target.
-				err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: uint32(tc.in)})
+				err = compiler.compileConstI32(wazeroir.NewOperationConstI32(uint32(tc.in)))
 				require.NoError(t, err)
 
 				if tc.fromKind == from8 {
@@ -120,7 +120,7 @@ func TestCompiler_compileSignExtend(t *testing.T) {
 				require.NoError(t, err)
 
 				// Setup the promote target.
-				err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: uint64(tc.in)})
+				err = compiler.compileConstI64(wazeroir.NewOperationConstI64(uint64(tc.in)))
 				require.NoError(t, err)
 
 				if tc.fromKind == from8 {
@@ -201,11 +201,11 @@ func TestCompiler_compileMemoryCopy(t *testing.T) {
 			require.NoError(t, err)
 
 			// Compile operands.
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.destOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.destOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.sourceOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.sourceOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.size})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.size))
 			require.NoError(t, err)
 
 			err = compiler.compileMemoryCopy()
@@ -285,11 +285,11 @@ func TestCompiler_compileMemoryFill(t *testing.T) {
 			require.NoError(t, err)
 
 			// Compile operands.
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.destOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.destOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.v})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.v))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.size})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.size))
 			require.NoError(t, err)
 
 			err = compiler.compileMemoryFill()
@@ -426,11 +426,11 @@ func TestCompiler_compileMemoryInit(t *testing.T) {
 			require.NoError(t, err)
 
 			// Compile operands.
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.destOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.destOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.sourceOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.sourceOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.copySize})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.copySize))
 			require.NoError(t, err)
 
 			err = compiler.compileMemoryInit(wazeroir.OperationMemoryInit{
@@ -563,11 +563,11 @@ func TestCompiler_compileTableCopy(t *testing.T) {
 			require.NoError(t, err)
 
 			// Compile operands.
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.destOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.destOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.sourceOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.sourceOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.size})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.size))
 			require.NoError(t, err)
 
 			err = compiler.compileTableCopy(wazeroir.OperationTableCopy{})
@@ -655,11 +655,11 @@ func TestCompiler_compileTableInit(t *testing.T) {
 			require.NoError(t, err)
 
 			// Compile operands.
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.destOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.destOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.sourceOffset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.sourceOffset))
 			require.NoError(t, err)
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.copySize})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.copySize))
 			require.NoError(t, err)
 
 			err = compiler.compileTableInit(wazeroir.OperationTableInit{
@@ -781,10 +781,10 @@ func TestCompiler_compileTableSet(t *testing.T) {
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.offset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.offset))
 			require.NoError(t, err)
 
-			err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: uint64(tc.in)})
+			err = compiler.compileConstI64(wazeroir.NewOperationConstI64(uint64(tc.in)))
 			require.NoError(t, err)
 
 			err = compiler.compileTableSet(wazeroir.OperationTableSet{TableIndex: tc.tableIndex})
@@ -913,7 +913,7 @@ func TestCompiler_compileTableGet(t *testing.T) {
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.offset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.offset))
 			require.NoError(t, err)
 
 			err = compiler.compileTableGet(wazeroir.OperationTableGet{TableIndex: tc.tableIndex})
diff --git a/internal/engine/compiler/compiler_stack_test.go b/internal/engine/compiler/compiler_stack_test.go
index 45609a3b..46b97bde 100644
--- a/internal/engine/compiler/compiler_stack_test.go
+++ b/internal/engine/compiler/compiler_stack_test.go
@@ -45,9 +45,9 @@ func TestCompiler_releaseRegisterToStack(t *testing.T) {
 			compiler.setRuntimeValueLocationStack(s)
 
 			if tc.isFloat {
-				err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(val)})
+				err = compiler.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(val)))
 			} else {
-				err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: val})
+				err = compiler.compileConstI64(wazeroir.NewOperationConstI64(val))
 			}
 			require.NoError(t, err)
 			// Release the register allocated value to the memory stack so that we can see the value after exiting.
@@ -120,12 +120,12 @@ func TestCompiler_compileLoadValueOnStackToRegister(t *testing.T) {
 
 			// To verify the behavior, increment the value on the register.
 			if tc.isFloat {
-				err = compiler.compileConstF64(wazeroir.OperationConstF64{Value: 1})
+				err = compiler.compileConstF64(wazeroir.NewOperationConstF64(1))
 				require.NoError(t, err)
 				err = compiler.compileAdd(wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF64})
 				require.NoError(t, err)
 			} else {
-				err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: 1})
+				err = compiler.compileConstI64(wazeroir.NewOperationConstI64(1))
 				require.NoError(t, err)
 				err = compiler.compileAdd(wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64})
 				require.NoError(t, err)
@@ -242,7 +242,7 @@ func TestCompiler_compilePick(t *testing.T) {
 		{
 			name: "float on register",
 			pickTargetSetupFunc: func(compiler compilerImpl, _ *callEngine) error {
-				return compiler.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(pickTargetValue)})
+				return compiler.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(pickTargetValue)))
 			},
 			isPickTargetFloat:      true,
 			isPickTargetOnRegister: true,
@@ -250,7 +250,7 @@ func TestCompiler_compilePick(t *testing.T) {
 		{
 			name: "int on register",
 			pickTargetSetupFunc: func(compiler compilerImpl, _ *callEngine) error {
-				return compiler.compileConstI64(wazeroir.OperationConstI64{Value: pickTargetValue})
+				return compiler.compileConstI64(wazeroir.NewOperationConstI64(pickTargetValue))
 			},
 			isPickTargetFloat:      false,
 			isPickTargetOnRegister: true,
@@ -374,7 +374,7 @@ func TestCompiler_compileDrop(t *testing.T) {
 		const expectedTopLiveValue = 100
 		for i := 0; i < liveNum+dropTargetNum; i++ {
 			if i == liveNum-1 {
-				err := compiler.compileConstI64(wazeroir.OperationConstI64{Value: expectedTopLiveValue})
+				err := compiler.compileConstI64(wazeroir.NewOperationConstI64(expectedTopLiveValue))
 				require.NoError(t, err)
 			} else {
 				compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
@@ -432,7 +432,7 @@ func TestCompiler_compileDrop(t *testing.T) {
 
 		// Place the top value.
 		const expectedTopLiveValue = 100
-		err = compiler.compileConstI64(wazeroir.OperationConstI64{Value: expectedTopLiveValue})
+		err = compiler.compileConstI64(wazeroir.NewOperationConstI64(expectedTopLiveValue))
 		require.NoError(t, err)
 
 		require.Equal(t, uint64(total), compiler.runtimeValueLocationStack().sp)
@@ -569,9 +569,9 @@ func TestCompiler_compileSelect(t *testing.T) {
 						err = compiler.compileEnsureOnRegister(c)
 						require.NoError(t, err)
 					} else if tc.condValueOnCondRegister {
-						err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: 0})
+						err = compiler.compileConstI32(wazeroir.NewOperationConstI32(0))
 						require.NoError(t, err)
-						err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: 0})
+						err = compiler.compileConstI32(wazeroir.NewOperationConstI32(0))
 						require.NoError(t, err)
 						if tc.selectX1 {
 							err = compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
@@ -722,9 +722,9 @@ func TestCompiler_compileSet(t *testing.T) {
 				x1.valueType = runtimeValueTypeI32
 				env.stack()[x1.stackPointer] = uint64(x1Value)
 			} else {
-				err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: 0})
+				err = compiler.compileConstI32(wazeroir.NewOperationConstI32(0))
 				require.NoError(t, err)
-				err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: 0})
+				err = compiler.compileConstI32(wazeroir.NewOperationConstI32(0))
 				require.NoError(t, err)
 				err = compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
 				require.NoError(t, err)
diff --git a/internal/engine/compiler/compiler_vec_test.go b/internal/engine/compiler/compiler_vec_test.go
index aa0f3fc6..bd28468b 100644
--- a/internal/engine/compiler/compiler_vec_test.go
+++ b/internal/engine/compiler/compiler_vec_test.go
@@ -550,7 +550,7 @@ func TestCompiler_compileV128Load(t *testing.T) {
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.offset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.offset))
 			require.NoError(t, err)
 
 			err = compiler.compileV128Load(wazeroir.OperationV128Load{
@@ -752,7 +752,7 @@ func TestCompiler_compileV128LoadLane(t *testing.T) {
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.offset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.offset))
 			require.NoError(t, err)
 
 			err = compiler.compileV128Const(wazeroir.OperationV128Const{
@@ -811,7 +811,7 @@ func TestCompiler_compileV128Store(t *testing.T) {
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.offset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.offset))
 			require.NoError(t, err)
 
 			err = compiler.compileV128Const(wazeroir.OperationV128Const{Lo: ^uint64(0), Hi: ^uint64(0)})
@@ -950,7 +950,7 @@ func TestCompiler_compileV128StoreLane(t *testing.T) {
 			err := compiler.compilePreamble()
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.offset})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.offset))
 			require.NoError(t, err)
 
 			err = compiler.compileV128Const(wazeroir.OperationV128Const{
@@ -1182,7 +1182,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI8x16,
 			laneIndex: 5,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xff})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xff))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{5: 0xff},
@@ -1192,7 +1192,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI8x16,
 			laneIndex: 5,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xff << 8})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xff << 8))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{},
@@ -1202,7 +1202,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI8x16,
 			laneIndex: 5,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xff})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xff))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{5: 0xff},
@@ -1212,7 +1212,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI16x8,
 			laneIndex: 0,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xee_ff})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xee_ff))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{0: 0xff, 1: 0xee},
@@ -1222,7 +1222,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI16x8,
 			laneIndex: 3,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xaa_00})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xaa_00))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{7: 0xaa},
@@ -1232,7 +1232,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI16x8,
 			laneIndex: 3,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xaa_bb << 16})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xaa_bb << 16))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{},
@@ -1242,7 +1242,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI32x4,
 			laneIndex: 0,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xaa_bb_cc_dd})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xaa_bb_cc_dd))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{0: 0xdd, 1: 0xcc, 2: 0xbb, 3: 0xaa},
@@ -1252,7 +1252,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI32x4,
 			laneIndex: 3,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xaa_bb_cc_dd})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xaa_bb_cc_dd))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{12: 0xdd, 13: 0xcc, 14: 0xbb, 15: 0xaa},
@@ -1262,7 +1262,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI64x2,
 			laneIndex: 0,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI64(wazeroir.OperationConstI64{Value: 0xaa_bb_cc_dd_01_02_03_04})
+				err := c.compileConstI64(wazeroir.NewOperationConstI64(0xaa_bb_cc_dd_01_02_03_04))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{0: 0x04, 1: 0x03, 2: 0x02, 3: 0x01, 4: 0xdd, 5: 0xcc, 6: 0xbb, 7: 0xaa},
@@ -1272,7 +1272,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeI64x2,
 			laneIndex: 1,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI64(wazeroir.OperationConstI64{Value: 0xaa_bb_cc_dd_01_02_03_04})
+				err := c.compileConstI64(wazeroir.NewOperationConstI64(0xaa_bb_cc_dd_01_02_03_04))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{8: 0x04, 9: 0x03, 10: 0x02, 11: 0x01, 12: 0xdd, 13: 0xcc, 14: 0xbb, 15: 0xaa},
@@ -1282,7 +1282,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeF32x4,
 			laneIndex: 0,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(0xaa_bb_cc_dd)})
+				err := c.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(0xaa_bb_cc_dd)))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{0: 0xdd, 1: 0xcc, 2: 0xbb, 3: 0xaa},
@@ -1292,7 +1292,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeF32x4,
 			laneIndex: 1,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(0xaa_bb_cc_dd)})
+				err := c.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(0xaa_bb_cc_dd)))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{4: 0xdd, 5: 0xcc, 6: 0xbb, 7: 0xaa},
@@ -1302,7 +1302,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeF32x4,
 			laneIndex: 2,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(0xaa_bb_cc_dd)})
+				err := c.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(0xaa_bb_cc_dd)))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{8: 0xdd, 9: 0xcc, 10: 0xbb, 11: 0xaa},
@@ -1312,7 +1312,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeF32x4,
 			laneIndex: 3,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(0xaa_bb_cc_dd)})
+				err := c.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(0xaa_bb_cc_dd)))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{12: 0xdd, 13: 0xcc, 14: 0xbb, 15: 0xaa},
@@ -1322,7 +1322,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeF64x2,
 			laneIndex: 0,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(0xaa_bb_cc_dd_01_02_03_04)})
+				err := c.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(0xaa_bb_cc_dd_01_02_03_04)))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{0: 0x04, 1: 0x03, 2: 0x02, 3: 0x01, 4: 0xdd, 5: 0xcc, 6: 0xbb, 7: 0xaa},
@@ -1332,7 +1332,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeF64x2,
 			laneIndex: 1,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(0xaa_bb_cc_dd_01_02_03_04)})
+				err := c.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(0xaa_bb_cc_dd_01_02_03_04)))
 				require.NoError(t, err)
 			},
 			exp: [16]byte{8: 0x04, 9: 0x03, 10: 0x02, 11: 0x01, 12: 0xdd, 13: 0xcc, 14: 0xbb, 15: 0xaa},
@@ -1342,7 +1342,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeF64x2,
 			laneIndex: 0,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(0.0)})
+				err := c.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(0.0)))
 				require.NoError(t, err)
 			},
 			lo:  math.Float64bits(1.0),
@@ -1354,7 +1354,7 @@ func TestCompiler_compileV128ReplaceLane(t *testing.T) {
 			shape:     wazeroir.ShapeF64x2,
 			laneIndex: 1,
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(0.0)})
+				err := c.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(0.0)))
 				require.NoError(t, err)
 			},
 			lo:  math.Float64bits(1.0),
@@ -1414,7 +1414,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 		{
 			name: "i8x16",
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0x1})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0x1))
 				require.NoError(t, err)
 			},
 			shape: wazeroir.ShapeI8x16,
@@ -1423,7 +1423,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 		{
 			name: "i16x8",
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xff_11})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xff_11))
 				require.NoError(t, err)
 			},
 			shape: wazeroir.ShapeI16x8,
@@ -1432,7 +1432,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 		{
 			name: "i32x4",
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0xff_11_ee_22})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0xff_11_ee_22))
 				require.NoError(t, err)
 			},
 			shape: wazeroir.ShapeI32x4,
@@ -1441,7 +1441,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 		{
 			name: "i64x2",
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstI64(wazeroir.OperationConstI64{Value: 0xff_00_ee_00_11_00_22_00})
+				err := c.compileConstI64(wazeroir.NewOperationConstI64(0xff_00_ee_00_11_00_22_00))
 				require.NoError(t, err)
 			},
 			shape: wazeroir.ShapeI64x2,
@@ -1450,7 +1450,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 		{
 			name: "f32x4",
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF32(wazeroir.OperationConstF32{Value: math.Float32frombits(0xff_11_ee_22)})
+				err := c.compileConstF32(wazeroir.NewOperationConstF32(math.Float32frombits(0xff_11_ee_22)))
 				require.NoError(t, err)
 			},
 			shape: wazeroir.ShapeF32x4,
@@ -1459,7 +1459,7 @@ func TestCompiler_compileV128Splat(t *testing.T) {
 		{
 			name: "f64x2",
 			originValueSetupFn: func(t *testing.T, c compilerImpl) {
-				err := c.compileConstF64(wazeroir.OperationConstF64{Value: math.Float64frombits(0xff_00_ee_00_11_00_22_00)})
+				err := c.compileConstF64(wazeroir.NewOperationConstF64(math.Float64frombits(0xff_00_ee_00_11_00_22_00)))
 				require.NoError(t, err)
 			},
 			shape: wazeroir.ShapeF64x2,
@@ -2679,7 +2679,7 @@ func TestCompiler_compileV128Shl(t *testing.T) {
 			})
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.s})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.s))
 			require.NoError(t, err)
 
 			err = compiler.compileV128Shl(wazeroir.OperationV128Shl{Shape: tc.shape})
@@ -2955,7 +2955,7 @@ func TestCompiler_compileV128Shr(t *testing.T) {
 			})
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: tc.s})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(tc.s))
 			require.NoError(t, err)
 
 			err = compiler.compileV128Shr(wazeroir.OperationV128Shr{Shape: tc.shape, Signed: tc.signed})
@@ -7391,7 +7391,7 @@ func TestCompiler_compileSelect_v128(t *testing.T) {
 		})
 		require.NoError(t, err)
 
-		err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: selector})
+		err = compiler.compileConstI32(wazeroir.NewOperationConstI32(selector))
 		require.NoError(t, err)
 
 		err = compiler.compileSelect(wazeroir.OperationSelect{IsTargetVector: true})
diff --git a/internal/engine/compiler/impl_amd64_test.go b/internal/engine/compiler/impl_amd64_test.go
index 0baeab06..37268156 100644
--- a/internal/engine/compiler/impl_amd64_test.go
+++ b/internal/engine/compiler/impl_amd64_test.go
@@ -378,7 +378,7 @@ func TestAmd64Compiler_readInstructionAddress(t *testing.T) {
 		// right after RET. Therefore, the jmp instruction above
 		// must target here.
 		const expectedReturnValue uint32 = 10000
-		err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: expectedReturnValue})
+		err = compiler.compileConstI32(wazeroir.NewOperationConstI32(expectedReturnValue))
 		require.NoError(t, err)
 
 		err = compiler.compileReturnFunction()
@@ -498,7 +498,7 @@ func TestAmd64Compiler_ensureClz_ABM(t *testing.T) {
 
 			compiler := env.requireNewCompiler(t, newCompiler, nil)
 
-			err := compiler.compileConstI32(wazeroir.OperationConstI32{Value: 10})
+			err := compiler.compileConstI32(wazeroir.NewOperationConstI32(10))
 			require.NoError(t, err)
 
 			err = compiler.compileClz(wazeroir.OperationClz{Type: wazeroir.UnsignedInt64})
@@ -553,7 +553,7 @@ func TestAmd64Compiler_ensureCtz_ABM(t *testing.T) {
 
 			compiler := env.requireNewCompiler(t, newCompiler, nil)
 
-			err := compiler.compileConstI32(wazeroir.OperationConstI32{Value: 10})
+			err := compiler.compileConstI32(wazeroir.NewOperationConstI32(10))
 			require.NoError(t, err)
 
 			err = compiler.compileCtz(wazeroir.OperationCtz{Type: wazeroir.UnsignedInt64})
diff --git a/internal/engine/compiler/impl_vec_amd64_test.go b/internal/engine/compiler/impl_vec_amd64_test.go
index d46eb225..9573b2f1 100644
--- a/internal/engine/compiler/impl_vec_amd64_test.go
+++ b/internal/engine/compiler/impl_vec_amd64_test.go
@@ -187,9 +187,9 @@ func TestAmd64Compiler_compileV128ShrI64x2SignedImpl(t *testing.T) {
 				c.locationStack.markRegisterUnused(loc.register)
 
 				// Instead, push the conditional flag value which is supposed be interpreted as 1 (=shiftAmount).
-				err := c.compileConstI32(wazeroir.OperationConstI32{Value: 0})
+				err := c.compileConstI32(wazeroir.NewOperationConstI32(0))
 				require.NoError(t, err)
-				err = c.compileConstI32(wazeroir.OperationConstI32{Value: 0})
+				err = c.compileConstI32(wazeroir.NewOperationConstI32(0))
 				require.NoError(t, err)
 				err = c.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
 				require.NoError(t, err)
@@ -214,7 +214,7 @@ func TestAmd64Compiler_compileV128ShrI64x2SignedImpl(t *testing.T) {
 			})
 			require.NoError(t, err)
 
-			err = compiler.compileConstI32(wazeroir.OperationConstI32{Value: shiftAmount})
+			err = compiler.compileConstI32(wazeroir.NewOperationConstI32(shiftAmount))
 			require.NoError(t, err)
 
 			amdCompiler := compiler.(*amd64Compiler)
diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go
index 9f2fcfe1..066e4a0e 100644
--- a/internal/wazeroir/compiler_test.go
+++ b/internal/wazeroir/compiler_test.go
@@ -138,7 +138,7 @@ func TestCompile(t *testing.T) {
 			},
 			expected: &CompilationResult{
 				Operations: []Operation{ // begin with params: []
-					OperationConstI32{Value: 8}, // [8]
+					NewOperationConstI32(8), // [8]
 					OperationLoad{Type: UnsignedTypeI32, Arg: MemoryArg{Alignment: 2, Offset: 0}}, // [x]
 					OperationDrop{Depth: &InclusiveRange{}},                                       // []
 					OperationBr{Target: Label{Kind: LabelKindReturn}},                             // return!
@@ -165,7 +165,7 @@ func TestCompile(t *testing.T) {
 			},
 			expected: &CompilationResult{
 				Operations: []Operation{ // begin with params: []
-					OperationConstI32{Value: 8}, // [8]
+					NewOperationConstI32(8), // [8]
 					OperationLoad{Type: UnsignedTypeI32, Arg: MemoryArg{Alignment: 2, Offset: 0}}, // [x]
 					OperationDrop{Depth: &InclusiveRange{}},                                       // []
 					OperationBr{Target: Label{Kind: LabelKindReturn}},                             // return!
@@ -344,9 +344,9 @@ func TestCompile_BulkMemoryOperations(t *testing.T) {
 
 	expected := &CompilationResult{
 		Operations: []Operation{ // begin with params: []
-			OperationConstI32{16},                             // [16]
-			OperationConstI32{0},                              // [16, 0]
-			OperationConstI32{7},                              // [16, 0, 7]
+			NewOperationConstI32(16),                          // [16]
+			NewOperationConstI32(0),                           // [16, 0]
+			NewOperationConstI32(7),                           // [16, 0, 7]
 			OperationMemoryInit{1},                            // []
 			OperationDataDrop{1},                              // []
 			OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
@@ -433,8 +433,8 @@ func TestCompile_MultiValue(t *testing.T) {
 			// )
 			expected: &CompilationResult{
 				Operations: []Operation{ // begin with params: []
-					OperationConstF64{Value: 4}, // [4]
-					OperationConstF64{Value: 5}, // [4, 5]
+					NewOperationConstF64(4), // [4]
+					NewOperationConstF64(5), // [4, 5]
 					OperationBr{
 						Target: Label{FrameID: 2, Kind: LabelKindContinuation}, // arbitrary FrameID
 					},
@@ -464,8 +464,8 @@ func TestCompile_MultiValue(t *testing.T) {
 			},
 			expected: &CompilationResult{
 				Operations: []Operation{ // begin with params: []
-					OperationConstI32{Value: 306},                     // [306]
-					OperationConstI64{Value: 356},                     // [306, 356]
+					NewOperationConstI32(306),                         // [306]
+					NewOperationConstI64(356),                         // [306, 356]
 					OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 				},
 				LabelCallers: map[LabelID]uint32{},
@@ -499,19 +499,19 @@ func TestCompile_MultiValue(t *testing.T) {
 			//	)
 			expected: &CompilationResult{
 				Operations: []Operation{ // begin with params: [$0]
-					OperationConstI32{Value: 1}, // [$0, 1]
-					OperationPick{Depth: 1},     // [$0, 1, $0]
+					NewOperationConstI32(1), // [$0, 1]
+					OperationPick{Depth: 1}, // [$0, 1, $0]
 					OperationBrIf{ // [$0, 1]
 						Then: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindHeader}},
 						Else: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindElse}},
 					},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindHeader}},
-					OperationConstI32{Value: 2},         // [$0, 1, 2]
+					NewOperationConstI32(2),             // [$0, 1, 2]
 					OperationAdd{Type: UnsignedTypeI32}, // [$0, 3]
 					OperationBr{Target: Label{FrameID: 2, Kind: LabelKindContinuation}},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindElse}},
-					OperationConstI32{Value: uint32(api.EncodeI32(-2))}, // [$0, 1, -2]
-					OperationAdd{Type: UnsignedTypeI32},                 // [$0, -1]
+					NewOperationConstI32(uint32(api.EncodeI32(-2))), // [$0, 1, -2]
+					OperationAdd{Type: UnsignedTypeI32},             // [$0, -1]
 					OperationBr{Target: Label{FrameID: 2, Kind: LabelKindContinuation}},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindContinuation}},
 					OperationDrop{Depth: &InclusiveRange{Start: 1, End: 1}}, // .L2 = [3], .L2_else = [-1]
@@ -557,9 +557,9 @@ func TestCompile_MultiValue(t *testing.T) {
 			//	)
 			expected: &CompilationResult{
 				Operations: []Operation{ // begin with params: [$0]
-					OperationConstI32{Value: 1}, // [$0, 1]
-					OperationConstI32{Value: 2}, // [$0, 1, 2]
-					OperationPick{Depth: 2},     // [$0, 1, 2, $0]
+					NewOperationConstI32(1), // [$0, 1]
+					NewOperationConstI32(2), // [$0, 1, 2]
+					OperationPick{Depth: 2}, // [$0, 1, 2, $0]
 					OperationBrIf{ // [$0, 1, 2]
 						Then: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindHeader}},
 						Else: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindElse}},
@@ -614,9 +614,9 @@ func TestCompile_MultiValue(t *testing.T) {
 			//	)
 			expected: &CompilationResult{
 				Operations: []Operation{ // begin with params: [$0]
-					OperationConstI32{Value: 1}, // [$0, 1]
-					OperationConstI32{Value: 2}, // [$0, 1, 2]
-					OperationPick{Depth: 2},     // [$0, 1, 2, $0]
+					NewOperationConstI32(1), // [$0, 1]
+					NewOperationConstI32(2), // [$0, 1, 2]
+					OperationPick{Depth: 2}, // [$0, 1, 2, $0]
 					OperationBrIf{ // [$0, 1, 2]
 						Then: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindHeader}},
 						Else: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindElse}},
@@ -761,7 +761,7 @@ func TestCompile_CallIndirectNonZeroTableIndex(t *testing.T) {
 
 	expected := &CompilationResult{
 		Operations: []Operation{ // begin with params: []
-			OperationConstI32{},
+			NewOperationConstI32(0),
 			OperationCallIndirect{TypeIndex: 2, TableIndex: 5},
 			OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 		},
@@ -807,7 +807,7 @@ func TestCompile_Refs(t *testing.T) {
 				wasm.OpcodeEnd,
 			},
 			expected: []Operation{
-				OperationConstI64{Value: 0},
+				NewOperationConstI64(0),
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 			},
@@ -820,7 +820,7 @@ func TestCompile_Refs(t *testing.T) {
 				wasm.OpcodeEnd,
 			},
 			expected: []Operation{
-				OperationConstI64{Value: 0},
+				NewOperationConstI64(0),
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 			},
@@ -849,7 +849,7 @@ func TestCompile_Refs(t *testing.T) {
 				wasm.OpcodeEnd,
 			},
 			expected: []Operation{
-				OperationConstI64{Value: 0},
+				NewOperationConstI64(0),
 				OperationEqz{Type: UnsignedInt64},
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
@@ -887,7 +887,7 @@ func TestCompile_TableGetOrSet(t *testing.T) {
 				wasm.OpcodeEnd,
 			},
 			expected: []Operation{
-				OperationConstI32{Value: 10},
+				NewOperationConstI32(10),
 				OperationTableGet{TableIndex: 0},
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
@@ -902,8 +902,8 @@ func TestCompile_TableGetOrSet(t *testing.T) {
 				wasm.OpcodeEnd,
 			},
 			expected: []Operation{
-				OperationConstI32{Value: 10},
-				OperationConstI64{Value: 0},
+				NewOperationConstI32(10),
+				NewOperationConstI64(0),
 				OperationTableSet{TableIndex: 0},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 			},
@@ -917,7 +917,7 @@ func TestCompile_TableGetOrSet(t *testing.T) {
 				wasm.OpcodeEnd,
 			},
 			expected: []Operation{
-				OperationConstI32{Value: 10},
+				NewOperationConstI32(10),
 				OperationRefFunc{FunctionIndex: 1},
 				OperationTableSet{TableIndex: 0},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
@@ -956,8 +956,8 @@ func TestCompile_TableGrowFillSize(t *testing.T) {
 				wasm.OpcodeEnd,
 			},
 			expected: []Operation{
-				OperationConstI64{Value: 0}, // Null ref.
-				OperationConstI32{Value: 1},
+				NewOperationConstI64(0), // Null ref.
+				NewOperationConstI32(1),
 				OperationTableGrow{TableIndex: 1},
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
@@ -973,9 +973,9 @@ func TestCompile_TableGrowFillSize(t *testing.T) {
 				wasm.OpcodeEnd,
 			},
 			expected: []Operation{
-				OperationConstI32{Value: 10},
-				OperationConstI64{Value: 0}, // Null ref.
-				OperationConstI32{Value: 1},
+				NewOperationConstI32(10),
+				NewOperationConstI64(0), // Null ref.
+				NewOperationConstI32(1),
 				OperationTableFill{TableIndex: 1},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 			},
@@ -1103,7 +1103,7 @@ func TestCompile_Locals(t *testing.T) {
 				}}},
 			},
 			expected: []Operation{
-				OperationConstI32{Value: 0x1},
+				NewOperationConstI32(0x1),
 				OperationSet{Depth: 1, IsTargetVector: false},
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
@@ -1171,7 +1171,7 @@ func TestCompile_Locals(t *testing.T) {
 				}}},
 			},
 			expected: []Operation{
-				OperationConstF32{math.Float32frombits(1)},
+				NewOperationConstF32(math.Float32frombits(1)),
 				OperationPick{Depth: 0, IsTargetVector: false},
 				OperationSet{Depth: 2, IsTargetVector: false},
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 1}},
@@ -2937,7 +2937,7 @@ func TestCompile_select_vectors(t *testing.T) {
 			expected: []Operation{
 				OperationV128Const{Lo: 0x1, Hi: 0x2},
 				OperationV128Const{Lo: 0x3, Hi: 0x4},
-				OperationConstI32{Value: 0},
+				NewOperationConstI32(0),
 				OperationSelect{IsTargetVector: true},
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 1}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}},
@@ -2963,7 +2963,7 @@ func TestCompile_select_vectors(t *testing.T) {
 			expected: []Operation{
 				OperationV128Const{Lo: 0x1, Hi: 0x2},
 				OperationV128Const{Lo: 0x3, Hi: 0x4},
-				OperationConstI32{Value: 0},
+				NewOperationConstI32(0),
 				OperationSelect{IsTargetVector: true},
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 1}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}},
diff --git a/internal/wazeroir/operations_test.go b/internal/wazeroir/operations_test.go
index d3a5162d..6e897f5e 100644
--- a/internal/wazeroir/operations_test.go
+++ b/internal/wazeroir/operations_test.go
@@ -12,3 +12,38 @@ func TestOperationKind_String(t *testing.T) {
 		require.NotEqual(t, "", k.String())
 	}
 }
+
+// TestUnionOperation_String ensures that UnionOperation's stringer is well-defined for all supported OpKinds.
+func TestUnionOperation_String(t *testing.T) {
+	op := UnionOperation{}
+	for _, k := range []OperationKind{
+		OperationKindUnreachable,
+		OperationKindMemorySize,
+		OperationKindMemoryGrow,
+		OperationKindI32WrapFromI64,
+		OperationKindF32DemoteFromF64,
+		OperationKindF64PromoteFromF32,
+		OperationKindI32ReinterpretFromF32,
+		OperationKindI64ReinterpretFromF64,
+		OperationKindF32ReinterpretFromI32,
+		OperationKindF64ReinterpretFromI64,
+		OperationKindSignExtend32From8,
+		OperationKindSignExtend32From16,
+		OperationKindSignExtend64From8,
+		OperationKindSignExtend64From16,
+		OperationKindSignExtend64From32,
+		OperationKindMemoryCopy,
+		OperationKindMemoryFill,
+		OperationKindBuiltinFunctionCheckExitCode,
+		OperationKindCall,
+		OperationKindGlobalGet,
+		OperationKindGlobalSet,
+		OperationKindConstI32,
+		OperationKindConstI64,
+		OperationKindConstF32,
+		OperationKindConstF64,
+	} {
+		op.OpKind = k
+		require.NotEqual(t, "", op.String())
+	}
+}