64 lines
2.3 KiB
Go
64 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 (
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||
|
|
)
|
||
|
|
|
||
|
|
// scanAddressing flags the two pseudo-register spellings the language
|
||
|
|
// reference (docs/asm/OPERANDS.md) calls its sharpest edge: a numeric offset
|
||
|
|
// against FP names no symbol, and a negative numeric offset against SP
|
||
|
|
// addresses the hardware stack pointer rather than the virtual frame.
|
||
|
|
func scanAddressing(t *ast.Text, cfg Config) []Diagnostic {
|
||
|
|
var out []Diagnostic
|
||
|
|
for _, s := range t.Body {
|
||
|
|
in, ok := s.(*ast.Instr)
|
||
|
|
if !ok {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
for _, op := range in.Operands {
|
||
|
|
if op.Kind != ast.OpAddr {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
// FP is a pseudo-register, never a base register of a memory
|
||
|
|
// operand; a Base of FP only arises from the numeric form
|
||
|
|
// 0(FP), which the parser routes through memory parsing. The
|
||
|
|
// toolchain rejects it with "cannot reference FP without a
|
||
|
|
// symbol" and gasm with "unknown base register FP".
|
||
|
|
if strings.EqualFold(op.Addr.Base, "FP") && !cfg.Disable[CodeUnnamedFPRef] {
|
||
|
|
out = append(out, Diagnostic{
|
||
|
|
Pos: op.Pos,
|
||
|
|
Severity: Error,
|
||
|
|
Code: CodeUnnamedFPRef,
|
||
|
|
Message: "FP is referenced without a symbol; the assembler requires name+offset(FP)",
|
||
|
|
})
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
// A numeric offset against (SP) addresses the hardware stack
|
||
|
|
// pointer. Positive offsets are the deliberate runtime idiom
|
||
|
|
// for reading caller frames, but a negative offset is exactly
|
||
|
|
// the local-frame spelling with the name missing: x-8(SP) and
|
||
|
|
// -8(SP) are one character apart and mean different registers.
|
||
|
|
// Only the negative form is flagged, which keeps the rule
|
||
|
|
// silent across GOROOT's 140-odd deliberate hardware-SP
|
||
|
|
// references (all positive) while catching the mistyped local.
|
||
|
|
if strings.EqualFold(op.Addr.Base, "SP") && op.Addr.Sym == nil &&
|
||
|
|
op.Addr.HasOff && op.Addr.Offset < 0 && !cfg.Disable[CodeHardwareSP] {
|
||
|
|
out = append(out, Diagnostic{
|
||
|
|
Pos: op.Pos,
|
||
|
|
Severity: Warning,
|
||
|
|
Code: CodeHardwareSP,
|
||
|
|
Message: fmt.Sprintf("%d(SP) addresses the hardware stack pointer, not the virtual frame; "+
|
||
|
|
"a frame local is spelled x%d(SP)", op.Addr.Offset, op.Addr.Offset),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|