diff --git a/Makefile b/Makefile index 48fb8db7..9346e33f 100644 --- a/Makefile +++ b/Makefile @@ -9,16 +9,13 @@ TARGET ?= unix ifeq ($(TARGET),unix) # Regular *nix system. -SIZE = size else ifeq ($(TARGET),pca10040) # PCA10040: nRF52832 development board -SIZE = arm-none-eabi-size OBJCOPY = arm-none-eabi-objcopy TGOFLAGS += -target $(TARGET) else ifeq ($(TARGET),arduino) -SIZE = avr-size OBJCOPY = avr-objcopy TGOFLAGS += -target $(TARGET) @@ -68,13 +65,11 @@ build/tgo: *.go # Binary that can run on the host. build/%: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go - ./build/tgo build $(TGOFLAGS) -o $@ $(subst src/,,$<) - @$(SIZE) $@ + ./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<) # ELF file that can run on a microcontroller. build/%.elf: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go - ./build/tgo build $(TGOFLAGS) -o $@ $(subst src/,,$<) - @$(SIZE) $@ + ./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<) # Convert executable to Intel hex file (for flashing). build/%.hex: build/%.elf diff --git a/binutils.go b/binutils.go new file mode 100644 index 00000000..bb83c872 --- /dev/null +++ b/binutils.go @@ -0,0 +1,160 @@ +package main + +import ( + "debug/elf" + "sort" + "strings" +) + +// Statistics about code size in a program. +type ProgramSize struct { + Packages map[string]*PackageSize + Sum *PackageSize + Code uint64 + Data uint64 + BSS uint64 +} + +// Return the list of package names (ProgramSize.Packages) sorted +// alphabetically. +func (ps *ProgramSize) SortedPackageNames() []string { + names := make([]string, 0, len(ps.Packages)) + for name := range ps.Packages { + names = append(names, name) + } + sort.Strings(names) + return names +} + +// The size of a package, calculated from the linked object file. +type PackageSize struct { + Code uint64 + ROData uint64 + Data uint64 + BSS uint64 +} + +// Flash usage in regular microcontrollers. +func (ps *PackageSize) Flash() uint64 { + return ps.Code + ps.ROData + ps.Data +} + +// Static RAM usage in regular microcontrollers. +func (ps *PackageSize) RAM() uint64 { + return ps.Data + ps.BSS +} + +type symbolList []elf.Symbol + +func (l symbolList) Len() int { + return len(l) +} + +func (l symbolList) Less(i, j int) bool { + bind_i := elf.ST_BIND(l[i].Info) + bind_j := elf.ST_BIND(l[j].Info) + if l[i].Value == l[j].Value && bind_i != elf.STB_WEAK && bind_j == elf.STB_WEAK { + // sort weak symbols after non-weak symbols + return true + } + return l[i].Value < l[j].Value +} + +func (l symbolList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +// Calculate program/data size breakdown of each package for a given ELF file. +func Sizes(path string) (*ProgramSize, error) { + file, err := elf.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + var sumCode uint64 + var sumData uint64 + var sumBSS uint64 + for _, section := range file.Sections { + if section.Flags&elf.SHF_ALLOC == 0 { + continue + } + if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS { + continue + } + if section.Type == elf.SHT_NOBITS { + sumBSS += section.Size + } else if section.Flags&elf.SHF_EXECINSTR != 0 { + sumCode += section.Size + } else if section.Flags&elf.SHF_WRITE != 0 { + sumData += section.Size + } + } + + allSymbols, err := file.Symbols() + if err != nil { + return nil, err + } + symbols := make([]elf.Symbol, 0, len(allSymbols)) + for _, symbol := range allSymbols { + symType := elf.ST_TYPE(symbol.Info) + if symbol.Size == 0 { + continue + } + if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE { + continue + } + if symbol.Section >= elf.SectionIndex(len(file.Sections)) { + continue + } + section := file.Sections[symbol.Section] + if section.Flags&elf.SHF_ALLOC == 0 { + continue + } + symbols = append(symbols, symbol) + } + sort.Sort(symbolList(symbols)) + + sizes := map[string]*PackageSize{} + var lastSymbolValue uint64 + for _, symbol := range symbols { + symType := elf.ST_TYPE(symbol.Info) + //bind := elf.ST_BIND(symbol.Info) + section := file.Sections[symbol.Section] + pkgName := "(bootstrap)" + symName := strings.TrimLeft(symbol.Name, "(*") + dot := strings.IndexByte(symName, '.') + if dot > 0 { + pkgName = symName[:dot] + } + pkgSize := sizes[pkgName] + if pkgSize == nil { + pkgSize = &PackageSize{} + sizes[pkgName] = pkgSize + } + if lastSymbolValue != symbol.Value || lastSymbolValue == 0 { + if symType == elf.STT_FUNC { + pkgSize.Code += symbol.Size + } else if section.Flags&elf.SHF_WRITE != 0 { + if section.Type == elf.SHT_NOBITS { + pkgSize.BSS += symbol.Size + } else { + pkgSize.Data += symbol.Size + } + } else { + pkgSize.ROData += symbol.Size + } + } + lastSymbolValue = symbol.Value + } + + sum := &PackageSize{} + for _, pkg := range sizes { + sum.Code += pkg.Code + sum.ROData += pkg.ROData + sum.Data += pkg.Data + sum.BSS += pkg.BSS + } + + return &ProgramSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil +} diff --git a/main.go b/main.go index 4312ea4a..79dd340b 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ import ( ) // Helper function for Compiler object. -func Compile(pkgName, outpath string, spec *TargetSpec, printIR, dumpSSA bool, action func(string) error) error { +func Compile(pkgName, outpath string, spec *TargetSpec, printIR, dumpSSA bool, printSizes string, action func(string) error) error { c, err := NewCompiler(pkgName, spec.Triple, dumpSSA) if err != nil { return err @@ -97,6 +97,25 @@ func Compile(pkgName, outpath string, spec *TargetSpec, printIR, dumpSSA bool, a return err } + if printSizes == "short" || printSizes == "full" { + sizes, err := Sizes(executable) + if err != nil { + return err + } + if printSizes == "short" { + fmt.Printf(" code data bss | flash ram\n") + fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) + } else { + fmt.Printf(" code rodata data bss | flash ram | package\n") + for _, name := range sizes.SortedPackageNames() { + pkgSize := sizes.Packages[name] + fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name) + } + fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM()) + fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS) + } + } + if strings.HasSuffix(outpath, ".hex") { // Get an Intel .hex file from the .elf file. tmppath = filepath.Join(dir, "main.hex") @@ -112,13 +131,13 @@ func Compile(pkgName, outpath string, spec *TargetSpec, printIR, dumpSSA bool, a } } -func Build(pkgName, outpath, target string, printIR, dumpSSA bool) error { +func Build(pkgName, outpath, target string, printIR, dumpSSA bool, printSizes string) error { spec, err := LoadTarget(target) if err != nil { return err } - return Compile(pkgName, outpath, spec, printIR, dumpSSA, func(tmppath string) error { + return Compile(pkgName, outpath, spec, printIR, dumpSSA, printSizes, func(tmppath string) error { if err := os.Rename(tmppath, outpath); err != nil { // Moving failed. Do a file copy. inf, err := os.Open(tmppath) @@ -146,13 +165,13 @@ func Build(pkgName, outpath, target string, printIR, dumpSSA bool) error { }) } -func Flash(pkgName, target, port string, printIR, dumpSSA bool) error { +func Flash(pkgName, target, port string, printIR, dumpSSA bool, printSizes string) error { spec, err := LoadTarget(target) if err != nil { return err } - return Compile(pkgName, ".hex", spec, printIR, dumpSSA, func(tmppath string) error { + return Compile(pkgName, ".hex", spec, printIR, dumpSSA, printSizes, func(tmppath string) error { // Create the command. flashCmd := spec.Flasher parts := strings.Split(flashCmd, " ") // TODO: this should be a real shell split @@ -216,6 +235,7 @@ func main() { printIR := flag.Bool("printir", false, "print LLVM IR") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") target := flag.String("target", llvm.DefaultTargetTriple(), "LLVM target") + printSize := flag.String("size", "", "print sizes (none, short, full)") port := flag.String("port", "/dev/ttyACM0", "flash port") if len(os.Args) < 2 { @@ -241,7 +261,7 @@ func main() { usage() os.Exit(1) } - err := Build(flag.Arg(0), *outpath, *target, *printIR, *dumpSSA) + err := Build(flag.Arg(0), *outpath, *target, *printIR, *dumpSSA, *printSize) if err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) @@ -252,7 +272,7 @@ func main() { usage() os.Exit(1) } - err := Flash(flag.Arg(0), *target, *port, *printIR, *dumpSSA) + err := Flash(flag.Arg(0), *target, *port, *printIR, *dumpSSA, *printSize) if err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1)