fix(lint): trailing-label CFG guard and the goroutine alias
Assisted-by: GLM 5.3
This commit is contained in:
+36
-4
@@ -4,7 +4,7 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
||||
@@ -86,6 +86,15 @@ func (l *liveness) buildCFG(t *ast.Text) {
|
||||
labelToBlock[b.label] = i
|
||||
}
|
||||
}
|
||||
// A trailing label whose block was never flushed (no instruction follows
|
||||
// it) still holds the index the next block would have taken, which is one
|
||||
// past the end. Drop those entries so a branch to such a label wires no
|
||||
// edge instead of indexing past the live sets in the dataflow.
|
||||
for name, v := range labelToBlock {
|
||||
if v >= len(l.blocks) {
|
||||
delete(labelToBlock, name)
|
||||
}
|
||||
}
|
||||
for i, b := range l.blocks {
|
||||
if len(b.instrs) == 0 {
|
||||
if i+1 < len(l.blocks) {
|
||||
@@ -210,9 +219,13 @@ func isUnconditionalBranchAny(m string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// branchTarget returns the local-label target of a branch, if it is one.
|
||||
// branchTarget returns the local-label target of a branch, if it is one. The
|
||||
// last bare-symbol operand is taken because the Plan 9 branch target sits in
|
||||
// the destination position, after any operand registers: CBZ R0, done or
|
||||
// BEQ R1, R2, done. Scanning forward would pick R0 up as the target.
|
||||
func branchTarget(in *ast.Instr) (string, bool) {
|
||||
for _, op := range in.Operands {
|
||||
for _, op := range slices.Backward(in.Operands) {
|
||||
|
||||
if op.Kind == ast.OpAddr && op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "" &&
|
||||
op.Addr.Base == "" && op.Addr.Sym.Name != "" {
|
||||
return op.Addr.Sym.Name, true
|
||||
@@ -294,6 +307,20 @@ func isCompare(m string) bool {
|
||||
m == "FCMP" || m == "FCMPE"
|
||||
}
|
||||
|
||||
// goroutineAlias maps the assembler's architectural alias for the goroutine
|
||||
// register to its numeric name, verified against go tool asm of the Go 1.27
|
||||
// toolchain: `MOVQ AX, g` encodes the same bytes as R14 on amd64, and on
|
||||
// arm64, riscv64 and loong64 the alias is the ONLY spelling the toolchain
|
||||
// accepts (a numeric R28, X27 or R22 operand is rejected), so a kernel can
|
||||
// clobber the goroutine register through `g` alone. The name is
|
||||
// case-sensitive: only lowercase `g` assembles.
|
||||
var goroutineAlias = map[arch.Arch]string{
|
||||
arch.AMD64: "R14",
|
||||
arch.ARM64: "R28",
|
||||
arch.RISCV: "X27",
|
||||
arch.LOONG64: "R22",
|
||||
}
|
||||
|
||||
// gprName returns the canonical general-purpose register name of an operand, or
|
||||
// "" if the operand is not a bare GPR reference.
|
||||
func gprName(op *ast.Operand, a arch.Arch) string {
|
||||
@@ -307,6 +334,11 @@ func gprName(op *ast.Operand, a arch.Arch) string {
|
||||
if r, ok := arch.ForArch(a).Register(name); ok && (r.Class == arch.GPR || r.Class == arch.GPRSub) {
|
||||
return canonicalGPR(name)
|
||||
}
|
||||
// The goroutine alias is not an arch-table register; resolve it so a
|
||||
// clobber written through `g` is audited like the numeric register.
|
||||
if n, ok := goroutineAlias[a]; ok && name == "g" {
|
||||
return n
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -445,7 +477,7 @@ func clobberedGoFixed(l *liveness, a arch.Arch, reachesRuntime bool) (always, ru
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
always = clobbered(alwaysSet)
|
||||
|
||||
Reference in New Issue
Block a user