// Copyright (c) 2026 Petr Balvín (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 } // x86asmUnrecognised lists the VEX mnemonics whose machine code the // golang.org/x/arch decoder cannot resolve; their bytes are verified against // go tool asm in the ground-truth tests instead. var x86asmUnrecognised = map[string]bool{ "ANDNL": true, "ANDNQ": true, "MULXL": true, "MULXQ": true, "RORXL": true, "RORXQ": true, "VFMADD213SD": true, "VFNMADD231SD": true, } // 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 } // The x86 decoder's table lacks a handful of rows the Go assembler // emits (the scalar 213/231 FMA spellings among them); those are // pinned byte for byte against go tool asm in TestVexGroundTruth // instead of round-tripped here. inst, err := x86asm.Decode(code, 64) if err != nil { if strings.Contains(err.Error(), "unrecognized instruction") && x86asmUnrecognised[mnem] { continue } t.Errorf("%s: Decode(% x): %v", mnem, err, code) continue } // The decoder folds the Plan 9 L/Q GPR-width spellings (VCVTSI2SDL/ // SDQ, SSL/SSQ) onto the base name; the W bit carries the width. got := inst.Op.String() if got != mnem && !(len(mnem) > len(got) && mnem[:len(got)] == got) { t.Errorf("%s: decoded as %s (% x)", mnem, got, 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 wantOp string // decoded mnemonic, when it differs from mnem (the X/Y spellings) }{ // 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", ""}, {"VFMADD213SD X0,X1,X2", "VFMADD213SD", []Operand{vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "c4e2f1a9d0", ""}, {"VFNMADD231SD X0,X1,X2", "VFNMADD231SD", []Operand{vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "c4e2f1bdd0", ""}, // Packed single XOR and byte compare (NDS form). {"VXORPS Y0,Y1,Y2", "VXORPS", []Operand{vreg(t, "Y0"), vreg(t, "Y1"), vreg(t, "Y2")}, "c5f457d0", ""}, {"VPCMPEQB Y0,Y1,Y2", "VPCMPEQB", []Operand{vreg(t, "Y0"), vreg(t, "Y1"), vreg(t, "Y2")}, "c5f574d0", ""}, // Octa byte shifts (vvvv carries the destination). {"VPSLLDQ $2,X0,X1", "VPSLLDQ", []Operand{Imm(2), vreg(t, "X0"), vreg(t, "X1")}, "c5f173f802", ""}, {"VPSRLDQ $2,Y0,Y1", "VPSRLDQ", []Operand{Imm(2), vreg(t, "Y0"), vreg(t, "Y1")}, "c5f573d802", ""}, // Two-source shuffle, blend and carry-less multiply (NDS + imm8). {"VPERM2F128 $3,Y0,Y1,Y2", "VPERM2F128", []Operand{Imm(3), vreg(t, "Y0"), vreg(t, "Y1"), vreg(t, "Y2")}, "c4e37506d003", ""}, {"VPBLENDD $3,X0,X1,X2", "VPBLENDD", []Operand{Imm(3), vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "c4e37102d003", ""}, {"VPBLENDD $3,Y0,Y1,Y2", "VPBLENDD", []Operand{Imm(3), vreg(t, "Y0"), vreg(t, "Y1"), vreg(t, "Y2")}, "c4e37502d003", ""}, {"VPCLMULQDQ $0,X0,X1,X2", "VPCLMULQDQ", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "c4e37144d000", ""}, {"VGF2P8AFFINEQB $0,X0,X1,X2", "VGF2P8AFFINEQB", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "c4e3f1ced000", ""}, // Two-operand test and the non-temporal and broadcast stores. {"VPTEST X0,X1", "VPTEST", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "c4e27917c8", ""}, {"VPTEST Y0,Y1", "VPTEST", []Operand{vreg(t, "Y0"), vreg(t, "Y1")}, "c4e27d17c8", ""}, {"VMOVNTDQ Y0,(AX)", "VMOVNTDQ", []Operand{vreg(t, "Y0"), Ptr(AX, 0, 32)}, "c5fde700", ""}, {"VMOVNTDQ X0,(AX)", "VMOVNTDQ", []Operand{vreg(t, "X0"), Ptr(AX, 0, 16)}, "c5f9e700", ""}, {"VBROADCASTI128 (AX),Y1", "VBROADCASTI128", []Operand{Ptr(AX, 0, 16), vreg(t, "Y1")}, "c4e27d5a08", ""}, // Aligned integer move and the full zeroing form. {"VMOVDQA X0,X1", "VMOVDQA", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "c5f97fc1", ""}, {"VMOVDQA (AX),X1", "VMOVDQA", []Operand{Ptr(AX, 0, 16), vreg(t, "X1")}, "c5f96f08", ""}, {"VMOVDQA Y0,Y1", "VMOVDQA", []Operand{vreg(t, "Y0"), vreg(t, "Y1")}, "c5fd7fc1", ""}, {"VZEROALL", "VZEROALL", []Operand{}, "c5fc77", ""}, // BMI1/BMI2 general-register VEX forms. {"ANDNL AX,BX,CX", "ANDNL", []Operand{AX, BX, CX}, "c4e260f2c8", ""}, {"ANDNQ AX,BX,CX", "ANDNQ", []Operand{AX, BX, CX}, "c4e2e0f2c8", ""}, {"MULXL AX,BX,CX", "MULXL", []Operand{AX, BX, CX}, "c4e263f6c8", ""}, {"MULXQ AX,BX,CX", "MULXQ", []Operand{AX, BX, CX}, "c4e2e3f6c8", ""}, {"RORXL $3,AX,CX", "RORXL", []Operand{Imm(3), AX, CX}, "c4e37bf0c803", ""}, {"RORXQ $3,AX,CX", "RORXQ", []Operand{Imm(3), AX, CX}, "c4e3fbf0c803", ""}, // 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", ""}, // 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"}, // 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 { // The decoder's AVX/BMI table lacks a few rows the Go // assembler emits (the GPR VEX forms and the scalar FMA // spellings); their bytes are the ground truth here. if x86asmUnrecognised[c.mnem] { continue } 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)) } wantOp := c.wantOp if wantOp == "" { wantOp = c.mnem } if inst.Op.String() != wantOp { 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) } } }