// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package lint import ( "fmt" "strings" "sourcedock.dev/petrbalvin/gasm-devkit/ast" "sourcedock.dev/petrbalvin/gasm-devkit/token" ) // amd64WordNames are the canonical 64-bit register spellings of go tool asm: // the assembler has no RAX/EAX/RBX forms, and a file using them assembles // under gasm but fails under the toolchain it must ship with. The rule code // (CodeNonportableRegister) is declared in lint.go. var amd64WordNames = map[string]string{ "AX": "AX", "BX": "BX", "CX": "CX", "DX": "DX", "SI": "SI", "DI": "DI", "BP": "BP", "SP": "SP", } // nonportableRegister maps a gasm-only register alias to the canonical go // tool asm spelling. R or E followed by a canonical word name is the only // alias family; R8–R15 are already canonical. func nonportableRegister(name string) (string, bool) { up := strings.ToUpper(name) if len(up) != 3 { return "", false } var rest string switch up[0] { case 'R', 'E': rest = up[1:] default: return "", false } if canon, ok := amd64WordNames[rest]; ok && canon != up { return canon, true } return "", false } // scanNonportableRegisters returns one diagnostic per distinct non-portable // register spelling in t. gasm neither requires nor registers these names // (they parse as bare symbols), and go tool asm rejects them outright, so a // kernel containing one only links through the gasm goobj path, never // through a normal go build. func scanNonportableRegisters(t *ast.Text) []Diagnostic { seen := map[string]bool{} var out []Diagnostic report := func(name string, pos token.Position) { canon, ok := nonportableRegister(name) if !ok { return } key := strings.ToUpper(name) if seen[key] { return } seen[key] = true out = append(out, Diagnostic{ Pos: pos, Severity: Warning, Code: CodeNonportableRegister, Message: fmt.Sprintf("register %q is a gasm extension; go tool asm spells it %s", name, canon), }) } for _, s := range t.Body { in, ok := s.(*ast.Instr) if !ok { continue } for _, op := range in.Operands { if op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "" { report(op.Addr.Sym.Name, op.Pos) } if op.Addr.Base != "" { report(op.Addr.Base, op.Pos) } if op.Addr.Index != "" { report(op.Addr.Index, op.Pos) } } } return out }