Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -21,6 +21,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
TEXT argument area to the size the `// func` signature implies, computed
|
TEXT argument area to the size the `// func` signature implies, computed
|
||||||
by the new `lint.ExpectedArgSize`.
|
by the new `lint.ExpectedArgSize`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **The corpus audit assembles like the build.** A file's `//go:build`
|
||||||
|
constraint decides which target architectures attempt it: cpu_x86.s is
|
||||||
|
an x86 build alone, and the msan and goexperiment.runtimesecret trees
|
||||||
|
are compiled by no supported build, so they leave the measured set
|
||||||
|
instead of failing it. The headline now reads "assemble for every
|
||||||
|
applicable target": every real-code GOROOT assembly file, the tree
|
||||||
|
without testdata, assembles for all four architectures (250 of 250,
|
||||||
|
100 %); over the whole tree including testdata the measure is 271 of
|
||||||
|
322 (84.2 %).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **Rename edits land in their own documents.** A rename collected the
|
- **Rename edits land in their own documents.** A rename collected the
|
||||||
|
|||||||
@@ -129,10 +129,13 @@ can emit today is narrower, and a recognised but unencodable instruction is
|
|||||||
reported as an explicit error, never as a wrong byte.
|
reported as an explicit error, never as a wrong byte.
|
||||||
|
|
||||||
The same measurement runs over GOROOT's whole assembly corpus:
|
The same measurement runs over GOROOT's whole assembly corpus:
|
||||||
`gasm audit-instructions --corpus` reports 291 of 353 attemptable files
|
`gasm audit-instructions --corpus` reports every real-code GOROOT assembly
|
||||||
(82.4 %) assembling for every target architecture today (files named for
|
file (the tree without testdata) assembling for every target its build
|
||||||
other Go ports are counted but never attempted), with the top failure
|
admits: 250 of 250, 100 %. Over the whole tree including testdata the
|
||||||
reasons per architecture; the number moves with every release.
|
measure is 271 of 322 attemptable (84.2 %); files named for other Go ports
|
||||||
|
are counted but never attempted, and `//go:build` constraints decide which
|
||||||
|
targets attempt a file at all, exactly as the build does. The number moves
|
||||||
|
with every release.
|
||||||
|
|
||||||
### Validation status
|
### Validation status
|
||||||
|
|
||||||
|
|||||||
@@ -393,6 +393,52 @@ func TestRunCorpusAuditGOOS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRunCorpusAuditBuildConstraint covers the //go:build classification end
|
||||||
|
// to end: a generic-named file whose constraint admits one target is
|
||||||
|
// attempted there alone (cpu_x86.s on amd64), and a file whose constraint
|
||||||
|
// admits none of the four targets is never attempted (the msan and
|
||||||
|
// goexperiment trees).
|
||||||
|
func TestRunCorpusAuditBuildConstraint(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
write := func(name, src string) {
|
||||||
|
t.Helper()
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(src), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write("x86.s", "//go:build 386 || amd64\n\nTEXT \xc2\xb7f(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
write("racey.s", "//go:build race\n\nTEXT \xc2\xb7r(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
write("plain.s", "TEXT \xc2\xb7p(SB), NOSPLIT, $0\n\tRET\n")
|
||||||
|
|
||||||
|
stats, err := runCorpusAudit(dir, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("runCorpusAudit: %v", err)
|
||||||
|
}
|
||||||
|
tally := func(name string) *corpusTally {
|
||||||
|
for i, tg := range stats.targets {
|
||||||
|
if tg.name == name {
|
||||||
|
return stats.tallies[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("no tally for %s", name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if stats.narrowed != 1 || stats.excluded != 1 || stats.generic != 1 {
|
||||||
|
t.Errorf("buckets = narrowed %d, excluded %d, generic %d; want 1, 1, 1", stats.narrowed, stats.excluded, stats.generic)
|
||||||
|
}
|
||||||
|
if a := tally("amd64"); a.attempted != 2 || a.assembled != 2 {
|
||||||
|
t.Errorf("amd64 = %d/%d, want 2/2 (x86.s and plain.s)", a.assembled, a.attempted)
|
||||||
|
}
|
||||||
|
for _, name := range []string{"arm64", "riscv64", "loong64"} {
|
||||||
|
if a := tally(name); a.attempted != 1 || a.assembled != 1 {
|
||||||
|
t.Errorf("%s = %d/%d, want 1/1 (plain.s only)", name, a.assembled, a.attempted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stats.full != 2 {
|
||||||
|
t.Errorf("full = %d, want 2 (x86.s over its one target, plain.s over all four)", stats.full)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestGenerateGoAsmHeaderRuntime pins the generator against the real thing:
|
// TestGenerateGoAsmHeaderRuntime pins the generator against the real thing:
|
||||||
// the runtime package of the ambient toolchain, whose header the toolchain's
|
// the runtime package of the ambient toolchain, whose header the toolchain's
|
||||||
// own -asmhdr output was sampled from. Skipped in short mode: it type-checks
|
// own -asmhdr output was sampled from. Skipped in short mode: it type-checks
|
||||||
|
|||||||
+122
-14
@@ -5,6 +5,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/build/constraint"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -476,8 +477,10 @@ type corpusStats struct {
|
|||||||
root string
|
root string
|
||||||
files int
|
files int
|
||||||
generic int // files attempted for all four architectures
|
generic int // files attempted for all four architectures
|
||||||
|
narrowed int // files whose //go:build admits a proper subset of the four
|
||||||
|
excluded int // files whose //go:build admits none of the four: never compiled
|
||||||
otherPort int // files named for another Go port: never attempted
|
otherPort int // files named for another Go port: never attempted
|
||||||
full int // files that assembled for every target architecture
|
full int // files that assembled for every applicable target architecture
|
||||||
targets []corpusTarget
|
targets []corpusTarget
|
||||||
tallies []*corpusTally
|
tallies []*corpusTally
|
||||||
}
|
}
|
||||||
@@ -573,6 +576,57 @@ func otherGOOSFile(path string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildConstraint returns the file's leading //go:build expression, or nil
|
||||||
|
// when the file carries none. The constraint governs the same header block
|
||||||
|
// go/build reads: blank lines and comments may precede it, and the first
|
||||||
|
// line that is neither ends the block. A constraint that does not parse
|
||||||
|
// narrows nothing, so the file stays in the attempted set: the audit must
|
||||||
|
// never exclude a file the toolchain would compile.
|
||||||
|
func buildConstraint(src string) constraint.Expr {
|
||||||
|
for line := range strings.SplitSeq(src, "\n") {
|
||||||
|
t := strings.TrimSpace(line)
|
||||||
|
switch {
|
||||||
|
case t == "":
|
||||||
|
continue
|
||||||
|
case strings.HasPrefix(t, "//"):
|
||||||
|
if constraint.IsGoBuild(t) {
|
||||||
|
e, err := constraint.Parse(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unixOS is go/build's unixOS set: the GOOSes the unix build tag admits.
|
||||||
|
var unixOS = map[string]bool{
|
||||||
|
"aix": true, "android": true, "darwin": true, "dragonfly": true,
|
||||||
|
"freebsd": true, "hurd": true, "illumos": true, "ios": true,
|
||||||
|
"linux": true, "netbsd": true, "openbsd": true, "solaris": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// constraintTags answers the build tags a plain `go build` sets for a
|
||||||
|
// target: the GOOS and GOARCH, gc, and unix on the unix-like GOOSes. No
|
||||||
|
// experiment, sanitiser or cgo tag is ever true: the audit models the
|
||||||
|
// default build, and no GOROOT assembly file's constraint hinges on cgo.
|
||||||
|
func constraintTags(goarch, goos string) func(string) bool {
|
||||||
|
return func(tag string) bool {
|
||||||
|
switch tag {
|
||||||
|
case goarch, goos, "gc":
|
||||||
|
return true
|
||||||
|
case "unix":
|
||||||
|
return unixOS[goos]
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
||||||
files, err := asmFiles(root)
|
files, err := asmFiles(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -590,8 +644,8 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
|||||||
tallies[i] = &corpusTally{reasons: map[string]int{}, example: map[string]string{}}
|
tallies[i] = &corpusTally{reasons: map[string]int{}, example: map[string]string{}}
|
||||||
}
|
}
|
||||||
// full is the north-star number: a file counts when every architecture
|
// full is the north-star number: a file counts when every architecture
|
||||||
// its name allows assembles it.
|
// its build admits assembles it.
|
||||||
full, generic, otherPort := 0, 0, 0
|
full, generic, otherPort, narrowedCount, excluded := 0, 0, 0, 0, 0
|
||||||
|
|
||||||
// Header generation is created on first use, so a corpus with no
|
// Header generation is created on first use, so a corpus with no
|
||||||
// go_asm.h includes never pays for a temp directory.
|
// go_asm.h includes never pays for a temp directory.
|
||||||
@@ -614,28 +668,78 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
|||||||
// invisible to a file-name rule).
|
// invisible to a file-name rule).
|
||||||
goos := goosFromFilename(path)
|
goos := goosFromFilename(path)
|
||||||
|
|
||||||
|
// The GOOS the header generation type-checks under follows the
|
||||||
|
// file's name when the name carries one; the ambient GOOS is the
|
||||||
|
// honest guess otherwise.
|
||||||
|
namedArch := arch.FromFilename(path)
|
||||||
var wanted []int // indexes into targets
|
var wanted []int // indexes into targets
|
||||||
if a := arch.FromFilename(path); a != arch.Unknown {
|
other := false
|
||||||
|
switch {
|
||||||
|
case namedArch != arch.Unknown:
|
||||||
for i, tg := range targets {
|
for i, tg := range targets {
|
||||||
if tg.a == a {
|
if tg.a == namedArch {
|
||||||
wanted = append(wanted, i)
|
wanted = append(wanted, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if otherPortFile(path) {
|
case otherPortFile(path):
|
||||||
// A file named for a Go port gasm does not support (arm,
|
// A file named for a Go port gasm does not support (arm,
|
||||||
// 386, s390x, ...) or for another GOOS is compiled by no
|
// 386, s390x, ...) or for another GOOS is compiled by no
|
||||||
// supported-arch build, so it is neither generic nor a
|
// supported-arch build, so it is neither generic nor a
|
||||||
// per-arch attempt: counting it as generic would make the
|
// per-arch attempt: counting it as generic would make the
|
||||||
// headline unreachably low for reasons no supported target
|
// headline unreachably low for reasons no supported target
|
||||||
// can fix.
|
// can fix.
|
||||||
|
other = true
|
||||||
otherPort++
|
otherPort++
|
||||||
} else {
|
default:
|
||||||
generic++
|
|
||||||
for i := range targets {
|
for i := range targets {
|
||||||
wanted = append(wanted, i)
|
wanted = append(wanted, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A //go:build constraint narrows the set of targets the file is
|
||||||
|
// assembled for, the way the go command compiles the file only for
|
||||||
|
// the targets the expression admits: cpu_x86.s belongs to the x86
|
||||||
|
// build alone, and a file whose constraint admits none of the four
|
||||||
|
// targets (the goexperiment.runtimesecret and msan trees) is
|
||||||
|
// compiled by no supported build. The tags mirror what a plain
|
||||||
|
// `go build` sets: the GOOS and GOARCH, gc, and unix on the
|
||||||
|
// unix-like GOOSes; no experiment, sanitiser or cgo tag is ever
|
||||||
|
// true. The GOOS is the file's own when the name carries one,
|
||||||
|
// else the ambient one.
|
||||||
|
goosForEval := goos
|
||||||
|
if goosForEval == "" {
|
||||||
|
goosForEval = runtime.GOOS
|
||||||
|
}
|
||||||
|
narrowed := false
|
||||||
|
if len(wanted) > 0 {
|
||||||
|
if ce := buildConstraint(src); ce != nil {
|
||||||
|
kept := make([]int, 0, len(wanted))
|
||||||
|
for _, i := range wanted {
|
||||||
|
tg := targets[i]
|
||||||
|
if ce.Eval(constraintTags(goarchName(tg.a), goosForEval)) {
|
||||||
|
kept = append(kept, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(kept) < len(wanted) {
|
||||||
|
narrowed = true
|
||||||
|
}
|
||||||
|
wanted = kept
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case other:
|
||||||
|
// already tallied above
|
||||||
|
case len(wanted) == 0:
|
||||||
|
excluded++
|
||||||
|
case namedArch != arch.Unknown:
|
||||||
|
// a per-arch attempt over the constraint's subset
|
||||||
|
case narrowed:
|
||||||
|
narrowedCount++
|
||||||
|
default:
|
||||||
|
generic++
|
||||||
|
}
|
||||||
|
|
||||||
// A file that includes go_asm.h parses against a per-target header:
|
// A file that includes go_asm.h parses against a per-target header:
|
||||||
// the defines differ per architecture (internal/cpu's layout, for
|
// the defines differ per architecture (internal/cpu's layout, for
|
||||||
// one) and per GOOS (sys_darwin_arm64.s's trampoline constants,
|
// one) and per GOOS (sys_darwin_arm64.s's trampoline constants,
|
||||||
@@ -719,6 +823,8 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
|||||||
root: root,
|
root: root,
|
||||||
files: len(files),
|
files: len(files),
|
||||||
generic: generic,
|
generic: generic,
|
||||||
|
narrowed: narrowedCount,
|
||||||
|
excluded: excluded,
|
||||||
otherPort: otherPort,
|
otherPort: otherPort,
|
||||||
full: full,
|
full: full,
|
||||||
targets: targets,
|
targets: targets,
|
||||||
@@ -728,13 +834,15 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
|||||||
|
|
||||||
// printCorpusStats renders the corpus audit report.
|
// printCorpusStats renders the corpus audit report.
|
||||||
func printCorpusStats(s *corpusStats, list bool) {
|
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 narrowed by //go:build; %d excluded by //go:build; %d named for other Go ports, never attempted)\n",
|
||||||
|
s.root, s.files, s.generic, s.narrowed, s.excluded, 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 and the ones no supported target compiles sit in
|
||||||
// assemble, so counting them in the denominator would report the gap
|
// the count for completeness but can never assemble, so counting them
|
||||||
// of architectures gasm deliberately does not target.
|
// in the denominator would report the gap of platforms gasm
|
||||||
attemptable := max(s.files-s.otherPort, 1)
|
// deliberately does not target.
|
||||||
fmt.Printf(" assemble for every target architecture: %d of %d attemptable (%.1f%%)\n", s.full, attemptable, 100*float64(s.full)/float64(attemptable))
|
attemptable := max(s.files-s.otherPort-s.excluded, 1)
|
||||||
|
fmt.Printf(" assemble for every applicable target: %d of %d attemptable (%.1f%%)\n", s.full, attemptable, 100*float64(s.full)/float64(attemptable))
|
||||||
for i, tg := range s.targets {
|
for i, tg := range s.targets {
|
||||||
t := s.tallies[i]
|
t := s.tallies[i]
|
||||||
fmt.Printf(" %s: %d/%d attempted\n", tg.name, t.assembled, t.attempted)
|
fmt.Printf(" %s: %d/%d attempted\n", tg.name, t.assembled, t.attempted)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
||||||
@@ -64,3 +65,62 @@ func TestGasmEncodable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBuildConstraint pins the //go:build reader: the constraint governs the
|
||||||
|
// leading comment block, the first non-comment line ends it (a tag below a
|
||||||
|
// #include governs nothing, exactly as go/build drops it), and a file
|
||||||
|
// without one admits every target.
|
||||||
|
func TestBuildConstraint(t *testing.T) {
|
||||||
|
admits := func(src, goarch, goos string) bool {
|
||||||
|
t.Helper()
|
||||||
|
e := buildConstraint(src)
|
||||||
|
if e == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return e.Eval(constraintTags(goarch, goos))
|
||||||
|
}
|
||||||
|
const ret = "TEXT \xc2\xb7f(SB), NOSPLIT, $0\n\tRET\n"
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
amd64, arm64 bool
|
||||||
|
}{
|
||||||
|
{"no constraint", ret, true, true},
|
||||||
|
{"x86 only", "//go:build 386 || amd64\n\n" + ret, true, false},
|
||||||
|
{"arm64 and linux", "//go:build arm64 && linux\n\n" + ret, false, true},
|
||||||
|
{"msan never", "//go:build msan\n\n" + ret, false, false},
|
||||||
|
{"experiment never", "//go:build goexperiment.runtimesecret\n\n" + ret, false, false},
|
||||||
|
{"below an include governs nothing", "#include \"textflag.h\"\n//go:build amd64\n" + ret, true, true},
|
||||||
|
{"unparsable narrows nothing", "//go:build (amd64\n" + ret, true, true},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
if got := admits(c.src, "amd64", runtime.GOOS); got != c.amd64 {
|
||||||
|
t.Errorf("amd64 admission = %v, want %v", got, c.amd64)
|
||||||
|
}
|
||||||
|
if got := admits(c.src, "arm64", runtime.GOOS); got != c.arm64 {
|
||||||
|
t.Errorf("arm64 admission = %v, want %v", got, c.arm64)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConstraintTags pins the tag set a plain `go build` sets: the GOOS and
|
||||||
|
// GOARCH, gc, unix on the unix-like GOOSes; nothing else is ever true.
|
||||||
|
func TestConstraintTags(t *testing.T) {
|
||||||
|
ok := constraintTags("amd64", "linux")
|
||||||
|
for _, tag := range []string{"amd64", "linux", "gc", "unix"} {
|
||||||
|
if !ok(tag) {
|
||||||
|
t.Errorf("tag %q = false, want true", tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tag := range []string{"arm64", "freebsd", "darwin", "cgo", "race", "msan", "goexperiment.runtimesecret"} {
|
||||||
|
if ok(tag) {
|
||||||
|
t.Errorf("tag %q = true, want false", tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fb := constraintTags("arm64", "freebsd")
|
||||||
|
if !fb("unix") {
|
||||||
|
t.Error("unix on freebsd = false, want true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user