feat(audit): list every corpus failure per architecture

This commit is contained in:
2026-09-21 21:17:34 +02:00
parent 53de91b2df
commit 5382c9a8e4
3 changed files with 43 additions and 15 deletions
+31 -11
View File
@@ -38,7 +38,7 @@ import (
// construction and are excluded from the diff; the other architectures list // construction and are excluded from the diff; the other architectures list
// their conditional branches outright. // their conditional branches outright.
func cmdAuditInstructions(args []string) error { func cmdAuditInstructions(args []string) error {
fs := newCommand("audit-instructions", "gasm audit-instructions [--corpus [dir]] [-I dir] [amd64|arm64|riscv64|loong64]", ` fs := newCommand("audit-instructions", "gasm audit-instructions [--corpus [dir]] [--list] [-I dir] [amd64|arm64|riscv64|loong64]", `
Compare the gasm encoder for the given architecture (default amd64) against Compare the gasm encoder for the given architecture (default amd64) against
go tool asm and print the diff: superset encodings (gasm-only, shippable via go tool asm and print the diff: superset encodings (gasm-only, shippable via
gasm asm --format goobj) and known-but-unencodable names (the backlog). The gasm asm --format goobj) and known-but-unencodable names (the backlog). The
@@ -55,16 +55,19 @@ toolchain probing. A file whose name carries a recognisable _arch suffix is
attempted for that architecture; a file without one is attempted for all attempted for that architecture; a file without one is attempted for all
four, exactly as a GOARCH build would compile it. The report gives the four, exactly as a GOARCH build would compile it. The report gives the
per-architecture pass rates and the most common failure reasons, which drive per-architecture pass rates and the most common failure reasons, which drive
the encodability backlog by frequency rather than by table order. the encodability backlog by frequency rather than by table order. With
-list the report also prints every failing file with its reason, per
architecture.
`) `)
corpus := fs.Bool("corpus", false, "assemble a corpus of .s files and report pass rates and failure reasons") corpus := fs.Bool("corpus", false, "assemble a corpus of .s files and report pass rates and failure reasons")
list := fs.Bool("list", false, "with --corpus, list every failing file with its reason, per architecture")
var dirs includeDirs var dirs includeDirs
fs.Var(&dirs, "I", "directory to search for #include files (may be repeated)") fs.Var(&dirs, "I", "directory to search for #include files (may be repeated)")
if err := fs.Parse(args); err != nil { if err := fs.Parse(args); err != nil {
return err return err
} }
if *corpus { if *corpus {
return cmdAuditCorpus(fs.Args(), dirs) return cmdAuditCorpus(fs.Args(), dirs, *list)
} }
archName := "amd64" archName := "amd64"
switch n := len(fs.Args()); { switch n := len(fs.Args()); {
@@ -403,20 +406,30 @@ type corpusTally struct {
assembled int assembled int
reasons map[string]int // failure reason → count reasons map[string]int // failure reason → count
example map[string]string // failure reason → one representative file example map[string]string // failure reason → one representative file
fails []corpusFailure // every failure, in file order, for --list
} }
func (t *corpusTally) fail(path, reason string) { // corpusFailure is one failed attempt, recorded for the --list report.
type corpusFailure struct {
path string
reason string
detail string
}
func (t *corpusTally) fail(path string, err error) {
reason := corpusReason(err)
t.reasons[reason]++ t.reasons[reason]++
if t.example[reason] == "" { if t.example[reason] == "" {
t.example[reason] = path t.example[reason] = path
} }
t.fails = append(t.fails, corpusFailure{path: path, reason: reason, detail: firstLine(err.Error())})
} }
// cmdAuditCorpus implements audit-instructions --corpus. The include // cmdAuditCorpus implements audit-instructions --corpus. The include
// directories carry #include resolution over a corpus whose files refer to // directories carry #include resolution over a corpus whose files refer to
// headers such as GOROOT/pkg/include, the same -I a toolchain comparison // headers such as GOROOT/pkg/include, the same -I a toolchain comparison
// needs. // needs.
func cmdAuditCorpus(args []string, dirs includeDirs) error { func cmdAuditCorpus(args []string, dirs includeDirs, list bool) error {
if len(args) > 1 { if len(args) > 1 {
return &usageError{fmt.Errorf("audit-instructions --corpus takes at most one directory argument")} return &usageError{fmt.Errorf("audit-instructions --corpus takes at most one directory argument")}
} }
@@ -454,7 +467,7 @@ func cmdAuditCorpus(args []string, dirs includeDirs) error {
if err != nil { if err != nil {
return err return err
} }
printCorpusStats(stats) printCorpusStats(stats, list)
return nil return nil
} }
@@ -645,7 +658,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
hdrDir, err := hdr.dirFor(pkgDir, goos, goarchName(tg.a)) hdrDir, err := hdr.dirFor(pkgDir, goos, goarchName(tg.a))
if err != nil { if err != nil {
ok = false ok = false
t.fail(path, corpusReason(err)) t.fail(path, err)
continue continue
} }
f, errs := parser.ParseWithOptions(path, src, parser.Options{ f, errs := parser.ParseWithOptions(path, src, parser.Options{
@@ -654,12 +667,12 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
}) })
if len(errs) > 0 { if len(errs) > 0 {
ok = false ok = false
t.fail(path, corpusReason(errs[0])) t.fail(path, errs[0])
continue continue
} }
if _, err := assembleFile(tg.a, f); err != nil { if _, err := assembleFile(tg.a, f); err != nil {
ok = false ok = false
t.fail(path, corpusReason(err)) t.fail(path, err)
continue continue
} }
t.assembled++ t.assembled++
@@ -684,7 +697,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
} }
if err != nil { if err != nil {
ok = false ok = false
t.fail(path, corpusReason(err)) t.fail(path, err)
continue continue
} }
t.assembled++ t.assembled++
@@ -706,7 +719,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
} }
// printCorpusStats renders the corpus audit report. // printCorpusStats renders the corpus audit report.
func printCorpusStats(s *corpusStats) { func printCorpusStats(s *corpusStats, list bool) {
fmt.Printf("corpus %s: %d files (%d generic, attempted for all architectures; %d named for other Go ports, never attempted)\n", s.root, s.files, s.generic, s.otherPort) fmt.Printf("corpus %s: %d files (%d generic, attempted for all architectures; %d named for other Go ports, never attempted)\n", s.root, s.files, s.generic, s.otherPort)
// The rate is over the files a supported build would attempt: the // The rate is over the files a supported build would attempt: the
// other ports' files sit in the count for completeness but can never // other ports' files sit in the count for completeness but can never
@@ -721,6 +734,13 @@ func printCorpusStats(s *corpusStats) {
fmt.Printf(" %4d %s\n", t.reasons[r], r) fmt.Printf(" %4d %s\n", t.reasons[r], r)
fmt.Printf(" e.g. %s\n", t.example[r]) fmt.Printf(" e.g. %s\n", t.example[r])
} }
if !list {
continue
}
for _, f := range t.fails {
fmt.Printf(" FAIL %s\n", f.path)
fmt.Printf(" %s: %s\n", f.reason, f.detail)
}
} }
} }
+4 -2
View File
@@ -365,7 +365,7 @@ add: 16 bytes, args=24, frame=0 NOSPLIT
## audit-instructions ## audit-instructions
```text ```text
Usage: gasm audit-instructions [--corpus [dir]] [-I dir] [amd64|arm64|riscv64|loong64] Usage: gasm audit-instructions [--corpus [dir]] [--list] [-I dir] [amd64|arm64|riscv64|loong64]
``` ```
Compare the gasm encoder for the given architecture (default amd64) against the Compare the gasm encoder for the given architecture (default amd64) against the
@@ -403,7 +403,9 @@ architecture; a file without one is attempted for all four, exactly as a
The report gives the headline number (files The report gives the headline number (files
that assemble for every target architecture), the per-architecture pass rates that assemble for every target architecture), the per-architecture pass rates
and the most common failure reasons with one representative file each, which and the most common failure reasons with one representative file each, which
drive the encodability backlog by frequency rather than by table order. A run drive the encodability backlog by frequency rather than by table order. With
`--list` the report additionally prints every failing file with its failure
reason, per architecture. A run
over GOROOT takes under a second. over GOROOT takes under a second.
```sh ```sh
+8 -2
View File
@@ -1,8 +1,8 @@
.TH GASM-AUDIT-INSTRUCTIONS 1 "2026-09-19" "gasm" "User Commands" .TH GASM-AUDIT-INSTRUCTIONS 1 "2026-09-21" "gasm" "User Commands"
.SH NAME .SH NAME
gasm-audit-instructions \- diff the encoder against the Go toolchain, or measure a corpus gasm-audit-instructions \- diff the encoder against the Go toolchain, or measure a corpus
.SH SYNOPSIS .SH SYNOPSIS
.B gasm audit\-instructions [\-\-corpus [\fIdir\fR]] [\-I dir] [amd64|arm64|riscv64|loong64] .B gasm audit\-instructions [\-\-corpus [\fIdir\fR]] [\-\-list] [\-I dir] [amd64|arm64|riscv64|loong64]
.SH DESCRIPTION .SH DESCRIPTION
Compare the gasm encoder for the given architecture (default amd64) Compare the gasm encoder for the given architecture (default amd64)
against against
@@ -39,6 +39,12 @@ second.
Assemble a corpus of .s files and report pass rates and failure Assemble a corpus of .s files and report pass rates and failure
reasons. reasons.
.TP .TP
.B \-\-list
With
.BR \-\-corpus ,
print every failing file with its failure reason, per architecture,
instead of one representative file per reason.
.TP
.B \-I \fIdir\fR .B \-I \fIdir\fR
Directory to search for #include files; may be repeated, searched in Directory to search for #include files; may be repeated, searched in
order after the source directory. A corpus run whose files include order after the source directory. A corpus run whose files include