270 lines
9.7 KiB
Go
270 lines
9.7 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package asm
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
|
)
|
|
|
|
// TestAmd64ExtensionRegistry checks the mnemonic lookup for the amd64 layer:
|
|
// one mnemonic across several vector lengths or W bits resolves to every
|
|
// entry, the lookup is case-insensitive, and the counts match the registered
|
|
// families.
|
|
func TestAmd64ExtensionRegistry(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
mnem string
|
|
forms int
|
|
}{
|
|
{"VCVTNE2PS2BF16", 3},
|
|
{"VCVTNEPS2BF16", 3},
|
|
{"VDPBF16PS", 3},
|
|
{"VP2INTERSECTD", 3},
|
|
{"VP2INTERSECTQ", 3},
|
|
{"VMOVSH", 3},
|
|
{"VMOVW", 4},
|
|
{"VADDSH", 1},
|
|
{"VSQRTSH", 1},
|
|
{"VCOMISH", 1},
|
|
{"VCVTSH2SS", 1},
|
|
{"VCVTSI2SH", 2},
|
|
{"VCVTSH2SI", 2},
|
|
{"VADDPH", 3},
|
|
{"VSQRTPH", 3},
|
|
{"VSCALEFSH", 1},
|
|
{"VGETEXPSH", 1},
|
|
{"VCMPSH", 1},
|
|
{"VGETMANTSH", 1},
|
|
{"VREDUCESH", 1},
|
|
{"VRNDSCALESH", 1},
|
|
{"VCVTPH2W", 3},
|
|
{"VCVTPH2UW", 3},
|
|
{"VCVTW2PH", 3},
|
|
{"VCVTUW2PH", 3},
|
|
{"VCVTPH2DQ", 3},
|
|
{"VCVTPH2UDQ", 3},
|
|
{"VCVTDQ2PH", 3},
|
|
{"VCVTUDQ2PH", 3},
|
|
{"VCVTPH2QQ", 3},
|
|
{"VCVTPH2UQQ", 3},
|
|
{"VCVTQQ2PH", 3},
|
|
{"VCVTUQQ2PH", 3},
|
|
{"VCVTPH2PD", 3},
|
|
{"VCVTPD2PH", 3},
|
|
{"VRNDSCALEPH", 3},
|
|
{"VREDUCEPH", 3},
|
|
{"VGETMANTPH", 3},
|
|
{"VFMADD132PH", 3},
|
|
{"VFMADD213PH", 3},
|
|
{"VFMADD231PH", 3},
|
|
{"VFMSUB132PH", 3},
|
|
{"VFMSUB213PH", 3},
|
|
{"VFMSUB231PH", 3},
|
|
{"VFMADDSUB132PH", 3},
|
|
{"VFMADDSUB213PH", 3},
|
|
{"VFMADDSUB231PH", 3},
|
|
{"VFMSUBADD132PH", 3},
|
|
{"VFMSUBADD213PH", 3},
|
|
{"VFMSUBADD231PH", 3},
|
|
{"VFMADD132SH", 1},
|
|
{"VFMADD213SH", 1},
|
|
{"VFMADD231SH", 1},
|
|
{"VFMSUB132SH", 1},
|
|
{"VFMSUB213SH", 1},
|
|
{"VFMSUB231SH", 1},
|
|
{"VFMULCPH", 3},
|
|
{"VFCMULCPH", 3},
|
|
{"VFMULCSH", 1},
|
|
{"VFCMULCSH", 1},
|
|
{"VFMADDCPH", 3},
|
|
{"VFCMADDCPH", 3},
|
|
{"VFMADDCSH", 1},
|
|
{"VFCMADDCSH", 1},
|
|
{"VMINMAXPH", 3},
|
|
{"VMINMAXSH", 1},
|
|
{"VPDPWSUD", 2},
|
|
{"VPDPWSUDS", 2},
|
|
{"VPDPWUSD", 2},
|
|
{"VPDPWUSDS", 2},
|
|
} {
|
|
cands, ok := LookupExtension(arch.AMD64, tt.mnem)
|
|
if !ok {
|
|
t.Fatalf("LookupExtension(AMD64, %s) found nothing", tt.mnem)
|
|
}
|
|
if len(cands) != tt.forms {
|
|
t.Errorf("%s registers %d forms, want %d", tt.mnem, len(cands), tt.forms)
|
|
}
|
|
lower, ok := LookupExtension(arch.AMD64, strings.ToLower(tt.mnem))
|
|
if !ok || len(lower) != tt.forms {
|
|
t.Errorf("the %s lookup is not case-insensitive", tt.mnem)
|
|
}
|
|
}
|
|
if got := arch.Extensions(arch.AMD64); len(got) != 191 {
|
|
t.Errorf("the amd64 layer registers %d instructions, want 191", len(got))
|
|
}
|
|
if _, ok := LookupExtension(arch.AMD64, "NOSUCHINSTR"); ok {
|
|
t.Error("a non-extended mnemonic resolved")
|
|
}
|
|
// VPOPCNTD and VPOPCNTQ are toolchain instructions today: they stay in
|
|
// the generated table and out of the extension layer.
|
|
if _, ok := LookupExtension(arch.AMD64, "VPOPCNTD"); ok {
|
|
t.Error("VPOPCNTD is an extension, want it in the generated table alone")
|
|
}
|
|
}
|
|
|
|
// TestAmd64ExtensionAboveGeneratedTable pins the layering twice over: no
|
|
// registered mnemonic sits in the generated amd64 table, and the encoder
|
|
// mirror asm.Encodable answers false for every one of them, so the layer
|
|
// stays out of the main encoders by test and not by promise.
|
|
func TestAmd64ExtensionAboveGeneratedTable(t *testing.T) {
|
|
for _, mnem := range ExtensionNames(arch.AMD64) {
|
|
if _, found := arch.ForArch(arch.AMD64).Lookup(mnem); found {
|
|
t.Errorf("%s leaked into the generated amd64 table", mnem)
|
|
}
|
|
if Encodable(mnem) {
|
|
t.Errorf("%s is encodable through the main encoder, the layer is not sealed", mnem)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEncodeExtensionAmd64 encodes through the registry and pins the same
|
|
// golden words the arch table tests pin, proving the registry resolves to the
|
|
// right encoding.
|
|
func TestEncodeExtensionAmd64(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
name string
|
|
mnem string
|
|
ops []arch.ExtOperand
|
|
want string
|
|
}{
|
|
{"bf16 convert", "VCVTNE2PS2BF16",
|
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtZmm(6)},
|
|
"62f2574872f4"},
|
|
{"bf16 narrow convert", "VCVTNEPS2BF16",
|
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtYmm(6)},
|
|
"62f27e4872f5"},
|
|
{"dot product", "VDPBF16PS",
|
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtXmm(6)},
|
|
"62f2560852f4"},
|
|
{"intersect into a mask", "VP2INTERSECTD",
|
|
[]arch.ExtOperand{arch.ExtYmm(2), arch.ExtYmm(1), arch.ExtMask(2)},
|
|
"62f26f2868d1"},
|
|
{"scalar fp16 add, high registers", "VADDSH",
|
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtXmm(30)},
|
|
"6205160058f4"},
|
|
{"scalar compare", "VCOMISH",
|
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtXmm(30)},
|
|
"62057c082ff5"},
|
|
{"integer into a scalar fp16", "VCVTSI2SH",
|
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtGpr64(12), arch.ExtXmm(30)},
|
|
"624596002af4"},
|
|
{"scalar fp16 into an integer", "VCVTSH2SI",
|
|
[]arch.ExtOperand{arch.ExtXmm(30), arch.ExtGpr32(2)},
|
|
"62957e082dd6"},
|
|
{"word move into an xmm", "VMOVW",
|
|
[]arch.ExtOperand{arch.ExtGpr64(12), arch.ExtXmm(30)},
|
|
"62457d086ef4"},
|
|
{"scalar compare into a mask", "VCMPSH",
|
|
[]arch.ExtOperand{arch.ExtImmediate(0x7b), arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtMask(5)},
|
|
"62931600c2ec7b"},
|
|
{"mantissa extract with a control byte", "VGETMANTSH",
|
|
[]arch.ExtOperand{arch.ExtImmediate(0x0b), arch.ExtXmm(29), arch.ExtXmm(28), arch.ExtXmm(30)},
|
|
"6203140027f40b"},
|
|
{"packed fp16 add under embedded rounding", "VADDPH",
|
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundTruncate)},
|
|
"62f5547858f4"},
|
|
{"scalar fp16 minimum with exceptions suppressed", "VMINSH",
|
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtRounded(arch.ExtXmm(6), arch.ExtRoundSAE)},
|
|
"62f556185df4"},
|
|
{"fp16 to signed words", "VCVTPH2W",
|
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(6)},
|
|
"62f57d487df5"},
|
|
{"dwords to fp16 over a broadcast source", "VCVTDQ2PH",
|
|
[]arch.ExtOperand{arch.ExtBroadcast(9, 0), arch.ExtYmm(30)},
|
|
"62457c585b31"},
|
|
{"fp16 to signed qwords, rounded", "VCVTPH2QQ",
|
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundTruncate)},
|
|
"62f57d787bf5"},
|
|
{"fp16 scalar out of memory into an integer", "VCVTSH2SI",
|
|
[]arch.ExtOperand{arch.ExtMemory(9, 0), arch.ExtGpr64(12)},
|
|
"6255fe082d21"},
|
|
{"fp16 to double-precision, widened", "VCVTPH2PD",
|
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtZmm(6)},
|
|
"62f57c485af5"},
|
|
{"packed fp16 rounding to fraction bits", "VRNDSCALEPH",
|
|
[]arch.ExtOperand{arch.ExtImmediate(0x7b), arch.ExtZmm(5), arch.ExtZmm(6)},
|
|
"62f37c4808f57b"},
|
|
{"packed fused multiply-add, high registers", "VFMADD132PH",
|
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
|
"6206154098f4"},
|
|
{"fused multiply-add out of a broadcast source", "VFMADD231PH",
|
|
[]arch.ExtOperand{arch.ExtYmm(5), arch.ExtBroadcast(1, 0), arch.ExtYmm(6)},
|
|
"62f65538b831"},
|
|
{"scalar multiply-subtract under embedded rounding", "VFMSUB231SH",
|
|
[]arch.ExtOperand{arch.ExtXmm(5), arch.ExtXmm(4), arch.ExtRounded(arch.ExtXmm(6), arch.ExtRoundTruncate)},
|
|
"62f65578bbf4"},
|
|
{"complex multiply, conjugating the first source", "VFCMULCPH",
|
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
|
"62061740d6f4"},
|
|
{"complex multiply-add, conjugating the second source", "VFMADDCPH",
|
|
[]arch.ExtOperand{arch.ExtZmm(29), arch.ExtZmm(28), arch.ExtZmm(30)},
|
|
"6206164056f4"},
|
|
{"complex multiply-add, conjugated first source, rounded", "VFCMADDCPH",
|
|
[]arch.ExtOperand{arch.ExtZmm(5), arch.ExtZmm(4), arch.ExtRounded(arch.ExtZmm(6), arch.ExtRoundNearest)},
|
|
"62f6571856f4"},
|
|
{"scalar complex multiply-add out of memory", "VFMADDCSH",
|
|
[]arch.ExtOperand{arch.ExtXmm(29), arch.ExtMemory(9, 0), arch.ExtXmm(30)},
|
|
"624616005731"},
|
|
{"minimum or maximum under a control byte", "VMINMAXPH",
|
|
[]arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtZmm(29), arch.ExtMemory(9, 0), arch.ExtZmm(30)},
|
|
"62431440523188"},
|
|
{"scalar minimum or maximum out of memory", "VMINMAXSH",
|
|
[]arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtXmm(28), arch.ExtMemory(9, 0), arch.ExtXmm(29)},
|
|
"62431c00532988"},
|
|
{"vnni dot product through the VEX word", "VPDPWSUD",
|
|
[]arch.ExtOperand{arch.ExtXmm(2), arch.ExtXmm(1), arch.ExtXmm(3)},
|
|
"c4e26ad2d9"},
|
|
{"saturating dot product, high registers", "VPDPWUSDS",
|
|
[]arch.ExtOperand{arch.ExtYmm(10), arch.ExtYmm(15), arch.ExtYmm(8)},
|
|
"c4422dd3c7"},
|
|
{"dot product out of memory", "VPDPWSUD",
|
|
[]arch.ExtOperand{arch.ExtXmm(2), arch.ExtMemory(1, 127), arch.ExtXmm(1)},
|
|
"c4e26ad2497f"},
|
|
} {
|
|
got, err := EncodeExtension(arch.AMD64, tt.mnem, tt.ops...)
|
|
if err != nil {
|
|
t.Errorf("%s: encode: %v", tt.name, err)
|
|
continue
|
|
}
|
|
if hex.EncodeToString(got) != tt.want {
|
|
t.Errorf("%s:\n got %x\n want %s", tt.name, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEncodeExtensionAmd64Errors checks the registry's diagnostics on the
|
|
// amd64 side: a wrong arity names the form's count and a mis-classed operand
|
|
// surfaces the entry's own message.
|
|
func TestEncodeExtensionAmd64Errors(t *testing.T) {
|
|
if _, err := EncodeExtension(arch.AMD64, "VP2INTERSECTD", arch.ExtZmm(1)); err == nil {
|
|
t.Error("one operand encoded, want an arity error")
|
|
} else if !strings.Contains(err.Error(), "3 operands") {
|
|
t.Errorf("arity error %q does not name the count", err)
|
|
}
|
|
_, err := EncodeExtension(arch.AMD64, "VCVTNEPS2BF16", arch.ExtZmm(1), arch.ExtZmm(2))
|
|
if err == nil {
|
|
t.Fatal("a ZMM destination encoded on the narrow convert, want an error")
|
|
}
|
|
if !strings.Contains(err.Error(), "YMM register") {
|
|
t.Errorf("error %q does not name the YMM destination", err)
|
|
}
|
|
if _, err := EncodeExtension(arch.AMD64, "VCVTNE2PS2BF16"); err == nil ||
|
|
!strings.Contains(err.Error(), "takes 3 operands, got 0") {
|
|
t.Errorf("zero-operand error = %v, want the operand-count diagnostic", err)
|
|
}
|
|
}
|