356 lines
19 KiB
Go
356 lines
19 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
||
package asm
|
||
|
||
import (
|
||
"os"
|
||
"strings"
|
||
"testing"
|
||
|
||
"golang.org/x/arch/x86/x86asm"
|
||
|
||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||
)
|
||
|
||
// TestEvexGroundTruth checks the EVEX (AVX-512) encodings byte for byte
|
||
// against machine code extracted from the Go toolchain's assembly of the
|
||
// same instructions, covering every operand shape the go-flac AVX-512
|
||
// kernels use: NDS arithmetic, immediate and variable shifts, shuffles with
|
||
// an immediate, lane extracts, narrowing stores, broadcasts from a GPR or
|
||
// memory, mask destinations, mask moves, disp8×N compression and the 5-bit
|
||
// register fields (X/Y 16–31, Z 0–31).
|
||
func TestEvexGroundTruth(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
mnem string
|
||
ops []Operand
|
||
want string
|
||
}{
|
||
// NDS integer arithmetic / logic.
|
||
{"VPXORD Z12,Z12,Z12", "VPXORD", []Operand{vreg(t, "Z12"), vreg(t, "Z12"), vreg(t, "Z12")}, "62511d48efe4"},
|
||
{"VPXORQ Z8,Z9,Z10", "VPXORQ", []Operand{vreg(t, "Z8"), vreg(t, "Z9"), vreg(t, "Z10")}, "6251b548efd0"},
|
||
{"VPADDD Z1,Z0,Z0", "VPADDD", []Operand{vreg(t, "Z1"), vreg(t, "Z0"), vreg(t, "Z0")}, "62f17d48fec1"},
|
||
{"VPSUBQ Z8,Z11,Z11", "VPSUBQ", []Operand{vreg(t, "Z8"), vreg(t, "Z11"), vreg(t, "Z11")}, "6251a548fbd8"},
|
||
{"VPUNPCKLDQ Z5,Z3,Z6", "VPUNPCKLDQ", []Operand{vreg(t, "Z5"), vreg(t, "Z3"), vreg(t, "Z6")}, "62f1654862f5"},
|
||
{"VPUNPCKHDQ Z5,Z3,Z7", "VPUNPCKHDQ", []Operand{vreg(t, "Z5"), vreg(t, "Z3"), vreg(t, "Z7")}, "62f165486afd"},
|
||
{"VPMULLQ Z9,Z10,Z10", "VPMULLQ", []Operand{vreg(t, "Z9"), vreg(t, "Z10"), vreg(t, "Z10")}, "6252ad4840d1"},
|
||
{"VPMULLD Z13,Z11,Z2", "VPMULLD", []Operand{vreg(t, "Z13"), vreg(t, "Z11"), vreg(t, "Z2")}, "62d2254840d5"},
|
||
{"VPERMD Z0,Z15,Z8", "VPERMD", []Operand{vreg(t, "Z0"), vreg(t, "Z15"), vreg(t, "Z8")}, "6272054836c0"},
|
||
// Packed-double arithmetic (EVEX forms carry W=1).
|
||
{"VADDPD Z11,Z10,Z10", "VADDPD", []Operand{vreg(t, "Z11"), vreg(t, "Z10"), vreg(t, "Z10")}, "6251ad4858d3"},
|
||
{"VMULPD Z13,Z12,Z12", "VMULPD", []Operand{vreg(t, "Z13"), vreg(t, "Z12"), vreg(t, "Z12")}, "62519d4859e5"},
|
||
{"VFMADD231PD Z14,Z12,Z10", "VFMADD231PD", []Operand{vreg(t, "Z14"), vreg(t, "Z12"), vreg(t, "Z10")}, "62529d48b8d6"},
|
||
// Align (NDS + imm8).
|
||
{"VALIGND $12,Z12,Z0,Z1", "VALIGND", []Operand{Imm(12), vreg(t, "Z12"), vreg(t, "Z0"), vreg(t, "Z1")}, "62d37d4803cc0c"},
|
||
{"VALIGND $15,Z9,Z0,Z1", "VALIGND", []Operand{Imm(15), vreg(t, "Z9"), vreg(t, "Z0"), vreg(t, "Z1")}, "62d37d4803c90f"},
|
||
// Shifts: immediate (/digit) and variable (XMM count).
|
||
{"VPSRAD $31,Z3,Z5", "VPSRAD", []Operand{Imm(31), vreg(t, "Z3"), vreg(t, "Z5")}, "62f1554872e31f"},
|
||
{"VPSLLD $1,Z3,Z4", "VPSLLD", []Operand{Imm(1), vreg(t, "Z3"), vreg(t, "Z4")}, "62f15d4872f301"},
|
||
{"VPSRAQ X31,Z8,Z8", "VPSRAQ", []Operand{vreg(t, "X31"), vreg(t, "Z8"), vreg(t, "Z8")}, "6211bd48e2c7"},
|
||
// Mask destinations (the K register occupies the reg field).
|
||
{"VPCMPEQD Z0,Z3,K1", "VPCMPEQD", []Operand{vreg(t, "Z0"), vreg(t, "Z3"), vreg(t, "K1")}, "62f1654876c8"},
|
||
{"VPCMPEQD Y30,Y11,K1", "VPCMPEQD", []Operand{vreg(t, "Y30"), vreg(t, "Y11"), vreg(t, "K1")}, "6291252876ce"},
|
||
// Mask moves and test (VEX-encoded).
|
||
{"KMOVW K1,CX", "KMOVW", []Operand{vreg(t, "K1"), CX}, "c5f893c9"},
|
||
{"KMOVW K1,R12", "KMOVW", []Operand{vreg(t, "K1"), vreg(t, "R12")}, "c57893e1"},
|
||
{"KTESTW K1,K1", "KTESTW", []Operand{vreg(t, "K1"), vreg(t, "K1")}, "c5f899c9"},
|
||
// Moves, incl. disp8×N (64 for a 512-bit operand).
|
||
{"VMOVDQU32 (SI)(R15*4),Z3", "VMOVDQU32", []Operand{Idx(SI, vreg(t, "R15"), 4, 0, 64), vreg(t, "Z3")}, "62b17e486f1cbe"},
|
||
{"VMOVDQU32 4(SI)(AX*1),Z4", "VMOVDQU32", []Operand{Idx(SI, AX, 1, 4, 64), vreg(t, "Z4")}, "62f17e486fa40604000000"},
|
||
{"VMOVDQU32 16(SI)(R15*4),Z4", "VMOVDQU32", []Operand{Idx(SI, vreg(t, "R15"), 4, 16, 64), vreg(t, "Z4")}, "62b17e486fa4be10000000"},
|
||
{"VMOVDQU32 Z0,4(SI)(AX*1)", "VMOVDQU32", []Operand{vreg(t, "Z0"), Idx(SI, AX, 1, 4, 64)}, "62f17e487f840604000000"},
|
||
{"VMOVDQU32 Z3,(DI)(R15*4)", "VMOVDQU32", []Operand{vreg(t, "Z3"), Idx(DI, vreg(t, "R15"), 4, 0, 64)}, "62b17e487f1cbf"},
|
||
// VMOVDQU64 — the W1 qword variant.
|
||
{"VMOVDQU64 (SI)(R15*4),Z3", "VMOVDQU64", []Operand{Idx(SI, vreg(t, "R15"), 4, 0, 64), vreg(t, "Z3")}, "62b1fe486f1cbe"},
|
||
{"VMOVDQU64 Z0,4(SI)(AX*1)", "VMOVDQU64", []Operand{vreg(t, "Z0"), Idx(SI, AX, 1, 4, 64)}, "62f1fe487f840604000000"},
|
||
{"VMOVDQU64 Z1,Z2", "VMOVDQU64", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f1fe487fca"},
|
||
// The wider AVX-512 F/BW integer set.
|
||
{"VPADDB Z1,Z2,Z3", "VPADDB", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d48fcd9"},
|
||
{"VPSUBW Z1,Z2,Z3", "VPSUBW", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d48f9d9"},
|
||
{"VPANDQ Z1,Z2,Z3", "VPANDQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f1ed48dbd9"},
|
||
{"VPANDND Z1,Z2,Z3", "VPANDND", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d48dfd9"},
|
||
{"VPMULLW Z1,Z2,Z3", "VPMULLW", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d48d5d9"},
|
||
{"VPMINUB Z1,Z2,Z3", "VPMINUB", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d48dad9"},
|
||
{"VPMAXUQ Z1,Z2,Z3", "VPMAXUQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f2ed483fd9"},
|
||
{"VPAVGW Z1,Z2,Z3", "VPAVGW", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d48e3d9"},
|
||
{"VPSLLVQ Z3,Z1,Z2", "VPSLLVQ", []Operand{vreg(t, "Z3"), vreg(t, "Z1"), vreg(t, "Z2")}, "62f2f54847d3"},
|
||
{"VPSRAVQ Z3,Z1,Z2", "VPSRAVQ", []Operand{vreg(t, "Z3"), vreg(t, "Z1"), vreg(t, "Z2")}, "62f2f54846d3"},
|
||
{"VPSHUFD $0x1B,Z1,Z2", "VPSHUFD", []Operand{Imm(0x1B), vreg(t, "Z1"), vreg(t, "Z2")}, "62f17d4870d11b"},
|
||
{"VPSHUFB Z1,Z2,Z3", "VPSHUFB", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f26d4800d9"},
|
||
{"VMOVDQU8 Z1,Z2", "VMOVDQU8", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17f487fca"},
|
||
{"VMOVDQU16 Z1,Z2", "VMOVDQU16", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f1ff487fca"},
|
||
// Indices 16–31: rm[4] rides in X̄ for register operands.
|
||
{"VPSHUFD $1,X16,X17", "VPSHUFD", []Operand{Imm(1), vreg(t, "X16"), vreg(t, "X17")}, "62a17d0870c801"},
|
||
{"VMOVUPD (DI),Z14", "VMOVUPD", []Operand{Ptr(DI, 0, 64), vreg(t, "Z14")}, "6271fd481037"},
|
||
{"VMOVUPD 64(DI),Z14", "VMOVUPD", []Operand{Ptr(DI, 64, 64), vreg(t, "Z14")}, "6271fd48107701"},
|
||
// Conversions and narrowing stores (reg = wide source).
|
||
{"VCVTQQ2PD Z12,Z12", "VCVTQQ2PD", []Operand{vreg(t, "Z12"), vreg(t, "Z12")}, "6251fe48e6e4"},
|
||
{"VCVTQQ2PD X13,X13", "VCVTQQ2PD", []Operand{vreg(t, "X13"), vreg(t, "X13")}, "6251fe08e6ed"},
|
||
{"VPMOVSXDQ 32(SI),Z12", "VPMOVSXDQ", []Operand{Ptr(SI, 32, 32), vreg(t, "Z12")}, "62727d48256601"},
|
||
{"VPMOVDW Z0,Y0", "VPMOVDW", []Operand{vreg(t, "Z0"), vreg(t, "Y0")}, "62f27e4833c0"},
|
||
{"VPMOVQD Z11,Y11", "VPMOVQD", []Operand{vreg(t, "Z11"), vreg(t, "Y11")}, "62527e4835db"},
|
||
// Lane extracts.
|
||
{"VEXTRACTI64X4 $1,Z8,Y9", "VEXTRACTI64X4", []Operand{Imm(1), vreg(t, "Z8"), vreg(t, "Y9")}, "6253fd483bc101"},
|
||
{"VEXTRACTF64X4 $1,Z10,Y11", "VEXTRACTF64X4", []Operand{Imm(1), vreg(t, "Z10"), vreg(t, "Y11")}, "6253fd481bd301"},
|
||
// Broadcasts: GPR source (0x7C) vs memory source (0x58/0x59, disp8×4/8).
|
||
{"VPBROADCASTD AX,Z15", "VPBROADCASTD", []Operand{AX, vreg(t, "Z15")}, "62727d487cf8"},
|
||
{"VPBROADCASTD (SI),Z8", "VPBROADCASTD", []Operand{Ptr(SI, 0, 4), vreg(t, "Z8")}, "62727d485806"},
|
||
{"VPBROADCASTD 4(SI),Z10", "VPBROADCASTD", []Operand{Ptr(SI, 4, 4), vreg(t, "Z10")}, "62727d48585601"},
|
||
{"VPBROADCASTQ R8,X31", "VPBROADCASTQ", []Operand{vreg(t, "R8"), vreg(t, "X31")}, "6242fd087cf8"},
|
||
{"VPBROADCASTQ AX,Z9", "VPBROADCASTQ", []Operand{AX, vreg(t, "Z9")}, "6272fd487cc8"},
|
||
// Register indices 16–31 exist only in EVEX encodings.
|
||
{"VPBROADCASTD AX,Y30", "VPBROADCASTD", []Operand{AX, vreg(t, "Y30")}, "62627d287cf0"},
|
||
// Packed double arithmetic / unpack (EVEX forms carry W=1).
|
||
{"VSUBPD Z1,Z2,Z3", "VSUBPD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f1ed485cd9"},
|
||
{"VDIVPD Z4,Z5,Z6", "VDIVPD", []Operand{vreg(t, "Z4"), vreg(t, "Z5"), vreg(t, "Z6")}, "62f1d5485ef4"},
|
||
{"VMINPD Z7,Z8,Z9", "VMINPD", []Operand{vreg(t, "Z7"), vreg(t, "Z8"), vreg(t, "Z9")}, "6271bd485dcf"},
|
||
{"VMAXPD Z10,Z11,Z12", "VMAXPD", []Operand{vreg(t, "Z10"), vreg(t, "Z11"), vreg(t, "Z12")}, "6251a5485fe2"},
|
||
{"VUNPCKLPD Z1,Z2,Z3", "VUNPCKLPD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f1ed4814d9"},
|
||
{"VUNPCKHPD Z1,Z2,Z3", "VUNPCKHPD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f1ed4815d9"},
|
||
{"VSUBPD 64(AX),Z1,Z2", "VSUBPD", []Operand{Ptr(AX, 64, 64), vreg(t, "Z1"), vreg(t, "Z2")}, "62f1f5485c5001"},
|
||
{"VSUBPD Z17,Z18,Z19", "VSUBPD", []Operand{vreg(t, "Z17"), vreg(t, "Z18"), vreg(t, "Z19")}, "62a1ed405cd9"},
|
||
// VMOVDDUP — duplicate the low double; disp8×N = 64 at 512 bits, and
|
||
// X16/X17 force EVEX (the mod=11 rm[4] extension rides in X̄).
|
||
{"VMOVDDUP Z1,Z2", "VMOVDDUP", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f1ff4812d1"},
|
||
{"VMOVDDUP 64(AX),Z1", "VMOVDDUP", []Operand{Ptr(AX, 64, 64), vreg(t, "Z1")}, "62f1ff48124801"},
|
||
{"VMOVDDUP X16,X17", "VMOVDDUP", []Operand{vreg(t, "X16"), vreg(t, "X17")}, "62a1ff0812c8"},
|
||
// Conversions: DQ→PS, PS→PD (pp = 00, the Go assembler's choice),
|
||
// DQ→PD (the destination sets the length).
|
||
{"VCVTDQ2PS Z1,Z2", "VCVTDQ2PS", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17c485bd1"},
|
||
{"VCVTPS2PD Y1,Z2", "VCVTPS2PD", []Operand{vreg(t, "Y1"), vreg(t, "Z2")}, "62f17c485ad1"},
|
||
{"VCVTPS2PD 32(AX),Z2", "VCVTPS2PD", []Operand{Ptr(AX, 32, 32), vreg(t, "Z2")}, "62f17c485a5001"},
|
||
{"VCVTDQ2PD Y1,Z2", "VCVTDQ2PD", []Operand{vreg(t, "Y1"), vreg(t, "Z2")}, "62f17e48e6d1"},
|
||
// PD→DQ conversions: the source is the wide operand and fixes the
|
||
// length (ZMM source → L'L = 10 even with an XMM destination; a
|
||
// memory source takes the length the mnemonic's spelling implies).
|
||
{"VCVTPD2DQ Z1,Y2", "VCVTPD2DQ", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f1ff48e6d1"},
|
||
{"VCVTPD2DQ 64(AX),Y2", "VCVTPD2DQ", []Operand{Ptr(AX, 64, 64), vreg(t, "Y2")}, "62f1ff48e65001"},
|
||
{"VCVTTPD2DQ Z3,Y4", "VCVTTPD2DQ", []Operand{vreg(t, "Z3"), vreg(t, "Y4")}, "62f1fd48e6e3"},
|
||
}
|
||
for _, c := range cases {
|
||
want := strings.ReplaceAll(c.want, " ", "")
|
||
code, err := Encode(c.mnem, c.ops...)
|
||
if err != nil {
|
||
t.Errorf("%s: Encode: %v", c.name, err)
|
||
continue
|
||
}
|
||
if got := hexCompact(code); got != want {
|
||
t.Errorf("%s: bytes %s, want %s", c.name, got, 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())
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestEvexMasking checks the AVX-512 mask operand (K1–K7, placed freely among
|
||
// the operands) and the .Z zeroing suffix, byte for byte against the Go
|
||
// assembler.
|
||
func TestEvexMasking(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
mnem string
|
||
ops []Operand
|
||
want string
|
||
}{
|
||
// Masked arithmetic: K anywhere among the operands; .Z sets the z bit.
|
||
{"VPADDD.Z merging+zeroing", "VPADDD.Z", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K2"), vreg(t, "Z3")}, "62f16dcafed9"},
|
||
{"VPADDD merging", "VPADDD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K1"), vreg(t, "Z3")}, "62f16d49fed9"},
|
||
{"VADDPD.Z", "VADDPD.Z", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K2"), vreg(t, "Z3")}, "62f1edca58d9"},
|
||
{"VPMINSD.Z", "VPMINSD.Z", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K5"), vreg(t, "Z3")}, "62f26dcd39d9"},
|
||
{"VPMINSQ.Z", "VPMINSQ.Z", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K5"), vreg(t, "Z3")}, "62f2edcd39d9"},
|
||
// Masked immediate shift (K before the destination).
|
||
{"VPSRAD.Z", "VPSRAD.Z", []Operand{Imm(1), vreg(t, "Z2"), vreg(t, "K1"), vreg(t, "Z3")}, "62f165c972e201"},
|
||
{"VPSLLD merge", "VPSLLD", []Operand{Imm(4), vreg(t, "Z1"), vreg(t, "K2"), vreg(t, "Z3")}, "62f1654a72f104"},
|
||
// Masked align.
|
||
{"VALIGND", "VALIGND", []Operand{Imm(12), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K3"), vreg(t, "Z4")}, "62f36d4b03e10c"},
|
||
// Masked conversion and extract.
|
||
{"VCVTQQ2PD.Z", "VCVTQQ2PD.Z", []Operand{vreg(t, "Z1"), vreg(t, "K2"), vreg(t, "Z3")}, "62f1fecae6d9"},
|
||
{"VEXTRACTI64X4", "VEXTRACTI64X4", []Operand{Imm(1), vreg(t, "Z1"), vreg(t, "K2"), vreg(t, "Y3")}, "62f3fd4a3bcb01"},
|
||
// Masked moves: K sits between the register and memory operands.
|
||
{"VMOVDQU8 store", "VMOVDQU8", []Operand{vreg(t, "Z1"), vreg(t, "K3"), Ptr(SI, 0, 64)}, "62f17f4b7f0e"},
|
||
{"VMOVDQU32 load", "VMOVDQU32", []Operand{Ptr(SI, 0, 64), vreg(t, "K4"), vreg(t, "Z1")}, "62f17e4c6f0e"},
|
||
{"VMOVDQU32 store", "VMOVDQU32", []Operand{vreg(t, "Z1"), vreg(t, "K4"), Ptr(DI, 0, 64)}, "62f17e4c7f0f"},
|
||
// Masked comparison with a K destination: dst K1, mask K2.
|
||
{"VPCMPEQD k-dst+mask", "VPCMPEQD", []Operand{vreg(t, "Z0"), vreg(t, "Z3"), vreg(t, "K2"), vreg(t, "K1")}, "62f1654a76c8"},
|
||
// Masked floating point: packed double, the scalar SD/SS forms (which
|
||
// exist under EVEX only for masked and zeroing use) and conversions.
|
||
{"VSUBPD.Z", "VSUBPD.Z", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K3"), vreg(t, "Z4")}, "62f1edcb5ce1"},
|
||
{"VADDSD merge", "VADDSD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "K3"), vreg(t, "X4")}, "62f1ef0b58e1"},
|
||
{"VSUBSD.Z", "VSUBSD.Z", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "K5"), vreg(t, "X3")}, "62f1ef8d5cd9"},
|
||
{"VADDSS merge", "VADDSS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "K1"), vreg(t, "X3")}, "62f16e0958d9"},
|
||
{"VCVTPD2DQ merge", "VCVTPD2DQ", []Operand{vreg(t, "Z1"), vreg(t, "K2"), vreg(t, "Y3")}, "62f1ff4ae6d9"},
|
||
{"VCVTTPD2DQ.Z", "VCVTTPD2DQ.Z", []Operand{vreg(t, "Z1"), vreg(t, "K2"), vreg(t, "Y3")}, "62f1fdcae6d9"},
|
||
{"VCVTDQ2PS.Z", "VCVTDQ2PS.Z", []Operand{vreg(t, "Z1"), vreg(t, "K4"), vreg(t, "Z2")}, "62f17ccc5bd1"},
|
||
{"VCVTDQ2PD merge", "VCVTDQ2PD", []Operand{vreg(t, "X1"), vreg(t, "K2"), vreg(t, "X3")}, "62f17e0ae6d9"},
|
||
{"VCVTDQ2PD.Z", "VCVTDQ2PD.Z", []Operand{vreg(t, "Y1"), vreg(t, "K2"), vreg(t, "Z2")}, "62f17ecae6d1"},
|
||
{"VCVTPS2PD.Z", "VCVTPS2PD.Z", []Operand{vreg(t, "Y1"), vreg(t, "K3"), vreg(t, "Z2")}, "62f17ccb5ad1"},
|
||
}
|
||
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 := hexCompact(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
|
||
}
|
||
want := c.mnem
|
||
if i := len(want) - 2; i > 0 && want[i:] == ".Z" {
|
||
want = want[:i]
|
||
}
|
||
if inst.Op.String() != want {
|
||
t.Errorf("%s: decoded as %s", c.name, inst.Op.String())
|
||
}
|
||
}
|
||
|
||
// Error cases.
|
||
bad := []struct {
|
||
name string
|
||
mnem string
|
||
ops []Operand
|
||
}{
|
||
{"zeroing without mask", "VPADDD.Z", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}},
|
||
{"K0 mask", "VPADDD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K0"), vreg(t, "Z3")}},
|
||
{"two masks", "VPADDD", []Operand{vreg(t, "Z1"), vreg(t, "K1"), vreg(t, "K2"), vreg(t, "Z3")}},
|
||
{".Z on VEX-only", "VPSHUFD.Z", []Operand{Imm(1), vreg(t, "X0"), vreg(t, "X1")}},
|
||
{"unsupported suffix", "VPADDD.BCST", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}},
|
||
{"KMOVW.Z", "KMOVW.Z", []Operand{vreg(t, "K1"), vreg(t, "K2")}},
|
||
}
|
||
for _, c := range bad {
|
||
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
||
t.Errorf("%s: expected an error, got none", c.name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestEvexErrors checks the EVEX-specific error paths.
|
||
func TestEvexErrors(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
mnem string
|
||
ops []Operand
|
||
}{
|
||
{"NDS arity", "VPXORD", []Operand{vreg(t, "Z0"), vreg(t, "Z1")}},
|
||
{"KMOVW arity", "KMOVW", []Operand{vreg(t, "K1")}},
|
||
{"KMOVW no K", "KMOVW", []Operand{AX, CX}},
|
||
{"VMOVUPD Z gpr", "VMOVUPD", []Operand{AX, vreg(t, "Z1")}},
|
||
{"broadcast src", "VPBROADCASTD", []Operand{Imm(1), vreg(t, "Z1")}},
|
||
{"VPMOVDW src", "VPMOVDW", []Operand{AX, vreg(t, "Y0")}},
|
||
{"align arity", "VALIGND", []Operand{Imm(1), vreg(t, "Z0"), vreg(t, "Z1")}},
|
||
// VEX-only mnemonics reject registers only EVEX can encode.
|
||
{"VMOVMSKPS X16", "VMOVMSKPS", []Operand{vreg(t, "X16"), AX}},
|
||
}
|
||
for _, c := range cases {
|
||
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
||
t.Errorf("%s: expected an error, got none", c.name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestAssembleGoFlacAVX512Kernel assembles the whole production AVX-512
|
||
// kernel — all functions plus the file-global idx16 constant — and checks
|
||
// that the static-symbol load resolves to the right bytes in the image.
|
||
// Skipped when the sibling repository is not checked out.
|
||
func TestAssembleGoFlacAVX512Kernel(t *testing.T) {
|
||
path := "../../go-libraries/go-flac/avx512_amd64.s"
|
||
if _, err := os.Stat(path); err != nil {
|
||
t.Skip("go-libraries repository not present next to gasm-devkit")
|
||
}
|
||
src, err := os.ReadFile(path)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
f, errs := parser.Parse(path, string(src))
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFile(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFile: %v", err)
|
||
}
|
||
if len(img.Funcs) != 10 {
|
||
t.Errorf("functions = %d, want 10", len(img.Funcs))
|
||
}
|
||
|
||
// idx16 as the DATA directives define it: dwords 1..16.
|
||
idx := make([]byte, 0, 64)
|
||
for i := 1; i <= 16; i++ {
|
||
idx = append(idx, byte(i), 0, 0, 0)
|
||
}
|
||
image := img.Bytes()
|
||
base := img.Symbols["idx16"]
|
||
if base == 0 {
|
||
t.Fatal("idx16 not laid out")
|
||
}
|
||
if got := image[base : base+64]; hexCompact(got) != hexCompact(idx) {
|
||
t.Errorf("idx16 contents %x, want %x", got, idx)
|
||
}
|
||
|
||
// The VMOVDQU32 idx16(SB), Z13 load (62 71 7e 48 6f 2d + rel32) must
|
||
// resolve to idx16 within the image.
|
||
loads := 0
|
||
for _, fn := range img.Funcs {
|
||
code := img.Code[fn.Offset : fn.Offset+fn.Size]
|
||
pat := []byte{0x62, 0x71, 0x7e, 0x48, 0x6f, 0x2d}
|
||
for pos := 0; ; {
|
||
i := indexOf(code[pos:], pat)
|
||
if i < 0 {
|
||
break
|
||
}
|
||
i += pos
|
||
rel := int32(uint32(code[i+6]) | uint32(code[i+7])<<8 | uint32(code[i+8])<<16 | uint32(code[i+9])<<24)
|
||
target := fn.Offset + i + 10 + int(rel)
|
||
if target != base {
|
||
t.Errorf("%s: idx16 load at +%d targets 0x%x, want 0x%x", fn.Name, i, target, base)
|
||
}
|
||
loads++
|
||
pos = i + 10
|
||
}
|
||
}
|
||
if loads != 1 {
|
||
t.Errorf("idx16 loads found = %d, want 1", loads)
|
||
}
|
||
}
|
||
|
||
// hexCompact renders bytes as a lowercase hex string without separators.
|
||
func hexCompact(b []byte) string {
|
||
const hexdig = "0123456789abcdef"
|
||
out := make([]byte, len(b)*2)
|
||
for i, c := range b {
|
||
out[i*2] = hexdig[c>>4]
|
||
out[i*2+1] = hexdig[c&0xf]
|
||
}
|
||
return string(out)
|
||
}
|
||
|
||
// indexOf returns the index of the first occurrence of pat in b, or -1.
|
||
func indexOf(b, pat []byte) int {
|
||
for i := 0; i+len(pat) <= len(b); i++ {
|
||
j := 0
|
||
for j < len(pat) && b[i+j] == pat[j] {
|
||
j++
|
||
}
|
||
if j == len(pat) {
|
||
return i
|
||
}
|
||
}
|
||
return -1
|
||
}
|