Repository: tetratelabs/wazero
Pull request #613 (2022-06-01 08:20:24)

SIMD: implements bitshift operations
This implements bitshift SIMD instructions for the interpreter and compiler(amd64).
Notably, the engines pass `simd_bit_shift.wast` spec test.

This also fixes a bug in compileV128Shuffle for arm64 while there.

part of #484 

mathetake: ok passed spectest.... will backfill the unittests

Patch:
diff --git a/internal/asm/amd64/consts.go b/internal/asm/amd64/consts.go
index 5f90e604..55444881 100644
--- a/internal/asm/amd64/consts.go
+++ b/internal/asm/amd64/consts.go
@@ -203,12 +203,12 @@ const (
 	POPCNTL
 	// POPCNTQ is the POPCNT instruction in 64-bit mode. https://www.felixcloutier.com/x86/popcnt
 	POPCNTQ
-	// PSLLL is the PSLLW instruction. https://www.felixcloutier.com/x86/psllw:pslld:psllq
-	PSLLL
+	// PSLLD is the PSLLD instruction. https://www.felixcloutier.com/x86/psllw:pslld:psllq
+	PSLLD
 	// PSLLQ is the PSLLQ instruction. https://www.felixcloutier.com/x86/psllw:pslld:psllq
 	PSLLQ
-	// PSRLL is the PSRLW instruction. https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
-	PSRLL
+	// PSRLD is the PSRLD instruction. https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
+	PSRLD
 	// PSRLQ is the PSRLQ instruction. https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
 	PSRLQ
 	// ROLL is the ROL instruction in 32-bit mode. https://www.felixcloutier.com/x86/rcl:rcr:rol:ror
@@ -397,6 +397,18 @@ const (
 	POR
 	// PANDN is the PANDN instruction https://www.felixcloutier.com/x86/pandn
 	PANDN
+	// PSRAD is the PSRAD instruction https://www.felixcloutier.com/x86/psraw:psrad:psraq
+	PSRAD
+	// PSRAW is the PSRAW instruction https://www.felixcloutier.com/x86/psraw:psrad:psraq
+	PSRAW
+	// PSRLW is the PSRLW instruction https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
+	PSRLW
+	// PSLLW is the PSLLW instruction https://www.felixcloutier.com/x86/psllw:pslld:psllq
+	PSLLW
+	// PUNPCKLBW is the PUNPCKLBW instruction https://www.felixcloutier.com/x86/punpcklbw:punpcklwd:punpckldq:punpcklqdq
+	PUNPCKLBW
+	// PUNPCKHBW is the PUNPCKHBW instruction https://www.felixcloutier.com/x86/punpckhbw:punpckhwd:punpckhdq:punpckhqdq
+	PUNPCKHBW
 )
 
 // InstructionName returns the name for an instruction
@@ -562,12 +574,12 @@ func InstructionName(instruction asm.Instruction) string {
 		return "POPCNTL"
 	case POPCNTQ:
 		return "POPCNTQ"
-	case PSLLL:
-		return "PSLLL"
+	case PSLLD:
+		return "PSLLD"
 	case PSLLQ:
 		return "PSLLQ"
-	case PSRLL:
-		return "PSRLL"
+	case PSRLD:
+		return "PSRLD"
 	case PSRLQ:
 		return "PSRLQ"
 	case ROLL:
@@ -756,8 +768,24 @@ func InstructionName(instruction asm.Instruction) string {
 		return "POR"
 	case PANDN:
 		return "PANDN"
+	case PSRAD:
+		return "PSRAD"
+	case PSRAW:
+		return "PSRAW"
+	case PSRLW:
+		return "PSRLW"
+	case PSLLW:
+		return "PSLLW"
+	case PUNPCKLBW:
+		return "PUNPCKLBW"
+	case PUNPCKHBW:
+		return "PUNPCKHBW"
+	case NEGQ:
+		return "NEGQ"
+	case NONE:
+		return "NONE"
 	}
-	return "Unknown"
+	panic(instruction)
 }
 
 // Amd64-specific registers.
diff --git a/internal/asm/amd64/impl.go b/internal/asm/amd64/impl.go
index 79699b18..cc58f074 100644
--- a/internal/asm/amd64/impl.go
+++ b/internal/asm/amd64/impl.go
@@ -1274,6 +1274,26 @@ var registerToRegisterOpcode = map[asm.Instruction]struct {
 	MOVMSKPS: {opcode: []byte{0x0f, 0x50}, requireSrcFloat: true, requireDstFloat: false},
 	// https://www.felixcloutier.com/x86/movmskpd
 	MOVMSKPD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x50}, requireSrcFloat: true, requireDstFloat: false},
+	// https://www.felixcloutier.com/x86/psraw:psrad:psraq
+	PSRAD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xe2}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/psraw:psrad:psraq
+	PSRAW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xe1}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
+	PSRLQ: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xd3}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
+	PSRLD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xd2}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
+	PSRLW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xd1}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/psllw:pslld:psllq
+	PSLLW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xf1}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/psllw:pslld:psllq
+	PSLLD: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xf2}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/psllw:pslld:psllq
+	PSLLQ: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0xf3}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/punpcklbw:punpcklwd:punpckldq:punpcklqdq
+	PUNPCKLBW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x60}, requireSrcFloat: true, requireDstFloat: true},
+	// https://www.felixcloutier.com/x86/punpckhbw:punpckhwd:punpckhdq:punpckhqdq
+	PUNPCKHBW: {mandatoryPrefix: 0x66, opcode: []byte{0x0f, 0x68}, requireSrcFloat: true, requireDstFloat: true},
 }
 
 var RegisterToRegisterShiftOpcode = map[asm.Instruction]struct {
@@ -1875,7 +1895,7 @@ func (a *AssemblerImpl) EncodeConstToRegister(n *NodeImpl) (err error) {
 
 	isFloatReg := IsVectorRegister(n.DstReg)
 	switch n.Instruction {
-	case PSLLL, PSLLQ, PSRLL, PSRLQ:
+	case PSLLD, PSLLQ, PSRLD, PSRLQ, PSRAW, PSRLW, PSLLW:
 		if !isFloatReg {
 			return fmt.Errorf("%s needs float register but got %s", InstructionName(n.Instruction), RegisterName(n.DstReg))
 		}
@@ -1889,9 +1909,9 @@ func (a *AssemblerImpl) EncodeConstToRegister(n *NodeImpl) (err error) {
 		return fmt.Errorf("constant must fit in 32-bit integer for %s, but got %d", InstructionName(n.Instruction), n.SrcConst)
 	} else if (n.Instruction == SHLQ || n.Instruction == SHRQ) && (n.SrcConst < 0 || n.SrcConst > math.MaxUint8) {
 		return fmt.Errorf("constant must fit in positive 8-bit integer for %s, but got %d", InstructionName(n.Instruction), n.SrcConst)
-	} else if (n.Instruction == PSLLL ||
+	} else if (n.Instruction == PSLLD ||
 		n.Instruction == PSLLQ ||
-		n.Instruction == PSRLL ||
+		n.Instruction == PSRLD ||
 		n.Instruction == PSRLQ) && (n.SrcConst < math.MinInt8 || n.SrcConst > math.MaxInt8) {
 		return fmt.Errorf("constant must fit in signed 8-bit integer for %s, but got %d", InstructionName(n.Instruction), n.SrcConst)
 	}
@@ -1988,7 +2008,7 @@ func (a *AssemblerImpl) EncodeConstToRegister(n *NodeImpl) (err error) {
 			a.Buf.Write([]byte{rexPrefix, 0xc1, modRM})
 			a.WriteConst(n.SrcConst, 8)
 		}
-	case PSLLL:
+	case PSLLD:
 		// https://www.felixcloutier.com/x86/psllw:pslld:psllq
 		modRM := 0b11_000_000 | // Specifying that opeand is register.
 			0b00_110_000 | // PSLL with immediate needs "/6" extension.
@@ -2012,10 +2032,10 @@ func (a *AssemblerImpl) EncodeConstToRegister(n *NodeImpl) (err error) {
 			a.Buf.Write([]byte{0x66, 0x0f, 0x73, modRM})
 			a.WriteConst(n.SrcConst, 8)
 		}
-	case PSRLL:
+	case PSRLD:
 		// https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
 		// https://www.felixcloutier.com/x86/psllw:pslld:psllq
-		modRM := 0b11_000_000 | // Specifying that opeand is register.
+		modRM := 0b11_000_000 | // Specifying that operand is register.
 			0b00_010_000 | // PSRL with immediate needs "/2" extension.
 			regBits
 		if rexPrefix != RexPrefixNone {
@@ -2027,7 +2047,7 @@ func (a *AssemblerImpl) EncodeConstToRegister(n *NodeImpl) (err error) {
 		}
 	case PSRLQ:
 		// https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
-		modRM := 0b11_000_000 | // Specifying that opeand is register.
+		modRM := 0b11_000_000 | // Specifying that operand is register.
 			0b00_010_000 | // PSRL with immediate needs "/2" extension.
 			regBits
 		if rexPrefix != RexPrefixNone {
@@ -2037,6 +2057,39 @@ func (a *AssemblerImpl) EncodeConstToRegister(n *NodeImpl) (err error) {
 			a.Buf.Write([]byte{0x66, 0x0f, 0x73, modRM})
 			a.WriteConst(n.SrcConst, 8)
 		}
+	case PSRAW:
+		// https://www.felixcloutier.com/x86/psraw:psrad:psraq
+		modRM := 0b11_000_000 | // Specifying that operand is register.
+			0b00_100_000 | // PSRAW with immediate needs "/4" extension.
+			regBits
+		a.Buf.WriteByte(0x66)
+		if rexPrefix != RexPrefixNone {
+			a.Buf.WriteByte(rexPrefix)
+		}
+		a.Buf.Write([]byte{0x0f, 0x71, modRM})
+		a.WriteConst(n.SrcConst, 8)
+	case PSRLW:
+		// https://www.felixcloutier.com/x86/psrlw:psrld:psrlq
+		modRM := 0b11_000_000 | // Specifying that operand is register.
+			0b00_010_000 | // PSRLW with immediate needs "/2" extension.
+			regBits
+		a.Buf.WriteByte(0x66)
+		if rexPrefix != RexPrefixNone {
+			a.Buf.WriteByte(rexPrefix)
+		}
+		a.Buf.Write([]byte{0x0f, 0x71, modRM})
+		a.WriteConst(n.SrcConst, 8)
+	case PSLLW:
+		// https://www.felixcloutier.com/x86/psllw:pslld:psllq
+		modRM := 0b11_000_000 | // Specifying that operand is register.
+			0b00_110_000 | // PSLLW with immediate needs "/6" extension.
+			regBits
+		a.Buf.WriteByte(0x66)
+		if rexPrefix != RexPrefixNone {
+			a.Buf.WriteByte(rexPrefix)
+		}
+		a.Buf.Write([]byte{0x0f, 0x71, modRM})
+		a.WriteConst(n.SrcConst, 8)
 	case XORL, XORQ:
 		// https://www.felixcloutier.com/x86/xor
 		if inst == XORQ {
diff --git a/internal/asm/amd64/impl_staticconst.go b/internal/asm/amd64/impl_staticconst.go
index c9377a14..376ae625 100644
--- a/internal/asm/amd64/impl_staticconst.go
+++ b/internal/asm/amd64/impl_staticconst.go
@@ -75,11 +75,6 @@ func (a *AssemblerImpl) maybeFlushConstants(isEndOfFunction bool) {
 }
 
 func (a *AssemblerImpl) encodeStaticConstToRegister(n *NodeImpl) (err error) {
-	if n.Instruction != MOVDQU {
-		err = errorEncodingUnsupported(n)
-		return
-	}
-
 	a.pool.addConst(n.staticConst)
 
 	dstReg3Bits, rexPrefix, err := register3bits(n.DstReg, registerSpecifierPositionModRMFieldReg)
@@ -104,12 +99,31 @@ func (a *AssemblerImpl) encodeStaticConstToRegister(n *NodeImpl) (err error) {
 	modRM := 0b00_000_101 | // Indicate "MOVDQU [RIP + 32bit displacement], DstReg" encoding.
 		(dstReg3Bits << 3) // Place the DstReg on ModRM:reg.
 
-	// https://www.felixcloutier.com/x86/movdqu:vmovdqu8:vmovdqu16:vmovdqu32:vmovdqu64
-	inst = append(inst, 0xf3) // mandatory prefix
+	var mandatoryPrefix byte
+	var opcodes []byte
+	switch n.Instruction {
+	case MOVDQU:
+		// https://www.felixcloutier.com/x86/movdqu:vmovdqu8:vmovdqu16:vmovdqu32:vmovdqu64
+		mandatoryPrefix = 0xf3
+		opcodes = []byte{0x0f, 0x6f}
+	case LEAQ:
+		// https://www.felixcloutier.com/x86/lea
+		rexPrefix |= RexPrefixW
+		opcodes = []byte{0x8d}
+	default:
+		err = errorEncodingUnsupported(n)
+		return
+	}
+
+	if mandatoryPrefix != 0 {
+		inst = append(inst, mandatoryPrefix)
+	}
 	if rexPrefix != RexPrefixNone {
 		inst = append(inst, rexPrefix)
 	}
-	inst = append(inst, 0x0f, 0x6f, modRM,
+
+	inst = append(inst, opcodes...)
+	inst = append(inst, modRM,
 		0x0, 0x0, 0x0, 0x0, // Preserve 4 bytes for displacement.
 	)
 
diff --git a/internal/engine/compiler/compiler.go b/internal/engine/compiler/compiler.go
index c704f686..2d58a0da 100644
--- a/internal/engine/compiler/compiler.go
+++ b/internal/engine/compiler/compiler.go
@@ -466,4 +466,12 @@ type compiler interface {
 	// compileV128AndNot adds instructions which are equivalent to wasm.OpcodeVecV128AndNotName instruction.
 	// See wasm.OpcodeVecV128AndNotName.
 	compileV128AndNot(*wazeroir.OperationV128AndNot) error
+	// compileV128Shr adds instructions which are equivalent to wasm.OpcodeVecXXXShrYYYY instructions.
+	// See wasm.OpcodeVecI8x16ShrSName wasm.OpcodeVecI8x16ShrUName wasm.OpcodeVecI16x8ShrSName
+	// wasm.OpcodeVecI16x8ShrUName wasm.OpcodeVecI32x4ShrSName wasm.OpcodeVecI32x4ShrUName.
+	// wasm.OpcodeVecI64x2ShrSName wasm.OpcodeVecI64x2ShrUName.
+	compileV128Shr(shr *wazeroir.OperationV128Shr) error
+	// compileV128Shl adds instructions which are equivalent to wasm.OpcodeVecXXXShl instructions.
+	// See wasm.OpcodeVecI8x16ShlName wasm.OpcodeVecI16x8ShlName wasm.OpcodeVecI32x4ShlName wasm.OpcodeVecI64x2ShlName
+	compileV128Shl(shr *wazeroir.OperationV128Shl) error
 }
diff --git a/internal/engine/compiler/engine.go b/internal/engine/compiler/engine.go
index 7ece4faf..aef6037a 100644
--- a/internal/engine/compiler/engine.go
+++ b/internal/engine/compiler/engine.go
@@ -1088,6 +1088,10 @@ func compileWasmFunction(_ wasm.Features, ir *wazeroir.CompilationResult) (*code
 			err = compiler.compileV128Bitselect(o)
 		case *wazeroir.OperationV128AndNot:
 			err = compiler.compileV128AndNot(o)
+		case *wazeroir.OperationV128Shr:
+			err = compiler.compileV128Shr(o)
+		case *wazeroir.OperationV128Shl:
+			err = compiler.compileV128Shl(o)
 		default:
 			err = errors.New("unsupported")
 		}
diff --git a/internal/engine/compiler/impl_amd64.go b/internal/engine/compiler/impl_amd64.go
index df5aadc3..c0c97968 100644
--- a/internal/engine/compiler/impl_amd64.go
+++ b/internal/engine/compiler/impl_amd64.go
@@ -1757,8 +1757,8 @@ func (c *amd64Compiler) compileAbs(o *wazeroir.OperationAbs) (err error) {
 
 	// First shift left by one to clear the sign bit, and then shift right by one.
 	if o.Type == wazeroir.Float32 {
-		c.assembler.CompileConstToRegister(amd64.PSLLL, 1, target.register)
-		c.assembler.CompileConstToRegister(amd64.PSRLL, 1, target.register)
+		c.assembler.CompileConstToRegister(amd64.PSLLD, 1, target.register)
+		c.assembler.CompileConstToRegister(amd64.PSRLD, 1, target.register)
 	} else {
 		c.assembler.CompileConstToRegister(amd64.PSLLQ, 1, target.register)
 		c.assembler.CompileConstToRegister(amd64.PSRLQ, 1, target.register)
diff --git a/internal/engine/compiler/impl_vec_amd64.go b/internal/engine/compiler/impl_vec_amd64.go
index 7e5ccb78..5b6f0bb5 100644
--- a/internal/engine/compiler/impl_vec_amd64.go
+++ b/internal/engine/compiler/impl_vec_amd64.go
@@ -620,7 +620,7 @@ func (c *amd64Compiler) compileV128BitMask(o *wazeroir.OperationV128BitMask) err
 		// 		byte_sat(R2(w5)), byte_sat(R2(w6)), byte_sat(R2(w7)), byte_sat(R2(w8)),
 		//  ]
 		//  where R1 is the destination register, and
-		// 	byte_sat(w) = w if w fits as signed 8-bit,
+		// 	byte_sat(w) = int8(w) if w fits as signed 8-bit,
 		//                0x80 if w is less than 0x80
 		//                0x7F if w is greater than 0x7f
 		//
@@ -765,3 +765,301 @@ func (c *amd64Compiler) compileV128AndNot(*wazeroir.OperationV128AndNot) error {
 	c.pushVectorRuntimeValueLocationOnRegister(x2.register)
 	return nil
 }
+
+// compileV128Shr implements compiler.compileV128Shr for amd64.
+func (c *amd64Compiler) compileV128Shr(o *wazeroir.OperationV128Shr) error {
+	// https://stackoverflow.com/questions/35002937/sse-simd-shift-with-one-byte-element-size-granularity
+	if o.Shape == wazeroir.ShapeI8x16 {
+		return c.compileV128ShrI8x16Impl(o.Signed)
+	} else if o.Shape == wazeroir.ShapeI64x2 && o.Signed {
+		return c.compileV128ShrI64x2SignedImpl()
+	} else {
+		return c.compileV128ShrImpl(o)
+	}
+}
+
+// compileV128ShrImpl implements shift right instructions except for i8x16 (logical/arithmetic) and i64x2 (arithmetic).
+func (c *amd64Compiler) compileV128ShrImpl(o *wazeroir.OperationV128Shr) error {
+	s := c.locationStack.pop()
+	if err := c.compileEnsureOnGeneralPurposeRegister(s); err != nil {
+		return err
+	}
+
+	x1 := c.locationStack.popV128()
+	if err := c.compileEnsureOnGeneralPurposeRegister(x1); err != nil {
+		return err
+	}
+
+	vecTmp, err := c.allocateRegister(registerTypeVector)
+	if err != nil {
+		return err
+	}
+
+	var moduleConst int64
+	var shift asm.Instruction
+	switch o.Shape {
+	case wazeroir.ShapeI16x8:
+		moduleConst = 0xf // modulo 16.
+		if o.Signed {
+			shift = amd64.PSRAW
+		} else {
+			shift = amd64.PSRLW
+		}
+	case wazeroir.ShapeI32x4:
+		moduleConst = 0x1f // modulo 32.
+		if o.Signed {
+			shift = amd64.PSRAD
+		} else {
+			shift = amd64.PSRLD
+		}
+	case wazeroir.ShapeI64x2:
+		moduleConst = 0x3f // modulo 64.
+		shift = amd64.PSRLQ
+	}
+
+	gpShiftAmount := s.register
+	c.assembler.CompileConstToRegister(amd64.ANDQ, moduleConst, gpShiftAmount)
+	c.assembler.CompileRegisterToRegister(amd64.MOVL, gpShiftAmount, vecTmp)
+	c.assembler.CompileRegisterToRegister(shift, vecTmp, x1.register)
+
+	c.locationStack.markRegisterUnused(gpShiftAmount)
+	c.pushVectorRuntimeValueLocationOnRegister(x1.register)
+	return nil
+}
+
+// compileV128ShrI64x2SignedImpl implements compiler.compileV128Shr for i64x4 signed (arithmetic) shift.
+// PSRAQ instruction requires AVX, so we emulate it without AVX instructions. https://www.felixcloutier.com/x86/psraw:psrad:psraq
+func (c *amd64Compiler) compileV128ShrI64x2SignedImpl() error {
+	const shiftCountRegister = amd64.RegCX
+	// If another value lives on the CX register, we release it to the stack.
+	c.onValueReleaseRegisterToStack(shiftCountRegister)
+
+	s := c.locationStack.pop()
+	if s.onStack() {
+		s.setRegister(shiftCountRegister)
+		c.compileLoadValueOnStackToRegister(s)
+	} else if s.onConditionalRegister() {
+		c.compileMoveConditionalToGeneralPurposeRegister(s, shiftCountRegister)
+	} else { // already on register.
+		old := s.register
+		c.assembler.CompileRegisterToRegister(amd64.MOVL, old, shiftCountRegister)
+		s.setRegister(shiftCountRegister)
+		c.locationStack.markRegisterUnused(old)
+	}
+	c.locationStack.markRegisterUnused(shiftCountRegister)
+	tmp, err := c.allocateRegister(registerTypeGeneralPurpose)
+	if err != nil {
+		return err
+	}
+
+	x1 := c.locationStack.popV128()
+	if err := c.compileEnsureOnGeneralPurposeRegister(x1); err != nil {
+		return err
+	}
+
+	// Extract each lane into tmp, execute SHR on tmp, and write it back to the lane.
+	c.assembler.CompileRegisterToRegisterWithArg(amd64.PEXTRQ, x1.register, tmp, 0)
+	c.assembler.CompileRegisterToRegister(amd64.SARQ, shiftCountRegister, tmp)
+	c.assembler.CompileRegisterToRegisterWithArg(amd64.PINSRQ, tmp, x1.register, 0)
+	c.assembler.CompileRegisterToRegisterWithArg(amd64.PEXTRQ, x1.register, tmp, 1)
+	c.assembler.CompileRegisterToRegister(amd64.SARQ, shiftCountRegister, tmp)
+	c.assembler.CompileRegisterToRegisterWithArg(amd64.PINSRQ, tmp, x1.register, 1)
+
+	c.locationStack.markRegisterUnused(shiftCountRegister)
+	c.pushVectorRuntimeValueLocationOnRegister(x1.register)
+	return nil
+}
+
+// i8x16LogicalSHRMaskTable is necessary for emulating non-existent packed bytes logical right shifts on amd64.
+// The mask is applied after performing packed word shifts on the value to clear out the unnecessary bits.
+var i8x16LogicalSHRMaskTable = [8 * 16]byte{ // (the number of possible shift amount 0, 1, ..., 7.) * 16 bytes.
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // for 0 shift
+	0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, // for 1 shift
+	0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, // for 2 shift
+	0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, // for 3 shift
+	0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // for 4 shift
+	0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, // for 5 shift
+	0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, // for 6 shift
+	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // for 7 shift
+}
+
+// compileV128ShrI64x2SignedImpl implements compiler.compileV128Shr for i8x16 signed logical/arithmetic shifts.
+// amd64 doesn't have packed byte shifts, so we need this special casing.
+// See https://stackoverflow.com/questions/35002937/sse-simd-shift-with-one-byte-element-size-granularity
+func (c *amd64Compiler) compileV128ShrI8x16Impl(signed bool) error {
+	s := c.locationStack.pop()
+	if err := c.compileEnsureOnGeneralPurposeRegister(s); err != nil {
+		return err
+	}
+
+	v := c.locationStack.popV128()
+	if err := c.compileEnsureOnGeneralPurposeRegister(v); err != nil {
+		return err
+	}
+
+	vecTmp, err := c.allocateRegister(registerTypeVector)
+	if err != nil {
+		return err
+	}
+
+	gpShiftAmount := s.register
+	c.assembler.CompileConstToRegister(amd64.ANDQ, 0x7, gpShiftAmount) // mod 8.
+
+	if signed {
+		c.locationStack.markRegisterUsed(vecTmp)
+		vecTmp2, err := c.allocateRegister(registerTypeVector)
+		if err != nil {
+			return err
+		}
+
+		vreg := v.register
+
+		// Copy the value from v.register to vecTmp.
+		c.assembler.CompileRegisterToRegister(amd64.MOVDQA, vreg, vecTmp)
+
+		// Assuming that we have
+		//  vreg   = [b1, ..., b16]
+		//  vecTmp = [b1, ..., b16]
+		// at this point, then we use PUNPCKLBW and PUNPCKHBW to produce:
+		//  vreg   = [b1, b1, b2, b2, ..., b8, b8]
+		//  vecTmp = [b9, b9, b10, b10, ..., b16, b16]
+		c.assembler.CompileRegisterToRegister(amd64.PUNPCKLBW, vreg, vreg)
+		c.assembler.CompileRegisterToRegister(amd64.PUNPCKHBW, vecTmp, vecTmp)
+
+		// Adding 8 to the shift amount, and then move the amount to vecTmp2.
+		c.assembler.CompileConstToRegister(amd64.ADDQ, 0x8, gpShiftAmount)
+		c.assembler.CompileRegisterToRegister(amd64.MOVL, gpShiftAmount, vecTmp2)
+
+		// Perform the word packed arithmetic right shifts on vreg and vecTmp.
+		// This changes these two registers as:
+		//  vreg   = [xxx, b1 >> s, xxx, b2 >> s, ..., xxx, b8 >> s]
+		//  vecTmp = [xxx, b9 >> s, xxx, b10 >> s, ..., xxx, b16 >> s]
+		// where xxx is 1 or 0 depending on each byte's sign, and ">>" is the arithmetic shift on a byte.
+		c.assembler.CompileRegisterToRegister(amd64.PSRAW, vecTmp2, vreg)
+		c.assembler.CompileRegisterToRegister(amd64.PSRAW, vecTmp2, vecTmp)
+
+		// Finally, we can get the result by packing these two word vectors.
+		c.assembler.CompileRegisterToRegister(amd64.PACKSSWB, vecTmp, vreg)
+
+		c.locationStack.markRegisterUnused(gpShiftAmount, vecTmp)
+		c.pushVectorRuntimeValueLocationOnRegister(vreg)
+	} else {
+		c.assembler.CompileRegisterToRegister(amd64.MOVL, gpShiftAmount, vecTmp)
+		// amd64 doesn't have packed byte shifts, so we packed word shift here, and then mark-out
+		// the unnecessary bits below.
+		c.assembler.CompileRegisterToRegister(amd64.PSRLW, vecTmp, v.register)
+
+		gpTmp, err := c.allocateRegister(registerTypeGeneralPurpose)
+		if err != nil {
+			return err
+		}
+
+		// Read the initial address of the mask table into gpTmp register.
+		err = c.assembler.CompileLoadStaticConstToRegister(amd64.LEAQ, i8x16LogicalSHRMaskTable[:], gpTmp)
+		if err != nil {
+			return err
+		}
+
+		// We have to get the mask according to the shift amount, so we first have to do
+		// gpShiftAmount << 4 = gpShiftAmount*16 to get the initial offset of the mask (16 is the size of each mask in bytes).
+		c.assembler.CompileConstToRegister(amd64.SHLQ, 4, gpShiftAmount)
+
+		// Now ready to read the content of the mask into the vecTmp.
+		c.assembler.CompileMemoryWithIndexToRegister(amd64.MOVDQU,
+			gpTmp, 0, gpShiftAmount, 1,
+			vecTmp,
+		)
+
+		// Finally, clear out the unnecessary
+		c.assembler.CompileRegisterToRegister(amd64.PAND, vecTmp, v.register)
+
+		c.locationStack.markRegisterUnused(gpShiftAmount)
+		c.pushVectorRuntimeValueLocationOnRegister(v.register)
+	}
+	return nil
+}
+
+// i8x16SHLMaskTable is necessary for emulating non-existent packed bytes left shifts on amd64.
+// The mask is applied after performing packed word shifts on the value to clear out the unnecessary bits.
+var i8x16SHLMaskTable = [8 * 16]byte{ // (the number of possible shift amount 0, 1, ..., 7.) * 16 bytes.
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // for 0 shift
+	0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, // for 1 shift
+	0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, // for 2 shift
+	0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, // for 3 shift
+	0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // for 4 shift
+	0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, // for 5 shift
+	0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, // for 6 shift
+	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // for 7 shift
+}
+
+// compileV128Shl implements compiler.compileV128Shl for amd64.
+func (c *amd64Compiler) compileV128Shl(o *wazeroir.OperationV128Shl) error {
+	s := c.locationStack.pop()
+	if err := c.compileEnsureOnGeneralPurposeRegister(s); err != nil {
+		return err
+	}
+
+	x1 := c.locationStack.popV128()
+	if err := c.compileEnsureOnGeneralPurposeRegister(x1); err != nil {
+		return err
+	}
+
+	vecTmp, err := c.allocateRegister(registerTypeVector)
+	if err != nil {
+		return err
+	}
+
+	var modulo int64
+	var shift asm.Instruction
+	switch o.Shape {
+	case wazeroir.ShapeI8x16:
+		modulo = 0x7 // modulo 8.
+		// x86 doesn't have packed bytes shift, so we use PSLLW and mask-out the redundant bits.
+		// See https://stackoverflow.com/questions/35002937/sse-simd-shift-with-one-byte-element-size-granularity
+		shift = amd64.PSLLW
+	case wazeroir.ShapeI16x8:
+		modulo = 0xf // modulo 16.
+		shift = amd64.PSLLW
+	case wazeroir.ShapeI32x4:
+		modulo = 0x1f // modulo 32.
+		shift = amd64.PSLLD
+	case wazeroir.ShapeI64x2:
+		modulo = 0x3f // modulo 64.
+		shift = amd64.PSLLQ
+	}
+
+	gpShiftAmount := s.register
+	c.assembler.CompileConstToRegister(amd64.ANDQ, modulo, gpShiftAmount)
+	c.assembler.CompileRegisterToRegister(amd64.MOVL, gpShiftAmount, vecTmp)
+	c.assembler.CompileRegisterToRegister(shift, vecTmp, x1.register)
+
+	if o.Shape == wazeroir.ShapeI8x16 {
+		gpTmp, err := c.allocateRegister(registerTypeGeneralPurpose)
+		if err != nil {
+			return err
+		}
+
+		// Read the initial address of the mask table into gpTmp register.
+		err = c.assembler.CompileLoadStaticConstToRegister(amd64.LEAQ, i8x16SHLMaskTable[:], gpTmp)
+		if err != nil {
+			return err
+		}
+
+		// We have to get the mask according to the shift amount, so we first have to do
+		// gpShiftAmount << 4 = gpShiftAmount*16 to get the initial offset of the mask (16 is the size of each mask in bytes).
+		c.assembler.CompileConstToRegister(amd64.SHLQ, 4, gpShiftAmount)
+
+		// Now ready to read the content of the mask into the vecTmp.
+		c.assembler.CompileMemoryWithIndexToRegister(amd64.MOVDQU,
+			gpTmp, 0, gpShiftAmount, 1,
+			vecTmp,
+		)
+
+		// Finally, clear out the unnecessary
+		c.assembler.CompileRegisterToRegister(amd64.PAND, vecTmp, x1.register)
+	}
+
+	c.locationStack.markRegisterUnused(gpShiftAmount)
+	c.pushVectorRuntimeValueLocationOnRegister(x1.register)
+	return nil
+}
diff --git a/internal/engine/compiler/impl_vec_arm64.go b/internal/engine/compiler/impl_vec_arm64.go
index 7345ae87..2825ae85 100644
--- a/internal/engine/compiler/impl_vec_arm64.go
+++ b/internal/engine/compiler/impl_vec_arm64.go
@@ -513,50 +513,67 @@ func (c *arm64Compiler) compileV128Splat(o *wazeroir.OperationV128Splat) (err er
 	return
 }
 
+func (c *arm64Compiler) onValueReleaseRegisterToStack(reg asm.Register) {
+	for i := uint64(0); i < c.locationStack.sp; i++ {
+		prevValue := c.locationStack.stack[i]
+		if prevValue.register == reg {
+			c.compileReleaseRegisterToStack(prevValue)
+			break
+		}
+	}
+}
+
 // compileV128Shuffle implements compiler.compileV128Shuffle for arm64.
 func (c *arm64Compiler) compileV128Shuffle(o *wazeroir.OperationV128Shuffle) (err error) {
-	w := c.locationStack.popV128()
-	v := c.locationStack.popV128()
-	if err := c.compileEnsureOnGeneralPurposeRegister(v); err != nil {
-		return err
-	}
+	// Shuffle needs two operands (v, w) must be next to each other.
+	// For simplicity, we use V29 for v and V30 for w values respectively.
+	const vReg, wReg = arm64.RegV29, arm64.RegV30
 
-	r1 := v.register
-	var r2 asm.Register
-	if r1 < arm64.RegV30 {
-		r2 = v.register + 1
-	} else {
-		r2 = v.register - 1
+	// Ensures that w value is placed on wReg.
+	w := c.locationStack.popV128()
+	if w.register != wReg {
+		// If wReg is already in use, save the value onto the stack.
+		c.onValueReleaseRegisterToStack(wReg)
+
+		if w.onRegister() {
+			c.assembler.CompileVectorRegisterToVectorRegister(arm64.VMOV, w.register, wReg,
+				arm64.VectorArrangement16B, arm64.VectorIndexNone, arm64.VectorIndexNone)
+			// We no longer use the old register.
+			c.markRegisterUnused(w.register)
+		} else { // on stack
+			w.setRegister(wReg)
+			c.compileLoadValueOnStackToRegister(w)
+		}
 	}
 
-	if w.onRegister() {
-		c.assembler.CompileVectorRegisterToVectorRegister(arm64.VMOV, w.register, r2,
-			arm64.VectorArrangement16B, arm64.VectorIndexNone, arm64.VectorIndexNone)
-		c.markRegisterUnused(w.register)
-	} else { // on stack
-		w.setRegister(r2)
-		c.compileLoadValueOnStackToRegister(w)
+	// Ensures that v value is placed on wReg.
+	v := c.locationStack.popV128()
+	if v.register != vReg {
+		// If vReg is already in use, save the value onto the stack.
+		c.onValueReleaseRegisterToStack(vReg)
+
+		if v.onRegister() {
+			c.assembler.CompileVectorRegisterToVectorRegister(arm64.VMOV, v.register, vReg,
+				arm64.VectorArrangement16B, arm64.VectorIndexNone, arm64.VectorIndexNone)
+			// We no longer use the old register.
+			c.markRegisterUnused(v.register)
+		} else { // on stack
+			v.setRegister(vReg)
+			c.compileLoadValueOnStackToRegister(v)
+		}
 	}
 
-	c.locationStack.markRegisterUsed(r1, r2)
-
+	c.locationStack.markRegisterUsed(vReg, wReg)
 	result, err := c.allocateRegister(registerTypeVector)
 	if err != nil {
 		return err
 	}
 
 	c.assembler.CompileLoadStaticConstToVectorRegister(arm64.VMOV, o.Lanes[:], result, arm64.VectorArrangementQ)
-
-	var origin asm.Register
-	if r1 < r2 {
-		origin = r1
-	} else {
-		origin = r2
-	}
-	c.assembler.CompileVectorRegisterToVectorRegister(arm64.TBL2, origin, result, arm64.VectorArrangement16B,
+	c.assembler.CompileVectorRegisterToVectorRegister(arm64.TBL2, vReg, result, arm64.VectorArrangement16B,
 		arm64.VectorIndexNone, arm64.VectorIndexNone)
 
-	c.locationStack.markRegisterUnused(r1, r2)
+	c.locationStack.markRegisterUnused(vReg, wReg)
 	c.pushVectorRuntimeValueLocationOnRegister(result)
 	return
 }
@@ -670,3 +687,13 @@ func (c *arm64Compiler) compileV128Bitselect(o *wazeroir.OperationV128Bitselect)
 func (c *arm64Compiler) compileV128AndNot(o *wazeroir.OperationV128AndNot) error {
 	return fmt.Errorf("TODO: %s is not implemented yet on arm64 compiler", o.Kind())
 }
+
+// compileV128Shr implements compiler.compileV128Shr for arm64.
+func (c *arm64Compiler) compileV128Shr(o *wazeroir.OperationV128Shr) error {
+	return fmt.Errorf("TODO: %s is not implemented yet on arm64 compiler", o.Kind())
+}
+
+// compileV128Shl implements compiler.compileV128Shl for arm64.
+func (c *arm64Compiler) compileV128Shl(o *wazeroir.OperationV128Shl) error {
+	return fmt.Errorf("TODO: %s is not implemented yet on arm64 compiler", o.Kind())
+}
diff --git a/internal/engine/interpreter/interpreter.go b/internal/engine/interpreter/interpreter.go
index 26699288..047f560a 100644
--- a/internal/engine/interpreter/interpreter.go
+++ b/internal/engine/interpreter/interpreter.go
@@ -638,6 +638,11 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
 		case *wazeroir.OperationV128Xor:
 		case *wazeroir.OperationV128Bitselect:
 		case *wazeroir.OperationV128AndNot:
+		case *wazeroir.OperationV128Shr:
+			op.b1 = o.Shape
+			op.b3 = o.Signed
+		case *wazeroir.OperationV128Shl:
+			op.b1 = o.Shape
 		default:
 			panic(fmt.Errorf("BUG: unimplemented operation %s", op.kind.String()))
 		}
@@ -2422,6 +2427,134 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, callCtx *wasm.CallCont
 			ce.pushValue(x1Lo & (^x2Lo))
 			ce.pushValue(x1Hi & (^x2Hi))
 			frame.pc++
+		case wazeroir.OperationKindV128Shl:
+			s := ce.popValue()
+			hi, lo := ce.popValue(), ce.popValue()
+			switch op.b1 {
+			case wazeroir.ShapeI8x16:
+				s = s % 8
+				lo = uint64(uint8(lo<<s)) |
+					uint64(uint8((lo>>8)<<s))<<8 |
+					uint64(uint8((lo>>16)<<s))<<16 |
+					uint64(uint8((lo>>24)<<s))<<24 |
+					uint64(uint8((lo>>32)<<s))<<32 |
+					uint64(uint8((lo>>40)<<s))<<40 |
+					uint64(uint8((lo>>48)<<s))<<48 |
+					uint64(uint8((lo>>56)<<s))<<56
+				hi = uint64(uint8(hi<<s)) |
+					uint64(uint8((hi>>8)<<s))<<8 |
+					uint64(uint8((hi>>16)<<s))<<16 |
+					uint64(uint8((hi>>24)<<s))<<24 |
+					uint64(uint8((hi>>32)<<s))<<32 |
+					uint64(uint8((hi>>40)<<s))<<40 |
+					uint64(uint8((hi>>48)<<s))<<48 |
+					uint64(uint8((hi>>56)<<s))<<56
+			case wazeroir.ShapeI16x8:
+				s = s % 16
+				lo = uint64(uint16(lo<<s)) |
+					uint64(uint16((lo>>16)<<s))<<16 |
+					uint64(uint16((lo>>32)<<s))<<32 |
+					uint64(uint16((lo>>48)<<s))<<48
+				hi = uint64(uint16(hi<<s)) |
+					uint64(uint16((hi>>16)<<s))<<16 |
+					uint64(uint16((hi>>32)<<s))<<32 |
+					uint64(uint16((hi>>48)<<s))<<48
+			case wazeroir.ShapeI32x4:
+				s = s % 32
+				lo = uint64(uint32(lo<<s)) | uint64(uint32((lo>>32)<<s))<<32
+				hi = uint64(uint32(hi<<s)) | uint64(uint32((hi>>32)<<s))<<32
+			case wazeroir.ShapeI64x2:
+				s = s % 64
+				lo = lo << s
+				hi = hi << s
+			}
+			ce.pushValue(lo)
+			ce.pushValue(hi)
+			frame.pc++
+		case wazeroir.OperationKindV128Shr:
+			s := ce.popValue()
+			hi, lo := ce.popValue(), ce.popValue()
+			switch op.b1 {
+			case wazeroir.ShapeI8x16:
+				s = s % 8
+				if op.b3 { // signed
+					lo = uint64(uint8(int8(lo)>>s)) |
+						uint64(uint8(int8(lo>>8)>>s))<<8 |
+						uint64(uint8(int8(lo>>16)>>s))<<16 |
+						uint64(uint8(int8(lo>>24)>>s))<<24 |
+						uint64(uint8(int8(lo>>32)>>s))<<32 |
+						uint64(uint8(int8(lo>>40)>>s))<<40 |
+						uint64(uint8(int8(lo>>48)>>s))<<48 |
+						uint64(uint8(int8(lo>>56)>>s))<<56
+					hi = uint64(uint8(int8(hi)>>s)) |
+						uint64(uint8(int8(hi>>8)>>s))<<8 |
+						uint64(uint8(int8(hi>>16)>>s))<<16 |
+						uint64(uint8(int8(hi>>24)>>s))<<24 |
+						uint64(uint8(int8(hi>>32)>>s))<<32 |
+						uint64(uint8(int8(hi>>40)>>s))<<40 |
+						uint64(uint8(int8(hi>>48)>>s))<<48 |
+						uint64(uint8(int8(hi>>56)>>s))<<56
+				} else {
+					lo = uint64(uint8(lo)>>s) |
+						uint64(uint8(lo>>8)>>s)<<8 |
+						uint64(uint8(lo>>16)>>s)<<16 |
+						uint64(uint8(lo>>24)>>s)<<24 |
+						uint64(uint8(lo>>32)>>s)<<32 |
+						uint64(uint8(lo>>40)>>s)<<40 |
+						uint64(uint8(lo>>48)>>s)<<48 |
+						uint64(uint8(lo>>56)>>s)<<56
+					hi = uint64(uint8(hi)>>s) |
+						uint64(uint8(hi>>8)>>s)<<8 |
+						uint64(uint8(hi>>16)>>s)<<16 |
+						uint64(uint8(hi>>24)>>s)<<24 |
+						uint64(uint8(hi>>32)>>s)<<32 |
+						uint64(uint8(hi>>40)>>s)<<40 |
+						uint64(uint8(hi>>48)>>s)<<48 |
+						uint64(uint8(hi>>56)>>s)<<56
+				}
+			case wazeroir.ShapeI16x8:
+				s = s % 16
+				if op.b3 { // signed
+					lo = uint64(uint16(int16(lo)>>s)) |
+						uint64(uint16(int16(lo>>16)>>s))<<16 |
+						uint64(uint16(int16(lo>>32)>>s))<<32 |
+						uint64(uint16(int16(lo>>48)>>s))<<48
+					hi = uint64(uint16(int16(hi)>>s)) |
+						uint64(uint16(int16(hi>>16)>>s))<<16 |
+						uint64(uint16(int16(hi>>32)>>s))<<32 |
+						uint64(uint16(int16(hi>>48)>>s))<<48
+				} else {
+					lo = uint64(uint16(lo)>>s) |
+						uint64(uint16(lo>>16)>>s)<<16 |
+						uint64(uint16(lo>>32)>>s)<<32 |
+						uint64(uint16(lo>>48)>>s)<<48
+					hi = uint64(uint16(hi)>>s) |
+						uint64(uint16(hi>>16)>>s)<<16 |
+						uint64(uint16(hi>>32)>>s)<<32 |
+						uint64(uint16(hi>>48)>>s)<<48
+				}
+			case wazeroir.ShapeI32x4:
+				s = s % 32
+				if op.b3 {
+					lo = uint64(uint32(int32(lo)>>s)) | uint64(uint32(int32(lo>>32)>>s))<<32
+					hi = uint64(uint32(int32(hi)>>s)) | uint64(uint32(int32(hi>>32)>>s))<<32
+				} else {
+					lo = uint64(uint32(lo)>>s) | uint64(uint32(lo>>32)>>s)<<32
+					hi = uint64(uint32(hi)>>s) | uint64(uint32(hi>>32)>>s)<<32
+				}
+			case wazeroir.ShapeI64x2:
+				s = s % 64
+				if op.b3 { // signed
+					lo = uint64(int64(lo) >> s)
+					hi = uint64(int64(hi) >> s)
+				} else {
+					lo = lo >> s
+					hi = hi >> s
+				}
+			}
+			ce.pushValue(lo)
+			ce.pushValue(hi)
+			frame.pc++
 		}
 	}
 	ce.popFrame()
diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go
index 258e165a..4b21f6f0 100644
--- a/internal/wasm/func_validation.go
+++ b/internal/wasm/func_validation.go
@@ -1289,6 +1289,17 @@ func (m *Module) validateFunctionWithMaxStackValues(
 				}
 				valueTypeStack.push(ValueTypeV128)
 				pc += 15
+			case OpcodeVecI8x16Shl, OpcodeVecI8x16ShrS, OpcodeVecI8x16ShrU,
+				OpcodeVecI16x8Shl, OpcodeVecI16x8ShrS, OpcodeVecI16x8ShrU,
+				OpcodeVecI32x4Shl, OpcodeVecI32x4ShrS, OpcodeVecI32x4ShrU,
+				OpcodeVecI64x2Shl, OpcodeVecI64x2ShrS, OpcodeVecI64x2ShrU:
+				if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil {
+					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
+				}
+				if err := valueTypeStack.popAndVerifyType(ValueTypeV128); err != nil {
+					return fmt.Errorf("cannot pop the operand for %s: %v", vectorInstructionName[vecOpcode], err)
+				}
+				valueTypeStack.push(ValueTypeV128)
 			default:
 				return fmt.Errorf("TODO: SIMD instruction %s will be implemented in #506", vectorInstructionName[vecOpcode])
 			}
diff --git a/internal/wazeroir/compiler.go b/internal/wazeroir/compiler.go
index cc4c416b..2e18e7db 100644
--- a/internal/wazeroir/compiler.go
+++ b/internal/wazeroir/compiler.go
@@ -2132,6 +2132,54 @@ operatorSwitch:
 			c.emit(
 				&OperationV128AndNot{},
 			)
+		case wasm.OpcodeVecI8x16Shl:
+			c.emit(
+				&OperationV128Shl{Shape: ShapeI8x16},
+			)
+		case wasm.OpcodeVecI8x16ShrS:
+			c.emit(
+				&OperationV128Shr{Shape: ShapeI8x16, Signed: true},
+			)
+		case wasm.OpcodeVecI8x16ShrU:
+			c.emit(
+				&OperationV128Shr{Shape: ShapeI8x16, Signed: false},
+			)
+		case wasm.OpcodeVecI16x8Shl:
+			c.emit(
+				&OperationV128Shl{Shape: ShapeI16x8},
+			)
+		case wasm.OpcodeVecI16x8ShrS:
+			c.emit(
+				&OperationV128Shr{Shape: ShapeI16x8, Signed: true},
+			)
+		case wasm.OpcodeVecI16x8ShrU:
+			c.emit(
+				&OperationV128Shr{Shape: ShapeI16x8, Signed: false},
+			)
+		case wasm.OpcodeVecI32x4Shl:
+			c.emit(
+				&OperationV128Shl{Shape: ShapeI32x4},
+			)
+		case wasm.OpcodeVecI32x4ShrS:
+			c.emit(
+				&OperationV128Shr{Shape: ShapeI32x4, Signed: true},
+			)
+		case wasm.OpcodeVecI32x4ShrU:
+			c.emit(
+				&OperationV128Shr{Shape: ShapeI32x4, Signed: false},
+			)
+		case wasm.OpcodeVecI64x2Shl:
+			c.emit(
+				&OperationV128Shl{Shape: ShapeI64x2},
+			)
+		case wasm.OpcodeVecI64x2ShrS:
+			c.emit(
+				&OperationV128Shr{Shape: ShapeI64x2, Signed: true},
+			)
+		case wasm.OpcodeVecI64x2ShrU:
+			c.emit(
+				&OperationV128Shr{Shape: ShapeI64x2, Signed: false},
+			)
 		default:
 			return fmt.Errorf("unsupported vector instruction in wazeroir: %s", wasm.VectorInstructionName(vecOp))
 		}
diff --git a/internal/wazeroir/operations.go b/internal/wazeroir/operations.go
index 6ed9bc5a..741fab77 100644
--- a/internal/wazeroir/operations.go
+++ b/internal/wazeroir/operations.go
@@ -332,6 +332,10 @@ func (o OperationKind) String() (ret string) {
 		return "V128AndNot"
 	case OperationKindV128BitMask:
 		return "V128BitMask"
+	case OperationKindV128Shl:
+		return "V128Shl"
+	case OperationKindV128Shr:
+		return "V128Shr"
 	default:
 		panic("BUG")
 	}
@@ -450,6 +454,8 @@ const (
 	OperationKindV128Xor
 	OperationKindV128Bitselect
 	OperationKindV128AndNot
+	OperationKindV128Shl
+	OperationKindV128Shr
 )
 
 type Label struct {
@@ -1497,3 +1503,24 @@ type OperationV128AndNot struct{}
 func (o *OperationV128AndNot) Kind() OperationKind {
 	return OperationKindV128AndNot
 }
+
+// OperationV128Shl implements Operation.
+type OperationV128Shl struct {
+	Shape Shape
+}
+
+// Kind implements Operation.Kind.
+func (o *OperationV128Shl) Kind() OperationKind {
+	return OperationKindV128Shl
+}
+
+// OperationV128Shr implements Operation.
+type OperationV128Shr struct {
+	Shape  Shape
+	Signed bool
+}
+
+// Kind implements Operation.Kind.
+func (o *OperationV128Shr) Kind() OperationKind {
+	return OperationKindV128Shr
+}
diff --git a/internal/wazeroir/signature.go b/internal/wazeroir/signature.go
index 389664c8..924baac3 100644
--- a/internal/wazeroir/signature.go
+++ b/internal/wazeroir/signature.go
@@ -504,7 +504,11 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature
 			return signature_V128_F32, nil
 		case wasm.OpcodeVecF64x2ExtractLane:
 			return signature_V128_F64, nil
-		case wasm.OpcodeVecI8x16ReplaceLane, wasm.OpcodeVecI16x8ReplaceLane, wasm.OpcodeVecI32x4ReplaceLane:
+		case wasm.OpcodeVecI8x16ReplaceLane, wasm.OpcodeVecI16x8ReplaceLane, wasm.OpcodeVecI32x4ReplaceLane,
+			wasm.OpcodeVecI8x16Shl, wasm.OpcodeVecI8x16ShrS, wasm.OpcodeVecI8x16ShrU,
+			wasm.OpcodeVecI16x8Shl, wasm.OpcodeVecI16x8ShrS, wasm.OpcodeVecI16x8ShrU,
+			wasm.OpcodeVecI32x4Shl, wasm.OpcodeVecI32x4ShrS, wasm.OpcodeVecI32x4ShrU,
+			wasm.OpcodeVecI64x2Shl, wasm.OpcodeVecI64x2ShrS, wasm.OpcodeVecI64x2ShrU:
 			return signature_V128I32_V128, nil
 		case wasm.OpcodeVecI64x2ReplaceLane:
 			return signature_V128I64_V128, nil


Test patch:
diff --git a/internal/asm/amd64/impl_staticconst_test.go b/internal/asm/amd64/impl_staticconst_test.go
index bcdd2c0b..89c96b6c 100644
--- a/internal/asm/amd64/impl_staticconst_test.go
+++ b/internal/asm/amd64/impl_staticconst_test.go
@@ -140,7 +140,7 @@ func TestAssemblerImpl_encodeStaticConstToRegister(t *testing.T) {
 	require.NoError(t, err)
 	err = a.CompileLoadStaticConstToRegister(MOVDQU, consts[1], RegX0)
 	require.NoError(t, err)
-	err = a.CompileLoadStaticConstToRegister(MOVDQU, consts[0], RegX0)
+	err = a.CompileLoadStaticConstToRegister(LEAQ, consts[0], RegX0)
 	require.NoError(t, err)
 	err = a.CompileLoadStaticConstToRegister(MOVDQU, consts[2], RegX12)
 	require.NoError(t, err)
@@ -150,23 +150,24 @@ func TestAssemblerImpl_encodeStaticConstToRegister(t *testing.T) {
 
 	require.Equal(t, []byte{
 		0x0f, 0x0b, // dummy instruction.
-		// 0x2: movdqu xmm12, xmmword ptr [rip + 0x19]
-		// where rip = 0x0b, therefore [rip + 0x19] = [0x24] = consts[0].
-		0xf3, 0x44, 0x0f, 0x6f, 0x25, 0x19, 0x00, 0x00, 0x00,
-		// 0x0b: movdqu xmm0, xmmword ptr [rip + 0x19]
-		// where rip = 0x13, therefore [rip + 0x19] = [0x2c] = consts[1].
-		0xf3, 0x0f, 0x6f, 0x05, 0x19, 0x00, 0x00, 0x00,
-		// 0x13: movdqu xmm0, xmmword ptr [rip + 0x9]
-		// where rip = 0x1b, therefore [rip + 0x9] = [0x24] = consts[0].
-		0xf3, 0x0f, 0x6f, 0x05, 0x09, 0x00, 0x00, 0x00,
-		// 0x1b: movdqu xmm12, xmmword ptr [rip + 0xc]
-		// where rip = 0x24, therefore [rip + 0xc] = [0x30] = consts[2].
+		// 0x2: movdqu xmm12, xmmword ptr [rip + 0x18]
+		// where rip = 0x0b, therefore [rip + 0x18] = [0x23] = consts[0].
+		0xf3, 0x44, 0x0f, 0x6f, 0x25, 0x18, 0x00, 0x00, 0x00,
+		// 0x0b: movdqu xmm0, xmmword ptr [rip + 0x18]
+		// where rip = 0x13, therefore [rip + 0x18] = [0x2b] = consts[1].
+		0xf3, 0x0f, 0x6f, 0x05, 0x18, 0x00, 0x00, 0x00,
+		// 0x13: lea rax, [rip + 9]
+		// where rip = 0x1a, therefore [rip + 0x9] = [0x23] = consts[0].
+		0x48, 0x8d, 0x05, 0x09, 0x00, 0x00, 0x00,
+		// 0x1a: movdqu xmm12, xmmword ptr [rip + 0xc]
+		// where rip = 0x23, therefore [rip + 0xc] = [0x2f] = consts[2].
 		0xf3, 0x44, 0x0f, 0x6f, 0x25, 0x0c, 0x00, 0x00, 0x00,
-		// 0x24: consts[0]
+		// 0x23: consts[0]
 		0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
-		// 0x2c: consts[1]
+		// 0x2b: consts[1]
 		0x22, 0x22, 0x22, 0x22,
-		// 0x30: consts[2]
+		// 0x2f: consts[2]
 		0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
 	}, actual)
+
 }
diff --git a/internal/asm/amd64/impl_test.go b/internal/asm/amd64/impl_test.go
index 50a815c1..df9d2e73 100644
--- a/internal/asm/amd64/impl_test.go
+++ b/internal/asm/amd64/impl_test.go
@@ -633,6 +633,106 @@ func TestAssemblerImpl_EncodeRegisterToRegister(t *testing.T) {
 			},
 			exp: []byte{0x66, 0x45, 0xf, 0xdf, 0xef},
 		},
+		{
+			name: "psrad xmm13, xmm15",
+			n: &NodeImpl{
+				Instruction: PSRAD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX15,
+				DstReg:      RegX13,
+			},
+			exp: []byte{0x66, 0x45, 0xf, 0xe2, 0xef},
+		},
+		{
+			name: "psraw xmm1, xmm1",
+			n: &NodeImpl{
+				Instruction: PSRAW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX1,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0xf, 0xe1, 0xc9},
+		},
+		{
+			name: "psrlq xmm14, xmm14",
+			n: &NodeImpl{
+				Instruction: PSRLQ,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX14,
+				DstReg:      RegX14,
+			},
+			exp: []byte{0x66, 0x45, 0xf, 0xd3, 0xf6},
+		},
+		{
+			name: "psrld xmm3, xmm3",
+			n: &NodeImpl{
+				Instruction: PSRLD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX3,
+				DstReg:      RegX3,
+			},
+			exp: []byte{0x66, 0xf, 0xd2, 0xdb},
+		},
+		{
+			name: "psrlw xmm15, xmm1",
+			n: &NodeImpl{
+				Instruction: PSRLW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX1,
+				DstReg:      RegX15,
+			},
+			exp: []byte{0x66, 0x44, 0xf, 0xd1, 0xf9},
+		},
+		{
+			name: "psllw xmm1, xmm15",
+			n: &NodeImpl{
+				Instruction: PSLLW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX15,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0xf1, 0xcf},
+		},
+		{
+			name: "punpcklbw xmm1, xmm15",
+			n: &NodeImpl{
+				Instruction: PUNPCKLBW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX15,
+				DstReg:      RegX1,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x60, 0xcf},
+		},
+		{
+			name: "punpckhbw xmm11, xmm1",
+			n: &NodeImpl{
+				Instruction: PUNPCKHBW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX1,
+				DstReg:      RegX11,
+			},
+			exp: []byte{0x66, 0x44, 0xf, 0x68, 0xd9},
+		},
+		{
+			name: "pslld xmm11, xmm1",
+			n: &NodeImpl{
+				Instruction: PSLLD,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX1,
+				DstReg:      RegX11,
+			},
+			exp: []byte{0x66, 0x44, 0xf, 0xf2, 0xd9},
+		},
+		{
+			name: "psllq xmm11, xmm15",
+			n: &NodeImpl{
+				Instruction: PSLLQ,
+				Types:       OperandTypesRegisterToRegister,
+				SrcReg:      RegX15,
+				DstReg:      RegX11,
+			},
+			exp: []byte{0x66, 0x45, 0xf, 0xf3, 0xdf},
+		},
 	}
 
 	for _, tt := range tests {
@@ -648,3 +748,87 @@ func TestAssemblerImpl_EncodeRegisterToRegister(t *testing.T) {
 		})
 	}
 }
+
+func TestAssemblerImpl_EncodeConstToRegister(t *testing.T) {
+	// These are not supported by golang-asm, so we test here instead of integration tests.
+	tests := []struct {
+		name string
+		n    *NodeImpl
+		exp  []byte
+	}{
+		{
+			name: "psraw xmm10, 1",
+			n: &NodeImpl{
+				Instruction: PSRAW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcConst:    1,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x71, 0xe2, 0x1},
+		},
+		{
+			name: "psraw xmm10, 8",
+			n: &NodeImpl{
+				Instruction: PSRAW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcConst:    8,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x71, 0xe2, 0x8},
+		},
+
+		{
+			name: "psrlw xmm10, 1",
+			n: &NodeImpl{
+				Instruction: PSRLW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcConst:    1,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x71, 0xd2, 0x1},
+		},
+		{
+			name: "psrlw xmm10, 8",
+			n: &NodeImpl{
+				Instruction: PSRLW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcConst:    8,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x71, 0xd2, 0x8},
+		},
+		{
+			name: "psllw xmm10, 1",
+			n: &NodeImpl{
+				Instruction: PSLLW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcConst:    1,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x71, 0xf2, 0x1},
+		},
+		{
+			name: "psllw xmm10, 8",
+			n: &NodeImpl{
+				Instruction: PSLLW,
+				Types:       OperandTypesRegisterToRegister,
+				SrcConst:    8,
+				DstReg:      RegX10,
+			},
+			exp: []byte{0x66, 0x41, 0xf, 0x71, 0xf2, 0x8},
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			a := NewAssemblerImpl()
+			err := a.EncodeConstToRegister(tc.n)
+			require.NoError(t, err)
+
+			actual, err := a.Assemble()
+			require.NoError(t, err)
+			require.Equal(t, tc.exp, actual, hex.EncodeToString(actual))
+		})
+	}
+}
diff --git a/internal/engine/compiler/compiler_vec_test.go b/internal/engine/compiler/compiler_vec_test.go
index 36b16e05..3216a914 100644
--- a/internal/engine/compiler/compiler_vec_test.go
+++ b/internal/engine/compiler/compiler_vec_test.go
@@ -2267,3 +2267,574 @@ func TestCompiler_compileV128Bitselect(t *testing.T) {
 		})
 	}
 }
+
+func TestCompiler_compileV128Shl(t *testing.T) {
+	if runtime.GOARCH != "amd64" {
+		// TODO: implement on amd64.
+		t.Skip()
+	}
+
+	tests := []struct {
+		name   string
+		shape  wazeroir.Shape
+		s      uint32
+		x, exp [16]byte
+	}{
+		{
+			name:  "i8x16/shift=0",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s: 0,
+		},
+		{
+			name:  "i8x16/shift=1",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff,
+				1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff,
+			},
+			exp: [16]byte{
+				2, 0xfe, 2, 0xfe, 2, 0xfe, 2, 0xfe,
+				2, 0xfe, 2, 0xfe, 2, 0xfe, 2, 0xfe,
+			},
+			s: 1,
+		},
+		{
+			name:  "i8x16/shift=2",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				4, 0, 4, 0, 4, 0, 4, 0,
+				4, 0, 4, 0, 4, 0, 4, 0,
+			},
+			s: 2,
+		},
+		{
+			name:  "i8x16/shift=3",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff,
+				1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff,
+			},
+			exp: [16]byte{
+				8, 0xff & ^0b111, 8, 0xff & ^0b111, 8, 0xff & ^0b111, 8, 0xff & ^0b111,
+				8, 0xff & ^0b111, 8, 0xff & ^0b111, 8, 0xff & ^0b111, 8, 0xff & ^0b111,
+			},
+			s: 3,
+		},
+		{
+			name:  "i8x16/shift=4",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1,
+				0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1,
+			},
+			exp: [16]byte{
+				0xff & ^0b1111, 16, 0xff & ^0b1111, 16, 0xff & ^0b1111, 16, 0xff & ^0b1111, 16,
+				0xff & ^0b1111, 16, 0xff & ^0b1111, 16, 0xff & ^0b1111, 16, 0xff & ^0b1111, 16,
+			},
+			s: 4,
+		},
+		{
+			name:  "i8x16/shift=5",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				0xff, 0xff, 0xff, 0xff, 1, 1, 1, 1,
+				0xff, 0xff, 0xff, 0xff, 1, 1, 1, 1,
+			},
+			exp: [16]byte{
+				0xff & ^0b11111, 0xff & ^0b11111, 0xff & ^0b11111, 0xff & ^0b11111, 32, 32, 32, 32,
+				0xff & ^0b11111, 0xff & ^0b11111, 0xff & ^0b11111, 0xff & ^0b11111, 32, 32, 32, 32,
+			},
+			s: 5,
+		},
+		{
+			name:  "i8x16/shift=6",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				0xff, 0x81, 0xff, 0x81, 0xff, 0x81, 0xff, 0x81,
+				0xff, 0x81, 0xff, 0x81, 0xff, 0x81, 0xff, 0x81,
+			},
+			exp: [16]byte{
+				0xc0, 1 << 6, 0xc0, 1 << 6, 0xc0, 1 << 6, 0xc0, 1 << 6,
+				0xc0, 1 << 6, 0xc0, 1 << 6, 0xc0, 1 << 6, 0xc0, 1 << 6,
+			},
+			s: 6,
+		},
+		{
+			name:  "i8x16/shift=7",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+			},
+			exp: [16]byte{
+				0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+				0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+			},
+			s: 7,
+		},
+		{
+			name:  "i16x8/shift=0",
+			shape: wazeroir.ShapeI16x8,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s: 0,
+		},
+		{
+			name:  "i16x8/shift=1",
+			shape: wazeroir.ShapeI16x8,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				2, 0, 2, 0, 2, 0, 2, 0,
+				2, 0, 2, 0, 2, 0, 2, 0,
+			},
+			s: 1,
+		},
+		{
+			name:  "i16x8/shift=7",
+			shape: wazeroir.ShapeI16x8,
+			x: [16]byte{
+				1, 1, 1, 1, 0x80, 0x80, 0x80, 0x80,
+				0, 0x80, 0, 0x80, 0b11, 0b11, 0b11, 0b11,
+			},
+			exp: [16]byte{
+				0, 1, 0, 1, 0, 0x80, 0, 0x80,
+				0, 0, 0, 0, 0, 0b11, 0, 0b11,
+			},
+			s: 8,
+		},
+		{
+			name:  "i16x8/shift=15",
+			shape: wazeroir.ShapeI16x8,
+			x: [16]byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+			},
+			exp: [16]byte{
+				0, 0x80, 0, 0x80, 0, 0x80, 0, 0x80,
+				0, 0x80, 0, 0x80, 0, 0x80, 0, 0x80,
+			},
+			s: 15,
+		},
+		{
+			name:  "i32x4/shift=0",
+			shape: wazeroir.ShapeI32x4,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s: 0,
+		},
+		{
+			name:  "i32x4/shift=1",
+			shape: wazeroir.ShapeI32x4,
+			x: [16]byte{
+				1, 0x80, 0, 0x80, 1, 0x80, 0, 0x80,
+				1, 0x80, 0, 0x80, 1, 0x80, 0, 0x80,
+			},
+			exp: [16]byte{
+				2, 0, 1, 0, 2, 0, 1, 0,
+				2, 0, 1, 0, 2, 0, 1, 0,
+			},
+			s: 1,
+		},
+		{
+			name:  "i32x4/shift=31",
+			shape: wazeroir.ShapeI32x4,
+			x: [16]byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+			},
+			exp: [16]byte{
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+			},
+			s: 31,
+		},
+		{
+			name:  "i64x2/shift=0",
+			shape: wazeroir.ShapeI64x2,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s: 0,
+		},
+		{
+			name:  "i64x2/shift=5",
+			shape: wazeroir.ShapeI64x2,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1 << 5, 0, 1<<4 | 1<<5, 0, 1<<4 | 1<<5, 0, 1<<4 | 1<<5, 0,
+				1 << 5, 0, 1<<4 | 1<<5, 0, 1<<4 | 1<<5, 0, 1<<4 | 1<<5, 0,
+			},
+			s: 5,
+		},
+		{
+			name:  "i64x2/shift=63",
+			shape: wazeroir.ShapeI64x2,
+			x: [16]byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+			},
+			exp: [16]byte{
+				0, 0, 0, 0, 0, 0, 0, 0x80,
+				0, 0, 0, 0, 0, 0, 0, 0x80,
+			},
+			s: 63,
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			env := newCompilerEnvironment()
+			compiler := env.requireNewCompiler(t, newCompiler,
+				&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
+
+			err := compiler.compilePreamble()
+			require.NoError(t, err)
+
+			err = compiler.compileV128Const(&wazeroir.OperationV128Const{
+				Lo: binary.LittleEndian.Uint64(tc.x[:8]),
+				Hi: binary.LittleEndian.Uint64(tc.x[8:]),
+			})
+			require.NoError(t, err)
+
+			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.s})
+			require.NoError(t, err)
+
+			err = compiler.compileV128Shl(&wazeroir.OperationV128Shl{Shape: tc.shape})
+			require.NoError(t, err)
+
+			require.Equal(t, uint64(2), compiler.runtimeValueLocationStack().sp)
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+
+			err = compiler.compileReturnFunction()
+			require.NoError(t, err)
+
+			// Generate and run the code under test.
+			code, _, _, err := compiler.compile()
+			require.NoError(t, err)
+			env.exec(code)
+
+			lo, hi := env.stackTopAsV128()
+			var actual [16]byte
+			binary.LittleEndian.PutUint64(actual[:8], lo)
+			binary.LittleEndian.PutUint64(actual[8:], hi)
+			require.Equal(t, tc.exp, actual)
+		})
+	}
+}
+
+func TestCompiler_compileV128Shr(t *testing.T) {
+	if runtime.GOARCH != "amd64" {
+		// TODO: implement on amd64.
+		t.Skip()
+	}
+
+	tests := []struct {
+		name   string
+		signed bool
+		shape  wazeroir.Shape
+		s      uint32
+		x, exp [16]byte
+	}{
+		{
+			name:  "i8x16/shift=0/signed=false",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s:      0,
+			signed: false,
+		},
+		{
+			name:  "i8x16/shift=7/signed=false",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+				0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+			},
+			exp: [16]byte{
+				1, 1, 1, 1, 1, 1, 1, 1,
+				1, 1, 1, 1, 1, 1, 1, 1,
+			},
+			s:      7,
+			signed: false,
+		},
+		{
+			name:  "i8x16/shift=0/signed=false",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s:      0,
+			signed: true,
+		},
+		{
+			name:  "i8x16/shift=7/signed=false",
+			shape: wazeroir.ShapeI8x16,
+			x: [16]byte{
+				1, 0x80, 0x7e, 0x80, 1, 0x80, 0x7e, 0x80,
+				1, 0x80, 0x7e, 0x80, 1, 0x80, 0x7e, 0x80,
+			},
+			exp: [16]byte{
+				0, 0xff, 0, 0xff, 0, 0xff, 0, 0xff,
+				0, 0xff, 0, 0xff, 0, 0xff, 0, 0xff,
+			},
+			s:      7,
+			signed: true,
+		},
+		{
+			name:  "i16x8/shift=0/signed=false",
+			shape: wazeroir.ShapeI16x8,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s:      0,
+			signed: false,
+		},
+		{
+			name:  "i16x8/shift=8/signed=false",
+			shape: wazeroir.ShapeI16x8,
+			x: [16]byte{
+				0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80,
+				0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80,
+			},
+			exp: [16]byte{
+				0x80, 0, 0x80, 0, 0x80, 0, 0x80, 0,
+				0x80, 0, 0x80, 0, 0x80, 0, 0x80, 0,
+			},
+			s:      8,
+			signed: false,
+		},
+		{
+			name:  "i16x8/shift=0/signed=true",
+			shape: wazeroir.ShapeI16x8,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s:      0,
+			signed: true,
+		},
+		{
+			name:  "i16x8/shift=8/signed=true",
+			shape: wazeroir.ShapeI16x8,
+			x: [16]byte{
+				0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80,
+				0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80,
+			},
+			exp: [16]byte{
+				0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff,
+				0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff,
+			},
+			s:      8,
+			signed: true,
+		},
+		{
+			name:  "i32x4/shift=0/signed=false",
+			shape: wazeroir.ShapeI32x4,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s:      0,
+			signed: false,
+		},
+		{
+			name:  "i32x4/shift=16/signed=false",
+			shape: wazeroir.ShapeI32x4,
+			x: [16]byte{
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+			},
+			exp: [16]byte{
+				0, 0x80, 0, 0, 0, 0x80, 0, 0,
+				0, 0x80, 0, 0, 0, 0x80, 0, 0,
+			},
+			s:      16,
+			signed: false,
+		},
+		{
+			name:  "i32x4/shift=0/signed=true",
+			shape: wazeroir.ShapeI32x4,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s:      0,
+			signed: true,
+		},
+		{
+			name:  "i32x4/shift=16/signed=true",
+			shape: wazeroir.ShapeI32x4,
+			x: [16]byte{
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+			},
+			exp: [16]byte{
+				0, 0x80, 0xff, 0xff, 0, 0x80, 0xff, 0xff,
+				0, 0x80, 0xff, 0xff, 0, 0x80, 0xff, 0xff,
+			},
+			s:      16,
+			signed: true,
+		},
+		{
+			name:  "i64x2/shift=0/signed=false",
+			shape: wazeroir.ShapeI32x4,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s:      0,
+			signed: false,
+		},
+		{
+			name:  "i64x2/shift=16/signed=false",
+			shape: wazeroir.ShapeI64x2,
+			x: [16]byte{
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+			},
+			exp: [16]byte{
+				0, 0x80, 0, 0, 0, 0x80, 0, 0,
+				0, 0x80, 0, 0, 0, 0x80, 0, 0,
+			},
+			s:      16,
+			signed: false,
+		},
+		{
+			name:  "i64x2/shift=0/signed=true",
+			shape: wazeroir.ShapeI64x2,
+			x: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			exp: [16]byte{
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+				1, 0x80, 1, 0x80, 1, 0x80, 1, 0x80,
+			},
+			s:      0,
+			signed: true,
+		},
+		{
+			name:  "i64x2/shift=16/signed=true",
+			shape: wazeroir.ShapeI64x2,
+			x: [16]byte{
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+				0, 0, 0, 0x80, 0, 0, 0, 0x80,
+			},
+			exp: [16]byte{
+				0, 0x80, 0, 0, 0, 0x80, 0xff, 0xff,
+				0, 0x80, 0, 0, 0, 0x80, 0xff, 0xff,
+			},
+			s:      16,
+			signed: true,
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			env := newCompilerEnvironment()
+			compiler := env.requireNewCompiler(t, newCompiler,
+				&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
+
+			err := compiler.compilePreamble()
+			require.NoError(t, err)
+
+			err = compiler.compileV128Const(&wazeroir.OperationV128Const{
+				Lo: binary.LittleEndian.Uint64(tc.x[:8]),
+				Hi: binary.LittleEndian.Uint64(tc.x[8:]),
+			})
+			require.NoError(t, err)
+
+			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.s})
+			require.NoError(t, err)
+
+			err = compiler.compileV128Shr(&wazeroir.OperationV128Shr{Shape: tc.shape, Signed: tc.signed})
+			require.NoError(t, err)
+
+			require.Equal(t, uint64(2), compiler.runtimeValueLocationStack().sp)
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+
+			err = compiler.compileReturnFunction()
+			require.NoError(t, err)
+
+			// Generate and run the code under test.
+			code, _, _, err := compiler.compile()
+			require.NoError(t, err)
+			env.exec(code)
+
+			lo, hi := env.stackTopAsV128()
+			var actual [16]byte
+			binary.LittleEndian.PutUint64(actual[:8], lo)
+			binary.LittleEndian.PutUint64(actual[8:], hi)
+			require.Equal(t, tc.exp, actual)
+		})
+	}
+}
diff --git a/internal/engine/compiler/impl_vec_amd64_test.go b/internal/engine/compiler/impl_vec_amd64_test.go
index 0ff7b4f9..a26283ca 100644
--- a/internal/engine/compiler/impl_vec_amd64_test.go
+++ b/internal/engine/compiler/impl_vec_amd64_test.go
@@ -62,3 +62,157 @@ func TestAmd64Compiler_V128Shuffle_ConstTable_MiddleOfFunction(t *testing.T) {
 	binary.LittleEndian.PutUint64(actual[8:], hi)
 	require.Equal(t, exp, actual)
 }
+
+func TestAmd64Compiler_compileV128ShrI64x2SignedImpl(t *testing.T) {
+	x := [16]byte{
+		0, 0, 0, 0x80, 0, 0, 0, 0x80,
+		0, 0, 0, 0x80, 0, 0, 0, 0x80,
+	}
+	exp := [16]byte{
+		0, 0, 0, 0x40, 0, 0, 0, 0x80 | 0x80>>1,
+		0, 0, 0, 0x40, 0, 0, 0, 0x80 | 0x80>>1,
+	}
+	shiftAmount := uint32(1)
+
+	tests := []struct {
+		name               string
+		shiftAmountSetupFn func(t *testing.T, c *amd64Compiler)
+		verifyFn           func(t *testing.T, env *compilerEnv)
+	}{
+		{
+			name: "RegR10/CX not in use",
+			shiftAmountSetupFn: func(t *testing.T, c *amd64Compiler) {
+				// Move the shift amount to R10.
+				loc := c.locationStack.peek()
+				oldReg, newReg := loc.register, amd64.RegR10
+				c.assembler.CompileRegisterToRegister(amd64.MOVQ, oldReg, newReg)
+				loc.setRegister(newReg)
+				c.locationStack.markRegisterUnused(oldReg)
+				c.locationStack.markRegisterUsed(newReg)
+			},
+			verifyFn: func(t *testing.T, env *compilerEnv) {},
+		},
+		{
+			name: "RegR10/CX in use",
+			shiftAmountSetupFn: func(t *testing.T, c *amd64Compiler) {
+				// Pop the shift amount and vector values temporarily.
+				shiftAmountLocation := c.locationStack.pop()
+				vec := c.locationStack.popV128()
+
+				// Move the shift amount to R10.
+				oldReg, newReg := shiftAmountLocation.register, amd64.RegR10
+				c.assembler.CompileRegisterToRegister(amd64.MOVQ, oldReg, newReg)
+				c.locationStack.markRegisterUnused(oldReg)
+				c.locationStack.markRegisterUsed(newReg)
+
+				// Create the previous usage of CX register.
+				c.pushRuntimeValueLocationOnRegister(amd64.RegCX, runtimeValueTypeI32)
+				c.assembler.CompileConstToRegister(amd64.MOVQ, 100, amd64.RegCX)
+
+				// push the operands back to the location registers.
+				c.pushVectorRuntimeValueLocationOnRegister(vec.register)
+				c.pushRuntimeValueLocationOnRegister(newReg, runtimeValueTypeI32)
+			},
+			verifyFn: func(t *testing.T, env *compilerEnv) {
+				// at the bottom of stack, the previous value on the CX register must be saved.
+				actual := env.stack()[0]
+				require.Equal(t, uint64(100), actual)
+			},
+		},
+		{
+			name: "Stack/CX not in use",
+			shiftAmountSetupFn: func(t *testing.T, c *amd64Compiler) {
+				// Release the shift amount value to the stack.
+				loc := c.locationStack.peek()
+				c.compileReleaseRegisterToStack(loc)
+			},
+			verifyFn: func(t *testing.T, env *compilerEnv) {},
+		},
+		{
+			name: "Stack/CX in use",
+			shiftAmountSetupFn: func(t *testing.T, c *amd64Compiler) {
+				// Pop the shift amount and vector values temporarily.
+				shiftAmountReg := c.locationStack.pop().register
+				require.NotEqual(t, amd64.RegCX, shiftAmountReg)
+				vec := c.locationStack.popV128()
+
+				// Create the previous usage of CX register.
+				c.pushRuntimeValueLocationOnRegister(amd64.RegCX, runtimeValueTypeI32)
+				c.assembler.CompileConstToRegister(amd64.MOVQ, 100, amd64.RegCX)
+
+				// push the operands back to the location registers.
+				c.pushVectorRuntimeValueLocationOnRegister(vec.register)
+				// Release the shift amount value to the stack.
+				loc := c.pushRuntimeValueLocationOnRegister(shiftAmountReg, runtimeValueTypeI32)
+				c.compileReleaseRegisterToStack(loc)
+			},
+			verifyFn: func(t *testing.T, env *compilerEnv) {
+				// at the bottom of stack, the previous value on the CX register must be saved.
+				actual := env.stack()[0]
+				require.Equal(t, uint64(100), actual)
+			},
+		},
+		{
+			name: "CondReg/CX not in use",
+			shiftAmountSetupFn: func(t *testing.T, c *amd64Compiler) {
+				// Ignore the pushed const.
+				loc := c.locationStack.pop()
+				c.locationStack.markRegisterUnused(loc.register)
+
+				// Instead, push the conditional flag value which is supposed be interpreted as 1 (=shiftAmount).
+				err := c.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
+				require.NoError(t, err)
+				err = c.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
+				require.NoError(t, err)
+				err = c.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
+				require.NoError(t, err)
+			},
+			verifyFn: func(t *testing.T, env *compilerEnv) {},
+		},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			env := newCompilerEnvironment()
+			compiler := env.requireNewCompiler(t, newCompiler,
+				&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
+
+			err := compiler.compilePreamble()
+			require.NoError(t, err)
+
+			err = compiler.compileV128Const(&wazeroir.OperationV128Const{
+				Lo: binary.LittleEndian.Uint64(x[:8]),
+				Hi: binary.LittleEndian.Uint64(x[8:]),
+			})
+			require.NoError(t, err)
+
+			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: shiftAmount})
+			require.NoError(t, err)
+
+			amdCompiler := compiler.(*amd64Compiler)
+			tc.shiftAmountSetupFn(t, amdCompiler)
+
+			err = amdCompiler.compileV128ShrI64x2SignedImpl()
+			require.NoError(t, err)
+
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+
+			err = compiler.compileReturnFunction()
+			require.NoError(t, err)
+
+			// Generate and run the code under test.
+			code, _, _, err := compiler.compile()
+			require.NoError(t, err)
+			env.exec(code)
+
+			lo, hi := env.stackTopAsV128()
+			var actual [16]byte
+			binary.LittleEndian.PutUint64(actual[:8], lo)
+			binary.LittleEndian.PutUint64(actual[8:], hi)
+			require.Equal(t, exp, actual)
+
+			tc.verifyFn(t, env)
+		})
+	}
+}
diff --git a/internal/engine/compiler/impl_vec_arm64_test.go b/internal/engine/compiler/impl_vec_arm64_test.go
index 3b93d41a..5ce04d5a 100644
--- a/internal/engine/compiler/impl_vec_arm64_test.go
+++ b/internal/engine/compiler/impl_vec_arm64_test.go
@@ -4,6 +4,7 @@ import (
 	"encoding/binary"
 	"testing"
 
+	"github.com/tetratelabs/wazero/internal/asm"
 	"github.com/tetratelabs/wazero/internal/asm/arm64"
 	"github.com/tetratelabs/wazero/internal/testing/require"
 	"github.com/tetratelabs/wazero/internal/wasm"
@@ -63,3 +64,156 @@ func TestArm64Compiler_V128Shuffle_ConstTable_MiddleOfFunction(t *testing.T) {
 	binary.LittleEndian.PutUint64(actual[8:], hi)
 	require.Equal(t, exp, actual)
 }
+
+func TestArm64Compiler_V128Shuffle_combinations(t *testing.T) {
+	movValueRegisterToRegister := func(t *testing.T, c *arm64Compiler, src *runtimeValueLocation, dst asm.Register) {
+		c.assembler.CompileVectorRegisterToVectorRegister(arm64.VMOV, src.register, dst,
+			arm64.VectorArrangement16B, arm64.VectorIndexNone, arm64.VectorIndexNone)
+		c.locationStack.markRegisterUnused(src.register)
+		src.setRegister(dst)
+		// We have to set the lower 64-bits' location as well.
+		c.locationStack.stack[src.stackPointer-1].setRegister(dst)
+		c.locationStack.markRegisterUsed(dst)
+	}
+
+	tests := []struct {
+		name                        string
+		init                        func(t *testing.T, c *arm64Compiler)
+		wReg, vReg                  asm.Register
+		verifyFnc                   func(t *testing.T, env *compilerEnv)
+		expStackPointerAfterShuffle uint64
+	}{
+		{
+			name:                        "w=v1, v=v2",
+			wReg:                        arm64.RegV1,
+			vReg:                        arm64.RegV2,
+			init:                        func(t *testing.T, c *arm64Compiler) {},
+			verifyFnc:                   func(t *testing.T, env *compilerEnv) {},
+			expStackPointerAfterShuffle: 2,
+		},
+		{
+			name:                        "w=v2, v=v1",
+			wReg:                        arm64.RegV2,
+			vReg:                        arm64.RegV1,
+			init:                        func(t *testing.T, c *arm64Compiler) {},
+			verifyFnc:                   func(t *testing.T, env *compilerEnv) {},
+			expStackPointerAfterShuffle: 2,
+		},
+		{
+			name:                        "w=v29, v=v30",
+			wReg:                        arm64.RegV29, // will be moved to v30.
+			vReg:                        arm64.RegV30, // will be moved to v29.
+			init:                        func(t *testing.T, c *arm64Compiler) {},
+			verifyFnc:                   func(t *testing.T, env *compilerEnv) {},
+			expStackPointerAfterShuffle: 2,
+		},
+		{
+			name: "w=v12, v=v30",
+			wReg: arm64.RegV12, // will be moved to v30.
+			vReg: arm64.RegV30, // will be moved to v29.
+			init: func(t *testing.T, c *arm64Compiler) {
+				// Set up the previous value on the v3 register.
+				err := c.compileV128Const(&wazeroir.OperationV128Const{
+					Lo: 1234,
+					Hi: 5678,
+				})
+				require.NoError(t, err)
+				movValueRegisterToRegister(t, c, c.locationStack.peek(), arm64.RegV29)
+			},
+			verifyFnc: func(t *testing.T, env *compilerEnv) {
+				// Previous value on the V3 register must be saved onto the stack.
+				lo, hi := env.stack()[0], env.stack()[1]
+				require.Equal(t, uint64(1234), lo)
+				require.Equal(t, uint64(5678), hi)
+			},
+			expStackPointerAfterShuffle: 4,
+		},
+		{
+			name: "w=v29, v=v12",
+			wReg: arm64.RegV29, // will be moved to v30.
+			vReg: arm64.RegV12, // will be moved to v29.
+			init: func(t *testing.T, c *arm64Compiler) {
+				// Set up the previous value on the v3 register.
+				err := c.compileV128Const(&wazeroir.OperationV128Const{
+					Lo: 1234,
+					Hi: 5678,
+				})
+				require.NoError(t, err)
+				movValueRegisterToRegister(t, c, c.locationStack.peek(), arm64.RegV30)
+			},
+			verifyFnc: func(t *testing.T, env *compilerEnv) {
+				// Previous value on the V3 register must be saved onto the stack.
+				lo, hi := env.stack()[0], env.stack()[1]
+				require.Equal(t, uint64(1234), lo)
+				require.Equal(t, uint64(5678), hi)
+			},
+			expStackPointerAfterShuffle: 4,
+		},
+	}
+
+	lanes := [16]byte{1, 1, 1, 1, 0, 0, 0, 0, 10, 10, 10, 10, 0, 0, 0, 31}
+	v := [16]byte{0: 0xa, 1: 0xb, 10: 0xc}
+	w := [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1}
+	exp := [16]byte{
+		0xb, 0xb, 0xb, 0xb,
+		0xa, 0xa, 0xa, 0xa,
+		0xc, 0xc, 0xc, 0xc,
+		0xa, 0xa, 0xa, 1,
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			env := newCompilerEnvironment()
+			compiler := env.requireNewCompiler(t, newCompiler,
+				&wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
+
+			err := compiler.compilePreamble()
+			require.NoError(t, err)
+
+			ac := compiler.(*arm64Compiler)
+			tc.init(t, ac)
+
+			err = compiler.compileV128Const(&wazeroir.OperationV128Const{
+				Lo: binary.LittleEndian.Uint64(v[:8]),
+				Hi: binary.LittleEndian.Uint64(v[8:]),
+			})
+			require.NoError(t, err)
+
+			vLocation := compiler.runtimeValueLocationStack().peek()
+			movValueRegisterToRegister(t, ac, vLocation, tc.vReg)
+
+			err = compiler.compileV128Const(&wazeroir.OperationV128Const{
+				Lo: binary.LittleEndian.Uint64(w[:8]),
+				Hi: binary.LittleEndian.Uint64(w[8:]),
+			})
+			require.NoError(t, err)
+
+			wLocation := compiler.runtimeValueLocationStack().peek()
+			movValueRegisterToRegister(t, ac, wLocation, tc.wReg)
+
+			err = compiler.compileV128Shuffle(&wazeroir.OperationV128Shuffle{Lanes: lanes})
+			require.NoError(t, err)
+
+			require.Equal(t, tc.expStackPointerAfterShuffle, compiler.runtimeValueLocationStack().sp)
+			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
+
+			err = compiler.compileReturnFunction()
+			require.NoError(t, err)
+
+			// Generate and run the code under test.
+			code, _, _, err := compiler.compile()
+			require.NoError(t, err)
+
+			env.exec(code)
+
+			lo, hi := env.stackTopAsV128()
+			var actual [16]byte
+			binary.LittleEndian.PutUint64(actual[:8], lo)
+			binary.LittleEndian.PutUint64(actual[8:], hi)
+			require.Equal(t, exp, actual)
+
+			tc.verifyFnc(t, env)
+		})
+	}
+}
diff --git a/internal/integration_test/asm/amd64_debug/golang_asm.go b/internal/integration_test/asm/amd64_debug/golang_asm.go
index e6c6b3a3..f4bf0b13 100644
--- a/internal/integration_test/asm/amd64_debug/golang_asm.go
+++ b/internal/integration_test/asm/amd64_debug/golang_asm.go
@@ -508,9 +508,9 @@ var castAsGolangAsmInstruction = [...]obj.As{
 	amd64.ORQ:       x86.AORQ,
 	amd64.POPCNTL:   x86.APOPCNTL,
 	amd64.POPCNTQ:   x86.APOPCNTQ,
-	amd64.PSLLL:     x86.APSLLL,
+	amd64.PSLLD:     x86.APSLLL,
 	amd64.PSLLQ:     x86.APSLLQ,
-	amd64.PSRLL:     x86.APSRLL,
+	amd64.PSRLD:     x86.APSRLL,
 	amd64.PSRLQ:     x86.APSRLQ,
 	amd64.ROLL:      x86.AROLL,
 	amd64.ROLQ:      x86.AROLQ,
diff --git a/internal/integration_test/asm/amd64_debug/impl_test.go b/internal/integration_test/asm/amd64_debug/impl_test.go
index c569eddf..c6695b1e 100644
--- a/internal/integration_test/asm/amd64_debug/impl_test.go
+++ b/internal/integration_test/asm/amd64_debug/impl_test.go
@@ -1507,20 +1507,20 @@ func TestAssemblerImpl_encodeConstToRegister(t *testing.T) {
 				expErr: "RET is unsupported for from:const,to:register type",
 			},
 			{
-				n:      &amd64.NodeImpl{Instruction: amd64.PSLLL, Types: amd64.OperandTypesConstToRegister},
+				n:      &amd64.NodeImpl{Instruction: amd64.PSLLD, Types: amd64.OperandTypesConstToRegister},
 				expErr: "invalid register [nil]",
 			},
 			{
-				n:      &amd64.NodeImpl{Instruction: amd64.PSLLL, Types: amd64.OperandTypesConstToRegister, DstReg: amd64.RegAX},
-				expErr: "PSLLL needs float register but got AX",
+				n:      &amd64.NodeImpl{Instruction: amd64.PSLLD, Types: amd64.OperandTypesConstToRegister, DstReg: amd64.RegAX},
+				expErr: "PSLLD needs float register but got AX",
 			},
 			{
 				n:      &amd64.NodeImpl{Instruction: amd64.ADDQ, Types: amd64.OperandTypesConstToRegister, DstReg: amd64.RegX0},
 				expErr: "ADDQ needs int register but got X0",
 			},
 			{
-				n:      &amd64.NodeImpl{Instruction: amd64.PSLLL, Types: amd64.OperandTypesConstToRegister, DstReg: amd64.RegX0, SrcConst: 2199023255552},
-				expErr: "constant must fit in 32-bit integer for PSLLL, but got 2199023255552",
+				n:      &amd64.NodeImpl{Instruction: amd64.PSLLD, Types: amd64.OperandTypesConstToRegister, DstReg: amd64.RegX0, SrcConst: 2199023255552},
+				expErr: "constant must fit in 32-bit integer for PSLLD, but got 2199023255552",
 			},
 			{
 				n:      &amd64.NodeImpl{Instruction: amd64.SHLQ, Types: amd64.OperandTypesConstToRegister, DstReg: amd64.RegR10, SrcConst: 32768},
@@ -1580,7 +1580,7 @@ func TestAssemblerImpl_encodeConstToRegister(t *testing.T) {
 		}
 	})
 	t.Run("float instructions", func(t *testing.T) {
-		for _, inst := range []asm.Instruction{amd64.PSLLL, amd64.PSLLQ, amd64.PSRLL, amd64.PSRLQ} {
+		for _, inst := range []asm.Instruction{amd64.PSLLD, amd64.PSLLQ, amd64.PSLLD, amd64.PSRLQ} {
 			inst := inst
 			t.Run(amd64.InstructionName(inst), func(t *testing.T) {
 				for _, reg := range floatRegisters {
diff --git a/internal/integration_test/spectest/v2/spec_test.go b/internal/integration_test/spectest/v2/spec_test.go
index a93a487c..663e179c 100644
--- a/internal/integration_test/spectest/v2/spec_test.go
+++ b/internal/integration_test/spectest/v2/spec_test.go
@@ -34,7 +34,7 @@ func TestCompiler(t *testing.T) {
 				"simd_load_splat.json", "simd_load_zero.json", "simd_store.json", "simd_store16_lane.json",
 				"simd_store32_lane.json", "simd_store64_lane.json", "simd_store8_lane.json":
 				return true
-			case "simd_bitwise.json", "simd_boolean.json":
+			case "simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json":
 				// TODO: implement on arm64.
 				return runtime.GOARCH == "amd64"
 			default:
@@ -54,7 +54,7 @@ func TestInterpreter(t *testing.T) {
 				"simd_load32_lane.json", "simd_load64_lane.json", "simd_load8_lane.json", "simd_lane.json",
 				"simd_load_extend.json", "simd_load_splat.json", "simd_load_zero.json", "simd_store.json",
 				"simd_store16_lane.json", "simd_store32_lane.json", "simd_store64_lane.json", "simd_store8_lane.json",
-				"simd_bitwise.json", "simd_boolean.json":
+				"simd_bitwise.json", "simd_boolean.json", "simd_bit_shift.json":
 				return true
 			default:
 				return false // others not supported, yet!
diff --git a/internal/wasm/func_validation_test.go b/internal/wasm/func_validation_test.go
index dfaeabe5..2b3fb8d5 100644
--- a/internal/wasm/func_validation_test.go
+++ b/internal/wasm/func_validation_test.go
@@ -2706,6 +2706,18 @@ func TestModule_funcValidation_SIMD(t *testing.T) {
 		)
 	}
 
+	vi2v := func(vec OpcodeVec) (ret []byte) {
+		ret = addV128Const(ret)
+		return append(ret,
+			OpcodeI32Const,
+			1, 1, 1, 1,
+			OpcodeVecPrefix,
+			vec,
+			OpcodeDrop,
+			OpcodeEnd,
+		)
+	}
+
 	load := func(vec OpcodeVec, offset, align uint32) (ret []byte) {
 		ret = []byte{
 			OpcodeI32Const,
@@ -2950,6 +2962,18 @@ func TestModule_funcValidation_SIMD(t *testing.T) {
 		{name: OpcodeVecV128OrName, body: vv2v(OpcodeVecV128Or)},
 		{name: OpcodeVecV128XorName, body: vv2v(OpcodeVecV128Xor)},
 		{name: OpcodeVecV128BitselectName, body: vvv2v(OpcodeVecV128Bitselect)},
+		{name: OpcodeVecI8x16ShlName, body: vi2v(OpcodeVecI8x16Shl)},
+		{name: OpcodeVecI8x16ShrSName, body: vi2v(OpcodeVecI8x16ShrS)},
+		{name: OpcodeVecI8x16ShrUName, body: vi2v(OpcodeVecI8x16ShrU)},
+		{name: OpcodeVecI16x8ShlName, body: vi2v(OpcodeVecI16x8Shl)},
+		{name: OpcodeVecI16x8ShrSName, body: vi2v(OpcodeVecI16x8ShrS)},
+		{name: OpcodeVecI16x8ShrUName, body: vi2v(OpcodeVecI16x8ShrU)},
+		{name: OpcodeVecI32x4ShlName, body: vi2v(OpcodeVecI32x4Shl)},
+		{name: OpcodeVecI32x4ShrSName, body: vi2v(OpcodeVecI32x4ShrS)},
+		{name: OpcodeVecI32x4ShrUName, body: vi2v(OpcodeVecI32x4ShrU)},
+		{name: OpcodeVecI64x2ShlName, body: vi2v(OpcodeVecI64x2Shl)},
+		{name: OpcodeVecI64x2ShrSName, body: vi2v(OpcodeVecI64x2ShrS)},
+		{name: OpcodeVecI64x2ShrUName, body: vi2v(OpcodeVecI64x2ShrU)},
 	}
 
 	for _, tt := range tests {