feat(asm): add VEX shuffle, extract, permute, move and FP forms

Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
2026-07-07 13:57:53 +02:00
parent d5a4a6de45
commit 39870f91f6
9 changed files with 590 additions and 36 deletions
+184 -2
View File
@@ -4,6 +4,7 @@
package asm
import (
"strings"
"testing"
"golang.org/x/arch/x86/x86asm"
@@ -26,7 +27,12 @@ func TestVexNDS3(t *testing.T) {
if spec.form != vexNDS3 {
continue
}
code, err := Encode(mnem, vreg(t, "Y0"), vreg(t, "Y1"), vreg(t, "Y2"))
// 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
@@ -72,7 +78,7 @@ func TestVexXMM(t *testing.T) {
if inst.Op != x86asm.VPXOR {
t.Fatalf("decoded %s, want VPXOR", inst.Op)
}
// vpxor xmm7, xmm7, xmm7 → C5 C9 EF FF (2-byte VEX, L=0).
// 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)
}
@@ -140,3 +146,179 @@ func TestVexShiftImm(t *testing.T) {
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"},
// 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)
}
}
}