2026-08-30 21:07:45 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package lint
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2026-09-26 11:08:43 +02:00
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
2026-08-30 21:07:45 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|