Repository: tetratelabs/wazero
Pull request #1634 (2023-08-17 00:52:17)

wazevo: adds support for local.tee, selects
#1496 


Patch:
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index f21dd192..f4a79b71 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -4119,7 +4119,6 @@ func (ce *callEngine) callNativeFuncWithListener(ctx context.Context, m *wasm.Mo
 // popMemoryOffset takes a memory offset off the stack for use in load and store instructions.
 // As the top of stack value is 64-bit, this ensures it is in range before returning it.
 func (ce *callEngine) popMemoryOffset(op *wazeroir.UnionOperation) uint32 {
-	// TODO: Document what 'us' is and why we expect to look at value 1.
 	offset := op.U2 + ce.popValue()
 	if offset > math.MaxUint32 {
 		panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr.go b/internal/engine/wazevo/backend/isa/arm64/instr.go
index 4f4a60ea..cf601b47 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr.go
@@ -87,6 +87,9 @@ var defKinds = [numInstructionKinds]defKind{
 	fpuStore64:      defKindNone,
 	fpuStore128:     defKindNone,
 	udf:             defKindNone,
+	cSel:            defKindRD,
+	fpuCSel32:       defKindRD,
+	fpuCSel64:       defKindRD,
 }
 
 // defs returns the list of regalloc.VReg that are defined by the instruction.
@@ -176,6 +179,9 @@ var useKinds = [numInstructionKinds]useKind{
 	fpuStore128:     useKindRNAMode,
 	loadFpuConst32:  useKindNone,
 	loadFpuConst64:  useKindNone,
+	cSel:            useKindRNRM,
+	fpuCSel32:       useKindRNRM,
+	fpuCSel64:       useKindRNRM,
 }
 
 // uses returns the list of regalloc.VReg that are used by the instruction.
@@ -440,12 +446,35 @@ func (i *instruction) asFpuLoad(dst operand, amode addressMode, sizeInBits byte)
 	i.amode = amode
 }
 
-func (i *instruction) asCSst(rd regalloc.VReg, c condFlag) {
+func (i *instruction) asCSet(rd regalloc.VReg, c condFlag) {
 	i.kind = cSet
 	i.rd = operandNR(rd)
 	i.u1 = uint64(c)
 }
 
+func (i *instruction) asCSel(rd, rn, rm operand, c condFlag, _64bit bool) {
+	i.kind = cSel
+	i.rd = rd
+	i.rn = rn
+	i.rm = rm
+	i.u1 = uint64(c)
+	if _64bit {
+		i.u3 = 1
+	}
+}
+
+func (i *instruction) asFpuCSel(rd, rn, rm operand, c condFlag, _64bit bool) {
+	if _64bit {
+		i.kind = fpuCSel64
+	} else {
+		i.kind = fpuCSel32
+	}
+	i.rd = rd
+	i.rn = rn
+	i.rm = rm
+	i.u1 = uint64(c)
+}
+
 func (i *instruction) asBr(target label) {
 	if target == returnLabel {
 		panic("BUG: call site should special case for returnLabel")
@@ -521,7 +550,7 @@ func (i *instruction) asFpuCmp(rn, rm operand, is64bit bool) {
 	i.kind = fpuCmp
 	i.rn, i.rm = rn, rm
 	if is64bit {
-		i.u1 = 1
+		i.u3 = 1
 	}
 }
 
@@ -757,7 +786,13 @@ func (i *instruction) String() (str string) {
 		}
 		str = fmt.Sprintf("%sxt%s %s, %s", signedStr, fromStr, formatVRegSized(i.rd.nr(), toBits), formatVRegSized(i.rn.nr(), 32))
 	case cSel:
-		panic("TODO")
+		size := is64SizeBitToSize(i.u3)
+		str = fmt.Sprintf("csel %s, %s, %s, %s",
+			formatVRegSized(i.rd.nr(), size),
+			formatVRegSized(i.rn.nr(), size),
+			formatVRegSized(i.rm.nr(), size),
+			condFlag(i.u1),
+		)
 	case cSet:
 		str = fmt.Sprintf("cset %s, %s", formatVRegSized(i.rd.nr(), 64), condFlag(i.u1))
 	case cCmpImm:
@@ -805,9 +840,19 @@ func (i *instruction) String() (str string) {
 	case intToFpu:
 		panic("TODO")
 	case fpuCSel32:
-		panic("TODO")
+		str = fmt.Sprintf("fcsel %s, %s, %s, %s",
+			formatVRegSized(i.rd.nr(), 32),
+			formatVRegSized(i.rn.nr(), 32),
+			formatVRegSized(i.rm.nr(), 32),
+			condFlag(i.u1),
+		)
 	case fpuCSel64:
-		panic("TODO")
+		str = fmt.Sprintf("fcsel %s, %s, %s, %s",
+			formatVRegSized(i.rd.nr(), 64),
+			formatVRegSized(i.rn.nr(), 64),
+			formatVRegSized(i.rm.nr(), 64),
+			condFlag(i.u1),
+		)
 	case fpuRound:
 		panic("TODO")
 	case movToFpu:
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
index 111df9e2..52baefed 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go
@@ -195,7 +195,7 @@ func (i *instruction) encode(c backend.Compiler) {
 		// https://developer.arm.com/documentation/ddi0596/2020-12/SIMD-FP-Instructions/FCMP--Floating-point-quiet-Compare--scalar--?lang=en
 		rn, rm := regNumberInEncoding[i.rn.realReg()], regNumberInEncoding[i.rm.realReg()]
 		var ftype uint32
-		if i.u1 == 1 {
+		if i.u3 == 1 {
 			ftype = 0b01 // double precision.
 		}
 		c.Emit4Bytes(0b1111<<25 | ftype<<22 | 1<<21 | rm<<16 | 0b1<<13 | rn<<5)
@@ -212,11 +212,58 @@ func (i *instruction) encode(c backend.Compiler) {
 		c.Emit4Bytes(
 			uint32(off&0b11)<<29 | 0b1<<28 | uint32(off&0b1111111111_1111111100)<<3 | rd,
 		)
+	case cSel:
+		c.Emit4Bytes(encodeConditionalSelect(
+			kind,
+			regNumberInEncoding[i.rd.realReg()],
+			regNumberInEncoding[i.rn.realReg()],
+			regNumberInEncoding[i.rm.realReg()],
+			condFlag(i.u1),
+			i.u3 == 1,
+		))
+	case fpuCSel32:
+		c.Emit4Bytes(encodeFpuCSel(
+			regNumberInEncoding[i.rd.realReg()],
+			regNumberInEncoding[i.rn.realReg()],
+			regNumberInEncoding[i.rm.realReg()],
+			condFlag(i.u1),
+			false,
+		))
+	case fpuCSel64:
+		c.Emit4Bytes(encodeFpuCSel(
+			regNumberInEncoding[i.rd.realReg()],
+			regNumberInEncoding[i.rn.realReg()],
+			regNumberInEncoding[i.rm.realReg()],
+			condFlag(i.u1),
+			true,
+		))
 	default:
 		panic(i.String())
 	}
 }
 
+func encodeFpuCSel(rd, rn, rm uint32, c condFlag, _64bit bool) uint32 {
+	var ftype uint32
+	if _64bit {
+		ftype = 0b01 // double precision.
+	}
+	return 0b1111<<25 | ftype<<22 | 0b1<<21 | rm<<16 | uint32(c)<<12 | 0b11<<10 | rn<<5 | rd
+}
+
+// encodeConditionalSelect encodes as "Conditional select" in
+// https://developer.arm.com/documentation/ddi0596/2020-12/Index-by-Encoding/Data-Processing----Register?lang=en#condsel
+func encodeConditionalSelect(kind instructionKind, rd, rn, rm uint32, c condFlag, _64bit bool) uint32 {
+	if kind != cSel {
+		panic("TODO: support other conditional select")
+	}
+
+	ret := 0b110101<<23 | rm<<16 | uint32(c)<<12 | rn<<5 | rd
+	if _64bit {
+		ret |= 0b1 << 31
+	}
+	return ret
+}
+
 // encodeLoadFpuConst32 encodes the following three instructions:
 //
 //	ldr s8, #8  ;; literal load of data.f32
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
index 30d38ce9..f9ccd7ca 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr.go
@@ -7,6 +7,8 @@ package arm64
 // and merge the multiple instructions if possible. It can be considered as "N:1" instruction selection.
 
 import (
+	"fmt"
+
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
@@ -55,31 +57,10 @@ func (m *machine) LowerConditionalBranch(b *ssa.Instruction) {
 			cc = cc.invert()
 		}
 
-		if x.Type() != y.Type() {
-			panic("TODO(maybe): support icmp with different types")
-		}
-
-		extMod := extModeOf(x.Type(), signed)
-
+		m.lowerIcmpToFlag(x, y, signed)
 		cbr := m.allocateInstr()
 		cbr.asCondBr(cc.asCond(), target, false /* ignored */)
-
-		// First operand must be in pure register form.
-		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extMod)
-		// Second operand can be in any of Imm12, ER, SR, or NR form supported by the SUBS instructions.
-		rm := m.getOperand_Imm12_ER_SR_NR(m.compiler.ValueDefinition(y), extMod)
-
-		alu := m.allocateInstr()
-		// subs zr, rn, rm
-		alu.asALU(
-			aluOpSubS,
-			// We don't need the result, just need to set flags.
-			operandNR(xzrVReg),
-			rn,
-			rm,
-			x.Type().Bits() == 64,
-		)
-		m.insert2(alu, cbr)
+		m.insert(cbr)
 		m.compiler.MarkLowered(cvalDef.Instr)
 	case m.compiler.MatchInstr(cvalDef, ssa.OpcodeFcmp): // This case we can use the Fpu flag directly.
 		cvalInstr := cvalDef.Instr
@@ -88,18 +69,10 @@ func (m *machine) LowerConditionalBranch(b *ssa.Instruction) {
 		if b.Opcode() == ssa.OpcodeBrz {
 			cc = cc.invert()
 		}
-
-		if x.Type() != y.Type() {
-			panic("TODO(maybe): support icmp with different types")
-		}
-
-		rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
-		rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
-		cmp := m.allocateInstr()
-		cmp.asFpuCmp(rn, rm, x.Type().Bits() == 64)
+		m.lowerFcmpToFlag(x, y)
 		cbr := m.allocateInstr()
 		cbr.asCondBr(cc.asCond(), target, false /* ignored */)
-		m.insert2(cmp, cbr)
+		m.insert(cbr)
 		m.compiler.MarkLowered(cvalDef.Instr)
 	default:
 		rn := m.getOperand_NR(cvalDef, extModeNone)
@@ -171,6 +144,9 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		undef := m.allocateInstr()
 		undef.asUDF()
 		m.insert(undef)
+	case ssa.OpcodeSelect:
+		c, x, y := instr.SelectData()
+		m.lowerSelect(c, x, y, instr.Return())
 	default:
 		panic("TODO: lowering " + instr.Opcode().String())
 	}
@@ -271,7 +247,7 @@ func (m *machine) lowerIcmp(si *ssa.Instruction) {
 	m.insert(alu)
 
 	cset := m.allocateInstr()
-	cset.asCSst(m.compiler.VRegOf(si.Return()), flag)
+	cset.asCSet(m.compiler.VRegOf(si.Return()), flag)
 	m.insert(cset)
 }
 
@@ -303,7 +279,7 @@ func (m *machine) lowerFcmp(x, y, result ssa.Value, c ssa.FloatCmpCond) {
 	m.insert(fc)
 
 	cset := m.allocateInstr()
-	cset.asCSst(m.compiler.VRegOf(result), condFlagFromSSAFloatCmpCond(c))
+	cset.asCSet(m.compiler.VRegOf(result), condFlagFromSSAFloatCmpCond(c))
 	m.insert(cset)
 }
 
@@ -341,18 +317,7 @@ func (m *machine) lowerExitWithCode(execCtxVReg regalloc.VReg, code wazevoapi.Ex
 	m.insert(exitSeq)
 }
 
-func (m *machine) lowerExitIfTrueWithCode(execCtxVReg regalloc.VReg, cond ssa.Value, code wazevoapi.ExitCode) {
-	condDef := m.compiler.ValueDefinition(cond)
-	if !m.compiler.MatchInstr(condDef, ssa.OpcodeIcmp) {
-		// We can have general case just like cachine.LowerConditionalBranch.
-		panic("TODO: OpcodeExitIfTrueWithCode must come after Icmp at the moment")
-	}
-	m.compiler.MarkLowered(condDef.Instr)
-
-	cvalInstr := condDef.Instr
-	x, y, c := cvalInstr.IcmpData()
-	cc, signed := condFlagFromSSAIntegerCmpCond(c), c.Signed()
-
+func (m *machine) lowerIcmpToFlag(x, y ssa.Value, signed bool) {
 	if x.Type() != y.Type() {
 		panic("TODO(maybe): support icmp with different types")
 	}
@@ -375,11 +340,92 @@ func (m *machine) lowerExitIfTrueWithCode(execCtxVReg regalloc.VReg, cond ssa.Va
 		x.Type().Bits() == 64,
 	)
 	m.insert(alu)
+}
+
+func (m *machine) lowerFcmpToFlag(x, y ssa.Value) {
+	if x.Type() != y.Type() {
+		panic("TODO(maybe): support icmp with different types")
+	}
+
+	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+	rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
+	cmp := m.allocateInstr()
+	cmp.asFpuCmp(rn, rm, x.Type().Bits() == 64)
+	fmt.Println(x.Type())
+	m.insert(cmp)
+}
+
+func (m *machine) lowerExitIfTrueWithCode(execCtxVReg regalloc.VReg, cond ssa.Value, code wazevoapi.ExitCode) {
+	condDef := m.compiler.ValueDefinition(cond)
+	if !m.compiler.MatchInstr(condDef, ssa.OpcodeIcmp) {
+		// We can have general case just like cachine.LowerConditionalBranch.
+		panic("TODO: OpcodeExitIfTrueWithCode must come after Icmp at the moment")
+	}
+	m.compiler.MarkLowered(condDef.Instr)
+
+	cvalInstr := condDef.Instr
+	x, y, c := cvalInstr.IcmpData()
+	signed := c.Signed()
+	m.lowerIcmpToFlag(x, y, signed)
 
 	// We have to skip the entire exit sequence if the condition is false.
 	cbr := m.allocateInstr()
-	cbr.asCondBr(cc.invert().asCond(), invalidLabel, false /* ignored */)
+	cbr.asCondBr(condFlagFromSSAIntegerCmpCond(c).invert().asCond(), invalidLabel, false /* ignored */)
 	cbr.condBrOffsetResolve(exitWithCodeEncodingSize + 4 /* br offset is from the beginning of this instruction */)
 	m.insert(cbr)
 	m.lowerExitWithCode(execCtxVReg, code)
 }
+
+func (m *machine) lowerSelect(c, x, y, result ssa.Value) {
+	cvalDef := m.compiler.ValueDefinition(c)
+
+	var cc condFlag
+	switch {
+	case m.compiler.MatchInstr(cvalDef, ssa.OpcodeIcmp): // This case, we can use the ALU flag set by SUBS instruction.
+		cvalInstr := cvalDef.Instr
+		x, y, c := cvalInstr.IcmpData()
+		cc = condFlagFromSSAIntegerCmpCond(c)
+		m.lowerIcmpToFlag(x, y, c.Signed())
+		m.compiler.MarkLowered(cvalDef.Instr)
+	case m.compiler.MatchInstr(cvalDef, ssa.OpcodeFcmp): // This case we can use the Fpu flag directly.
+		cvalInstr := cvalDef.Instr
+		x, y, c := cvalInstr.FcmpData()
+		cc = condFlagFromSSAFloatCmpCond(c)
+		m.lowerFcmpToFlag(x, y)
+		m.compiler.MarkLowered(cvalDef.Instr)
+	default:
+		rn := m.getOperand_NR(cvalDef, extModeNone)
+		if c.Type() != ssa.TypeI32 && c.Type() != ssa.TypeI64 {
+			panic("TODO?BUG?: support select with non-integer condition")
+		}
+		alu := m.allocateInstr()
+		// subs zr, rn, zr
+		alu.asALU(
+			aluOpSubS,
+			// We don't need the result, just need to set flags.
+			operandNR(xzrVReg),
+			rn,
+			operandNR(xzrVReg),
+			c.Type().Bits() == 64,
+		)
+		m.insert(alu)
+		cc = ne
+	}
+
+	rn := m.getOperand_NR(m.compiler.ValueDefinition(x), extModeNone)
+	rm := m.getOperand_NR(m.compiler.ValueDefinition(y), extModeNone)
+
+	rd := operandNR(m.compiler.VRegOf(result))
+	switch x.Type() {
+	case ssa.TypeI32, ssa.TypeI64:
+		// csel rd, rn, rm, cc
+		csel := m.allocateInstr()
+		csel.asCSel(rd, rn, rm, cc, x.Type().Bits() == 64)
+		m.insert(csel)
+	case ssa.TypeF32, ssa.TypeF64:
+		// fcsel rd, rn, rm, cc
+		fcsel := m.allocateInstr()
+		fcsel.asFpuCSel(rd, rn, rm, cc, x.Type().Bits() == 64)
+		m.insert(fcsel)
+	}
+}
diff --git a/internal/engine/wazevo/backend/isa/arm64/machine.go b/internal/engine/wazevo/backend/isa/arm64/machine.go
index 0cd712af..637ca30c 100644
--- a/internal/engine/wazevo/backend/isa/arm64/machine.go
+++ b/internal/engine/wazevo/backend/isa/arm64/machine.go
@@ -214,10 +214,6 @@ func (m *machine) insert(i *instruction) {
 	m.pendingInstructions = append(m.pendingInstructions, i)
 }
 
-func (m *machine) insert2(i1, i2 *instruction) {
-	m.pendingInstructions = append(m.pendingInstructions, i1, i2)
-}
-
 func (m *machine) FlushPendingInstructions() {
 	l := len(m.pendingInstructions)
 	if l == 0 {
diff --git a/internal/engine/wazevo/frontend/lower.go b/internal/engine/wazevo/frontend/lower.go
index f867ab42..9445b65b 100644
--- a/internal/engine/wazevo/frontend/lower.go
+++ b/internal/engine/wazevo/frontend/lower.go
@@ -71,6 +71,11 @@ func (l *loweringState) reset() {
 	l.unreachableDepth = 0
 }
 
+func (l *loweringState) peek() (ret ssa.Value) {
+	tail := len(l.values) - 1
+	return l.values[tail]
+}
+
 func (l *loweringState) pop() (ret ssa.Value) {
 	tail := len(l.values) - 1
 	ret = l.values[tail]
@@ -450,8 +455,36 @@ func (c *Compiler) lowerOpcode(op wasm.Opcode) {
 			return
 		}
 		variable := c.localVariable(index)
-		v := state.pop()
-		builder.DefineVariableInCurrentBB(variable, v)
+		newValue := state.pop()
+		builder.DefineVariableInCurrentBB(variable, newValue)
+
+	case wasm.OpcodeLocalTee:
+		index := c.readI32u()
+		if state.unreachable {
+			return
+		}
+		variable := c.localVariable(index)
+		newValue := state.peek()
+		builder.DefineVariableInCurrentBB(variable, newValue)
+
+	case wasm.OpcodeSelect, wasm.OpcodeTypedSelect:
+		if op == wasm.OpcodeTypedSelect {
+			state.pc += 2 // ignores the type which is only needed during validation.
+		}
+
+		if state.unreachable {
+			return
+		}
+
+		cond := state.pop()
+		v2 := state.pop()
+		v1 := state.pop()
+
+		sl := builder.AllocateInstruction()
+		sl.AsSelect(cond, v1, v2)
+		builder.InsertInstruction(sl)
+		state.push(sl.Return())
+
 	case wasm.OpcodeMemorySize:
 		state.pc++ // skips the memory index.
 		if state.unreachable {
diff --git a/internal/engine/wazevo/ssa/instructions.go b/internal/engine/wazevo/ssa/instructions.go
index 6f58b974..b7dfb217 100644
--- a/internal/engine/wazevo/ssa/instructions.go
+++ b/internal/engine/wazevo/ssa/instructions.go
@@ -319,14 +319,9 @@ const (
 	// `nop`.
 	OpcodeNop
 
-	// OpcodeSelect ...
-	// `v = select c, x, y`.
+	// OpcodeSelect chooses between two values based on a condition `c`: `v = Select c, x, y`.
 	OpcodeSelect
 
-	// OpcodeSelectSpectreGuard ...
-	// `v = select_spectre_guard c, x, y`.
-	OpcodeSelectSpectreGuard
-
 	// OpcodeBitselect ...
 	// `v = bitselect c, x, y`.
 	OpcodeBitselect
@@ -868,6 +863,7 @@ var instructionSideEffects = [opcodeEnd]sideEffect{
 	OpcodeFdiv:               sideEffectFalse,
 	OpcodeFmul:               sideEffectFalse,
 	OpcodeFmax:               sideEffectFalse,
+	OpcodeSelect:             sideEffectFalse,
 	OpcodeFmin:               sideEffectFalse,
 }
 
@@ -888,6 +884,7 @@ var instructionReturnTypes = [opcodeEnd]returnTypesFn{
 	OpcodeJump:      returnTypesFnNoReturns,
 	OpcodeUndefined: returnTypesFnNoReturns,
 	OpcodeIconst:    returnTypesFnSingle,
+	OpcodeSelect:    returnTypesFnSingle,
 	OpcodeSExtend:   returnTypesFnSingle,
 	OpcodeUExtend:   returnTypesFnSingle,
 	OpcodeCallIndirect: func(b *builder, instr *Instruction) (t1 Type, ts []Type) {
@@ -1339,6 +1336,23 @@ func (i *Instruction) ExtendData() (from, to byte, signed bool) {
 	return
 }
 
+// AsSelect initializes this instruction as an unsigned extension instruction with OpcodeSelect.
+func (i *Instruction) AsSelect(c, x, y Value) {
+	i.opcode = OpcodeSelect
+	i.v = c
+	i.v2 = x
+	i.u64 = uint64(y)
+	i.typ = x.Type()
+}
+
+// SelectData returns the select data for this instruction necessary for backends.
+func (i *Instruction) SelectData() (c, x, y Value) {
+	c = i.v
+	x = i.v2
+	y = Value(i.u64)
+	return
+}
+
 // ExtendFromToBits returns the from and to bit size for the extension instruction.
 func (i *Instruction) ExtendFromToBits() (from, to byte) {
 	from = byte(i.u64 >> 8)
@@ -1379,6 +1393,8 @@ func (i *Instruction) Format(b Builder) string {
 		instSuffix = fmt.Sprintf(" %s, %#x", i.v.Format(b), int32(i.u64))
 	case OpcodeUload8, OpcodeUload16, OpcodeUload32, OpcodeSload8, OpcodeSload16, OpcodeSload32:
 		instSuffix = fmt.Sprintf(" %s, %#x", i.v.Format(b), int32(i.u64))
+	case OpcodeSelect:
+		instSuffix = fmt.Sprintf(" %s, %s, %s", i.v.Format(b), i.v2.Format(b), Value(i.u64).Format(b))
 	case OpcodeIconst:
 		switch i.typ {
 		case TypeI32:
@@ -1592,8 +1608,6 @@ func (o Opcode) String() (ret string) {
 		return "Nop"
 	case OpcodeSelect:
 		return "Select"
-	case OpcodeSelectSpectreGuard:
-		return "SelectSpectreGuard"
 	case OpcodeBitselect:
 		return "Bitselect"
 	case OpcodeVsplit:
diff --git a/internal/wasm/instruction.go b/internal/wasm/instruction.go
index ba65e9a2..ded24666 100644
--- a/internal/wasm/instruction.go
+++ b/internal/wasm/instruction.go
@@ -35,7 +35,6 @@ const (
 	//
 	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-controlmathsfbrl
 	OpcodeBr Opcode = 0x0c
-	// ^^ TODO: Add a diagram to help explain br l means that branch into AFTER l for non-loop labels
 
 	OpcodeBrIf         Opcode = 0x0d
 	OpcodeBrTable      Opcode = 0x0e
diff --git a/internal/wasm/store.go b/internal/wasm/store.go
index d056133b..da531307 100644
--- a/internal/wasm/store.go
+++ b/internal/wasm/store.go
@@ -141,7 +141,6 @@ type (
 		Val uint64
 		// ValHi is only used for vector type globals, and holds the higher bits of the vector.
 		ValHi uint64
-		// ^^ TODO: this should be guarded with atomics when mutable
 	}
 
 	// FunctionTypeID is a uniquely assigned integer for a function type.


Test patch:
diff --git a/internal/engine/wazevo/backend/backend_test.go b/internal/engine/wazevo/backend/backend_test.go
index e1f20f25..e36ee2a1 100644
--- a/internal/engine/wazevo/backend/backend_test.go
+++ b/internal/engine/wazevo/backend/backend_test.go
@@ -56,6 +56,24 @@ L1 (SSA Block: blk0):
 	str x30, [sp, #-0x10]!
 	ldr x30, [sp], #0x10
 	ret
+`,
+		},
+		{
+			name: "selects", m: testcases.Selects.Module,
+			afterFinalizeARM64: `
+L1 (SSA Block: blk0):
+	str x30, [sp, #-0x10]!
+	subs xzr, x4, x5
+	csel w0, w2, w3, eq
+	subs wzr, w3, wzr
+	csel x1, x4, x5, ne
+	fcmp d2, d3
+	fcsel s8, s0, s1, gt
+	fcmp s0, s1
+	fcsel d1, d2, d3, ne
+	mov q0.8b, q8.8b
+	ldr x30, [sp], #0x10
+	ret
 `,
 		},
 		{
@@ -1420,17 +1438,17 @@ L1 (SSA Block: blk0):
 	cset x10?, ls
 	fcmp s2?, s3?
 	cset x11?, ge
-	fcmp s4?, s5?
+	fcmp d4?, d5?
 	cset x12?, eq
-	fcmp s4?, s5?
+	fcmp d4?, d5?
 	cset x13?, ne
-	fcmp s4?, s5?
+	fcmp d4?, d5?
 	cset x14?, mi
-	fcmp s4?, s5?
+	fcmp d4?, d5?
 	cset x15?, gt
-	fcmp s4?, s5?
+	fcmp d4?, d5?
 	cset x16?, ls
-	fcmp s4?, s5?
+	fcmp d4?, d5?
 	cset x17?, ge
 	str w17?, [#ret_space, #0x18]
 	str w16?, [#ret_space, #0x10]
@@ -1461,17 +1479,17 @@ L1 (SSA Block: blk0):
 	cset x4, ls
 	fcmp s0, s1
 	cset x5, ge
-	fcmp s2, s3
+	fcmp d2, d3
 	cset x6, eq
-	fcmp s2, s3
+	fcmp d2, d3
 	cset x7, ne
-	fcmp s2, s3
+	fcmp d2, d3
 	cset x11, mi
-	fcmp s2, s3
+	fcmp d2, d3
 	cset x10, gt
-	fcmp s2, s3
+	fcmp d2, d3
 	cset x9, ls
-	fcmp s2, s3
+	fcmp d2, d3
 	cset x8, ge
 	str w8, [sp, #0x28]
 	str w9, [sp, #0x20]
diff --git a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
index 953fb861..98774814 100644
--- a/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/instr_encoding_test.go
@@ -17,6 +17,14 @@ func TestInstruction_encode(t *testing.T) {
 		setup func(*instruction)
 		want  string
 	}{
+		{want: "4100839a", setup: func(i *instruction) { i.asCSel(operandNR(x1VReg), operandNR(x2VReg), operandNR(x3VReg), eq, true) }},
+		{want: "4110839a", setup: func(i *instruction) { i.asCSel(operandNR(x1VReg), operandNR(x2VReg), operandNR(x3VReg), ne, true) }},
+		{want: "4100831a", setup: func(i *instruction) { i.asCSel(operandNR(x1VReg), operandNR(x2VReg), operandNR(x3VReg), eq, false) }},
+		{want: "4110831a", setup: func(i *instruction) { i.asCSel(operandNR(x1VReg), operandNR(x2VReg), operandNR(x3VReg), ne, false) }},
+		{want: "41cc631e", setup: func(i *instruction) { i.asFpuCSel(operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), gt, true) }},
+		{want: "41bc631e", setup: func(i *instruction) { i.asFpuCSel(operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), lt, true) }},
+		{want: "41cc231e", setup: func(i *instruction) { i.asFpuCSel(operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), gt, false) }},
+		{want: "41bc231e", setup: func(i *instruction) { i.asFpuCSel(operandNR(v1VReg), operandNR(v2VReg), operandNR(v3VReg), lt, false) }},
 		{want: "5b28030b", setup: func(i *instruction) {
 			i.asALU(aluOpAdd, operandNR(tmpRegVReg), operandNR(x2VReg), operandSR(x3VReg, 10, shiftOpLSL), false)
 		}},
@@ -130,8 +138,8 @@ func TestInstruction_encode(t *testing.T) {
 		{want: "b21c4093", setup: func(i *instruction) { i.asExtend(x18VReg, x5VReg, 8, 64, true) }},
 		{want: "b23c4093", setup: func(i *instruction) { i.asExtend(x18VReg, x5VReg, 16, 64, true) }},
 		{want: "b27c4093", setup: func(i *instruction) { i.asExtend(x18VReg, x5VReg, 32, 64, true) }},
-		{want: "f2079f9a", setup: func(i *instruction) { i.asCSst(x18VReg, ne) }},
-		{want: "f2179f9a", setup: func(i *instruction) { i.asCSst(x18VReg, eq) }},
+		{want: "f2079f9a", setup: func(i *instruction) { i.asCSet(x18VReg, ne) }},
+		{want: "f2179f9a", setup: func(i *instruction) { i.asCSet(x18VReg, eq) }},
 		{want: "32008012", setup: func(i *instruction) { i.asMOVN(x18VReg, 1, 0, false) }},
 		{want: "52559512", setup: func(i *instruction) { i.asMOVN(x18VReg, 0xaaaa, 0, false) }},
 		{want: "f2ff9f12", setup: func(i *instruction) { i.asMOVN(x18VReg, 0xffff, 0, false) }},
diff --git a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
index 0187b0ed..4540b803 100644
--- a/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
+++ b/internal/engine/wazevo/backend/isa/arm64/lower_instr_test.go
@@ -152,7 +152,7 @@ func TestMachine_LowerConditionalBranch(t *testing.T) {
 				return cmpInSameGroupFromParams(true, ssa.IntegerCmpCondInvalid, ssa.FloatCmpCondEqual, ctx, builder, m)
 			},
 			instructions: []string{
-				"fcmp s1, s2",
+				"fcmp d1, d2",
 				"b.ne L1",
 			},
 		},
@@ -162,7 +162,7 @@ func TestMachine_LowerConditionalBranch(t *testing.T) {
 				return cmpInSameGroupFromParams(false, ssa.IntegerCmpCondInvalid, ssa.FloatCmpCondGreaterThan, ctx, builder, m)
 			},
 			instructions: []string{
-				"fcmp s1, s2",
+				"fcmp d1, d2",
 				"b.gt L1",
 			},
 		},
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 00994c29..9d7c573a 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -35,6 +35,25 @@ func TestE2E(t *testing.T) {
 		imported, m *wasm.Module
 		calls       []callCase
 	}{
+		{
+			name: "selects", m: testcases.Selects.Module,
+			calls: []callCase{
+				{
+					params: []uint64{
+						0, 1, // i32,
+						200, 100, // i64,
+						uint64(math.Float32bits(3.0)), uint64(math.Float32bits(10.0)),
+						math.Float64bits(-123.4), math.Float64bits(-10000000000.0),
+					},
+					expResults: []uint64{
+						1,
+						200,
+						uint64(math.Float32bits(3.0)),
+						math.Float64bits(-123.4),
+					},
+				},
+			},
+		},
 		{
 			name: "swap", m: testcases.SwapParamAndReturn.Module,
 			calls: []callCase{
diff --git a/internal/engine/wazevo/frontend/frontend_test.go b/internal/engine/wazevo/frontend/frontend_test.go
index 8260b133..6b6014a0 100644
--- a/internal/engine/wazevo/frontend/frontend_test.go
+++ b/internal/engine/wazevo/frontend/frontend_test.go
@@ -77,6 +77,32 @@ blk0: (exec_ctx:i64, module_ctx:i64)
 			expAfterOpt: `
 blk0: (exec_ctx:i64, module_ctx:i64)
 	Jump blk_ret
+`,
+		},
+		{
+			name: "selects", m: testcases.Selects.Module,
+			exp: `
+blk0: (exec_ctx:i64, module_ctx:i64, v2:i32, v3:i32, v4:i64, v5:i64, v6:f32, v7:f32, v8:f64, v9:f64)
+	v10:i32 = Icmp eq, v4, v5
+	v11:i32 = Select v10, v2, v3
+	v12:i64 = Select v3, v4, v5
+	v13:i32 = Fcmp gt, v8, v9
+	v14:f32 = Select v13, v6, v7
+	v15:i32 = Fcmp neq, v6, v7
+	v16:f64 = Select v15, v8, v9
+	Jump blk_ret, v11, v12, v14, v16
+`,
+		},
+		{
+			name: "local param tee return", m: testcases.LocalParamTeeReturn.Module,
+			exp: `
+blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
+	v3:i32 = Iconst_32 0x0
+	Jump blk_ret, v2, v2
+`,
+			expAfterOpt: `
+blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
+	Jump blk_ret, v2, v2
 `,
 		},
 		{
diff --git a/internal/engine/wazevo/testcases/testcases.go b/internal/engine/wazevo/testcases/testcases.go
index 05c4d08a..28666ca0 100644
--- a/internal/engine/wazevo/testcases/testcases.go
+++ b/internal/engine/wazevo/testcases/testcases.go
@@ -98,6 +98,15 @@ var (
 			wasm.OpcodeEnd,
 		}, []wasm.ValueType{i32}),
 	}
+	LocalParamTeeReturn = TestCase{
+		Name: "local_param_tee_return",
+		Module: SingleFunctionModule(i32_i32i32, []byte{
+			wasm.OpcodeLocalGet, 0,
+			wasm.OpcodeLocalTee, 1,
+			wasm.OpcodeLocalGet, 1,
+			wasm.OpcodeEnd,
+		}, []wasm.ValueType{i32}),
+	}
 	SwapParamAndReturn = TestCase{
 		Name: "swap_param_and_return",
 		Module: SingleFunctionModule(i32i32_i32i32, []byte{
@@ -106,6 +115,45 @@ var (
 			wasm.OpcodeEnd,
 		}, nil),
 	}
+	Selects = TestCase{
+		Name: "swap_param_and_return",
+		Module: SingleFunctionModule(i32i32i64i64f32f32f64f64_i32i64, []byte{
+			// i32 select.
+			wasm.OpcodeLocalGet, 0, // x
+			wasm.OpcodeLocalGet, 1, // y
+			// cond
+			wasm.OpcodeLocalGet, 2,
+			wasm.OpcodeLocalGet, 3,
+			wasm.OpcodeI64Eq,
+			wasm.OpcodeSelect,
+
+			// i64 select.
+			wasm.OpcodeLocalGet, 2, // x
+			wasm.OpcodeLocalGet, 3, // y
+			wasm.OpcodeLocalGet, 1, // cond
+			wasm.OpcodeTypedSelect, 1, wasm.ValueTypeI64,
+
+			// f32 select.
+			wasm.OpcodeLocalGet, 4, // x
+			wasm.OpcodeLocalGet, 5, // y
+			// cond
+			wasm.OpcodeLocalGet, 6,
+			wasm.OpcodeLocalGet, 7,
+			wasm.OpcodeF64Gt,
+			wasm.OpcodeTypedSelect, 1, wasm.ValueTypeF32,
+
+			// f64 select.
+			wasm.OpcodeLocalGet, 6, // x
+			wasm.OpcodeLocalGet, 7, // y
+			// cond
+			wasm.OpcodeLocalGet, 4,
+			wasm.OpcodeLocalGet, 5,
+			wasm.OpcodeF32Ne,
+			wasm.OpcodeTypedSelect, 1, wasm.ValueTypeF64,
+
+			wasm.OpcodeEnd,
+		}, nil),
+	}
 	SwapParamsAndReturn = TestCase{
 		Name: "swap_params_and_return",
 		Module: SingleFunctionModule(i32i32_i32i32, []byte{
@@ -1216,16 +1264,17 @@ func SingleFunctionModule(typ wasm.FunctionType, body []byte, localTypes []wasm.
 }
 
 var (
-	vv                  = wasm.FunctionType{}
-	v_i32               = wasm.FunctionType{Results: []wasm.ValueType{i32}}
-	v_i32i32            = wasm.FunctionType{Results: []wasm.ValueType{i32, i32}}
-	i32_v               = wasm.FunctionType{Params: []wasm.ValueType{i32}}
-	i32_i32             = wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}
-	i32i32_i32          = wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}
-	i32i32_i32i32       = wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}}
-	i32_i32i32          = wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32, i32}}
-	i32f32f64_v         = wasm.FunctionType{Params: []wasm.ValueType{i32, f32, f64}, Results: nil}
-	i64f32f64_i64f32f64 = wasm.FunctionType{Params: []wasm.ValueType{i64, f32, f64}, Results: []wasm.ValueType{i64, f32, f64}}
+	vv                              = wasm.FunctionType{}
+	v_i32                           = wasm.FunctionType{Results: []wasm.ValueType{i32}}
+	v_i32i32                        = wasm.FunctionType{Results: []wasm.ValueType{i32, i32}}
+	i32_v                           = wasm.FunctionType{Params: []wasm.ValueType{i32}}
+	i32_i32                         = wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}}
+	i32i32_i32                      = wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}}
+	i32i32_i32i32                   = wasm.FunctionType{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}}
+	i32i32i64i64f32f32f64f64_i32i64 = wasm.FunctionType{Params: []wasm.ValueType{i32, i32, i64, i64, f32, f32, f64, f64}, Results: []wasm.ValueType{i32, i64, f32, f64}}
+	i32_i32i32                      = wasm.FunctionType{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32, i32}}
+	i32f32f64_v                     = wasm.FunctionType{Params: []wasm.ValueType{i32, f32, f64}, Results: nil}
+	i64f32f64_i64f32f64             = wasm.FunctionType{Params: []wasm.ValueType{i64, f32, f64}, Results: []wasm.ValueType{i64, f32, f64}}
 )
 
 const (