96 lines
2.6 KiB
Go
96 lines
2.6 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|||
|
|
|
|||
|
|
package lint
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
|||
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// TestVEXSSEMixing checks the encoding-mixing advisory: VEX and legacy SSE
|
|||
|
|
// in one kernel pay a transition penalty on every switch, so the finding is
|
|||
|
|
// informational, not a defect.
|
|||
|
|
func TestVEXSSEMixing(t *testing.T) {
|
|||
|
|
// GOROOT's memmove shape: VMOVDQU on Y registers beside MOVOU on X.
|
|||
|
|
diags := lintSrc(t, `
|
|||
|
|
#include "textflag.h"
|
|||
|
|
TEXT ·copy(SB), NOSPLIT, $0
|
|||
|
|
VMOVDQU (SI), Y4
|
|||
|
|
MOVOU (SI), X0
|
|||
|
|
RET
|
|||
|
|
`)
|
|||
|
|
if codes(diags)[CodeVEXSSEMixing] != 1 {
|
|||
|
|
t.Fatalf("want one vex-sse-mixing, got %+v", diags)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// One encoding throughout is the recommendation.
|
|||
|
|
diags = lintSrc(t, `
|
|||
|
|
#include "textflag.h"
|
|||
|
|
TEXT ·copy(SB), NOSPLIT, $0
|
|||
|
|
VMOVDQU (SI), Y4
|
|||
|
|
VPSUBD Y1, Y2, Y3
|
|||
|
|
RET
|
|||
|
|
`)
|
|||
|
|
if codes(diags)[CodeVEXSSEMixing] != 0 {
|
|||
|
|
t.Fatalf("pure VEX kernel must not be flagged: %+v", diags)
|
|||
|
|
}
|
|||
|
|
diags = lintSrc(t, `
|
|||
|
|
#include "textflag.h"
|
|||
|
|
TEXT ·copy(SB), NOSPLIT, $0
|
|||
|
|
MOVOU (SI), X0
|
|||
|
|
PADDB X1, X2
|
|||
|
|
RET
|
|||
|
|
`)
|
|||
|
|
if codes(diags)[CodeVEXSSEMixing] != 0 {
|
|||
|
|
t.Fatalf("pure legacy kernel must not be flagged: %+v", diags)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// VZEROUPPER is the boundary management, not a side of the mixing: the
|
|||
|
|
// idiomatic epilogue after legacy SSE stays clean.
|
|||
|
|
diags = lintSrc(t, `
|
|||
|
|
#include "textflag.h"
|
|||
|
|
TEXT ·copy(SB), NOSPLIT, $0
|
|||
|
|
MOVOU (SI), X0
|
|||
|
|
PADDB X1, X2
|
|||
|
|
VZEROUPPER
|
|||
|
|
RET
|
|||
|
|
`)
|
|||
|
|
if codes(diags)[CodeVEXSSEMixing] != 0 {
|
|||
|
|
t.Fatalf("VZEROUPPER epilogue must not be flagged: %+v", diags)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// A symbol whose name collides with the register spelling (the runtime's
|
|||
|
|
// internal∕cpu·X86 feature flags) is not a register.
|
|||
|
|
diags = lintSrc(t, `
|
|||
|
|
#include "textflag.h"
|
|||
|
|
TEXT ·probe(SB), NOSPLIT, $0
|
|||
|
|
CMPB internal∕cpu·X86+const_offsetX86HasERMS(SB), $1
|
|||
|
|
VMOVDQU (SI), Y4
|
|||
|
|
RET
|
|||
|
|
`)
|
|||
|
|
if codes(diags)[CodeVEXSSEMixing] != 0 {
|
|||
|
|
t.Fatalf("a symbol named X86 must not count as legacy SSE: %+v", diags)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// The rule is amd64-only: arm64 V registers are not SSE at all.
|
|||
|
|
diags = lintSrcArch(t, "f_arm64.s", `
|
|||
|
|
#include "textflag.h"
|
|||
|
|
TEXT ·f(SB), NOSPLIT, $0
|
|||
|
|
VLD1 (R0), [V1.B16]
|
|||
|
|
RET
|
|||
|
|
`)
|
|||
|
|
if codes(diags)[CodeVEXSSEMixing] != 0 {
|
|||
|
|
t.Fatalf("arm64 must not be flagged: %+v", diags)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// -disable silences the rule.
|
|||
|
|
f, _ := parser.Parse("t_amd64.s", "#include \"textflag.h\"\nTEXT ·copy(SB), NOSPLIT, $0\n\tVMOVDQU (SI), Y4\n\tMOVOU (SI), X0\n\tRET\n")
|
|||
|
|
diags = File(f, Config{Arch: arch.AMD64, Disable: map[string]bool{CodeVEXSSEMixing: true}})
|
|||
|
|
if codes(diags)[CodeVEXSSEMixing] != 0 {
|
|||
|
|
t.Fatalf("disabled rule must stay silent: %+v", diags)
|
|||
|
|
}
|
|||
|
|
}
|