Files
gasm-sdk/lint/registers.go
T

88 lines
2.4 KiB
Go
Raw Normal View History

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