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)
|
||
}
|
||
}
|