Repository: tetratelabs/wazero
Pull request #2055 (2024-02-13 20:00:33)

wazevo(amd64): completes all v2 specification
🎉 👯 🕺 💃 🎖️ 


Patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index 3ce12455..932d0c9a 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -1581,6 +1581,8 @@ const (
 	sseOpcodeUnpcklps
 	sseOpcodeCvtps2pd
 	sseOpcodeCvtpd2ps
+	sseOpcodeCvttpd2dq
+	sseOpcodeShufps
 )
 
 func (s sseOpcode) String() string {
@@ -1925,6 +1927,10 @@ func (s sseOpcode) String() string {
 		return "cvtps2pd"
 	case sseOpcodeCvtpd2ps:
 		return "cvtpd2ps"
+	case sseOpcodeCvttpd2dq:
+		return "cvttpd2dq"
+	case sseOpcodeShufps:
+		return "shufps"
 	default:
 		panic("BUG")
 	}
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
index d097b6ac..f1f7bcdd 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -161,8 +161,6 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3814, 3
 		case sseOpcodeBlendvpd:
 			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3815, 3
-		case sseOpcodeCvttps2dq:
-			legPrex, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5B, 2
 		case sseOpcodeDivps:
 			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0F5E, 2
 		case sseOpcodeDivpd:
@@ -453,6 +451,10 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			prefix, opcode, opcodeNum = legacyPrefixesNone, 0x0F5A, 2
 		case sseOpcodeCvtpd2ps:
 			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0F5A, 2
+		case sseOpcodeCvttps2dq:
+			prefix, opcode, opcodeNum = legacyPrefixes0xF3, 0x0F5B, 2
+		case sseOpcodeCvttpd2dq:
+			prefix, opcode, opcodeNum = legacyPrefixes0x66, 0x0FE6, 2
 		default:
 			panic(fmt.Sprintf("Unsupported sseOpcode: %s", op))
 		}
@@ -1095,6 +1097,8 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A08, 3
 		case sseOpcodeRoundpd:
 			legPrex, opcode, opcodeNum = legacyPrefixes0x66, 0x0F3A09, 3
+		case sseOpcodeShufps:
+			legPrex, opcode, opcodeNum = legacyPrefixesNone, 0x0FC6, 2
 		default:
 			panic(fmt.Sprintf("Unsupported sseOpcode: %s", op))
 		}
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 54de4f10..c766d440 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -34,6 +34,8 @@ func NewBackend() backend.Machine {
 		constI8x16LogicalSHRMaskTableIndex: -1,
 		constF64x2CvtFromIMaskIndex:        -1,
 		constTwop52Index:                   -1,
+		constI32sMaxOnF64x2Index:           -1,
+		constI32uMaxOnF64x2Index:           -1,
 	}
 }
 
@@ -66,7 +68,8 @@ type (
 
 		constSwizzleMaskConstIndex, constSqmulRoundSatIndex,
 		constI8x16SHLMaskTableIndex, constI8x16LogicalSHRMaskTableIndex,
-		constF64x2CvtFromIMaskIndex, constTwop52Index int
+		constF64x2CvtFromIMaskIndex, constTwop52Index,
+		constI32sMaxOnF64x2Index, constI32uMaxOnF64x2Index int
 	}
 
 	_const struct {
@@ -129,6 +132,8 @@ func (m *machine) Reset() {
 	m.constI8x16LogicalSHRMaskTableIndex = -1
 	m.constF64x2CvtFromIMaskIndex = -1
 	m.constTwop52Index = -1
+	m.constI32sMaxOnF64x2Index = -1
+	m.constI32uMaxOnF64x2Index = -1
 }
 
 // ExecutableContext implements backend.Machine.
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine_vec.go b/internal/engine/wazevo/backend/isa/amd64/machine_vec.go
index 501ce9e6..7acb9810 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine_vec.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine_vec.go
@@ -529,20 +529,20 @@ func (m *machine) lowerVFcvtFromInt(x, ret ssa.Value, lane ssa.VecLane, signed b
 			m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePslld, newOperandImm32(0xa), tmp))
 			m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrld, newOperandImm32(0xa), tmp))
 
-			// Subtract the higher 16-bits from vr == clear the lower 16-bits of vr.
+			// Subtract the higher 16-bits from tmp2: clear the lower 16-bits of tmp2.
 			m.insert(m.allocateInstr().asXmmRmR(sseOpcodePsubd, newOperandReg(tmp), tmp2))
 
 			// Convert the lower 16-bits in tmp.
 			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtdq2ps, newOperandReg(tmp), tmp))
 
-			// Left shift by one and convert vr, meaning that halved conversion result of higher 16-bits in vr.
+			// Left shift by one and convert tmp2, meaning that halved conversion result of higher 16-bits in tmp2.
 			m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrld, newOperandImm32(1), tmp2))
 			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtdq2ps, newOperandReg(tmp2), tmp2))
 
 			// Double the converted halved higher 16bits.
 			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAddps, newOperandReg(tmp2), tmp2))
 
-			// Get the conversion result by add tmp (holding lower 16-bit conversion) into vr.
+			// Get the conversion result by add tmp (holding lower 16-bit conversion) into tmp2.
 			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAddps, newOperandReg(tmp), tmp2))
 
 			m.copyTo(tmp2, m.c.VRegOf(ret))
@@ -585,13 +585,13 @@ func (m *machine) lowerVFcvtFromInt(x, ret ssa.Value, lane ssa.VecLane, signed b
 
 var (
 	// i32sMaxOnF64x2 holds math.MaxInt32(=2147483647.0) on two f64 lanes.
-	i32sMaxOnF64x2 = [16]byte{ //nolint:unused
+	i32sMaxOnF64x2 = [16]byte{
 		0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 0xdf, 0x41, // float64(2147483647.0)
 		0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 0xdf, 0x41, // float64(2147483647.0)
 	}
 
 	// i32sMaxOnF64x2 holds math.MaxUint32(=4294967295.0) on two f64 lanes.
-	i32uMaxOnF64x2 = [16]byte{ //nolint:unused
+	i32uMaxOnF64x2 = [16]byte{
 		0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xef, 0x41, // float64(4294967295.0)
 		0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xef, 0x41, // float64(4294967295.0)
 	}
@@ -611,7 +611,132 @@ var (
 )
 
 func (m *machine) lowerVFcvtToIntSat(x, ret ssa.Value, lane ssa.VecLane, signed bool) {
-	panic("TODO")
+	_xx := m.getOperand_Reg(m.c.ValueDefinition(x))
+	xx := m.copyToTmp(_xx.reg())
+
+	switch lane {
+	case ssa.VecLaneF32x4:
+		if signed {
+			tmp := m.copyToTmp(xx)
+
+			// Assuming we have xx = [v1, v2, v3, v4].
+			//
+			// Set all bits if lane is not NaN on tmp.
+			// tmp[i] = 0xffffffff  if vi != NaN
+			//        = 0           if vi == NaN
+			m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeCmpps, uint8(cmpPredEQ_OQ), newOperandReg(tmp), tmp))
+
+			// Clear NaN lanes on xx, meaning that
+			// 	xx[i] = vi  if vi != NaN
+			//	        0   if vi == NaN
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAndps, newOperandReg(tmp), xx))
+
+			// tmp[i] = ^vi         if vi != NaN
+			//        = 0xffffffff  if vi == NaN
+			// which means that tmp[i] & 0x80000000 != 0 if and only if vi is negative.
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeXorps, newOperandReg(xx), tmp))
+
+			// xx[i] = int32(vi)   if vi != NaN and xx is not overflowing.
+			//       = 0x80000000  if vi != NaN and xx is overflowing (See https://www.felixcloutier.com/x86/cvttps2dq)
+			//       = 0           if vi == NaN
+			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvttps2dq, newOperandReg(xx), xx))
+
+			// Below, we have to convert 0x80000000 into 0x7FFFFFFF for positive overflowing lane.
+			//
+			// tmp[i] = 0x80000000                         if vi is positive
+			//        = any satisfying any&0x80000000 = 0  if vi is negative or zero.
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAndps, newOperandReg(xx), tmp))
+
+			// Arithmetic right shifting tmp by 31, meaning that we have
+			// tmp[i] = 0xffffffff if vi is positive, 0 otherwise.
+			m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrad, newOperandImm32(0x1f), tmp))
+
+			// Flipping 0x80000000 if vi is positive, otherwise keep intact.
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp), xx))
+		} else {
+			tmp := m.c.AllocateVReg(ssa.TypeV128)
+			m.insert(m.allocateInstr().asZeros(tmp))
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMaxps, newOperandReg(tmp), xx))
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodePcmpeqd, newOperandReg(tmp), tmp))
+			m.insert(m.allocateInstr().asXmmRmiReg(sseOpcodePsrld, newOperandImm32(0x1), tmp))
+			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvtdq2ps, newOperandReg(tmp), tmp))
+			tmp2 := m.copyToTmp(xx)
+			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvttps2dq, newOperandReg(xx), xx))
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeSubps, newOperandReg(tmp), tmp2))
+			m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeCmpps, uint8(cmpPredLE_OS), newOperandReg(tmp2), tmp))
+			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvttps2dq, newOperandReg(tmp2), tmp2))
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp), tmp2))
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodePxor, newOperandReg(tmp), tmp))
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodePmaxsd, newOperandReg(tmp), tmp2))
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodePaddd, newOperandReg(tmp2), xx))
+		}
+
+	case ssa.VecLaneF64x2:
+		tmp2 := m.c.AllocateVReg(ssa.TypeV128)
+		if signed {
+			tmp := m.copyToTmp(xx)
+
+			// Set all bits for non-NaN lanes, zeros otherwise.
+			// I.e. tmp[i] = 0xffffffff_ffffffff if vi != NaN, 0 otherwise.
+			m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeCmppd, uint8(cmpPredEQ_OQ), newOperandReg(tmp), tmp))
+
+			maskLabel := m.getOrAllocateConstLabel(&m.constI32sMaxOnF64x2Index, i32sMaxOnF64x2[:])
+			// Load the 2147483647 into tmp2's each lane.
+			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskLabel)), tmp2))
+
+			// tmp[i] = 2147483647 if vi != NaN, 0 otherwise.
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAndps, newOperandReg(tmp2), tmp))
+
+			// MINPD returns the source register's value as-is, so we have
+			//  xx[i] = vi   if vi != NaN
+			//        = 0    if vi == NaN
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMinpd, newOperandReg(tmp), xx))
+
+			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeCvttpd2dq, newOperandReg(xx), xx))
+		} else {
+			tmp := m.c.AllocateVReg(ssa.TypeV128)
+			m.insert(m.allocateInstr().asZeros(tmp))
+
+			//  xx[i] = vi   if vi != NaN && vi > 0
+			//        = 0    if vi == NaN || vi <= 0
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMaxpd, newOperandReg(tmp), xx))
+
+			// tmp2[i] = float64(math.MaxUint32) = math.MaxUint32
+			maskIndex := m.getOrAllocateConstLabel(&m.constI32uMaxOnF64x2Index, i32uMaxOnF64x2[:])
+			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskIndex)), tmp2))
+
+			// xx[i] = vi   if vi != NaN && vi > 0 && vi <= math.MaxUint32
+			//       = 0    otherwise
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeMinpd, newOperandReg(tmp2), xx))
+
+			// Round the floating points into integer.
+			m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeRoundpd, 0x3, newOperandReg(xx), xx))
+
+			// tmp2[i] = float64(0x1.0p52)
+			maskIndex = m.getOrAllocateConstLabel(&m.constTwop52Index, twop52[:])
+			m.insert(m.allocateInstr().asXmmUnaryRmR(sseOpcodeMovdqu, newOperandMem(m.newAmodeRipRel(maskIndex)), tmp2))
+
+			// xx[i] = float64(0x1.0p52) + float64(uint32(vi)) if vi != NaN && vi > 0 && vi <= math.MaxUint32
+			//       = 0                                       otherwise
+			//
+			// This means that xx[i] holds exactly the same bit of uint32(vi) in its lower 32-bits.
+			m.insert(m.allocateInstr().asXmmRmR(sseOpcodeAddpd, newOperandReg(tmp2), xx))
+
+			// At this point, we have
+			// 	xx  = [uint32(v0), float64(0x1.0p52), uint32(v1), float64(0x1.0p52)]
+			//  tmp = [0, 0, 0, 0]
+			// as 32x4 lanes. Therefore, SHUFPS with 0b00_00_10_00 results in
+			//	xx = [xx[00], xx[10], tmp[00], tmp[00]] = [xx[00], xx[10], 0, 0]
+			// meaning that for i = 0 and 1, we have
+			//  xx[i] = uint32(vi) if vi != NaN && vi > 0 && vi <= math.MaxUint32
+			//        = 0          otherwise.
+			m.insert(m.allocateInstr().asXmmRmRImm(sseOpcodeShufps, 0b00_00_10_00, newOperandReg(tmp), xx))
+		}
+	default:
+		panic(fmt.Sprintf("invalid lane type: %s", lane))
+	}
+
+	m.copyTo(xx, m.c.VRegOf(ret))
 }
 
 func (m *machine) lowerNarrow(x, y, ret ssa.Value, lane ssa.VecLane, signed bool) {


Test patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
index 22662506..ac7d2531 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
@@ -462,7 +462,7 @@ func TestInstruction_format_encode(t *testing.T) {
 			wantFormat: "blendvps %xmm1, %xmm0",
 		},
 		{
-			setup:      func(i *instruction) { i.asXmmRmR(sseOpcodeCvttps2dq, newOperandReg(xmm1VReg), xmm0VReg) },
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeCvttps2dq, newOperandReg(xmm1VReg), xmm0VReg) },
 			want:       "f30f5bc1",
 			wantFormat: "cvttps2dq %xmm1, %xmm0",
 		},
@@ -4024,6 +4024,18 @@ func TestInstruction_format_encode(t *testing.T) {
 			want:       "66410f5ac7",
 			wantFormat: "cvtpd2ps %xmm15, %xmm0",
 		},
+		{
+			setup:      func(i *instruction) { i.asXmmUnaryRmR(sseOpcodeCvttpd2dq, newOperandReg(xmm15VReg), xmm11VReg) },
+			want:       "66450fe6df",
+			wantFormat: "cvttpd2dq %xmm15, %xmm11",
+		},
+		{
+			setup: func(i *instruction) {
+				i.asXmmRmRImm(sseOpcodeShufps, 0b00_00_10_00, newOperandReg(xmm15VReg), xmm11VReg)
+			},
+			want:       "450fc6df08",
+			wantFormat: "shufps $8, %xmm15, %xmm11",
+		},
 	} {
 		tc := tc
 		t.Run(tc.wantFormat, func(t *testing.T) {
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index 5783c780..fb02bb0d 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -15,8 +15,6 @@ import (
 	"github.com/tetratelabs/wazero/experimental/logging"
 	"github.com/tetratelabs/wazero/experimental/opt"
 	"github.com/tetratelabs/wazero/internal/engine/wazevo/testcases"
-	"github.com/tetratelabs/wazero/internal/integration_test/spectest"
-	v2 "github.com/tetratelabs/wazero/internal/integration_test/spectest/v2"
 	"github.com/tetratelabs/wazero/internal/leb128"
 	"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
 	"github.com/tetratelabs/wazero/internal/testing/dwarftestdata"
@@ -1468,104 +1466,3 @@ func TestDWARF(t *testing.T) {
 	err = r.Close(ctx)
 	require.NoError(t, err)
 }
-
-// TODO: delete this and rune them in internal/integration_test/spectest/v2/spec_test.go after amd64 is done.
-func TestSpectestV2(t *testing.T) {
-	if runtime.GOARCH != "amd64" {
-		return
-	}
-
-	config := opt.NewRuntimeConfigOptimizingCompiler()
-
-	for _, tc := range []struct {
-		name string
-	}{
-		{"block"},
-		{"bulk"},
-		{"i32"},
-		{"i64"},
-		{"br"},
-		{"call"},
-		{"call_indirect"},
-		{"conversions"},
-		{"global"},
-		{"if"},
-		{"linking"},
-		{"loop"},
-		{"memory_copy"},
-		{"memory_fill"},
-		{"memory_init"},
-		{"memory_trap"},
-		{"ref_null"},
-		{"ref_is_null"},
-		{"ref_func"},
-		{"table_copy"},
-		{"table_fill"},
-		{"table_get"},
-		{"table_init"},
-		{"table_set"},
-		{"table_size"},
-		{"select"},
-		{"simd_boolean"},
-		{"simd_bit_shift"},
-		{"simd_bitwise"},
-		{"simd_const"},
-		{"simd_conversions"},
-		{"simd_i8x16_arith"},
-		{"simd_i8x16_arith2"},
-		{"simd_i8x16_sat_arith"},
-		{"simd_i16x8_arith"},
-		{"simd_i16x8_arith2"},
-		{"simd_i16x8_sat_arith"},
-		{"simd_i32x4_arith"},
-		{"simd_i32x4_arith2"},
-		{"simd_i64x2_arith"},
-		{"simd_i64x2_arith2"},
-		{"simd_i8x16_cmp"},
-		{"simd_i16x8_cmp"},
-		{"simd_i32x4_cmp"},
-		{"simd_i64x2_cmp"},
-		{"simd_f32x4"},
-		{"simd_f64x2"},
-		{"simd_f32x4_arith"},
-		{"simd_f64x2_arith"},
-		{"simd_f32x4_cmp"},
-		{"simd_f64x2_cmp"},
-		{"simd_f32x4_rounding"},
-		{"simd_f64x2_rounding"},
-		{"simd_f32x4_pmin_pmax"},
-		{"simd_f64x2_pmin_pmax"},
-		//{"simd_i32x4_trunc_sat_f32x4"},
-		//{"simd_i32x4_trunc_sat_f64x2"},
-		{"simd_i32x4_dot_i16x8"},
-		{"simd_i16x8_extmul_i8x16"},
-		{"simd_i64x2_extmul_i32x4"},
-		{"simd_i32x4_extmul_i16x8"},
-		{"simd_i16x8_extadd_pairwise_i8x16"},
-		{"simd_i32x4_extadd_pairwise_i16x8"},
-		{"simd_i16x8_q15mulr_sat_s"},
-		{"simd_int_to_int_extend"},
-		//{"simd_load"},
-		{"simd_load_extend"},
-		{"simd_load_splat"},
-		{"simd_load_zero"},
-		{"simd_load8_lane"},
-		{"simd_load16_lane"},
-		{"simd_load32_lane"},
-		{"simd_load64_lane"},
-		{"simd_lane"},
-		{"simd_linking"},
-		//{"simd_splat"},
-		{"simd_store"},
-		{"simd_store8_lane"},
-		{"simd_store16_lane"},
-		{"simd_store32_lane"},
-		{"simd_store64_lane"},
-	} {
-		t.Run(tc.name, func(t *testing.T) {
-			ctx := context.Background()
-			spectest.RunCase(t, v2.Testcases, tc.name, ctx, config,
-				-1, 0, math.MaxInt)
-		})
-	}
-}
diff --git a/internal/integration_test/spectest/v2/spec_test.go b/internal/integration_test/spectest/v2/spec_test.go
index 1841cc94..b270ed22 100644
--- a/internal/integration_test/spectest/v2/spec_test.go
+++ b/internal/integration_test/spectest/v2/spec_test.go
@@ -2,7 +2,6 @@ package v2
 
 import (
 	"context"
-	"runtime"
 	"testing"
 
 	"github.com/tetratelabs/wazero"
@@ -26,9 +25,8 @@ func TestInterpreter(t *testing.T) {
 }
 
 func TestWazevo(t *testing.T) {
-	if runtime.GOARCH != "arm64" {
+	if !platform.CompilerSupported() {
 		t.Skip()
 	}
-	c := opt.NewRuntimeConfigOptimizingCompiler().WithCoreFeatures(enabledFeatures)
-	spectest.Run(t, Testcases, context.Background(), c)
+	spectest.Run(t, Testcases, context.Background(), opt.NewRuntimeConfigOptimizingCompiler().WithCoreFeatures(enabledFeatures))
 }