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:
+38
-6
@@ -319,18 +319,27 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
|
||||
}
|
||||
}
|
||||
|
||||
// Register liveness: a callee-saved register that is written but never
|
||||
// saved and restored is clobbered across the call. The check runs over the
|
||||
// control-flow graph and is skipped for macro-using files, where an opaque
|
||||
// macro may perform the save/restore.
|
||||
// Register liveness: a register the Go ABI fixes across calls that is
|
||||
// written but never saved and restored is clobbered. The check runs over
|
||||
// the control-flow graph and is skipped for macro-using files, where an
|
||||
// opaque macro may perform the save/restore.
|
||||
if doLabelChecks && archKnown && !cfg.Disable[CodeRegisterClobber] {
|
||||
live := analyzeLiveness(t, cfg.Arch)
|
||||
if clobbered := clobberedCalleeSaved(live, cfg.Arch); len(clobbered) > 0 {
|
||||
always, rt := clobberedGoFixed(live, cfg.Arch, reachesRuntime(t))
|
||||
if len(always) > 0 {
|
||||
out = append(out, Diagnostic{
|
||||
Pos: t.Keyword.Pos,
|
||||
Severity: Warning,
|
||||
Code: CodeRegisterClobber,
|
||||
Message: fmt.Sprintf("callee-saved register(s) %s written but never saved/restored", strings.Join(clobbered, ", ")),
|
||||
Message: fmt.Sprintf("register(s) %s written but never saved/restored: fixed by the Go ABI (frame/goroutine pointer)", strings.Join(always, ", ")),
|
||||
})
|
||||
}
|
||||
if len(rt) > 0 {
|
||||
out = append(out, Diagnostic{
|
||||
Pos: t.Keyword.Pos,
|
||||
Severity: Warning,
|
||||
Code: CodeRegisterClobber,
|
||||
Message: fmt.Sprintf("goroutine-pointer register(s) %s written but never saved/restored in a function that can reach the Go runtime", strings.Join(rt, ", ")),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -341,6 +350,29 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
|
||||
return out
|
||||
}
|
||||
|
||||
// reachesRuntime reports whether a function can reach the Go runtime: it is
|
||||
// not NOSPLIT (so the stack-split and traceback machinery runs) or it makes a
|
||||
// CALL. Goroutine-pointer registers must survive such functions; a NOSPLIT
|
||||
// leaf may clobber them, since the ABI0 transition restores them (the
|
||||
// runtime's own assembly relies on this, e.g. R14 on amd64).
|
||||
func reachesRuntime(t *ast.Text) bool {
|
||||
nosplit := false
|
||||
for _, f := range t.Flags {
|
||||
if strings.EqualFold(f, "NOSPLIT") {
|
||||
nosplit = true
|
||||
}
|
||||
}
|
||||
for _, s := range t.Body {
|
||||
if in, ok := s.(*ast.Instr); ok {
|
||||
switch strings.ToUpper(in.Mnemonic.Text) {
|
||||
case "CALL", "BL", "JAL": // amd64, arm64/loong64, riscv64 calls
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return !nosplit
|
||||
}
|
||||
|
||||
// usesFPArgs reports whether a function references its arguments through the FP
|
||||
// pseudo-register — i.e. it uses the stack-based ABI0 layout, where the
|
||||
// declared argument size must match the signature.
|
||||
|
||||
Reference in New Issue
Block a user