Files

57 lines
1.9 KiB
Go
Raw Permalink 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/arch"
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
)
// reservedRegister returns the platform-reserved general-purpose register of
// the architecture, or "" when it has none. arm64 reserves R18 for platform
// use: the OS may own it (the Windows TEB, the Darwin el0 TLS), the Go
// toolchain never allocates it, and the Go assembler offers no spelling that
// even addresses it, so a kernel writing R18 cannot be assembled by the tool
// it must ship with. riscv64, loong64 and amd64 reserve no register beyond
// the ones the Go ABI fixes, which the register-clobber audit covers.
func reservedRegister(a arch.Arch) string {
if a == arch.ARM64 {
return "R18"
}
return ""
}
// scanReservedRegisterWrites flags instructions whose destination is the
// platform-reserved register. Only the Plan 9 destination (the last
// operand) is checked: reads such as MOVD (R18), R0 are legitimate address
// uses, and multi-register stores (STP) keep the rule silent rather than
// guess. Like the label heuristics, the check is suppressed in macro-using
// files, where an opaque macro may save and restore the register.
func scanReservedRegisterWrites(t *ast.Text, a arch.Arch) []Diagnostic {
res := reservedRegister(a)
if res == "" {
return nil
}
var out []Diagnostic
for _, s := range t.Body {
in, ok := s.(*ast.Instr)
if !ok || len(in.Operands) == 0 {
continue
}
dst := in.Operands[dstIndex(in)]
if r := gprName(dst, a); r != "" && strings.EqualFold(r, res) {
out = append(out, Diagnostic{
Pos: dst.Pos,
Severity: Warning,
Code: CodeReservedRegister,
Message: fmt.Sprintf("write to %s, the platform-reserved register: the OS may own it and the Go assembler cannot spell it", res),
})
}
}
return out
}