// Copyright (c) 2026 Petr BalvĂ­n (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 }