2026-07-06 09:49:50 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package asm
|
|
|
|
|
|
|
|
|
|
import (
|
2026-07-07 13:57:53 +02:00
|
|
|
"strings"
|
2026-07-06 09:49:50 +02:00
|
|
|
"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
|
|
|
|
|
}
|
2026-07-07 13:57:53 +02:00
|
|
|
// 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"))
|
2026-07-06 09:49:50 +02:00
|
|
|
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)
|
|
|
|
|
}
|
2026-07-07 13:57:53 +02:00
|
|
|
// vpxor xmm7, xmm7, xmm7 → C5 C1 EF FF (2-byte VEX, L=0).
|
2026-07-06 09:49:50 +02:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-07 13:57:53 +02:00
|
|
|
|
|
|
|
|
// 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 {
|
2026-07-15 17:13:28 +02:00
|
|
|
name string
|
|
|
|
|
mnem string
|
|
|
|
|
ops []Operand
|
|
|
|
|
want string
|
|
|
|
|
wantOp string // decoded mnemonic, when it differs from mnem (the X/Y spellings)
|
2026-07-07 13:57:53 +02:00
|
|
|
}{
|
|
|
|
|
// Three-operand NDS form.
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
2026-07-07 13:57:53 +02:00
|
|
|
// Floating point (packed and scalar) and FMA — same NDS form, the pp
|
|
|
|
|
// bits and map select the operation.
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
2026-07-07 13:57:53 +02:00
|
|
|
// Two-operand reg/rm form (v̄vvv must be 1111).
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
2026-07-07 13:57:53 +02:00
|
|
|
// Immediate shifts.
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
2026-07-08 12:51:35 +02:00
|
|
|
// Variable-count shifts: the count lives in an XMM register or memory
|
|
|
|
|
// and the instruction takes the NDS form.
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
2026-07-07 13:57:53 +02:00
|
|
|
// Immediate shuffle (reg=dst, rm=src, imm8).
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
2026-07-07 13:57:53 +02:00
|
|
|
// Three-operand + immediate (reg=dst, vvvv=src1, rm=src2, imm8).
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
2026-07-07 13:57:53 +02:00
|
|
|
// Lane extract (reg=YMM source, rm=XMM/memory destination, imm8).
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
2026-07-07 13:57:53 +02:00
|
|
|
// Moves — each direction picks its own opcode and VEX.W.
|
2026-07-15 17:13:28 +02:00
|
|
|
{"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", ""},
|
|
|
|
|
// Packed double arithmetic and unpack — the NDS form, the opcode
|
|
|
|
|
// selects the operation.
|
|
|
|
|
{"VSUBPD Y1,Y2,Y3", "VSUBPD", []Operand{vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c5ed5cd9", ""},
|
|
|
|
|
{"VDIVPD X1,X2,X3", "VDIVPD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5e95ed9", ""},
|
|
|
|
|
{"VMINPD Y1,Y2,Y3", "VMINPD", []Operand{vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c5ed5dd9", ""},
|
|
|
|
|
{"VMAXPD X4,X5,X6", "VMAXPD", []Operand{vreg(t, "X4"), vreg(t, "X5"), vreg(t, "X6")}, "c5d15ff4", ""},
|
|
|
|
|
{"VUNPCKLPD X1,X2,X3", "VUNPCKLPD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5e914d9", ""},
|
|
|
|
|
{"VUNPCKLPD Y1,Y2,Y3", "VUNPCKLPD", []Operand{vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c5ed14d9", ""},
|
|
|
|
|
{"VSUBPD (AX),X1,X2", "VSUBPD", []Operand{Ptr(AX, 0, 16), vreg(t, "X1"), vreg(t, "X2")}, "c5f15c10", ""},
|
|
|
|
|
// Scalar double and single arithmetic (F2 / F3 pp, 128-bit only).
|
|
|
|
|
{"VSUBSD X1,X2,X3", "VSUBSD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5eb5cd9", ""},
|
|
|
|
|
{"VDIVSD X7,X1,X2", "VDIVSD", []Operand{vreg(t, "X7"), vreg(t, "X1"), vreg(t, "X2")}, "c5f35ed7", ""},
|
|
|
|
|
{"VMINSD X1,X2,X3", "VMINSD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5eb5dd9", ""},
|
|
|
|
|
{"VMAXSD X3,X4,X5", "VMAXSD", []Operand{vreg(t, "X3"), vreg(t, "X4"), vreg(t, "X5")}, "c5db5feb", ""},
|
|
|
|
|
{"VADDSS X1,X2,X3", "VADDSS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5ea58d9", ""},
|
|
|
|
|
{"VSUBSS X1,X2,X3", "VSUBSS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5ea5cd9", ""},
|
|
|
|
|
{"VMULSS X9,X10,X11", "VMULSS", []Operand{vreg(t, "X9"), vreg(t, "X10"), vreg(t, "X11")}, "c4412a59d9", ""},
|
|
|
|
|
{"VDIVSS X1,X2,X3", "VDIVSS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5ea5ed9", ""},
|
|
|
|
|
{"VMINSS X6,X7,X8", "VMINSS", []Operand{vreg(t, "X6"), vreg(t, "X7"), vreg(t, "X8")}, "c5425dc6", ""},
|
|
|
|
|
{"VMAXSS X1,X2,X3", "VMAXSS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5ea5fd9", ""},
|
|
|
|
|
{"VADDSD 8(AX),X1,X2", "VADDSD", []Operand{Ptr(AX, 8, 8), vreg(t, "X1"), vreg(t, "X2")}, "c5f3585008", ""},
|
|
|
|
|
// VMOVDDUP — duplicate the low double (reg=dst, rm=src, F2 pp).
|
|
|
|
|
{"VMOVDDUP X1,X2", "VMOVDDUP", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "c5fb12d1", ""},
|
|
|
|
|
{"VMOVDDUP Y1,Y2", "VMOVDDUP", []Operand{vreg(t, "Y1"), vreg(t, "Y2")}, "c5ff12d1", ""},
|
|
|
|
|
{"VMOVDDUP 8(AX),X1", "VMOVDDUP", []Operand{Ptr(AX, 8, 8), vreg(t, "X1")}, "c5fb124808", ""},
|
|
|
|
|
// Conversions: DQ→PS (no prefix), PS→PD (Go emits it without the F3
|
|
|
|
|
// prefix — see the table comment), DQ→PD.
|
|
|
|
|
{"VCVTDQ2PS X1,X2", "VCVTDQ2PS", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "c5f85bd1", ""},
|
|
|
|
|
{"VCVTDQ2PS Y3,Y4", "VCVTDQ2PS", []Operand{vreg(t, "Y3"), vreg(t, "Y4")}, "c5fc5be3", ""},
|
|
|
|
|
{"VCVTPS2PD X1,X2", "VCVTPS2PD", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "c5f85ad1", ""},
|
|
|
|
|
{"VCVTPS2PD X1,Y2", "VCVTPS2PD", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "c5fc5ad1", ""},
|
|
|
|
|
// PD→DQ conversions: the X/Y spellings fix the source length and the
|
|
|
|
|
// destination is always XMM; the decoder reports the base mnemonic.
|
|
|
|
|
{"VCVTPD2DQX X1,X2", "VCVTPD2DQX", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "c5fbe6d1", "VCVTPD2DQ"},
|
|
|
|
|
{"VCVTPD2DQY Y1,X2", "VCVTPD2DQY", []Operand{vreg(t, "Y1"), vreg(t, "X2")}, "c5ffe6d1", "VCVTPD2DQ"},
|
|
|
|
|
{"VCVTTPD2DQX X3,X4", "VCVTTPD2DQX", []Operand{vreg(t, "X3"), vreg(t, "X4")}, "c5f9e6e3", "VCVTTPD2DQ"},
|
|
|
|
|
{"VCVTTPD2DQY Y5,X6", "VCVTTPD2DQY", []Operand{vreg(t, "Y5"), vreg(t, "X6")}, "c5fde6f5", "VCVTTPD2DQ"},
|
|
|
|
|
{"VCVTPD2DQY (AX),X1", "VCVTPD2DQY", []Operand{Ptr(AX, 0, 32), vreg(t, "X1")}, "c5ffe608", "VCVTPD2DQ"},
|
2026-07-07 13:57:53 +02:00
|
|
|
// No-operand.
|
2026-07-15 17:13:28 +02:00
|
|
|
{"VZEROUPPER", "VZEROUPPER", nil, "c5f877", ""},
|
2026-07-07 13:57:53 +02:00
|
|
|
}
|
|
|
|
|
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))
|
|
|
|
|
}
|
2026-07-15 17:13:28 +02:00
|
|
|
wantOp := c.wantOp
|
|
|
|
|
if wantOp == "" {
|
|
|
|
|
wantOp = c.mnem
|
|
|
|
|
}
|
|
|
|
|
if inst.Op.String() != wantOp {
|
2026-07-07 13:57:53 +02:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|