feat(asm): add did-you-mean suggestions for undefined labels

Assisted-by: DeepSeek V4 Pro
This commit is contained in:
2026-08-05 18:52:00 +02:00
parent 6228d77566
commit 49566de7fb
+68 -4
View File
@@ -162,7 +162,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
target := labelFromOperand(ops[0]) target := labelFromOperand(ops[0])
targetOff, ok := offsets[target] targetOff, ok := offsets[target]
if !ok { if !ok {
return nil, fmt.Errorf("undefined label %q", target) return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
} }
offset := int32(targetOff - pc) offset := int32(targetOff - pc)
// AUIPC X1, upper 20 bits // AUIPC X1, upper 20 bits
@@ -187,7 +187,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
} }
targetOff, ok := offsets[target] targetOff, ok := offsets[target]
if !ok { if !ok {
return nil, fmt.Errorf("undefined label %q", target) return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
} }
offset := int32(targetOff - pc) offset := int32(targetOff - pc)
// C.J: funct3=0x5, offset in ±2 KB, bit 0 must be 0. // C.J: funct3=0x5, offset in ±2 KB, bit 0 must be 0.
@@ -208,7 +208,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
} }
targetOff, ok := offsets[target] targetOff, ok := offsets[target]
if !ok { if !ok {
return nil, fmt.Errorf("undefined label %q", target) return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
} }
offset := int32(targetOff - pc) offset := int32(targetOff - pc)
// JAL X0, target → C.J when offset fits. // JAL X0, target → C.J when offset fits.
@@ -435,7 +435,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
target := labelFromOperand(ops[2]) target := labelFromOperand(ops[2])
targetOff, ok := offsets[target] targetOff, ok := offsets[target]
if !ok { if !ok {
return nil, fmt.Errorf("undefined label %q", target) return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
} }
offset := int32(targetOff - pc) offset := int32(targetOff - pc)
if rs1 < 0 || rs2 < 0 { if rs1 < 0 || rs2 < 0 {
@@ -1064,3 +1064,67 @@ func labelFromOperand(op *ast.Operand) string {
} }
return op.Raw return op.Raw
} }
// suggestLabel returns a "did you mean" suggestion for an undefined label.
func suggestLabel(target string, offsets map[string]int) string {
if len(offsets) == 0 {
return ""
}
// Find the closest matching label using Levenshtein distance.
bestDist := len(target) + 1
var best string
for name := range offsets {
dist := levenshtein(target, name)
if dist < bestDist {
bestDist = dist
best = name
}
}
// Only suggest if the distance is small enough.
if bestDist <= 3 && bestDist < len(target)/2+1 {
return fmt.Sprintf(" — did you mean %q?", best)
}
return ""
}
// levenshtein computes the Levenshtein distance between two strings.
func levenshtein(a, b string) int {
la, lb := len(a), len(b)
if la == 0 {
return lb
}
if lb == 0 {
return la
}
// Create a matrix of distances.
prev := make([]int, lb+1)
curr := make([]int, lb+1)
for j := 0; j <= lb; j++ {
prev[j] = j
}
for i := 1; i <= la; i++ {
curr[0] = i
for j := 1; j <= lb; j++ {
cost := 1
if a[i-1] == b[j-1] {
cost = 0
}
curr[j] = min3(curr[j-1]+1, prev[j]+1, prev[j-1]+cost)
}
prev, curr = curr, prev
}
return prev[lb]
}
func min3(a, b, c int) int {
if a < b {
if a < c {
return a
}
return c
}
if b < c {
return b
}
return c
}