122 lines
4.3 KiB
Go
122 lines
4.3 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 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)
|
||
|
|
}
|
||
|
|
}
|