Files
gasm-sdk/asm/extension_amd64_test.go
T

122 lines
4.3 KiB
Go
Raw Normal View History

// 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 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},
} {
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) != 15 {
t.Errorf("the amd64 layer registers %d instructions, want 15", 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"},
} {
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)
}
}