feat(lint): add register-width-mismatch rule
Test / vet (push) Successful in 48s
Test / test (push) Successful in 2m31s
Test / build (push) Successful in 44s

Assisted-by: MiMo V2.5 Pro
This commit is contained in:
2026-08-21 01:20:38 +02:00
parent e4c9d78968
commit 5e06d6a6aa
2 changed files with 108 additions and 13 deletions
+82 -13
View File
@@ -63,19 +63,20 @@ type Config struct {
// Rule codes.
const (
CodeUnknownInstr = "unknown-instruction"
CodeOperandCount = "operand-count"
CodeUndefinedLabel = "undefined-label"
CodeDuplicateLabel = "duplicate-label"
CodeMissingRet = "missing-ret"
CodeMissingTextflag = "missing-textflag-include"
CodeUnreachable = "unreachable-code"
CodeABIArgSize = "abi-argsize"
CodeRegisterClobber = "register-clobber"
CodeFuncdata = "funcdata-pcdata"
CodeUnusedLabel = "unused-label"
CodeInvalidFlag = "invalid-textflag"
CodeStackImbalance = "stack-imbalance"
CodeUnknownInstr = "unknown-instruction"
CodeOperandCount = "operand-count"
CodeUndefinedLabel = "undefined-label"
CodeDuplicateLabel = "duplicate-label"
CodeMissingRet = "missing-ret"
CodeMissingTextflag = "missing-textflag-include"
CodeUnreachable = "unreachable-code"
CodeABIArgSize = "abi-argsize"
CodeRegisterClobber = "register-clobber"
CodeFuncdata = "funcdata-pcdata"
CodeUnusedLabel = "unused-label"
CodeInvalidFlag = "invalid-textflag"
CodeStackImbalance = "stack-imbalance"
CodeRegisterWidthMismatch = "register-width-mismatch"
)
// knownTextFlags are the flags recognised by the Go assembler's textflag.h.
@@ -299,6 +300,21 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
}
}
// Register-width mismatch: amd64 instructions with Q suffix
// should use 64-bit registers, L/W/B suffix should use
// 32/16/8-bit registers.
if cfg.Arch == arch.AMD64 && !cfg.Disable[CodeRegisterWidthMismatch] && !isMacroInvocation(mnem, macros) {
if msg := checkRegisterWidth(upper, st.Operands); msg != "" {
out = append(out, Diagnostic{
Pos: st.Mnemonic.Pos,
End: st.Mnemonic.End,
Severity: Warning,
Code: CodeRegisterWidthMismatch,
Message: msg,
})
}
}
if isJump(cfg.Arch, upper) {
for _, op := range st.Operands {
if name, pos, ok := localLabelRef(op); ok && !tab.IsRegister(name) && !arch.IsPseudoReg(name) {
@@ -714,6 +730,59 @@ func isSPReg(op *ast.Operand, a arch.Arch) bool {
return false
}
// checkRegisterWidth detects amd64 register-width mismatches: a Q-suffix
// instruction (64-bit) using a 32-bit register, or an L/W/B-suffix
// instruction using a 64-bit register.
func checkRegisterWidth(mnem string, ops []*ast.Operand) string {
// Determine expected width from mnemonic suffix.
var expected int // 0=unknown, 8/4/2/1=bytes
switch {
case strings.HasSuffix(mnem, "Q"):
expected = 8
case strings.HasSuffix(mnem, "L"):
expected = 4
case strings.HasSuffix(mnem, "W"):
expected = 2
case strings.HasSuffix(mnem, "B"):
expected = 1
default:
return "" // no suffix, can't determine width
}
for _, op := range ops {
if op.Kind != ast.OpAddr || op.Addr.Sym == nil {
continue
}
name := strings.ToLower(op.Addr.Sym.Name)
regWidth := amd64RegWidth(name)
if regWidth == 0 {
continue // not a register or unknown
}
if expected == 8 && regWidth == 4 {
return fmt.Sprintf("%s uses 32-bit register %s (expected 64-bit)", mnem, op.Addr.Sym.Name)
}
if expected == 4 && regWidth == 8 {
return fmt.Sprintf("%s uses 64-bit register %s (expected 32-bit)", mnem, op.Addr.Sym.Name)
}
}
return ""
}
// amd64RegWidth returns the width in bytes of an amd64 register name.
func amd64RegWidth(name string) int {
switch name {
case "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15":
return 8
case "eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp":
return 4
case "ax", "bx", "cx", "dx", "si", "di", "bp", "sp":
return 2
case "al", "bl", "cl", "dl", "ah", "bh", "ch", "dh":
return 1
}
return 0
}
func countRange(min, max int) string {
if min == max {
return fmt.Sprintf("%d operand(s)", min)