Repository: tetratelabs/wazero
Pull request #1898 (2024-01-05 03:49:31)

threads: add function validation of atomics


Patch:
diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go
index e9aef72c..2174bf3c 100644
--- a/internal/wasm/func_validation.go
+++ b/internal/wasm/func_validation.go
@@ -86,6 +86,8 @@ func (m *Module) validateFunctionWithMaxStackValues(
 				instName = MiscInstructionName(body[pc+1])
 			} else if op == OpcodeVecPrefix {
 				instName = VectorInstructionName(body[pc+1])
+			} else if op == OpcodeAtomicPrefix {
+				instName = AtomicInstructionName(body[pc+1])
 			} else {
 				instName = InstructionName(op)
 			}
@@ -1404,6 +1406,376 @@ func (m *Module) validateFunctionWithMaxStackValues(
 			}
 			valueTypeStack.pushStackLimit(len(bt.Params))
 			pc += num
+		} else if op == OpcodeAtomicPrefix {
+			pc++
+			// Atomic instructions come with two bytes where the first byte is always OpcodeAtomicPrefix,
+			// and the second byte determines the actual instruction.
+			atomicOpcode := body[pc]
+			// TODO: Check that CoreFeatureThreads is enabled
+			pc++
+
+			if atomicOpcode == OpcodeAtomicFence {
+				// No memory requirement and no arguments or return, however the immediate byte value must be 0.
+				imm := body[pc]
+				if imm != 0x0 {
+					return fmt.Errorf("invalid immediate value for %s", AtomicInstructionName(atomicOpcode))
+				}
+				continue
+			}
+
+			// All atomic operations except fence (checked above) require memory
+			if memory == nil {
+				return fmt.Errorf("memory must exist for %s", AtomicInstructionName(atomicOpcode))
+			}
+			align, _, read, err := readMemArg(pc, body)
+			if err != nil {
+				return err
+			}
+			pc += read - 1
+			switch atomicOpcode {
+			case OpcodeAtomicMemoryNotify:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicMemoryWait32:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicMemoryWait64:
+				if 1<<align > 64/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI32Load:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI64Load:
+				if 1<<align > 64/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI32Load8U:
+				if 1<<align != 1 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI32Load16U:
+				if 1<<align != 16/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI64Load8U:
+				if 1<<align != 1 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI64Load16U:
+				if 1<<align > 16/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI64Load32U:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI32Store:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+			case OpcodeAtomicI64Store:
+				if 1<<align > 64/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+			case OpcodeAtomicI32Store8:
+				if 1<<align > 1 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+			case OpcodeAtomicI32Store16:
+				if 1<<align > 16/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+			case OpcodeAtomicI64Store8:
+				if 1<<align > 1 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+			case OpcodeAtomicI64Store16:
+				if 1<<align > 16/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+			case OpcodeAtomicI64Store32:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+			case OpcodeAtomicI32RmwAdd, OpcodeAtomicI32RmwSub, OpcodeAtomicI32RmwAnd, OpcodeAtomicI32RmwOr, OpcodeAtomicI32RmwXor, OpcodeAtomicI32RmwXchg:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI32Rmw8AddU, OpcodeAtomicI32Rmw8SubU, OpcodeAtomicI32Rmw8AndU, OpcodeAtomicI32Rmw8OrU, OpcodeAtomicI32Rmw8XorU, OpcodeAtomicI32Rmw8XchgU:
+				if 1<<align > 1 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI32Rmw16AddU, OpcodeAtomicI32Rmw16SubU, OpcodeAtomicI32Rmw16AndU, OpcodeAtomicI32Rmw16OrU, OpcodeAtomicI32Rmw16XorU, OpcodeAtomicI32Rmw16XchgU:
+				if 1<<align > 16/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI64RmwAdd, OpcodeAtomicI64RmwSub, OpcodeAtomicI64RmwAnd, OpcodeAtomicI64RmwOr, OpcodeAtomicI64RmwXor, OpcodeAtomicI64RmwXchg:
+				if 1<<align > 64/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI64Rmw8AddU, OpcodeAtomicI64Rmw8SubU, OpcodeAtomicI64Rmw8AndU, OpcodeAtomicI64Rmw8OrU, OpcodeAtomicI64Rmw8XorU, OpcodeAtomicI64Rmw8XchgU:
+				if 1<<align > 1 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI64Rmw16AddU, OpcodeAtomicI64Rmw16SubU, OpcodeAtomicI64Rmw16AndU, OpcodeAtomicI64Rmw16OrU, OpcodeAtomicI64Rmw16XorU, OpcodeAtomicI64Rmw16XchgU:
+				if 1<<align > 16/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI64Rmw32AddU, OpcodeAtomicI64Rmw32SubU, OpcodeAtomicI64Rmw32AndU, OpcodeAtomicI64Rmw32OrU, OpcodeAtomicI64Rmw32XorU, OpcodeAtomicI64Rmw32XchgU:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI32RmwCmpxchg:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI32Rmw8CmpxchgU:
+				if 1<<align > 1 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI32Rmw16CmpxchgU:
+				if 1<<align > 16/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI32)
+			case OpcodeAtomicI64RmwCmpxchg:
+				if 1<<align > 64/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI64Rmw8CmpxchgU:
+				if 1<<align > 1 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI64Rmw16CmpxchgU:
+				if 1<<align > 16/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			case OpcodeAtomicI64Rmw32CmpxchgU:
+				if 1<<align > 32/8 {
+					return fmt.Errorf("invalid memory alignment")
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI64); err != nil {
+					return err
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return err
+				}
+				valueTypeStack.push(ValueTypeI64)
+			default:
+				return fmt.Errorf("invalid atomic opcode: 0x%x", atomicOpcode)
+			}
 		} else if op == OpcodeLoop {
 			br.Reset(body[pc+1:])
 			bt, num, err := DecodeBlockType(m.TypeSection, br, enabledFeatures)


Test patch:
diff --git a/internal/wasm/func_validation_test.go b/internal/wasm/func_validation_test.go
index e571ad2e..2ec55e76 100644
--- a/internal/wasm/func_validation_test.go
+++ b/internal/wasm/func_validation_test.go
@@ -3678,3 +3678,1171 @@ func Test_SplitCallStack(t *testing.T) {
 		})
 	}
 }
+
+func TestModule_funcValidation_Atomic(t *testing.T) {
+	t.Run("valid bytecode", func(t *testing.T) {
+		tests := []struct {
+			name               string
+			body               []byte
+			noDropBeforeReturn bool
+		}{
+			{
+				name: "i32.atomic.load8_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Load8U, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.load16_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Load16U, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.load",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Load, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.load8_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Load8U, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.load16_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Load16U, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.load32_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Load32U, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.load",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Load, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.store8",
+				body: []byte{
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Store8, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i32.atomic.store16",
+				body: []byte{
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Store16, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i32.atomic.store",
+				body: []byte{
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Store, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i64.atomic.store8",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Store8, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i64.atomic.store16",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Store16, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i64.atomic.store32",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Store32, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i64.atomic.store",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Store, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i32.atomic.rmw8.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AddU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AddU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.add",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwAdd, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AddU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AddU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AddU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.add",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwAdd, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8SubU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16SubU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.sub",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwSub, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8SubU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16SubU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32SubU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.sub",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwSub, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AndU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AndU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.and",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwAnd, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AndU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AndU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AndU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.and",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwAnd, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.or",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8OrU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.or_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16OrU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.or",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwOr, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.or_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8OrU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.or_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16OrU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.or_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32OrU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.or",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwOr, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XorU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XorU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.xor",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwXor, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XorU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XorU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XorU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.xor",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwXor, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XchgU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XchgU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.xchg",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwXchg, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XchgU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XchgU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XchgU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.xchg",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwXchg, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.cmpxchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8CmpxchgU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.cmpxchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.cmpxchg",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwCmpxchg, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8CmpxchgU, 0x0, 0x8, // alignment=2^0, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.cmpxchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.cmpxchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.cmpxchg",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwCmpxchg, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "memory.atomic.wait32",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicMemoryWait32, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "memory.atomic.wait64",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicMemoryWait64, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "memory.atomic.notify",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicMemoryNotify, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "memory.atomic.fence",
+				body: []byte{
+					OpcodeAtomicPrefix, OpcodeAtomicFence, 0x0,
+				},
+				noDropBeforeReturn: true,
+			},
+		}
+
+		for _, tt := range tests {
+			tc := tt
+			t.Run(tc.name, func(t *testing.T) {
+				body := append([]byte{}, tc.body...)
+				if !tt.noDropBeforeReturn {
+					body = append(body, OpcodeDrop)
+				}
+				body = append(body, OpcodeEnd)
+				m := &Module{
+					TypeSection:     []FunctionType{v_v},
+					FunctionSection: []Index{0},
+					CodeSection:     []Code{{Body: body}},
+				}
+
+				t.Run("with memory", func(t *testing.T) {
+					err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
+						0, []Index{0}, nil, &Memory{}, []Table{}, nil, bytes.NewReader(nil))
+					require.NoError(t, err)
+				})
+
+				t.Run("without memory", func(t *testing.T) {
+					err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
+						0, []Index{0}, nil, nil, []Table{}, nil, bytes.NewReader(nil))
+					// Only fence doesn't require memory
+					if tc.name == "memory.atomic.fence" {
+						require.NoError(t, err)
+					} else {
+						require.Error(t, err, fmt.Sprintf("memory must exist for %s", tc.name))
+					}
+				})
+			})
+		}
+	})
+
+	t.Run("atomic.fence bad immediate", func(t *testing.T) {
+		body := []byte{
+			OpcodeAtomicPrefix, OpcodeAtomicFence, 0x1,
+			OpcodeEnd,
+		}
+		m := &Module{
+			TypeSection:     []FunctionType{v_v},
+			FunctionSection: []Index{0},
+			CodeSection:     []Code{{Body: body}},
+		}
+		err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
+			0, []Index{0}, nil, &Memory{}, []Table{}, nil, bytes.NewReader(nil))
+		require.Error(t, err, "invalid immediate value for atomic.fence")
+	})
+
+	t.Run("bad alignment", func(t *testing.T) {
+		tests := []struct {
+			name               string
+			body               []byte
+			noDropBeforeReturn bool
+		}{
+			{
+				name: "i32.atomic.load8_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Load8U, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.load16_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Load16U, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.load",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Load, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.load8_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Load8U, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.load16_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Load16U, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.load32_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Load32U, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.load",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Load, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.store8",
+				body: []byte{
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Store8, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i32.atomic.store16",
+				body: []byte{
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Store16, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i32.atomic.store",
+				body: []byte{
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x0,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Store, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i64.atomic.store8",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Store8, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i64.atomic.store16",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Store16, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i64.atomic.store32",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Store32, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i64.atomic.store",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Store, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+				noDropBeforeReturn: true,
+			},
+			{
+				name: "i32.atomic.rmw8.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AddU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AddU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.add",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwAdd, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AddU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AddU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.add_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AddU, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.add",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwAdd, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8SubU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16SubU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.sub",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwSub, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8SubU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16SubU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.sub_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32SubU, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.sub",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwSub, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8AndU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16AndU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.and",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwAnd, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8AndU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16AndU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.and_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32AndU, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.and",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwAnd, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.or",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8OrU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.or_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16OrU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.or",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwOr, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.or_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8OrU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.or_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16OrU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.or_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32OrU, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.or",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwOr, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XorU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XorU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.xor",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwXor, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XorU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XorU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.xor_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XorU, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.xor",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwXor, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8XchgU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16XchgU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.xchg",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwXchg, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8XchgU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16XchgU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32XchgU, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.xchg",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwXchg, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw8.cmpxchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw8CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw16.cmpxchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI32Rmw16CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i32.atomic.rmw.cmpxchg",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeI32Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI32RmwCmpxchg, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw8.xchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw8CmpxchgU, 0x1, 0x8, // alignment=2^1, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw16.cmpxchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw16CmpxchgU, 0x2, 0x8, // alignment=2^2, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw32.cmpxchg_u",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicI64Rmw32CmpxchgU, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "i64.atomic.rmw.cmpxchg",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicI64RmwCmpxchg, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "memory.atomic.wait32",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicMemoryWait32, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+			{
+				name: "memory.atomic.wait64",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI64Const, 0x1,
+					OpcodeI64Const, 0x2,
+					OpcodeAtomicPrefix, OpcodeAtomicMemoryWait64, 0x4, 0x8, // alignment=2^4, offset=8
+				},
+			},
+			{
+				name: "memory.atomic.notify",
+				body: []byte{
+					OpcodeI32Const, 0x0,
+					OpcodeI32Const, 0x1,
+					OpcodeAtomicPrefix, OpcodeAtomicMemoryNotify, 0x3, 0x8, // alignment=2^3, offset=8
+				},
+			},
+		}
+
+		for _, tt := range tests {
+			tc := tt
+			t.Run(tc.name, func(t *testing.T) {
+				body := append([]byte{}, tc.body...)
+				if !tt.noDropBeforeReturn {
+					body = append(body, OpcodeDrop)
+				}
+				body = append(body, OpcodeEnd)
+				m := &Module{
+					TypeSection:     []FunctionType{v_v},
+					FunctionSection: []Index{0},
+					CodeSection:     []Code{{Body: body}},
+				}
+				err := m.validateFunction(&stacks{}, api.CoreFeaturesV2,
+					0, []Index{0}, nil, &Memory{}, []Table{}, nil, bytes.NewReader(nil))
+				require.Error(t, err, "invalid memory alignment")
+			})
+		}
+	})
+}