feat(lint): surface the amd64 extension layer's refusals
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
5ee60c860b
commit
fa50521619
5 files changed
+281
-6
No files matched your search
@@ -4,10 +4,12 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/asm"
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
)
|
||||
|
||||
// TestExtensionInstructionsAreKnown pins the lint side of the
|
||||
@@ -50,3 +52,189 @@ TEXT ·f(SB), NOSPLIT, $0
|
||||
t.Errorf("an unregistered mnemonic must fire unknown-instruction once: %+v", diags)
|
||||
}
|
||||
}
|
||||
|
||||
// amd64ExtKernel spells one register-form statement per distinct mnemonic the
|
||||
// amd64 extension layer registers, decorations beside them where the entries
|
||||
// carry them, and the memory, broadcast and scaled-index spellings at the
|
||||
// tail. TestAmd64ExtensionInstructionsAreKnown lints it; the completeness
|
||||
// guard inside fails the moment a registered mnemonic lacks its row, so a
|
||||
// family added to the registry lands here in the same change.
|
||||
const amd64ExtKernel = `#include "textflag.h"
|
||||
TEXT ·f(SB), NOSPLIT, $0
|
||||
VCVTNE2PS2BF16 Z1, Z2, Z3{K5}
|
||||
VCVTNEPS2BF16 Z1, Y2{K5}
|
||||
VDPBF16PS Z1, Z2, Z3{K5}
|
||||
VP2INTERSECTD Z1, Z2, K3
|
||||
VP2INTERSECTQ Z1, Z2, K3
|
||||
VMOVSH X1, X2, X3
|
||||
VMOVW R12, X1
|
||||
VADDSH X1, X2, X3{RZ-SAE}
|
||||
VSUBSH X1, X2, X3{RZ-SAE}
|
||||
VMULSH X1, X2, X3{RZ-SAE}
|
||||
VDIVSH X1, X2, X3{RZ-SAE}
|
||||
VMINSH X1, X2, X3{SAE}
|
||||
VMAXSH X1, X2, X3{SAE}
|
||||
VSQRTSH X1, X2, X3{RZ-SAE}
|
||||
VSCALEFSH X1, X2, X3{RZ-SAE}
|
||||
VGETEXPSH X1, X2, X3{SAE}
|
||||
VCOMISH X1, X2{SAE}
|
||||
VUCOMISH X1, X2{SAE}
|
||||
VCVTSS2SH X1, X2, X3{RZ-SAE}
|
||||
VCVTSH2SS X1, X2, X3
|
||||
VCVTSH2SD X1, X2, X3
|
||||
VCVTSD2SH X1, X2, X3{RZ-SAE}
|
||||
VCVTSI2SH X1, R12D, X2
|
||||
VCVTUSI2SH X1, R12D, X2
|
||||
VCVTSH2SI X1, R12D
|
||||
VCVTSH2USI X1, R12D
|
||||
VADDPH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VSUBPH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VMULPH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VDIVPH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VMINPH Z1, Z2, Z3{K5}{SAE}
|
||||
VMAXPH Z1, Z2, Z3{K5}{SAE}
|
||||
VSQRTPH Z1, Z2{K5}{RZ-SAE}
|
||||
VCMPSH $0x7b, X1, X2, K3
|
||||
VGETMANTSH $0x0b, X1, X2, X4
|
||||
VREDUCESH $0x7b, X1, X2, X4
|
||||
VRNDSCALESH $0x7b, X1, X2, X4
|
||||
VCVTPH2W Z1, Z2{K5}{RZ-SAE}
|
||||
VCVTPH2UW Z1, Z2{K5}{RZ-SAE}
|
||||
VCVTW2PH Z1, Z2{K5}{RZ-SAE}
|
||||
VCVTUW2PH Z1, Z2{K5}{RZ-SAE}
|
||||
VCVTPH2DQ Y1, Z2{K5}{RZ-SAE}
|
||||
VCVTPH2UDQ Y1, Z2{K5}{RZ-SAE}
|
||||
VCVTDQ2PH Z1, Y2{K5}{RZ-SAE}
|
||||
VCVTUDQ2PH Z1, Y2{K5}{RZ-SAE}
|
||||
VCVTPH2QQ X1, Z2{K5}{RZ-SAE}
|
||||
VCVTPH2UQQ X1, Z2{K5}{RZ-SAE}
|
||||
VCVTQQ2PH Z1, X2{K5}{RZ-SAE}
|
||||
VCVTUQQ2PH Z1, X2{K5}{RZ-SAE}
|
||||
VCVTPH2PD X1, Z2{K5}
|
||||
VCVTPD2PH Z1, X2{K5}{RZ-SAE}
|
||||
VRNDSCALEPH $0x7b, Z1, Z3{K5}
|
||||
VREDUCEPH $0x7b, Z1, Z3{K5}
|
||||
VGETMANTPH $0x0b, Z1, Z3{K5}
|
||||
VFMADD132PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMADD213PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMADD231PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMSUB132PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMSUB213PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMSUB231PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMADDSUB132PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMADDSUB213PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMADDSUB231PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMSUBADD132PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMSUBADD213PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMSUBADD231PH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMADD132SH X1, X2, X3{RZ-SAE}
|
||||
VFMADD213SH X1, X2, X3{RZ-SAE}
|
||||
VFMADD231SH X1, X2, X3{RZ-SAE}
|
||||
VFMSUB132SH X1, X2, X3{RZ-SAE}
|
||||
VFMSUB213SH X1, X2, X3{RZ-SAE}
|
||||
VFMSUB231SH X1, X2, X3{RZ-SAE}
|
||||
VFMULCPH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFCMULCPH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMULCSH X1, X2, X3{RZ-SAE}
|
||||
VFCMULCSH X1, X2, X3{RZ-SAE}
|
||||
VFMADDCPH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFCMADDCPH Z1, Z2, Z3{K5}{RZ-SAE}
|
||||
VFMADDCSH X1, X2, X3{RZ-SAE}
|
||||
VFCMADDCSH X1, X2, X3{RZ-SAE}
|
||||
VMINMAXPH $0x7b, Z1, Z2, Z4{K5}
|
||||
VMINMAXSH $0x7b, X1, X2, X4
|
||||
VPDPWSUD X1, X2, X3
|
||||
VPDPWSUDS X1, X2, X3
|
||||
VPDPWUSD X1, X2, X3
|
||||
VPDPWUSDS X1, X2, X3
|
||||
VMOVSH (R9), X30
|
||||
VMOVSH X30, (R9)
|
||||
VADDPH Z28, (R9), Z30{K7}{Z}
|
||||
VCVTDQ2PH (R9){1TO8}, Y30
|
||||
VADDPH Z29, (R9)(R12*2), Z30
|
||||
VPDPWSUD X2, 127(R9), X1
|
||||
RET
|
||||
`
|
||||
|
||||
// TestAmd64ExtensionInstructionsAreKnown pins the lint side of the amd64
|
||||
// extension layer: every distinct mnemonic the registry carries lints without
|
||||
// unknown-instruction, and the whole kernel, whose statements the encoder
|
||||
// accepts one and all, lints clean, because the wired layer is the acceptance
|
||||
// rather than a gap of the generated table.
|
||||
func TestAmd64ExtensionInstructionsAreKnown(t *testing.T) {
|
||||
// The completeness guard: a registered mnemonic without a row here is a
|
||||
// gap in the kernel, not a pass.
|
||||
for _, name := range asm.ExtensionNames(arch.AMD64) {
|
||||
if !strings.Contains(amd64ExtKernel, "\t"+name+" ") {
|
||||
t.Errorf("the amd64 extension kernel has no row for %q", name)
|
||||
}
|
||||
}
|
||||
diags := lintArchFile(t, "ext_amd64.s", amd64ExtKernel, arch.AMD64, func(f *ast.File) (*asm.Image, error) {
|
||||
return asm.AssembleFile(f)
|
||||
})
|
||||
for _, code := range []string{CodeUnknownInstr, CodeUnencodable, CodeExtensionForm,
|
||||
CodeNonportableRegister, CodeOperandCount, CodeRegisterWidthMismatch} {
|
||||
if n := codes(diags)[code]; n != 0 {
|
||||
t.Errorf("%s fired %d times on the wired extension: %+v", code, n, diags)
|
||||
}
|
||||
}
|
||||
if len(diags) != 0 {
|
||||
t.Errorf("a kernel the encoder accepts must lint clean, got %+v", diags)
|
||||
}
|
||||
|
||||
// The registry consult is the layer's own, not a loosening of the rule:
|
||||
// a mnemonic neither table nor registry carries still fires.
|
||||
diags = lintSrcArch(t, "ext_amd64.s", `#include "textflag.h"
|
||||
TEXT ·f(SB), NOSPLIT, $0
|
||||
FROBNICATE Z1, Z2, Z3
|
||||
RET
|
||||
`)
|
||||
if codes(diags)[CodeUnknownInstr] != 1 {
|
||||
t.Errorf("an unregistered mnemonic must fire unknown-instruction once: %+v", diags)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAmd64ExtensionRefusals pins the sharper diagnostic the amd64 layer
|
||||
// carries: a statement the registry refuses at assembly time is a lint error
|
||||
// here, under the same text the assembler prints, and one diagnostic alone.
|
||||
// The rows are the layer's own validator refusals: the decorations a form
|
||||
// does not take, the registers outside a class, and the immediate layouts
|
||||
// with their reserved bits.
|
||||
func TestAmd64ExtensionRefusals(t *testing.T) {
|
||||
tests := []struct {
|
||||
stmt string
|
||||
want string
|
||||
}{
|
||||
{"VADDSH X5, X4, X6{K3}", "the entry's destination takes none"},
|
||||
{"VADDPH Y5, Y4, Y6{RZ-SAE}", "wants a ZMM register"},
|
||||
{"VADDPH Z5, Z4, Z6{K0}", "outside the masking registers k1-k7"},
|
||||
{"VADDPH Z5, Z4, Z6{Z}", "zeroing without a write mask"},
|
||||
{"VADDPH Z5, Z4, Z6{SAE}", "spells {sae} without a mode"},
|
||||
{"VADDPH Z29, (R9){RZ-SAE}, Z30", "the memory operand takes none"},
|
||||
{"VFMULCPH Z5, (R9){1TO8}, Z6", "the entry's memory operand takes none"},
|
||||
{"VADDPH Z29, Z28, Z30{BOGUS}", "is not a decoration the layer reads"},
|
||||
{"VADDPH Z29, Z28, Z30{K7}{K3}", "carries two write masks"},
|
||||
{"VCMPSH $300, X29, X28, K5", "outside the unsigned byte range"},
|
||||
{"VGETMANTSH $0x20, X29, X28, X30", "the upper nibble of the mantissa control is reserved"},
|
||||
{"VCVTSI2SH X29, X12, X30", "general register"},
|
||||
{"VADDPH Z5, Z4", "got 2 operands"},
|
||||
{"VMOVSH (Z4), X30", "not an extended-layer operand"},
|
||||
{"VMOVSH foo+4(SB), X30", "not an extended-layer operand"},
|
||||
{"VMOVSH 8(R9)(DX*3), X30", "outside the byte multipliers"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
src := "#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n\t" + tt.stmt + "\n\tRET\n"
|
||||
diags := lintSrcArch(t, "ext_amd64.s", src)
|
||||
if len(diags) != 1 {
|
||||
t.Errorf("%s: got %d diagnostics, want the one extension-form error: %+v", tt.stmt, len(diags), diags)
|
||||
continue
|
||||
}
|
||||
d := diags[0]
|
||||
if d.Code != CodeExtensionForm || d.Severity != Error {
|
||||
t.Errorf("%s: got %s %s, want an error-severity %s: %+v", tt.stmt, d.Severity, d.Code, CodeExtensionForm, d)
|
||||
}
|
||||
if !strings.Contains(d.Message, tt.want) {
|
||||
t.Errorf("%s: message %q does not name %q", tt.stmt, d.Message, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user