Files
gasm-sdk/asm/vex_test.go
T

333 lines
16 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package asm
import (
"strings"
"testing"
"golang.org/x/arch/x86/x86asm"
)
func vreg(t *testing.T, name string) Reg {
t.Helper()
r, ok := ParseReg(name)
if !ok {
t.Fatalf("unknown register %s", name)
}
return r
}
// TestVexNDS3 encodes `mnem Y0, Y1, Y2` for every three-operand NDS
// instruction and verifies it round-trips through the x86 decoder to the same
// mnemonic. A wrong opcode/map/pp surfaces as a different decoded instruction.
func TestVexNDS3(t *testing.T) {
for mnem, spec := range vexTable {
if spec.form != vexNDS3 {
continue
}
// Scalar (F2/F3 pp) instructions exist only in the 128-bit form.
vec := "Y"
if spec.pp >= 2 {
vec = "X"
}
code, err := Encode(mnem, vreg(t, vec+"0"), vreg(t, vec+"1"), vreg(t, vec+"2"))
if err != nil {
t.Errorf("%s: Encode: %v", mnem, err)
continue
}
inst, err := x86asm.Decode(code, 64)
if err != nil {
t.Errorf("%s: Decode(% x): %v", mnem, code, err)
continue
}
if inst.Op.String() != mnem {
t.Errorf("%s: decoded as %s (% x)", mnem, inst.Op.String(), code)
}
}
}
// TestVexGoFlac checks a representative go-flac instruction sequence encodes
// and decodes as expected.
func TestVexGoFlac(t *testing.T) {
// VPADDD Y5, Y8, Y8 → vpaddd ymm8, ymm8, ymm5.
code, err := Encode("VPADDD", vreg(t, "Y5"), vreg(t, "Y8"), vreg(t, "Y8"))
if err != nil {
t.Fatalf("Encode: %v", err)
}
inst, err := x86asm.Decode(code, 64)
if err != nil {
t.Fatalf("Decode(% x): %v", code, err)
}
if inst.Op != x86asm.VPADDD {
t.Fatalf("decoded %s, want VPADDD", inst.Op)
}
}
// TestVexXMM checks the 128-bit (XMM) form selects VEX.L=0.
func TestVexXMM(t *testing.T) {
code, err := Encode("VPXOR", vreg(t, "X7"), vreg(t, "X7"), vreg(t, "X7"))
if err != nil {
t.Fatalf("Encode: %v", err)
}
inst, err := x86asm.Decode(code, 64)
if err != nil {
t.Fatalf("Decode(% x): %v", code, err)
}
if inst.Op != x86asm.VPXOR {
t.Fatalf("decoded %s, want VPXOR", inst.Op)
}
// vpxor xmm7, xmm7, xmm7 → C5 C1 EF FF (2-byte VEX, L=0).
if code[0] != 0xC5 {
t.Errorf("expected 2-byte VEX (C5), got % x", code)
}
}
// TestVexRM validates the two-operand (reg=dst, rm=src, no vvvv) forms by
// round-tripping through the decoder.
func TestVexRM(t *testing.T) {
cases := []struct {
mnem string
ops []Operand
want x86asm.Op
}{
{"VPMOVSXWD", []Operand{Ptr(SI, 0, 16), vreg(t, "Y0")}, x86asm.VPMOVSXWD},
{"VPMOVSXDQ", []Operand{vreg(t, "X0"), vreg(t, "Y4")}, x86asm.VPMOVSXDQ},
{"VPMOVZXDQ", []Operand{vreg(t, "X4"), vreg(t, "Y4")}, x86asm.VPMOVZXDQ},
{"VPBROADCASTD", []Operand{vreg(t, "X0"), vreg(t, "Y15")}, x86asm.VPBROADCASTD},
{"VPMOVMSKB", []Operand{vreg(t, "X11"), AX}, x86asm.VPMOVMSKB},
{"VMOVMSKPS", []Operand{vreg(t, "Y7"), AX}, x86asm.VMOVMSKPS},
}
for _, c := range cases {
code, err := Encode(c.mnem, c.ops...)
if err != nil {
t.Errorf("%s: Encode: %v", c.mnem, err)
continue
}
inst, err := x86asm.Decode(code, 64)
if err != nil {
t.Errorf("%s: Decode(% x): %v", c.mnem, code, err)
continue
}
if inst.Op != c.want {
t.Errorf("%s: decoded as %s (% x)", c.mnem, inst.Op, code)
}
}
}
// TestVexShiftImm validates the immediate-shift form, checking the destination
// (VEX.vvvv) and source (ModRM.rm) land in the right places.
func TestVexShiftImm(t *testing.T) {
// VPSLLD $1, Y3, Y4 → vpslld ymm4, ymm3, 1.
code, err := Encode("VPSLLD", Imm(1), vreg(t, "Y3"), vreg(t, "Y4"))
if err != nil {
t.Fatalf("Encode: %v", err)
}
inst, err := x86asm.Decode(code, 64)
if err != nil {
t.Fatalf("Decode(% x): %v", code, err)
}
if inst.Op != x86asm.VPSLLD {
t.Fatalf("decoded %s, want VPSLLD (% x)", inst.Op, code)
}
// Intel order: dst, src, imm → "vpslld ymm4, ymm3, 0x1".
if got := x86asm.IntelSyntax(inst, 0, nil); got != "vpslld ymm4, ymm3, 0x1" {
t.Errorf("VPSLLD syntax = %q, want \"vpslld ymm4, ymm3, 0x1\" (% x)", got, code)
}
// VPSRAD $31, Y3, Y3 → vpsrad ymm3, ymm3, 31.
code, err = Encode("VPSRAD", Imm(31), vreg(t, "Y3"), vreg(t, "Y3"))
if err != nil {
t.Fatalf("Encode VPSRAD: %v", err)
}
inst, err = x86asm.Decode(code, 64)
if err != nil || inst.Op != x86asm.VPSRAD {
t.Fatalf("VPSRAD decoded %v (err %v), want VPSRAD", inst.Op, err)
}
}
// TestVexGroundTruth checks byte-for-byte agreement with the real Go
// assembler. The expected bytes were extracted from the machine code the Go
// toolchain produced for exactly these instructions (go build + a .text
// section dump of the resulting binary), never from a disassembler's
// rendering. This locks the v̄vvv = 1111 rule for unused vvvv fields (a
// value the hardware rejects with #UD and the x86 decoder silently ignores)
// as well as every new operand form.
func TestVexGroundTruth(t *testing.T) {
cases := []struct {
name string
mnem string
ops []Operand
want string
}{
// Three-operand NDS form.
{"VPADDQ Y8,Y9,Y8", "VPADDQ", []Operand{vreg(t, "Y8"), vreg(t, "Y9"), vreg(t, "Y8")}, "c44135d4c0"},
{"VPADDQ X9,X8,X8", "VPADDQ", []Operand{vreg(t, "X9"), vreg(t, "X8"), vreg(t, "X8")}, "c44139d4c1"},
{"VPXOR X7,X7,X7", "VPXOR", []Operand{vreg(t, "X7"), vreg(t, "X7"), vreg(t, "X7")}, "c5c1efff"},
{"VPSHUFB Y1,Y2,Y3", "VPSHUFB", []Operand{vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c4e26d00d9"},
{"VPMULLD Y1,Y2,Y3", "VPMULLD", []Operand{vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c4e26d40d9"},
{"VPUNPCKLDQ Y4,Y3,Y5", "VPUNPCKLDQ", []Operand{vreg(t, "Y4"), vreg(t, "Y3"), vreg(t, "Y5")}, "c5e562ec"},
{"VPERMD Y1,Y2,Y3", "VPERMD", []Operand{vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c4e26d36d9"},
// Floating point (packed and scalar) and FMA — same NDS form, the pp
// bits and map select the operation.
{"VADDPD Y9,Y8,Y8", "VADDPD", []Operand{vreg(t, "Y9"), vreg(t, "Y8"), vreg(t, "Y8")}, "c4413d58c1"},
{"VADDPD X1,X2,X3", "VADDPD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5e958d9"},
{"VMULPD Y12,Y12,Y12", "VMULPD", []Operand{vreg(t, "Y12"), vreg(t, "Y12"), vreg(t, "Y12")}, "c4411d59e4"},
{"VXORPD Y8,Y8,Y8", "VXORPD", []Operand{vreg(t, "Y8"), vreg(t, "Y8"), vreg(t, "Y8")}, "c4413d57c0"},
{"VUNPCKHPD X8,X8,X9", "VUNPCKHPD", []Operand{vreg(t, "X8"), vreg(t, "X8"), vreg(t, "X9")}, "c4413915c8"},
{"VADDSD X9,X8,X8", "VADDSD", []Operand{vreg(t, "X9"), vreg(t, "X8"), vreg(t, "X8")}, "c4413b58c1"},
{"VMULSD X0,X1,X1", "VMULSD", []Operand{vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X1")}, "c5f359c8"},
{"VFMADD231PD Y14,Y12,Y8", "VFMADD231PD", []Operand{vreg(t, "Y14"), vreg(t, "Y12"), vreg(t, "Y8")}, "c4429db8c6"},
{"VFMADD231PD (DI),Y12,Y8", "VFMADD231PD", []Operand{Ptr(DI, 0, 32), vreg(t, "Y12"), vreg(t, "Y8")}, "c4629db807"},
// Two-operand reg/rm form (v̄vvv must be 1111).
{"VPMOVSXDQ X0,Y4", "VPMOVSXDQ", []Operand{vreg(t, "X0"), vreg(t, "Y4")}, "c4e27d25e0"},
{"VPMOVSXWD (SI),Y0", "VPMOVSXWD", []Operand{Ptr(SI, 0, 8), vreg(t, "Y0")}, "c4e27d2306"},
{"VPBROADCASTD X0,Y15", "VPBROADCASTD", []Operand{vreg(t, "X0"), vreg(t, "Y15")}, "c4627d58f8"},
{"VCVTDQ2PD X12,Y12", "VCVTDQ2PD", []Operand{vreg(t, "X12"), vreg(t, "Y12")}, "c4417ee6e4"},
{"VCVTDQ2PD (SI),Y4", "VCVTDQ2PD", []Operand{Ptr(SI, 0, 16), vreg(t, "Y4")}, "c5fee626"},
{"VPMOVMSKB X11,AX", "VPMOVMSKB", []Operand{vreg(t, "X11"), AX}, "c4c179d7c3"},
{"VMOVMSKPS Y7,AX", "VMOVMSKPS", []Operand{vreg(t, "Y7"), AX}, "c5fc50c7"},
// Immediate shifts.
{"VPSLLD $1,Y3,Y4", "VPSLLD", []Operand{Imm(1), vreg(t, "Y3"), vreg(t, "Y4")}, "c5dd72f301"},
{"VPSRLQ $2,Y5,Y6", "VPSRLQ", []Operand{Imm(2), vreg(t, "Y5"), vreg(t, "Y6")}, "c5cd73d502"},
// Variable-count shifts: the count lives in an XMM register or memory
// and the instruction takes the NDS form.
{"VPSRLQ X0,Y8,Y8", "VPSRLQ", []Operand{vreg(t, "X0"), vreg(t, "Y8"), vreg(t, "Y8")}, "c53dd3c0"},
{"VPSRLQ (AX),Y8,Y8", "VPSRLQ", []Operand{Ptr(AX, 0, 16), vreg(t, "Y8"), vreg(t, "Y8")}, "c53dd300"},
{"VPSLLD X0,Y1,Y2", "VPSLLD", []Operand{vreg(t, "X0"), vreg(t, "Y1"), vreg(t, "Y2")}, "c5f5f2d0"},
{"VPSRLD X0,Y1,Y2", "VPSRLD", []Operand{vreg(t, "X0"), vreg(t, "Y1"), vreg(t, "Y2")}, "c5f5d2d0"},
{"VPSRAD X0,Y1,Y2", "VPSRAD", []Operand{vreg(t, "X0"), vreg(t, "Y1"), vreg(t, "Y2")}, "c5f5e2d0"},
{"VPSLLQ X0,Y1,Y2", "VPSLLQ", []Operand{vreg(t, "X0"), vreg(t, "Y1"), vreg(t, "Y2")}, "c5f5f3d0"},
// Immediate shuffle (reg=dst, rm=src, imm8).
{"VPSHUFD $0xEE,X8,X9", "VPSHUFD", []Operand{Imm(0xEE), vreg(t, "X8"), vreg(t, "X9")}, "c4417970c8ee"},
{"VPSHUFD $0xEE,Y1,Y2", "VPSHUFD", []Operand{Imm(0xEE), vreg(t, "Y1"), vreg(t, "Y2")}, "c5fd70d1ee"},
{"VPERMQ $0x1B,Y1,Y2", "VPERMQ", []Operand{Imm(0x1B), vreg(t, "Y1"), vreg(t, "Y2")}, "c4e3fd00d11b"},
{"VPERMQ $0x1B,Y11,Y12", "VPERMQ", []Operand{Imm(0x1B), vreg(t, "Y11"), vreg(t, "Y12")}, "c443fd00e31b"},
// Three-operand + immediate (reg=dst, vvvv=src1, rm=src2, imm8).
{"VSHUFPD $1,X1,X2,X3", "VSHUFPD", []Operand{Imm(1), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5e9c6d901"},
{"VSHUFPD $1,Y1,Y2,Y3", "VSHUFPD", []Operand{Imm(1), vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c5edc6d901"},
{"VPERM2I128 $0x31,Y1,Y2,Y3", "VPERM2I128", []Operand{Imm(0x31), vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c4e36d46d931"},
{"VINSERTI128 $1,X5,Y1,Y2", "VINSERTI128", []Operand{Imm(1), vreg(t, "X5"), vreg(t, "Y1"), vreg(t, "Y2")}, "c4e37538d501"},
// Lane extract (reg=YMM source, rm=XMM/memory destination, imm8).
{"VEXTRACTI128 $1,Y8,X9", "VEXTRACTI128", []Operand{Imm(1), vreg(t, "Y8"), vreg(t, "X9")}, "c4437d39c101"},
{"VEXTRACTI128 $1,Y8,(DI)", "VEXTRACTI128", []Operand{Imm(1), vreg(t, "Y8"), Ptr(DI, 0, 16)}, "c4637d390701"},
{"VEXTRACTF128 $1,Y8,X9", "VEXTRACTF128", []Operand{Imm(1), vreg(t, "Y8"), vreg(t, "X9")}, "c4437d19c101"},
// Moves — each direction picks its own opcode and VEX.W.
{"VMOVDQU (SI),Y1", "VMOVDQU", []Operand{Ptr(SI, 0, 32), vreg(t, "Y1")}, "c5fe6f0e"},
{"VMOVDQU Y3,(DI)", "VMOVDQU", []Operand{vreg(t, "Y3"), Ptr(DI, 0, 32)}, "c5fe7f1f"},
{"VMOVDQU X1,X2", "VMOVDQU", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "c5fa7fca"},
{"VMOVUPD (DI),Y14", "VMOVUPD", []Operand{Ptr(DI, 0, 32), vreg(t, "Y14")}, "c57d1037"},
{"VMOVUPD Y14,(DI)", "VMOVUPD", []Operand{vreg(t, "Y14"), Ptr(DI, 0, 32)}, "c57d1137"},
{"VMOVUPD X1,X2", "VMOVUPD", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "c5f911ca"},
{"VMOVQ X8,AX", "VMOVQ", []Operand{vreg(t, "X8"), AX}, "c461f97ec0"},
{"VMOVQ AX,X9", "VMOVQ", []Operand{AX, vreg(t, "X9")}, "c461f96ec8"},
{"VMOVQ X8,(DI)", "VMOVQ", []Operand{vreg(t, "X8"), Ptr(DI, 0, 8)}, "c461f97e07"},
{"VMOVQ (SI),X9", "VMOVQ", []Operand{Ptr(SI, 0, 8), vreg(t, "X9")}, "c461f96e0e"},
{"VMOVQ X8,X2", "VMOVQ", []Operand{vreg(t, "X8"), vreg(t, "X2")}, "c579d6c2"},
{"VMOVQ X2,X8", "VMOVQ", []Operand{vreg(t, "X2"), vreg(t, "X8")}, "c4c179d6d0"},
{"VMOVD X0,(SI)", "VMOVD", []Operand{vreg(t, "X0"), Ptr(SI, 0, 4)}, "c5f97e06"},
{"VMOVD AX,X0", "VMOVD", []Operand{AX, vreg(t, "X0")}, "c5f96ec0"},
{"VMOVSD (SI),X8", "VMOVSD", []Operand{Ptr(SI, 0, 8), vreg(t, "X8")}, "c57b1006"},
{"VMOVSD X8,(SI)", "VMOVSD", []Operand{vreg(t, "X8"), Ptr(SI, 0, 8)}, "c57b1106"},
// No-operand.
{"VZEROUPPER", "VZEROUPPER", nil, "c5f877"},
}
for _, c := range cases {
code, err := Encode(c.mnem, c.ops...)
if err != nil {
t.Errorf("%s: Encode: %v", c.name, err)
continue
}
if got := strings.ReplaceAll(hexBytes(code), " ", ""); got != c.want {
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
continue
}
inst, err := x86asm.Decode(code, 64)
if err != nil {
t.Errorf("%s: Decode(% x): %v", c.name, code, err)
continue
}
if inst.Len != len(code) {
t.Errorf("%s: Decode consumed %d of %d bytes", c.name, inst.Len, len(code))
}
if inst.Op.String() != c.mnem {
t.Errorf("%s: decoded as %s", c.name, inst.Op.String())
}
}
}
// TestVexNewFormsSyntax checks the decoded Intel-syntax rendering of the new
// SIMD forms (operand order is the decoder's, confirming the fields landed).
func TestVexNewFormsSyntax(t *testing.T) {
checkSyntax(t, "vpshufd xmm9, xmm8, 0xee", "VPSHUFD", Imm(0xEE), vreg(t, "X8"), vreg(t, "X9"))
checkSyntax(t, "vpermq ymm2, ymm1, 0x1b", "VPERMQ", Imm(0x1B), vreg(t, "Y1"), vreg(t, "Y2"))
checkSyntax(t, "vextracti128 xmm9, ymm8, 0x1", "VEXTRACTI128", Imm(1), vreg(t, "Y8"), vreg(t, "X9"))
checkSyntax(t, "vinserti128 ymm2, ymm1, xmm5, 0x1", "VINSERTI128", Imm(1), vreg(t, "X5"), vreg(t, "Y1"), vreg(t, "Y2"))
checkSyntax(t, "vperm2i128 ymm3, ymm2, ymm1, 0x31", "VPERM2I128", Imm(0x31), vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3"))
checkSyntax(t, "vpermd ymm3, ymm2, ymm1", "VPERMD", vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3"))
checkSyntax(t, "vshufpd xmm3, xmm2, xmm1, 0x1", "VSHUFPD", Imm(1), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3"))
checkSyntax(t, "vmovq rax, xmm8", "VMOVQ", vreg(t, "X8"), AX)
checkSyntax(t, "vmovq xmm9, rax", "VMOVQ", AX, vreg(t, "X9"))
checkSyntax(t, "vmovdqu ymm1, ymmword ptr [rsi]", "VMOVDQU", Ptr(SI, 0, 32), vreg(t, "Y1"))
checkSyntax(t, "vmovdqu ymmword ptr [rdi], ymm3", "VMOVDQU", vreg(t, "Y3"), Ptr(DI, 0, 32))
checkSyntax(t, "vzeroupper", "VZEROUPPER")
}
// TestVexMemoryForms round-trips the new forms with memory sources/destinations,
// covering the SIB/indexed path through the VEX prefix emitter.
func TestVexMemoryForms(t *testing.T) {
checkSyntax(t, "vpshufd ymm1, ymmword ptr [rsi], 0x4e", "VPSHUFD", Imm(0x4E), Ptr(SI, 0, 32), vreg(t, "Y1"))
checkSyntax(t, "vinserti128 ymm2, ymm1, xmmword ptr [rdi], 0x1", "VINSERTI128", Imm(1), Ptr(DI, 0, 16), vreg(t, "Y1"), vreg(t, "Y2"))
checkSyntax(t, "vmovdqu ymm1, ymmword ptr [rax+4*rbx]", "VMOVDQU", Idx(AX, BX, 4, 0, 32), vreg(t, "Y1"))
checkSyntax(t, "vpermq ymm2, ymmword ptr [rsi], 0x1b", "VPERMQ", Imm(0x1B), Ptr(SI, 0, 32), vreg(t, "Y2"))
checkSyntax(t, "vfmadd231pd ymm8, ymm12, ymm14", "VFMADD231PD", vreg(t, "Y14"), vreg(t, "Y12"), vreg(t, "Y8"))
checkSyntax(t, "vcvtdq2pd ymm12, xmmword ptr [rsi]", "VCVTDQ2PD", Ptr(SI, 0, 16), vreg(t, "Y12"))
// The top and bottom of the accepted imm8 span: $255 and $-1 both encode
// an all-bits-set control.
checkSyntax(t, "vpshufd xmm1, xmm0, 0xff", "VPSHUFD", Imm(255), vreg(t, "X0"), vreg(t, "X1"))
checkSyntax(t, "vpshufd xmm1, xmm0, 0xff", "VPSHUFD", Imm(-1), vreg(t, "X0"), vreg(t, "X1"))
}
// TestVexErrors checks that invalid operand shapes are rejected.
func TestVexErrors(t *testing.T) {
cases := []struct {
name string
mnem string
ops []Operand
}{
{"VPSHUFD arity", "VPSHUFD", []Operand{vreg(t, "X0"), vreg(t, "X1")}},
{"VPSHUFD non-imm control", "VPSHUFD", []Operand{vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}},
{"VPSHUFD gpr dst", "VPSHUFD", []Operand{Imm(1), vreg(t, "X0"), AX}},
{"VEXTRACTI128 arity", "VEXTRACTI128", []Operand{Imm(1), vreg(t, "Y0")}},
{"VEXTRACTI128 non-vec src", "VEXTRACTI128", []Operand{Imm(1), AX, vreg(t, "X0")}},
{"VINSERTI128 arity", "VINSERTI128", []Operand{Imm(1), vreg(t, "X0"), vreg(t, "Y1")}},
{"VINSERTI128 non-vec vvvv", "VINSERTI128", []Operand{Imm(1), vreg(t, "X0"), AX, vreg(t, "Y1")}},
{"VPERM2I128 non-imm control", "VPERM2I128", []Operand{AX, vreg(t, "Y0"), vreg(t, "Y1"), vreg(t, "Y2")}},
{"VMOVSD reg-reg", "VMOVSD", []Operand{vreg(t, "X1"), vreg(t, "X2")}},
{"VMOVD reg-reg", "VMOVD", []Operand{vreg(t, "X1"), vreg(t, "X2")}},
{"VMOVQ ymm", "VMOVQ", []Operand{vreg(t, "Y1"), AX}},
{"VMOVQ mixed X/Y", "VMOVQ", []Operand{vreg(t, "X1"), vreg(t, "Y2")}},
{"VMOVQ no vector", "VMOVQ", []Operand{AX, BX}},
{"VMOVDQU gpr", "VMOVDQU", []Operand{AX, vreg(t, "Y1")}},
{"VMOVUPD gpr", "VMOVUPD", []Operand{vreg(t, "X1"), AX}},
{"VZEROUPPER operands", "VZEROUPPER", []Operand{AX}},
{"VPSLLD non-vec dst", "VPSLLD", []Operand{Imm(1), vreg(t, "Y0"), AX}},
{"VPSLLD non-imm count", "VPSLLD", []Operand{AX, vreg(t, "Y0"), vreg(t, "Y1")}},
{"VPSLLD non-vec src", "VPSLLD", []Operand{Imm(1), AX, vreg(t, "Y1")}},
{"VPADDD non-vec vvvv", "VPADDD", []Operand{vreg(t, "Y0"), AX, vreg(t, "Y1")}},
{"VEXTRACTI128 non-imm lane", "VEXTRACTI128", []Operand{AX, vreg(t, "Y0"), vreg(t, "X0")}},
{"VMOVQ imm operand", "VMOVQ", []Operand{Imm(1), vreg(t, "X0")}},
{"VPSHUFD imm rm", "VPSHUFD", []Operand{Imm(1), Imm(2), vreg(t, "X0")}},
{"VPSHUFD imm range", "VPSHUFD", []Operand{Imm(256), vreg(t, "X0"), vreg(t, "X1")}},
{"VPERMQ imm range", "VPERMQ", []Operand{Imm(300), vreg(t, "Y0"), vreg(t, "Y1")}},
{"VEXTRACTI128 imm range", "VEXTRACTI128", []Operand{Imm(256), vreg(t, "Y0"), vreg(t, "X0")}},
{"VPSLLD imm range", "VPSLLD", []Operand{Imm(-129), vreg(t, "Y0"), vreg(t, "Y1")}},
}
for _, c := range cases {
if _, err := Encode(c.mnem, c.ops...); err == nil {
t.Errorf("%s: expected an error, got none", c.name)
}
}
}