diff --git a/builder/build.go b/builder/build.go index 64a1fde61d..57b67ed455 100644 --- a/builder/build.go +++ b/builder/build.go @@ -61,6 +61,10 @@ type BuildResult struct { // correctly printing test results: the import path isn't always the same as // the path listed on the command line. ImportPath string + + // Map from path to package name. It is needed to attribute binary size to + // the right Go package. + PackagePathMap map[string]string } // packageAction is the struct that is serialized to JSON and hashed, to work as @@ -242,6 +246,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return result, err } + // Store which filesystem paths map to which package name. + result.PackagePathMap = make(map[string]string, len(lprogram.Packages)) + for _, pkg := range lprogram.Sorted() { + result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() + } + // Create the *ssa.Program. This does not yet build the entire SSA of the // program so it's pretty fast and doesn't need to be parallelized. program := lprogram.LoadSSA() @@ -916,11 +926,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Print code size if requested. if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { - packagePathMap := make(map[string]string, len(lprogram.Packages)) - for _, pkg := range lprogram.Sorted() { - packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() - } - sizes, err := loadProgramSize(result.Executable, packagePathMap) + sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) if err != nil { return err } diff --git a/builder/sizes_test.go b/builder/sizes_test.go index bda5e07602..a1be28f274 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -1,6 +1,7 @@ package builder import ( + "regexp" "runtime" "testing" "time" @@ -55,26 +56,7 @@ func TestBinarySize(t *testing.T) { t.Parallel() // Build the binary. - options := compileopts.Options{ - Target: tc.target, - Opt: "z", - Semaphore: sema, - InterpTimeout: 60 * time.Second, - Debug: true, - VerifyIR: true, - } - target, err := compileopts.LoadTarget(&options) - if err != nil { - t.Fatal("could not load target:", err) - } - config := &compileopts.Config{ - Options: &options, - Target: target, - } - result, err := Build(tc.path, "", t.TempDir(), config) - if err != nil { - t.Fatal("could not build:", err) - } + result := buildBinary(t, tc.target, tc.path) // Check whether the size of the binary matches the expected size. sizes, err := loadProgramSize(result.Executable, nil) @@ -90,3 +72,69 @@ func TestBinarySize(t *testing.T) { }) } } + +// Check that the -size=full flag attributes binary size to the correct package +// without filesystem paths and things like that. +func TestSizeFull(t *testing.T) { + tests := []string{ + "microbit", + "wasip1", + } + + libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector" + pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task" + + for _, target := range tests { + target := target + t.Run(target, func(t *testing.T) { + t.Parallel() + + // Build the binary. + result := buildBinary(t, target, "examples/serial") + + // Check whether the binary doesn't contain any unexpected package + // names. + sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) + if err != nil { + t.Fatal("could not read program size:", err) + } + for _, pkg := range sizes.sortedPackageNames() { + if pkg == "(padding)" || pkg == "(unknown)" { + // TODO: correctly attribute all unknown binary size. + continue + } + if libMatch.MatchString(pkg) { + continue + } + if pkgMatch.MatchString(pkg) { + continue + } + t.Error("unexpected package name in size output:", pkg) + } + }) + } +} + +func buildBinary(t *testing.T, targetString, pkgName string) BuildResult { + options := compileopts.Options{ + Target: targetString, + Opt: "z", + Semaphore: sema, + InterpTimeout: 60 * time.Second, + Debug: true, + VerifyIR: true, + } + target, err := compileopts.LoadTarget(&options) + if err != nil { + t.Fatal("could not load target:", err) + } + config := &compileopts.Config{ + Options: &options, + Target: target, + } + result, err := Build(pkgName, "", t.TempDir(), config) + if err != nil { + t.Fatal("could not build:", err) + } + return result +}