diff --git a/cmd/gasm/audit.go b/cmd/gasm/audit.go index f3e4b69..d2c8569 100644 --- a/cmd/gasm/audit.go +++ b/cmd/gasm/audit.go @@ -38,7 +38,7 @@ import ( // construction and are excluded from the diff; the other architectures list // their conditional branches outright. 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 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 @@ -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 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 -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") + list := fs.Bool("list", false, "with --corpus, list every failing file with its reason, per architecture") var dirs includeDirs fs.Var(&dirs, "I", "directory to search for #include files (may be repeated)") if err := fs.Parse(args); err != nil { return err } if *corpus { - return cmdAuditCorpus(fs.Args(), dirs) + return cmdAuditCorpus(fs.Args(), dirs, *list) } archName := "amd64" switch n := len(fs.Args()); { @@ -403,20 +406,30 @@ type corpusTally struct { assembled int reasons map[string]int // failure reason → count 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]++ if t.example[reason] == "" { 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 // directories carry #include resolution over a corpus whose files refer to // headers such as GOROOT/pkg/include, the same -I a toolchain comparison // needs. -func cmdAuditCorpus(args []string, dirs includeDirs) error { +func cmdAuditCorpus(args []string, dirs includeDirs, list bool) error { if len(args) > 1 { 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 { return err } - printCorpusStats(stats) + printCorpusStats(stats, list) return nil } @@ -645,7 +658,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) { hdrDir, err := hdr.dirFor(pkgDir, goos, goarchName(tg.a)) if err != nil { ok = false - t.fail(path, corpusReason(err)) + t.fail(path, err) continue } f, errs := parser.ParseWithOptions(path, src, parser.Options{ @@ -654,12 +667,12 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) { }) if len(errs) > 0 { ok = false - t.fail(path, corpusReason(errs[0])) + t.fail(path, errs[0]) continue } if _, err := assembleFile(tg.a, f); err != nil { ok = false - t.fail(path, corpusReason(err)) + t.fail(path, err) continue } t.assembled++ @@ -684,7 +697,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) { } if err != nil { ok = false - t.fail(path, corpusReason(err)) + t.fail(path, err) continue } t.assembled++ @@ -706,7 +719,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) { } // 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) // The rate is over the files a supported build would attempt: the // 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(" 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) + } } } diff --git a/docs/CLI.md b/docs/CLI.md index 45db2bd..aa09713 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -365,7 +365,7 @@ add: 16 bytes, args=24, frame=0 NOSPLIT ## audit-instructions ```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 @@ -403,7 +403,9 @@ architecture; a file without one is attempted for all four, exactly as a The report gives the headline number (files that assemble for every target architecture), the per-architecture pass rates 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. ```sh diff --git a/docs/man/gasm-audit-instructions.1 b/docs/man/gasm-audit-instructions.1 index 82f85d5..e63f1e6 100644 --- a/docs/man/gasm-audit-instructions.1 +++ b/docs/man/gasm-audit-instructions.1 @@ -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 gasm-audit-instructions \- diff the encoder against the Go toolchain, or measure a corpus .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 Compare the gasm encoder for the given architecture (default amd64) against @@ -39,6 +39,12 @@ second. Assemble a corpus of .s files and report pass rates and failure reasons. .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 Directory to search for #include files; may be repeated, searched in order after the source directory. A corpus run whose files include