fix(lint): calibrate register-clobber to the Go ABI and add legacy SSE moves
Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
+54
-53
@@ -4,7 +4,6 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -248,7 +247,7 @@ func instrEffect(in *ast.Instr, a arch.Arch) regEffect {
|
||||
}
|
||||
|
||||
compare := isCompare(mnem)
|
||||
dstIdx := dstIndex(in, a)
|
||||
dstIdx := dstIndex(in)
|
||||
|
||||
for i, op := range in.Operands {
|
||||
r := gprName(op, a)
|
||||
@@ -281,13 +280,11 @@ func instrEffect(in *ast.Instr, a arch.Arch) regEffect {
|
||||
return eff
|
||||
}
|
||||
|
||||
// dstIndex returns the operand index of the destination register: last for the
|
||||
// Plan 9 (amd64) spelling, first for arm64/riscv64/loong64.
|
||||
func dstIndex(in *ast.Instr, a arch.Arch) int {
|
||||
if a == arch.AMD64 {
|
||||
return len(in.Operands) - 1
|
||||
}
|
||||
return 0
|
||||
// dstIndex returns the operand index of the destination register: in Plan 9
|
||||
// notation the destination is the last operand on every architecture Go
|
||||
// supports (amd64, arm64, riscv64 and loong64 alike).
|
||||
func dstIndex(in *ast.Instr) int {
|
||||
return len(in.Operands) - 1
|
||||
}
|
||||
|
||||
// isCompare reports whether the mnemonic only reads its operands (setting flags).
|
||||
@@ -372,41 +369,37 @@ func sameSet(a, b map[string]bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// calleeSavedGPRs returns the general-purpose registers an assembly function
|
||||
// must preserve for its caller, using the register names the assembler accepts
|
||||
// for each architecture.
|
||||
func calleeSavedGPRs(a arch.Arch) map[string]bool {
|
||||
// goFixedGPRs returns the general-purpose registers the Go ABI designates as
|
||||
// fixed across calls — the ones hand-written assembly must not permanently
|
||||
// clobber. This follows cmd/compile/abi-internal.md, not the platform ABI:
|
||||
// Go's stack-based ABI0 (which hand-written assembly uses) has no System V
|
||||
// style callee-saved registers, so clobbering the argument and scratch
|
||||
// registers (amd64 BX, R12, R13, R15, …) is legal.
|
||||
//
|
||||
// Two groups are returned. always holds registers whose loss is never safe.
|
||||
// runtime holds registers that survive an ABI0 leaf only because the
|
||||
// transition machinery restores them (on amd64 the g pointer is reloaded
|
||||
// from TLS): clobbering them is safe exactly in NOSPLIT functions that make
|
||||
// no calls, which is how the runtime's own assembly uses them.
|
||||
func goFixedGPRs(a arch.Arch) (always, runtime map[string]bool) {
|
||||
switch a {
|
||||
case arch.AMD64:
|
||||
return gprSet("BX", "BP", "R12", "R13", "R14", "R15")
|
||||
// BP maintains the frame chain; R14 holds the current goroutine.
|
||||
// R15 is scratch except in dynamically linked binaries, so it is not
|
||||
// flagged.
|
||||
return gprSet("BP"), gprSet("R14")
|
||||
case arch.ARM64:
|
||||
names := []string{"R29", "R30"} // FP, LR
|
||||
for i := 19; i <= 28; i++ {
|
||||
names = append(names, fmt.Sprintf("R%d", i))
|
||||
}
|
||||
return gprSet(names...)
|
||||
// R18 is reserved for the OS on some platforms, R28 holds the current
|
||||
// goroutine, R29 is the frame pointer.
|
||||
return gprSet("R18", "R28", "R29"), nil
|
||||
case arch.RISCV:
|
||||
// RA (X1) and the S registers (X8, X9, X18–X27) are callee-saved.
|
||||
names := []string{"X1", "RA", "X8", "X9", "S0", "S1", "FP"}
|
||||
for i := 18; i <= 27; i++ {
|
||||
names = append(names, fmt.Sprintf("X%d", i))
|
||||
}
|
||||
for i := 2; i <= 11; i++ {
|
||||
names = append(names, fmt.Sprintf("S%d", i))
|
||||
}
|
||||
return gprSet(names...)
|
||||
// X27 holds the current goroutine.
|
||||
return gprSet("X27"), nil
|
||||
case arch.LOONG64:
|
||||
// RA (R1), FP (R22) and S0–S8 (R23–R31) are callee-saved.
|
||||
names := []string{"R1", "RA", "R22", "FP"}
|
||||
for i := 23; i <= 31; i++ {
|
||||
names = append(names, fmt.Sprintf("R%d", i))
|
||||
}
|
||||
for i := 0; i <= 8; i++ {
|
||||
names = append(names, fmt.Sprintf("S%d", i))
|
||||
}
|
||||
return gprSet(names...)
|
||||
// R22 holds the current goroutine.
|
||||
return gprSet("R22"), nil
|
||||
}
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func gprSet(names ...string) map[string]bool {
|
||||
@@ -417,15 +410,16 @@ func gprSet(names ...string) map[string]bool {
|
||||
return m
|
||||
}
|
||||
|
||||
// clobberedCalleeSaved returns the callee-saved registers a function writes
|
||||
// without also saving and restoring them — i.e. registers whose caller-owned
|
||||
// value is lost across the call. It walks the blocks of the liveness analysis
|
||||
// (so the control-flow graph is what supplies the instruction set) and
|
||||
// aggregates each instruction's register effects.
|
||||
func clobberedCalleeSaved(l *liveness, a arch.Arch) []string {
|
||||
callee := calleeSavedGPRs(a)
|
||||
if len(callee) == 0 {
|
||||
return nil
|
||||
// clobberedGoFixed returns the Go-ABI-fixed registers a function writes
|
||||
// without also saving and restoring them. The first result lists registers
|
||||
// whose loss is never safe; the second lists the goroutine-pointer class,
|
||||
// whose loss is reported only when reachesRuntime is true (a non-NOSPLIT
|
||||
// function, or one that makes calls — the ABI0 transition machinery restores
|
||||
// the g pointer only on such paths).
|
||||
func clobberedGoFixed(l *liveness, a arch.Arch, reachesRuntime bool) (always, runtime []string) {
|
||||
alwaysSet, runtimeSet := goFixedGPRs(a)
|
||||
if len(alwaysSet) == 0 && len(runtimeSet) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
def := map[string]bool{}
|
||||
saved := map[string]bool{}
|
||||
@@ -444,12 +438,19 @@ func clobberedCalleeSaved(l *liveness, a arch.Arch) []string {
|
||||
}
|
||||
}
|
||||
}
|
||||
var out []string
|
||||
for r := range callee {
|
||||
if def[r] && !(saved[r] && restored[r]) {
|
||||
out = append(out, r)
|
||||
clobbered := func(set map[string]bool) []string {
|
||||
var out []string
|
||||
for r := range set {
|
||||
if def[r] && !(saved[r] && restored[r]) {
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
always = clobbered(alwaysSet)
|
||||
if reachesRuntime {
|
||||
runtime = clobbered(runtimeSet)
|
||||
}
|
||||
return always, runtime
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user