71 lines
2.3 KiB
Go
71 lines
2.3 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package lint
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
|
)
|
|
|
|
// checkVectorEncoding reports a kernel that mixes VEX-encoded instructions
|
|
// with legacy SSE operating on X registers. Every switch between the two
|
|
// encodings pays a transition penalty (the upper YMM halves must be saved
|
|
// and restored by the processor), so a kernel is written in one encoding or
|
|
// the other; the runtime's memmove mixes them deliberately behind feature
|
|
// dispatch, which is why the severity is informational.
|
|
func checkVectorEncoding(t *ast.Text, tab *arch.Table, macros map[string]bool) []Diagnostic {
|
|
vex, legacy := false, false
|
|
for _, s := range t.Body {
|
|
in, ok := s.(*ast.Instr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
upper := strings.ToUpper(in.Mnemonic.Text)
|
|
if isMacroInvocation(in.Mnemonic.Text, macros) {
|
|
continue
|
|
}
|
|
// VZEROUPPER and VZEROALL are the boundary management: their purpose
|
|
// is to clean the upper halves before legacy SSE code, so they count
|
|
// as neither side of the mixing.
|
|
if strings.HasPrefix(upper, "V") && upper != "VZEROUPPER" && upper != "VZEROALL" {
|
|
vex = true
|
|
continue
|
|
}
|
|
if !strings.HasPrefix(upper, "V") && hasXMMOperand(in, tab) {
|
|
legacy = true
|
|
}
|
|
}
|
|
if !vex || !legacy {
|
|
return nil
|
|
}
|
|
return []Diagnostic{{
|
|
Pos: t.Keyword.Pos,
|
|
Severity: Information,
|
|
Code: CodeVEXSSEMixing,
|
|
Message: "mixes VEX-encoded instructions with legacy SSE on X registers; each switch " +
|
|
"between the encodings pays a transition penalty, so keep one encoding per kernel",
|
|
}}
|
|
}
|
|
|
|
// hasXMMOperand reports whether an instruction addresses an X (128-bit SSE)
|
|
// register. Symbol names that collide with the spelling (a bare X86, say)
|
|
// are excluded by the register table lookup.
|
|
func hasXMMOperand(in *ast.Instr, tab *arch.Table) bool {
|
|
for _, op := range in.Operands {
|
|
if op.Kind != ast.OpAddr || op.Addr.Sym == nil {
|
|
continue
|
|
}
|
|
name := op.Addr.Sym.Name
|
|
if op.Addr.Sym.Pseudo != "" || op.Addr.Base != "" || op.Addr.Index != "" || name == "" {
|
|
continue
|
|
}
|
|
if r, ok := tab.Register(name); ok && r.Class == arch.Vector && strings.HasPrefix(strings.ToUpper(name), "X") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|