Repository: tetratelabs/wazero
Pull request #1321 (2023-03-30 20:33:16)

wasm: move ModuleInstance.Closed to the top
ModuleInstance.Closed is an atomic variable meant to be loaded and swapped with sync/atomic. Closed, being a 64 bit integer, requires 64 bit alignment. The simplest way we can get alignment is to place these atomic fields at the top of their struct.

Closed can be moved to a more logical place once support for Go 1.18 is dropped and its type changed to atomic.Uint64.


Patch:
diff --git a/internal/engine/compiler/compiler.go b/internal/engine/compiler/compiler.go
index 8378a83f..9ed519fc 100644
--- a/internal/engine/compiler/compiler.go
+++ b/internal/engine/compiler/compiler.go
@@ -50,55 +50,55 @@ type compiler interface {
 	// compilePick adds instructions to perform wazeroir.OperationPick.
 	compilePick(o wazeroir.OperationPick) error
 	// compileAdd adds instructions to perform wazeroir.OperationAdd.
-	compileAdd(o wazeroir.OperationAdd) error
+	compileAdd(o wazeroir.UnionOperation) error
 	// compileSub adds instructions to perform wazeroir.OperationSub.
-	compileSub(o wazeroir.OperationSub) error
+	compileSub(o wazeroir.UnionOperation) error
 	// compileMul adds instructions to perform wazeroir.OperationMul.
-	compileMul(o wazeroir.OperationMul) error
+	compileMul(o wazeroir.UnionOperation) error
 	// compileClz adds instructions to perform wazeroir.OperationClz.
-	compileClz(o wazeroir.OperationClz) error
+	compileClz(o wazeroir.UnionOperation) error
 	// compileCtz adds instructions to perform wazeroir.OperationCtz.
-	compileCtz(o wazeroir.OperationCtz) error
+	compileCtz(o wazeroir.UnionOperation) error
 	// compilePopcnt adds instructions to perform wazeroir.OperationPopcnt.
-	compilePopcnt(o wazeroir.OperationPopcnt) error
+	compilePopcnt(o wazeroir.UnionOperation) error
 	// compileDiv adds instructions to perform wazeroir.OperationDiv.
-	compileDiv(o wazeroir.OperationDiv) error
+	compileDiv(o wazeroir.UnionOperation) error
 	// compileRem adds instructions to perform wazeroir.OperationRem.
-	compileRem(o wazeroir.OperationRem) error
+	compileRem(o wazeroir.UnionOperation) error
 	// compileAnd adds instructions to perform wazeroir.OperationAnd.
-	compileAnd(o wazeroir.OperationAnd) error
+	compileAnd(o wazeroir.UnionOperation) error
 	// compileOr adds instructions to perform wazeroir.OperationOr.
-	compileOr(o wazeroir.OperationOr) error
+	compileOr(o wazeroir.UnionOperation) error
 	// compileXor adds instructions to perform wazeroir.OperationXor.
-	compileXor(o wazeroir.OperationXor) error
+	compileXor(o wazeroir.UnionOperation) error
 	// compileShl adds instructions to perform wazeroir.OperationShl.
-	compileShl(o wazeroir.OperationShl) error
+	compileShl(o wazeroir.UnionOperation) error
 	// compileShr adds instructions to perform wazeroir.OperationShr.
-	compileShr(o wazeroir.OperationShr) error
+	compileShr(o wazeroir.UnionOperation) error
 	// compileRotl adds instructions to perform wazeroir.OperationRotl.
-	compileRotl(o wazeroir.OperationRotl) error
+	compileRotl(o wazeroir.UnionOperation) error
 	// compileRotr adds instructions to perform wazeroir.OperationRotr.
-	compileRotr(o wazeroir.OperationRotr) error
+	compileRotr(o wazeroir.UnionOperation) error
 	// compileNeg adds instructions to perform wazeroir.OperationAbs.
-	compileAbs(o wazeroir.OperationAbs) error
+	compileAbs(o wazeroir.UnionOperation) error
 	// compileNeg adds instructions to perform wazeroir.OperationNeg.
-	compileNeg(o wazeroir.OperationNeg) error
+	compileNeg(o wazeroir.UnionOperation) error
 	// compileCeil adds instructions to perform wazeroir.OperationCeil.
-	compileCeil(o wazeroir.OperationCeil) error
+	compileCeil(o wazeroir.UnionOperation) error
 	// compileFloor adds instructions to perform wazeroir.OperationFloor.
-	compileFloor(o wazeroir.OperationFloor) error
+	compileFloor(o wazeroir.UnionOperation) error
 	// compileTrunc adds instructions to perform wazeroir.OperationTrunc.
-	compileTrunc(o wazeroir.OperationTrunc) error
+	compileTrunc(o wazeroir.UnionOperation) error
 	// compileNearest adds instructions to perform wazeroir.OperationNearest.
-	compileNearest(o wazeroir.OperationNearest) error
+	compileNearest(o wazeroir.UnionOperation) error
 	// compileSqrt adds instructions perform wazeroir.OperationSqrt.
-	compileSqrt(o wazeroir.OperationSqrt) error
+	compileSqrt(o wazeroir.UnionOperation) error
 	// compileMin adds instructions perform wazeroir.OperationMin.
-	compileMin(o wazeroir.OperationMin) error
+	compileMin(o wazeroir.UnionOperation) error
 	// compileMax adds instructions perform wazeroir.OperationMax.
-	compileMax(o wazeroir.OperationMax) error
+	compileMax(o wazeroir.UnionOperation) error
 	// compileCopysign adds instructions to perform wazeroir.OperationCopysign.
-	compileCopysign(o wazeroir.OperationCopysign) error
+	compileCopysign(o wazeroir.UnionOperation) error
 	// compileI32WrapFromI64 adds instructions to perform wazeroir.OperationI32WrapFromI64.
 	compileI32WrapFromI64() error
 	// compileITruncFromF adds instructions to perform wazeroir.OperationITruncFromF.
@@ -120,19 +120,19 @@ type compiler interface {
 	// compileExtend adds instructions to perform wazeroir.OperationExtend.
 	compileExtend(o wazeroir.OperationExtend) error
 	// compileEq adds instructions to perform wazeroir.OperationEq.
-	compileEq(o wazeroir.OperationEq) error
+	compileEq(o wazeroir.UnionOperation) error
 	// compileEq adds instructions to perform wazeroir.OperationNe.
-	compileNe(o wazeroir.OperationNe) error
+	compileNe(o wazeroir.UnionOperation) error
 	// compileEq adds instructions to perform wazeroir.OperationEqz.
-	compileEqz(o wazeroir.OperationEqz) error
+	compileEqz(o wazeroir.UnionOperation) error
 	// compileLt adds instructions to perform wazeroir.OperationLt.
-	compileLt(o wazeroir.OperationLt) error
+	compileLt(o wazeroir.UnionOperation) error
 	// compileGt adds instructions to perform wazeroir.OperationGt.
-	compileGt(o wazeroir.OperationGt) error
+	compileGt(o wazeroir.UnionOperation) error
 	// compileLe adds instructions to perform wazeroir.OperationLe.
-	compileLe(o wazeroir.OperationLe) error
+	compileLe(o wazeroir.UnionOperation) error
 	// compileLe adds instructions to perform wazeroir.OperationGe.
-	compileGe(o wazeroir.OperationGe) error
+	compileGe(o wazeroir.UnionOperation) error
 	// compileLoad adds instructions to perform wazeroir.OperationLoad.
 	compileLoad(o wazeroir.OperationLoad) error
 	// compileLoad8 adds instructions to perform wazeroir.OperationLoad8.
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index 54c6a570..ac9b7fea 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -348,13 +348,13 @@ const (
 	functionSize                     = 56
 
 	// Offsets for wasm.ModuleInstance.
-	moduleInstanceGlobalsOffset          = 24
-	moduleInstanceMemoryOffset           = 48
-	moduleInstanceTablesOffset           = 56
-	moduleInstanceEngineOffset           = 80
-	moduleInstanceTypeIDsOffset          = 96
-	moduleInstanceDataInstancesOffset    = 120
-	moduleInstanceElementInstancesOffset = 144
+	moduleInstanceGlobalsOffset          = 32
+	moduleInstanceMemoryOffset           = 56
+	moduleInstanceTablesOffset           = 64
+	moduleInstanceEngineOffset           = 88
+	moduleInstanceTypeIDsOffset          = 104
+	moduleInstanceDataInstancesOffset    = 128
+	moduleInstanceElementInstancesOffset = 152
 
 	// Offsets for wasm.TableInstance.
 	tableInstanceTableOffset    = 0
@@ -1126,70 +1126,6 @@ func compileWasmFunction(cmp compiler, ir *wazeroir.CompilationResult) (*code, e
 			err = cmp.compileStore16(o)
 		case wazeroir.OperationStore32:
 			err = cmp.compileStore32(o)
-		case wazeroir.OperationEq:
-			err = cmp.compileEq(o)
-		case wazeroir.OperationNe:
-			err = cmp.compileNe(o)
-		case wazeroir.OperationEqz:
-			err = cmp.compileEqz(o)
-		case wazeroir.OperationLt:
-			err = cmp.compileLt(o)
-		case wazeroir.OperationGt:
-			err = cmp.compileGt(o)
-		case wazeroir.OperationLe:
-			err = cmp.compileLe(o)
-		case wazeroir.OperationGe:
-			err = cmp.compileGe(o)
-		case wazeroir.OperationAdd:
-			err = cmp.compileAdd(o)
-		case wazeroir.OperationSub:
-			err = cmp.compileSub(o)
-		case wazeroir.OperationMul:
-			err = cmp.compileMul(o)
-		case wazeroir.OperationClz:
-			err = cmp.compileClz(o)
-		case wazeroir.OperationCtz:
-			err = cmp.compileCtz(o)
-		case wazeroir.OperationPopcnt:
-			err = cmp.compilePopcnt(o)
-		case wazeroir.OperationDiv:
-			err = cmp.compileDiv(o)
-		case wazeroir.OperationRem:
-			err = cmp.compileRem(o)
-		case wazeroir.OperationAnd:
-			err = cmp.compileAnd(o)
-		case wazeroir.OperationOr:
-			err = cmp.compileOr(o)
-		case wazeroir.OperationXor:
-			err = cmp.compileXor(o)
-		case wazeroir.OperationShl:
-			err = cmp.compileShl(o)
-		case wazeroir.OperationShr:
-			err = cmp.compileShr(o)
-		case wazeroir.OperationRotl:
-			err = cmp.compileRotl(o)
-		case wazeroir.OperationRotr:
-			err = cmp.compileRotr(o)
-		case wazeroir.OperationAbs:
-			err = cmp.compileAbs(o)
-		case wazeroir.OperationNeg:
-			err = cmp.compileNeg(o)
-		case wazeroir.OperationCeil:
-			err = cmp.compileCeil(o)
-		case wazeroir.OperationFloor:
-			err = cmp.compileFloor(o)
-		case wazeroir.OperationTrunc:
-			err = cmp.compileTrunc(o)
-		case wazeroir.OperationNearest:
-			err = cmp.compileNearest(o)
-		case wazeroir.OperationSqrt:
-			err = cmp.compileSqrt(o)
-		case wazeroir.OperationMin:
-			err = cmp.compileMin(o)
-		case wazeroir.OperationMax:
-			err = cmp.compileMax(o)
-		case wazeroir.OperationCopysign:
-			err = cmp.compileCopysign(o)
 		case wazeroir.OperationITruncFromF:
 			err = cmp.compileITruncFromF(o)
 		case wazeroir.OperationFConvertFromI:
@@ -1344,6 +1280,70 @@ func compileWasmFunction(cmp compiler, ir *wazeroir.CompilationResult) (*code, e
 				err = cmp.compileConstF32(o)
 			case wazeroir.OperationKindConstF64:
 				err = cmp.compileConstF64(o)
+			case wazeroir.OperationKindEq:
+				err = cmp.compileEq(o)
+			case wazeroir.OperationKindNe:
+				err = cmp.compileNe(o)
+			case wazeroir.OperationKindEqz:
+				err = cmp.compileEqz(o)
+			case wazeroir.OperationKindLt:
+				err = cmp.compileLt(o)
+			case wazeroir.OperationKindGt:
+				err = cmp.compileGt(o)
+			case wazeroir.OperationKindLe:
+				err = cmp.compileLe(o)
+			case wazeroir.OperationKindGe:
+				err = cmp.compileGe(o)
+			case wazeroir.OperationKindAdd:
+				err = cmp.compileAdd(o)
+			case wazeroir.OperationKindSub:
+				err = cmp.compileSub(o)
+			case wazeroir.OperationKindMul:
+				err = cmp.compileMul(o)
+			case wazeroir.OperationKindClz:
+				err = cmp.compileClz(o)
+			case wazeroir.OperationKindCtz:
+				err = cmp.compileCtz(o)
+			case wazeroir.OperationKindPopcnt:
+				err = cmp.compilePopcnt(o)
+			case wazeroir.OperationKindDiv:
+				err = cmp.compileDiv(o)
+			case wazeroir.OperationKindRem:
+				err = cmp.compileRem(o)
+			case wazeroir.OperationKindAnd:
+				err = cmp.compileAnd(o)
+			case wazeroir.OperationKindOr:
+				err = cmp.compileOr(o)
+			case wazeroir.OperationKindXor:
+				err = cmp.compileXor(o)
+			case wazeroir.OperationKindShl:
+				err = cmp.compileShl(o)
+			case wazeroir.OperationKindShr:
+				err = cmp.compileShr(o)
+			case wazeroir.OperationKindRotl:
+				err = cmp.compileRotl(o)
+			case wazeroir.OperationKindRotr:
+				err = cmp.compileRotr(o)
+			case wazeroir.OperationKindAbs:
+				err = cmp.compileAbs(o)
+			case wazeroir.OperationKindNeg:
+				err = cmp.compileNeg(o)
+			case wazeroir.OperationKindCeil:
+				err = cmp.compileCeil(o)
+			case wazeroir.OperationKindFloor:
+				err = cmp.compileFloor(o)
+			case wazeroir.OperationKindTrunc:
+				err = cmp.compileTrunc(o)
+			case wazeroir.OperationKindNearest:
+				err = cmp.compileNearest(o)
+			case wazeroir.OperationKindSqrt:
+				err = cmp.compileSqrt(o)
+			case wazeroir.OperationKindMin:
+				err = cmp.compileMin(o)
+			case wazeroir.OperationKindMax:
+				err = cmp.compileMax(o)
+			case wazeroir.OperationKindCopysign:
+				err = cmp.compileCopysign(o)
 
 			case wazeroir.OperationKindI32WrapFromI64:
 				err = cmp.compileI32WrapFromI64()
diff --git a/internal/engine/compiler/impl_amd64.go b/internal/engine/compiler/impl_amd64.go
index aadd85bc..5a976870 100644
--- a/internal/engine/compiler/impl_amd64.go
+++ b/internal/engine/compiler/impl_amd64.go
@@ -985,12 +985,14 @@ func (c *amd64Compiler) compilePick(o wazeroir.OperationPick) error {
 }
 
 // compileAdd implements compiler.compileAdd for the amd64 architecture.
-func (c *amd64Compiler) compileAdd(o wazeroir.OperationAdd) error {
+func (c *amd64Compiler) compileAdd(o wazeroir.UnionOperation) error {
 	// TODO: if the previous instruction is const, then
 	// this can be optimized. Same goes for other arithmetic instructions.
 
 	var instruction asm.Instruction
-	switch o.Type {
+
+	unsignedType := wazeroir.UnsignedType(o.B1)
+	switch unsignedType {
 	case wazeroir.UnsignedTypeI32:
 		instruction = amd64.ADDL
 	case wazeroir.UnsignedTypeI64:
@@ -1021,12 +1023,13 @@ func (c *amd64Compiler) compileAdd(o wazeroir.OperationAdd) error {
 }
 
 // compileSub implements compiler.compileSub for the amd64 architecture.
-func (c *amd64Compiler) compileSub(o wazeroir.OperationSub) error {
+func (c *amd64Compiler) compileSub(o wazeroir.UnionOperation) error {
 	// TODO: if the previous instruction is const, then
 	// this can be optimized. Same goes for other arithmetic instructions.
 
 	var instruction asm.Instruction
-	switch o.Type {
+	unsignedType := wazeroir.UnsignedType(o.B1)
+	switch unsignedType {
 	case wazeroir.UnsignedTypeI32:
 		instruction = amd64.SUBL
 	case wazeroir.UnsignedTypeI64:
@@ -1057,8 +1060,9 @@ func (c *amd64Compiler) compileSub(o wazeroir.OperationSub) error {
 }
 
 // compileMul implements compiler.compileMul for the amd64 architecture.
-func (c *amd64Compiler) compileMul(o wazeroir.OperationMul) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileMul(o wazeroir.UnionOperation) (err error) {
+	unsignedType := wazeroir.UnsignedType(o.B1)
+	switch unsignedType {
 	case wazeroir.UnsignedTypeI32:
 		err = c.compileMulForInts(true, amd64.MULL)
 	case wazeroir.UnsignedTypeI64:
@@ -1177,14 +1181,15 @@ func (c *amd64Compiler) compileMulForFloats(instruction asm.Instruction) error {
 }
 
 // compileClz implements compiler.compileClz for the amd64 architecture.
-func (c *amd64Compiler) compileClz(o wazeroir.OperationClz) error {
+func (c *amd64Compiler) compileClz(o wazeroir.UnionOperation) error {
 	target := c.locationStack.pop()
 	if err := c.compileEnsureOnRegister(target); err != nil {
 		return err
 	}
 
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
 	if c.cpuFeatures.HasExtra(platform.CpuExtraFeatureABM) {
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			c.assembler.CompileRegisterToRegister(amd64.LZCNTL, target.register, target.register)
 		} else {
 			c.assembler.CompileRegisterToRegister(amd64.LZCNTQ, target.register, target.register)
@@ -1202,7 +1207,7 @@ func (c *amd64Compiler) compileClz(o wazeroir.OperationClz) error {
 		jmpIfNonZero := c.assembler.CompileJump(amd64.JNE)
 
 		// If the value is zero, we just push the const value.
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			c.assembler.CompileConstToRegister(amd64.MOVL, int64(32), target.register)
 		} else {
 			c.assembler.CompileConstToRegister(amd64.MOVL, int64(64), target.register)
@@ -1215,14 +1220,14 @@ func (c *amd64Compiler) compileClz(o wazeroir.OperationClz) error {
 		// Start emitting non-zero case.
 		c.assembler.SetJumpTargetOnNext(jmpIfNonZero)
 		// First, we calculate the most significant set bit.
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			c.assembler.CompileRegisterToRegister(amd64.BSRL, target.register, target.register)
 		} else {
 			c.assembler.CompileRegisterToRegister(amd64.BSRQ, target.register, target.register)
 		}
 
 		// Now we XOR the value with the bit length minus one.
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			c.assembler.CompileConstToRegister(amd64.XORL, 31, target.register)
 		} else {
 			c.assembler.CompileConstToRegister(amd64.XORQ, 63, target.register)
@@ -1240,14 +1245,15 @@ func (c *amd64Compiler) compileClz(o wazeroir.OperationClz) error {
 }
 
 // compileCtz implements compiler.compileCtz for the amd64 architecture.
-func (c *amd64Compiler) compileCtz(o wazeroir.OperationCtz) error {
+func (c *amd64Compiler) compileCtz(o wazeroir.UnionOperation) error {
 	target := c.locationStack.pop()
 	if err := c.compileEnsureOnRegister(target); err != nil {
 		return err
 	}
 
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
 	if c.cpuFeatures.HasExtra(platform.CpuExtraFeatureABM) {
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			c.assembler.CompileRegisterToRegister(amd64.TZCNTL, target.register, target.register)
 		} else {
 			c.assembler.CompileRegisterToRegister(amd64.TZCNTQ, target.register, target.register)
@@ -1262,7 +1268,7 @@ func (c *amd64Compiler) compileCtz(o wazeroir.OperationCtz) error {
 		jmpIfNonZero := c.assembler.CompileJump(amd64.JNE)
 
 		// If the value is zero, we just push the const value.
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			c.assembler.CompileConstToRegister(amd64.MOVL, int64(32), target.register)
 		} else {
 			c.assembler.CompileConstToRegister(amd64.MOVL, int64(64), target.register)
@@ -1274,7 +1280,7 @@ func (c *amd64Compiler) compileCtz(o wazeroir.OperationCtz) error {
 
 		// Otherwise, emit the TZCNT.
 		c.assembler.SetJumpTargetOnNext(jmpIfNonZero)
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			c.assembler.CompileRegisterToRegister(amd64.TZCNTL, target.register, target.register)
 		} else {
 			c.assembler.CompileRegisterToRegister(amd64.TZCNTQ, target.register, target.register)
@@ -1292,13 +1298,14 @@ func (c *amd64Compiler) compileCtz(o wazeroir.OperationCtz) error {
 }
 
 // compilePopcnt implements compiler.compilePopcnt for the amd64 architecture.
-func (c *amd64Compiler) compilePopcnt(o wazeroir.OperationPopcnt) error {
+func (c *amd64Compiler) compilePopcnt(o wazeroir.UnionOperation) error {
 	target := c.locationStack.pop()
 	if err := c.compileEnsureOnRegister(target); err != nil {
 		return err
 	}
 
-	if o.Type == wazeroir.UnsignedInt32 {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	if unsignedInt == wazeroir.UnsignedInt32 {
 		c.assembler.CompileRegisterToRegister(amd64.POPCNTL, target.register, target.register)
 	} else {
 		c.assembler.CompileRegisterToRegister(amd64.POPCNTQ, target.register, target.register)
@@ -1311,8 +1318,9 @@ func (c *amd64Compiler) compilePopcnt(o wazeroir.OperationPopcnt) error {
 }
 
 // compileDiv implements compiler.compileDiv for the amd64 architecture.
-func (c *amd64Compiler) compileDiv(o wazeroir.OperationDiv) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileDiv(o wazeroir.UnionOperation) (err error) {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeUint32:
 		err = c.compileDivForInts(true, false)
 	case wazeroir.SignedTypeUint64:
@@ -1348,9 +1356,10 @@ func (c *amd64Compiler) compileDivForInts(is32Bit bool, signed bool) error {
 }
 
 // compileRem implements compiler.compileRem for the amd64 architecture.
-func (c *amd64Compiler) compileRem(o wazeroir.OperationRem) (err error) {
+func (c *amd64Compiler) compileRem(o wazeroir.UnionOperation) (err error) {
 	var vt runtimeValueType
-	switch o.Type {
+	signedInt := wazeroir.SignedInt(o.B1)
+	switch signedInt {
 	case wazeroir.SignedInt32:
 		err = c.performDivisionOnInts(true, true, true)
 		vt = runtimeValueTypeI32
@@ -1575,8 +1584,9 @@ func (c *amd64Compiler) compileDivForFloats(is32Bit bool) error {
 }
 
 // compileAnd implements compiler.compileAnd for the amd64 architecture.
-func (c *amd64Compiler) compileAnd(o wazeroir.OperationAnd) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileAnd(o wazeroir.UnionOperation) (err error) {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		err = c.compileSimpleBinaryOp(amd64.ANDL)
 	case wazeroir.UnsignedInt64:
@@ -1586,8 +1596,9 @@ func (c *amd64Compiler) compileAnd(o wazeroir.OperationAnd) (err error) {
 }
 
 // compileOr implements compiler.compileOr for the amd64 architecture.
-func (c *amd64Compiler) compileOr(o wazeroir.OperationOr) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileOr(o wazeroir.UnionOperation) (err error) {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		err = c.compileSimpleBinaryOp(amd64.ORL)
 	case wazeroir.UnsignedInt64:
@@ -1597,8 +1608,9 @@ func (c *amd64Compiler) compileOr(o wazeroir.OperationOr) (err error) {
 }
 
 // compileXor implements compiler.compileXor for the amd64 architecture.
-func (c *amd64Compiler) compileXor(o wazeroir.OperationXor) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileXor(o wazeroir.UnionOperation) (err error) {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		err = c.compileSimpleBinaryOp(amd64.XORL)
 	case wazeroir.UnsignedInt64:
@@ -1635,8 +1647,9 @@ func (c *amd64Compiler) compileSimpleBinaryOp(instruction asm.Instruction) error
 }
 
 // compileShl implements compiler.compileShl for the amd64 architecture.
-func (c *amd64Compiler) compileShl(o wazeroir.OperationShl) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileShl(o wazeroir.UnionOperation) (err error) {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		err = c.compileShiftOp(amd64.SHLL, false)
 	case wazeroir.UnsignedInt64:
@@ -1646,8 +1659,9 @@ func (c *amd64Compiler) compileShl(o wazeroir.OperationShl) (err error) {
 }
 
 // compileShr implements compiler.compileShr for the amd64 architecture.
-func (c *amd64Compiler) compileShr(o wazeroir.OperationShr) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileShr(o wazeroir.UnionOperation) (err error) {
+	signedInt := wazeroir.SignedInt(o.B1)
+	switch signedInt {
 	case wazeroir.SignedInt32:
 		err = c.compileShiftOp(amd64.SARL, true)
 	case wazeroir.SignedInt64:
@@ -1661,8 +1675,9 @@ func (c *amd64Compiler) compileShr(o wazeroir.OperationShr) (err error) {
 }
 
 // compileRotl implements compiler.compileRotl for the amd64 architecture.
-func (c *amd64Compiler) compileRotl(o wazeroir.OperationRotl) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileRotl(o wazeroir.UnionOperation) (err error) {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		err = c.compileShiftOp(amd64.ROLL, true)
 	case wazeroir.UnsignedInt64:
@@ -1672,8 +1687,9 @@ func (c *amd64Compiler) compileRotl(o wazeroir.OperationRotl) (err error) {
 }
 
 // compileRotr implements compiler.compileRotr for the amd64 architecture.
-func (c *amd64Compiler) compileRotr(o wazeroir.OperationRotr) (err error) {
-	switch o.Type {
+func (c *amd64Compiler) compileRotr(o wazeroir.UnionOperation) (err error) {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		err = c.compileShiftOp(amd64.RORL, true)
 	case wazeroir.UnsignedInt64:
@@ -1737,14 +1753,14 @@ func (c *amd64Compiler) compileShiftOp(instruction asm.Instruction, is32Bit bool
 // See the following discussions for how we could take the abs of floats on x86 assembly.
 // https://stackoverflow.com/questions/32408665/fastest-way-to-compute-absolute-value-using-sse/32422471#32422471
 // https://stackoverflow.com/questions/44630015/how-would-fabsdouble-be-implemented-on-x86-is-it-an-expensive-operation
-func (c *amd64Compiler) compileAbs(o wazeroir.OperationAbs) (err error) {
+func (c *amd64Compiler) compileAbs(o wazeroir.UnionOperation) (err error) {
 	target := c.locationStack.peek() // Note this is peek!
 	if err = c.compileEnsureOnRegister(target); err != nil {
 		return err
 	}
 
 	// First shift left by one to clear the sign bit, and then shift right by one.
-	if o.Type == wazeroir.Float32 {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		c.assembler.CompileConstToRegister(amd64.PSLLD, 1, target.register)
 		c.assembler.CompileConstToRegister(amd64.PSRLD, 1, target.register)
 	} else {
@@ -1755,7 +1771,7 @@ func (c *amd64Compiler) compileAbs(o wazeroir.OperationAbs) (err error) {
 }
 
 // compileNeg implements compiler.compileNeg for the amd64 architecture.
-func (c *amd64Compiler) compileNeg(o wazeroir.OperationNeg) (err error) {
+func (c *amd64Compiler) compileNeg(o wazeroir.UnionOperation) (err error) {
 	target := c.locationStack.peek() // Note this is peek!
 	if err := c.compileEnsureOnRegister(target); err != nil {
 		return err
@@ -1769,7 +1785,7 @@ func (c *amd64Compiler) compileNeg(o wazeroir.OperationNeg) (err error) {
 	// First we move the sign-bit mask (placed in memory) to the tmp register,
 	// since we cannot take XOR directly with float reg and const.
 	// And then negate the value by XOR it with the sign-bit mask.
-	if o.Type == wazeroir.Float32 {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		err = c.assembler.CompileStaticConstToRegister(amd64.MOVL, asm.NewStaticConst(u32.LeBytes(float32SignBitMask)), tmpReg)
 		if err != nil {
 			return err
@@ -1786,30 +1802,30 @@ func (c *amd64Compiler) compileNeg(o wazeroir.OperationNeg) (err error) {
 }
 
 // compileCeil implements compiler.compileCeil for the amd64 architecture.
-func (c *amd64Compiler) compileCeil(o wazeroir.OperationCeil) (err error) {
+func (c *amd64Compiler) compileCeil(o wazeroir.UnionOperation) (err error) {
 	// Internally, ceil can be performed via ROUND instruction with 0x02 mode.
 	// See https://android.googlesource.com/platform/bionic/+/882b8af/libm/x86_64/ceilf.S for example.
-	return c.compileRoundInstruction(o.Type == wazeroir.Float32, 0x02)
+	return c.compileRoundInstruction(wazeroir.Float(o.B1) == wazeroir.Float32, 0x02)
 }
 
 // compileFloor implements compiler.compileFloor for the amd64 architecture.
-func (c *amd64Compiler) compileFloor(o wazeroir.OperationFloor) (err error) {
+func (c *amd64Compiler) compileFloor(o wazeroir.UnionOperation) (err error) {
 	// Internally, floor can be performed via ROUND instruction with 0x01 mode.
 	// See https://android.googlesource.com/platform/bionic/+/882b8af/libm/x86_64/floorf.S for example.
-	return c.compileRoundInstruction(o.Type == wazeroir.Float32, 0x01)
+	return c.compileRoundInstruction(wazeroir.Float(o.B1) == wazeroir.Float32, 0x01)
 }
 
 // compileTrunc implements compiler.compileTrunc for the amd64 architecture.
-func (c *amd64Compiler) compileTrunc(o wazeroir.OperationTrunc) error {
+func (c *amd64Compiler) compileTrunc(o wazeroir.UnionOperation) error {
 	// Internally, trunc can be performed via ROUND instruction with 0x03 mode.
 	// See https://android.googlesource.com/platform/bionic/+/882b8af/libm/x86_64/truncf.S for example.
-	return c.compileRoundInstruction(o.Type == wazeroir.Float32, 0x03)
+	return c.compileRoundInstruction(wazeroir.Float(o.B1) == wazeroir.Float32, 0x03)
 }
 
 // compileNearest implements compiler.compileNearest for the amd64 architecture.
-func (c *amd64Compiler) compileNearest(o wazeroir.OperationNearest) error {
+func (c *amd64Compiler) compileNearest(o wazeroir.UnionOperation) error {
 	// Nearest can be performed via ROUND instruction with 0x00 mode.
-	return c.compileRoundInstruction(o.Type == wazeroir.Float32, 0x00)
+	return c.compileRoundInstruction(wazeroir.Float(o.B1) == wazeroir.Float32, 0x00)
 }
 
 func (c *amd64Compiler) compileRoundInstruction(is32Bit bool, mode int64) error {
@@ -1827,8 +1843,8 @@ func (c *amd64Compiler) compileRoundInstruction(is32Bit bool, mode int64) error
 }
 
 // compileMin implements compiler.compileMin for the amd64 architecture.
-func (c *amd64Compiler) compileMin(o wazeroir.OperationMin) error {
-	is32Bit := o.Type == wazeroir.Float32
+func (c *amd64Compiler) compileMin(o wazeroir.UnionOperation) error {
+	is32Bit := wazeroir.Float(o.B1) == wazeroir.Float32
 	if is32Bit {
 		return c.compileMinOrMax(is32Bit, true, amd64.MINSS)
 	} else {
@@ -1837,8 +1853,8 @@ func (c *amd64Compiler) compileMin(o wazeroir.OperationMin) error {
 }
 
 // compileMax implements compiler.compileMax for the amd64 architecture.
-func (c *amd64Compiler) compileMax(o wazeroir.OperationMax) error {
-	is32Bit := o.Type == wazeroir.Float32
+func (c *amd64Compiler) compileMax(o wazeroir.UnionOperation) error {
+	is32Bit := wazeroir.Float(o.B1) == wazeroir.Float32
 	if is32Bit {
 		return c.compileMinOrMax(is32Bit, false, amd64.MAXSS)
 	} else {
@@ -1940,8 +1956,8 @@ func (c *amd64Compiler) compileMinOrMax(is32Bit, isMin bool, minOrMaxInstruction
 }
 
 // compileCopysign implements compiler.compileCopysign for the amd64 architecture.
-func (c *amd64Compiler) compileCopysign(o wazeroir.OperationCopysign) error {
-	is32Bit := o.Type == wazeroir.Float32
+func (c *amd64Compiler) compileCopysign(o wazeroir.UnionOperation) error {
+	is32Bit := wazeroir.Float(o.B1) == wazeroir.Float32
 
 	x2 := c.locationStack.pop()
 	if err := c.compileEnsureOnRegister(x2); err != nil {
@@ -2005,12 +2021,12 @@ func (c *amd64Compiler) compileCopysign(o wazeroir.OperationCopysign) error {
 }
 
 // compileSqrt implements compiler.compileSqrt for the amd64 architecture.
-func (c *amd64Compiler) compileSqrt(o wazeroir.OperationSqrt) error {
+func (c *amd64Compiler) compileSqrt(o wazeroir.UnionOperation) error {
 	target := c.locationStack.peek() // Note this is peek!
 	if err := c.compileEnsureOnRegister(target); err != nil {
 		return err
 	}
-	if o.Type == wazeroir.Float32 {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		c.assembler.CompileRegisterToRegister(amd64.SQRTSS, target.register, target.register)
 	} else {
 		c.assembler.CompileRegisterToRegister(amd64.SQRTSD, target.register, target.register)
@@ -2870,13 +2886,13 @@ func (c *amd64Compiler) compileExtendImpl(inst asm.Instruction, destinationType
 }
 
 // compileEq implements compiler.compileEq for the amd64 architecture.
-func (c *amd64Compiler) compileEq(o wazeroir.OperationEq) error {
-	return c.compileEqOrNe(o.Type, true)
+func (c *amd64Compiler) compileEq(o wazeroir.UnionOperation) error {
+	return c.compileEqOrNe(wazeroir.UnsignedType(o.B1), true)
 }
 
 // compileNe implements compiler.compileNe for the amd64 architecture.
-func (c *amd64Compiler) compileNe(o wazeroir.OperationNe) error {
-	return c.compileEqOrNe(o.Type, false)
+func (c *amd64Compiler) compileNe(o wazeroir.UnionOperation) error {
+	return c.compileEqOrNe(wazeroir.UnsignedType(o.B1), false)
 }
 
 func (c *amd64Compiler) compileEqOrNe(t wazeroir.UnsignedType, shouldEqual bool) (err error) {
@@ -2984,13 +3000,14 @@ func (c *amd64Compiler) compileEqOrNeForFloats(x1Reg, x2Reg asm.Register, cmpIns
 }
 
 // compileEqz implements compiler.compileEqz for the amd64 architecture.
-func (c *amd64Compiler) compileEqz(o wazeroir.OperationEqz) (err error) {
+func (c *amd64Compiler) compileEqz(o wazeroir.UnionOperation) (err error) {
 	v := c.locationStack.pop()
 	if err = c.compileEnsureOnRegister(v); err != nil {
 		return err
 	}
 
-	switch o.Type {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		err = c.assembler.CompileStaticConstToRegister(amd64.CMPL, asm.NewStaticConst([]byte{0, 0, 0, 0}), v.register)
 	case wazeroir.UnsignedInt64:
@@ -3010,7 +3027,7 @@ func (c *amd64Compiler) compileEqz(o wazeroir.OperationEqz) (err error) {
 }
 
 // compileLt implements compiler.compileLt for the amd64 architecture.
-func (c *amd64Compiler) compileLt(o wazeroir.OperationLt) error {
+func (c *amd64Compiler) compileLt(o wazeroir.UnionOperation) error {
 	x2 := c.locationStack.pop()
 	if err := c.compileEnsureOnRegister(x2); err != nil {
 		return err
@@ -3024,7 +3041,8 @@ func (c *amd64Compiler) compileLt(o wazeroir.OperationLt) error {
 	// Emit the compare instruction.
 	var resultConditionState asm.ConditionalRegisterState
 	var inst asm.Instruction
-	switch o.Type {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeInt32:
 		resultConditionState = amd64.ConditionalRegisterStateL
 		inst = amd64.CMPL
@@ -3057,7 +3075,7 @@ func (c *amd64Compiler) compileLt(o wazeroir.OperationLt) error {
 }
 
 // compileGt implements compiler.compileGt for the amd64 architecture.
-func (c *amd64Compiler) compileGt(o wazeroir.OperationGt) error {
+func (c *amd64Compiler) compileGt(o wazeroir.UnionOperation) error {
 	x2 := c.locationStack.pop()
 	if err := c.compileEnsureOnRegister(x2); err != nil {
 		return err
@@ -3070,7 +3088,8 @@ func (c *amd64Compiler) compileGt(o wazeroir.OperationGt) error {
 
 	// Emit the compare instruction.
 	var resultConditionState asm.ConditionalRegisterState
-	switch o.Type {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeInt32:
 		resultConditionState = amd64.ConditionalRegisterStateG
 		c.assembler.CompileRegisterToRegister(amd64.CMPL, x1.register, x2.register)
@@ -3102,7 +3121,7 @@ func (c *amd64Compiler) compileGt(o wazeroir.OperationGt) error {
 }
 
 // compileLe implements compiler.compileLe for the amd64 architecture.
-func (c *amd64Compiler) compileLe(o wazeroir.OperationLe) error {
+func (c *amd64Compiler) compileLe(o wazeroir.UnionOperation) error {
 	x2 := c.locationStack.pop()
 	if err := c.compileEnsureOnRegister(x2); err != nil {
 		return err
@@ -3116,7 +3135,8 @@ func (c *amd64Compiler) compileLe(o wazeroir.OperationLe) error {
 	// Emit the compare instruction.
 	var inst asm.Instruction
 	var resultConditionState asm.ConditionalRegisterState
-	switch o.Type {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeInt32:
 		resultConditionState = amd64.ConditionalRegisterStateLE
 		inst = amd64.CMPL
@@ -3149,7 +3169,7 @@ func (c *amd64Compiler) compileLe(o wazeroir.OperationLe) error {
 }
 
 // compileGe implements compiler.compileGe for the amd64 architecture.
-func (c *amd64Compiler) compileGe(o wazeroir.OperationGe) error {
+func (c *amd64Compiler) compileGe(o wazeroir.UnionOperation) error {
 	x2 := c.locationStack.pop()
 	if err := c.compileEnsureOnRegister(x2); err != nil {
 		return err
@@ -3162,7 +3182,8 @@ func (c *amd64Compiler) compileGe(o wazeroir.OperationGe) error {
 
 	// Emit the compare instruction.
 	var resultConditionState asm.ConditionalRegisterState
-	switch o.Type {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeInt32:
 		c.assembler.CompileRegisterToRegister(amd64.CMPL, x1.register, x2.register)
 		resultConditionState = amd64.ConditionalRegisterStateGE
diff --git a/internal/engine/compiler/impl_arm64.go b/internal/engine/compiler/impl_arm64.go
index 53ec3fc9..88e00853 100644
--- a/internal/engine/compiler/impl_arm64.go
+++ b/internal/engine/compiler/impl_arm64.go
@@ -1343,7 +1343,7 @@ func (c *arm64Compiler) compilePick(o wazeroir.OperationPick) error {
 }
 
 // compileAdd implements compiler.compileAdd for the arm64 architecture.
-func (c *arm64Compiler) compileAdd(o wazeroir.OperationAdd) error {
+func (c *arm64Compiler) compileAdd(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1359,7 +1359,8 @@ func (c *arm64Compiler) compileAdd(o wazeroir.OperationAdd) error {
 	}
 
 	var inst asm.Instruction
-	switch o.Type {
+	unsignedType := wazeroir.UnsignedType(o.B1)
+	switch unsignedType {
 	case wazeroir.UnsignedTypeI32:
 		inst = arm64.ADDW
 	case wazeroir.UnsignedTypeI64:
@@ -1377,7 +1378,7 @@ func (c *arm64Compiler) compileAdd(o wazeroir.OperationAdd) error {
 }
 
 // compileSub implements compiler.compileSub for the arm64 architecture.
-func (c *arm64Compiler) compileSub(o wazeroir.OperationSub) error {
+func (c *arm64Compiler) compileSub(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1398,7 +1399,8 @@ func (c *arm64Compiler) compileSub(o wazeroir.OperationSub) error {
 
 	var inst asm.Instruction
 	var vt runtimeValueType
-	switch o.Type {
+	unsignedType := wazeroir.UnsignedType(o.B1)
+	switch unsignedType {
 	case wazeroir.UnsignedTypeI32:
 		inst = arm64.SUBW
 		vt = runtimeValueTypeI32
@@ -1419,7 +1421,7 @@ func (c *arm64Compiler) compileSub(o wazeroir.OperationSub) error {
 }
 
 // compileMul implements compiler.compileMul for the arm64 architecture.
-func (c *arm64Compiler) compileMul(o wazeroir.OperationMul) error {
+func (c *arm64Compiler) compileMul(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1433,7 +1435,8 @@ func (c *arm64Compiler) compileMul(o wazeroir.OperationMul) error {
 
 	var inst asm.Instruction
 	var vt runtimeValueType
-	switch o.Type {
+	unsignedType := wazeroir.UnsignedType(o.B1)
+	switch unsignedType {
 	case wazeroir.UnsignedTypeI32:
 		inst = arm64.MULW
 		vt = runtimeValueTypeI32
@@ -1455,12 +1458,13 @@ func (c *arm64Compiler) compileMul(o wazeroir.OperationMul) error {
 }
 
 // compileClz implements compiler.compileClz for the arm64 architecture.
-func (c *arm64Compiler) compileClz(o wazeroir.OperationClz) error {
+func (c *arm64Compiler) compileClz(o wazeroir.UnionOperation) error {
 	v, err := c.popValueOnRegister()
 	if err != nil {
 		return err
 	}
 
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
 	if isZeroRegister(v.register) {
 		// If the target is zero register, the result is always 32 (or 64 for 64-bits),
 		// so we allocate a register and put the const on it.
@@ -1469,7 +1473,7 @@ func (c *arm64Compiler) compileClz(o wazeroir.OperationClz) error {
 			return err
 		}
 		var vt runtimeValueType
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			vt = runtimeValueTypeI32
 			c.assembler.CompileConstToRegister(arm64.MOVW, 32, reg)
 		} else {
@@ -1482,7 +1486,7 @@ func (c *arm64Compiler) compileClz(o wazeroir.OperationClz) error {
 
 	reg := v.register
 	var vt runtimeValueType
-	if o.Type == wazeroir.UnsignedInt32 {
+	if unsignedInt == wazeroir.UnsignedInt32 {
 		vt = runtimeValueTypeI32
 		c.assembler.CompileRegisterToRegister(arm64.CLZW, reg, reg)
 	} else {
@@ -1494,12 +1498,13 @@ func (c *arm64Compiler) compileClz(o wazeroir.OperationClz) error {
 }
 
 // compileCtz implements compiler.compileCtz for the arm64 architecture.
-func (c *arm64Compiler) compileCtz(o wazeroir.OperationCtz) error {
+func (c *arm64Compiler) compileCtz(o wazeroir.UnionOperation) error {
 	v, err := c.popValueOnRegister()
 	if err != nil {
 		return err
 	}
 
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
 	reg := v.register
 	if isZeroRegister(reg) {
 		// If the target is zero register, the result is always 32 (or 64 for 64-bits),
@@ -1509,7 +1514,7 @@ func (c *arm64Compiler) compileCtz(o wazeroir.OperationCtz) error {
 			return err
 		}
 		var vt runtimeValueType
-		if o.Type == wazeroir.UnsignedInt32 {
+		if unsignedInt == wazeroir.UnsignedInt32 {
 			vt = runtimeValueTypeI32
 			c.assembler.CompileConstToRegister(arm64.MOVW, 32, reg)
 		} else {
@@ -1524,7 +1529,7 @@ func (c *arm64Compiler) compileCtz(o wazeroir.OperationCtz) error {
 	// we reverse the bits first, and then do CLZ, which is exactly the same as
 	// gcc implements __builtin_ctz for arm64.
 	var vt runtimeValueType
-	if o.Type == wazeroir.UnsignedInt32 {
+	if unsignedInt == wazeroir.UnsignedInt32 {
 		vt = runtimeValueTypeI32
 		c.assembler.CompileRegisterToRegister(arm64.RBITW, reg, reg)
 		c.assembler.CompileRegisterToRegister(arm64.CLZW, reg, reg)
@@ -1538,7 +1543,7 @@ func (c *arm64Compiler) compileCtz(o wazeroir.OperationCtz) error {
 }
 
 // compilePopcnt implements compiler.compilePopcnt for the arm64 architecture.
-func (c *arm64Compiler) compilePopcnt(o wazeroir.OperationPopcnt) error {
+func (c *arm64Compiler) compilePopcnt(o wazeroir.UnionOperation) error {
 	v, err := c.popValueOnRegister()
 	if err != nil {
 		return err
@@ -1566,7 +1571,8 @@ func (c *arm64Compiler) compilePopcnt(o wazeroir.OperationPopcnt) error {
 	//    UADDLV  V0.B8, V0
 	//
 	var movInst asm.Instruction
-	if o.Type == wazeroir.UnsignedInt32 {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	if unsignedInt == wazeroir.UnsignedInt32 {
 		movInst = arm64.FMOVS
 	} else {
 		movInst = arm64.FMOVD
@@ -1584,17 +1590,19 @@ func (c *arm64Compiler) compilePopcnt(o wazeroir.OperationPopcnt) error {
 }
 
 // compileDiv implements compiler.compileDiv for the arm64 architecture.
-func (c *arm64Compiler) compileDiv(o wazeroir.OperationDiv) error {
+func (c *arm64Compiler) compileDiv(o wazeroir.UnionOperation) error {
 	dividend, divisor, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
 	}
 
+	signedType := wazeroir.SignedType(o.B1)
+
 	// If the divisor is on the zero register, exit from the function deterministically.
 	if isZeroRegister(divisor.register) {
 		// Push any value so that the subsequent instruction can have a consistent location stack state.
 		v := c.locationStack.pushRuntimeValueLocationOnStack()
-		switch o.Type {
+		switch signedType {
 		case wazeroir.SignedTypeInt32, wazeroir.SignedTypeUint32:
 			v.valueType = runtimeValueTypeI32
 		case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
@@ -1606,7 +1614,7 @@ func (c *arm64Compiler) compileDiv(o wazeroir.OperationDiv) error {
 
 	var inst asm.Instruction
 	var vt runtimeValueType
-	switch o.Type {
+	switch signedType {
 	case wazeroir.SignedTypeUint32:
 		inst = arm64.UDIVW
 		if err := c.compileIntegerDivPrecheck(true, false, dividend.register, divisor.register); err != nil {
@@ -1704,7 +1712,7 @@ func (c *arm64Compiler) compileIntegerDivPrecheck(is32Bit, isSigned bool, divide
 }
 
 // compileRem implements compiler.compileRem for the arm64 architecture.
-func (c *arm64Compiler) compileRem(o wazeroir.OperationRem) error {
+func (c *arm64Compiler) compileRem(o wazeroir.UnionOperation) error {
 	dividend, divisor, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1723,7 +1731,8 @@ func (c *arm64Compiler) compileRem(o wazeroir.OperationRem) error {
 	}
 
 	var divInst, msubInst, cmpInst asm.Instruction
-	switch o.Type {
+	signedInt := wazeroir.SignedInt(o.B1)
+	switch signedInt {
 	case wazeroir.SignedUint32:
 		divInst = arm64.UDIVW
 		msubInst = arm64.MSUBW
@@ -1777,7 +1786,7 @@ func (c *arm64Compiler) compileRem(o wazeroir.OperationRem) error {
 }
 
 // compileAnd implements compiler.compileAnd for the arm64 architecture.
-func (c *arm64Compiler) compileAnd(o wazeroir.OperationAnd) error {
+func (c *arm64Compiler) compileAnd(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1798,7 +1807,8 @@ func (c *arm64Compiler) compileAnd(o wazeroir.OperationAnd) error {
 	}
 
 	var inst asm.Instruction
-	switch o.Type {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		inst = arm64.ANDW
 	case wazeroir.UnsignedInt64:
@@ -1811,7 +1821,7 @@ func (c *arm64Compiler) compileAnd(o wazeroir.OperationAnd) error {
 }
 
 // compileOr implements compiler.compileOr for the arm64 architecture.
-func (c *arm64Compiler) compileOr(o wazeroir.OperationOr) error {
+func (c *arm64Compiler) compileOr(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1827,7 +1837,8 @@ func (c *arm64Compiler) compileOr(o wazeroir.OperationOr) error {
 	}
 
 	var inst asm.Instruction
-	switch o.Type {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		inst = arm64.ORRW
 	case wazeroir.UnsignedInt64:
@@ -1840,7 +1851,7 @@ func (c *arm64Compiler) compileOr(o wazeroir.OperationOr) error {
 }
 
 // compileXor implements compiler.compileXor for the arm64 architecture.
-func (c *arm64Compiler) compileXor(o wazeroir.OperationXor) error {
+func (c *arm64Compiler) compileXor(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1854,7 +1865,8 @@ func (c *arm64Compiler) compileXor(o wazeroir.OperationXor) error {
 	}
 
 	var inst asm.Instruction
-	switch o.Type {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		inst = arm64.EORW
 	case wazeroir.UnsignedInt64:
@@ -1867,7 +1879,7 @@ func (c *arm64Compiler) compileXor(o wazeroir.OperationXor) error {
 }
 
 // compileShl implements compiler.compileShl for the arm64 architecture.
-func (c *arm64Compiler) compileShl(o wazeroir.OperationShl) error {
+func (c *arm64Compiler) compileShl(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1879,7 +1891,8 @@ func (c *arm64Compiler) compileShl(o wazeroir.OperationShl) error {
 	}
 
 	var inst asm.Instruction
-	switch o.Type {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		inst = arm64.LSLW
 	case wazeroir.UnsignedInt64:
@@ -1892,7 +1905,7 @@ func (c *arm64Compiler) compileShl(o wazeroir.OperationShl) error {
 }
 
 // compileShr implements compiler.compileShr for the arm64 architecture.
-func (c *arm64Compiler) compileShr(o wazeroir.OperationShr) error {
+func (c *arm64Compiler) compileShr(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1904,7 +1917,8 @@ func (c *arm64Compiler) compileShr(o wazeroir.OperationShr) error {
 	}
 
 	var inst asm.Instruction
-	switch o.Type {
+	signedInt := wazeroir.SignedInt(o.B1)
+	switch signedInt {
 	case wazeroir.SignedInt32:
 		inst = arm64.ASRW
 	case wazeroir.SignedInt64:
@@ -1921,7 +1935,7 @@ func (c *arm64Compiler) compileShr(o wazeroir.OperationShr) error {
 }
 
 // compileRotl implements compiler.compileRotl for the arm64 architecture.
-func (c *arm64Compiler) compileRotl(o wazeroir.OperationRotl) error {
+func (c *arm64Compiler) compileRotl(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1933,7 +1947,8 @@ func (c *arm64Compiler) compileRotl(o wazeroir.OperationRotl) error {
 	}
 
 	var inst, neginst asm.Instruction
-	switch o.Type {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		inst = arm64.RORW
 		neginst = arm64.NEGW
@@ -1952,7 +1967,7 @@ func (c *arm64Compiler) compileRotl(o wazeroir.OperationRotl) error {
 }
 
 // compileRotr implements compiler.compileRotr for the arm64 architecture.
-func (c *arm64Compiler) compileRotr(o wazeroir.OperationRotr) error {
+func (c *arm64Compiler) compileRotr(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -1964,7 +1979,8 @@ func (c *arm64Compiler) compileRotr(o wazeroir.OperationRotr) error {
 	}
 
 	var inst asm.Instruction
-	switch o.Type {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		inst = arm64.RORW
 	case wazeroir.UnsignedInt64:
@@ -1977,8 +1993,8 @@ func (c *arm64Compiler) compileRotr(o wazeroir.OperationRotr) error {
 }
 
 // compileAbs implements compiler.compileAbs for the arm64 architecture.
-func (c *arm64Compiler) compileAbs(o wazeroir.OperationAbs) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileAbs(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleUnop(arm64.FABSS, runtimeValueTypeF32)
 	} else {
 		return c.compileSimpleUnop(arm64.FABSD, runtimeValueTypeF64)
@@ -1986,8 +2002,8 @@ func (c *arm64Compiler) compileAbs(o wazeroir.OperationAbs) error {
 }
 
 // compileNeg implements compiler.compileNeg for the arm64 architecture.
-func (c *arm64Compiler) compileNeg(o wazeroir.OperationNeg) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileNeg(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleUnop(arm64.FNEGS, runtimeValueTypeF32)
 	} else {
 		return c.compileSimpleUnop(arm64.FNEGD, runtimeValueTypeF64)
@@ -1995,8 +2011,8 @@ func (c *arm64Compiler) compileNeg(o wazeroir.OperationNeg) error {
 }
 
 // compileCeil implements compiler.compileCeil for the arm64 architecture.
-func (c *arm64Compiler) compileCeil(o wazeroir.OperationCeil) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileCeil(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleUnop(arm64.FRINTPS, runtimeValueTypeF32)
 	} else {
 		return c.compileSimpleUnop(arm64.FRINTPD, runtimeValueTypeF64)
@@ -2004,8 +2020,8 @@ func (c *arm64Compiler) compileCeil(o wazeroir.OperationCeil) error {
 }
 
 // compileFloor implements compiler.compileFloor for the arm64 architecture.
-func (c *arm64Compiler) compileFloor(o wazeroir.OperationFloor) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileFloor(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleUnop(arm64.FRINTMS, runtimeValueTypeF32)
 	} else {
 		return c.compileSimpleUnop(arm64.FRINTMD, runtimeValueTypeF64)
@@ -2013,8 +2029,8 @@ func (c *arm64Compiler) compileFloor(o wazeroir.OperationFloor) error {
 }
 
 // compileTrunc implements compiler.compileTrunc for the arm64 architecture.
-func (c *arm64Compiler) compileTrunc(o wazeroir.OperationTrunc) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileTrunc(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleUnop(arm64.FRINTZS, runtimeValueTypeF32)
 	} else {
 		return c.compileSimpleUnop(arm64.FRINTZD, runtimeValueTypeF64)
@@ -2022,8 +2038,8 @@ func (c *arm64Compiler) compileTrunc(o wazeroir.OperationTrunc) error {
 }
 
 // compileNearest implements compiler.compileNearest for the arm64 architecture.
-func (c *arm64Compiler) compileNearest(o wazeroir.OperationNearest) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileNearest(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleUnop(arm64.FRINTNS, runtimeValueTypeF32)
 	} else {
 		return c.compileSimpleUnop(arm64.FRINTND, runtimeValueTypeF64)
@@ -2031,8 +2047,8 @@ func (c *arm64Compiler) compileNearest(o wazeroir.OperationNearest) error {
 }
 
 // compileSqrt implements compiler.compileSqrt for the arm64 architecture.
-func (c *arm64Compiler) compileSqrt(o wazeroir.OperationSqrt) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileSqrt(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleUnop(arm64.FSQRTS, runtimeValueTypeF32)
 	} else {
 		return c.compileSimpleUnop(arm64.FSQRTD, runtimeValueTypeF64)
@@ -2040,8 +2056,8 @@ func (c *arm64Compiler) compileSqrt(o wazeroir.OperationSqrt) error {
 }
 
 // compileMin implements compiler.compileMin for the arm64 architecture.
-func (c *arm64Compiler) compileMin(o wazeroir.OperationMin) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileMin(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleFloatBinop(arm64.FMINS)
 	} else {
 		return c.compileSimpleFloatBinop(arm64.FMIND)
@@ -2049,8 +2065,8 @@ func (c *arm64Compiler) compileMin(o wazeroir.OperationMin) error {
 }
 
 // compileMax implements compiler.compileMax for the arm64 architecture.
-func (c *arm64Compiler) compileMax(o wazeroir.OperationMax) error {
-	if o.Type == wazeroir.Float32 {
+func (c *arm64Compiler) compileMax(o wazeroir.UnionOperation) error {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		return c.compileSimpleFloatBinop(arm64.FMAXS)
 	} else {
 		return c.compileSimpleFloatBinop(arm64.FMAXD)
@@ -2068,7 +2084,7 @@ func (c *arm64Compiler) compileSimpleFloatBinop(inst asm.Instruction) error {
 }
 
 // compileCopysign implements compiler.compileCopysign for the arm64 architecture.
-func (c *arm64Compiler) compileCopysign(o wazeroir.OperationCopysign) error {
+func (c *arm64Compiler) compileCopysign(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -2076,7 +2092,7 @@ func (c *arm64Compiler) compileCopysign(o wazeroir.OperationCopysign) error {
 
 	var ldr asm.Instruction
 	var minValueOffsetInVM int64
-	if o.Type == wazeroir.Float32 {
+	if wazeroir.Float(o.B1) == wazeroir.Float32 {
 		ldr = arm64.FLDRS
 		minValueOffsetInVM = arm64CallEngineArchContextMinimum32BitSignedIntOffset
 	} else {
@@ -2346,13 +2362,13 @@ func (c *arm64Compiler) compileSimpleUnop(inst asm.Instruction, resultRuntimeVal
 }
 
 // compileEq implements compiler.compileEq for the arm64 architecture.
-func (c *arm64Compiler) compileEq(o wazeroir.OperationEq) error {
-	return c.emitEqOrNe(true, o.Type)
+func (c *arm64Compiler) compileEq(o wazeroir.UnionOperation) error {
+	return c.emitEqOrNe(true, wazeroir.UnsignedType(o.B1))
 }
 
 // compileNe implements compiler.compileNe for the arm64 architecture.
-func (c *arm64Compiler) compileNe(o wazeroir.OperationNe) error {
-	return c.emitEqOrNe(false, o.Type)
+func (c *arm64Compiler) compileNe(o wazeroir.UnionOperation) error {
+	return c.emitEqOrNe(false, wazeroir.UnsignedType(o.B1))
 }
 
 // emitEqOrNe implements compiler.compileEq and compiler.compileNe for the arm64 architecture.
@@ -2386,14 +2402,15 @@ func (c *arm64Compiler) emitEqOrNe(isEq bool, unsignedType wazeroir.UnsignedType
 }
 
 // compileEqz implements compiler.compileEqz for the arm64 architecture.
-func (c *arm64Compiler) compileEqz(o wazeroir.OperationEqz) error {
+func (c *arm64Compiler) compileEqz(o wazeroir.UnionOperation) error {
 	x1, err := c.popValueOnRegister()
 	if err != nil {
 		return err
 	}
 
 	var inst asm.Instruction
-	switch o.Type {
+	unsignedInt := wazeroir.UnsignedInt(o.B1)
+	switch unsignedInt {
 	case wazeroir.UnsignedInt32:
 		inst = arm64.CMPW
 	case wazeroir.UnsignedInt64:
@@ -2408,7 +2425,7 @@ func (c *arm64Compiler) compileEqz(o wazeroir.OperationEqz) error {
 }
 
 // compileLt implements compiler.compileLt for the arm64 architecture.
-func (c *arm64Compiler) compileLt(o wazeroir.OperationLt) error {
+func (c *arm64Compiler) compileLt(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -2416,7 +2433,8 @@ func (c *arm64Compiler) compileLt(o wazeroir.OperationLt) error {
 
 	var inst asm.Instruction
 	var conditionalRegister asm.ConditionalRegisterState
-	switch o.Type {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeUint32:
 		inst = arm64.CMPW
 		conditionalRegister = arm64.CondLO
@@ -2445,7 +2463,7 @@ func (c *arm64Compiler) compileLt(o wazeroir.OperationLt) error {
 }
 
 // compileGt implements compiler.compileGt for the arm64 architecture.
-func (c *arm64Compiler) compileGt(o wazeroir.OperationGt) error {
+func (c *arm64Compiler) compileGt(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -2453,7 +2471,8 @@ func (c *arm64Compiler) compileGt(o wazeroir.OperationGt) error {
 
 	var inst asm.Instruction
 	var conditionalRegister asm.ConditionalRegisterState
-	switch o.Type {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeUint32:
 		inst = arm64.CMPW
 		conditionalRegister = arm64.CondHI
@@ -2482,7 +2501,7 @@ func (c *arm64Compiler) compileGt(o wazeroir.OperationGt) error {
 }
 
 // compileLe implements compiler.compileLe for the arm64 architecture.
-func (c *arm64Compiler) compileLe(o wazeroir.OperationLe) error {
+func (c *arm64Compiler) compileLe(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -2490,7 +2509,8 @@ func (c *arm64Compiler) compileLe(o wazeroir.OperationLe) error {
 
 	var inst asm.Instruction
 	var conditionalRegister asm.ConditionalRegisterState
-	switch o.Type {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeUint32:
 		inst = arm64.CMPW
 		conditionalRegister = arm64.CondLS
@@ -2519,7 +2539,7 @@ func (c *arm64Compiler) compileLe(o wazeroir.OperationLe) error {
 }
 
 // compileGe implements compiler.compileGe for the arm64 architecture.
-func (c *arm64Compiler) compileGe(o wazeroir.OperationGe) error {
+func (c *arm64Compiler) compileGe(o wazeroir.UnionOperation) error {
 	x1, x2, err := c.popTwoValuesOnRegisters()
 	if err != nil {
 		return err
@@ -2527,7 +2547,8 @@ func (c *arm64Compiler) compileGe(o wazeroir.OperationGe) error {
 
 	var inst asm.Instruction
 	var conditionalRegister asm.ConditionalRegisterState
-	switch o.Type {
+	signedType := wazeroir.SignedType(o.B1)
+	switch signedType {
 	case wazeroir.SignedTypeUint32:
 		inst = arm64.CMPW
 		conditionalRegister = arm64.CondHS
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index 2017a18b..664283a6 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -298,6 +298,38 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 			case wazeroir.OperationKindConstI64:
 			case wazeroir.OperationKindConstF32:
 			case wazeroir.OperationKindConstF64:
+			case wazeroir.OperationKindEq:
+			case wazeroir.OperationKindNe:
+			case wazeroir.OperationKindEqz:
+			case wazeroir.OperationKindLt:
+			case wazeroir.OperationKindGt:
+			case wazeroir.OperationKindLe:
+			case wazeroir.OperationKindGe:
+			case wazeroir.OperationKindAdd:
+			case wazeroir.OperationKindSub:
+			case wazeroir.OperationKindMul:
+			case wazeroir.OperationKindClz:
+			case wazeroir.OperationKindCtz:
+			case wazeroir.OperationKindPopcnt:
+			case wazeroir.OperationKindDiv:
+			case wazeroir.OperationKindRem:
+			case wazeroir.OperationKindAnd:
+			case wazeroir.OperationKindOr:
+			case wazeroir.OperationKindXor:
+			case wazeroir.OperationKindShl:
+			case wazeroir.OperationKindShr:
+			case wazeroir.OperationKindRotl:
+			case wazeroir.OperationKindRotr:
+			case wazeroir.OperationKindAbs:
+			case wazeroir.OperationKindNeg:
+			case wazeroir.OperationKindCeil:
+			case wazeroir.OperationKindFloor:
+			case wazeroir.OperationKindTrunc:
+			case wazeroir.OperationKindNearest:
+			case wazeroir.OperationKindSqrt:
+			case wazeroir.OperationKindMin:
+			case wazeroir.OperationKindMax:
+			case wazeroir.OperationKindCopysign:
 
 			case wazeroir.OperationKindI32ReinterpretFromF32,
 				wazeroir.OperationKindI64ReinterpretFromF64,
@@ -436,70 +468,6 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 			op.U1 = uint64(o.Arg.Alignment)
 			op.U2 = uint64(o.Arg.Offset)
 		// const ops...
-		case wazeroir.OperationEq:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationNe:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationEqz:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationLt:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationGt:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationLe:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationGe:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationAdd:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationSub:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationMul:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationClz:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationCtz:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationPopcnt:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationDiv:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationRem:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationAnd:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationOr:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationXor:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationShl:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationShr:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationRotl:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationRotr:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationAbs:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationNeg:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationCeil:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationFloor:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationTrunc:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationNearest:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationSqrt:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationMin:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationMax:
-			op.B1 = byte(o.Type)
-		case wazeroir.OperationCopysign:
-			op.B1 = byte(o.Type)
 		case wazeroir.OperationITruncFromF:
 			op.B1 = byte(o.InputType)
 			op.B2 = byte(o.OutputType)
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index d768c7e3..0b1cfb00 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -58,6 +58,17 @@ type (
 	//
 	// This implements api.Module.
 	ModuleInstance struct {
+		// Closed is used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
+		//
+		// The update value is closedType + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
+		//
+		// Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
+		// See /RATIONALE.md
+		//
+		// TODO: Retype this to atomic.Unit64 when Go 1.18 is no longer supported. Until then, keep Closed at the top of
+		// this struct. See PR #1299 for an implementation and discussion.
+		Closed uint64
+
 		ModuleName     string
 		Exports        map[string]*Export
 		Globals        []*GlobalInstance
@@ -94,14 +105,6 @@ type (
 		//	  security implications.
 		Sys *internalsys.Context
 
-		// closed is the pointer used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
-		//
-		// The update value is closedType + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
-		//
-		// Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
-		// See /RATIONALE.md
-		Closed uint64
-
 		// CodeCloser is non-nil when the code should be closed after this module.
 		CodeCloser api.Closer
 
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index 04de7d16..4fea98b4 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -1133,395 +1133,395 @@ operatorSwitch:
 		)
 	case wasm.OpcodeI32Eqz:
 		c.emit(
-			OperationEqz{Type: UnsignedInt32},
+			NewOperationEqz(UnsignedInt32),
 		)
 	case wasm.OpcodeI32Eq:
 		c.emit(
-			OperationEq{Type: UnsignedTypeI32},
+			NewOperationEq(UnsignedTypeI32),
 		)
 	case wasm.OpcodeI32Ne:
 		c.emit(
-			OperationNe{Type: UnsignedTypeI32},
+			NewOperationNe(UnsignedTypeI32),
 		)
 	case wasm.OpcodeI32LtS:
 		c.emit(
-			OperationLt{Type: SignedTypeInt32},
+			NewOperationLt(SignedTypeInt32),
 		)
 	case wasm.OpcodeI32LtU:
 		c.emit(
-			OperationLt{Type: SignedTypeUint32},
+			NewOperationLt(SignedTypeUint32),
 		)
 	case wasm.OpcodeI32GtS:
 		c.emit(
-			OperationGt{Type: SignedTypeInt32},
+			NewOperationGt(SignedTypeInt32),
 		)
 	case wasm.OpcodeI32GtU:
 		c.emit(
-			OperationGt{Type: SignedTypeUint32},
+			NewOperationGt(SignedTypeUint32),
 		)
 	case wasm.OpcodeI32LeS:
 		c.emit(
-			OperationLe{Type: SignedTypeInt32},
+			NewOperationLe(SignedTypeInt32),
 		)
 	case wasm.OpcodeI32LeU:
 		c.emit(
-			OperationLe{Type: SignedTypeUint32},
+			NewOperationLe(SignedTypeUint32),
 		)
 	case wasm.OpcodeI32GeS:
 		c.emit(
-			OperationGe{Type: SignedTypeInt32},
+			NewOperationGe(SignedTypeInt32),
 		)
 	case wasm.OpcodeI32GeU:
 		c.emit(
-			OperationGe{Type: SignedTypeUint32},
+			NewOperationGe(SignedTypeUint32),
 		)
 	case wasm.OpcodeI64Eqz:
 		c.emit(
-			OperationEqz{Type: UnsignedInt64},
+			NewOperationEqz(UnsignedInt64),
 		)
 	case wasm.OpcodeI64Eq:
 		c.emit(
-			OperationEq{Type: UnsignedTypeI64},
+			NewOperationEq(UnsignedTypeI64),
 		)
 	case wasm.OpcodeI64Ne:
 		c.emit(
-			OperationNe{Type: UnsignedTypeI64},
+			NewOperationNe(UnsignedTypeI64),
 		)
 	case wasm.OpcodeI64LtS:
 		c.emit(
-			OperationLt{Type: SignedTypeInt64},
+			NewOperationLt(SignedTypeInt64),
 		)
 	case wasm.OpcodeI64LtU:
 		c.emit(
-			OperationLt{Type: SignedTypeUint64},
+			NewOperationLt(SignedTypeUint64),
 		)
 	case wasm.OpcodeI64GtS:
 		c.emit(
-			OperationGt{Type: SignedTypeInt64},
+			NewOperationGt(SignedTypeInt64),
 		)
 	case wasm.OpcodeI64GtU:
 		c.emit(
-			OperationGt{Type: SignedTypeUint64},
+			NewOperationGt(SignedTypeUint64),
 		)
 	case wasm.OpcodeI64LeS:
 		c.emit(
-			OperationLe{Type: SignedTypeInt64},
+			NewOperationLe(SignedTypeInt64),
 		)
 	case wasm.OpcodeI64LeU:
 		c.emit(
-			OperationLe{Type: SignedTypeUint64},
+			NewOperationLe(SignedTypeUint64),
 		)
 	case wasm.OpcodeI64GeS:
 		c.emit(
-			OperationGe{Type: SignedTypeInt64},
+			NewOperationGe(SignedTypeInt64),
 		)
 	case wasm.OpcodeI64GeU:
 		c.emit(
-			OperationGe{Type: SignedTypeUint64},
+			NewOperationGe(SignedTypeUint64),
 		)
 	case wasm.OpcodeF32Eq:
 		c.emit(
-			OperationEq{Type: UnsignedTypeF32},
+			NewOperationEq(UnsignedTypeF32),
 		)
 	case wasm.OpcodeF32Ne:
 		c.emit(
-			OperationNe{Type: UnsignedTypeF32},
+			NewOperationNe(UnsignedTypeF32),
 		)
 	case wasm.OpcodeF32Lt:
 		c.emit(
-			OperationLt{Type: SignedTypeFloat32},
+			NewOperationLt(SignedTypeFloat32),
 		)
 	case wasm.OpcodeF32Gt:
 		c.emit(
-			OperationGt{Type: SignedTypeFloat32},
+			NewOperationGt(SignedTypeFloat32),
 		)
 	case wasm.OpcodeF32Le:
 		c.emit(
-			OperationLe{Type: SignedTypeFloat32},
+			NewOperationLe(SignedTypeFloat32),
 		)
 	case wasm.OpcodeF32Ge:
 		c.emit(
-			OperationGe{Type: SignedTypeFloat32},
+			NewOperationGe(SignedTypeFloat32),
 		)
 	case wasm.OpcodeF64Eq:
 		c.emit(
-			OperationEq{Type: UnsignedTypeF64},
+			NewOperationEq(UnsignedTypeF64),
 		)
 	case wasm.OpcodeF64Ne:
 		c.emit(
-			OperationNe{Type: UnsignedTypeF64},
+			NewOperationNe(UnsignedTypeF64),
 		)
 	case wasm.OpcodeF64Lt:
 		c.emit(
-			OperationLt{Type: SignedTypeFloat64},
+			NewOperationLt(SignedTypeFloat64),
 		)
 	case wasm.OpcodeF64Gt:
 		c.emit(
-			OperationGt{Type: SignedTypeFloat64},
+			NewOperationGt(SignedTypeFloat64),
 		)
 	case wasm.OpcodeF64Le:
 		c.emit(
-			OperationLe{Type: SignedTypeFloat64},
+			NewOperationLe(SignedTypeFloat64),
 		)
 	case wasm.OpcodeF64Ge:
 		c.emit(
-			OperationGe{Type: SignedTypeFloat64},
+			NewOperationGe(SignedTypeFloat64),
 		)
 	case wasm.OpcodeI32Clz:
 		c.emit(
-			OperationClz{Type: UnsignedInt32},
+			NewOperationClz(UnsignedInt32),
 		)
 	case wasm.OpcodeI32Ctz:
 		c.emit(
-			OperationCtz{Type: UnsignedInt32},
+			NewOperationCtz(UnsignedInt32),
 		)
 	case wasm.OpcodeI32Popcnt:
 		c.emit(
-			OperationPopcnt{Type: UnsignedInt32},
+			NewOperationPopcnt(UnsignedInt32),
 		)
 	case wasm.OpcodeI32Add:
 		c.emit(
-			OperationAdd{Type: UnsignedTypeI32},
+			NewOperationAdd(UnsignedTypeI32),
 		)
 	case wasm.OpcodeI32Sub:
 		c.emit(
-			OperationSub{Type: UnsignedTypeI32},
+			NewOperationSub(UnsignedTypeI32),
 		)
 	case wasm.OpcodeI32Mul:
 		c.emit(
-			OperationMul{Type: UnsignedTypeI32},
+			NewOperationMul(UnsignedTypeI32),
 		)
 	case wasm.OpcodeI32DivS:
 		c.emit(
-			OperationDiv{Type: SignedTypeInt32},
+			NewOperationDiv(SignedTypeInt32),
 		)
 	case wasm.OpcodeI32DivU:
 		c.emit(
-			OperationDiv{Type: SignedTypeUint32},
+			NewOperationDiv(SignedTypeUint32),
 		)
 	case wasm.OpcodeI32RemS:
 		c.emit(
-			OperationRem{Type: SignedInt32},
+			NewOperationRem(SignedInt32),
 		)
 	case wasm.OpcodeI32RemU:
 		c.emit(
-			OperationRem{Type: SignedUint32},
+			NewOperationRem(SignedUint32),
 		)
 	case wasm.OpcodeI32And:
 		c.emit(
-			OperationAnd{Type: UnsignedInt32},
+			NewOperationAnd(UnsignedInt32),
 		)
 	case wasm.OpcodeI32Or:
 		c.emit(
-			OperationOr{Type: UnsignedInt32},
+			NewOperationOr(UnsignedInt32),
 		)
 	case wasm.OpcodeI32Xor:
 		c.emit(
-			OperationXor{Type: UnsignedInt64},
+			NewOperationXor(UnsignedInt64),
 		)
 	case wasm.OpcodeI32Shl:
 		c.emit(
-			OperationShl{Type: UnsignedInt32},
+			NewOperationShl(UnsignedInt32),
 		)
 	case wasm.OpcodeI32ShrS:
 		c.emit(
-			OperationShr{Type: SignedInt32},
+			NewOperationShr(SignedInt32),
 		)
 	case wasm.OpcodeI32ShrU:
 		c.emit(
-			OperationShr{Type: SignedUint32},
+			NewOperationShr(SignedUint32),
 		)
 	case wasm.OpcodeI32Rotl:
 		c.emit(
-			OperationRotl{Type: UnsignedInt32},
+			NewOperationRotl(UnsignedInt32),
 		)
 	case wasm.OpcodeI32Rotr:
 		c.emit(
-			OperationRotr{Type: UnsignedInt32},
+			NewOperationRotr(UnsignedInt32),
 		)
 	case wasm.OpcodeI64Clz:
 		c.emit(
-			OperationClz{Type: UnsignedInt64},
+			NewOperationClz(UnsignedInt64),
 		)
 	case wasm.OpcodeI64Ctz:
 		c.emit(
-			OperationCtz{Type: UnsignedInt64},
+			NewOperationCtz(UnsignedInt64),
 		)
 	case wasm.OpcodeI64Popcnt:
 		c.emit(
-			OperationPopcnt{Type: UnsignedInt64},
+			NewOperationPopcnt(UnsignedInt64),
 		)
 	case wasm.OpcodeI64Add:
 		c.emit(
-			OperationAdd{Type: UnsignedTypeI64},
+			NewOperationAdd(UnsignedTypeI64),
 		)
 	case wasm.OpcodeI64Sub:
 		c.emit(
-			OperationSub{Type: UnsignedTypeI64},
+			NewOperationSub(UnsignedTypeI64),
 		)
 	case wasm.OpcodeI64Mul:
 		c.emit(
-			OperationMul{Type: UnsignedTypeI64},
+			NewOperationMul(UnsignedTypeI64),
 		)
 	case wasm.OpcodeI64DivS:
 		c.emit(
-			OperationDiv{Type: SignedTypeInt64},
+			NewOperationDiv(SignedTypeInt64),
 		)
 	case wasm.OpcodeI64DivU:
 		c.emit(
-			OperationDiv{Type: SignedTypeUint64},
+			NewOperationDiv(SignedTypeUint64),
 		)
 	case wasm.OpcodeI64RemS:
 		c.emit(
-			OperationRem{Type: SignedInt64},
+			NewOperationRem(SignedInt64),
 		)
 	case wasm.OpcodeI64RemU:
 		c.emit(
-			OperationRem{Type: SignedUint64},
+			NewOperationRem(SignedUint64),
 		)
 	case wasm.OpcodeI64And:
 		c.emit(
-			OperationAnd{Type: UnsignedInt64},
+			NewOperationAnd(UnsignedInt64),
 		)
 	case wasm.OpcodeI64Or:
 		c.emit(
-			OperationOr{Type: UnsignedInt64},
+			NewOperationOr(UnsignedInt64),
 		)
 	case wasm.OpcodeI64Xor:
 		c.emit(
-			OperationXor{Type: UnsignedInt64},
+			NewOperationXor(UnsignedInt64),
 		)
 	case wasm.OpcodeI64Shl:
 		c.emit(
-			OperationShl{Type: UnsignedInt64},
+			NewOperationShl(UnsignedInt64),
 		)
 	case wasm.OpcodeI64ShrS:
 		c.emit(
-			OperationShr{Type: SignedInt64},
+			NewOperationShr(SignedInt64),
 		)
 	case wasm.OpcodeI64ShrU:
 		c.emit(
-			OperationShr{Type: SignedUint64},
+			NewOperationShr(SignedUint64),
 		)
 	case wasm.OpcodeI64Rotl:
 		c.emit(
-			OperationRotl{Type: UnsignedInt64},
+			NewOperationRotl(UnsignedInt64),
 		)
 	case wasm.OpcodeI64Rotr:
 		c.emit(
-			OperationRotr{Type: UnsignedInt64},
+			NewOperationRotr(UnsignedInt64),
 		)
 	case wasm.OpcodeF32Abs:
 		c.emit(
-			OperationAbs{Type: Float32},
+			NewOperationAbs(Float32),
 		)
 	case wasm.OpcodeF32Neg:
 		c.emit(
-			OperationNeg{Type: Float32},
+			NewOperationNeg(Float32),
 		)
 	case wasm.OpcodeF32Ceil:
 		c.emit(
-			OperationCeil{Type: Float32},
+			NewOperationCeil(Float32),
 		)
 	case wasm.OpcodeF32Floor:
 		c.emit(
-			OperationFloor{Type: Float32},
+			NewOperationFloor(Float32),
 		)
 	case wasm.OpcodeF32Trunc:
 		c.emit(
-			OperationTrunc{Type: Float32},
+			NewOperationTrunc(Float32),
 		)
 	case wasm.OpcodeF32Nearest:
 		c.emit(
-			OperationNearest{Type: Float32},
+			NewOperationNearest(Float32),
 		)
 	case wasm.OpcodeF32Sqrt:
 		c.emit(
-			OperationSqrt{Type: Float32},
+			NewOperationSqrt(Float32),
 		)
 	case wasm.OpcodeF32Add:
 		c.emit(
-			OperationAdd{Type: UnsignedTypeF32},
+			NewOperationAdd(UnsignedTypeF32),
 		)
 	case wasm.OpcodeF32Sub:
 		c.emit(
-			OperationSub{Type: UnsignedTypeF32},
+			NewOperationSub(UnsignedTypeF32),
 		)
 	case wasm.OpcodeF32Mul:
 		c.emit(
-			OperationMul{Type: UnsignedTypeF32},
+			NewOperationMul(UnsignedTypeF32),
 		)
 	case wasm.OpcodeF32Div:
 		c.emit(
-			OperationDiv{Type: SignedTypeFloat32},
+			NewOperationDiv(SignedTypeFloat32),
 		)
 	case wasm.OpcodeF32Min:
 		c.emit(
-			OperationMin{Type: Float32},
+			NewOperationMin(Float32),
 		)
 	case wasm.OpcodeF32Max:
 		c.emit(
-			OperationMax{Type: Float32},
+			NewOperationMax(Float32),
 		)
 	case wasm.OpcodeF32Copysign:
 		c.emit(
-			OperationCopysign{Type: Float32},
+			NewOperationCopysign(Float32),
 		)
 	case wasm.OpcodeF64Abs:
 		c.emit(
-			OperationAbs{Type: Float64},
+			NewOperationAbs(Float64),
 		)
 	case wasm.OpcodeF64Neg:
 		c.emit(
-			OperationNeg{Type: Float64},
+			NewOperationNeg(Float64),
 		)
 	case wasm.OpcodeF64Ceil:
 		c.emit(
-			OperationCeil{Type: Float64},
+			NewOperationCeil(Float64),
 		)
 	case wasm.OpcodeF64Floor:
 		c.emit(
-			OperationFloor{Type: Float64},
+			NewOperationFloor(Float64),
 		)
 	case wasm.OpcodeF64Trunc:
 		c.emit(
-			OperationTrunc{Type: Float64},
+			NewOperationTrunc(Float64),
 		)
 	case wasm.OpcodeF64Nearest:
 		c.emit(
-			OperationNearest{Type: Float64},
+			NewOperationNearest(Float64),
 		)
 	case wasm.OpcodeF64Sqrt:
 		c.emit(
-			OperationSqrt{Type: Float64},
+			NewOperationSqrt(Float64),
 		)
 	case wasm.OpcodeF64Add:
 		c.emit(
-			OperationAdd{Type: UnsignedTypeF64},
+			NewOperationAdd(UnsignedTypeF64),
 		)
 	case wasm.OpcodeF64Sub:
 		c.emit(
-			OperationSub{Type: UnsignedTypeF64},
+			NewOperationSub(UnsignedTypeF64),
 		)
 	case wasm.OpcodeF64Mul:
 		c.emit(
-			OperationMul{Type: UnsignedTypeF64},
+			NewOperationMul(UnsignedTypeF64),
 		)
 	case wasm.OpcodeF64Div:
 		c.emit(
-			OperationDiv{Type: SignedTypeFloat64},
+			NewOperationDiv(SignedTypeFloat64),
 		)
 	case wasm.OpcodeF64Min:
 		c.emit(
-			OperationMin{Type: Float64},
+			NewOperationMin(Float64),
 		)
 	case wasm.OpcodeF64Max:
 		c.emit(
-			OperationMax{Type: Float64},
+			NewOperationMax(Float64),
 		)
 	case wasm.OpcodeF64Copysign:
 		c.emit(
-			OperationCopysign{Type: Float64},
+			NewOperationCopysign(Float64),
 		)
 	case wasm.OpcodeI32WrapI64:
 		c.emit(
@@ -1661,7 +1661,7 @@ operatorSwitch:
 	case wasm.OpcodeRefIsNull:
 		// Simply compare the opaque pointer (i64) with zero.
 		c.emit(
-			OperationEqz{Type: UnsignedInt64},
+			NewOperationEqz(UnsignedInt64),
 		)
 	case wasm.OpcodeTableGet:
 		c.pc++
diff --git a/internal/wazeroir/operations.go b/internal/wazeroir/operations.go
index 90ce422d..a8be4bd8 100644
--- a/internal/wazeroir/operations.go
+++ b/internal/wazeroir/operations.go
@@ -734,38 +734,6 @@ var (
 	_ Operation = OperationStore8{}
 	_ Operation = OperationStore16{}
 	_ Operation = OperationStore32{}
-	_ Operation = OperationEq{}
-	_ Operation = OperationNe{}
-	_ Operation = OperationEqz{}
-	_ Operation = OperationLt{}
-	_ Operation = OperationGt{}
-	_ Operation = OperationLe{}
-	_ Operation = OperationGe{}
-	_ Operation = OperationAdd{}
-	_ Operation = OperationSub{}
-	_ Operation = OperationMul{}
-	_ Operation = OperationClz{}
-	_ Operation = OperationCtz{}
-	_ Operation = OperationPopcnt{}
-	_ Operation = OperationDiv{}
-	_ Operation = OperationRem{}
-	_ Operation = OperationAnd{}
-	_ Operation = OperationOr{}
-	_ Operation = OperationXor{}
-	_ Operation = OperationShl{}
-	_ Operation = OperationShr{}
-	_ Operation = OperationRotl{}
-	_ Operation = OperationRotr{}
-	_ Operation = OperationAbs{}
-	_ Operation = OperationNeg{}
-	_ Operation = OperationCeil{}
-	_ Operation = OperationFloor{}
-	_ Operation = OperationTrunc{}
-	_ Operation = OperationNearest{}
-	_ Operation = OperationSqrt{}
-	_ Operation = OperationMin{}
-	_ Operation = OperationMax{}
-	_ Operation = OperationCopysign{}
 	_ Operation = OperationITruncFromF{}
 	_ Operation = OperationFConvertFromI{}
 	_ Operation = OperationExtend{}
@@ -964,6 +932,47 @@ func (o UnionOperation) String() string {
 		OperationKindGlobalSet:
 		return fmt.Sprintf("%s %d", o.Kind(), o.B1)
 
+	case OperationKindEq,
+		OperationKindNe,
+		OperationKindAdd,
+		OperationKindSub,
+		OperationKindMul:
+		return fmt.Sprintf("%s.%s", UnsignedType(o.B1), o.Kind())
+
+	case OperationKindEqz,
+		OperationKindClz,
+		OperationKindCtz,
+		OperationKindPopcnt,
+		OperationKindAnd,
+		OperationKindOr,
+		OperationKindXor,
+		OperationKindShl,
+		OperationKindRotl,
+		OperationKindRotr:
+		return fmt.Sprintf("%s.%s", UnsignedInt(o.B1), o.Kind())
+
+	case OperationKindRem, OperationKindShr:
+		return fmt.Sprintf("%s.%s", SignedInt(o.B1), o.Kind())
+
+	case OperationKindLt,
+		OperationKindGt,
+		OperationKindLe,
+		OperationKindGe,
+		OperationKindDiv:
+		return fmt.Sprintf("%s.%s", SignedType(o.B1), o.Kind())
+
+	case OperationKindAbs,
+		OperationKindNeg,
+		OperationKindCeil,
+		OperationKindFloor,
+		OperationKindTrunc,
+		OperationKindNearest,
+		OperationKindSqrt,
+		OperationKindMin,
+		OperationKindMax,
+		OperationKindCopysign:
+		return fmt.Sprintf("%s.%s", Float(o.B1), o.Kind())
+
 	case OperationKindConstI32,
 		OperationKindConstI64:
 		return fmt.Sprintf("%s %#x", o.Kind(), o.U1)
@@ -1443,157 +1452,79 @@ func NewOperationConstF64(value float64) UnionOperation {
 	return UnionOperation{OpKind: OperationKindConstF64, U1: math.Float64bits(value)}
 }
 
-// OperationEq implements Operation.
+// NewOperationEq is a constructor for UnionOperation with Kind OperationKindEq.
 //
 // This corresponds to wasm.OpcodeI32EqName wasm.OpcodeI64EqName wasm.OpcodeF32EqName wasm.OpcodeF64EqName
-type OperationEq struct{ Type UnsignedType }
-
-// String implements fmt.Stringer.
-func (o OperationEq) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationEq) Kind() OperationKind {
-	return OperationKindEq
+func NewOperationEq(b UnsignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindEq, B1: byte(b)}
 }
 
-// OperationNe implements Operation.
+// NewOperationNe is a constructor for UnionOperation with Kind OperationKindNe.
 //
 // This corresponds to wasm.OpcodeI32NeName wasm.OpcodeI64NeName wasm.OpcodeF32NeName wasm.OpcodeF64NeName
-type OperationNe struct{ Type UnsignedType }
-
-// String implements fmt.Stringer.
-func (o OperationNe) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationNe) Kind() OperationKind {
-	return OperationKindNe
+func NewOperationNe(b UnsignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindNe, B1: byte(b)}
 }
 
-// OperationEqz implements Operation.
+// NewOperationEqz is a constructor for UnionOperation with Kind OperationKindEqz.
 //
 // This corresponds to wasm.OpcodeI32EqzName wasm.OpcodeI64EqzName
-type OperationEqz struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationEqz) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationEqz) Kind() OperationKind {
-	return OperationKindEqz
+func NewOperationEqz(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindEqz, B1: byte(b)}
 }
 
-// OperationLt implements Operation.
+// NewOperationLt is a constructor for UnionOperation with Kind OperationKindLt.
 //
 // This corresponds to wasm.OpcodeI32LtS wasm.OpcodeI32LtU wasm.OpcodeI64LtS wasm.OpcodeI64LtU wasm.OpcodeF32Lt wasm.OpcodeF64Lt
-type OperationLt struct{ Type SignedType }
-
-// String implements fmt.Stringer.
-func (o OperationLt) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationLt) Kind() OperationKind {
-	return OperationKindLt
+func NewOperationLt(b SignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindLt, B1: byte(b)}
 }
 
-// OperationGt implements Operation.
+// NewOperationGt is a constructor for UnionOperation with Kind OperationKindGt.
 //
 // This corresponds to wasm.OpcodeI32GtS wasm.OpcodeI32GtU wasm.OpcodeI64GtS wasm.OpcodeI64GtU wasm.OpcodeF32Gt wasm.OpcodeF64Gt
-type OperationGt struct{ Type SignedType }
-
-// String implements fmt.Stringer.
-func (o OperationGt) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationGt) Kind() OperationKind {
-	return OperationKindGt
+func NewOperationGt(b SignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindGt, B1: byte(b)}
 }
 
-// OperationLe implements Operation.
+// NewOperationLe is a constructor for UnionOperation with Kind OperationKindLe.
 //
 // This corresponds to wasm.OpcodeI32LeS wasm.OpcodeI32LeU wasm.OpcodeI64LeS wasm.OpcodeI64LeU wasm.OpcodeF32Le wasm.OpcodeF64Le
-type OperationLe struct{ Type SignedType }
-
-// String implements fmt.Stringer.
-func (o OperationLe) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationLe) Kind() OperationKind {
-	return OperationKindLe
+func NewOperationLe(b SignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindLe, B1: byte(b)}
 }
 
-// OperationGe implements Operation.
+// NewOperationGe is a constructor for UnionOperation with Kind OperationKindGe.
 //
 // This corresponds to wasm.OpcodeI32GeS wasm.OpcodeI32GeU wasm.OpcodeI64GeS wasm.OpcodeI64GeU wasm.OpcodeF32Ge wasm.OpcodeF64Ge
-type OperationGe struct{ Type SignedType }
-
-// String implements fmt.Stringer.
-func (o OperationGe) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationGe) Kind() OperationKind {
-	return OperationKindGe
+// NewOperationGe is the constructor for OperationGe
+func NewOperationGe(b SignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindGe, B1: byte(b)}
 }
 
-// OperationAdd implements Operation.
+// NewOperationAdd is a constructor for UnionOperation with Kind OperationKindAdd.
 //
 // This corresponds to wasm.OpcodeI32AddName wasm.OpcodeI64AddName wasm.OpcodeF32AddName wasm.OpcodeF64AddName.
-type OperationAdd struct{ Type UnsignedType }
-
-// String implements fmt.Stringer.
-func (o OperationAdd) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationAdd) Kind() OperationKind {
-	return OperationKindAdd
+func NewOperationAdd(b UnsignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindAdd, B1: byte(b)}
 }
 
-// OperationSub implements Operation.
+// NewOperationSub is a constructor for UnionOperation with Kind OperationKindSub.
 //
 // This corresponds to wasm.OpcodeI32SubName wasm.OpcodeI64SubName wasm.OpcodeF32SubName wasm.OpcodeF64SubName.
-type OperationSub struct{ Type UnsignedType }
-
-// String implements fmt.Stringer.
-func (o OperationSub) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationSub) Kind() OperationKind {
-	return OperationKindSub
+func NewOperationSub(b UnsignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindSub, B1: byte(b)}
 }
 
-// OperationMul implements Operation.
+// NewOperationMul is a constructor for UnionOperation with Kind wperationKindMul.
 //
 // This corresponds to wasm.OpcodeI32MulName wasm.OpcodeI64MulName wasm.OpcodeF32MulName wasm.OpcodeF64MulName.
-type OperationMul struct{ Type UnsignedType }
-
-// String implements fmt.Stringer.
-func (o OperationMul) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationMul) Kind() OperationKind {
-	return OperationKindMul
+// NewOperationMul is the constructor for OperationMul
+func NewOperationMul(b UnsignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindMul, B1: byte(b)}
 }
 
-// OperationClz implements Operation.
+// NewOperationClz is a constructor for UnionOperation with Kind OperationKindClz.
 //
 // This corresponds to wasm.OpcodeI32ClzName wasm.OpcodeI64ClzName.
 //
@@ -1601,74 +1532,42 @@ func (OperationMul) Kind() OperationKind {
 // current top of the stack, and push the count result.
 // For example, stack of [..., 0x00_ff_ff_ff] results in [..., 8].
 // See wasm.OpcodeI32Clz wasm.OpcodeI64Clz
-type OperationClz struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationClz) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationClz) Kind() OperationKind {
-	return OperationKindClz
+func NewOperationClz(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindClz, B1: byte(b)}
 }
 
-// OperationCtz implements Operation.
+// NewOperationCtz is a constructor for UnionOperation with Kind OperationKindCtz.
 //
 // This corresponds to wasm.OpcodeI32CtzName wasm.OpcodeI64CtzName.
 //
 // The engines are expected to count up the trailing zeros in the
 // current top of the stack, and push the count result.
 // For example, stack of [..., 0xff_ff_ff_00] results in [..., 8].
-type OperationCtz struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationCtz) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationCtz) Kind() OperationKind {
-	return OperationKindCtz
+func NewOperationCtz(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindCtz, B1: byte(b)}
 }
 
-// OperationPopcnt implements Operation.
+// NewOperationPopcnt is a constructor for UnionOperation with Kind OperationKindPopcnt.
 //
 // This corresponds to wasm.OpcodeI32PopcntName wasm.OpcodeI64PopcntName.
 //
 // The engines are expected to count up the number of set bits in the
 // current top of the stack, and push the count result.
 // For example, stack of [..., 0b00_00_00_11] results in [..., 2].
-type OperationPopcnt struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationPopcnt) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationPopcnt) Kind() OperationKind {
-	return OperationKindPopcnt
+func NewOperationPopcnt(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindPopcnt, B1: byte(b)}
 }
 
-// OperationDiv implements Operation.
+// NewOperationDiv is a constructor for UnionOperation with Kind OperationKindDiv.
 //
 // This corresponds to wasm.OpcodeI32DivS wasm.OpcodeI32DivU wasm.OpcodeI64DivS
 //
 //	wasm.OpcodeI64DivU wasm.OpcodeF32Div wasm.OpcodeF64Div.
-type OperationDiv struct{ Type SignedType }
-
-// String implements fmt.Stringer.
-func (o OperationDiv) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationDiv) Kind() OperationKind {
-	return OperationKindDiv
+func NewOperationDiv(b SignedType) UnionOperation {
+	return UnionOperation{OpKind: OperationKindDiv, B1: byte(b)}
 }
 
-// OperationRem implements Operation.
+// NewOperationRem is a constructor for UnionOperation with Kind OperationKindRem.
 //
 // This corresponds to wasm.OpcodeI32RemS wasm.OpcodeI32RemU wasm.OpcodeI64RemS wasm.OpcodeI64RemU.
 //
@@ -1676,254 +1575,135 @@ func (OperationDiv) Kind() OperationKind {
 // two values of integer type on the stack and puts the remainder of the result
 // onto the stack. For example, stack [..., 10, 3] results in [..., 1] where
 // the quotient is discarded.
-type OperationRem struct{ Type SignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationRem) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationRem) Kind() OperationKind {
-	return OperationKindRem
+// NewOperationRem is the constructor for OperationRem
+func NewOperationRem(b SignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindRem, B1: byte(b)}
 }
 
-// OperationAnd implements Operation.
+// NewOperationAnd is a constructor for UnionOperation with Kind OperationKindAnd.
 //
 // # This corresponds to wasm.OpcodeI32AndName wasm.OpcodeI64AndName
 //
 // The engines are expected to perform "And" operation on
 // top two values on the stack, and pushes the result.
-type OperationAnd struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationAnd) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationAnd) Kind() OperationKind {
-	return OperationKindAnd
+func NewOperationAnd(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindAnd, B1: byte(b)}
 }
 
-// OperationOr implements Operation.
+// NewOperationOr is a constructor for UnionOperation with Kind OperationKindOr.
 //
 // # This corresponds to wasm.OpcodeI32OrName wasm.OpcodeI64OrName
 //
 // The engines are expected to perform "Or" operation on
 // top two values on the stack, and pushes the result.
-type OperationOr struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationOr) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationOr) Kind() OperationKind {
-	return OperationKindOr
+func NewOperationOr(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindOr, B1: byte(b)}
 }
 
-// OperationXor implements Operation.
+// NewOperationXor is a constructor for UnionOperation with Kind OperationKindXor.
 //
 // # This corresponds to wasm.OpcodeI32XorName wasm.OpcodeI64XorName
 //
 // The engines are expected to perform "Xor" operation on
 // top two values on the stack, and pushes the result.
-type OperationXor struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationXor) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationXor) Kind() OperationKind {
-	return OperationKindXor
+func NewOperationXor(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindXor, B1: byte(b)}
 }
 
-// OperationShl implements Operation.
+// NewOperationShl is a constructor for UnionOperation with Kind OperationKindShl.
 //
 // # This corresponds to wasm.OpcodeI32ShlName wasm.OpcodeI64ShlName
 //
 // The engines are expected to perform "Shl" operation on
 // top two values on the stack, and pushes the result.
-type OperationShl struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationShl) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationShl) Kind() OperationKind {
-	return OperationKindShl
+func NewOperationShl(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindShl, B1: byte(b)}
 }
 
-// OperationShr implements Operation.
+// NewOperationShr is a constructor for UnionOperation with Kind OperationKindShr.
 //
 // # This corresponds to wasm.OpcodeI32ShrSName wasm.OpcodeI32ShrUName wasm.OpcodeI64ShrSName wasm.OpcodeI64ShrUName
 //
 // If OperationShr.Type is signed integer, then, the engines are expected to perform arithmetic right shift on the two
 // top values on the stack, otherwise do the logical right shift.
-type OperationShr struct{ Type SignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationShr) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationShr) Kind() OperationKind {
-	return OperationKindShr
+func NewOperationShr(b SignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindShr, B1: byte(b)}
 }
 
-// OperationRotl implements Operation.
+// NewOperationRotl is a constructor for UnionOperation with Kind OperationKindRotl.
 //
 // # This corresponds to wasm.OpcodeI32RotlName wasm.OpcodeI64RotlName
 //
 // The engines are expected to perform "Rotl" operation on
 // top two values on the stack, and pushes the result.
-type OperationRotl struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationRotl) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationRotl) Kind() OperationKind {
-	return OperationKindRotl
+func NewOperationRotl(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindRotl, B1: byte(b)}
 }
 
-// OperationRotr implements Operation.
+// NewOperationRotr is a constructor for UnionOperation with Kind OperationKindRotr.
 //
 // # This corresponds to wasm.OpcodeI32RotrName wasm.OpcodeI64RotrName
 //
 // The engines are expected to perform "Rotr" operation on
 // top two values on the stack, and pushes the result.
-type OperationRotr struct{ Type UnsignedInt }
-
-// String implements fmt.Stringer.
-func (o OperationRotr) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationRotr) Kind() OperationKind {
-	return OperationKindRotr
+func NewOperationRotr(b UnsignedInt) UnionOperation {
+	return UnionOperation{OpKind: OperationKindRotr, B1: byte(b)}
 }
 
-// OperationAbs implements Operation.
+// NewOperationAbs is a constructor for UnionOperation with Kind OperationKindAbs.
 //
 // This corresponds to wasm.OpcodeF32Abs wasm.OpcodeF64Abs
-type OperationAbs struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationAbs) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationAbs) Kind() OperationKind {
-	return OperationKindAbs
+func NewOperationAbs(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindAbs, B1: byte(b)}
 }
 
-// OperationNeg implements Operation.
+// NewOperationNeg is a constructor for UnionOperation with Kind OperationKindNeg.
 //
 // This corresponds to wasm.OpcodeF32Neg wasm.OpcodeF64Neg
-type OperationNeg struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationNeg) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationNeg) Kind() OperationKind {
-	return OperationKindNeg
+func NewOperationNeg(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindNeg, B1: byte(b)}
 }
 
-// OperationCeil implements Operation.
+// NewOperationCeil is a constructor for UnionOperation with Kind OperationKindCeil.
 //
 // This corresponds to wasm.OpcodeF32CeilName wasm.OpcodeF64CeilName
-type OperationCeil struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationCeil) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationCeil) Kind() OperationKind {
-	return OperationKindCeil
+func NewOperationCeil(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindCeil, B1: byte(b)}
 }
 
-// OperationFloor implements Operation.
+// NewOperationFloor is a constructor for UnionOperation with Kind OperationKindFloor.
 //
 // This corresponds to wasm.OpcodeF32FloorName wasm.OpcodeF64FloorName
-type OperationFloor struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationFloor) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationFloor) Kind() OperationKind {
-	return OperationKindFloor
+func NewOperationFloor(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindFloor, B1: byte(b)}
 }
 
-// OperationTrunc implements Operation.
+// NewOperationTrunc is a constructor for UnionOperation with Kind OperationKindTrunc.
 //
 // This corresponds to wasm.OpcodeF32TruncName wasm.OpcodeF64TruncName
-type OperationTrunc struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationTrunc) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationTrunc) Kind() OperationKind {
-	return OperationKindTrunc
+func NewOperationTrunc(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindTrunc, B1: byte(b)}
 }
 
-// OperationNearest implements Operation.
+// NewOperationNearest is a constructor for UnionOperation with Kind OperationKindNearest.
 //
 // # This corresponds to wasm.OpcodeF32NearestName wasm.OpcodeF64NearestName
 //
 // Note: this is *not* equivalent to math.Round and instead has the same
 // the semantics of LLVM's rint intrinsic. See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic.
 // For example, math.Round(-4.5) produces -5 while we want to produce -4.
-type OperationNearest struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationNearest) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationNearest) Kind() OperationKind {
-	return OperationKindNearest
+func NewOperationNearest(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindNearest, B1: byte(b)}
 }
 
-// OperationSqrt implements Operation.
+// NewOperationSqrt is a constructor for UnionOperation with Kind OperationKindSqrt.
 //
 // This corresponds to wasm.OpcodeF32SqrtName wasm.OpcodeF64SqrtName
-type OperationSqrt struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationSqrt) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationSqrt) Kind() OperationKind {
-	return OperationKindSqrt
+func NewOperationSqrt(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindSqrt, B1: byte(b)}
 }
 
-// OperationMin implements Operation.
+// NewOperationMin is a constructor for UnionOperation with Kind OperationKindMin.
 //
 // # This corresponds to wasm.OpcodeF32MinName wasm.OpcodeF64MinName
 //
@@ -1932,19 +1712,11 @@ func (OperationSqrt) Kind() OperationKind {
 //
 // Note: WebAssembly specifies that min/max must always return NaN if one of values is NaN,
 // which is a different behavior different from math.Min.
-type OperationMin struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationMin) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationMin) Kind() OperationKind {
-	return OperationKindMin
+func NewOperationMin(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindMin, B1: byte(b)}
 }
 
-// OperationMax implements Operation.
+// NewOperationMax is a constructor for UnionOperation with Kind OperationKindMax.
 //
 // # This corresponds to wasm.OpcodeF32MaxName wasm.OpcodeF64MaxName
 //
@@ -1953,35 +1725,19 @@ func (OperationMin) Kind() OperationKind {
 //
 // Note: WebAssembly specifies that min/max must always return NaN if one of values is NaN,
 // which is a different behavior different from math.Max.
-type OperationMax struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationMax) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationMax) Kind() OperationKind {
-	return OperationKindMax
+func NewOperationMax(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindMax, B1: byte(b)}
 }
 
-// OperationCopysign implements Operation.
+// NewOperationCopysign is a constructor for UnionOperation with Kind OperationKindCopysign.
 //
 // # This corresponds to wasm.OpcodeF32CopysignName wasm.OpcodeF64CopysignName
 //
 // The engines are expected to pop two float values from the stack, and copy the signbit of
 // the first-popped value to the last one.
 // For example, stack [..., 1.213, -5.0] results in [..., -1.213].
-type OperationCopysign struct{ Type Float }
-
-// String implements fmt.Stringer.
-func (o OperationCopysign) String() string {
-	return fmt.Sprintf("%s.%s", o.Type, o.Kind())
-}
-
-// Kind implements Operation.Kind.
-func (OperationCopysign) Kind() OperationKind {
-	return OperationKindCopysign
+func NewOperationCopysign(b Float) UnionOperation {
+	return UnionOperation{OpKind: OperationKindCopysign, B1: byte(b)}
 }
 
 // NewOperationI32WrapFromI64 is a constructor for UnionOperation with Kind OperationKindI32WrapFromI64.
diff --git a/site/content/community/users.md b/site/content/community/users.md
index 259d74d5..b917f692 100644
--- a/site/content/community/users.md
+++ b/site/content/community/users.md
@@ -16,6 +16,7 @@ considering their efforts before starting your own!
 | [go-re2][7]      | high performance regular expressions                                                                 |
 | [go-sqlite3][11] | [SQLite][12] bindings, `database/sql` driver                                                         |
 | [mjml-go][19]    | Compile [MJML][20] to HTML directly in Go                                                            |
+| [Wetware][28]    | Simple, secure & scalable clusters                                                                   |
 
 ### General purpose plugins
 
@@ -50,6 +51,7 @@ considering their efforts before starting your own!
 | [scale][13]   | implements [Polyglot][14] interfaces with WebAssembly via code generation |
 | [taubyte][21] | edge computing and web3 platform that runs [serverless functions][22]     |
 | [YoMo][26]    | Streaming [Serverless][27] Framework for buildnig Geo-distributed system  |
+| [Wetware][28] | Web3's answer to Cloud hosting                                            |
 
 ## Updating this list
 
@@ -113,3 +115,5 @@ experience.
 [26]: https://github.com/yomorun/yomo
 
 [27]: https://github.com/yomorun/yomo/tree/master/example/7-wasm
+
+[28]: https://github.com/wetware/ww


Test patch:
diff --git a/internal/engine/compiler/compiler_conditional_save_test.go b/internal/engine/compiler/compiler_conditional_save_test.go
index eb87fc20..803ecd97 100644
--- a/internal/engine/compiler/compiler_conditional_save_test.go
+++ b/internal/engine/compiler/compiler_conditional_save_test.go
@@ -34,7 +34,7 @@ func TestCompiler_conditional_value_saving(t *testing.T) {
 
 	require.NoError(t, err)
 	// Generate conditional flag via floating point comparisons.
-	err = compiler.compileLe(wazeroir.OperationLe{Type: wazeroir.SignedTypeFloat32})
+	err = compiler.compileLe(wazeroir.NewOperationLe(wazeroir.SignedTypeFloat32))
 	require.NoError(t, err)
 
 	// Ensures that we have conditional value at top of stack.
@@ -46,7 +46,7 @@ func TestCompiler_conditional_value_saving(t *testing.T) {
 	require.False(t, ok)
 
 	// We should be able to use the conditional value (an i32 value in Wasm) as an operand for, say, i32.add.
-	err = compiler.compileAdd(wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI32})
+	err = compiler.compileAdd(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI32))
 	require.NoError(t, err)
 
 	err = compiler.compileReturnFunction()
diff --git a/internal/engine/compiler/compiler_controlflow_test.go b/internal/engine/compiler/compiler_controlflow_test.go
index 7e297e2e..a00fd082 100644
--- a/internal/engine/compiler/compiler_controlflow_test.go
+++ b/internal/engine/compiler/compiler_controlflow_test.go
@@ -97,7 +97,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
 				// Le on unsigned integer produces the value on COND_LS register.
-				err := compiler.compileLe(wazeroir.OperationLe{Type: wazeroir.SignedTypeUint32})
+				err := compiler.compileLe(wazeroir.NewOperationLe(wazeroir.SignedTypeUint32))
 				require.NoError(t, err)
 			},
 		},
@@ -110,7 +110,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
 				// Le on signed integer produces the value on COND_LE register.
-				err := compiler.compileLe(wazeroir.OperationLe{Type: wazeroir.SignedTypeInt32})
+				err := compiler.compileLe(wazeroir.NewOperationLe(wazeroir.SignedTypeInt32))
 				require.NoError(t, err)
 			},
 		},
@@ -123,7 +123,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
 				// Ge on unsigned integer produces the value on COND_HS register.
-				err := compiler.compileGe(wazeroir.OperationGe{Type: wazeroir.SignedTypeUint32})
+				err := compiler.compileGe(wazeroir.NewOperationGe(wazeroir.SignedTypeUint32))
 				require.NoError(t, err)
 			},
 		},
@@ -136,7 +136,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
 				// Ge on signed integer produces the value on COND_GE register.
-				err := compiler.compileGe(wazeroir.OperationGe{Type: wazeroir.SignedTypeInt32})
+				err := compiler.compileGe(wazeroir.NewOperationGe(wazeroir.SignedTypeInt32))
 				require.NoError(t, err)
 			},
 		},
@@ -149,7 +149,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
 				// Gt on unsigned integer produces the value on COND_HI register.
-				err := compiler.compileGt(wazeroir.OperationGt{Type: wazeroir.SignedTypeUint32})
+				err := compiler.compileGt(wazeroir.NewOperationGt(wazeroir.SignedTypeUint32))
 				require.NoError(t, err)
 			},
 		},
@@ -162,7 +162,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
 				// Gt on signed integer produces the value on COND_GT register.
-				err := compiler.compileGt(wazeroir.OperationGt{Type: wazeroir.SignedTypeInt32})
+				err := compiler.compileGt(wazeroir.NewOperationGt(wazeroir.SignedTypeInt32))
 				require.NoError(t, err)
 			},
 		},
@@ -175,7 +175,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
 				// Lt on unsigned integer produces the value on COND_LO register.
-				err := compiler.compileLt(wazeroir.OperationLt{Type: wazeroir.SignedTypeUint32})
+				err := compiler.compileLt(wazeroir.NewOperationLt(wazeroir.SignedTypeUint32))
 				require.NoError(t, err)
 			},
 		},
@@ -188,7 +188,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
 				// Lt on signed integer produces the value on COND_LT register.
-				err := compiler.compileLt(wazeroir.OperationLt{Type: wazeroir.SignedTypeInt32})
+				err := compiler.compileLt(wazeroir.NewOperationLt(wazeroir.SignedTypeInt32))
 				require.NoError(t, err)
 			},
 		},
@@ -201,7 +201,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 				}
 				requirePushTwoFloat32Consts(t, x1, x2, compiler)
 				// Lt on floats produces the value on COND_MI register.
-				err := compiler.compileLt(wazeroir.OperationLt{Type: wazeroir.SignedTypeFloat32})
+				err := compiler.compileLt(wazeroir.NewOperationLt(wazeroir.SignedTypeFloat32))
 				require.NoError(t, err)
 			},
 		},
@@ -213,7 +213,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 					x2++
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
-				err := compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
+				err := compiler.compileEq(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32))
 				require.NoError(t, err)
 			},
 		},
@@ -225,7 +225,7 @@ func TestCompiler_compileBrIf(t *testing.T) {
 					x2 = x1
 				}
 				requirePushTwoInt32Consts(t, x1, x2, compiler)
-				err := compiler.compileNe(wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI32})
+				err := compiler.compileNe(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI32))
 				require.NoError(t, err)
 			},
 		},
@@ -828,7 +828,7 @@ func TestCompiler_compileCall(t *testing.T) {
 		err = compiler.compilePick(wazeroir.OperationPick{Depth: int(compiler.runtimeValueLocationStack().sp - 1)})
 		require.NoError(t, err)
 		// Adds the const to the picked value.
-		err = compiler.compileAdd(wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI32})
+		err = compiler.compileAdd(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI32))
 		require.NoError(t, err)
 		// Then store the added result into the bottom of the stack (which is treated as the result of the function).
 		err = compiler.compileSet(wazeroir.OperationSet{Depth: int(compiler.runtimeValueLocationStack().sp - 1)})
diff --git a/internal/engine/compiler/compiler_numeric_test.go b/internal/engine/compiler/compiler_numeric_test.go
index fecc50f9..c7e25558 100644
--- a/internal/engine/compiler/compiler_numeric_test.go
+++ b/internal/engine/compiler/compiler_numeric_test.go
@@ -169,11 +169,11 @@ func TestCompiler_compile_Add_Sub_Mul(t *testing.T) {
 							// Emit the operation.
 							switch kind {
 							case wazeroir.OperationKindAdd:
-								err = compiler.compileAdd(wazeroir.OperationAdd{Type: unsignedType})
+								err = compiler.compileAdd(wazeroir.NewOperationAdd(unsignedType))
 							case wazeroir.OperationKindSub:
-								err = compiler.compileSub(wazeroir.OperationSub{Type: unsignedType})
+								err = compiler.compileSub(wazeroir.NewOperationSub(unsignedType))
 							case wazeroir.OperationKindMul:
-								err = compiler.compileMul(wazeroir.OperationMul{Type: unsignedType})
+								err = compiler.compileMul(wazeroir.NewOperationMul(unsignedType))
 							}
 							require.NoError(t, err)
 
@@ -346,17 +346,17 @@ func TestCompiler_compile_And_Or_Xor_Shl_Rotl_Rotr(t *testing.T) {
 								// Emit the operation.
 								switch kind {
 								case wazeroir.OperationKindAnd:
-									err = compiler.compileAnd(wazeroir.OperationAnd{Type: unsignedInt})
+									err = compiler.compileAnd(wazeroir.NewOperationAnd(unsignedInt))
 								case wazeroir.OperationKindOr:
-									err = compiler.compileOr(wazeroir.OperationOr{Type: unsignedInt})
+									err = compiler.compileOr(wazeroir.NewOperationOr(unsignedInt))
 								case wazeroir.OperationKindXor:
-									err = compiler.compileXor(wazeroir.OperationXor{Type: unsignedInt})
+									err = compiler.compileXor(wazeroir.NewOperationXor(unsignedInt))
 								case wazeroir.OperationKindShl:
-									err = compiler.compileShl(wazeroir.OperationShl{Type: unsignedInt})
+									err = compiler.compileShl(wazeroir.NewOperationShl(unsignedInt))
 								case wazeroir.OperationKindRotl:
-									err = compiler.compileRotl(wazeroir.OperationRotl{Type: unsignedInt})
+									err = compiler.compileRotl(wazeroir.NewOperationRotl(unsignedInt))
 								case wazeroir.OperationKindRotr:
-									err = compiler.compileRotr(wazeroir.OperationRotr{Type: unsignedInt})
+									err = compiler.compileRotr(wazeroir.NewOperationRotr(unsignedInt))
 								}
 								require.NoError(t, err)
 
@@ -479,7 +479,7 @@ func TestCompiler_compileShr(t *testing.T) {
 						requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
 
 						// Emit the operation.
-						err = compiler.compileShr(wazeroir.OperationShr{Type: signedInt})
+						err = compiler.compileShr(wazeroir.NewOperationShr(signedInt))
 						require.NoError(t, err)
 
 						// We consumed two values, but push the result back.
@@ -618,44 +618,44 @@ func TestCompiler_compile_Le_Lt_Gt_Ge_Eq_Eqz_Ne(t *testing.T) {
 							// Emit the operation.
 							switch kind {
 							case wazeroir.OperationKindLe:
-								err = compiler.compileLe(wazeroir.OperationLe{Type: signedType})
+								err = compiler.compileLe(wazeroir.NewOperationLe(signedType))
 							case wazeroir.OperationKindLt:
-								err = compiler.compileLt(wazeroir.OperationLt{Type: signedType})
+								err = compiler.compileLt(wazeroir.NewOperationLt(signedType))
 							case wazeroir.OperationKindGe:
-								err = compiler.compileGe(wazeroir.OperationGe{Type: signedType})
+								err = compiler.compileGe(wazeroir.NewOperationGe(signedType))
 							case wazeroir.OperationKindGt:
-								err = compiler.compileGt(wazeroir.OperationGt{Type: signedType})
+								err = compiler.compileGt(wazeroir.NewOperationGt(signedType))
 							case wazeroir.OperationKindEq:
 								// Eq uses UnsignedType instead, so we translate the signed one.
 								switch signedType {
 								case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
-									err = compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
+									err = compiler.compileEq(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32))
 								case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
-									err = compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI64})
+									err = compiler.compileEq(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI64))
 								case wazeroir.SignedTypeFloat32:
-									err = compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeF32})
+									err = compiler.compileEq(wazeroir.NewOperationEq(wazeroir.UnsignedTypeF32))
 								case wazeroir.SignedTypeFloat64:
-									err = compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeF64})
+									err = compiler.compileEq(wazeroir.NewOperationEq(wazeroir.UnsignedTypeF64))
 								}
 							case wazeroir.OperationKindNe:
 								// Ne uses UnsignedType, so we translate the signed one.
 								switch signedType {
 								case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
-									err = compiler.compileNe(wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI32})
+									err = compiler.compileNe(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI32))
 								case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
-									err = compiler.compileNe(wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI64})
+									err = compiler.compileNe(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI64))
 								case wazeroir.SignedTypeFloat32:
-									err = compiler.compileNe(wazeroir.OperationNe{Type: wazeroir.UnsignedTypeF32})
+									err = compiler.compileNe(wazeroir.NewOperationNe(wazeroir.UnsignedTypeF32))
 								case wazeroir.SignedTypeFloat64:
-									err = compiler.compileNe(wazeroir.OperationNe{Type: wazeroir.UnsignedTypeF64})
+									err = compiler.compileNe(wazeroir.NewOperationNe(wazeroir.UnsignedTypeF64))
 								}
 							case wazeroir.OperationKindEqz:
 								// Eqz uses UnsignedInt, so we translate the signed one.
 								switch signedType {
 								case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
-									err = compiler.compileEqz(wazeroir.OperationEqz{Type: wazeroir.UnsignedInt32})
+									err = compiler.compileEqz(wazeroir.NewOperationEqz(wazeroir.UnsignedInt32))
 								case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
-									err = compiler.compileEqz(wazeroir.OperationEqz{Type: wazeroir.UnsignedInt64})
+									err = compiler.compileEqz(wazeroir.NewOperationEqz(wazeroir.UnsignedInt64))
 								}
 							}
 							require.NoError(t, err)
@@ -810,11 +810,11 @@ func TestCompiler_compile_Clz_Ctz_Popcnt(t *testing.T) {
 
 							switch kind {
 							case wazeroir.OperationKindClz:
-								err = compiler.compileClz(wazeroir.OperationClz{Type: tp})
+								err = compiler.compileClz(wazeroir.NewOperationClz(tp))
 							case wazeroir.OperationKindCtz:
-								err = compiler.compileCtz(wazeroir.OperationCtz{Type: tp})
+								err = compiler.compileCtz(wazeroir.NewOperationCtz(tp))
 							case wazeroir.OperationKindPopcnt:
-								err = compiler.compilePopcnt(wazeroir.OperationPopcnt{Type: tp})
+								err = compiler.compilePopcnt(wazeroir.NewOperationPopcnt(tp))
 							}
 							require.NoError(t, err)
 
@@ -868,7 +868,7 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 			name:    "min-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileMin(wazeroir.OperationMin{Type: wazeroir.Float32})
+				err := compiler.compileMin(wazeroir.NewOperationMin(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
@@ -885,7 +885,7 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 			name:    "min-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileMin(wazeroir.OperationMin{Type: wazeroir.Float64})
+				err := compiler.compileMin(wazeroir.NewOperationMin(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
@@ -902,7 +902,7 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 			name:    "max-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileMax(wazeroir.OperationMax{Type: wazeroir.Float32})
+				err := compiler.compileMax(wazeroir.NewOperationMax(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
@@ -919,7 +919,7 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 			name:    "max-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileMax(wazeroir.OperationMax{Type: wazeroir.Float64})
+				err := compiler.compileMax(wazeroir.NewOperationMax(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
@@ -936,7 +936,7 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 			name:    "copysign-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileCopysign(wazeroir.OperationCopysign{Type: wazeroir.Float32})
+				err := compiler.compileCopysign(wazeroir.NewOperationCopysign(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
@@ -953,7 +953,7 @@ func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
 			name:    "copysign-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileCopysign(wazeroir.OperationCopysign{Type: wazeroir.Float64})
+				err := compiler.compileCopysign(wazeroir.NewOperationCopysign(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
@@ -1064,7 +1064,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "abs-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileAbs(wazeroir.OperationAbs{Type: wazeroir.Float32})
+				err := compiler.compileAbs(wazeroir.NewOperationAbs(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1081,7 +1081,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "abs-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileAbs(wazeroir.OperationAbs{Type: wazeroir.Float64})
+				err := compiler.compileAbs(wazeroir.NewOperationAbs(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1098,7 +1098,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "neg-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileNeg(wazeroir.OperationNeg{Type: wazeroir.Float32})
+				err := compiler.compileNeg(wazeroir.NewOperationNeg(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1115,7 +1115,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "neg-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileNeg(wazeroir.OperationNeg{Type: wazeroir.Float64})
+				err := compiler.compileNeg(wazeroir.NewOperationNeg(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1132,7 +1132,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "ceil-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileCeil(wazeroir.OperationCeil{Type: wazeroir.Float32})
+				err := compiler.compileCeil(wazeroir.NewOperationCeil(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1149,7 +1149,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "ceil-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileCeil(wazeroir.OperationCeil{Type: wazeroir.Float64})
+				err := compiler.compileCeil(wazeroir.NewOperationCeil(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1166,7 +1166,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "floor-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileFloor(wazeroir.OperationFloor{Type: wazeroir.Float32})
+				err := compiler.compileFloor(wazeroir.NewOperationFloor(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1183,7 +1183,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "floor-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileFloor(wazeroir.OperationFloor{Type: wazeroir.Float64})
+				err := compiler.compileFloor(wazeroir.NewOperationFloor(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1200,7 +1200,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "trunc-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileTrunc(wazeroir.OperationTrunc{Type: wazeroir.Float32})
+				err := compiler.compileTrunc(wazeroir.NewOperationTrunc(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1217,7 +1217,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "trunc-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileTrunc(wazeroir.OperationTrunc{Type: wazeroir.Float64})
+				err := compiler.compileTrunc(wazeroir.NewOperationTrunc(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1234,7 +1234,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "nearest-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileNearest(wazeroir.OperationNearest{Type: wazeroir.Float32})
+				err := compiler.compileNearest(wazeroir.NewOperationNearest(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1251,7 +1251,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "nearest-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileNearest(wazeroir.OperationNearest{Type: wazeroir.Float64})
+				err := compiler.compileNearest(wazeroir.NewOperationNearest(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1268,7 +1268,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "sqrt-32-bit",
 			is32bit: true,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileSqrt(wazeroir.OperationSqrt{Type: wazeroir.Float32})
+				err := compiler.compileSqrt(wazeroir.NewOperationSqrt(wazeroir.Float32))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1285,7 +1285,7 @@ func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
 			name:    "sqrt-64-bit",
 			is32bit: false,
 			setupFunc: func(t *testing.T, compiler compilerImpl) {
-				err := compiler.compileSqrt(wazeroir.OperationSqrt{Type: wazeroir.Float64})
+				err := compiler.compileSqrt(wazeroir.NewOperationSqrt(wazeroir.Float64))
 				require.NoError(t, err)
 			},
 			verifyFunc: func(t *testing.T, v float64, raw uint64) {
@@ -1462,17 +1462,17 @@ func TestCompiler_compile_Div_Rem(t *testing.T) {
 
 							switch kind {
 							case wazeroir.OperationKindDiv:
-								err = compiler.compileDiv(wazeroir.OperationDiv{Type: signedType})
+								err = compiler.compileDiv(wazeroir.NewOperationDiv(signedType))
 							case wazeroir.OperationKindRem:
 								switch signedType {
 								case wazeroir.SignedTypeInt32:
-									err = compiler.compileRem(wazeroir.OperationRem{Type: wazeroir.SignedInt32})
+									err = compiler.compileRem(wazeroir.NewOperationRem(wazeroir.SignedInt32))
 								case wazeroir.SignedTypeInt64:
-									err = compiler.compileRem(wazeroir.OperationRem{Type: wazeroir.SignedInt64})
+									err = compiler.compileRem(wazeroir.NewOperationRem(wazeroir.SignedInt64))
 								case wazeroir.SignedTypeUint32:
-									err = compiler.compileRem(wazeroir.OperationRem{Type: wazeroir.SignedUint32})
+									err = compiler.compileRem(wazeroir.NewOperationRem(wazeroir.SignedUint32))
 								case wazeroir.SignedTypeUint64:
-									err = compiler.compileRem(wazeroir.OperationRem{Type: wazeroir.SignedUint64})
+									err = compiler.compileRem(wazeroir.NewOperationRem(wazeroir.SignedUint64))
 								case wazeroir.SignedTypeFloat32:
 									// Rem undefined for float32.
 									return
diff --git a/internal/engine/compiler/compiler_stack_test.go b/internal/engine/compiler/compiler_stack_test.go
index 46b97bde..d1230a3c 100644
--- a/internal/engine/compiler/compiler_stack_test.go
+++ b/internal/engine/compiler/compiler_stack_test.go
@@ -122,12 +122,12 @@ func TestCompiler_compileLoadValueOnStackToRegister(t *testing.T) {
 			if tc.isFloat {
 				err = compiler.compileConstF64(wazeroir.NewOperationConstF64(1))
 				require.NoError(t, err)
-				err = compiler.compileAdd(wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF64})
+				err = compiler.compileAdd(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeF64))
 				require.NoError(t, err)
 			} else {
 				err = compiler.compileConstI64(wazeroir.NewOperationConstI64(1))
 				require.NoError(t, err)
-				err = compiler.compileAdd(wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64})
+				err = compiler.compileAdd(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI64))
 				require.NoError(t, err)
 			}
 
@@ -574,9 +574,9 @@ func TestCompiler_compileSelect(t *testing.T) {
 						err = compiler.compileConstI32(wazeroir.NewOperationConstI32(0))
 						require.NoError(t, err)
 						if tc.selectX1 {
-							err = compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
+							err = compiler.compileEq(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32))
 						} else {
-							err = compiler.compileNe(wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI32})
+							err = compiler.compileNe(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI32))
 						}
 						require.NoError(t, err)
 					}
@@ -726,7 +726,7 @@ func TestCompiler_compileSet(t *testing.T) {
 				require.NoError(t, err)
 				err = compiler.compileConstI32(wazeroir.NewOperationConstI32(0))
 				require.NoError(t, err)
-				err = compiler.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
+				err = compiler.compileEq(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32))
 				require.NoError(t, err)
 				x1Value = 1
 			}
diff --git a/internal/engine/compiler/impl_amd64_test.go b/internal/engine/compiler/impl_amd64_test.go
index 37268156..589321f7 100644
--- a/internal/engine/compiler/impl_amd64_test.go
+++ b/internal/engine/compiler/impl_amd64_test.go
@@ -165,11 +165,11 @@ func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
 
 						switch kind {
 						case wazeroir.OperationKindDiv:
-							err = compiler.compileDiv(wazeroir.OperationDiv{Type: wazeroir.SignedTypeUint32})
+							err = compiler.compileDiv(wazeroir.NewOperationDiv(wazeroir.SignedTypeUint32))
 						case wazeroir.OperationKindMul:
-							err = compiler.compileMul(wazeroir.OperationMul{Type: wazeroir.UnsignedTypeI32})
+							err = compiler.compileMul(wazeroir.NewOperationMul(wazeroir.UnsignedTypeI32))
 						case wazeroir.OperationKindRem:
-							err = compiler.compileRem(wazeroir.OperationRem{Type: wazeroir.SignedUint32})
+							err = compiler.compileRem(wazeroir.NewOperationRem(wazeroir.SignedUint32))
 						}
 						require.NoError(t, err)
 
@@ -182,7 +182,7 @@ func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
 						// We add the value previously on the DX with the multiplication result
 						// in order to ensure that not saving existing DX value would cause
 						// the failure in a subsequent instruction.
-						err = compiler.compileAdd(wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI32})
+						err = compiler.compileAdd(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI32))
 						require.NoError(t, err)
 
 						require.NoError(t, compiler.compileReturnFunction())
@@ -291,11 +291,11 @@ func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
 
 						switch kind {
 						case wazeroir.OperationKindDiv:
-							err = compiler.compileDiv(wazeroir.OperationDiv{Type: wazeroir.SignedTypeInt64})
+							err = compiler.compileDiv(wazeroir.NewOperationDiv(wazeroir.SignedTypeInt64))
 						case wazeroir.OperationKindMul:
-							err = compiler.compileMul(wazeroir.OperationMul{Type: wazeroir.UnsignedTypeI64})
+							err = compiler.compileMul(wazeroir.NewOperationMul(wazeroir.UnsignedTypeI64))
 						case wazeroir.OperationKindRem:
-							err = compiler.compileRem(wazeroir.OperationRem{Type: wazeroir.SignedUint64})
+							err = compiler.compileRem(wazeroir.NewOperationRem(wazeroir.SignedUint64))
 						}
 						require.NoError(t, err)
 
@@ -308,7 +308,7 @@ func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
 						// We add the value previously on the DX with the multiplication result
 						// in order to ensure that not saving existing DX value would cause
 						// the failure in a subsequent instruction.
-						err = compiler.compileAdd(wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64})
+						err = compiler.compileAdd(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI64))
 						require.NoError(t, err)
 
 						require.NoError(t, compiler.compileReturnFunction())
@@ -501,7 +501,7 @@ func TestAmd64Compiler_ensureClz_ABM(t *testing.T) {
 			err := compiler.compileConstI32(wazeroir.NewOperationConstI32(10))
 			require.NoError(t, err)
 
-			err = compiler.compileClz(wazeroir.OperationClz{Type: wazeroir.UnsignedInt64})
+			err = compiler.compileClz(wazeroir.NewOperationClz(wazeroir.UnsignedInt64))
 			require.NoError(t, err)
 
 			compiler.compileNOP() // pad for jump target (when no ABM)
@@ -556,7 +556,7 @@ func TestAmd64Compiler_ensureCtz_ABM(t *testing.T) {
 			err := compiler.compileConstI32(wazeroir.NewOperationConstI32(10))
 			require.NoError(t, err)
 
-			err = compiler.compileCtz(wazeroir.OperationCtz{Type: wazeroir.UnsignedInt64})
+			err = compiler.compileCtz(wazeroir.NewOperationCtz(wazeroir.UnsignedInt64))
 			require.NoError(t, err)
 
 			compiler.compileNOP() // pad for jump target (when no ABM)
diff --git a/internal/engine/compiler/impl_vec_amd64_test.go b/internal/engine/compiler/impl_vec_amd64_test.go
index 9573b2f1..f5812b84 100644
--- a/internal/engine/compiler/impl_vec_amd64_test.go
+++ b/internal/engine/compiler/impl_vec_amd64_test.go
@@ -191,7 +191,7 @@ func TestAmd64Compiler_compileV128ShrI64x2SignedImpl(t *testing.T) {
 				require.NoError(t, err)
 				err = c.compileConstI32(wazeroir.NewOperationConstI32(0))
 				require.NoError(t, err)
-				err = c.compileEq(wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
+				err = c.compileEq(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32))
 				require.NoError(t, err)
 			},
 			verifyFn: func(t *testing.T, env *compilerEnv) {},
diff --git a/internal/wazeroir/compiler_test.go b/internal/wazeroir/compiler_test.go
index 066e4a0e..80b9bda6 100644
--- a/internal/wazeroir/compiler_test.go
+++ b/internal/wazeroir/compiler_test.go
@@ -506,12 +506,12 @@ func TestCompile_MultiValue(t *testing.T) {
 						Else: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindElse}},
 					},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindHeader}},
-					NewOperationConstI32(2),             // [$0, 1, 2]
-					OperationAdd{Type: UnsignedTypeI32}, // [$0, 3]
+					NewOperationConstI32(2),          // [$0, 1, 2]
+					NewOperationAdd(UnsignedTypeI32), // [$0, 3]
 					OperationBr{Target: Label{FrameID: 2, Kind: LabelKindContinuation}},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindElse}},
 					NewOperationConstI32(uint32(api.EncodeI32(-2))), // [$0, 1, -2]
-					OperationAdd{Type: UnsignedTypeI32},             // [$0, -1]
+					NewOperationAdd(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]
@@ -565,10 +565,10 @@ func TestCompile_MultiValue(t *testing.T) {
 						Else: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindElse}},
 					},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindHeader}},
-					OperationAdd{Type: UnsignedTypeI32}, // [$0, 3]
+					NewOperationAdd(UnsignedTypeI32), // [$0, 3]
 					OperationBr{Target: Label{FrameID: 2, Kind: LabelKindContinuation}},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindElse}},
-					OperationSub{Type: UnsignedTypeI32}, // [$0, -1]
+					NewOperationSub(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]
@@ -622,10 +622,10 @@ func TestCompile_MultiValue(t *testing.T) {
 						Else: BranchTargetDrop{Target: Label{FrameID: 2, Kind: LabelKindElse}},
 					},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindHeader}},
-					OperationAdd{Type: UnsignedTypeI32}, // [$0, 3]
+					NewOperationAdd(UnsignedTypeI32), // [$0, 3]
 					OperationBr{Target: Label{FrameID: 2, Kind: LabelKindContinuation}},
 					OperationLabel{Label: Label{FrameID: 2, Kind: LabelKindElse}},
-					OperationSub{Type: UnsignedTypeI32}, // [$0, -1]
+					NewOperationSub(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]
@@ -835,7 +835,7 @@ func TestCompile_Refs(t *testing.T) {
 			},
 			expected: []Operation{
 				OperationRefFunc{FunctionIndex: 100},
-				OperationEqz{Type: UnsignedInt64},
+				NewOperationEqz(UnsignedInt64),
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 			},
@@ -850,7 +850,7 @@ func TestCompile_Refs(t *testing.T) {
 			},
 			expected: []Operation{
 				NewOperationConstI64(0),
-				OperationEqz{Type: UnsignedInt64},
+				NewOperationEqz(UnsignedInt64),
 				OperationDrop{Depth: &InclusiveRange{Start: 0, End: 0}},
 				OperationBr{Target: Label{Kind: LabelKindReturn}}, // return!
 			},
diff --git a/internal/wazeroir/operations_test.go b/internal/wazeroir/operations_test.go
index 6e897f5e..42c47250 100644
--- a/internal/wazeroir/operations_test.go
+++ b/internal/wazeroir/operations_test.go
@@ -18,30 +18,48 @@ 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,
+		OperationKindMemorySize,
+		OperationKindMemoryGrow,
 		OperationKindConstI32,
 		OperationKindConstI64,
 		OperationKindConstF32,
 		OperationKindConstF64,
+		OperationKindEq,
+		OperationKindNe,
+		OperationKindEqz,
+		OperationKindLt,
+		OperationKindGt,
+		OperationKindLe,
+		OperationKindGe,
+		OperationKindAdd,
+		OperationKindSub,
+		OperationKindMul,
+		OperationKindClz,
+		OperationKindCtz,
+		OperationKindPopcnt,
+		OperationKindDiv,
+		OperationKindRem,
+		OperationKindAnd,
+		OperationKindOr,
+		OperationKindXor,
+		OperationKindShl,
+		OperationKindShr,
+		OperationKindRotl,
+		OperationKindRotr,
+		OperationKindAbs,
+		OperationKindNeg,
+		OperationKindCeil,
+		OperationKindFloor,
+		OperationKindTrunc,
+		OperationKindNearest,
+		OperationKindSqrt,
+		OperationKindMin,
+		OperationKindMax,
+		OperationKindCopysign,
+		OperationKindBuiltinFunctionCheckExitCode,
 	} {
 		op.OpKind = k
 		require.NotEqual(t, "", op.String())